一、概要
用户进程在能够读/写一个文件之前必须要先打开这个文件,从概念上说是一个进程与文件系统之间的一种有链接的通信,对于打开文件实质就是在进程和文件之间建立起连接,而“打开文件号”就是唯一的标识这个连接。在文件系统的处理当中,每当一个进程重复打开同一个文件时就建立起一个由file数据结构代表的独立的上下文。通常,一个file数据结构,即一个读/写文件的上下文,都是由一个“打开文件号”加以标识的。从上面可以看出,一个文件的打开与进程是有着紧密联系的。接下来就首先介绍进程中与文件有关的相关结构。
二、与进程有关的文件
(1)进程中与文件相关的结构
1、首先是task_struct结构,每个进程都有一个task_struct结构的对象,该对象包含了进程处理的上下文。在这里只贴出与文件有关的部分如下:
从上面进程中与文件有关的代码可以看出,在一个task_struct结构中与文件操作有关的结构之后两个即struct fs_struct结构对象和file_struct结构对象。下面分别讲解这两个对象的相关信息。
2、fs_struct结构。
其中涉及到的path结构如下:
3、file_struct对象
这个数据结构中最主要的就是一个file结构指针数组fd_array[],该数组的大小事固定的,即32,其下标为打开文件号即为文件描述符。
这个结构中两个重要的结构即为文件描述符管理结构fdtable与file对象。其中fdtable对象的代码如下:
files_struct结构:该结构保存了该进程中打开的所有的文件得信息。比如文件描述符管理表、打开的文件数组等等都是表示的是该进程打开的文件的详细信息。
files_struct结构与fdtable结构的结构体如下
对于文件结构体,其代表的是一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它有内核在打开文件时创建。同时在文件关闭之后,内核释放这个数据结构。
file对象的代码如下:
(2)进程中与文件相关的各个结构的联系
以上就是进程环境中与文件操作有关的所有的结构了,为了更好的理解结构之间的联系,下面给出各个结构之间的结构图。
三、open函数的内核追踪过程
打开文件的系统调用是open(),这个系统调用在物理文件系统层是通过do_sys_open()函数来实现的,其代码在/fs/open.c这个文件中。其具体的代码如下:
从上面的代码可以看出open()系统调用的主要是由以下的函数来实现的:
(1)getname()函数: 将从用户空间传入的路径名复制到内核空间。
(2) get_unused_fd_flags ( )函数: 获取一个没有使用的文件描述符fd ,这个函数主要是操作fdtable结构中的相关字段
(3) do_filp_open ( )函数: 建立file对象,并把file对象插入到文件系统超级块的s_files字段所指向的打开文件的链表。
(4)fd_install()函数: 将current->files_struct和文件对象进行关联。
下面就分别来讲解这些函数的具体操作
(1)getname()函数:这个函数在这里就不追踪了。比较麻烦。
(2)get_unused_fd_flags()函数:这个函数主要是通过内核函数__alloc_fd()函数来实现的该函数的代码如下:
这个函数主要是在文件描述符管理结构中把相应的下一个可用的fd所在的位置1,表示这个文件打开号是处于忙的状态,同时如果要打开的文件号大于文件数组的文件打开号的最大值,那么就进行扩容处理。主要涉及到的就是对open_fds位图中相应的位进行置1的处理和返回fd的位置。
(3) do_filp_open ( )函数,这个函数才是open系统调用中最主要的函数即主体函数,该函数的代码如下
其主要的函数为
path_openat()函数,该函数的功能是:根据路径名找到相应的文件对应的dentry对象和inode对象,然后通过这两个对象来对file对象进行赋值。最终返回赋值好的file对象。其中用到了路径名解析中的知识,这块知识在后面会进一步详细的讲解,所以在这里就不着重讲了。
该函数的内核代码如下:
这里将看看当O_CREAT标志位为1的情况下,即当如果要找的目标文件不存在的时候不返回错误,而是创建一个新的文件。创建新的文件主要是通过函数vfs_create()函数来实现,该函数主要是实现创建一个文件的功能。该函数的内核代码如下:
建立新文件的函数vfs_create()函数的详细细节在之后的博客中在进行跟踪,在这里只要知道这个函数是创建一个新文件就可以了。
(4)在回到 do_sys_open()函数中来讲解fd_install()函数。通过上面的三个函数已经创建了file对象并且通过找到文件的dentry对象和inode对象对file对象进行了赋值操作。接下来的工作就是把刚创建的file对象与current->files_struct进行关联。其实就是把返回的文件对象file对象地址赋值给current->files->fd[fd].
fd_install()函数主要是通过__fd_install()函数来实现的,所以下面贴出__fd_install()函数的代码如下:
四、总结
打开文件做的工作总结:
(1)把路径名从用户空间读取到内核空间。
(2)在fdtable中找到一个没有使用的文件描述符,然后修改fdtable结构中的相应字段。
(3)创建一个文件对象(file对象),然后通过路径名解析查找到目标文件的dentry结构和inode结构(如果要查 找的文件不存在则建立新的文件并创建该文件的dentry结构和inode结构),通过文件的dentry结构和inode结 构来对file对象进行赋值操作,并最终返回file对象的地址。
(4)把建立的file对象的地址赋值给current->files->fd[fd]中。
(5)返回fd即这个打开文件对应的文件描述符。
用户进程在能够读/写一个文件之前必须要先打开这个文件,从概念上说是一个进程与文件系统之间的一种有链接的通信,对于打开文件实质就是在进程和文件之间建立起连接,而“打开文件号”就是唯一的标识这个连接。在文件系统的处理当中,每当一个进程重复打开同一个文件时就建立起一个由file数据结构代表的独立的上下文。通常,一个file数据结构,即一个读/写文件的上下文,都是由一个“打开文件号”加以标识的。从上面可以看出,一个文件的打开与进程是有着紧密联系的。接下来就首先介绍进程中与文件有关的相关结构。
二、与进程有关的文件
(1)进程中与文件相关的结构
1、首先是task_struct结构,每个进程都有一个task_struct结构的对象,该对象包含了进程处理的上下文。在这里只贴出与文件有关的部分如下:
点击(此处)折叠或打开
- struct task_struct{
- ...........
- struct fs_struct *fs;
- struct file_struct *files;
- ...........
- }
2、fs_struct结构。
点击(此处)折叠或打开
- struct fs_struct {
- int users;
- rwlock_t lock;
- int umask;
- int in_exec;
- struct path root, pwd; //其中的root对象是用来存放根目录的相关信息,比如根目录的目录项等而pwd存放的是当前目录的相关信息。主要是当前目录的目录项对象。这两个对象主要是在文件路径名解析的时候用于查找文件开始的目录项。如果路径名查找是绝对路径查找,那么就会根据root->dentry来开始查找。而如果是相对路径查找就会根据pwd->dentry来进行查找。
- };
点击(此处)折叠或打开
- struct path {
- struct vfsmount *mnt;//只有当相应的目录为挂载目录的时候这个变量才存放一些有关挂载的信息
- struct dentry *dentry;//表示目录的目录项对象结构。
- }
点击(此处)折叠或打开
- struct files_struct {
- atomic_t count; //自动增量
- struct fdtable *fdt;
- struct fdtable fdtab;
- fd_set close_on_exec_init; //执行exec时
- 需要关闭的文件描述符初值集合
- fd_set open_fds_init; //当前打开文件
- 的文件描述符屏蔽字
- struct file * fd_array[NR_OPEN_DEFAULT];
- spinlock_t file_lock;
- };
这个结构中两个重要的结构即为文件描述符管理结构fdtable与file对象。其中fdtable对象的代码如下:
点击(此处)折叠或打开
- struct fdtable {
- unsigned int max_fds; //可以代开的最大文件数
- int max_fdset; //位图的最大长度
- int next_fd; //下一个可用的fd
- struct file ** fd; /* current fd array 指向files_struct的fd_array */
- fd_set *close_on_exec;
- fd_set *open_fds; //打开的文件标记,比如第2位为1,则打开了2号文件.这个字段和close_on_exec字段都是一个位图的形式来进行表示的
- struct rcu_head rcu;
- struct files_struct *free_files;
- struct fdtable *next;
- }
files_struct结构与fdtable结构的结构体如下

