内核源码:linux-2.6.38.8.tar.bz2
目标平台:ARM体系结构
在Linux系统中,打开或创建文件大概有以下三种形式:
/* fs/open.c */
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);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,
int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(dfd, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(4, ret, dfd, filename, flags, mode);
return ret;
}
SYSCALL_DEFINE2(creat, const char __user *, pathname, int, mode)
{
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
系统调用sys_creat仅仅是对sys_open函数的简单封装。
只有当参数filename为相对路径时,参数dfd才有意义,表示文件filename存在于文件描述符dfd所表示的目录之下。当dfd的值为AT_FDCWD时,则表示该文件位于调用进程的当前工作目录之下。
force_o_largefile函数表示当系统为64位时,默认配置O_LARGEFILE标志。
asmlinkage_protect函数只与x86体系结构有关。
do_sys_open函数的源代码如下所示:
/* fs/open.c */
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);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
1、IS_ERR函数用于判断指针函数的返回值是否为错误码,如果是,则表示该指针函数执行失败。而PTR_ERR函数用于将指针型的错误码转换为长整型的。
2、getname函数将文件名从用户空间拷贝到内核空间。putname函数则执行相反的操作。
3、get_unused_fd_flags函数用于获取尚未占用的文件描述符。put_unused_fd函数则执行相反的操作。
3.1、在Linux进程中,一个文件描述符唯一地表示一个打开的文件,而打开的文件使用struct file结构体来表示,简单地说,文件描述符就是struct file指针数组的索引。该指针数组保存在进程结构体struct task_struct的(struct files_struct *)files成员中。
/* include/linux/sched.h */
struct task_struct {
...
/* open file information */
struct files_struct *files;
...
};
/* include/linux/posix_types.h */
#undef __NFDBITS
#define __NFDBITS (8 * sizeof(unsigned long))
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
#undef __FDSET_LONGS
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) //在32位的系统上,它的值为32
typedef struct {
unsigned long fds_bits[__FDSET_LONGS];
} __kernel_fd_set;
/* include/linux/types.h */
typedef __kernel_fd_set fd_set;
/* arch/arm/include/asm/types.h */
#define BITS_PER_LONG 32
/* include/linux/fdtable.h */
#define NR_OPEN_DEFAULT BITS_PER_LONG
struct embedded_fd_set {
unsigned long fds_bits[1];
};
struct fdtable {
unsigned int max_fds; //当前可打开文件的最大数目,决定struct file指针数组fd的大小,close_on_exec和open_fds位图的比特位总数
struct file __rcu **fd; //指向内置它的struct files_struct结构体的成员fd_array或动态分配的struct file指针数组
fd_set *close_on_exec; //指向内置它的struct files_struct结构体的成员close_on_exec_init或动态分配的位图
fd_set *open_fds; //指向内置它的struct files_struct结构体的成员open_fds_init或动态分配的位图
//后两个成员在销毁时使用
struct rcu_head rcu; //rcu机制
struct fdtable *next; //用于加入fdtable_defer_list链表
};
struct files_struct {
atomic_t count; //引用计数
struct fdtable __rcu *fdt; //指向自身成员fdtab或动态分配的struct fdtable实例
struct fdtable fdtab; //当可打开文件的最大数目为NR_OPEN_DEFAULT时使用
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init; //位图,比特位数目刚好与NR_OPEN_DEFAULT一致
struct embedded_fd_set open_fds_init;
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
3.2、当使用系统调用sys_fork创建子进程而未配置CLONE_FILES标志时,进程结构体成员files的初始化由dup_fd函数来完成,它的主要功能是分配一个struct files_struct结构体实例并从旧实例*oldf中拷贝相关内容,如下所示:
/* fs/file.c */
struct files_struct *dup_fd(struct files_struct *oldf, int *errorp)
{
struct files_struct *newf;
struct file **old_fds, **new_fds;
int open_files, size, i;
struct fdtable *old_fdt, *new_fdt;
*errorp = -ENOMEM;
newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
if (!newf)
goto out;
atomic_set(&newf->count, 1);
spin_lock_init(&newf->file_lock); //初始化自旋锁
newf->next_fd = 0; //表示下一次打开文件时将要使用的文件描述符
//以默认值初始化newf->fdtab,其成员max_fds等于NR_OPEN_DEFAULT,close_on_exec指向newf->close_on_exec_init,
//open_fds指向newf->open_fds_init,fd指向newf->fd_array[0]等。
new_fdt = &newf->fdtab;
new_fdt->max_fds = NR_OPEN_DEFAULT;
new_fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
new_fdt->open_fds = (fd_set *)&newf->open_fds_init;
new_fdt->fd = &newf->fd_array[0];
new_fdt->next = NULL;
spin_lock(&oldf->file_lock);
old_fdt = files_fdtable(oldf); //从oldf->fdt中获取struct fdtable实例
open_files = count_open_files(old_fdt); //通过old_fdt->open_fds->fds_bits数组统计已打开的文件数,
//其值等于(最大文件描述符所在数组元素下标值 + 1) * 8 * sizeof(long)
//确认是否需要重新分配更大的内存空间来存储struct file指针数组和fd_set位图
while (unlikely(open_files > new_fdt->max_fds)) {
spin_unlock(&oldf->file_lock);
if (new_fdt != &newf->fdtab) //如果先前的struct fdtable实例是动态分配的,则须要先释放它
__free_fdtable(new_fdt);
//为struct fdtable自身及其struct file指针数组、close_on_exec位图和open_fds位图分配内存,
//并使用以open_files - 1值为参考而修正后的值来初始化max_fds成员,其值等于512乘以2的n次方(n为整数并大于等于0),
//且当该值超过sysctl_nr_open的大小时,则将该值向上修正为BITS_PER_LONG的倍数
new_fdt = alloc_fdtable(open_files - 1);
if (!new_fdt) {
*errorp = -ENOMEM;
goto out_release;
}
//如果open_files超过进程最大打开文件数限制sysctl_nr_open,则执行失败
if (unlikely(new_fdt->max_fds < open_files)) {
__free_fdtable(new_fdt);
*errorp = -EMFILE;
goto out_release;
}
//确认oldf->fdt在此期间是否已更新
spin_lock(&oldf->file_lock);
old_fdt = files_fdtable(oldf);
open_files = count_open_files(old_fdt);
}
old_fds = old_fdt->fd;
new_fds = new_fdt->fd;
//拷贝位图
memcpy(new_fdt->open_fds->fds_bits,
old_fdt->open_fds->fds_bits, open_files/8);
memcpy(new_fdt->close_on_exec->fds_bits,
old_fdt->close_on_exec->fds_bits, open_files/8);
for (i = open_files; i != 0; i--) {
struct file *f = *old_fds++;
if (f) {
get_file(f); //递增f->f_count计数器
} else {
//当struct file指针为NULL时,则清除new_fdt->open_fds位图中相应的比特位
FD_CLR(open_files - i, new_fdt->open_fds);
}
//拷贝struct file指针
rcu_assign_pointer(*new_fds++, f);
}
spin_unlock(&oldf->file_lock);
//置零struct file指针数组剩余元素
size = (new_fdt->max_fds - open_files) * sizeof(struct file *);
memset(new_fds, 0, size);
//置零位图剩余比特位
if (new_fdt->max_fds > open_files) {
int left = (new_fdt->max_fds-open_files)/8;
int start = open_files / (8 * sizeof(unsigned long));
memset(&new_fdt->open_fds->fds_bits[start], 0, left);
memset(&new_fdt->close_on_exec->fds_bits[start], 0, left);
}
//为newf->fdt指针赋值new_fdt
rcu_assign_pointer(newf->fdt, new_fdt);
return newf;
out_release:
kmem_cache_free(files_cachep, newf);
out:
return NULL;
}
3.3、通过alloc_fd函数获取尚未使用的最小的文件描述符(一个整数)。单个进程可打开文件数首先受到RLIMIT_NOFILE值的限制,然后受到sysctl_nr_open值的限制。
/* include/linux/file.h */
#define get_unused_fd_flags(flags) alloc_fd(0, (flags))
/* fs/file.c */
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值开始查找open_fds位图中第一个为零的比特位(也就是未使用的文件描述符)
fd = find_next_zero_bit(fdt->open_fds->fds_bits,
fdt->max_fds, fd);
//扩展*files->fdt,可扩展的范围受RLIMIT_NOFILE和sysctl_nr_open两者的限制
error = expand_files(files, fd);
if (error < 0)
goto out;
//如果扩展失败,则重复尝试,直到成功
if (error)
goto repeat;
if (start <= files->next_fd)
files->next_fd = fd + 1;
//置位open_fds和close_on_exec位图
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 1
//检查fdt->fd[fd]是否为NULL
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);
}
#endif
out:
spin_unlock(&files->file_lock);
return error;
}
4、fsnotify_open函数用于实现文件系统事件监控的IN_OPEN事件。
5、fd_install函数建立起文件指针(struct file *)f与文件描述符fd之间的关联。简单地说,就是将struct file指针存储在以文件描述符作为下标值的数组元素中。
6、do_filp_open函数执行打开或创建文件的实际操作。源代码如下所示:
/* fs/namei.c */
struct file *do_filp_open(int dfd, const char *pathname,
int open_flag, int mode, int acc_mode)
{
struct file *filp;
struct nameidata nd;
int error;
struct path path;
int count = 0;
//将open_flag中的最低两个比特位从00、01或10转变为相应的01、10或11,表示只读、只写或读写权限
int flag = open_to_namei_flags(open_flag);
int flags; //查找标志
if (!(open_flag & O_CREAT)) //只有在配置了O_CREAT标志时,mode参数才有效
mode = 0;
//禁止设置FMODE_NONOTIFY标志
open_flag &= ~FMODE_NONOTIFY;
//通过open等系统调用传进来的标志O_SYNC的值等于O_DSYNC
if (open_flag & __O_SYNC)
open_flag |= O_DSYNC;
//#define ACC_MODE(x) ("\004\002\006\006"[(x)&O_ACCMODE]),
//意思是根据open_flag最低两位的值来获取字符数组的值,下标0对应004,下标1对应002,下标2和3都对应006,
//即open_flag中的O_RDONLY、O_WRONLY和O_RDWR分别对应MAY_READ、MAY_WRITE和(MAY_READ|MAY_WRITE)。
if (!acc_mode)
acc_mode = MAY_OPEN | ACC_MODE(open_flag);
if (open_flag & O_TRUNC) //设置O_TRUNC标志时需要设置相应的写权限O_RDWR或O_WRONLY
acc_mode |= MAY_WRITE;
//允许Linux安全模块(Linux Security Module,LSM)的钩子函数区分append访问和普通的write访问。
//另外,目前基于LSM实现的安全框架有AppArmor、SELinux、Smack和TOMOYO等多种。
if (open_flag & O_APPEND)
acc_mode |= MAY_APPEND;
flags = LOOKUP_OPEN;
if (open_flag & O_CREAT) {
flags |= LOOKUP_CREATE;
if (open_flag & O_EXCL) //设置O_EXCL标志必须同时设置O_CREAT标志,否则不起作用
flags |= LOOKUP_EXCL;
}
if (open_flag & O_DIRECTORY) //设置O_DIRECTORY标志,如果pathname不是目录,则open执行失败
flags |= LOOKUP_DIRECTORY;
if (!(open_flag & O_NOFOLLOW)) //这里未设置O_NOFOLLOW标志,如果设置了O_NOFOLLOW标志,则在open符号链接文件时会失败
flags |= LOOKUP_FOLLOW;
filp = get_empty_filp();
if (!filp)
return ERR_PTR(-ENFILE);
filp->f_flags = open_flag; //保存open访问标志
//将文件指针filp、namei权限标志flag以及open访问权限mode分别存储于nd.intent.open相应的成员中
nd.intent.open.file = filp;
nd.intent.open.flags = flag;
nd.intent.open.create_mode = mode;
//如果设置了O_CREAT标志(表示如果文件不存在则创建),则直接跳转到标号creat处
if (open_flag & O_CREAT)
goto creat;
/* ----------------- 上半部分,简单地打开文件 ----------------------------------- */
error = do_path_lookup(dfd, pathname, flags, &nd); //查找操作,函数返回0时,保存在参数nd中的结果有效
if (unlikely(error))
goto out_filp2;
//对于路径中的最后一个目录项,当未配置查找标志LOOKUP_FOLLOW但有设置函数指针follow_link时,则返回错误码-ELOOP
error = -ELOOP;
if (!(nd.flags & LOOKUP_FOLLOW)) {
if (nd.inode->i_op->follow_link)
goto out_path2;
}
//对于路径中的最后一个目录项,当有配置查找标志LOOKUP_DIRECTORY但未设置函数指针lookup时,则返回错误码-ENOTDIR
error = -ENOTDIR;
if (nd.flags & LOOKUP_DIRECTORY) {
if (!nd.inode->i_op->lookup)
goto out_path2;
}
audit_inode(pathname, nd.path.dentry); //系统调用审计
filp = finish_open(&nd, open_flag, acc_mode); //打开操作
out2:
release_open_intent(&nd); //释放nd.intent.open.file(即filp)
return filp;
out_path2:
path_put(&nd.path); //释放nd.path.dentry和nd.path.mnt
out_filp2:
filp = ERR_PTR(error);
goto out2;
/* ------------------------ 下半部分,创建并打开文件 ---------------------------------- */
creat:
//与上半部分的查找操作一致,不过只查找到路径中的最后一个目录项的上一级,因为最后一个目录项可能尚未创建
error = path_init_rcu(dfd, pathname,
LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd); //这里只需要LOOKUP_PARENT和LOOKUP_REVAL这两个查找标志
if (error)
goto out_filp;
error = path_walk_rcu(pathname, &nd);
path_finish_rcu(&nd);
if (unlikely(error == -ECHILD || error == -ESTALE)) {
/* slower, locked walk */
if (error == -ESTALE) {
reval:
flags |= LOOKUP_REVAL;
}
error = path_init(dfd, pathname,
LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd);
if (error)
goto out_filp;
error = path_walk_simple(pathname, &nd);
}
if (unlikely(error))
goto out_filp;
if (unlikely(!audit_dummy_context()))
audit_inode(pathname, nd.path.dentry);
/* -----从标号creat开始至此的代码都为查找操作,其功能与do_path_lookup函数基本一致----- */
//处理路径中的最后一个目录项
nd.flags = flags; //查找标志赋原值
filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname); //参数path用于保存最后一个目录项
while (unlikely(!filp)) { //最后一个目录项为符号链接文件
struct path link = path;
struct inode *linki = link.dentry->d_inode;
void *cookie;
error = -ELOOP;
if (!(nd.flags & LOOKUP_FOLLOW)) //符号链接文件必须具备LOOKUP_FOLLOW查找标志
goto exit_dput;
if (count++ == 32) //最多循环32次
goto exit_dput;
nd.flags |= LOOKUP_PARENT; //表示查找到路径中的最后一个目录项的上一级
error = security_inode_follow_link(link.dentry, &nd); //安全模块检查
if (error)
goto exit_dput;
error = __do_follow_link(&link, &nd, &cookie);
if (unlikely(error)) { //错误返回
//cookie保存的是linki->i_op->follow_link函数指针所指向的函数的返回值
if (!IS_ERR(cookie) && linki->i_op->put_link)
linki->i_op->put_link(link.dentry, &nd, cookie);
nd.path = link; //这里保存link以便于后面统一的销毁操作
goto out_path;
}
nd.flags &= ~LOOKUP_PARENT;
filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
if (linki->i_op->put_link)
linki->i_op->put_link(link.dentry, &nd, cookie);
path_put(&link);
}
out:
if (nd.root.mnt)
path_put(&nd.root);
if (filp == ERR_PTR(-ESTALE) && !(flags & LOOKUP_REVAL)) //返回错误码ESTALE并且未配置LOOKUP_REVAL查找标志
goto reval;
release_open_intent(&nd);
return filp;
exit_dput:
path_put_conditional(&path, &nd);
out_path:
path_put(&nd.path);
out_filp:
filp = ERR_PTR(error);
goto out;
}
该函数以是否具有O_CREAT标志为特点被分为两个部分,要么只是简单的打开文件,要么就是创建并打开文件,只能执行其中之一。
6.1、struct nameidata结构体用来向函数传递参数,并保存这些函数操作后所获得的结果。
/* include/linux/namei.h */
enum { MAX_NESTED_LINKS = 8 };
struct nameidata {
struct path path; //传递参数或保存结果,初始值为根目录、当前目录或openat系统调用传入的文件描述符所表示的目录等三者之一
struct qstr last; //路径中最后一个目录项的名字(针对LOOKUP_PARENT标志)
struct path root; //根目录
struct file *file; //通过openat等系统调用传入的文件描述符所对应的struct file结构体实例
struct inode *inode; //始终指向更新后的path.dentry->d_inode
unsigned int flags; //查找标志
unsigned seq; //顺序计数,从path.dentry->d_seq中获取
int last_type; //成员last对应的文件类别,共LAST_NORM、LAST_ROOT、LAST_DOT、LAST_DOTDOT和LAST_BIND等五种
unsigned depth; //用于saved_names成员的索引,0在直接调用__do_follow_link函数时使用
char *saved_names[MAX_NESTED_LINKS + 1]; //在follow符号链接时,用于保存符号链接文件的内容,saved_names[0]在直接调用__do_follow_link函数时使用
/* Intent data */
union {
struct open_intent open;
} intent;
};
struct open_intent {
int flags; //namei权限标志
int create_mode; //在创建文件时将会使用的open等系统调用传入的访问权限
struct file *file; //当前正被打开或创建的文件的指针
};
/* include/linux/path.h */
struct path {
struct vfsmount *mnt; //文件或目录所在文件系统的相关信息
struct dentry *dentry; //目录项
};
/* include/linux/dcache.h */
struct qstr {
unsigned int hash; //文件名散列值
unsigned int len; //文件名长度
const unsigned char *name; //文件名
};
6.2、get_empty_filp函数用来分配struct file结构体实例(内存分配时有使用__GFP_ZERO标志,即表示所分配的内存在返回时已经全部清零)并初始化该实例的一些成员,如f_cred、f_security、f_u.fu_list、f_count、f_owner.lock、f_lock以及f_ep_links等。该函数执行成功与否还会受到内核可分配文件指针数的限制。一般情况下,可分配文件指针数至少为NR_FILE(8192)个,至多的个数与系统中内存的数量有关。如果当前进程具有CAP_SYS_ADMIN的能力,那么它就可以免受可分配文件指针数的限制。
6.3、__do_follow_link函数用于查找符号链接文件所指向的文件或目录的i节点。源代码如下所示:
/* fs/namei.c */
static __always_inline int
__do_follow_link(const struct path *link, struct nameidata *nd, void **p)
{
int error;
struct dentry *dentry = link->dentry;
BUG_ON(nd->flags & LOOKUP_RCU); //不能使用rcu-walk查找模式
touch_atime(link->mnt, dentry); //更新目录项所对应的i节点的访问时间
nd_set_link(nd, NULL); //将nd->saved_names[nd->depth]赋值为空指针
if (link->mnt == nd->path.mnt) //符号链接文件及其所在的目录在同一文件系统
mntget(link->mnt);
nd->last_type = LAST_BIND;
*p = dentry->d_inode->i_op->follow_link(dentry, nd); //获取符号链接文件的内容(保存在nd中)
error = PTR_ERR(*p);
if (!IS_ERR(*p)) { //成功返回
char *s = nd_get_link(nd); //返回nd->saved_names[nd->depth]所指向的路径
error = 0;
if (s) //可能为NULL,也可能为指针型的错误码
error = __vfs_follow_link(nd, s);
else if (nd->last_type == LAST_BIND) {
error = force_reval_path(&nd->path, nd); //尝试使目录项有效,如果失败则使之无效
if (error)
path_put(&nd->path);
}
}
return error;
}
static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
{
int ret;
if (IS_ERR(link)) //为指针型的错误码
goto fail;
if (*link == '/') { //link为绝对路径
set_root(nd); //尚未设置nd->root时则设置它
path_put(&nd->path);
nd->path = nd->root;
path_get(&nd->root);
}
nd->inode = nd->path.dentry->d_inode;
ret = link_path_walk(link, nd); //查找路径link
return ret;
fail:
path_put(&nd->path);
return PTR_ERR(link);
}
6.4、do_path_lookup函数的主要作用是根据路径名来查找相应文件的目录项及其i节点。该函数返回0时则表示执行成功。内核中根据路径名查找文件的操作都与该函数类似或直接使用该函数。源代码如下所示:
/* fs/namei.c */
static int do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
int retval;
/* 路径查找分为rcu-walk和ref-walk等两种不同的同步方式 */
retval = path_init_rcu(dfd, name, flags, nd);
if (unlikely(retval))
return retval;
retval = path_walk_rcu(name, nd);
path_finish_rcu(nd); //释放锁,清除LOOKUP_RCU查找标志,释放nd->file
if (nd->root.mnt) {
path_put(&nd->root);
nd->root.mnt = NULL;
}
//为避免混淆,这里使用错误码ECHILD来表示rcu-walk相关函数的执行失败,与该错误码本身的意思无关
if (unlikely(retval == -ECHILD || retval == -ESTALE)) { //-ECHILD表示rcu-walk相关函数执行失败
/* slower, locked walk */
if (retval == -ESTALE) //-ESTALE表示d_revalidate函数返回零
flags |= LOOKUP_REVAL; //重新使目录项有效
retval = path_init(dfd, name, flags, nd);
if (unlikely(retval))
return retval;
retval = path_walk(name, nd);
if (nd->root.mnt) {
path_put(&nd->root);
nd->root.mnt = NULL;
}
}
//系统调用审计
if (likely(!retval)) {
if (unlikely(!audit_dummy_context())) {
if (nd->path.dentry && nd->inode)
audit_inode(name, nd->path.dentry);
}
}
return retval;
}
path_init函数用于初始化struct nameidata实例*nd的一些成员,如last_type初始化为LAST_ROOT;flags初始化为查找标志;depth初始化为零;当name为绝对路径时,root初始化为当前进程的根目录current->fs->root,否则root.mnt初始化为空指针;path的初始化分三种情况,a、当name为绝对路径时,path初始化为当前进程的根目录current->fs->root,否则,b、当dfd为AT_FDCWD值时,path初始化为当前进程的当前目录current->fs->pwd,否则,c、path就初始化为current->files->fdt->fd[dfd]->f_path,即当前进程的以文件描述符dfd所表示的文件的path值,并且该文件的类型必须是目录,且具有能够进入该目录的权限;当path初始化完成之后,inode才能初始化为path.dentry->d_inode。相对于path_init函数,path_init_rcu函数则多做了一些初始化工作,比如在初始化path成员之后,初始化seq为path.dentry->d_seq->sequence值; 当name既不是绝对路径,而dfd又不等于AT_FDCWD值时,file初始化为current->files->fdt->fd[dfd](在其成员f_count不为零时),否则file的值为空指针;flags多了LOOKUP_RCU查找标志;加锁RCU读端临界区以及加锁vfsmount_lock。
path_walk_rcu、path_walk_simple和path_walk等函数都只是对link_path_walk函数(该函数是查找操作的核心)的简单封装,源代码如下所示:
/* fs/namei.c */
static inline int path_walk_rcu(const char *name, struct nameidata *nd)
{
current->total_link_count = 0; //清零单个文件路径处理符号链接的次数
return link_path_walk(name, nd);
}
static inline int path_walk_simple(const char *name, struct nameidata *nd)
{
current->total_link_count = 0;
return link_path_walk(name, nd);
}
static int path_walk(const char *name, struct nameidata *nd)
{
struct path save = nd->path;
int result;
current->total_link_count = 0;
/* make sure the stuff we saved doesn't go away */
path_get(&save);
result = link_path_walk(name, nd);
if (result == -ESTALE) { //从头开始查找,并配置LOOKUP_REVAL查找标志
/* nd->path had been dropped */
current->total_link_count = 0;
nd->path = save;
nd->inode = save.dentry->d_inode;
path_get(&nd->path);
nd->flags |= LOOKUP_REVAL;
result = link_path_walk(name, nd);
}
path_put(&save);
return result;
}
/* fs/namei.c */
//目录项:如路径/bin/ls,第一个斜杠、bin和ls都是目录项,目录项不是目录的意思
static int link_path_walk(const char *name, struct nameidata *nd)
{
struct path next;
int err;
unsigned int lookup_flags = nd->flags;
while (*name=='/') //跳过根目录及多余的前导斜杠字符
name++;
if (!*name) //路径名为空
goto return_reval;
if (nd->depth) //非零表示现在所处理的是符号链接所指向的路径名
lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE); //这里只需要LOOKUP_FOLLOW和LOOKUP_CONTINUE查找标志
//依次处理路径中的每个目录项
for(;;) {
struct inode *inode;
unsigned long hash;
struct qstr this;
unsigned int c;
nd->flags |= LOOKUP_CONTINUE;
if (nd->flags & LOOKUP_RCU) {
//调用nd->inode->i_op->permission函数指针所指向的函数或acl_permission_check函数来检查其是否具有搜索目录的权限
err = exec_permission(nd->inode, IPERM_FLAG_RCU);
if (err == -ECHILD) {
if (nameidata_drop_rcu(nd)) //释放锁、递增当前目录项及其所在文件系统的引用计数等
return -ECHILD;
goto exec_again; //rcu-walk类型的exec_permission函数执行失败,则跳转到执行ref-walk类型的
}
} else {
exec_again:
err = exec_permission(nd->inode, 0);
}
if (err)
break;
//计算当前目录项名字的长度和散列值(按字符依次处理)
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash();
do {
name++;
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
} while (c && (c != '/')); //当c不为空字符且不为斜杠字符时,循环继续
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash);
if (!c) //c为空字符则表示将处理路径name中的最后一个目录项
goto last_component;
while (*++name == '/'); //跳过多余的路径分隔符(斜杠),指向下一个目录项名字
if (!*name) //由上一语句和当前条件表明路径名尾部带有斜杠,被认为是一个目录
goto last_with_slashes;
//处理“.”和“..”目录项
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
//“..”表示父目录
if (nd->flags & LOOKUP_RCU) {
if (follow_dotdot_rcu(nd))
return -ECHILD;
} else
follow_dotdot(nd); //返回父目录,并更新struct nameidata结构体实例中的path和inode成员
/* fallthrough */
case 1:
continue; //“.”表示当前目录,则直接跳转到循环开头,接着处理下一个目录项
}
//next用于保存所要查找的文件或目录的struct path实例,而inode保存的是它的struct inode实例
err = do_lookup(nd, &this, &next, &inode);
if (err)
break;
err = -ENOENT;
if (!inode) //检查是否获得相应的i节点
goto out_dput;
//更新struct nameidata结构体实例nd中的path和inode成员
if (inode->i_op->follow_link) { //根据follow_link函数指针是否为NULL来判断该目录项所对应的文件类型是否为符号链接文件
err = do_follow_link(inode, &next, nd); //解析符号链接文件
if (err)
goto return_err;
nd->inode = nd->path.dentry->d_inode;
err = -ENOENT;
if (!nd->inode)
break;
} else { //非符号链接文件
path_to_nameidata(&next, nd);
nd->inode = inode;
}
err = -ENOTDIR;
if (!nd->inode->i_op->lookup) //路径中间的目录项名字对应的文件类型必须是目录,所以lookup函数指针必须定义
break;
continue;
//执行流程从这里重新回到循环开头
last_with_slashes: //对于路径名尾部带有斜杠的情况,必须配置LOOKUP_FOLLOW和LOOKUP_DIRECTORY查找标志
lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
nd->flags &= lookup_flags | ~LOOKUP_CONTINUE; //如果之前没有配置查找标志LOOKUP_CONTINUE则清除它
if (lookup_flags & LOOKUP_PARENT) //跳过路径中最后一个目录项的查找
goto lookup_parent;
//处理“.”和“..”目录项
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
if (nd->flags & LOOKUP_RCU) {
if (follow_dotdot_rcu(nd))
return -ECHILD;
} else
follow_dotdot(nd);
/* fallthrough */
case 1:
goto return_reval;
}
err = do_lookup(nd, &this, &next, &inode); //查找路径中的最后一个目录项
if (err)
break;
if (inode && unlikely(inode->i_op->follow_link) &&
(lookup_flags & LOOKUP_FOLLOW)) { //这里须要判断是否具有LOOKUP_FOLLOW查找标志,如果这里没有配置该标志,则表示操作符号链接文件自身
err = do_follow_link(inode, &next, nd);
if (err)
goto return_err;
nd->inode = nd->path.dentry->d_inode;
} else {
path_to_nameidata(&next, nd);
nd->inode = inode;
}
err = -ENOENT;
if (!nd->inode) //如果i节点不存在,则执行失败
break;
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!nd->inode->i_op->lookup)
break;
}
goto return_base; //跳转
lookup_parent: //路径中最后的目录项名字对应的i节点可能尚未创建,所以这里无须查找,查找到它的上一级目录项即可
nd->last = this;
nd->last_type = LAST_NORM; //普通文件
if (this.name[0] != '.')
goto return_base;
if (this.len == 1)
nd->last_type = LAST_DOT;
else if (this.len == 2 && this.name[1] == '.')
nd->last_type = LAST_DOTDOT;
else
goto return_base;
return_reval:
if (need_reval_dot(nd->path.dentry)) { //当目录项允许验证其有效性,且允许验证“.”和“..”目录项
if (nameidata_drop_rcu_last_maybe(nd))
return -ECHILD;
err = d_revalidate(nd->path.dentry, nd); //验证目录项的有效性并且不会调用d_invalidate函数来使之无效
if (!err)
err = -ESTALE;
if (err < 0)
break;
return 0;
}
return_base:
if (nameidata_drop_rcu_last_maybe(nd))
return -ECHILD;
return 0;
out_dput:
if (!(nd->flags & LOOKUP_RCU))
path_put_conditional(&next, nd);
break;
}
if (!(nd->flags & LOOKUP_RCU))
path_put(&nd->path);
return_err:
return err;
}
follow_dotdot函数用于返回父目录,分三种情况,如果当前目录为根目录则无操作,如果当前目录不是当前文件系统的相对根目录(nd->path.mnt->mnt_root),则简单地更新nd->path.dentry到nd->path.dentry->d_parent即可,否则不仅需要更新nd->path.dentry到nd->path->mnt->mnt_mountpoint(挂载点在父文件系统中的struct dentry实例),还需要更新nd->path->mnt到nd->path->mnt->mnt_parent。其中mnt_root和mnt_mountpoint两个struct dentry实例表示同一目录,即挂载点。之后,nd->inode 在三种情况下都更新为nd->path.dentry->d_inode。相对follow_dotdot函数,follow_dotdot_rcu函数则会增加更新nd->seq成员,其他更新一致。如果所返回的父目录为挂载点,则须要调用follow_mount函数来加以处理。
do_follow_link函数是对__do_follow_link函数的封装。该函数判断并递增(未达到限制时)link_count、total_link_count和depth等计数,然后调用__do_follow_link函数,而__do_follow_link函数最终可能还会调用link_path_walk函数,从而有可能形成死循环,造成系统不可用,所以Linux内核采用link_count计数来防止连续链接超过MAX_NESTED_LINKS(目前为8)次,并且path_walk等函数单次执行所处理的符号链接数total_link_count不能超过40次。link_count和total_link_count这两个计数是当前进程struct task_struct结构体实例的成员。源代码如下所示:
/* fs/namei.c */
static inline int do_follow_link(struct inode *inode, struct path *path, struct nameidata *nd)
{
void *cookie;
int err = -ELOOP;
if (nameidata_dentry_drop_rcu_maybe(nd, path->dentry)) //销毁rcu-walk查找模式所使用的数据
return -ECHILD;
BUG_ON(inode != path->dentry->d_inode); //两者必须一致
if (current->link_count >= MAX_NESTED_LINKS) //这里MAX_NESTED_LINKS的值为8
goto loop;
if (current->total_link_count >= 40)
goto loop;
BUG_ON(nd->depth >= MAX_NESTED_LINKS); //必须小于MAX_NESTED_LINKS
cond_resched(); //有条件的调用schedule函数自动放弃CPU
err = security_inode_follow_link(path->dentry, nd); //安全模式检查
if (err)
goto loop;
//递增计数
current->link_count++;
current->total_link_count++;
nd->depth++;
err = __do_follow_link(path, nd, &cookie);
//cookie保存的是path->dentry->d_inode->i_op->follow_link函数指针所指向的函数的返回值
if (!IS_ERR(cookie) && path->dentry->d_inode->i_op->put_link) //follow_link所指函数执行成功并且put_link函数指针为真
path->dentry->d_inode->i_op->put_link(path->dentry, nd, cookie);
path_put(path);
//递减计数,除current->total_link_count之外
current->link_count--;
nd->depth--;
return err;
loop:
path_put_conditional(path, nd);
path_put(&nd->path);
return err;
}
do_lookup函数根据目录项struct qstr形式的名称从全局的目录项哈希链表中查找,如果链表中不存在该目录项,则需要调用特定文件系统的i节点(该i节点属于上一级目录项)的lookup函数指针所指向的函数,从该文件系统中查找并构建相应的目录项实例及其对应的i节点实例(只有在将要构建该i节点所需的信息存在于该文件系统中时)。源代码如下:
/* fs/namei.c */
static int do_lookup(struct nameidata *nd, struct qstr *name,
struct path *path, struct inode **inode)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry;
struct inode *dir;
int err;
//使用特定文件系统的哈希算法来重新计算目录项名称的哈希值
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
err = parent->d_op->d_hash(parent, nd->inode, name);
if (err < 0)
return err;
}
//以rcu-walk模式查找
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
*inode = nd->inode;
dentry = __d_lookup_rcu(parent, name, &seq, inode); //从哈希链表中查找目录项
if (!dentry) {
if (nameidata_drop_rcu(nd))
return -ECHILD;
goto need_lookup; //当哈希链表中不存在该目录项时则跳转到标号need_lookup
}
//这里*inode已更新为dentry->d_inode
//顺序计数不一致,表示目录项parent可能已经被修改
if (__read_seqcount_retry(&parent->d_seq, nd->seq))
return -ECHILD;
nd->seq = seq; //保存当前目录项的顺序计数
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
dentry = do_revalidate_rcu(dentry, nd);
if (!dentry) //dentry为NULL
goto need_lookup;
if (IS_ERR(dentry)) //dentry为错误码
goto fail;
if (!(nd->flags & LOOKUP_RCU)) //未配置LOOKUP_RCU查找标志则跳转到标号done
goto done;
}
path->mnt = mnt;
path->dentry = dentry;
//处理path->dentry->d_flags的DCACHE_MOUNTED、DCACHE_MANAGE_TRANSIT和DCACHE_NEED_AUTOMOUNT等三个标志
//特别的,这里如果有配置DCACHE_NEED_AUTOMOUNT标志,则__follow_mount_rcu函数直接返回false
if (likely(__follow_mount_rcu(nd, path, inode, false)))
return 0; //成功返回
if (nameidata_drop_rcu(nd))
return -ECHILD;
//如果以rcu-walk模式查找失败,则后面以ref-walk模式继续查找
}
dentry = __d_lookup(parent, name);
if (!dentry) //dentry为NULL
goto need_lookup;
found:
//标志DCACHE_OP_REVALIDATE表示dentry->d_op->d_revalidate函数指针为真
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
dentry = do_revalidate(dentry, nd);
if (!dentry) //dentry为NULL
goto need_lookup;
if (IS_ERR(dentry)) //dentry为错误码
goto fail;
}
done:
//保存path
path->mnt = mnt;
path->dentry = dentry;
//处理path->dentry->d_flags的DCACHE_MOUNTED、DCACHE_MANAGE_TRANSIT和DCACHE_NEED_AUTOMOUNT等三个标志
err = follow_managed(path, nd->flags);
if (unlikely(err < 0)) {
path_put_conditional(path, nd);
return err;
}
*inode = path->dentry->d_inode;
return 0; //成功返回
need_lookup:
dir = parent->d_inode;
BUG_ON(nd->inode != dir);
mutex_lock(&dir->i_mutex);
//如果前面的查找失败是因为不相关的重命名或在等待目录信号量时它已经被创建则再次查找
dentry = d_lookup(parent, name);
if (likely(!dentry)) { //dentry为NULL,likely表示dentry为NULL的可能性很大
dentry = d_alloc_and_lookup(parent, name, nd);
mutex_unlock(&dir->i_mutex);
if (IS_ERR(dentry)) //dentry为错误码
goto fail;
goto done;
}
//在等待释放互斥锁时目录项缓存已经重新填充,所以需要重新验证该目录项,即跳转到标号found
mutex_unlock(&dir->i_mutex);
goto found;
fail:
return PTR_ERR(dentry);
}
__d_lookup函数只是简单地根据名字从目录项缓存哈希链表中查找所需的目录项。d_lookup函数是对__d_lookup函数的加锁(读端顺序锁rename_lock)封装。__d_lookup_rcu函数的功能与__d_lookup函数一致,只是其采用的加锁方式不一样,且只能用于rcu-walk查找模式。源代码如下:
/* fs/dcache.c */
struct dentry *__d_lookup(struct dentry *parent, struct qstr *name)
{
unsigned int len = name->len;
unsigned int hash = name->hash;
const unsigned char *str = name->name;
struct dcache_hash_bucket *b = d_hash(parent, hash); //确定所在链式哈希表dentry_hashtable的桶
struct hlist_bl_node *node;
struct dentry *found = NULL;
struct dentry *dentry;
rcu_read_lock();
/* 这里的d_hash是结构体struct dentry的成员,类型为struct hlist_bl_node;
** b->head是链表头;node指向链表元素;dentry指向链表元素node对应的struct dentry结构体实例。*/
hlist_bl_for_each_entry_rcu(dentry, node, &b->head, d_hash) {
const char *tname;
int tlen;
if (dentry->d_name.hash != hash) //哈希值不一致
continue;
spin_lock(&dentry->d_lock);
if (dentry->d_parent != parent) //父目录项不一致
goto next; //查找失败
if (d_unhashed(dentry)) //无效目录项
goto next;
tlen = dentry->d_name.len;
tname = dentry->d_name.name;
if (parent->d_flags & DCACHE_OP_COMPARE) { //DCACHE_OP_COMPARE标志表示函数指针d_compare为真
if (parent->d_op->d_compare(parent, parent->d_inode,
dentry, dentry->d_inode,
tlen, tname, name)) //函数返回零表示查找成功
goto next;
} else {
if (dentry_cmp(tname, tlen, str, len)) //通用比较函数
goto next;
}
dentry->d_count++;
found = dentry;
spin_unlock(&dentry->d_lock);
break;
next:
spin_unlock(&dentry->d_lock);
}
rcu_read_unlock();
return found;
}
do_revalidate函数用于确认从哈希链表中所查找到的目录项及其相应的数据是否仍然有效。该函数通过d_revalidate函数直接调用特定文件系统的dentry->d_op->d_revalidate函数指针所指向的函数,如果所指向的函数的返回值为零则调用d_invalidate函数使该目录项无效。do_revalidate_rcu函数的功能与do_revalidate函数基本一致,在其所调用的d_revalidate函数的返回值为错误码-ECHILD时则会调用do_revalidate函数。
follow_managed函数用于处理目录项的DCACHE_MOUNTED、DCACHE_NEED_AUTOMOUNT和DCACHE_MANAGE_TRANSIT等三个标志。源代码如下:
/* fs/namei.c */
static int follow_managed(struct path *path, unsigned flags)
{
unsigned managed;
bool need_mntput = false;
int ret;
while (managed = ACCESS_ONCE(path->dentry->d_flags),
managed &= DCACHE_MANAGED_DENTRY,
unlikely(managed != 0)) { //表达式里的逗号为运算符,所以整个表达式的值为managed != 0的值
if (managed & DCACHE_MANAGE_TRANSIT) { //标志DCACHE_MANAGE_TRANSIT表示d_manage函数指针为真
BUG_ON(!path->dentry->d_op);
BUG_ON(!path->dentry->d_op->d_manage);
//目前只用于autofs文件系统,用于监控自动挂载的状态
ret = path->dentry->d_op->d_manage(path->dentry,
false, false);
if (ret < 0)
return ret == -EISDIR ? 0 : ret; //这里的-EISDIR表示忽略其上的所有挂载
}
//挂载点对应的目录在内核中使用两个目录项来表示,一个属于父文件系统,另一个属于当前文件系统
if (managed & DCACHE_MOUNTED) {
struct vfsmount *mounted = lookup_mnt(path); //查找当前文件系统的struct vfsmount实例
if (mounted) {
dput(path->dentry);
if (need_mntput)
mntput(path->mnt);
path->mnt = mounted;
path->dentry = dget(mounted->mnt_root); //挂载点在当前文件系统中的表示
need_mntput = true;
continue;
}
}
//标志DCACHE_NEED_AUTOMOUNT表示path->dentry->d_op->d_automount函数指针为真
if (managed & DCACHE_NEED_AUTOMOUNT) {
ret = follow_automount(path, flags, &need_mntput); //处理自动加载
if (ret < 0)
return ret == -EISDIR ? 0 : ret; //这里的-EISDIR表示其被当作普通目录,查找继续
continue;
}
break;
}
return 0;
}
d_alloc_and_lookup函数用于当name所对应的目录项在目录项缓存哈希链表中不存在时,调用d_alloc函数分配目录项新实例并初始化成员,然后调用父目录项的parent->d_inode->i_op->lookup函数指针所指向的函数从相应的文件系统中获取i节点并通过d_splice_alias函数将其关联到新分配的目录项(该目录项会被添加到目录项缓存相应的哈希链表中),其中i节点的获得是先从父目录的内容中获得该i节点的编号,然后根据该编号从存储器中获取i节点。
6.5、finish_open函数执行真正的打开操作,源代码如下:
/* fs/namei.c */
static struct file *finish_open(struct nameidata *nd,
int open_flag, int acc_mode)
{
struct file *filp;
int will_truncate;
int error;
//是否截断文件,设备文件、管道文件和套接字文件忽略该标志
will_truncate = open_will_truncate(open_flag, nd->path.dentry->d_inode);
if (will_truncate) {
//获取所在文件系统的写权限,如果nd->path.mnt->mnt_flags配置有MNT_READONLY标志
//或者nd->path.mnt->mnt_sb->s_flags配置有MS_RDONLY标志,则该文件系统只有读权限。
error = mnt_want_write(nd->path.mnt);
if (error)
goto exit;
}
error = may_open(&nd->path, acc_mode, open_flag);
if (error) {
if (will_truncate)
//递减上一步mnt_want_write函数所递增的nd->path.mnt->mnt_writers
//(或者nd->path.mnt->mnt_pcp->mnt_writers(配置CONFIG_SMP时))的值
mnt_drop_write(nd->path.mnt);
goto exit;
}
filp = nameidata_to_filp(nd);
if (!IS_ERR(filp)) {
//总是返回零,IMA(Integrity Measurement Architecture,完整性度量架构)
error = ima_file_check(filp, acc_mode);
if (error) {
fput(filp);
filp = ERR_PTR(error);
}
}
if (!IS_ERR(filp)) {
if (will_truncate) {
error = handle_truncate(filp); //文件大小截断为零
if (error) {
fput(filp);
filp = ERR_PTR(error);
}
}
}
if (will_truncate)
mnt_drop_write(nd->path.mnt);
path_put(&nd->path);
return filp;
exit:
path_put(&nd->path);
return ERR_PTR(error);
}
may_open函数并没有执行文件的打开操作,而只是处理打开标志的特殊情形以及i节点访问权限的确认,源代码如下:
/* fs/namei.c */
int may_open(struct path *path, int acc_mode, int flag)
{
struct dentry *dentry = path->dentry;
struct inode *inode = dentry->d_inode;
int error;
if (!inode)
return -ENOENT;
//共七种文件类型
switch (inode->i_mode & S_IFMT) {
case S_IFLNK:
return -ELOOP; //对于符号链接文件,则直接错误返回
case S_IFDIR:
if (acc_mode & MAY_WRITE) //对于目录,则只能只读打开
return -EISDIR;
break;
case S_IFBLK:
case S_IFCHR:
if (path->mnt->mnt_flags & MNT_NODEV) //当所在文件系统不允许访问设备文件时则错误返回
return -EACCES;
/*FALLTHRU*/
case S_IFIFO:
case S_IFSOCK:
flag &= ~O_TRUNC; //清除O_TRUNC标志
break;
}
//检查i节点的访问权限,调用通用函数generic_permission或特定于文件系统的permission函数,
//即inode->i_op->permission函数指针所指向的函数。
error = inode_permission(inode, acc_mode);
if (error)
return error;
//打开只能执行追加操作的文件时,打开标志必须配置O_WRONLY(或O_RDWR)和O_APPEND等两个标志,
//且不能配置O_TRUNC标志。
if (IS_APPEND(inode)) {
if ((flag & O_ACCMODE) != O_RDONLY && !(flag & O_APPEND))
return -EPERM;
if (flag & O_TRUNC)
return -EPERM;
}
//打开标志O_NOATIME只能由属主或特权用户来配置
if (flag & O_NOATIME && !is_owner_or_cap(inode))
return -EPERM;
//撤销该文件上所有未完成的租约
return break_lease(inode, flag);
}
nameidata_to_filp函数用于将nd中保存的一些信息拷贝到nd->intent.open.file所指向的struct file结构体实例中并执行最终的打开操作,源代码如下:
/* fs/open.c */
struct file *nameidata_to_filp(struct nameidata *nd)
{
const struct cred *cred = current_cred();
struct file *filp;
filp = nd->intent.open.file; //在do_filp_open函数中由get_empty_filp函数生成
nd->intent.open.file = NULL;
//一般情况下,这时的filp->f_path.dentry尚未赋值
if (filp->f_path.dentry == NULL) {
path_get(&nd->path);
filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp,
NULL, cred);
}
return filp;
}
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
struct file *f,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
struct inode *inode;
int error;
//OPEN_FMODE将打开标志O_RDONLY、O_WRONLY或O_RDWR转换为相应的FMODE_READ、FMODE_WRITE或(FMODE_READ|FMODE_WRITE)
f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |
FMODE_PREAD | FMODE_PWRITE;
inode = dentry->d_inode;
if (f->f_mode & FMODE_WRITE) {
error = __get_file_write_access(inode, mnt); //针对非特殊文件,判断所在文件系统是否具有写权限
if (error)
goto cleanup_file;
if (!special_file(inode->i_mode))
file_take_write(f); //将f->f_mnt_write_state设置为FILE_MNT_WRITE_TAKEN
}
f->f_mapping = inode->i_mapping; //指向文件内容
f->f_path.dentry = dentry;
f->f_path.mnt = mnt;
f->f_pos = 0; //文件偏移量
f->f_op = fops_get(inode->i_fop);
file_sb_list_add(f, inode->i_sb); //通过f->f_u.fu_list成员将该文件指针添加到超级块的inode->i_sb->s_files链表中
error = security_dentry_open(f, cred); //安全模块检查
if (error)
goto cleanup_all;
if (!open && f->f_op)
open = f->f_op->open; //特定文件系统的i节点的open函数
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
ima_counts_get(f); //针对普通文件
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); //清除不再使用的打开标志
file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping); //初始化预读状态的成员f->f_ra.ra_pages和f->f_ra.prev_pos
//当配置有O_DIRECT打开标志时,direct_IO和get_xip_mem函数指针必须为真
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_mem))) {
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);
if (!special_file(inode->i_mode)) {
file_reset_write(f);
mnt_drop_write(mnt);
}
}
file_sb_list_del(f);
f->f_path.dentry = NULL;
f->f_path.mnt = NULL;
cleanup_file:
put_filp(f);
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
handle_truncate函数执行文件的截断操作(将文件截断为零)并实现文件系统事件监控的IN_ACCESS、IN_ATTRIB以及IN_MODIFY事件。文件的截断操作实际由i节点的setattr函数指针(即filp->f_path.dentry->d_inode->i_op->setattr)所指向的函数来执行,这个特定于文件系统的函数一般会首先置零该i节点的i_size成员,然后调用该i节点的truncate函数指针所指向的函数来完成最终的操作。如果truncate函数指针为空值,则忽略该步骤,因为有些类型的文件(如符号链接文件、目录等)不能执行截断操作。
6.6、do_last函数只有在do_filp_open函数设置有O_CREAT标志时才执行。源代码如下:
/* fs/namei.c */
static struct file *do_last(struct nameidata *nd, struct path *path,
int open_flag, int acc_mode,
int mode, const char *pathname)
{
struct dentry *dir = nd->path.dentry;
struct file *filp;
int error = -EISDIR;
switch (nd->last_type) {
case LAST_DOTDOT: //“..”表示父目录
follow_dotdot(nd);
dir = nd->path.dentry;
case LAST_DOT: //“.”表示当前目录
//该目录项具备DCACHE_OP_REVALIDATE标志,且相应的文件系统具备FS_REVAL_DOT标志则为真
if (need_reval_dot(dir)) {
int status = d_revalidate(nd->path.dentry, nd);
if (!status) //返回零
status = -ESTALE;
if (status < 0) {
error = status;
goto exit; //错误返回
}
}
case LAST_ROOT: //根目录无须创建也不能通过带O_CREAT标志来打开
goto exit;
case LAST_BIND: //符号链接文件的内容为空
audit_inode(pathname, dir); //系统调用审计
goto ok;
}
//路径名最后携带斜杠,即表示最后一个目录项为目录
//目录不能通过open等系统调用加O_CREAT标志来创建或打开
if (nd->last.name[nd->last.len])
goto exit;
mutex_lock(&dir->d_inode->i_mutex);
path->dentry = lookup_hash(nd);
path->mnt = nd->path.mnt;
error = PTR_ERR(path->dentry);
if (IS_ERR(path->dentry)) { //lookup_hash函数错误返回
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) { //相应的i节点不存在
error = mnt_want_write(nd->path.mnt); //获取所在文件系统的写权限
if (error)
goto exit_mutex_unlock;
error = __open_namei_create(nd, path, open_flag, mode); //创建i节点并保存到相应的目录项中
if (error) {
mnt_drop_write(nd->path.mnt);
goto exit;
}
filp = nameidata_to_filp(nd); //从nd获取数据填充filp并打开文件
mnt_drop_write(nd->path.mnt);
path_put(&nd->path);
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);
error = -EEXIST;
//O_CREAT和O_EXCL标志同时配置且文件已经存在时则错误返回
if (open_flag & O_EXCL)
goto exit_dput;
//处理path->dentry->d_flags的DCACHE_MOUNTED、DCACHE_MANAGE_TRANSIT和DCACHE_NEED_AUTOMOUNT等三个标志
error = follow_managed(path, nd->flags);
if (error < 0)
goto exit_dput;
error = -ENOENT;
//上面的follow_managed函数可能会更改目录项及其i节点
if (!path->dentry->d_inode)
goto exit_dput;
//如果为符号链接文件则返回空指针
if (path->dentry->d_inode->i_op->follow_link)
return NULL;
//更新struct nameidata结构体实例nd中的path
path_to_nameidata(path, nd);
nd->inode = path->dentry->d_inode;
error = -EISDIR;
if (S_ISDIR(nd->inode->i_mode)) //如果为目录则错误返回
goto exit;
ok:
filp = finish_open(nd, open_flag, acc_mode); //打开操作
return filp;
exit_mutex_unlock:
mutex_unlock(&dir->d_inode->i_mutex);
exit_dput:
path_put_conditional(path, nd);
exit:
path_put(&nd->path);
return ERR_PTR(error);
}
该函数所调用的函数大部分在前面就已经说明过,除之以外,lookup_hash函数主要用于创建新目录项,而__open_namei_create函数主要用于在文件系统中创建该目录项对应的i节点。它们的源代码如下所示:
/* fs/namei.c */
static struct dentry *lookup_hash(struct nameidata *nd)
{
return __lookup_hash(&nd->last, nd->path.dentry, nd);
}
static struct dentry *__lookup_hash(struct qstr *name,
struct dentry *base, struct nameidata *nd)
{
struct inode *inode = base->d_inode;
struct dentry *dentry;
int err;
err = exec_permission(inode, 0); //权限检查
if (err)
return ERR_PTR(err);
if (base->d_flags & DCACHE_OP_HASH) { //具有DCACHE_OP_HASH标志则表示d_hash函数指针为真
err = base->d_op->d_hash(base, inode, name);
dentry = ERR_PTR(err);
if (err < 0)
goto out;
}
//调用__d_lookup函数根据名字name从目录项缓存哈希链表中查找相应的目录项
dentry = d_lookup(base, name);
//标志DCACHE_OP_REVALIDATE表示dentry->d_op->d_revalidate函数指针为真
if (dentry && (dentry->d_flags & DCACHE_OP_REVALIDATE))
dentry = do_revalidate(dentry, nd);
if (!dentry) //未找到目录项
//调用d_alloc函数分配struct dentry结构体内存并调用inode->i_op->lookup函数指针
//所指向的函数从文件系统中获取数据并填充所分配的内存
dentry = d_alloc_and_lookup(base, name, nd);
out:
return dentry;
}
/* fs/namei.c */
static int __open_namei_create(struct nameidata *nd, struct path *path,
int open_flag, int mode)
{
int error;
struct dentry *dir = nd->path.dentry;
if (!IS_POSIXACL(dir->d_inode)) //不支持访问控制列表
mode &= ~current_umask(); //根据文件创建的权限掩码计算mode
error = security_path_mknod(&nd->path, path->dentry, mode, 0); //安全模块检查
if (error)
goto out_unlock;
error = vfs_create(dir->d_inode, path->dentry, mode, nd); //创建i节点
out_unlock:
mutex_unlock(&dir->d_inode->i_mutex);
dput(nd->path.dentry);
nd->path.dentry = path->dentry; //保存更新后的目录项path->dentry
if (error)
return error;
return may_open(&nd->path, 0, open_flag & ~O_TRUNC); //无须检查写权限及截断文件
}
int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
struct nameidata *nd)
{
int error = may_create(dir, dentry); //dir必须具备MAY_WRITE和MAY_EXEC权限
if (error)
return error;
if (!dir->i_op->create) //create函数指针必须为真
return -EACCES;
mode &= S_IALLUGO;
mode |= S_IFREG; //普通文件
error = security_inode_create(dir, dentry, mode); //安全模块检查
if (error)
return error;
error = dir->i_op->create(dir, dentry, mode, nd); //创建i节点
if (!error) //成功返回
fsnotify_create(dir, dentry); //实现文件系统事件监控的IN_CREATE事件
return error;
}