文件的打开操作有两个起点,第一个起点是open,另外一个是openat。这两者的区别仅仅在于搜索的其实目录不同,前者是从当前工作目录开始搜索而后者则由用户指定起始目录。这两个函数都只是对内部的系统调用do_sys_open的一个封装,do_sys_open函数原型如下所示:
longdo_sys_open(int dfd, const char __user *filename, int flags, int mode)
函数的第一个参数以一个整形的数值代表搜索的起点,这些标志数值在系统里面和相应的宏对应,第二个参数是从用户空间传递进来的文件名称,当然根据之前的分析这个文件名称需要经过复制才能被装入到内核空间,而后面两个则控制文件的打开以及打开文件之后的操作。
VFS架构下的文件包含两个部分,一个是struct file结构体,这一部分主要来源于文件系统中的文件节点,另一个是一个int型的文件描述符,这一部分主要用于VFS对文件的管理。
long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { struct open_flags op; int lookup = build_open_flags(flags, mode, &op); char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { fd = get_unused_fd_flags(flags); if (fd >= 0) { struct file *f = do_filp_open(dfd, tmp, &op, lookup); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f); fd_install(fd, f); } } putname(tmp); } return fd; }
上面是整个系统调用打开文件操作的实现,首先函数会将用户空间传递过来的一些参数复制到内核空间并且会有一个用户空间参数转化为内核空间参数的过程(build_open_flags就是做这种转化的)。
int fd =PTR_ERR(tmp);
这一句包含多层含义, fd作为一个返回值对参数的复制进行验证的,同时fd还是一个文件索引,利用这个索引VFS能够通过进程相关的资源队列快速获取文件结构体。
在最底层的if判断之后,主要有三个操作。第一个操作利用get_unused_fd_flags函数获取当前进程没有被使用的文件索引;第二个操作利用do_filp_open读取文件结构体,这个函数会有一个搜索文件节点并且读取文件节点的操作;第三个操作利用fd_install将文件结构体与文件索引进行关联。
get_unused_fd_flags被定义为一个宏,这个宏的实现是以下列方式调用alloc_fd(0,flags)
int alloc_fd(unsigned start, unsigned flags) { struct files_struct *files = current->files; unsigned int fd; int error; struct fdtable *fdt; spin_lock(&files->file_lock); repeat: fdt = files_fdtable(files); fd = start; if (fd < files->next_fd) fd = files->next_fd; if (fd < fdt->max_fds) fd = find_next_zero_bit(fdt->open_fds->fds_bits, fdt->max_fds, fd); error = expand_files(files, fd); if (error < 0) goto out; if (error) goto repeat; if (start <= files->next_fd) files->next_fd = fd + 1; FD_SET(fd, fdt->open_fds); if (flags & O_CLOEXEC) FD_SET(fd, fdt->close_on_exec); else FD_CLR(fd, fdt->close_on_exec); error = fd; 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); } out: spin_unlock(&files->file_lock); return error; }
files_struct是VFS架构中管理文件的核心,这个结构体内部包含三个主要的成员变量来管理当前进程打开的文件。结构体定义如下,其中fd指针是一个file指针的数组,数组的项数由max_fds计数,而每个file结构体在fd数组中的索引由open_fds位数组计算得出,每个进程可以打开的文件都有上限。上限的设置有两个,第一个是内核中限定的可以打开文件的计数,另一个是进程的资源限制。后者是可以修改的,而前者在编译时确定。(估计系统设置的可以打开的文件的上限是1024项,open_fds是一个32位的数组,数组项数为32,同样的一块内存的基本单位为4K,刚好是1024项)。
struct fdtable { unsigned int max_fds; struct file __rcu **fd; /* current fd array */ fd_set *close_on_exec; fd_set *open_fds; struct rcu_head rcu; struct fdtable *next; };
通过上面对结构体的分析,可以知道分配一个文件索引,从上次分配好的文件索引开始。搜索整个位图标志,其中高位作为数组的索引而低位作为数组中每一项的索引直到找到位图为0的位。搜索得到的文件索引可能超出了范围,所以可能需要进行一个文件数组的扩展。然后设置相应的位图为1.