大家可以使用以下函数打开一个文件,无论是字符设备文件还是已经挂载好的文件系统中的文件:
int open(const char*pathname,int flags,mode_t mode);
其中open这个函数就是使用了open函数进行系统调用,其系统调用为:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
do_sys_open函数参数含义:
pathname:字符串类型的文件名称,比如“./a.txt”
flags:以什么样的方式打开文件
mode:为打开文件时赋予的文件用户、用户组的权限
do_sys_open函数分析,文件在fs/open.c中:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op;
//根据标志位,填充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);
//负责分配文件描述符
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
//解析文件路径,得到文件的索引节点,创建文件结构体
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {//创建文件结构体失败
put_unused_fd(fd);//释放fd
fd = PTR_ERR(f);
} else {//创建文件结构体
fsnotify_open(f);//使进程可以使用notify监视文件系统的事件
fd_install(fd, f);//把刚刚创建的文件添加到进程的打开文件表中
}
}
putname(tmp);//释放存放文件路径的内存
return fd;
}
do_sys_open首先使用函数build_open_flags根据参数flags和mode填充open_flags 结构体,用于后面的文件创建,然后使用getname函数把文件路径从用户空间缓冲区复制到内核空间,接着使用get_unused_fd_flags和函数分配文件描述符,最后使用do_filp_open函数解析文件路径,得到文件的索引节点,创建文件结构体。如果创建失败则释放fd返回错误;如果成功则通过fsnotify_open函数使能进程可以使用notify监视文件系统的事件并且通过fd_install函数把刚刚创建的文件添加到进程的打开文件表中,返回fd。所以下面主要分析几个函数:
先看看第一个函数,get_unused_fd_flags这个函数,在fs/file.c文件中:
int get_unused_fd_flags(unsigned flags)
{
return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}
EXPORT_SYMBOL(get_unused_fd_flags);
int __alloc_fd(struct files_struct *files,
unsigned start, unsigned end, unsigned flags)
{
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;//获取next_fd,也就是上次分配的fd加上一
//如果fd比fd表小
if (fd < fdt->max_fds)
fd = find_next_fd(fdt, fd);//在文件描述符位图中查找一个空闲的fd
/*
* N.B. For clone tasks sharing a files structure, this test
* will limit the total number of files that can be opened.
*/
error = -EMFILE;
//如果fd大于进程打开文件数量上限
if (fd >= end)
goto out;
//到这里表示fd是ok的,需要扩大打开文件表
error = expand_files(files, fd);
if (error < 0)
goto out;
/*
* If we needed to expand the fs array we
* might have blocked - try again.
*/
//上面返回值小于0说明扩展失败
//这里返回值等于0说明啥也没做,需要再试一次
if (error)
goto repeat;
//记录下一次分配文件描述符的开始尝试位置
if (start <= files->next_fd)
files->next_fd = fd + 1;
//在文件描述位图中记录fd已经分配
__set_open_fd(fd, fdt);
//如果进程设置了O_CLOEXEC,表示使用系统调用execve装在程序的时候关闭文件
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);//在位图中设置fd对应的位
else
__clear_close_on_exec(fd, fdt);//在位图中清除fd对应的位
error = fd;
#if 1
/* 合理性检查 */
if (rcu_access_pointer(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;
}
get_unused_fd_flags函数是通过__alloc_fd来申请文件描述符的,在__alloc_fd中主要做了一下几步,首先使用函数files_fdtable获取文件描述符位图,然后通过find_next_fd函数在文件描述符位图中查找一个空闲的fd,接着使用expand_files函数扩大打开文件表,最后在文件描述位图中记录fd。我们看看expand_files函数:
static int expand_files(struct files_struct *files, unsigned int nr)
__releases(files->file_lock)
__acquires(files->file_lock)
{
struct fdtable *fdt;
int expanded = 0;
repeat:
fdt = files_fdtable(files);
/* 判断是否需要扩展,如果不需要,这里返回0,只有这里会返回0 */
if (nr < fdt->max_fds)
return expanded;
/* 判断能不能扩展 */
if (nr >= sysctl_nr_open)
return -EMFILE;
//根据resize_in_progress参数判断是否回到repeat
if (unlikely(files->resize_in_progress)) {
spin_unlock(&files->file_lock);
expanded = 1;
wait_event(files->resize_wait, !files->resize_in_progress);
spin_lock(&files->file_lock);
goto repeat;
}
/* 开始扩展 */
files->resize_in_progress = true;
expanded = expand_fdtable(files, nr);
files->resize_in_progress = false;
wake_up_all(&files->resize_wait);
return expanded;
}
expand_files函数也就是进行一些情况判断最后还是通过expand_fdtable来扩展:
static int expand_fdtable(struct files_struct *files, unsigned int nr)
__releases(files->file_lock)
__acquires(files->file_lock)
{
struct fdtable *new_fdt, *cur_fdt;
spin_unlock(&files->file_lock);
new_fdt = alloc_fdtable(nr);//分配fdtable内存
/* make sure all __fd_install() have seen resize_in_progress
* or have finished their rcu_read_lock_sched() section.
*/
//确保文件使用者不大于1
if (atomic_read(&files->count) > 1)
synchronize_sched();
spin_lock(&files->file_lock);
if (!new_fdt)
return -ENOMEM;
/*
* extremely unlikely race - sysctl_nr_open decreased between the check in
* caller and alloc_fdtable(). Cheaper to catch it here...
*/
//检查分配的空间nr是否足够
if (unlikely(new_fdt->max_fds <= nr)) {
__free_fdtable(new_fdt);
return -EMFILE;
}
cur_fdt = files_fdtable(files);
BUG_ON(nr < cur_fdt->max_fds);
//把旧的文件表的内容复制到新的文件表上
copy_fdtable(new_fdt, cur_fdt);
//文件的指针指向新表
rcu_assign_pointer(files->fdt, new_fdt);
//释放旧的文件表
if (cur_fdt != &files->fdtab)
call_rcu(&cur_fdt->rcu, free_fdtable_rcu);
/* coupled with smp_rmb() in __fd_install() */
smp_wmb();
return 1;
}
expand_fdtable首先使用alloc_fdtable函数分配一个新的文件表,然后确保文件使用者数量不大于1和分配的空间充足,然后把旧的文件表的内容复制到新的文件表上,最后让文件的指针指向新表并且释放旧的文件表。
然后我们看看第二个函数do_filp_open:
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;
//分配内存,并且根据参数设置nd
set_nameidata(&nd, dfd, pathname);
//LOOKUP_RCU说明使用RCU方式查找,
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
//RCU方式查找不到便使用普通查找
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
//如果还找不到就要使用LOOKUP_REVAL查找,
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();//
return filp;
}
do_filp_open主要是通过path_openat进行解析文件路径,得到文件的索引节点,创建文件结构体的,其三次调用path_openat分别是1.优先使用RCU方式查找,如果之前使用过一般RCU就可以找到,找不到再使用普通查找,一般第一次使用都是普通查找,如果还找不到说明本地的目录项已经过期了,需要真正的去到硬件查找,一般是网络文件系统中读取的资源远程被修改了。我们看看path_openat函数,文件位于fs/namei.c中:
static struct file *path_openat(struct nameidata *nd,
const struct open_flags *op, unsigned flags)
{
struct file *file;
int error;
file = alloc_empty_file(op->open_flag, current_cred());
if (IS_ERR(file))
return file;
//临时文件的打开使用do_tmpfile函数
if (unlikely(file->f_flags & __O_TMPFILE)) {
error = do_tmpfile(nd, flags, op, file);
//O_PATH表示仅仅获取fd,没有真正打开文件,则调用do_o_path
} else if (unlikely(file->f_flags & O_PATH)) {
error = do_o_path(nd, flags, file);//
} else {//普通打开文件的做法
//初始化nd中的其他参数,通过开头是不是"/"和dfd是否是AT_FDCWD,识别是绝对路径还是相对路径,设置RCU
const char *s = path_init(nd, flags);
//link_path_walk:命名解析函数,针对路径循环搜索,直到找到最后一个不是目录的文件
//do_last:处理打开文件的最后操作
while (!(error = link_path_walk(s, nd)) &&
(error = do_last(nd, file, op)) > 0) {
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
s = trailing_symlink(nd);//查看是否为链接文件,链接文件处理
}
terminate_walk(nd);//查找完成操作,包括解RCU锁
}
if (likely(!error)) {
if (likely(file->f_mode & FMODE_OPENED))
return file;
WARN_ON(1);
error = -EINVAL;
}
fput(file);//初始化一个任务,关闭的时候回调用到____fput
if (error == -EOPENSTALE) {
if (flags & LOOKUP_RCU)
error = -ECHILD;
else
error = -ESTALE;
}
return ERR_PTR(error);
}
path_openat首先根据文件类型进行文件查找,我们一般的打开操作是要通过path_init初始化路径,然后通过link_path_walk函数针对路径循环搜索,直到找到文件,接着使用do_last进行文件的打开,最后terminate_walk表示查找工作完成了,把path_init过程中分配的一些解析路径的资源释放掉和一些锁解锁了。在返回前会使用fput初始化一个任务,这个任务是____fput,我们会在关闭的时候回调____fput。
fput函数,文件在fs/file_table.c:
void fput(struct file *file)
{
if (atomic_long_dec_and_test(&file->f_count)) {
struct task_struct *task = current;
if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
init_task_work(&file->f_u.fu_rcuhead, ____fput);
if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
return;
/*
* After this task has run exit_task_work(),
* task_work_add() will fail. Fall through to delayed
* fput to avoid leaking *file.
*/
}
if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))
schedule_delayed_work(&delayed_fput_work, 1);
}
}
static void ____fput(struct callback_head *work)
{
__fput(container_of(work, struct file, f_u.fu_rcuhead));
}
fput函数会调用____fput,而最终调用的是__fput函数。
static void __fput(struct file *file)
{
struct dentry *dentry = file->f_path.dentry;
struct vfsmount *mnt = file->f_path.mnt;
struct inode *inode = file->f_inode;
if (unlikely(!(file->f_mode & FMODE_OPENED)))
goto out;
might_sleep();
fsnotify_close(file);//通知关闭文件事件,进程可以使用inotify监视文件的事件
/*
* The function eventpoll_release() should be the first called
* in the file cleanup chain.
*/
//如果进程使用eventpoll监听文件系统的事件,那么把文件从eventpoll中删除
eventpoll_release(file);
locks_remove_file(file);//如果进程持有文件锁,那么释放文件锁
ima_file_free(file);
if (unlikely(file->f_flags & FASYNC)) {
if (file->f_op->fasync)
file->f_op->fasync(-1, file, 0);
}
//调用具体文件系统类型的release函数
if (file->f_op->release)
file->f_op->release(inode, file);
if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
!(file->f_mode & FMODE_PATH))) {
cdev_put(inode->i_cdev);
}
fops_put(file->f_op);//把文件操作结合结构体引用计数减一
//解除file实例和目录项,挂载描述符以及索引节点的关联
put_pid(file->f_owner.pid);
if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
i_readcount_dec(inode);
if (file->f_mode & FMODE_WRITER) {
put_write_access(inode);
__mnt_drop_write(mnt);
}
dput(dentry);//释放目录项
mntput(mnt);//释放挂载描述符
out:
file_free(file);
}
我们看看path_init函数:
static const char *path_init(struct nameidata *nd, unsigned flags)
{
const char *s = nd->name->name;
if (!*s)
flags &= ~LOOKUP_RCU;
if (flags & LOOKUP_RCU)
rcu_read_lock();
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
nd->depth = 0;
//flags中表明是从根目录搜寻,
if (flags & LOOKUP_ROOT) {
struct dentry *root = nd->root.dentry;
struct inode *inode = root->d_inode;
//查看文件权限上是否允许
if (*s && unlikely(!d_can_lookup(root)))
return ERR_PTR(-ENOTDIR);
nd->path = nd->root;
nd->inode = inode;
//对nd、RCU和顺序锁进行初始化
if (flags & LOOKUP_RCU) {
nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
nd->root_seq = nd->seq;
nd->m_seq = read_seqbegin(&mount_lock);
} else {
path_get(&nd->path);
}
return s;
}
nd->root.mnt = NULL;
nd->path.mnt = NULL;
nd->path.dentry = NULL;
nd->m_seq = read_seqbegin(&mount_lock);
//通过字符的开头是"/",也可以表示绝对路径,则也要做相关初始化
if (*s == '/') {
set_root(nd);
if (likely(!nd_jump_root(nd)))
return s;
return ERR_PTR(-ECHILD);
} else if (nd->dfd == AT_FDCWD) {
if (flags & LOOKUP_RCU) {
struct fs_struct *fs = current->fs;
unsigned seq;
do {
seq = read_seqcount_begin(&fs->seq);
nd->path = fs->pwd;
nd->inode = nd->path.dentry->d_inode;
nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
} while (read_seqcount_retry(&fs->seq, seq));
} else {
get_fs_pwd(current->fs, &nd->path);
nd->inode = nd->path.dentry->d_inode;
}
return s;
} else {//开头不是"/",表示使用相对路径
/* Caller must check execute permissions on the starting path component */
struct fd f = fdget_raw(nd->dfd);
struct dentry *dentry;
if (!f.file)
return ERR_PTR(-EBADF);
dentry = f.file->f_path.dentry;
if (*s && unlikely(!d_can_lookup(dentry))) {
fdput(f);
return ERR_PTR(-ENOTDIR);
}
nd->path = f.file->f_path;
if (flags & LOOKUP_RCU) {
nd->inode = nd->path.dentry->d_inode;
nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
} else {
path_get(&nd->path);
nd->inode = nd->path.dentry->d_inode;
}
fdput(f);
return s;
}
}
path_init初始化路径也就是根据nd计算出文件的路径而已。计算出文件的路径后是调用link_path_walk解析文件路径,针对路径循环搜索,直到找到最后一个不是目录的文件,现在看看:
static int link_path_walk(const char *name, struct nameidata *nd)
{
int err;
if (IS_ERR(name))
return PTR_ERR(name);
while (*name=='/')//如果是/开头,这需要将其+到不是"/"为止
name++;
if (!*name)//如果字符是无效值,则异常退出
return 0;
/* 到这里,我们可以开始搜寻循环 */
for(;;) {
u64 hash_len;
int type;
err = may_lookup(nd);//查询文件权限是否允许访问
if (err)
return err;
//算出该文件名的哈希值,和文件名长度
hash_len = hash_name(nd->path.dentry, name);
type = LAST_NORM;
//判断文件名是否使用了"."或者"..",是则标明type
if (name[0] == '.') switch (hashlen_len(hash_len)) {
case 2:
if (name[1] == '.') {
type = LAST_DOTDOT;
nd->flags |= LOOKUP_JUMPED;
}
break;
case 1:
type = LAST_DOT;
}
//文件名没有"."或者"..",则是普通文件或目录,则查看是否有hash缓存表,有表示内存中存在缓存,没有直接退出
if (likely(type == LAST_NORM)) {
struct dentry *parent = nd->path.dentry;
nd->flags &= ~LOOKUP_JUMPED;
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
struct qstr this = { { .hash_len = hash_len }, .name = name };
err = parent->d_op->d_hash(parent, &this);
if (err < 0)
return err;
hash_len = this.hash_len;
name = this.name;
}
}
nd->last.hash_len = hash_len;
nd->last.name = name;
nd->last_type = type;
name += hashlen_len(hash_len);
if (!*name)
goto OK;
/*
* If it wasn't NUL, we know it was '/'. Skip that
* slash, and continue until no more slashes.
*/
//当有多个"/"时,则搜索时去掉
do {
name++;
} while (unlikely(*name == '/'));
if (unlikely(!*name)) {
OK:
/* 深度为0则文件名字解析结束返回 */
if (!nd->depth)
return 0;
/* 字符串结束则文件名字解析结束返回 */
name = nd->stack[nd->depth - 1].name;
if (!name)
return 0;
/* 依据刚刚识别的类型,做不同的操作 */
err = walk_component(nd, WALK_FOLLOW);
} else {
/* 依据刚刚识别的类型,做不同的操作 */
err = walk_component(nd, WALK_FOLLOW | WALK_MORE);
}
if (err < 0)
return err;
//下面是符号连接处理
if (err) {
const char *s = get_link(nd);//根据符号链接获取文件名
if (IS_ERR(s))
return PTR_ERR(s);
err = 0;
if (unlikely(!s)) {
/* jumped */
put_link(nd);
} else {
nd->stack[nd->depth - 1].name = name;
name = s;
continue;
}
}
//普通文件的搜索,搜索失败则返回错误
if (unlikely(!d_can_lookup(nd->path.dentry))) {
if (nd->flags & LOOKUP_RCU) {
if (unlazy_walk(nd))
return -ECHILD;
}
return -ENOTDIR;
}
}
}
link_path_walk其实就是在for循环里面解析路径,步骤是这样的,首先在文件的每一层目录中查询文件的访问权限,然后判断每一次层的目录名字,进入下一个循环,直到解析深度为0或者文件字符串结束表示解析完毕。再判断该文件是否为连接文件,如果是连接文件,需要继续解析。还要对每一层的目录使用walk_component函数进行处理:
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)) {
//处理"."和".."文件名,成功则设置nd->path.dentry和nd->inode,如果父目录是挂载点找到挂载点目录再处理。
err = handle_dots(nd, nd->last_type);
if (!(flags & WALK_MORE) && nd->depth)
put_link(nd);
return err;
}
/* 快速的文件搜索:RCU */
err = lookup_fast(nd, &path, &inode, &seq);
if (unlikely(err <= 0)) {
if (err < 0)
return err;
//慢速的搜索
path.dentry = lookup_slow(&nd->last, nd->path.dentry,
nd->flags);
if (IS_ERR(path.dentry))
return PTR_ERR(path.dentry);
path.mnt = nd->path.mnt;
err = follow_managed(&path, nd);//dentry管理相关
if (unlikely(err < 0))
return err;
if (unlikely(d_is_negative(path.dentry))) {
path_to_nameidata(&path, nd);
return -ENOENT;
}
seq = 0; /* we are already out of RCU mode */
inode = d_backing_inode(path.dentry);
}
return step_into(nd, &path, flags, inode, seq);
}
walk_component首先使用handle_dots处理文件名的.和…,然后通过lookup_fast使用RCU方式快速搜索文件,失败则使用lookup_slow进行慢速的搜索。我们先看看lookup_fast:
static int lookup_fast(struct nameidata *nd,
struct path *path, struct inode **inode,
unsigned *seqp)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry;
int status = 1;
int err;
/*
* Rename seqlock is not required here because in the off chance
* of a false negative due to a concurrent rename, the caller is
* going to fall back to non-racy lookup.
*/
//文件可能存在RCU锁,这表示可能有别的进程使用,则有可能被加载到 dcache 中
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
bool negative;
//RCU方式搜索,只使用内存屏障作为防护手段
dentry = __d_lookup_rcu(parent, &nd->last, &seq);
if (unlikely(!dentry)) {
if (unlazy_walk(nd))
return -ECHILD;
return 0;
}
/*
* This sequence count validates that the inode matches
* the dentry name information from lookup.
*/
//此序列计数验证inode是否与查找中的dentry名称信息相匹配
*inode = d_backing_inode(dentry);
negative = d_is_negative(dentry);
//在子目录的read_seqcount_begin的内存屏障就足够,这里判断父目录和子目录的seq是否一致
if (unlikely(read_seqcount_retry(&dentry->d_seq, seq)))
return -ECHILD;
/*
* This sequence count validates that the parent had no
* changes while we did the lookup of the dentry above.
*
* The memory barrier in read_seqcount_begin of child is
* enough, we can use __read_seqcount_retry here.
*/
if (unlikely(__read_seqcount_retry(&parent->d_seq, nd->seq)))
return -ECHILD;
*seqp = seq;
//找到的目录是否需要重新生效,需要就执行denter的对应函数
status = d_revalidate(dentry, nd->flags);
if (likely(status > 0)) {
/*
* Note: do negative dentry check after revalidation in
* case that drops it.
*/
if (unlikely(negative))
return -ENOENT;
path->mnt = mnt;
path->dentry = dentry;
if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
return 1;
}
if (unlazy_child(nd, dentry, seq))
return -ECHILD;
if (unlikely(status == -ECHILD))
/* we'd been told to redo it in non-rcu mode */
status = d_revalidate(dentry, nd->flags);
} else {//普通查找
//普通查找抵用函数__d_lookup
dentry = __d_lookup(parent, &nd->last);
if (unlikely(!dentry))
return 0;
//找到的目录是否需要重新生效,需要就执行denter的对应函数
status = d_revalidate(dentry, nd->flags);
}
if (unlikely(status <= 0)) {
if (!status)
d_invalidate(dentry);
dput(dentry);
return status;
}
if (unlikely(d_is_negative(dentry))) {
dput(dentry);
return -ENOENT;
}
path->mnt = mnt;
path->dentry = dentry;
err = follow_managed(path, nd);//dentry管理相关
if (likely(err > 0))
*inode = d_backing_inode(path->dentry);
return err;
}
调用__d_lookup_rcu函数在哈希桶中找到对应的dentry,如果找到了就返回dentry,如果没找到就切换到ref-walk模式继续查)。根据这个dentry得到对应的inode,进行一系列的检查操作,这样是为了确保在读取的时候,并没有其他进程对这些结构进行修改操作(rcu-walk模式并没有加锁),更新的临时变量path,这时候不能直接修改nd变量,因为不能确定这个分量是不是目录,nd记录的信息必须是目录,然后结束。
我们再看看那慢速模式:
static struct dentry *lookup_slow(const struct qstr *name,
struct dentry *dir,
unsigned int flags)
{
struct inode *inode = dir->d_inode;
struct dentry *res;
inode_lock_shared(inode);//上锁
res = __lookup_slow(name, dir, flags);
inode_unlock_shared(inode);//解锁
return res;
}
static struct dentry *__lookup_slow(const struct qstr *name,
struct dentry *dir,
unsigned int flags)
{
struct dentry *dentry, *old;
struct inode *inode = dir->d_inode;
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
/* Don't go there if it's already dead */
if (unlikely(IS_DEADDIR(inode)))
return ERR_PTR(-ENOENT);
again:
//分配内存
dentry = d_alloc_parallel(dir, name, &wq);
if (IS_ERR(dentry))
return dentry;
if (unlikely(!d_in_lookup(dentry))) {//普通查找方式成功(第二种方式)
if (!(flags & LOOKUP_NO_REVAL)) {
int error = d_revalidate(dentry, flags);
if (unlikely(error <= 0)) {
if (!error) {
d_invalidate(dentry);
dput(dentry);
goto again;
}
dput(dentry);
dentry = ERR_PTR(error);
}
}
} else {//普通查找失败,需要进入硬件查找(第三种方式)
//调用文件系统的lookup函数
old = inode->i_op->lookup(inode, dentry, flags);
d_lookup_done(dentry);
if (unlikely(old)) {
dput(dentry);
dentry = old;
}
}
return dentry;
}
看到这里,大家应该就明白了,为什么这种查找方法很慢呢,因为这种查找是互斥操作,进程可能会阻塞。而且进入普通查找可能会失败,失败就要进行硬件上的查找了,也就是网络文件系统遇到比较多那种的情况,当前文件信息失效,远程文件信息被修改了。这个情况还要调用到根据文件系统类型调用相关的lookup函数,这个文件的inode的操作函数初始化是在文件系统类型注册的时候做的。