Linux 系统调用之open(二)

上一篇里面我们以打开"/home/gaobsh/a.txt"为例分析了系统调用"open"的内核实现。其中我们假设了路径名中所有部分的dentry结构体均已存在在系统缓存中。即通过 lookup_fast()函数成功找到目标文件的dentry。这里我们分析一下如果上述路径名某一部分如果不能通过 lookup_fast()函数查找到的情况。这里还以 上一篇里面的例子进行分析。在 walk_component()函数通过调用 lookup_fast()函数如果失败,则进入 lookup_slow()函数。在 lookup_slow()函数为目标文件创建一个新的dentry,并加入到系统缓存中。现来分析其具体过程。例如假设文件夹“gaobsh”尚不存在在系统缓存中(虽然这种情况几乎不存在。。。)。 lookup_slow()函数在本示例中定义等价与如下(在这里我们仍然不考虑函数调用失败和进程同步等问题):
static struct dentry *lookup_slow(const struct qstr *name,
                  struct dentry *dir,
                  unsigned int flags)
{
    struct dentry *dentry = ERR_PTR(-ENOENT), *old;
    struct inode *inode = dir->d_inode;
    DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);

    inode_lock_shared(inode);
    /* Don't go there if it's already dead */
    if (unlikely(IS_DEADDIR(inode))) /* if 判断为 0 */
        ...
again:
    dentry = d_alloc_parallel(dir, name, &wq);
    if (IS_ERR(dentry)) /* if 判断为 0 */
        ...
    if (unlikely(!d_in_lookup(dentry))) { /*不考虑并发性问题, if 判断为 0 */
        ...
    } else {
        old = inode->i_op->lookup(inode, dentry, flags); /* 一般情况下,返回NULL */
        d_lookup_done(dentry);
        if (unlikely(old)) { /* if 判断为  0 */
            ...
        }
    }
out:
    inode_unlock_shared(inode);
    return dentry;
}

先来分析其中的d_alloc_parallel()函数。在本例中,该函数定义等价与如下,(该函数涉及很多并发性问题的考虑,参考这篇文章):

struct dentry *d_alloc_parallel(struct dentry *parent,
				const struct qstr *name,
				wait_queue_head_t *wq)
{
	unsigned int hash = name->hash;
	struct hlist_bl_head *b = in_lookup_hash(parent, hash);
	struct hlist_bl_node *node;
	struct dentry *new = d_alloc(parent, name);
	struct dentry *dentry;
	unsigned seq, r_seq, d_seq;

	if (unlikely(!new)) /* if 判断为 0 */
		...

retry:
	rcu_read_lock();
	seq = smp_load_acquire(&parent->d_inode->i_dir_seq) & ~1;
	r_seq = read_seqbegin(&rename_lock);
	dentry = __d_lookup_rcu(parent, name, &d_seq); 
	if (unlikely(dentry)) { /* if 判断为 0 (dentry 刚刚被创建和初始化,但尚未被加入哈希表中 (不考虑并发性问题)) */
		...
	}
	if (unlikely(read_seqretry(&rename_lock, r_seq))) { /* 暂不考虑并发性问题,假设此时无需 retry, if 判断为 0 */
		...
	}
	hlist_bl_lock(b);
	if (unlikely(parent->d_inode->i_dir_seq != seq)) { /* 暂不考虑,假设 if 判断为 0 */
		...
	}
	/*
	 * No changes for the parent since the beginning of d_lookup().
	 * Since all removals from the chain happen with hlist_bl_lock(),
	 * any potential in-lookup matches are going to stay here until
	 * we unlock the chain.  All fields are stable in everything
	 * we encounter.
	 */
    /*遍历 in_lookup_hashtable,在不考虑并发性的问题情况下,新创建的这个 dentry 在 in_lookup_hashtable 中也找不到 */
     hlist_bl_for_each_entry(dentry, node, b, d_u.d_in_lookup_hash) { 
		if (dentry->d_name.hash != hash) / if 判断始终为 1, 直到整个遍历结束 */
			continue; 
		...
	}
	rcu_read_unlock();
	/* we can't take ->d_lock here; it's OK, though. */
	new->d_flags |= DCACHE_PAR_LOOKUP;
	new->d_wait = wq;
    /*将新创建的 dentry 加入到 in_lookup_hashtable 中,以便并发访问的其他程序能够找到,不会重复创建相同 dentry */
    hlist_bl_add_head_rcu(&new->d_u.d_in_lookup_hash, b); 
	hlist_bl_unlock(b);
	return new; /* 函数返回 */
mismatch:
    ...
}

先看d_alloc()函数。本示例中该函数等价如下:

struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
	struct dentry *dentry = __d_alloc(parent->d_sb, name);
	if (!dentry)
		return NULL;
	dentry->d_flags |= DCACHE_RCUACCESS;
	spin_lock(&parent->d_lock);
	/*
	 * don't need child lock because it is not subject
	 * to concurrency here
	 */
	__dget_dlock(parent);
	dentry->d_parent = parent;
	list_add(&dentry->d_child, &parent->d_subdirs); /* 将新创建的 dentry->d_child 加入 父目录的 dentry->d_subdirs 链后面,此处不做详细分析 */
	spin_unlock(&parent->d_lock);

	return dentry;
}

先看其中的__d_alloc()函数,在这里等价如下:

struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
	struct dentry *dentry;
	char *dname;
	int err;

	dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
	if (!dentry)
		return NULL;

	/*
	 * We guarantee that the inline name is always NUL-terminated.
	 * This way the memcpy() done by the name switching in rename
	 * will still always have a NUL at the end, even if we might
	 * be overwriting an internal NUL character
	 */
	dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
	if (unlikely(!name)) { /* 这里if 判断结果为0 */
		...
	} else if (name->len > DNAME_INLINE_LEN-1) { /*DNAME_INLINE_LEN=32,name ~ gaobsh/a.txt, if 判断为0 */ 
		...
	} else  {
		dname = dentry->d_iname;
	}	

	dentry->d_name.len = name->len;
	dentry->d_name.hash = name->hash;
	memcpy(dname, name->name, name->len);
	dname[name->len] = 0;

	/* Make sure we always see the terminating NUL character */
	smp_wmb();
	dentry->d_name.name = dname; 
    /* 到这里,由于 name->len 小于 dentry->d_iname 数组的大小,因此 dentry->d_iname 和 dentry->d_name 
    *指向的是同一个名字 (gaobsh/a.txt) */

    /* 下面是根据参数 struct qstr *name 和 struct super_block *sb 提供的信息初始化刚刚创建的 dentry */
	dentry->d_lockref.count = 1;
	dentry->d_flags = 0;
	spin_lock_init(&dentry->d_lock);
	seqcount_init(&dentry->d_seq);
	dentry->d_inode = NULL; /* 注意,d_inode 没有赋值,整个dentry只能算是个空壳...*/
	dentry->d_parent = dentry;
	dentry->d_sb = sb;
	dentry->d_op = NULL;
	dentry->d_fsdata = NULL;
	INIT_HLIST_BL_NODE(&dentry->d_hash);
	INIT_LIST_HEAD(&dentry->d_lru);
	INIT_LIST_HEAD(&dentry->d_subdirs);
	INIT_HLIST_NODE(&dentry->d_u.d_alias);
	INIT_LIST_HEAD(&dentry->d_child);
	d_set_d_op(dentry, dentry->d_sb->s_d_op); // 在本例中 dentry->d_op = sb->s_d_op (== NULL)  
    /**************************************************************************/

     if (dentry->d_op && dentry->d_op->d_init) { /*dentry->d_op == 0*/
		...
	}

	this_cpu_inc(nr_dentry);

	return dentry;
}

可见,在__d_alloc()函数中,创建新的 struct dentry,并根据 父目录的dentry->d_sb 和本目录的 name 进行相关初始化。该函数返回之后,我们回到d_alloc()函数。在d_alloc()函数调用完毕__d_alloc()函数之后,再对新创建的 dentry 进行简单设置之后便返回了,详见上面代码部分。d_alloc()函数结束之后返回到d_alloc_parallel()函数。d_alloc_parallel()函数执行完d_alloc()函数之后,进行简单操作(不考虑并发性问题的考虑)之后,返回新创建的 dentry。d_alloc_parallel()函数返回之后,便回到lookup_slow()函数。需要注意的是,在整个d_alloc_parallel()函数中,只是创建了一个新的dentry,但还没找到相应的inode,整个dentry目前只是一具空壳。下面将进入inode->i_op->lookup() 函数,在这个函数中,将找到相应的inode,并与dentry联系起来。这是一个函数指针,要分析该函数需知道该指针指向的函数。其实该指针指向的是ext4_lookup()函数。该函数定义如下:

