Linux的open函数的调用过程,Linux 中open系统调用实现原理
用戶空間的函數(shù)在內(nèi)核里面的入口函數(shù)是sys_open
通過(guò)grep open /usr/include/asm/unistd_64.h查找到的
#define __NR_open2
__SYSCALL(__NR_open, sys_open)
觀察unistd_64.h,我們可以猜測(cè)用戶空間open函數(shù)最終調(diào)用的系統(tǒng)調(diào)用號(hào)是2來(lái)發(fā)起的sys_open系統(tǒng)調(diào)用(畢竟glibc一般的做法都會(huì)做,用戶空間的函數(shù)名字和內(nèi)核空間中調(diào)用的很像,如果需要得到非常準(zhǔn)確的,請(qǐng)查看glibc源碼找到對(duì)應(yīng)的系統(tǒng)調(diào)用號(hào),再和內(nèi)核里面的系統(tǒng)調(diào)用號(hào)去一一對(duì)比)。這里我們不糾結(jié)。
函數(shù)內(nèi)容
通過(guò)前面的我們得知,sys_open實(shí)際就是下面這個(gè)函數(shù)(fs/open.c中)
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
其中先調(diào)用force_o_largefile()來(lái)判斷是否需要設(shè)置大文件標(biāo)識(shí),然后調(diào)用do_sys_open來(lái)完成具體的工作。其中
force_o_largefile()
在IA64系統(tǒng)中arch/ia64/include/asm/fcntl.h,定義如下
#define _ASM_IA64_FCNTL_H
#define force_o_largefile()(personality(current->personality) != PER_LINUX32)
#include
#endif /* _ASM_IA64_FCNTL_H */
而其余的在include/linux/fcntl.h中
#ifndef force_o_largefile
#define force_o_largefile() (BITS_PER_LONG != 32)
#endif
所以,在非32位的OS上,force_o_largefile()都為true,而32位的OS則為false
另外,我們可以查看我們的OS位數(shù):
# grep CONFIG_64BIT /boot/config-2.6.32-220.el6.x86_64
CONFIG_64BIT=y //64位
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */
所以:只有在32位的OS上此處才為false(這里不考慮IA64架構(gòu),我們考慮的是x86架構(gòu)),所以64位的系統(tǒng)上flags會(huì)自動(dòng)加上O_LARGEFILE,32位的則沒(méi)有,所以文件最大大小受索引節(jié)點(diǎn)中表示文件大小的32位的i_size的影響,只能訪問(wèn)2的32次方字節(jié),即4GB(實(shí)際高位一般不用,所以通常只有2G)。加上O_LAGEFILE之后啟用索引節(jié)點(diǎn)的i_dir_acl字段也可以一起表示文件的大小了,這樣位數(shù)就變成了64位,2的64位就4GB*4GB,單個(gè)文件這么大已經(jīng)很大了16T了
我們重點(diǎn)來(lái)看do_Sys_open函數(shù)do_sys_open(AT_FDCWD, filename, flags, mode)
函數(shù)原型long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)。
這個(gè)函數(shù)這里(我們講述最主要的內(nèi)容)執(zhí)行過(guò)程
fd(int型)
(1)在當(dāng)前進(jìn)程打開的文件位圖表中,找到第一個(gè)為0的位,返回這個(gè)位在位圖表里面的下標(biāo),這個(gè)下標(biāo)就是將用分配的未使用的文件描述符fd
(2)把當(dāng)前進(jìn)程的文件表擴(kuò)展一個(gè)文件(即嘗試添加一個(gè)struct file到當(dāng)前
進(jìn)程的文件列表中),進(jìn)程task_struct->files_struct->fd_array[NR_OPEN_DEFAULT]是一個(gè)struct file 數(shù)組,而NR_OPEN_DEFAULT在64位系統(tǒng)中等于64(因?yàn)橐话氵M(jìn)程打開的文件數(shù)大多都用這個(gè)數(shù)組就可以直接放下了),如果擴(kuò)展操作導(dǎo)致當(dāng)前進(jìn)程的這個(gè)存放struct file的數(shù)組放不下了,如要裝第65個(gè)struct flie結(jié)構(gòu)體,那么將重新分配一片內(nèi)存區(qū)專門用來(lái)存放struct file結(jié)構(gòu)體,并且這個(gè)內(nèi)存區(qū)的大小為128個(gè)struct file結(jié)構(gòu)體,然后將當(dāng)前進(jìn)程的task_struct->files_struct->fdtable->fd指針指向這片內(nèi)存的首地址,然后把之前數(shù)組里面的內(nèi)容復(fù)制到這片內(nèi)存區(qū)里面來(lái)。下次添加如果超過(guò)了128個(gè)了,則分配256個(gè)大小直到256個(gè)也裝滿,超過(guò)256則分配512,依次類推,總是2的冪次方,且會(huì)把之前的復(fù)制到新分配的內(nèi)存里面去
注意:這里只是更新了進(jìn)程的這個(gè)file table,新的進(jìn)程描述符對(duì)應(yīng)的struct file還沒(méi)有生成進(jìn)去。
(3)設(shè)置進(jìn)程的文件位圖中新分配的這個(gè)文件描述符位為(1)中找到的下標(biāo),并更新下一次該分配的進(jìn)程描述符起點(diǎn)
struct file結(jié)構(gòu)體
Struct file =kmem_cache_zalloc(filp_cachep, GFP_KERNEL);
pathname查找或建立對(duì)應(yīng)的dentry
并設(shè)置此dentry對(duì)應(yīng)的inode。內(nèi)核做這個(gè)事情借助于一個(gè)nameidata數(shù)據(jù)結(jié)構(gòu)
(1)如果pathname中第一個(gè)字符是“/”,那么說(shuō)明使用絕對(duì)路徑,設(shè)置nameidata為更目錄對(duì)應(yīng)的dentry和當(dāng)前目錄的inode,mount點(diǎn)等
(2)如果不是“/”,則使用相對(duì)路徑,設(shè)置nameidata為當(dāng)前目錄對(duì)應(yīng)的dentry,inode,mount點(diǎn)等
(3)一層一層往下查找,直到找到最終的那個(gè)文件或者目錄分量,注意:如”/usr/bin/make”,先找“/”(這是3.1就做了的),再找“/”下面的usr,再找bin,最后找make。
這里細(xì)說(shuō)一下第一層怎么在“/“下面找到”usr“的:
第一層查找先找“/”下面的usr對(duì)應(yīng)的dentry,內(nèi)核通過(guò)“/”對(duì)應(yīng)的dentry和”usr”字符串兩個(gè)參數(shù)進(jìn)行hash運(yùn)算獲取一個(gè)dentry的鏈表
然后逐個(gè)看這個(gè)鏈表里面有沒(méi)有parent dentry為“/”對(duì)應(yīng)的dentry的,以及dentry對(duì)應(yīng)的名字的hash值是否與“usr”對(duì)應(yīng)的hash值相同
前面條件都滿足這里再看一下parent dentry是否有DCACHE_OP_COMPARE標(biāo)識(shí),如果有此標(biāo)識(shí)且文件系統(tǒng)實(shí)現(xiàn)了dentry->dentry_operations->d_compare函數(shù),那么就調(diào)用文件系統(tǒng)的這個(gè)函數(shù)來(lái)判斷
如果條件都符合,那么說(shuō)明內(nèi)存中usr對(duì)應(yīng)的這個(gè)dentry是存在的,如果這個(gè)dentry->d_flags中包含DCACHE_OP_REVALIDATE,那么就會(huì)調(diào)用此dentry->dentry_operatoin->d_revalidate來(lái)進(jìn)行一次核對(duì)(網(wǎng)絡(luò)文件系統(tǒng)此函數(shù)都實(shí)現(xiàn)了,以便于遠(yuǎn)程的便跟,在這里會(huì)得到更新)
如果最終usr對(duì)應(yīng)的dentry不存在,那么內(nèi)核就在內(nèi)存中直接分配一個(gè)dentry結(jié)構(gòu)體并且把dentry的name和“usr”對(duì)應(yīng)起來(lái),并且設(shè)置這個(gè)dentry的parent為“/”對(duì)應(yīng)的dentry,然后還要調(diào)用”/”對(duì)應(yīng)的dentry->d_inode這個(gè)inode的inode_operation->lookup(“/”的inode,新建的dentry,flags),如果返回了新的dentry,那么就把dentry結(jié)構(gòu)體指針指向新返回的dentry,否則還是返回剛剛新創(chuàng)建的那個(gè)dentry。(一般的文件系統(tǒng)都實(shí)現(xiàn)了inode_operation->lookup,我猜他們會(huì)在這個(gè)函數(shù)里面如果/usr存在則把dentry對(duì)應(yīng)的inode給設(shè)置好。。如果/usr不存在則返回一個(gè)NULL之類的,以一個(gè)錯(cuò)誤跳出整個(gè)路徑執(zhí)行)
到這里,無(wú)論是dentry已經(jīng)存在于內(nèi)存中找到的,還是新創(chuàng)建的dentry,總之,對(duì)應(yīng)于“usr”結(jié)構(gòu)的dentry在內(nèi)存中已經(jīng)存在了。然后調(diào)用follow_managed()函數(shù)找到“usr”最新的vfsmount(這里有一點(diǎn)點(diǎn)麻煩,后續(xù)會(huì)專門講,這里只需要指定如果”/dev/sda”mount 到了/mnt,/dev/sdb 也mount到了/mnt,那么這里返回的是最新的這條/dev/sdb mount到/mnt這個(gè)vfsmount)。
然后把這個(gè)已經(jīng)找到的或創(chuàng)建的dentry(已經(jīng)存在于內(nèi)存中的dentry已經(jīng)有了inode和它綁定,新建立的dentry也通過(guò)inode_operation->lookup建立起來(lái)了inode和dentry的聯(lián)系(此函數(shù)會(huì)在操作真正的磁盤介質(zhì)吧inode讀出來(lái)))和這個(gè)最新的vfsmount存到struct path中
然后把這個(gè)含有dentry,vfsmount的path結(jié)構(gòu)體存入nameidata數(shù)據(jù)結(jié)構(gòu)中,到這里,“usr“對(duì)應(yīng)的dentry,inode,vfsmount都準(zhǔn)備好了,且存到了nameidata中了
(4)接著(3)里面,一層一層的往下找,依次會(huì)找到usr,bin,最后到了”make”
這里就不調(diào)用一層一層往下找的函數(shù)了,進(jìn)入另外一個(gè)函數(shù)do_last()函數(shù)來(lái)
處理。在dolast,在dolast里面如果此dentry不存在則創(chuàng)建它,如果有O_CREATE
標(biāo)識(shí)則創(chuàng)建這個(gè)文件的inode(這里會(huì)調(diào)用vfs_create函數(shù),繼續(xù)調(diào)用dentry->inode_operation->create來(lái)建立inode,文件系統(tǒng)實(shí)現(xiàn)的此函數(shù)會(huì)操作正在的磁盤介質(zhì)去創(chuàng)建inode),并且建立inode和dentry的聯(lián)系,并且建立”make”對(duì)應(yīng)的vfsmount為最新的mount結(jié)構(gòu),至此,“/usr/bin/make”中最后一個(gè)分量“make”的dentry,inode,vfsmount都存到nameidata中去了。
接著還會(huì)把2中分配的file結(jié)構(gòu)體的path(包含dentry和vfsmount)的dentry分量設(shè)置為nameidata的這個(gè)dentry(dentry結(jié)構(gòu)體中已經(jīng)有inode的指針),vfsmount也設(shè)置為nameidata的vfsmount,并且設(shè)置file結(jié)構(gòu)體的file->f_mapppin為nameidata中dentry的inode的i_mapping.并且設(shè)置file->f_pos指針為0。
至此,make對(duì)應(yīng)的新分配的這個(gè)struct file結(jié)構(gòu)體中的dentry,inode,vfsmount都為nameidata中的了,并且struct file映射到內(nèi)存的address_space也設(shè)置為了inode對(duì)應(yīng)的address_space,struct file的當(dāng)前位置指針設(shè)置為了0,“make”分量的這個(gè)struct file結(jié)構(gòu)體準(zhǔn)備好了。
接著還會(huì)把這個(gè)struct file結(jié)構(gòu)體加入其inode對(duì)應(yīng)的super_block超級(jí)塊的s_files鏈表中,即struct file結(jié)構(gòu)體會(huì)加入其自身inode所在超級(jí)塊的所有文件鏈表中。
并且如果自身inode的file_operations不為空則還會(huì)設(shè)置這個(gè)struct file的file_operation等于inode的這個(gè)file_operations,即公用inode的file的操作方法。如果inode的file_operations沒(méi)有實(shí)現(xiàn),則設(shè)置為空。
設(shè)置此文件標(biāo)識(shí)符為FILE_OPENED.
fd到這個(gè)struct file結(jié)構(gòu)體的聯(lián)系
調(diào)用fd_install(fd,f)來(lái)把1中分配的文件描述符和3中的struct file建立聯(lián)系。
過(guò)程簡(jiǎn)單描述一下,先獲取當(dāng)前進(jìn)程的fdtable(簡(jiǎn)單可理解為進(jìn)程的關(guān)聯(lián)的所有文件數(shù)組)的所有文件數(shù)組fdtable=current->files->fdt,(current為當(dāng)前進(jìn)程task_struct),設(shè)置fdtable->fd[fd]=file,(下標(biāo)fd即新分配的文件描述符,file即為3中創(chuàng)建的struct file結(jié)構(gòu)體)。
這樣,進(jìn)程和文件描述符,struct file,dentry,inode,vfsmount就全部關(guān)聯(lián)起來(lái)了。
附圖片:完整的內(nèi)核調(diào)用我畫的visio學(xué)習(xí)圖,歡迎糾正理解,圖片需要放很大才能看清。。
。。汗。。圖片有4M多,上傳不了。。
參考:kernel 3.6.7源碼
總結(jié)
以上是生活随笔為你收集整理的Linux的open函数的调用过程,Linux 中open系统调用实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 工行小额费3元怎么回事?
- 下一篇: linux 卸载nfs device i