Linux 中open系統調用實現原理【轉】


轉自:http://blog.chinaunix.net/uid-25968088-id-3426026.html

目錄

OPEN系統調用過程

Open在內核里面的入口函數時sys_open

Sys_open函數內容

do_sys_open(AT_FDCWD, filename, flags, mode)

1.      找到一個本進程沒有使用的文件描述符fdint型)

2.      分配一個全新的struct file結構體

3.      根據傳人的pathname查找或建立對應的dentry

4.      建立fd到這個struct file結構體的聯系

 

WORD里面的目錄復制過來似乎不能直接用。。還是放在這里當主線看吧..

用戶空間的Open函數在內核里面的入口函數是sys_open

    通過grep open /usr/include/asm/unistd_64.h查找到的

#define __NR_open                               2

__SYSCALL(__NR_open, sys_open)

    觀察unistd_64.h,我們可以猜測用戶空間open函數最終調用的系統調用號是2來發起的sys_open系統調用(畢竟glibc一般的做法都會做,用戶空間的函數名字和內核空間中調用的很像,如果需要得到非常准確的,請查看glibc源碼找到對應的系統調用號,再和內核里面的系統調用號去一一對比)。這里我們不糾結。

 

Sys_open函數內容

 

通過前面的Linux 編程中的API函數和系統調用的關系 我們得知,sys_open實際就是下面這個函數(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;

}

其中先調用force_o_largefile()來判斷是否需要設置大文件標識,然后調用do_sys_open來完成具體的工作。其中

force_o_largefile()

         在IA64系統中 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位數:

# 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架構,我們考慮的是x86架構),所以64位的系統上flags會自動加上O_LARGEFILE,32位的則沒有,所以文件最大大小受索引節點中表示文件大小的32位的i_size的影響,只能訪問2的32次方字節,即4GB(實際高位一般不用,所以通常只有2G)。加上O_LAGEFILE之后啟用索引節點的i_dir_acl字段也可以一起表示文件的大小了,這樣位數就變成了64位,2的64位就4GB*4GB,單個文件這么大已經很大了16T了

 

 

我們重點來看do_Sys_open函數

do_sys_open(AT_FDCWD, filename, flags, mode)

函數原型long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)。

這個函數這里(我們講述最主要的內容)執行過程

 

1.    找到一個本進程沒有使用的文件描述符fdint型)

 

(1)在當前進程打開的文件位圖表中,找到第一個為0的位,返回這個位在位圖表里面的下標,這個下標就是將用分配的未使用的文件描述符fd

 

(2)把當前進程的文件表擴展一個文件(即嘗試添加一個struct file到當前

進程的文件列表中),進程task_struct-> files_struct-> fd_array[NR_OPEN_DEFAULT]是一個struct file 數組,而NR_OPEN_DEFAULT在64位系統中等於64(因為一般進程打開的文件數大多都用這個數組就可以直接放下了),如果擴展操作導致當前進程的這個存放struct file的數組放不下了,如要裝第65個struct flie結構體,那么將重新分配一片內存區專門用來存放struct file結構體,並且這個內存區的大小為128個struct file結構體,然后將當前進程的task_struct->files_struct->fdtable->fd指針指向這片內存的首地址,然后把之前數組里面的內容復制到這片內存區里面來。下次添加如果超過了128個了,則分配256個大小直到256個也裝滿,超過256則分配512,依次類推,總是2的冪次方,且會把之前的復制到新分配的內存里面去

 

注意:這里只是更新了進程的這個file table,新的進程描述符對應的struct file還沒有生成進去。

 

(3)設置進程的文件位圖中新分配的這個文件描述符位為(1)中找到的下標,並更新下一次該分配的進程描述符起點

 

2.    分配一個全新的struct file結構體

Struct file =kmem_cache_zalloc(filp_cachep, GFP_KERNEL);

 

3.    根據傳人的pathname查找或建立對應的dentry

並設置此dentry對應的inode。內核做這個事情借助於一個nameidata數據結構

(1)    如果pathname中第一個字符是“/”,那么說明使用絕對路徑,設置nameidata為更目錄對應的dentry和當前目錄的inode,mount點等

 

(2)    如果不是“/”,則使用相對路徑,設置nameidata為當前目錄對應的dentry,inode,mount點等

 

(3)    一層一層往下查找,直到找到最終的那個文件或者目錄分量,注意:如/usr/bin/make,先找“/”(這是3.1就做了的),再找“/”下面的usr,再找bin,最后找make。

 

這里細說一下第一層怎么在“/“下面找到”usr“的:

第一層查找先找“/”下面的usr對應的dentry,內核通過“/”對應的dentry和usr字符串兩個參數進行hash運算獲取一個dentry的鏈表

