Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数:
-
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
-
{
-
long ret;
-
-
if (force_o_largefile())
-
flags |= O_LARGEFILE;
-
-
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
-
-
asmlinkage_protect(3, ret, filename, flags, mode);
-
return ret;
-
}
-
-
-
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_flags(flags);
-
if (fd >= 0) {
-
-
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
-
if (IS_ERR(f)) {
-
-
put_unused_fd(fd);
-
fd = PTR_ERR(f);
-
} else {
-
-
fsnotify_open(f->f_path.dentry);
-
-
fd_install(fd, f);
-
}
-
}
-
-
putname(tmp);
-
}
-
return fd;
-
}
接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:
-
struct file *do_filp_open(int dfd, const char *pathname,int open_flag, int mode, int acc_mode)
-
{
-
-
-
-
-
int flag = open_to_namei_flags(open_flag);
-
int force_reval = 0;
-
-
-
if (open_flag & __O_SYNC)
-
open_flag |= O_DSYNC;
-
-
if (!acc_mode)
-
acc_mode = MAY_OPEN | ACC_MODE(open_flag);
-
-
-
if (flag & O_TRUNC)
-
acc_mode |= MAY_WRITE;
-
-
-
if (flag & O_APPEND)
-
acc_mode |= MAY_APPEND;
-
-
-
if (!(flag & O_CREAT)) {
-
-
filp = get_empty_filp();
-
-
if (filp == NULL)
-
return ERR_PTR(-ENFILE);
-
-
nd.intent.open.file = filp;
-
filp->f_flags = open_flag;
-
nd.intent.open.flags = flag;
-
nd.intent.open.create_mode = 0;
-
-
error = do_path_lookup(dfd, pathname,lookup_flags(flag)|LOOKUP_OPEN, &nd);
-
if (IS_ERR(nd.intent.open.file)) {
-
if (error == 0) {
-
error = PTR_ERR(nd.intent.open.file);
-
-
path_put(&nd.path);
-
}
-
} else if (error)
-
-
release_open_intent(&nd);
-
if (error)
-
return ERR_PTR(error);
-
goto ok;
-
}
-
-
-
reval:
-
-
error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);
-
if (error)
-
return ERR_PTR(error);
-
if (force_reval)
-
nd.flags |= LOOKUP_REVAL;
-
-
error = path_walk(pathname, &nd);
-
if (error) {
-
if (nd.root.mnt)
-
path_put(&nd.root);
-
return ERR_PTR(error);
-
}
-
if (unlikely(!audit_dummy_context()))
-
-
audit_inode(pathname, nd.path.dentry);
-
-
-
error = -EISDIR;
-
if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])
-
goto exit_parent;
-
-
error = -ENFILE;
-
-
filp = get_empty_filp();
-
if (filp == NULL)
-
goto exit_parent;
-
-
nd.intent.open.file = filp;
-
filp->f_flags = open_flag;
-
nd.intent.open.flags = flag;
-
nd.intent.open.create_mode = mode;
-
dir = nd.path.dentry;
-
nd.flags &= ~LOOKUP_PARENT;
-
nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;
-
if (flag & O_EXCL)
-
nd.flags |= LOOKUP_EXCL;
-
mutex_lock(&dir->d_inode->i_mutex);
-
-
path.dentry = lookup_hash(&nd);
-
path.mnt = nd.path.mnt;
-
-
do_last:
-
error = PTR_ERR(path.dentry);
-
if (IS_ERR(path.dentry)) {
-
mutex_unlock(&dir->d_inode->i_mutex);
-
goto exit;
-
}
-
-
if (IS_ERR(nd.intent.open.file)) {
-
error = PTR_ERR(nd.intent.open.file);
-
goto exit_mutex_unlock;
-
}
-
-
-
if (!path.dentry->d_inode) {
-
-
error = mnt_want_write(nd.path.mnt);
-
if (error)
-
goto exit_mutex_unlock;
-
-
error = __open_namei_create(&nd, &path, flag, mode);
-
if (error) {
-
mnt_drop_write(nd.path.mnt);
-
goto exit;
-
}
-
-
filp = nameidata_to_filp(&nd);
-
-
mnt_drop_write(nd.path.mnt);
-
if (nd.root.mnt)
-
-
path_put(&nd.root);
-
if (!IS_ERR(filp)) {
-
error = ima_file_check(filp, acc_mode);
-
if (error) {
-
fput(filp);
-
filp = ERR_PTR(error);
-
}
-
}
-
return filp;
-
}
-
-
-
mutex_unlock(&dir->d_inode->i_mutex);
-
-
audit_inode(pathname, path.dentry);
-
-
-
-
-
path_to_nameidata(&path, &nd);
-
error = -EISDIR;
-
-
if (S_ISDIR(path.dentry->d_inode->i_mode))
-
goto exit;
-
ok:
-
-
will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);
-
if (will_truncate) {
-
-
error = mnt_want_write(nd.path.mnt);
-
if (error)
-
goto exit;
-
}
-
-
error = may_open(&nd.path, acc_mode, flag);
-
if (error) {
-
if (will_truncate)
-
mnt_drop_write(nd.path.mnt);
-
goto exit;
-
}
-
filp = nameidata_to_filp(&nd);
-
if (!IS_ERR(filp)) {
-
error = ima_file_check(filp, acc_mode);
-
if (error) {
-
fput(filp);
-
filp = ERR_PTR(error);
-
}
-
}
-
if (!IS_ERR(filp)) {
-
if (acc_mode & MAY_WRITE)
-
vfs_dq_init(nd.path.dentry->d_inode);
-
-
if (will_truncate) {
-
-
error = handle_truncate(&nd.path);
-
if (error) {
-
fput(filp);
-
filp = ERR_PTR(error);
-
}
-
}
-
}
-
-
if (will_truncate)
-
mnt_drop_write(nd.path.mnt);
-
if (nd.root.mnt)
-
path_put(&nd.root);
-
return filp;
-
-
exit_mutex_unlock:
-
mutex_unlock(&dir->d_inode->i_mutex);
-
exit_dput:
-
path_put_conditional(&path, &nd);
-
exit:
-
if (!IS_ERR(nd.intent.open.file))
-
release_open_intent(&nd);
-
exit_parent:
-
if (nd.root.mnt)
-
path_put(&nd.root);
-
path_put(&nd.path);
-
return ERR_PTR(error);
-
-
do_link:
-
error = -ELOOP;
-
if (flag & O_NOFOLLOW)
-
-
goto exit_dput;
-
-
-
-
nd.flags |= LOOKUP_PARENT;
-
-
error = security_inode_follow_link(path.dentry, &nd);
-
if (error)
-
goto exit_dput;
-
-
error = __do_follow_link(&path, &nd);
-
path_put(&path);
-
if (error) {
-
release_open_intent(&nd);
-
if (nd.root.mnt)
-
path_put(&nd.root);
-
if (error == -ESTALE && !force_reval) {
-
force_reval = 1;
-
goto reval;
-
}
-
return ERR_PTR(error);
-
}
-
nd.flags &= ~LOOKUP_PARENT;
-
-
if (nd.last_type == LAST_BIND)
-
goto ok;
-
error = -EISDIR;
-
if (nd.last_type != LAST_NORM)
-
goto exit;
-
if (nd.last.name[nd.last.len]) {
-
__putname(nd.last.name);
-
goto exit;
-
}
-
error = -ELOOP;
-
-
if (count++==32) {
-
__putname(nd.last.name);
-
goto exit;
-
}
-
dir = nd.path.dentry;
-
mutex_lock(&dir->d_inode->i_mutex);
-
-
path.dentry = lookup_hash(&nd);
-
path.mnt = nd.path.mnt;
-
__putname(nd.last.name);
-
goto do_last;
-
}
分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:
在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在vfs里面是由do_path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构。
基本函数流程及调用方式如下所示:
打开过程首先是open系统调用访问SYSCALL_DEFINE3函数,然后调用do_sys_open 函数完成主要功能,再调用函数do_filp_open完成主要的打开功能,下面详细看下do_filp_open中调用的do_path_lookup主要过程:
-
staic int do_path_lookup(int dfd,const char *name,unsigned int flags,struct nameidata *nd)
-
{
-
int retval=path_init(dfd,name,flags,nd);
-
-
-
if(!retval)
-
retval = path_walk(name,nd);
-
-
-
}
我们进一步看看path_walk
-
int path_walk(const char *name,struct nameidata *nd)
-
{
-
return link_path_walk(name,nd);
-
-
}
link_path_walk的主要工作是有其内部函数__link_path_walk 来完成的
result = __link_path_walk(name,nd)
至此我们转向最重要的代码__link_walk_path,该函数把传进来的字符串name,也就是用户指定的路径,按路径分隔符分解成一系列小的component。比如用户说,我要找/path/to/dest这个文件,那么我们的文件系统就会按path,to,dest一个一个来找,知道最后一个分量是文件或者查找完成。他找的时候,会先用path_init初始化过的根路径去找第一个分量,也就是path。然后用path的dentry->d_inode去找to,这样循环到最后一个。注意,内核会缓存找到的路径分量,所以往往只有第一次访问一个路径的时候,才会去访问磁盘,后面的访问会直接从缓存里找,下面会看到,很多与页告诉缓存打交道的代码。但不管怎样,第一遍查找总是会访问磁盘的。
static int __link_path_walk(const char *name,struct nameidata *nd)
{
}
至此,按照每一个component查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。