文件系统之open
在文件读写之前,我们必须先打开文件。从应用程序的角度来看,这是通过标准库的open函数完成的,该函数返回一个文件描述符。内核中是由系统调用do_sys_open()函数完成。
//系统调用
SYSCALL_DEFINE3(open...)
//具体实现
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp;
if (fd)
return fd;
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
/*在内核中,每个打开的文件由一个文件描述符表示
该描述符在特定于进程的数组中充当位置索引(数组是
task_struct->files->fd_arry),该数组的元素包含了file结构,其中
包括每个打开文件的所有必要信息。因此,调用下面
函数查找一个未使用的文件描述符,返回的是上面
说的数组的下标*/
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
/* 此函数主要功能为打开文件file获得file结构*/
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
/*将fd与对应的file结构关联起来*/
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}
//打开文件具体实现
struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;
/*初始化nameidata结构,该结构是open过程的重要辅助结构,下面单独对其说明*/
set_nameidata(&nd, dfd, pathname);
//查找打开文件的主要函数
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}
//nameidata结构说明:nameidata在打开每一层目录项时是变化的,主要用于记录当前文件系统current->vfsmount和current->dentry.
struct nameidata {
struct path path; //该字段用于保存当前目录项。该字段是path结构,该结构将目录项和该目录项所关联的vfsmount结构进行封装。
struct qstr last; //当前目录项的名称
struct path root; //当前文件系统的根目录
struct inode *inode; /* path.dentry.d_inode */
unsigned int flags;
...
};
static struct file *path_openat(struct nameidata *nd,
const struct open_flags *op, unsigned flags)
{
const char *s;
struct file *file;
int opened = 0;
int error;
file = get_empty_filp(); //获得一个新的file结构
...
/*对接下来的路径遍历做一些准备工作,主要用于判断路径遍历的起始位置,即通过根目录/,或当前路径(pwd),或指定路径(openat系统调用可以指定)*/
s = path_init(nd, flags);
/*link_path_walk会遍历路径中的每个dentry项,此函数之后,nameidata保存了最后一个目录项的信息,但是内核并没有确定最后一个目录项是否真的存在,这些工作将在do_last()中进行。*/
while (!(error = link_path_walk(s, nd)) &&
(error = do_last(nd, file, op, &opened)) > 0) {
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
s = trailing_symlink(nd);
if (IS_ERR(s)) {
error = PTR_ERR(s);
break;
}
}
}
static int link_path_walk(const char *name, struct nameidata *nd)
{
for(;;) {
err = walk_component(nd, WALK_GET); //遍历路径中的每一项dentry
}
}
static int walk_component(struct nameidata *nd, int flags)
{
struct path path;
struct inode *inode;
unsigned seq;
int err;
/*
* "." and ".." are special - ".." especially so because it has
* to be able to know about the current root directory and
* parent relationships.
*/
if (unlikely(nd->last_type != LAST_NORM)) {
err = handle_dots(nd, nd->last_type); //处理.和..特殊dentry项
if (flags & WALK_PUT)
put_link(nd);
return err;
}
err = lookup_fast(nd, &path, &inode, &seq); //搜索dentry cache
if (unlikely(err <= 0)) {
if (err < 0)
return err;
path.dentry = lookup_slow(&nd->last, nd->path.dentry,
nd->flags); /*读取磁盘上的内容到内存中的dentry cache进行进一步搜索*/
if (IS_ERR(path.dentry))
return PTR_ERR(path.dentry);
if (unlikely(d_is_negative(path.dentry))) {
dput(path.dentry);
return -ENOENT;
}
path.mnt = nd->path.mnt;
err = follow_managed(&path, nd);
if (unlikely(err < 0))
return err;
seq = 0; /* we are already out of RCU mode */
inode = d_backing_inode(path.dentry);
}
}
static int do_last(struct nameidata *nd,struct file *file, const struct open_flags *op,int *opened)
//调用vfs_open打开目标文件
vfs_open(&nd->path, file, current_cred()); //打开目标文件
//调用do_dentry_open
do_dentry_open(file, inode, NULL, cred);
//将目标文件inode对应的具体文件操作函数赋给file结构
f->f_op = fops_get(inode->i_fop);
//调用具体文件系统的open函数打开文件,后续就可以进行文件读写操作了
error = open(inode, f);