Open()函數的內核追蹤


Open()函數的內核追蹤node

open函數相信你們都用過,這裏就很少說它的使用方法等事項,現直接進入正題...linux


用戶態程序調用open函數時,會產生一箇中斷號爲5的中斷請求,其值以該宏__NR__open進行標示.然後該進程上下文(process context)將會被切換到內核空間。待內核中的相關操做完成後,就會從內核返回,此時還須要一次進程上下文切換(process contextswitch)數組


待進程執行流進入內核後,會經過一系列轉換(這裏咱們不關心),最終進入SYSCALL_DEFINE3(open,...)函數中。看起來該函數定義比較特殊,其實SYSCALL_DRFINE3是一個宏,它被定義成以下形式:ide

#defineSYSCALL_DEFINE3(name,...)SYSCALL_DEFINEx(3,_##name,__VA_ARGS__)函數


SYSCALL_DEFINEx宏具備以下形式:ui


#ifdefCONFIG_FTRACE_SYSCALLS
#defineSYSCALL_DEFINEx(x,sname,...) \
staticconstchar*types_##sname[]={ \
__SC_STR_TDECL##x(__VA_ARGS__) \
}; \
staticconstchar*args_##sname[]={ \
__SC_STR_ADECL##x(__VA_ARGS__) \
}; \
SYSCALL_METADATA(sname,x); \
__SYSCALL_DEFINEx(x,sname,__VA_ARGS__)
#else
#defineSYSCALL_DEFINEx(x,sname,...) \
__SYSCALL_DEFINEx(x,sname,__VA_ARGS__)
#endifspa


能夠看到,不管是何種形式的宏定義,最終都會進入__SYSCALL_DEFINEx中,而__SYSCALL_DEFINEx的定義以下:.net


#ifdefCONFIG_HAVE_SYSCALL_WRAPPERS

#defineSYSCALL_DEFINE(name)staticinlinelongSYSC_##name

#define__SYSCALL_DEFINEx(x,name,...) \
asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__)); \
staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__)); \
asmlinkagelongSyS##name(__SC_LONG##x(__VA_ARGS__)) \
{ \
__SC_TEST##x(__VA_ARGS__); \
return(long)SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
} \
SYSCALL_ALIAS(sys##name,SyS##name); \
staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__))

#else/* CONFIG_HAVE_SYSCALL_WRAPPERS */

#defineSYSCALL_DEFINE(name)asmlinkagelongsys_##name
#define__SYSCALL_DEFINEx(x,name,...) \
asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__))

#endif/* CONFIG_HAVE_SYSCALL_WRAPPERS */指針


通過該宏的替換做用之後,最終咱們就會獲得sys_openSYS_open所對應的函數原型。以下:blog


asmlinkage long sys_open(const char __user filename,intflags,intmode);


這也就是咱們最多見到的open函數所對應的在內核中的實現部份。其實,對於linux下全部的系統調用函數,採用上述方法都可找到與其對應的內核函數sys_xxx().


接下來咱們來看sys_open()函數。其實現以下:


SYSCALL_DEFINE3(open,const char __user*,filename,int,flags,int,mode)
{
longret;

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);
returnret;
}


在函數中首先調用force_o_largefile()宏進行LARGEFILE?確認。如果LARGEFILE則將其在flags中置位。隨後調用主處理函數do_sys_open進行後續處理。其實,open的工做也就是在該函數中進行的。該函數原型以下:


longdo_sys_open(intdfd,constchar__user*filename,intflags,intmode);


在該函數中,若是經過getname()獲得filename變量中文件名的指針沒有錯誤的話,接下來就會掉用get_unused_fd_flags()函數得到一個沒被使用的文件描述符fd。注意,對於文件描述符fd來說,它只對本進程有效,也即它只在該進程中可見而在其它進程中表明着徹底不一樣的文件。在32位系統中,一個進程最多打開32個文件,而在64位系統中能夠打開64個文件。該函數就是用來得到一個未被使用的文件描述符fd.至於它的獲取過程是很複雜的,這裏不進行講述。有興趣的話,能夠到www.kernel.org下載最新的內核源碼進行研究。


