轉自:http://blog.chinaunix.net/uid-25968088-id-3426026.html
目錄
OPEN系統調用過程
Open在內核里面的入口函數時sys_open
Sys_open函數內容
do_sys_open(AT_FDCWD, filename, flags, mode)
1. 找到一個本進程沒有使用的文件描述符fd(int型)
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. 找到一個本進程沒有使用的文件描述符fd(int型)
(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源碼