Linux open系統呼叫流程(1)
1.概述
我們知道,Linux把裝置看成特殊的檔案,稱為裝置檔案。在操作檔案之前,首先必須開啟檔案,開啟檔案的函式是通過open系統呼叫來實現的。而簡單的檔案開啟操作,在Linux核心實現卻是非常的複雜。open函式開啟原理就是將程序files_struct結構體和檔案物件file相關聯。那麼具體是怎麼實現的呢?讓我們一起走進Linux核心檔案開啟流程。
2. 首先,通過系統呼叫sys_open函式:
//開啟檔案的系統呼叫 asmlinkage long sys_open(const char __user *filename, int flags, int mode) { long ret; if (force_o_largefile()) flags |= O_LARGEFILE; //呼叫do_sys_open函式 ret = do_sys_open(AT_FDCWD, filename, flags, mode); /* avoid REGPARM breakage on x86: */ prevent_tail_call(ret); return ret; }
這個函式進行了簡單的處理,呼叫do_sys_open函式:
long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { /*將從使用者空間傳入的路徑名複製到核心空間*/ char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { /*得到一個沒有使用的檔案描述符*/ fd = get_unused_fd(); if (fd >= 0) { /*file物件是檔案物件,存在於記憶體,所以沒有回寫,f_op被賦值*/ struct file *f = do_filp_open(dfd, tmp, flags, mode); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f->f_path.dentry); /*將current->files_struct和檔案物件關聯*/ fd_install(fd, f); } } putname(tmp); } return fd; }
這個函式主要完成以下幾件事情:
(1)呼叫get_unused_fd得到一個沒有使用的檔案描述符,這是為讀,寫準備的,每個開啟的檔案都有一個檔案描述符。
(2) 呼叫do_filp_open構建 struct file檔案物件,並填充相關資訊,這個函式非常複雜,我們以後再看。
(3) 呼叫fd_install將檔案物件和程序的files_struct物件關聯。
首先看一下get_unused_fd函式:
/*找到一個沒有使用的檔案描述符,並標記為busy * Find an empty file descriptor entry, and mark it busy. */ int get_unused_fd(void) { /*得到files_struct結構體*/ struct files_struct * files = current->files; int fd, error; /*定義fdtable結構*/ struct fdtable *fdt; error = -EMFILE; spin_lock(&files->file_lock); repeat: /*返回files的fdt指標*/ fdt = files_fdtable(files); /*從fdt->open_ds->fds_bits陣列查詢一個沒有置位的檔案描述符,open_ds表示開啟的檔案描述符集,當點陣圖為1表示已經開啟,為0已經關閉*/ fd = find_next_zero_bit(fdt->open_fds->fds_bits, fdt->max_fds, files->next_fd); /* * N.B. For clone tasks sharing a files structure, this test * will limit the total number of files that can be opened. */ if (fd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) goto out; /* Do we need to expand the fd array or fd set? */ error = expand_files(files, fd); if (error < 0) goto out; if (error) { /* * If we needed to expand the fs array we * might have blocked - try again. */ error = -EMFILE; goto repeat; } /*將檔案描述符集合的fd置位*/ FD_SET(fd, fdt->open_fds); FD_CLR(fd, fdt->close_on_exec); /*下一個描述符,即搜尋的位置加1*/ files->next_fd = fd + 1; #if 1 /* Sanity check */ if (fdt->fd[fd] != NULL) { printk(KERN_WARNING "get_unused_fd: slot %d not NULL!\n", fd); fdt->fd[fd] = NULL; } #endif error = fd; out: spin_unlock(&files->file_lock); return error; }
在第7行,得到當前程序的files指標。 在第16-18行,返回開啟檔案表,在開啟的檔案描述符集open_ds的fds_bits陣列查詢對應位置為0的點陣圖,返回位置,表示這個檔案描述符沒有被使用。接下來,在41-43行,分別將open_fds的fd位置的點陣圖置位,並將fd+1賦值給下一下檔案描述符。如果這個檔案描述符被佔用,就將fdt->fd[fd]=NULL. 最後返回檔案描述符fd.
接下來,調do_filp_open函式,其主要功能是返回一個已經填充好的檔案物件指標。這個函式比較複雜,在下一節進行分析。
最後,分析一下fd_install函式,傳入引數檔案描述符fd和檔案物件f,具體如下:
/*
* Install a file pointer in the fd array.
*
* The VFS is full of places where we drop the files lock between
* setting the open_fds bitmap and installing the file in the file
* array. At any such point, we are vulnerable to a dup2() race
* installing a file in the array before us. We need to detect this and
* fput() the struct file we are about to overwrite in this case.
*
* It should never happen - if we allow dup2() do it, _really_ bad things
* will follow.
*/
//將程序的current->files物件與file檔案物件進行繫結,從而直接操作定義的方法
void fastcall fd_install(unsigned int fd, struct file * file)
{
/*程序的files_struct物件*/
struct files_struct *files = current->files;
/*程序檔案表*/
struct fdtable *fdt;
spin_lock(&files->file_lock);
/*取得fdt物件*/
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
/*將fdt->fd[fd]指向file物件*/
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
}
這個函式首先得到files_struct物件指標,然後呼叫rcu_assign_pointer,將檔案物件file賦給fdt->fd[fd], 這樣,檔案物件就和程序相關聯起來了。
因此,不同的程序開啟相同的檔案,每次開啟都會構建一個struct file檔案物件,然後將這個物件和具體的程序相關聯。其實open呼叫可以概括如下:
(1)得到一個未使用的檔案描述符
(2)構建檔案物件struct file
(3)將檔案物件和程序相關聯