在得到了有效的fd以後,咱們經過do_filp_open()函數打開或者建立相應的文件,而且返回與之對應的文件結構struct file *f。若是函數返回的結構地址有效的話,那麼就會調用fsnotify_open()函數(參數爲f)將該文件加入到文件監控的系統中。該系統是用來監控文件被打開,建立,讀寫,關閉,修改等操做的,具體工做原理見後面文章。本文中不作講述。隨後調用fd_install()函數將struct file *f加入到fd索引位置處的數組中。若是後續過程當中,有對該文件描述符的操做的話,就會經過查找該數組獲得對應的文件結構,然後在進行相關操做。完成這些工做以後,open函數就返回了。返回值也就是剛纔獲得的fd.


那麼,在do_filp_open()函數中有具體作了哪些工做呢?文件是如何被建立的呢?以及文件若存在的話,又是怎樣被找到,然後被打開的呢?在中篇中咱們將會回答這些問題.


接着上篇咱們來回答文章最後提出的問題:在do_filp_open()函數中有具體作了哪些工做呢?文件是如何被建立的呢?以及文件若存在的話,又是怎樣被找到,然後被打開的呢?下面咱們來回答這些問題。


能夠推斷,在do_filp_open函數作了open函數的所有工做,包括建立,打開等等。該函數的原型是這樣的:


structfile*do_filp_open(intdfd,constchar*pathname,intopen_flag,intmode,intacc_mode);


須要說明的是該函數參數列表中的open_flag的低兩位的含義和該函數內部的flag變量中的是不一樣的。具體的區別以下:

open_flag參數(其實它就是sys_open函數中的flag參數)中的低兩位具備以下含義時:

00-read-only
01-write-only
10-read-only
11-special


它們將會經過選擇性的+1操做轉變成具備以下含義的值,並存入本地變量flag中:


00-no permissions needed
01-read-permission
10-write-permission
11-read-write

好了,如今咱們來看do_filp_open()函數的實現。細心的朋友會發現,在函數的開始會進行一系列flagmode標誌位的檢查,這裏咱們不關心。以後就會調用path_init()函數進行後續操做前的初始化工做。path_init()函數主要是爲了填充nd(nd是一個指向struct nameidata結構的指針)結構。


在函數path_init內部,會判斷*pathname是否是字符'/',如果則經過以下代碼設置nd->rootnd->path,該root便是指向current->fs->root的指針.


set_root(nd);
nd->path=nd->root;
path_get(&nd->root);


若上述字符不是'/',則在檢查dfd參數,若是該值爲AT_FDCWD,那麼咱們就調用get_fs_pwd()函數將nd->path設置成current->fs->pwd指針所指向的當前工做目錄。這裏須要說明一下AT_FDCWD宏的含義。該宏的值是-100,它主要是用來指示openat應使用當前工做目錄。

若是以上判斷都不爲真的話,那麼,此時會調用fget_light()函數來得到dfd所對應的file結構(struct file *)。這裏的dfdopen函數返回的fd,也就是上篇中分配的fd是否是具用相同的含義,這裏還不得而知。我的感受好像是由VFS分配或查找以存在的文件時獲得的。而且若不是create文件的話,它們應該具用相同的含義,不然不相同。這裏指示猜想,還沒深究。但願能有高人先來告訴鄙人一下,或是一塊兒發貼來討論一下。我會盡快來澄清這個問題的。


上面經過fget_light函數獲得的file須要經過S_ISDIR(file->f_path.dentry->d_inode->i_mode)來檢查這個已經存在的inode是否是一個目錄,如果則一切OK。不然fail。如果目錄的話,接下來調用file_permission(file,MAY_EXEC)來判斷該文件的執行權限。如果具備執行權限,那麼此時也應具備read權限,若全OK的話,審查就算是經過了。以後,經過以下語句設置nd->path,並將其引用計數加一。


nd->path=file->f_path;
path_get(&file->f_path);


記住,還沒完呢,必定要調用fput_light()fget_light()返回的file指針空間釋放掉。同時,實參中的fput_needed要和fget_light()函數返回的值相同。不然,後果...本身去體驗一下就知道了...


