1. 书结上文,继续分析do_filp_open函数,其传入4个参数:
dfd:相对目录
tmp:文件路径名,例如要打开/usr/src/kernels/linux-2.6.30
flags:打开标志
mode:打开模式
/*
* Note that while the flag value (low two bits) for sys_open means:
* 00 - read-only
* 01 - write-only
* 10 - read-write
* 11 - special
* it is changed into
* 00 - no permissions needed
* 01 - read-permission
* 10 - write-permission
* 11 - read-write
* for the internal routines (ie open_namei()/follow_link() etc). 00 is
* used by symlinks.
*/
static struct file *do_filp_open(int dfd, const char *filename, int flags,
int mode)
{
int namei_flags, error;
/*创建nameidata结构体,返回的安装点对象和目录项对象放在此结构体*/
struct nameidata nd;
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE)
namei_flags++;
/*根据上级的dentry对象得到新的dentry结构,并从中得到相关的inode节点号,再用iget函数分配新的inode结构,将新的dentry对象与inode对象关联起来*/
error = open_namei(dfd, filename, namei_flags, mode, &nd);
/*将nameidata结构体转化为struct file文件对象结构体*/
if (!error)
return nameidata_to_filp(&nd, flags);
return ERR_PTR(error);
}
初看起来,寥寥几行代码,貌似简单。其实不然,一会就知道了。此函数调用了open_namei和nameidata_to_filp. 后一个函数通过名字就可以猜出来,是将nameidata结构转化为filp,也就是利用nd结构赋值给文件指针file,然后返回这个文件指针。而open_namei肯定是填充nd结构体,具体功能可表述为: 根据上级目录项对象,查询下一级的目录项对象,如果在目录项缓存找到下一级的目录项对象,则直接返回,并填充nd的挂载点对象和目录项对象。否则,构建一个子目录项对象,并利用iget函数分配一个新的inode结构,将子目录项对象和inode结构相关联。这样,一直循环到最后一下分量。最后返回的是最后一个分量的目录项对象和挂载点对象。可以看到,在这两个函数中,都利用了nameidata结构,具体看一下神奇的结构:
struct nameidata {
struct dentry *dentry;/*当前目录项对象*/
struct vfsmount *mnt;/*已安装的文件系统对象的地址*/
struct qstr last;/*路径名最后一部分*/
unsigned int flags;/*查询标志*/
int last_type;/*路径名最后一部分的类型*/
unsigned depth;/*当前符号链接的深度,一般小于6*/
char *saved_names[MAX_NESTED_LINKS + 1];/*关联符号链接的路径名数组*/
/* Intent data */
union {
struct open_intent open;/*想要打开的文件的联合体*/
} intent;
};
struct open_intent {
int flags;/*标志*/
int create_mode;/*创建模式*/
struct file *file;/*文件对象指针*/
};
open_intent文件对象就是最后返回的文件对象。
由于namidata_to_filp比较简单,先看一下:
/**将nameidata相关项赋值给struct file对象
* nameidata_to_filp - convert a nameidata to an open filp.
* @nd: pointer to nameidata
* @flags: open flags
*
* Note that this function destroys the original nameidata
*/
struct file *nameidata_to_filp(struct nameidata *nd, int flags)
{
struct file *filp;
/* Pick up the filp from the open intent */
/*取得文件指针*/
filp = nd->intent.open.file;
/* Has the filesystem initialised the file for us? */
/*文件系统是否已经初始化了dentry*/
if (filp->f_path.dentry == NULL)
filp = __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL);
else
path_release(nd);
return filp;
}
首先取得文件对象指针,然后判断文件对象是否已经初始化,如果没有初始化,就调用__dentry_open函数,对文件对象进行初始化。
/*对struct file结构体赋值*/
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
int flags, struct file *f,
int (*open)(struct inode *, struct file *))
{
struct inode *inode;
int error;
/*设置文件打开标志*/
f->f_flags = flags;
f->f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK |
FMODE_PREAD | FMODE_PWRITE;
/*取得inode节点*/
inode = dentry->d_inode;
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_file;
}
/*地址空间对象*/
f->f_mapping = inode->i_mapping;
/*目录项对象*/
f->f_path.dentry = dentry;
/*挂载点对象*/
f->f_path.mnt = mnt;
/*文件指针位置 */
f->f_pos = 0;
/*inode节点在初始化的时候已经赋值了i_fop,现在将文件操作赋值给f_op*/
f->f_op = fops_get(inode->i_fop);
file_move(f, &inode->i_sb->s_files);
/*文件open操作*/
if (!open && f->f_op)/*open为NULL*/
open = f->f_op->open;
/*普通文件open为空,如果是设备文件,需要打开*/
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
/*预读初始化*/
file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);
/* NB: we're sure to have correct a_ops only after f_op->open */
if (f->f_flags & O_DIRECT) {
if (!f->f_mapping->a_ops ||
((!f->f_mapping->a_ops->direct_IO) &&
(!f->f_mapping->a_ops->get_xip_page))) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
return f;
cleanup_all:
fops_put(f->f_op);
if (f->f_mode & FMODE_WRITE)
put_write_access(inode);
file_kill(f);
f->f_path.dentry = NULL;
f->f_path.mnt = NULL;
cleanup_file:
put_filp(f);
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
首先,设置文件打开标志f->f_flags. 然后初始化地址空间对象,目录项对象,挂载点对象,文件指针位置,文件相关操作。需要说明两点:
(1)地址空间对象和索引节点相关联,在构建索引节点时已经赋值了。它涉及到具体的磁盘块操作,在后面的章节将会解释。
(2)f_op这个非常重要,也是在构建索引节点时,将具体文件系统的文件操作函数集的指针赋给索引节点的i_fop域。对于打开文件,目录,符号链接,对应的操作函数集是不相同的。
接下来,第31行-38行,如果是普通文件,可能不需要打开。如果是设备文件,就需要打开操作。例如SCSI设备的sg_open函数。
最后,对文件预读进行初始化。
在说完nameidata_to_filp函数之后,需要解释open_namei函数:
/*
* open_namei()
*
* namei for open - this is in fact almost the whole open-routine.
*
* Note that the low bits of "flag" aren't the same as in the open
* system call - they are 00 - no permissions needed
* 01 - read permission needed
* 10 - write permission needed
* 11 - read/write permissions needed
* which is a lot more logical, and also allows the "no perm" needed
* for symlinks (where the permissions are checked later).
* SMP-safe
*/
int open_namei(int dfd, const char *pathname, int flag,
int mode, struct nameidata *nd)
{
int acc_mode, error;
/*定义path结构,包括安装点对象和目录项对象*/
struct path path;
struct dentry *dir;
int count = 0;
acc_mode = ACC_MODE(flag);
/* O_TRUNC implies we need access checks for write permissions */
/*截断标志,需要写权限*/
if (flag & O_TRUNC)
acc_mode |= MAY_WRITE;
/* Allow the LSM permission hook to distinguish append
access from general write access. */
if (flag & O_APPEND)
acc_mode |= MAY_APPEND;
/*
* The simplest case - just a plain lookup.
不需要创建文件,直接打开文件即可,创建目录项对象和挂载点对象,并将它们填充到nd结构体
*/
if (!(flag & O_CREAT)) {
error = path_lookup_open(dfd, pathname, lookup_flags(flag),
nd, flag);
if (error)
return error;
goto ok;
}
/*
* Create - we need to know the parent.
,由于是创建文件,即文件不存在,所以返回父目录项对象
在创建文件时设置 LOOKUP_PARENT
*/
error = path_lookup_create(dfd,pathname,LOOKUP_PARENT,nd,flag,mode);
if (error)
return error;
/*
* We have the parent and last component. First of all, check
* that we are not asked to creat(2) an obvious directory - that
* will not do.
*/
error = -EISDIR;
if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len])
goto exit;
/*对于创建文件,nd保存了上一个分量的目录项对象和挂载点对象。对于打开文件,nd保存了最后一个分量的目录项对象和挂载点对象*/
dir = nd->dentry;
nd->flags &= ~LOOKUP_PARENT;
mutex_lock(&dir->d_inode->i_mutex);
/*将path.dentry和mnt赋值*/
path.dentry = lookup_hash(nd);
path.mnt = nd->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)) {
mutex_unlock(&dir->d_inode->i_mutex);
error = PTR_ERR(nd->intent.open.file);
goto exit_dput;
}
/* Negative dentry, just create the file */
/*如果是创建文件*/
if (!path.dentry->d_inode) {
/*创建索引节点,并标识为*/
error = open_namei_create(nd, &path, flag, mode);
if (error)
goto exit;
return 0;
}
/*
* It already exists.
*/
mutex_unlock(&dir->d_inode->i_mutex);
audit_inode_update(path.dentry->d_inode);
error = -EEXIST;
if (flag & O_EXCL)
goto exit_dput;
if (__follow_mount(&path)) {
error = -ELOOP;
if (flag & O_NOFOLLOW)
goto exit_dput;
}
error = -ENOENT;
if (!path.dentry->d_inode)
goto exit_dput;
if (path.dentry->d_inode->i_op && path.dentry->d_inode->i_op->follow_link)
goto do_link;
/*将path的目录项对象和挂载点对象赋给nd*/
path_to_nameidata(&path, nd);
error = -EISDIR;
if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode))
goto exit;
ok:
error = may_open(nd, acc_mode, flag);
if (error)
goto exit;
return 0;
exit_dput:
dput_path(&path, nd);
exit:
if (!IS_ERR(nd->intent.open.file))
release_open_intent(nd);
path_release(nd);
return error;
do_link:
error = -ELOOP;
if (flag & O_NOFOLLOW)
goto exit_dput;
/*
* This is subtle. Instead of calling do_follow_link() we do the
* thing by hands. The reason is that this way we have zero link_count
* and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT.
* After that we have the parent and last component, i.e.
* we are in the same situation as after the first path_walk().
* Well, almost - if the last component is normal we get its copy
* stored in nd->last.name and we will have to putname() it when we
* are done. Procfs-like symlinks just set LAST_BIND.
*/
nd->flags |= LOOKUP_PARENT;
error = security_inode_follow_link(path.dentry, nd);
if (error)
goto exit_dput;
error = __do_follow_link(&path, nd);
if (error) {
/* Does someone understand code flow here? Or it is only
* me so stupid? Anathema to whoever designed this non-sense
* with "intent.open".
*/
release_open_intent(nd);
return 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->dentry;
mutex_lock(&dir->d_inode->i_mutex);
path.dentry = lookup_hash(nd);
path.mnt = nd->mnt;
__putname(nd->last.name);
goto do_last;
}
首先进行文件打开设置工作,第40行,如果是打开操作,则调用path_lookup_open函数。第53行,如果文件不存在,就创建一个文件,调用path_lookup_create函数。在第88行,如果是创建文件,需要建立磁盘上的索引节点,即调用open_namei_create函数。我们逐一解释:
首先path_lookup_open函数:
/**
* path_lookup_open - lookup a file path with open intent
* @dfd: the directory to use as base, or AT_FDCWD
* @name: pointer to file name
* @lookup_flags: lookup intent flags
* @nd: pointer to nameidata
* @open_flags: open intent flags
*/
int path_lookup_open(int dfd, const char *name, unsigned int lookup_flags,
struct nameidata *nd, int open_flags)
{
return __path_lookup_intent_open(dfd, name, lookup_flags, nd,
open_flags, 0);
}
封装了__path_lookup_intent_open函数。
path_lookup_create函数:
/**
* path_lookup_create - lookup a file path with open + create intent
* @dfd: the directory to use as base, or AT_FDCWD
* @name: pointer to file name
* @lookup_flags: lookup intent flags
* @nd: pointer to nameidata
* @open_flags: open intent flags
* @create_mode: create intent flags
*/
static int path_lookup_create(int dfd, const char *name,
unsigned int lookup_flags, struct nameidata *nd,
int open_flags, int create_mode)
{
return __path_lookup_intent_open(dfd, name, lookup_flags|LOOKUP_CREATE,
nd, open_flags, create_mode);
}
也封装了__path_lookup_intent_open函数,只是增加了创建标志LOOKUP_CREATE, 在create操作的lookup_flags设置了LOOKUP_PARENT,接下来,将看到这个标志的作用。
继续跟踪__path_lookup_intent_open函数:
static int __path_lookup_intent_open(int dfd, const char *name,
unsigned int lookup_flags, struct nameidata *nd,
int open_flags, int create_mode)
{
/*分配struct file对象指针*/
struct file *filp = get_empty_filp();
int err;
if (filp == NULL)
return -ENFILE;
/*想要打开的文件*/
nd->intent.open.file = filp;
/*打开标志*/
nd->intent.open.flags = open_flags;
/*创建模式*/
nd->intent.open.create_mode = create_mode;
/*调用do_path_lookup函数,设置LOOKUP_OPEN*/
err = do_path_lookup(dfd, name, lookup_flags|LOOKUP_OPEN, nd);
if (IS_ERR(nd->intent.open.file)) {
if (err == 0) {
err = PTR_ERR(nd->intent.open.file);
path_release(nd);
}
} else if (err != 0)
release_open_intent(nd);
return err;
}
首先调用get_empty_flip函数分配一个空闲的文件对象filp, 设置intent.open的相关域,包括“想要打开的文件”,打开标志和创建模式。最后,调用do_path_lookup对文件路径进行解析,并填充nd。
/*路径查找函数do_path_lookup*/
/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
static int fastcall do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
struct fs_struct *fs = current->fs;
/*如果只有斜线号,设置最后一个分量的类型为LAST_ROOT*/
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
nd->depth = 0;
/*如果是从根目录开始查找*/
if (*name=='/') {
read_lock(&fs->lock);
if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
/*nd->mnt设置为根安装点*/
nd->mnt = mntget(fs->altrootmnt);
/*nd->dentry开始目录项对象设置为根目录项对象*/
nd->dentry = dget(fs->altroot);
read_unlock(&fs->lock);
if (__emul_lookup_dentry(name,nd))
goto out; /* found in altroot */
read_lock(&fs->lock);
}
/*增加安装点的引用计数*/
nd->mnt = mntget(fs->rootmnt);
/*增加目录项的使用计数*/
nd->dentry = dget(fs->root);
read_unlock(&fs->lock);
/*如果是当前工作目录*/
} else if (dfd == AT_FDCWD) {
read_lock(&fs->lock);
/*从进程的fs_struct对象找到当前挂载点对象*/
nd->mnt = mntget(fs->pwdmnt);
/*从进程的fs_struct对象找到当前目录的目录项对象*/
nd->dentry = dget(fs->pwd);
read_unlock(&fs->lock);
} else {/*当dfd!=AT_FDCWD,这种情况也是有可能出现的*/
struct dentry *dentry;
/*根据dfd得到file对象*/
file = fget_light(dfd, &fput_needed);
retval = -EBADF;
if (!file)
goto out_fail;
/*目录项对象*/
dentry = file->f_path.dentry;
retval = -ENOTDIR;
if (!S_ISDIR(dentry->d_inode->i_mode))
goto fput_fail;
retval = file_permission(file, MAY_EXEC);
if (retval)
goto fput_fail;
/*nd->mnt赋值*/
nd->mnt = mntget(file->f_path.mnt);
/*nd->dentry赋值,f_path.dentry是和文件相关的目录项对象*/
nd->dentry = dget(dentry);
fput_light(file, fput_needed);
}
current->total_link_count = 0;
/*路径分解函数,调用实际文件系统操作*/
retval = link_path_walk(name, nd);
out:
if (likely(retval == 0)) {
if (unlikely(!audit_dummy_context() && nd && nd->dentry &&
nd->dentry->d_inode))
audit_inode(name, nd->dentry->d_inode);
}
out_fail:
return retval;
fput_fail:
fput_light(file, fput_needed);
goto out_fail;
}
第11-14行,设置初始化nd->last_type, flags和depth. 其中depth表示符号链接的深度。由于符号链接可以链接自己,因此需要限制链接的深度。
第16行,如果第一个字符为/,表示从根目录开始解析,设置nd->mnt为根挂载点对象,nd->dentry为根目录项对象,然后增加引用计数。
第34行,如果是从当前目录开始,将nd->mnt设置为当前目录的挂载点对象,nd->dentry设置为当前目录的目录项对象。
第41行,否则,将nd->mnt和nd->dentry分别设置为f_path.mnt和f_pat.dentry.
接下来,第63行,初始化符号链接总数,调用实际文件系统的路径分解函数link_path_walk.
int fastcall link_path_walk(const char *name, struct nameidata *nd)
{
struct nameidata save = *nd;
int result;
/* make sure the stuff we saved doesn't go away */
/*首先备份一下安装点对象和目录项对象*/
dget(save.dentry);
mntget(save.mnt);
/*真正的名称解析函数*/
result = __link_path_walk(name, nd);
if (result == -ESTALE) {
*nd = save;
dget(nd->dentry);
mntget(nd->mnt);
nd->flags |= LOOKUP_REVAL;
result = __link_path_walk(name, nd);
}
/*减少并释放备份的nameidata对象*/
dput(save.dentry);
mntput(save.mnt);
return result;
}
首先,备份挂载点对象和目录项对象,然后调用__link_path_walk解析.
这个函数也比较复杂,在下一节中继续分析!