对于文件结构体,其代表的是一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它有内核在打开文件时创建。同时在文件关闭之后,内核释放这个数据结构。
file对象的代码如下:
点击(此处)折叠或打开
- struct file {
- /*
- * fu_list becomes invalid after file_free is called and queued via
- * fu_rcuhead for RCU freeing
- */
- union {
- struct list_head fu_list;
- struct rcu_head fu_rcuhead;
- } f_u;
- struct path f_path;//该文件所对应的path结构,主要是存放的是该文件的目录项对象结构。
- #define f_dentry f_path.dentry
- #define f_vfsmnt f_path.mnt
- const struct file_operations *f_op;//文件操作的指针,如果打开的文件存在那么这个指针是通过dentry里的对象来设置。而如果打开的文件不存在需要新建立,那么这个指针是通过父目录的dentry中的对象来设置。
- //以下的信息主要是通过文件的inode节点中的信息进行设置的
- atomic_t f_count;
- unsigned int f_flags;
- mode_t f_mode;
- loff_t f_pos; //这个字段是很重要的,表示的是文件中的当前读写位置,当使用系统调用lseek的时候就是改变的这个字段的值
- struct fown_struct f_owner;
- unsigned int f_uid, f_gid;
- struct file_ra_state f_ra;
- unsigned long f_version;
- #ifdef CONFIG_SECURITY
- void *f_security;
- #endif
- /* needed for tty driver, and maybe others */
- void *private_data;
- #ifdef CONFIG_EPOLL
- /* Used by fs/eventpoll.c to link all the hooks to this file */
- struct list_head f_ep_links;
- spinlock_t f_ep_lock;
- #endif /* #ifdef CONFIG_EPOLL */
- struct address_space *f_mapping;//这个指针是想的是地址空间的对象的指针,主要是用于在page cache中进行查找操作。
- };
以上就是进程环境中与文件操作有关的所有的结构了,为了更好的理解结构之间的联系,下面给出各个结构之间的结构图。