static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
{
	struct inode *inode;
	struct ext4_dir_entry_2 *de;
	struct buffer_head *bh;

	if (ext4_encrypted_inode(dir)) { /* if 判断为 0 */
		...
	}

       if (dentry->d_name.len > EXT4_NAME_LEN) /* if 判断为 0 */
	       ...
    /*********根据父目录和目标文件的文件名,找到目标文件的inode *********/
	bh = ext4_find_entry(dir, &dentry->d_name, &de, NULL);
	if (IS_ERR(bh))
		return (struct dentry *) bh;
	inode = NULL;
	if (bh) {
		__u32 ino = le32_to_cpu(de->inode);
		brelse(bh);
		if (!ext4_valid_inum(dir->i_sb, ino)) {
			EXT4_ERROR_INODE(dir, "bad inode number: %u", ino);
			return ERR_PTR(-EFSCORRUPTED);
		}
		if (unlikely(ino == dir->i_ino)) {
			EXT4_ERROR_INODE(dir, "'%pd' linked to parent dir",
					 dentry);
			return ERR_PTR(-EFSCORRUPTED);
		}
		inode = ext4_iget_normal(dir->i_sb, ino);
		if (inode == ERR_PTR(-ESTALE)) {
			EXT4_ERROR_INODE(dir,
					 "deleted inode referenced: %u",
					 ino);
			return ERR_PTR(-EFSCORRUPTED);
		}
		if (!IS_ERR(inode) && ext4_encrypted_inode(dir) &&
		    (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) &&
		    !fscrypt_has_permitted_context(dir, inode)) {
			ext4_warning(inode->i_sb,
				     "Inconsistent encryption contexts: %lu/%lu",
				     dir->i_ino, inode->i_ino);
			iput(inode);
			return ERR_PTR(-EPERM);
		}
	}
    /*******************************************************************/
    return d_splice_alias(inode, dentry);
}

可见这个函数中大部分代码是用来找到目标文件的inode。因为在前面的代码中,只是创建了一个dentry的“空壳”,还没有与任何一个inode相关联,这个dentry的d_inode 被初始化为 dentry->d_inode = NULL。在ext4_lookup()这个函数中找到对应的inode。这也符合这个函数的名字:lookup,就是要找到inode。这里寻找inode的过程依赖ext4文件系统的配置和技术细节,这里将不进行深入研究。而在这个函数的最后一部分,d_splice_alias()函数中,将新创建的dentry与找到的inode进行关联,并顺便将新的dentry加入到哈希表中,这样下次执行 lookup_fast()函数时,即可迅速找到可用的dentry。下面看d_splice_alias()函数的定义:

struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
{
    if (IS_ERR(inode))
        return ERR_CAST(inode);

    BUG_ON(!d_unhashed(dentry));

    if (!inode)
        goto out;

    security_d_instantiate(dentry, inode); /* 暂不分析该函数,可认为该函数什么都不做 */
    spin_lock(&inode->i_lock);

    if (S_ISDIR(inode->i_mode)) { /* 判断是否为文件夹,if 判断为 1 */
        struct dentry *new = __d_find_any_alias(inode); /* 我们值考虑最简单的情况,一般情况下 new == NULL */
        if (unlikely(new)) { /一般情况下, if 判断为 0 */
            ...
        }
    }
out:
    __d_add(dentry, inode);
    return NULL;
}

可见,这个函数里面只调用了__d_add()函数。该函数定义如下:

static inline void __d_add(struct dentry *dentry, struct inode *inode)
{
    struct inode *dir = NULL;
    unsigned n;
    spin_lock(&dentry->d_lock);
    if (unlikely(d_in_lookup(dentry))) {/* 注意,此处if 判断为 1,但这里我们并不做具体分析 */
        ...
    }
    if (inode) { /* if 判断为 1 */
        unsigned add_flags = d_flags_for_inode(inode);
        hlist_add_head(&dentry->d_u.d_alias, &inode->i_dentry);
        raw_write_seqcount_begin(&dentry->d_seq);
        __d_set_inode_and_type(dentry, inode, add_flags); /*设置dentry的inode和flags*/
        raw_write_seqcount_end(&dentry->d_seq);
        fsnotify_update_flags(dentry);
    }
    __d_rehash(dentry); /此时新创建的dentry 已经完全可用,将其加入到 哈希表中 */
    if (dir) /* if 判断为 1,但此处不做分析 */
        ...
    spin_unlock(&dentry->d_lock);
    if (inode)
        spin_unlock(&inode->i_lock);
}

可见,在这个函数中,最终设置了dentry的inode,并将其加入到哈希表中,如此一来,下次可直接通过lookup_fast()函数找到该dentry。__d_add()函数返回后,回到d_splice_alias()函数,之后又返回到ext4_lookup()函数,之后又返回到lookup_slow()函数,lookup_slow()函数紧接着调用lookup_done()函数,这个函数只是处理并发性问题,这里我们不考虑,可以认为该函数什么都没做。在这之后,lookup_slow()函数返回至walk_component()函数,后面执行的操作和我们上一篇文章所分析的一样。

    注意,如果是路径名的最后一部分不在dcache中,那么创建dentry以及查找inode的操作不在lookup_slow()函数中进行,而是在do_last()函数中,通过调用lookup_open()函数来完成,与上述过程略有不同,但基本过程是一样的。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页