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)将文件对象和进程相关联