三、open函数的内核追踪过程
打开文件的系统调用是open(),这个系统调用在物理文件系统层是通过do_sys_open()函数来实现的,其代码在/fs/open.c这个文件中。其具体的代码如下:
点击(此处)折叠或打开
- long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
- {
- struct open_flags op;
- int lookup = build_open_flags(flags, mode, &op);
- struct filename *tmp = getname(filename);//将从用户空间传入的路径名复制到内核空间
- int fd = PTR_ERR(tmp);
-
- if (!IS_ERR(tmp)) {
- fd = get_unused_fd_flags(flags);//获取一个没有使用的文件描述符fd
- if (fd >= 0) {
- struct file *f = do_filp_open(dfd, tmp, &op, lookup);//建立file对象
- if (IS_ERR(f)) {
- put_unused_fd(fd);
- fd = PTR_ERR(f);
- } else {
- fsnotify_open(f);
- fd_install(fd, f);//将current->files_struct和文件对象进行关联
- }
- }
- putname(tmp);
- }
- return fd;
- }
(1)getname()函数: 将从用户空间传入的路径名复制到内核空间。
(2) get_unused_fd_flags ( )函数: 获取一个没有使用的文件描述符fd ,这个函数主要是操作fdtable结构中的相关字段
(3) do_filp_open ( )函数: 建立file对象,并把file对象插入到文件系统超级块的s_files字段所指向的打开文件的链表。
(4)fd_install()函数: 将current->files_struct和文件对象进行关联。
下面就分别来讲解这些函数的具体操作
(1)getname()函数:这个函数在这里就不追踪了。比较麻烦。
(2)get_unused_fd_flags()函数:这个函数主要是通过内核函数__alloc_fd()函数来实现的该函数的代码如下:
点击(此处)折叠或打开
- /*
- * allocate a file descriptor, mark it busy.
- *功能:分配一个文件描述符,并把它标记为忙的状态
- *其中的files参数为:当前进程中的current->files变量
- */
- int __alloc_fd(struct files_struct *files,
- unsigned start, unsigned end, unsigned flags)
- {
- unsigned int fd;
- int error;
- struct fdtable *fdt;//文件描述符管理结构
-
- spin_lock(&files->file_lock);
- repeat:
- fdt = files_fdtable(files);//指向files_struct结构中的fdtable指针
- fd = start;
- if (fd < files->next_fd)//首先判断fd是不是小于进程中下一个可用的fd,如果是则直接赋值下一个可用的fd给fd
- fd = files->next_fd;
-
- if (fd < fdt->max_fds)
- fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, fd);//将open_fds所指向的位图中的相应的为设置成1,表示这个打开的文件号已经不再空闲了。这个位图表示的是已经在使用中的打开文件号。同时还将指针close_on_exec所指向的位图中的相应的为清0,表示如果当前进程通过exec()系统调用执行一个可执行程序的话无需将这个文件进行关闭。
-
- /*
- * N.B. For clone tasks sharing a files structure, this test
- * will limit the total number of files that can be opened.
- */
- error = -EMFILE;
- if (fd >= end)
- goto out;
-
- error = expand_files(files, fd);//如果所指向的fd超出了file结构指针数组的容量就通过该函数来扩充该数组的容量,并让指针fd指向新的数组。同时会使用expand_fdset来扩充位图的容量,并使open_fds指针也指向新的位图。
- if (error < 0)
- goto out;
-
- /*
- * If we needed to expand the fs array we
- * might have blocked - try again.
- */
- if (error)
- goto repeat;
-
- if (start <= files->next_fd)
- files->next_fd = fd + 1;
-
- __set_open_fd(fd, fdt);
- if (flags & O_CLOEXEC)
- __set_close_on_exec(fd, fdt);
- else
- __clear_close_on_exec(fd, fdt);
- error = fd;
- #if 1
- /* Sanity check */
- if (rcu_dereference_raw(fdt->fd[fd]) != NULL) {
- printk(KERN_WARNING "alloc_fd: slot %d not NULL!n", fd);
- rcu_assign_pointer(fdt->fd[fd], NULL);
- }
- #endif
-
- out:
- spin_unlock(&files->file_lock);
- return error;
- }
(3) do_filp_open ( )函数,这个函数才是open系统调用中最主要的函数即主体函数,该函数的代码如下
点击(此处)折叠或打开
- struct file *do_filp_open(int dfd, struct filename *pathname,
- const struct open_flags *op, int flags)
- {
- struct nameidata nd;
- struct file *filp;
-
- filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
- if (unlikely(filp == ERR_PTR(-ECHILD)))
- filp = path_openat(dfd, pathname, &nd, op, flags);
- if (unlikely(filp == ERR_PTR(-ESTALE)))
- filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
- return filp;
- }
该函数的内核代码如下:
点击(此处)折叠或打开
- static struct file *path_openat(int dfd, struct filename *pathname,
- struct nameidata *nd, const struct open_flags *op, int flags)
- {
- struct file *base = NULL;
- struct file *file;
- struct path path;
- int opened = 0;
- int error;
-
- file = get_empty_filp();//从内存中的空闲的file对象队列中,分配一个空闲的file对象出来
- if (IS_ERR(file))
- return file;
-
- file->f_flags = op->open_flag;
-
- error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base);//进行路劲查找,查找到路劲的开始目录的dentry对象。主要是通过current->fs中的值对nd进行一些赋值处理
- if (unlikely(error))
- goto out;
-
- current->total_link_count = 0;
- error = link_path_walk(pathname->name, nd);//这个函数主要是完成路径名查找的后半段从开始目录到最终的文件父目录的查找过程。主要是通过路径分量名在内存中查找路径分量名对应的目录项对象。直到找到最终文件对应的父目录的目录项对象即dentry结构。(找到最终文件的父目录的目录项对象和inode节点对象)
- if (unlikely(error))
- goto out;
-
- error = do_last(nd, &path, file, op, &opened, pathname);//这个函数主要通过检查最终的文件是不是存在,如果存在直接找到该文件的dentry对象和inode对象。并对file对象进行赋值操作。如果要查找的文件不存在的时候,就会创建这个文件,创建文件的操作是通过vfs_create函数来实现的,在这里就不详细讲解这里面的知识了,这部分知识将留在后面进行讲解。(主要是判断文件是不是存在,然后进行一些列的处理)
- while (unlikely(error > 0)) { /* trailing symlink */
- struct path link = path;
- void *cookie;
- if (!(nd->flags & LOOKUP_FOLLOW)) {
- path_put_conditional(&path, nd);
- path_put(&nd->path);
- error = -ELOOP;
- break;
- }
- error = may_follow_link(&link, nd);
- if (unlikely(error))
- break;
- nd->flags |= LOOKUP_PARENT;
- nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
- error = follow_link(&link, nd, &cookie);
- if (unlikely(error))
- break;
- error = do_last(nd, &path, file, op, &opened, pathname);
- put_link(nd, &link, cookie);
- }
- out:
- if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))
- path_put(&nd->root);//这个函数的功能是释放dentry结构和mnt结构
- if (base)
- fput(base);
- if (!(opened & FILE_OPENED)) {
- BUG_ON(!error);
- put_filp(file);
- }
- if (unlikely(error)) {
- if (error == -EOPENSTALE) {
- if (flags & LOOKUP_RCU)
- error = -ECHILD;
- else
- error = -ESTALE;
- }
- file = ERR_PTR(error);
- }
- return file;
- }
- 当找到了目标文件的dentry结构和inode结构之后就对file对象进行相应的赋值,最后就可以进行返回了。
点击(此处)折叠或打开
- int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
- bool want_excl)
- {
- int error = may_create(dir, dentry);
- if (error)
- return error;
-
- if (!dir->i_op->create)
- return -EACCES; /* shouldn't it be ENOSYS? */
- mode &= S_IALLUGO;
- mode |= S_IFREG;
- error = security_inode_create(dir, dentry, mode);
- if (error)
- return error;
- error = dir->i_op->create(dir, dentry, mode, want_excl);
- if (!error)
- fsnotify_create(dir, dentry);
- return error;
- }
(4)在回到 do_sys_open()函数中来讲解fd_install()函数。通过上面的三个函数已经创建了file对象并且通过找到文件的dentry对象和inode对象对file对象进行了赋值操作。接下来的工作就是把刚创建的file对象与current->files_struct进行关联。其实就是把返回的文件对象file对象地址赋值给current->files->fd[fd].
fd_install()函数主要是通过__fd_install()函数来实现的,所以下面贴出__fd_install()函数的代码如下:
点击(此处)折叠或打开
- void __fd_install(struct files_struct *files, unsigned int fd,
- struct file *file)
- {
- struct fdtable *fdt;
- spin_lock(&files->file_lock);
- fdt = files_fdtable(files);
- BUG_ON(fdt->fd[fd] != NULL);
- rcu_assign_pointer(fdt->fd[fd], file);//把file对象的地址赋值给current->files->fd[fd].这里在文件描述符管理表中的fd指针指向的就是current->files->fd[]数组
- spin_unlock(&files->file_lock);
- }
四、总结
打开文件做的工作总结:
(1)把路径名从用户空间读取到内核空间。
(2)在fdtable中找到一个没有使用的文件描述符,然后修改fdtable结构中的相应字段。
(3)创建一个文件对象(file对象),然后通过路径名解析查找到目标文件的dentry结构和inode结构(如果要查 找的文件不存在则建立新的文件并创建该文件的dentry结构和inode结构),通过文件的dentry结构和inode结 构来对file对象进行赋值操作,并最终返回file对象的地址。
(4)把建立的file对象的地址赋值给current->files->fd[fd]中。
(5)返回fd即这个打开文件对应的文件描述符。