好了,到這裏咱們的前期初始化工做就完成了。下一步經過調用link_path_walk(pathname,&nd)函數進行文件名解析。其實它是一個很基本的文件名字解析函數,用於將pathname轉換成最終的dentry,並存儲於nd->path.dentry中。注意這裏返回的dentry實際上是其父結點相應信息。不理解的話,繼續往下看。待該函數返回時,若是沒有錯誤,那麼以後就能夠經過get_enpty_filp()basename(pathname)申請filp(類型爲:struct file *)指針了。若申請成功,則將filp放入nd.intent.open.file域中,同時初始化filpnd.intent.open指針結構所指的實例中的f_flagsflagscreate_mode等域。


接下來就剩open的最實質性的工做了。它是經過do_last函數來完成的。


do_last函數的原型以下(位於文件fs/namei.c)

staticstructfile*do_last(structnameidata*nd,strcut path*path,intopen_flag,intacc_mode,intmode,const char*pathname)


函數中首先會根據open_flag中的標誌位判斷該文件是否是須要被建立,若否,那麼會進行最後階段的inode節點的查找,若是此時查找成功,這直接調用path_to_nameidate()函數經過以下語句設置nd->path.dentry的值:


nd->path.dentry=path->dentry


若沒有查找成功而且flag中有沒有將O_CREAT標誌爲置位。那麼,此時經過將error設置成-ENOTDIR並向用戶態返回NODIR的出錯提示。


可是,若是用戶在使用open函數時,使用了create選項的話,那麼咱們只好進行下面的操做了:建立新的inode節點。這項工做是經過__open_namei_create()函數完成的。在該函數內部,會調用VFS層的vfs_create()函數將控制下發到不一樣的文件系統處理函數中。它是經過這樣的調用過成完成的:


error=dir->i_op->create(dir,dentry,mode,nd)


若是當前使用的是ext4文件系統的話,那麼create指針指向的是ext4_create,不然就有多是ext3_create等等。具體的還要視具體狀況而定。到了這裏咱們也能夠初步的瞭解VFS的做用了吧。就是將更低層的不一樣文件系統類型做統一管理,爲上層的使用提供統一的函數接口,這爲其做用之一。欲想知道create函數指針是如何被初始化的,請看或查閱內核模塊加載的過程。這裏再也不詳述。inode被成功建立後,其相關的建立時間,訪問時間,修改時間,giduideuid以及訪問權限等一些屬性信息就會被正確的初始化。該過程結束後,咱們還會調用fsnotifyhook函數fsnotify_create將新建文件節點的事件提交到監控系統。待完成後,操做流就會調用nameidata_to_filp(nd)進行nd->path.dentry的設置,並調用__denry_open()函數作打開文件的工做。其實在該函數仍然是VFS層函數,作實質工做的函數是經過f->f_op->open方式進行相應文件系統處理函數的調用的。open的初始化,請看create函數的過程實現。待這些操做完成後,控制將會返回到do_sys_open中。


另外,接前文若是文件已經存在,那好,餘下的就是一點點收尾工做,在函數finish_open()中調用nameidata_to_filp()函數將文件打開。以後一樣,控制返回到do_sys_open()中。


好了,如今咱們已經回到了最初的地方,dentry找到了,inode生成了,filp肯定了,接下來就是filpfd的綁定了。


它是如何完成的呢。說明這個以前,咱們先看如下fsnotify_open()函數,它的做用是將filp的監控點打開,並將其添加到監控系統中。


完成filpfd綁定功能的函數是fd_install()。它的功能就是將filp添加到fdt->fd指定的數組空間,索引是fd的位置中去。其中struct fdtable *fdt是經過以下方式獲得的:


structfiles_struct*file=current->files;
structfdtable*fdt;
spin_lock(&files->file_lock);
fdt=files_fdtable(files);
rcu_assign_pointer(fdt->fd[fd],filp);
spin_unlock(&files->file_lock)


從上面的程序片斷中能夠看到,每一個運行的進程都有屬於本身的文件描述符表,由指針current->files指示。


上述操做都完成後,那麼空間也將會隨即返回到用戶空間了,返回值固然就是fd。這就是咱們傳遞給readwritefcntlfioctl等函數的文件描述符。至此,文件的打開操做就所有完成了。

相關文章
相關標籤/搜索