然后逐個看這個鏈表里面有沒有parent dentry為“/”對應的dentry的,以及dentry對應的名字的hash值是否與“usr”對應的hash值相同

前面條件都滿足這里再看一下parent dentry是否有DCACHE_OP_COMPARE標識,如果有此標識且文件系統實現了dentry->dentry_operations->d_compare函數,那么就調用文件系統的這個函數來判斷

如果條件都符合,那么說明內存中usr對應的這個dentry是存在的,如果這個dentry->d_flags中包含DCACHE_OP_REVALIDATE,那么就會調用此dentry->dentry_operatoin->d_revalidate來進行一次核對(網絡文件系統此函數都實現了,以便於遠程的便跟,在這里會得到更新)

如果最終usr對應的dentry不存在,那么內核就在內存中直接分配一個dentry結構體並且把dentry的name和“usr”對應起來,並且設置這個dentry的parent為“/”對應的dentry,然后還要調用/對應的dentry->d_inode這個inode的inode_operation->lookup(/的inode,新建的dentry,flags),如果返回了新的dentry,那么就把dentry結構體指針指向新返回的dentry,否則還是返回剛剛新創建的那個dentry。(一般的文件系統都實現了inode_operation->lookup,我猜他們會在這個函數里面如果/usr存在則把dentry對應的inode給設置好。。如果/usr不存在則返回一個NULL之類的,以一個錯誤跳出整個路徑執行)

到這里,無論是dentry已經存在於內存中找到的,還是新創建的dentry,總之,對應於“usr”結構的dentry在內存中已經存在了。然后調用follow_managed()函數找到“usr”最新的vfsmount(這里有一點點麻煩,后續會專門講,這里只需要指定如果/dev/sda mount 到了/mnt,/dev/sdb 也mount到了/mnt,那么這里返回的是最新的這條/dev/sdb mount到/mnt這個vfsmount)。

然后把這個已經找到的或創建的dentry(已經存在於內存中的dentry已經有了inode和它綁定,新建立的dentry也通過inode_operation->lookup建立起來了inode和dentry的聯系(此函數會在操作真正的磁盤介質吧inode讀出來))和這個最新的vfsmount存到struct path中

然后把這個含有dentry,vfsmount的path結構體存入nameidata數據結構中,到這里,“usr“對應的dentry,inode,vfsmount都准備好了,且存到了nameidata中了

 

(4)    接着(3)里面,一層一層的往下找,依次會找到usr,bin,最后到了make

這里就不調用一層一層往下找的函數了,進入另外一個函數do_last()函數來

處理。在dolast,在dolast里面如果此dentry不存在則創建它,如果有O_CREATE

標識則創建這個文件的inode(這里會調用vfs_create函數,繼續調用dentry->inode_operation->create來建立inode,文件系統實現的此函數會操作正在的磁盤介質去創建inode),並且建立inode和dentry的聯系,並且建立make對應的vfsmount為最新的mount結構,至此,“/usr/bin/make”中最后一個分量“make”的dentry,inode,vfsmount都存到nameidata中去了。

接着還會把2中分配的file結構體的path(包含dentry和vfsmount)的dentry分量設置為nameidata的這個dentry(dentry結構體中已經有inode的指針),vfsmount也設置為nameidata的vfsmount,並且設置file結構體的file->f_mapppin為nameidata中dentry的inode的i_mapping.並且設置file->f_pos指針為0。

至此,make對應的新分配的這個struct file結構體中的dentry,inode,vfsmount都為nameidata中的了,並且struct file映射到內存的address_space也設置為了inode對應的address_space,struct file的當前位置指針設置為了0,“make”分量的這個struct file結構體准備好了。

接着還會把這個struct file結構體加入其inode對應的super_block超級塊的s_files鏈表中,即struct file結構體會加入其自身inode所在超級塊的所有文件鏈表中。

並且如果自身inode的file_operations不為空則還會設置這個struct file的file_operation等於inode的這個file_operations,即公用inode的file的操作方法。如果inode的file_operations沒有實現,則設置為空。

設置此文件標識符為FILE_OPENED.

 

4.    建立fd到這個struct file結構體的聯系

 

調用fd_install(fd,f)來把1中分配的文件描述符和3中的struct file建立聯系。

過程簡單描述一下,先獲取當前進程的fdtable(簡單可理解為進程的關聯的所有文件數組)的所有文件數組fdtable=current->files->fdt,(current為當前進程task_struct),設置fdtable->fd[fd]=file,(下標fd即新分配的文件描述符,file即為3中創建的struct file結構體)。

這樣,進程和文件描述符,struct file,dentry,inode,vfsmount就全部關聯起來了。

 

附圖片:完整的內核調用我畫的visio學習圖,歡迎糾正理解,圖片需要放很大才能看清。。
。。汗。。圖片有4M多,上傳不了。。

參考:kernel 3.6.7源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM