linux核心檔案IO的系統呼叫實現分析(open)
http://blog.chinaunix.net/uid-23969156-id-3086824.html
閱讀(349) | 評論(0) | 轉發(2) |
<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
1. 引言
從事Linux環境工作2年有餘,一直懵懵懂懂,1年前拜讀了《萊昂氏UNIX原始碼分析》一書,感覺自己的學習道路漫漫且修遠。最近受chinaunix的精華文帖啟發,擬將近來的部分核心呼叫分析筆記拿出來與各前輩先進共同探討學習,以壯個人學習之路。
本部分主要講述的是檔案I/O操作的2.6.11核心版本實現,包括了主要的資料結構、巨集定義和函式流程。以下分別講述open,create,close,read,write,lseek系統呼叫。
2. 主要參考
《萊昂氏UNIX原始碼分析》
《UNIX環境高階程式設計》
www.kernel.org
3. 主要資料結構
3.1. FD
對於核心而言,所有開啟檔案都由檔案描述符引用。檔案描述符是一個非負整數。當開啟一個現存檔案或建立一個新檔案時,核心向程式返回一個檔案描述符。
當讀、寫一個檔案時,用open或creat返回的檔案描述符fd標識該檔案,將其作為引數傳送給read或write。在POSIX.1應用程式中,檔案描述符為常數0、1和2分別代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即標準輸入,標準輸出和標準出錯輸出,這些常數都定義在標頭檔案;中。
檔案描述符的範圍是0~OPEN_MAX,在目前常用的linux系統中,是32位整形所能表示的整數,即65535,64位機上則更多。
3.2. File
struct file {
struct list_head f_list; //檔案連結串列指標
struct dentry *f_dentry; // 檔案對應的目錄結構
struct vfsmount *f_vfsmnt; // 虛擬檔案系統掛載點
struct file_operations *f_op; // 檔案操作函式指標
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode; // 檔案模式
int f_error;
loff_t f_pos; // 檔案offset
struct fown_struct f_owner; //檔案owner 結構
unsigned int f_uid, f_gid;
struct file_ra_state f_ra; // 跟蹤上次檔案操作狀態的結構指標
size_t f_maxcount; // 檔案大小
unsigned long f_version;
void *f_security; // hook 檔案操作的security結構指標
void *private_data; // tty 驅動器所需資料
#ifdef CONFIG_EPOLL
struct list_head f_ep_links; // EPOLL 機制檢測所需連結串列結構
spinlock_t f_ep_lock; // 相容早期gcc bug 的標誌
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; // 地址對映表
}
3.3. File_struct
File_struct結構儲存了程式開啟的所有檔案表資料。
struct files_struct {
atomic_t count; // 自動增量
spinlock_t file_lock; // 低位成員保護標識
int max_fds; // 最大檔案控制程式碼數目
int max_fdset; // 最大的fd集合容量
int next_fd; // 下一個空閒fd
struct file ** fd; // 當前fd對應的檔案結構指標列表
fd_set *close_on_exec; // 可執行close的fd集合
fd_set *open_fds; // 開啟的fd集合
fd_set close_on_exec_init; //
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; // 預設開啟的fd佇列
};
4. open 函式
4.1. 原型與引數
int open(const char * pathname, int oflag, .../*, mode_t mode * / ) -1代表錯誤。
這裡的oflag是一個整形,主要供open 函式使用,部分fcntl函式也會使用。詳細的說明請用 man 2 open就可以看到了。以下列出了2.6核心定義的open和fcntl函式所使用的flag巨集定義,說明的格式如巨集定義名稱<實際常數值>;: 描述。
O_ACCMODE <0003>;: 讀寫檔案操作時,用於取出flag的低2位。
O_RDONLY<00>;: 只讀開啟
O_WRONLY<01>;: 只寫開啟
O_RDWR<02>;: 讀寫開啟
O_CREAT<0100>;: 檔案不存在則建立,需要mode_t,not fcntl
O_EXCL<0200>;: 如果同時指定了O_CREAT,而檔案已經存在,則出錯, not fcntl
O_NOCTTY<0400>;: 如果pathname指終端裝置,則不將此裝置分配作為此程式的控制終端。O_TRUNC<01000>;: 如果此檔案存在,而且為只讀或只寫成功開啟,則將其長度截短為0。 O_APPEND<02000>;: 每次寫時都加到檔案的尾端
O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一個F I F O、一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I / O操作設定非阻塞方式。
O_NDELAY;;
O_SYNC<010000>;: 使每次write都等到物理I/O操作完成。
FASYNC<020000>;: 相容BSD的fcntl同步操作
O_DIRECT<040000>;: 直接磁碟操作標識
O_LARGEFILE<0100000>;: 大檔案標識
O_DIRECTORY<0200000>;: 必須是目錄
O_NOFOLLOW<0400000>;: 不獲取連線檔案
O_NOATIME<01000000>;: 暫無
當新建立一個檔案時,需要指定mode 引數,以下說明的格式如巨集定義名稱<實際常數值>;: 描述。
S_IRWXU<00700>;:檔案擁有者有讀寫執行許可權
S_IRUSR (S_IREAD)<00400>;:檔案擁有者僅有讀許可權
S_IWUSR (S_IWRITE)<00200>;:檔案擁有者僅有寫許可權
S_IXUSR (S_IEXEC)<00100>;:檔案擁有者僅有執行許可權
S_IRWXG<00070>;:組使用者有讀寫執行許可權
S_IRGRP<00040>;:組使用者僅有讀許可權
S_IWGRP<00020>;:組使用者僅有寫許可權
S_IXGRP<00010>;:組使用者僅有執行許可權
S_IRWXO<00007>;:其他使用者有讀寫執行許可權
S_IROTH<00004>;:其他使用者僅有讀許可權
S_IWOTH<00002>;:其他使用者僅有寫許可權
S_IXOTH<00001>;:其他使用者僅有執行許可權
4.2. 實現分析
4.2.1. 主要函式呼叫關係圖
sys_open( 見4.2.2 節)
| ----------- getname( 見4.2.3 節 )
| ----------- filp_open( 見4.2.4節 )
| | ------------ open_namei( 見4.2.4.1節 )
| | | ----------- may_open
| | ------------ dentry_open( 見4.2.4.2節 )
4.2.2. 主呼叫函式sys_open,函式定義在/fs/open.c檔案
asmlinkage long sys_open(const char __user * filename, int flags, int mode){
char * tmp;
int fd, error;
// 如果不是32位處理器,則增加大檔案標識
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
// 為了提高使用效率,在使用之前先將檔名拷貝到核心資料區。見3.2.2說明
tmp = getname(filename);
// 獲取到返回值,如果出錯,則返回,否則執行開啟操作。
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
// 從程式的檔案表中找出一個空閒的檔案表指標,如果出錯,則返回
fd = get_unused_fd();
if (fd >= 0) {
// 執行開啟操作。見3.2.3說明
struct file *f = filp_open(tmp, flags, mode);
// 獲取返回結果,如果出錯,則跳轉至out_error,否則執行fd_install
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
// 新增開啟的檔案表 f 到當前程式的檔案表佇列中。見3.2.4說明
fd_install(fd, f);
}
out:
// 釋放getname分配的記憶體空間
putname(tmp);
}
return fd;
out_error:
// 將檔案表指標返回到程式的檔案表中,並返回錯誤程式碼。
put_unused_fd(fd);
fd = error;
goto out;
}
從事Linux環境工作2年有餘,一直懵懵懂懂,1年前拜讀了《萊昂氏UNIX原始碼分析》一書,感覺自己的學習道路漫漫且修遠。最近受chinaunix的精華文帖啟發,擬將近來的部分核心呼叫分析筆記拿出來與各前輩先進共同探討學習,以壯個人學習之路。
本部分主要講述的是檔案I/O操作的2.6.11核心版本實現,包括了主要的資料結構、巨集定義和函式流程。以下分別講述open,create,close,read,write,lseek系統呼叫。
2. 主要參考
《萊昂氏UNIX原始碼分析》
《UNIX環境高階程式設計》
www.kernel.org
3. 主要資料結構
3.1. FD
對於核心而言,所有開啟檔案都由檔案描述符引用。檔案描述符是一個非負整數。當開啟一個現存檔案或建立一個新檔案時,核心向程式返回一個檔案描述符。
當讀、寫一個檔案時,用open或creat返回的檔案描述符fd標識該檔案,將其作為引數傳送給read或write。在POSIX.1應用程式中,檔案描述符為常數0、1和2分別代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即標準輸入,標準輸出和標準出錯輸出,這些常數都定義在標頭檔案;中。
檔案描述符的範圍是0~OPEN_MAX,在目前常用的linux系統中,是32位整形所能表示的整數,即65535,64位機上則更多。
3.2. File
struct file {
struct list_head f_list; //檔案連結串列指標
struct dentry *f_dentry; // 檔案對應的目錄結構
struct vfsmount *f_vfsmnt; // 虛擬檔案系統掛載點
struct file_operations *f_op; // 檔案操作函式指標
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode; // 檔案模式
int f_error;
loff_t f_pos; // 檔案offset
struct fown_struct f_owner; //檔案owner 結構
unsigned int f_uid, f_gid;
struct file_ra_state f_ra; // 跟蹤上次檔案操作狀態的結構指標
size_t f_maxcount; // 檔案大小
unsigned long f_version;
void *f_security; // hook 檔案操作的security結構指標
void *private_data; // tty 驅動器所需資料
#ifdef CONFIG_EPOLL
struct list_head f_ep_links; // EPOLL 機制檢測所需連結串列結構
spinlock_t f_ep_lock; // 相容早期gcc bug 的標誌
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; // 地址對映表
}
3.3. File_struct
File_struct結構儲存了程式開啟的所有檔案表資料。
struct files_struct {
atomic_t count; // 自動增量
spinlock_t file_lock; // 低位成員保護標識
int max_fds; // 最大檔案控制程式碼數目
int max_fdset; // 最大的fd集合容量
int next_fd; // 下一個空閒fd
struct file ** fd; // 當前fd對應的檔案結構指標列表
fd_set *close_on_exec; // 可執行close的fd集合
fd_set *open_fds; // 開啟的fd集合
fd_set close_on_exec_init; //
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; // 預設開啟的fd佇列
};
4. open 函式
4.1. 原型與引數
int open(const char * pathname, int oflag, .../*, mode_t mode * / ) -1代表錯誤。
這裡的oflag是一個整形,主要供open 函式使用,部分fcntl函式也會使用。詳細的說明請用 man 2 open就可以看到了。以下列出了2.6核心定義的open和fcntl函式所使用的flag巨集定義,說明的格式如巨集定義名稱<實際常數值>;: 描述。
O_ACCMODE <0003>;: 讀寫檔案操作時,用於取出flag的低2位。
O_RDONLY<00>;: 只讀開啟
O_WRONLY<01>;: 只寫開啟
O_RDWR<02>;: 讀寫開啟
O_CREAT<0100>;: 檔案不存在則建立,需要mode_t,not fcntl
O_EXCL<0200>;: 如果同時指定了O_CREAT,而檔案已經存在,則出錯, not fcntl
O_NOCTTY<0400>;: 如果pathname指終端裝置,則不將此裝置分配作為此程式的控制終端。O_TRUNC<01000>;: 如果此檔案存在,而且為只讀或只寫成功開啟,則將其長度截短為0。 O_APPEND<02000>;: 每次寫時都加到檔案的尾端
O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一個F I F O、一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I / O操作設定非阻塞方式。
O_NDELAY;;
O_SYNC<010000>;: 使每次write都等到物理I/O操作完成。
FASYNC<020000>;: 相容BSD的fcntl同步操作
O_DIRECT<040000>;: 直接磁碟操作標識
O_LARGEFILE<0100000>;: 大檔案標識
O_DIRECTORY<0200000>;: 必須是目錄
O_NOFOLLOW<0400000>;: 不獲取連線檔案
O_NOATIME<01000000>;: 暫無
當新建立一個檔案時,需要指定mode 引數,以下說明的格式如巨集定義名稱<實際常數值>;: 描述。
S_IRWXU<00700>;:檔案擁有者有讀寫執行許可權
S_IRUSR (S_IREAD)<00400>;:檔案擁有者僅有讀許可權
S_IWUSR (S_IWRITE)<00200>;:檔案擁有者僅有寫許可權
S_IXUSR (S_IEXEC)<00100>;:檔案擁有者僅有執行許可權
S_IRWXG<00070>;:組使用者有讀寫執行許可權
S_IRGRP<00040>;:組使用者僅有讀許可權
S_IWGRP<00020>;:組使用者僅有寫許可權
S_IXGRP<00010>;:組使用者僅有執行許可權
S_IRWXO<00007>;:其他使用者有讀寫執行許可權
S_IROTH<00004>;:其他使用者僅有讀許可權
S_IWOTH<00002>;:其他使用者僅有寫許可權
S_IXOTH<00001>;:其他使用者僅有執行許可權
4.2. 實現分析
4.2.1. 主要函式呼叫關係圖
sys_open( 見4.2.2 節)
| ----------- getname( 見4.2.3 節 )
| ----------- filp_open( 見4.2.4節 )
| | ------------ open_namei( 見4.2.4.1節 )
| | | ----------- may_open
| | ------------ dentry_open( 見4.2.4.2節 )
4.2.2. 主呼叫函式sys_open,函式定義在/fs/open.c檔案
asmlinkage long sys_open(const char __user * filename, int flags, int mode){
char * tmp;
int fd, error;
// 如果不是32位處理器,則增加大檔案標識
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
// 為了提高使用效率,在使用之前先將檔名拷貝到核心資料區。見3.2.2說明
tmp = getname(filename);
// 獲取到返回值,如果出錯,則返回,否則執行開啟操作。
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
// 從程式的檔案表中找出一個空閒的檔案表指標,如果出錯,則返回
fd = get_unused_fd();
if (fd >= 0) {
// 執行開啟操作。見3.2.3說明
struct file *f = filp_open(tmp, flags, mode);
// 獲取返回結果,如果出錯,則跳轉至out_error,否則執行fd_install
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
// 新增開啟的檔案表 f 到當前程式的檔案表佇列中。見3.2.4說明
fd_install(fd, f);
}
out:
// 釋放getname分配的記憶體空間
putname(tmp);
}
return fd;
out_error:
// 將檔案表指標返回到程式的檔案表中,並返回錯誤程式碼。
put_unused_fd(fd);
fd = error;
goto out;
}
4.2.3. sys_open子函式getname
getname函式主要功能是在使用檔名之前將其拷貝到核心資料區,正常結束時返回核心分配的空間首地址,出錯時返回錯誤程式碼。其呼叫了函式do_getname來實現。
static inline int do_getname(const char __user *filename, char *page){
int retval;
unsigned long len = PATH_MAX; // 核心允許的最大路徑長度
// 如果程式的地址限制是否和KERNEL_DS相等,則檢查檔名是否小於使用者程式空間
if (!segment_eq(get_fs(), KERNEL_DS)) {
// 檔名地址大於使用者程式空間,則返回錯誤-EFAULT
if ((unsigned long) filename >= TASK_SIZE)
return -EFAULT;
// 獲取較小的地址長度
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename;
}
// 將filename拷貝len長度到page中,返回實際拷貝長度
retval = strncpy_from_user(page, filename, len);
if (retval >0) {
// 如果retval大於等於len,則返回-ENAMETOOLONG
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
// filename 為空,則返回-ENOENT
retval = -ENOENT;
return retval;
}
char * getname(const char __user * filename){
char *tmp, *result;
result = ERR_PTR(-ENOMEM);
// 從核心快取中分配空間,如果成功,則執行do_getname
tmp = __getname();
if (tmp) {
// 執行檔名拷貝操作
int retval = do_getname(filename, tmp);
result = tmp;
if (retval < 0) {
// do_getname出錯,則釋放空間,並返回錯誤程式碼
__putname(tmp);
result = ERR_PTR(retval);
}
}
// 如果前面操作成功,且audit_context不為空,則將當前檔名新增到audit列表中
if (unlikely(current->;audit_context) && !IS_ERR(result) && result)
audit_getname(result);
// 返回處理結果
return result;
}
4.2.4. sys_open子函式filp_open
getname函式主要功能是在使用檔名之前將其拷貝到核心資料區,正常結束時返回核心分配的空間首地址,出錯時返回錯誤程式碼。其呼叫了函式do_getname來實現。
static inline int do_getname(const char __user *filename, char *page){
int retval;
unsigned long len = PATH_MAX; // 核心允許的最大路徑長度
// 如果程式的地址限制是否和KERNEL_DS相等,則檢查檔名是否小於使用者程式空間
if (!segment_eq(get_fs(), KERNEL_DS)) {
// 檔名地址大於使用者程式空間,則返回錯誤-EFAULT
if ((unsigned long) filename >= TASK_SIZE)
return -EFAULT;
// 獲取較小的地址長度
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename;
}
// 將filename拷貝len長度到page中,返回實際拷貝長度
retval = strncpy_from_user(page, filename, len);
if (retval >0) {
// 如果retval大於等於len,則返回-ENAMETOOLONG
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
// filename 為空,則返回-ENOENT
retval = -ENOENT;
return retval;
}
char * getname(const char __user * filename){
char *tmp, *result;
result = ERR_PTR(-ENOMEM);
// 從核心快取中分配空間,如果成功,則執行do_getname
tmp = __getname();
if (tmp) {
// 執行檔名拷貝操作
int retval = do_getname(filename, tmp);
result = tmp;
if (retval < 0) {
// do_getname出錯,則釋放空間,並返回錯誤程式碼
__putname(tmp);
result = ERR_PTR(retval);
}
}
// 如果前面操作成功,且audit_context不為空,則將當前檔名新增到audit列表中
if (unlikely(current->;audit_context) && !IS_ERR(result) && result)
audit_getname(result);
// 返回處理結果
return result;
}
4.2.4. sys_open子函式filp_open
在較高版本的核心裡函式名改為do_filp_open,但是函式內容一樣。
這後面的函式使用了一個nameidata的資料結構來描述檔案相關的運算元據。
struct nameidata {
struct dentry *dentry; // 目錄資料
struct vfsmount *mnt; // 虛擬檔案掛載點資料
struct qstr last; // hash值
unsigned int flags; // 檔案操作標識
int last_type; // 型別
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
union {
struct open_intent open;
} intent; // 專用資料
};
struct file *filp_open(const char * filename, int flags, int mode){
int namei_flags, error;
struct nameidata nd;
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE)
namei_flags++; // 如果flags有O_WRONLY,則增加O_RDONLY
if (namei_flags & O_TRUNC)
namei_flags |= 2; // 如果有O_TRUNC,則增加O_RDWR
error = open_namei(filename, namei_flags, mode, &nd); // 如3.2.3.1 描述
if (!error)
return dentry_open(nd.dentry, nd.mnt, flags); // 如3.2.3.2描述
return ERR_PTR(error); // 返回錯誤程式碼
}
4.2.4.1. filp_open子函式open_namei
open_namei函式主要執行檔案操作的inode部分的開啟等操作。
int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd){
int acc_mode, error = 0;
struct dentry *dentry;
struct dentry *dir;
int count = 0;
acc_mode = ACC_MODE(flag); // 取出低2位操作標識
if (flag & O_APPEND) // 取出O_APPEND操作標識
acc_mode |= MAY_APPEND;
//賦值open函式的專用資料
nd->;intent.open.flags = flag;
nd->;intent.open.create_mode = mode;
// 如果不需要建立檔案,則在程式目錄檔案表搜尋已有檔案,並把結果拷貝到nd中
if (!(flag & O_CREAT)) {
error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);
if (error) // 錯誤程式碼有ENOENT,ENOTDIR,EAGAIN,ESTALE,
return error;
goto ok; // 否則執行開啟函式,更新inode資料
}
// 在程式檔案表中搜尋該檔案,如果不存在,則建立,結果由nd儲存
error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);
if (error)
return error;
// 檢測nd的結果是否是一個目錄檔案,是則返回
error = -EISDIR;
if (nd->;last_type != LAST_NORM || nd->;last.name[nd->;last.len])
goto exit;
// 獲取檔案的相關目錄資料,結果返回到dentry中。
dir = nd->;dentry;
nd->;flags &= ~LOOKUP_PARENT;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
do_last:
// 如果dentry是一個錯誤值,則返回
error = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
up(&dir->;d_inode->;i_sem);
goto exit;
}
// 如果dentry不存在,則建立他
if (!dentry->;d_inode) {
if (!IS_POSIXACL(dir->;d_inode))
mode &= ~current->;fs->;umask;
error = vfs_create(dir->;d_inode, dentry, mode, nd); // 建立inode
up(&dir->;d_inode->;i_sem);
dput(nd->;dentry);
nd->;dentry = dentry;
if (error)
goto exit;
acc_mode = 0;
flag &= ~O_TRUNC;
goto ok;
}
up(&dir->;d_inode->;i_sem);
error = -EEXIST; // 如果指定了O_EXCL和O_CREAT,檔案存在時,出錯
if (flag & O_EXCL)
goto exit_dput;
if (d_mountpoint(dentry)) { // 檢測檔案是否是連線檔案
error = -ELOOP;
if (flag & O_NOFOLLOW) // 如果指定不遍歷連線檔案,則返回
goto exit_dput;
// 檢測dentry掛載點
while (__follow_down(&nd->;mnt,&dentry) && d_mountpoint(dentry));
}
error = -ENOENT;
if (!dentry->;d_inode) // inode 不存在,則返回
goto exit_dput;
if (dentry->;d_inode->;i_op && dentry->;d_inode->;i_op->;follow_link)
goto do_link; // 允許遍歷連線檔案,則手工找到連線檔案對應的檔案
// 將處理後的dentry複製到nd結構中,並判斷其是否是目錄,是則返回錯誤
dput(nd->;dentry);
nd->;dentry = dentry;
error = -EISDIR;
if (dentry->;d_inode && S_ISDIR(dentry->;d_inode->;i_mode))
goto exit;
ok:
error = may_open(nd, acc_mode, flag); // 開啟檔案,返回處理結果程式碼。如3.2.3.1.1描述
if (error)
goto exit;
return 0;
exit_dput:
dput(dentry); // 釋放dentry
exit:
path_release(nd); // 釋放nd結構
return error; // 返回錯誤程式碼
do_link:
error = -ELOOP;
if (flag & O_NOFOLLOW)
goto exit_dput; // 不允許遍歷連線檔案,則返回錯誤
// 以下程式碼是手工找到連線檔案對應的檔案dentry資料
nd->;flags |= LOOKUP_PARENT;
error = security_inode_follow_link(dentry, nd);
if (error)
goto exit_dput;
error = __do_follow_link(dentry, nd);
dput(dentry);
if (error)
return error;
nd->;flags &= ~LOOKUP_PARENT;
if (nd->;last_type == LAST_BIND) {
dentry = nd->;dentry;
goto ok;
}
error = -EISDIR;
if (nd->;last_type != LAST_NORM)
goto exit;
if (nd->;last.name[nd->;last.len]) {
putname(nd->;last.name);
goto exit;
}
error = -ELOOP;
if (count++==32) {
putname(nd->;last.name);
goto exit;
}
dir = nd->;dentry;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
putname(nd->;last.name);
goto do_last;
}
4.2.4.1.1. filp_open子函式may_open
這後面的函式使用了一個nameidata的資料結構來描述檔案相關的運算元據。
struct nameidata {
struct dentry *dentry; // 目錄資料
struct vfsmount *mnt; // 虛擬檔案掛載點資料
struct qstr last; // hash值
unsigned int flags; // 檔案操作標識
int last_type; // 型別
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
union {
struct open_intent open;
} intent; // 專用資料
};
struct file *filp_open(const char * filename, int flags, int mode){
int namei_flags, error;
struct nameidata nd;
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE)
namei_flags++; // 如果flags有O_WRONLY,則增加O_RDONLY
if (namei_flags & O_TRUNC)
namei_flags |= 2; // 如果有O_TRUNC,則增加O_RDWR
error = open_namei(filename, namei_flags, mode, &nd); // 如3.2.3.1 描述
if (!error)
return dentry_open(nd.dentry, nd.mnt, flags); // 如3.2.3.2描述
return ERR_PTR(error); // 返回錯誤程式碼
}
4.2.4.1. filp_open子函式open_namei
open_namei函式主要執行檔案操作的inode部分的開啟等操作。
int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd){
int acc_mode, error = 0;
struct dentry *dentry;
struct dentry *dir;
int count = 0;
acc_mode = ACC_MODE(flag); // 取出低2位操作標識
if (flag & O_APPEND) // 取出O_APPEND操作標識
acc_mode |= MAY_APPEND;
//賦值open函式的專用資料
nd->;intent.open.flags = flag;
nd->;intent.open.create_mode = mode;
// 如果不需要建立檔案,則在程式目錄檔案表搜尋已有檔案,並把結果拷貝到nd中
if (!(flag & O_CREAT)) {
error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);
if (error) // 錯誤程式碼有ENOENT,ENOTDIR,EAGAIN,ESTALE,
return error;
goto ok; // 否則執行開啟函式,更新inode資料
}
// 在程式檔案表中搜尋該檔案,如果不存在,則建立,結果由nd儲存
error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);
if (error)
return error;
// 檢測nd的結果是否是一個目錄檔案,是則返回
error = -EISDIR;
if (nd->;last_type != LAST_NORM || nd->;last.name[nd->;last.len])
goto exit;
// 獲取檔案的相關目錄資料,結果返回到dentry中。
dir = nd->;dentry;
nd->;flags &= ~LOOKUP_PARENT;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
do_last:
// 如果dentry是一個錯誤值,則返回
error = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
up(&dir->;d_inode->;i_sem);
goto exit;
}
// 如果dentry不存在,則建立他
if (!dentry->;d_inode) {
if (!IS_POSIXACL(dir->;d_inode))
mode &= ~current->;fs->;umask;
error = vfs_create(dir->;d_inode, dentry, mode, nd); // 建立inode
up(&dir->;d_inode->;i_sem);
dput(nd->;dentry);
nd->;dentry = dentry;
if (error)
goto exit;
acc_mode = 0;
flag &= ~O_TRUNC;
goto ok;
}
up(&dir->;d_inode->;i_sem);
error = -EEXIST; // 如果指定了O_EXCL和O_CREAT,檔案存在時,出錯
if (flag & O_EXCL)
goto exit_dput;
if (d_mountpoint(dentry)) { // 檢測檔案是否是連線檔案
error = -ELOOP;
if (flag & O_NOFOLLOW) // 如果指定不遍歷連線檔案,則返回
goto exit_dput;
// 檢測dentry掛載點
while (__follow_down(&nd->;mnt,&dentry) && d_mountpoint(dentry));
}
error = -ENOENT;
if (!dentry->;d_inode) // inode 不存在,則返回
goto exit_dput;
if (dentry->;d_inode->;i_op && dentry->;d_inode->;i_op->;follow_link)
goto do_link; // 允許遍歷連線檔案,則手工找到連線檔案對應的檔案
// 將處理後的dentry複製到nd結構中,並判斷其是否是目錄,是則返回錯誤
dput(nd->;dentry);
nd->;dentry = dentry;
error = -EISDIR;
if (dentry->;d_inode && S_ISDIR(dentry->;d_inode->;i_mode))
goto exit;
ok:
error = may_open(nd, acc_mode, flag); // 開啟檔案,返回處理結果程式碼。如3.2.3.1.1描述
if (error)
goto exit;
return 0;
exit_dput:
dput(dentry); // 釋放dentry
exit:
path_release(nd); // 釋放nd結構
return error; // 返回錯誤程式碼
do_link:
error = -ELOOP;
if (flag & O_NOFOLLOW)
goto exit_dput; // 不允許遍歷連線檔案,則返回錯誤
// 以下程式碼是手工找到連線檔案對應的檔案dentry資料
nd->;flags |= LOOKUP_PARENT;
error = security_inode_follow_link(dentry, nd);
if (error)
goto exit_dput;
error = __do_follow_link(dentry, nd);
dput(dentry);
if (error)
return error;
nd->;flags &= ~LOOKUP_PARENT;
if (nd->;last_type == LAST_BIND) {
dentry = nd->;dentry;
goto ok;
}
error = -EISDIR;
if (nd->;last_type != LAST_NORM)
goto exit;
if (nd->;last.name[nd->;last.len]) {
putname(nd->;last.name);
goto exit;
}
error = -ELOOP;
if (count++==32) {
putname(nd->;last.name);
goto exit;
}
dir = nd->;dentry;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
putname(nd->;last.name);
goto do_last;
}
4.2.4.1.1. filp_open子函式may_open
在較高版本的核心裡作為open_namei的子函式。。。
may_open執行許可權檢測和檔案開啟,和truncate的操作。
int may_open(struct nameidata *nd, int acc_mode, int flag){
struct dentry *dentry = nd->;dentry;
struct inode *inode = dentry->;d_inode;
int error;
if (!inode) return -ENOENT; // inode為空,則返回錯誤
if (S_ISLNK(inode->;i_mode)) // 連線檔案,返回錯誤
return -ELOOP;
if (S_ISDIR(inode->;i_mode) && (flag & FMODE_WRITE))
return -EISDIR; // 是目錄且僅有寫許可權,返回錯誤
error = permission(inode, acc_mode, nd); // 見擦inode的accmode
if (error)
return error;
if (S_ISFIFO(inode->;i_mode) || S_ISSOCK(inode->;i_mode)) {
flag &= ~O_TRUNC; // 如果是FIFO檔案,則不允許truncate
} else if (S_ISBLK(inode->;i_mode) || S_ISCHR(inode->;i_mode)) {
if (nd->;mnt->;mnt_flags & MNT_NODEV)
return -EACCES; // 如果是裝置,則不允許truncate,否則返回錯誤
flag &= ~O_TRUNC;
} else if (IS_RDONLY(inode) && (flag & FMODE_WRITE))
return -EROFS; 如果flag標識和inode許可權衝突,則返回錯誤
// 如果inode只允許append方式寫入,則不允許truncate和非append寫入方式。
if (IS_APPEND(inode)) {
if ((flag & FMODE_WRITE) && !(flag & O_APPEND))
return -EPERM;
if (flag & O_TRUNC)
return -EPERM;
}
// O_NOATIME方式僅在inode使用者是檔案擁有者或者超級使用者情況下才被允許
if (flag & O_NOATIME)
if (current->;fsuid != inode->;i_uid && !capable(CAP_FOWNER))
return -EPERM;
// 檢查是否有其他程式在使用該檔案
error = break_lease(inode, flag);
if (error)
return error;
if (flag & O_TRUNC) {
error = get_write_access(inode); // 獲取一次inode寫操作許可權
if (error)
return error;
// 鎖定inode
error = locks_verify_locked(inode);
if (!error) {
DQUOT_INIT(inode); // 對inode執行配額初始化
error = do_truncate(dentry, 0); // truncate dentry
}
put_write_access(inode); // 釋放當前寫操作許可權
if (error)
return error;
} else
if (flag & FMODE_WRITE) // 如果有寫標識,則對inode執行配額初始化
DQUOT_INIT(inode);
return 0;
}
4.2.4.2. open_namei子函式dentry_open
dentry_open函式主要實現檔案表的對應開啟等操作,返回檔案指標。
struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags){
struct file * f;
struct inode *inode;
int error;
error = -ENFILE;
f = get_empty_filp(); // 從程式檔案表中獲取一個未使用的檔案結構指標,空則出錯返回
if (!f)
goto cleanup_dentry;
// 設定檔案的flags和mode標識
f->;f_flags = flags;
f->;f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
inode = dentry->;d_inode;
if (f->;f_mode & FMODE_WRITE) {
error = get_write_access(inode); // 獲取一次inode寫操作許可權
if (error)
goto cleanup_file;
}
// 初始化檔案結構
f->;f_mapping = inode->;i_mapping;
f->;f_dentry = dentry;
f->;f_vfsmnt = mnt;
f->;f_pos = 0;
f->;f_op = fops_get(inode->;i_fop);
file_move(f, &inode->;i_sb->;s_files);
// 呼叫檔案驅動模組初始化物理磁碟
if (f->;f_op && f->;f_op->;open) {
error = f->;f_op->;open(inode,f);
if (error)
goto cleanup_all;
}
f->;f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
// 初始化上次讀取狀態
file_ra_state_init(&f->;f_ra, f->;f_mapping->;host->;i_mapping);
// 如果設定了O_DIRECT,則檢測檔案結構中是否有驅動的操作函式指標
if (f->;f_flags & O_DIRECT) {
if (!f->;f_mapping->;a_ops || !f->;f_mapping->;a_ops->;direct_IO) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
return f; // 返回檔案結構
cleanup_all: // 出錯,則釋放資源並返回
fops_put(f->;f_op);
if (f->;f_mode & FMODE_WRITE)
put_write_access(inode);
file_kill(f);
f->;f_dentry = NULL;
f->;f_vfsmnt = NULL;
cleanup_file:
put_filp(f);
cleanup_dentry:
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
4.3. 總結
open函式的主要操作就是為檔案初始化inode,初始化檔案結構,重新整理程式檔案連結串列。
5. creat 函式
asmlinkage long sys_creat(const char __user * pathname, int mode){
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
從上面的簡短的實現程式碼可以看出,creat的系統呼叫直接以O_CREAT|O_WRONLY|O_TRUNC標識呼叫open函式實現的。
may_open執行許可權檢測和檔案開啟,和truncate的操作。
int may_open(struct nameidata *nd, int acc_mode, int flag){
struct dentry *dentry = nd->;dentry;
struct inode *inode = dentry->;d_inode;
int error;
if (!inode) return -ENOENT; // inode為空,則返回錯誤
if (S_ISLNK(inode->;i_mode)) // 連線檔案,返回錯誤
return -ELOOP;
if (S_ISDIR(inode->;i_mode) && (flag & FMODE_WRITE))
return -EISDIR; // 是目錄且僅有寫許可權,返回錯誤
error = permission(inode, acc_mode, nd); // 見擦inode的accmode
if (error)
return error;
if (S_ISFIFO(inode->;i_mode) || S_ISSOCK(inode->;i_mode)) {
flag &= ~O_TRUNC; // 如果是FIFO檔案,則不允許truncate
} else if (S_ISBLK(inode->;i_mode) || S_ISCHR(inode->;i_mode)) {
if (nd->;mnt->;mnt_flags & MNT_NODEV)
return -EACCES; // 如果是裝置,則不允許truncate,否則返回錯誤
flag &= ~O_TRUNC;
} else if (IS_RDONLY(inode) && (flag & FMODE_WRITE))
return -EROFS; 如果flag標識和inode許可權衝突,則返回錯誤
// 如果inode只允許append方式寫入,則不允許truncate和非append寫入方式。
if (IS_APPEND(inode)) {
if ((flag & FMODE_WRITE) && !(flag & O_APPEND))
return -EPERM;
if (flag & O_TRUNC)
return -EPERM;
}
// O_NOATIME方式僅在inode使用者是檔案擁有者或者超級使用者情況下才被允許
if (flag & O_NOATIME)
if (current->;fsuid != inode->;i_uid && !capable(CAP_FOWNER))
return -EPERM;
// 檢查是否有其他程式在使用該檔案
error = break_lease(inode, flag);
if (error)
return error;
if (flag & O_TRUNC) {
error = get_write_access(inode); // 獲取一次inode寫操作許可權
if (error)
return error;
// 鎖定inode
error = locks_verify_locked(inode);
if (!error) {
DQUOT_INIT(inode); // 對inode執行配額初始化
error = do_truncate(dentry, 0); // truncate dentry
}
put_write_access(inode); // 釋放當前寫操作許可權
if (error)
return error;
} else
if (flag & FMODE_WRITE) // 如果有寫標識,則對inode執行配額初始化
DQUOT_INIT(inode);
return 0;
}
4.2.4.2. open_namei子函式dentry_open
dentry_open函式主要實現檔案表的對應開啟等操作,返回檔案指標。
struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags){
struct file * f;
struct inode *inode;
int error;
error = -ENFILE;
f = get_empty_filp(); // 從程式檔案表中獲取一個未使用的檔案結構指標,空則出錯返回
if (!f)
goto cleanup_dentry;
// 設定檔案的flags和mode標識
f->;f_flags = flags;
f->;f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
inode = dentry->;d_inode;
if (f->;f_mode & FMODE_WRITE) {
error = get_write_access(inode); // 獲取一次inode寫操作許可權
if (error)
goto cleanup_file;
}
// 初始化檔案結構
f->;f_mapping = inode->;i_mapping;
f->;f_dentry = dentry;
f->;f_vfsmnt = mnt;
f->;f_pos = 0;
f->;f_op = fops_get(inode->;i_fop);
file_move(f, &inode->;i_sb->;s_files);
// 呼叫檔案驅動模組初始化物理磁碟
if (f->;f_op && f->;f_op->;open) {
error = f->;f_op->;open(inode,f);
if (error)
goto cleanup_all;
}
f->;f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
// 初始化上次讀取狀態
file_ra_state_init(&f->;f_ra, f->;f_mapping->;host->;i_mapping);
// 如果設定了O_DIRECT,則檢測檔案結構中是否有驅動的操作函式指標
if (f->;f_flags & O_DIRECT) {
if (!f->;f_mapping->;a_ops || !f->;f_mapping->;a_ops->;direct_IO) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
return f; // 返回檔案結構
cleanup_all: // 出錯,則釋放資源並返回
fops_put(f->;f_op);
if (f->;f_mode & FMODE_WRITE)
put_write_access(inode);
file_kill(f);
f->;f_dentry = NULL;
f->;f_vfsmnt = NULL;
cleanup_file:
put_filp(f);
cleanup_dentry:
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
4.3. 總結
open函式的主要操作就是為檔案初始化inode,初始化檔案結構,重新整理程式檔案連結串列。
5. creat 函式
asmlinkage long sys_creat(const char __user * pathname, int mode){
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
從上面的簡短的實現程式碼可以看出,creat的系統呼叫直接以O_CREAT|O_WRONLY|O_TRUNC標識呼叫open函式實現的。
相關熱門文章
給主人留下些什麼吧!~~
評論熱議
相關文章
- Linux核心分析--系統呼叫實現程式碼分析(轉)Linux
- Linux檔案IO open、dup、fork核心原理圖解Linux圖解
- Linux系統程式設計(2)——檔案與IO之系統呼叫與檔案IO操作Linux程式設計
- linux系統程式設計之檔案與IO(一):檔案描述符、open,closeLinux程式設計
- linux系統程式設計之檔案與IO(五):stat()系統呼叫獲取檔案資訊Linux程式設計
- Linux檔案系統的實現Linux
- linux系統程式設計之檔案與IO(二):系統呼叫read和writeLinux程式設計
- Linux系統呼叫詳解(實現機制分析)Linux
- Linux系統程式設計-檔案IOLinux程式設計
- linux系統程式設計之檔案與IO(六):實現ls -l功能Linux程式設計
- LiteOS-A核心中的procfs檔案系統分析
- Linux系統程式設計之檔案IOLinux程式設計
- linux系統程式設計之檔案與IO(四):目錄訪問相關係統呼叫Linux程式設計
- 鴻蒙輕核心原始碼分析:檔案系統LittleFS鴻蒙原始碼
- 《Linux核心設計與實現》讀書筆記(十三)- 虛擬檔案系統Linux筆記
- Linux系統呼叫過程分析Linux
- Linux系統程式設計(七)檔案許可權系統呼叫Linux程式設計
- 檔案系統(四):FAT32檔案系統實現原理
- linux檔案系統和日誌分析Linux
- Linux檔案系統與日誌分析Linux
- Linux 核心101:虛擬檔案系統的使命Linux
- 幾個重要的Linux系統核心檔案介紹(zt)Linux
- 在Linux中,如何實現檔案系統的快照和克隆?Linux
- linux系統程式設計之檔案與IO(三):利用lseek()建立空洞檔案Linux程式設計
- linux的檔案系統Linux
- Linux C 檔案IOLinux
- Linux檔案IO操作Linux
- 為Linux-3.10.1核心新增系統呼叫Linux
- Linux核心模組程式設計--系統呼叫(轉)Linux程式設計
- Linux核心模組程式設計/proc 檔案系統(轉)Linux程式設計
- Linux核心啟動之根檔案系統掛載Linux
- 【linux】系統呼叫版串列埠分析&原始碼實戰Linux串列埠原始碼
- Linux--檔案系統與日誌分析Linux
- Linux系統篇-檔案系統&虛擬檔案系統Linux
- 【移植Linux 3.4.2核心之四】修改核心程式碼支援YAFFS檔案系統Linux
- Unable to open kernel device "\\.\vmci": 系統找不到指定的檔案。.dev
- Linux作業系統分析 | 深入理解系統呼叫Linux作業系統
- Linux系統程式設計(3)——檔案與IO之fcntl函式Linux程式設計函式