<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=446&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（五）：文件系统——文件和目录的操作]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=446</link>
		<description><![CDATA[linux源码解读（五）：文件系统——文件和目录的操作 最近发表的帖子。]]></description>
		<lastBuildDate>Sat, 08 Oct 2022 04:42:01 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（五）：文件系统——文件和目录的操作]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=453#p453</link>
			<description><![CDATA[<p>对于普通用户，平时使用操作系统是肯定涉及到创建、更改、删除文件(比如mkdir、rmdir、rm、chmod、ln等)；有些文件是高权限用户建的，低权限用户甚至都打不开，也删不掉；为了方便管理不同业务类型的文件，还需要在不同的逻辑分区建文件夹，分门别类各种文件；linux下用ls -l命令还可以查看文件的详细属性，这一系列的功能构师怎么实现的了？功能都在fs/namei.c文件中</p><p>&#160; 1、（1）权限检查，核心就是依靠inode结构体中的i_mode成员变量了！这个变量是unsigned short类型，一共2byte=16bit长；linux用低9位表示当前用户权限、用户组权限、其他用户权限，用户平时用ls -l查到的权限就是靠这个字段得到的！举个例子：rwx------表示当前用户有读写执行权限，用户组没有任何权限，其他用户也没有任何权限，所有权限表示刚好使用9bit；</p><p>&#160; i_mode节点右移3位，与上0007后得到用户组权限<br />&#160; &#160; &#160; &#160;i_mode节点右移6位，与上0007后得到当前用户权限<br />&#160; &#160; &#160; &#160;chmod改的就是i_mode这个字段</p><div class="codebox"><pre class="vscroll"><code>/*
 *    permission()
 *
 * is used to check for read/write/execute permissions on a file.
 * I don&#039;t know if we should look at just the euid or both euid and
 * uid, but that should be easily changed.
 */
//// 检测文件访问权限
// 参数：inode - 文件的i节点指针；mask - 访问属性屏蔽码。
// 返回：访问许可返回1，否则返回0.
static int permission(struct m_inode * inode,int mask)
{
    int mode = inode-&gt;i_mode;

/* special case: not even root can read/write a deleted file */
    // 如果i节点有对应的设备，但该i节点的连接计数值等于0，表示该文件
    // 已被删除，则返回。否则，如果进程的有效用户ID(euid)与i节点的
    // 用户id相同，则取文件宿主的访问权限。否则如果与组id相同，
    // 则取组用户的访问权限。
    if (inode-&gt;i_dev &amp;&amp; !inode-&gt;i_nlinks)
        return 0;
    else if (current-&gt;euid==inode-&gt;i_uid)
        mode &gt;&gt;= 6;
    else if (current-&gt;egid==inode-&gt;i_gid)
        mode &gt;&gt;= 3;
    /* &amp;0007：取最后3位
       &amp;mask：取传入参数的位
    */
    if (((mode &amp; mask &amp; 0007) == mask) 
            || suser())/*要么是管理员，是超级用户*/
        return 1;
    return 0;
}</code></pre></div><p>&#160; &#160;（2）因为是涉及到设备名、文件名、目录路径的比对，自然少不了字符串相关的操作。平时在3环做应用开发，码农都习惯于使用操作系统提供的库函数，比如strcmp、strcat等，但是现在还在内核，哪来的库函数直接调用了，只能自己动手重新写字符串的比较函数，如下：</p><div class="codebox"><pre class="vscroll"><code>*
 * ok, we cannot use strncmp, as the name is not in our data space.
 * Thus we&#039;ll have to use match. No big problem. Match also makes
 * some sanity tests.
 *
 * NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
 */
//// 指定长度字符串比较函数
// 参数：len - 比较的字符串长度；name - 文件名指针；de - 目录项结构
// 返回：相同返回1，不同返回0. 
// 下面函数中的寄存器变了same被保存在eax寄存器中，以便高效访问。
static int match(int len,const char * name,struct dir_entry * de)
{
    register int same ;

    // 首先判断函数参数的有效性。如果目录项指针空，或者目录项i节点等于0，或者
    // 要比较的字符串长度超过文件名长度，则返回0.如果要比较的长度len小于NAME_LEN，
    // 但是目录项中文件名长度超过len，也返回0.
    if (!de || !de-&gt;inode || len &gt; NAME_LEN)
        return 0;
    if (len &lt; NAME_LEN &amp;&amp; de-&gt;name[len])
        return 0;
    // 然后使用嵌套汇编语句进行快速比较操作。他会在用户数据空间(fs段)执行字符串的比较
    // 操作。%0 - eax（比较结果same）；%1 - eax (eax初值0)；%2 - esi(名字指针)；
    // %3 - edi(目录项名指针)；%4 - ecx(比较的字节长度值len).
    __asm__(&quot;cld\n\t&quot;
        &quot;fs ; repe ; cmpsb\n\t&quot;
        &quot;setz %%al&quot;
        :&quot;=a&quot; (same)
        :&quot;0&quot; (0),&quot;S&quot; ((long) name),&quot;D&quot; ((long) de-&gt;name),&quot;c&quot; (len)
        );
    return same;
}</code></pre></div><p>&#160; （3）还有在某个目录下查找名为xxx的文件，比如：&quot;find /home -name test&quot;命令，就是在home目录下查找名为test的文件，实现如下：</p><p>&#160; 注意：函数的参数有两个双重指针，第二个双重指针明显是用来保存返回值的！</p><div class="codebox"><pre class="vscroll"><code>/*
 *    find_entry()
 *
 * finds an entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as a parameter - res_dir). It does NOT read the inode of the
 * entry - you&#039;ll have to do that yourself if you want to.
 *
 * This also takes care of the few special cases due to &#039;..&#039;-traversal
 * over a pseudo-root and a mount point.
 */
//// 查找指定目录和文件名的目录项。 find -name &quot;xxx&quot; /xxx/xxx
// 参数：*dir - 指定目录i节点的指针；name - 文件名；namelen - 文件名长度；
// 该函数在指定目录的数据（文件）中搜索指定文件名的目录项。并对指定文件名
// 是&#039;..&#039;的情况根据当前进行的相关设置进行特殊处理。关于函数参数传递指针的指针
// 作用，请参见seched.c中的注释。
// 返回：成功则函数高速缓冲区指针，并在*res_dir处返回的目录项结构指针。失败则
// 返回空指针NULL。
static struct buffer_head * find_entry(struct m_inode ** dir,
    const char * name, int namelen, struct dir_entry ** res_dir)
{
    int entries;
    int block,i;
    struct buffer_head * bh;
    struct dir_entry * de;
    struct super_block * sb;

    // 同样，本函数一上来也需要对函数参数的有效性进行判断和验证。如果我们在前面
    // 定义了符号常数NO_TRUNCATE,那么如果文件名长度超过最大长度NAME_LEN，则不予
    // 处理。如果没有定义过NO_TRUNCATE，那么在文件名长度超过最大长度NAME_LEN时截短之。
#ifdef NO_TRUNCATE
    if (namelen &gt; NAME_LEN)
        return NULL;
#else
    if (namelen &gt; NAME_LEN)
        namelen = NAME_LEN;
#endif
    // 首先计算本目录中目录项项数entries(也即是当前目录中能存放的最大目录个数)。目录i节点i_size字段中含有本目录包含的数据
    // 长度，因此其除以一个目录项的长度（16字节）即课得到该目录中目录项数。然后置空 
    // 返回目录项结构指针。如果长度等于0，则返回NULL，退出。
    entries = (*dir)-&gt;i_size / (sizeof (struct dir_entry));
    *res_dir = NULL;
    if (!namelen)
        return NULL;
    // 接下来我们对目录项文件名是&#039;..&#039;的情况进行特殊处理。如果当前进程指定的根i节点就是
    // 函数参数指定的目录，则说明对于本进程来说，这个目录就是它伪根目录，即进程只能访问
    // 该目录中的项而不能后退到其父目录中去。也即对于该进程本目录就如同是文件系统的根目录，
    // 因此我们需要将文件名修改为‘.’。
    // 否则，如果该目录的i节点号等于ROOT_INO（1号）的话，说明确实是文件系统的根i节点。
    // 则取文件系统的超级块。如果被安装到的i节点存在，则先放回原i节点，然后对被安装到
    // 的i节点进行处理。于是我们让*dir指向该被安装到的i节点；并且该i节点的引用数加1.
    // 即针对这种情况，我们悄悄的进行了“偷梁换柱”工程。:-)
/* check for &#039;..&#039;, as we might have to do some &quot;magic&quot; for it */
    if (namelen==2 &amp;&amp; get_fs_byte(name)==&#039;.&#039; &amp;&amp; get_fs_byte(name+1)==&#039;.&#039;) {
/* &#039;..&#039; in a pseudo-root results in a faked &#039;.&#039; (just change namelen) */
        if ((*dir) == current-&gt;root)
            namelen=1;
        else if ((*dir)-&gt;i_num == ROOT_INO) {
/* &#039;..&#039; over a mount-point results in &#039;dir&#039; being exchanged for the mounted
   directory-inode. NOTE! We set mounted, so that we can iput the new dir */
            sb=get_super((*dir)-&gt;i_dev);
            if (sb-&gt;s_imount) {
                iput(*dir);
                (*dir)=sb-&gt;s_imount;
                (*dir)-&gt;i_count++;
            }
        }
    }
    // 现在我们开始正常操作，查找指定文件名的目录项在什么地方。因此我们需要读取目录的
    // 数据，即取出目录i节点对应块设备数据区中的数据块（逻辑块）信息。这些逻辑块的块号
    // 保存在i节点结构的i_zone[9]数组中。我们先取其中第一个块号。如果目录i节点指向的
    // 第一个直接磁盘块好为0，则说明该目录竟然不含数据，这不正常。于是返回NULL退出，
    // 否则我们就从节点所在设备读取指定的目录项数据块。当然，如果不成功，则也返回NULL 退出。
    if (!(block = (*dir)-&gt;i_zone[0]))
        return NULL;
    if (!(bh = bread((*dir)-&gt;i_dev,block)))
        return NULL;
    // 此时我们就在这个读取的目录i节点数据块中搜索匹配指定文件名的目录项。首先让de指向
    // 缓冲块中的数据块部分。并在不超过目录中目录项数的条件下，循环执行搜索。其中i是目录中
    // 的目录项索引号。在循环开始时初始化为0.
    i = 0;
    de = (struct dir_entry *) bh-&gt;b_data;
    while (i &lt; entries) {
        // 如果当前目录项数据块已经搜索完，还没有找到匹配的目录项，则释放当前目录项数据块。
        // 再读入目录的下一个逻辑块。若这块为空。则只要还没有搜索完目录中的所有目录项，就
        // 跳过该块，继续读目录的下一逻辑块。若该块不空，就让de指向该数据块，然后在其中继续
        // 搜索。其中DIR_ENTRIES_PER_BLOCK可得到当前搜索的目录项所在目录文件中的块号，而bmap()
        // 函数则课计算出在设备上对应的逻辑块号.
        if ((char *)de &gt;= BLOCK_SIZE+bh-&gt;b_data) {
            brelse(bh);
            bh = NULL;
            if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
                !(bh = bread((*dir)-&gt;i_dev,block))) {
                i += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            de = (struct dir_entry *) bh-&gt;b_data;
        }
        // 如果找到匹配的目录项的话，则返回该目录项结构指针de和该目录项i节点指针*dir以及该目录项
        // 数据块指针bh，并退出函数。否则继续在目录项数据块中比较下一个目录项。
        if (match(namelen,name,de)) {
            *res_dir = de;
            return bh;
        }
        de++;
        i++;
    }
    // 如果指定目录中的所有目录项都搜索完后，还没有找到相应的目录项，则释放目录的数据块，
    // 最后返回NULL（失败）。
    brelse(bh);
    return NULL;
}</code></pre></div><p>&#160; &#160;这个函数的开头就出现了一个新的结构体dir_entry，是这样定义的：结构体很简单，只有2个字段，分别是当前文件或目录中包含的inode个数，以及自己的名字；最后一个参数也是用这个结构体保存找到的文件名称和inode节点号数，通过inode节点号数从inode位图查看该inode是否被使用，也可以查找到该文件的inode节点在磁盘的block位置，进而找到文件元信息；</p><div class="codebox"><pre><code>struct dir_entry {
    unsigned short inode;
    char name[NAME_LEN];
};</code></pre></div><p>&#160; （4）既然能够查找文件，也就能新建文件或目录，linux的实现方式如下：</p><div class="codebox"><pre class="vscroll"><code>/*
 *    add_entry()
 *
 * adds a file entry to the specified directory, using the same
 * semantics as find_entry(). It returns NULL if it failed.
 *
 * NOTE!! The inode part of &#039;de&#039; is left at 0 - which means you
 * may not sleep between calling this and putting something into
 * the entry, as someone else might have used it while you slept.
 */
//// 根据指定的目录和文件名添加目录项
// 参数：dir - 指定目录的i节点；name - 文件名；namelen - 文件名长度；
// 返回：高速缓冲区指针；res_dir - 返回的目录项结构指针。
static struct buffer_head * add_entry(struct m_inode * dir,
    const char * name, int namelen, struct dir_entry ** res_dir)
{
    int block,i;
    struct buffer_head * bh;
    struct dir_entry * de;

    // 同样，本函数一上来也需要对函数参数的有效性进行判断和验证。
    // 如果我们在前面定义了符号常数NO_TRUNCATE，那么如果文件名长
    // 度超过最大长度NAME_LEN，则不予处理。如果没有定义过NO_TRUNCATE，
    // 那么在文件名长度超过最大长度NAME_LEN时截短之。
    *res_dir = NULL;
#ifdef NO_TRUNCATE
    if (namelen &gt; NAME_LEN)
        return NULL;
#else
    if (namelen &gt; NAME_LEN)
        namelen = NAME_LEN;
#endif
    // 现在我们开始操作，向指定目录中添加一个指定文件名的目录项。因此
    // 我们需要先读取目录的数据，即取出目录i节点对应块数据区中的数据块
    // 信息。这些逻辑块的块号保存在i节点结构i_zone[9]数组中。我们先取
    // 其中第1个块号，如果目录i节点指向的第一个直接磁盘块号为0，则说明
    // 该目录竟然不含数据，这不正常。于是返回NULL退出。否则我们就从节点
    // 所在设备读取指定目录项数据块。当然，如果不成功，则也返回NULL退出。
    // 如果参数提供的文件名长度等于0，则也返回NULL退出。
    if (!namelen)
        return NULL;
    if (!(block = dir-&gt;i_zone[0]))/*目录文件存储的第一个磁盘逻辑块号，肯定不会是0（0是引导块）*/
        return NULL;
    //目录数据必须存磁盘，不能只存内存，否则关机后就全丢了
    if (!(bh = bread(dir-&gt;i_dev,block)))/*读取目录文件第一个逻辑块的数据到缓存区，里面存放的都是dir_entry，所以下面把b_data强转成dir_entry*/
        return NULL;
    // 此时我们就在这个目录i节点数据块中循环查找最后未使用的空目录项。
    // 首先让目录项结构指针de指向缓冲块中的数据块部分，即第一个目录项处。
    // 其中i是目录中的目录项索引号，在循环开始时初始化为0.
    i = 0;
    de = (struct dir_entry *) bh-&gt;b_data;
    while (1) {
        // 如果当前目录项数据块已经搜索完毕，但还没有找到需要的空目录项，
        // 则释放当前目录项数据块，再读入目录的下一个逻辑块。如果对应的逻辑块。
        // 如果对应的逻辑块不存在就创建一块。如果读取或创建操作失败则返回空。
        // 如果此次读取的磁盘逻辑块数据返回的缓冲块数据为空，说明这块逻辑块
        // 可能是因为不存在而新创建的空块，则把目录项索引值加上一块逻辑块所
        // 能容纳的目录项数DIR_ENTRIES_PER_BLOCK，用以跳过该块并继续搜索。
        // 否则说明新读入的块上有目录项数据，于是让目录项结构指针de指向该块
        // 的缓冲块数据部分，然后在其中继续搜索。其中i/DIR_ENTRIES_PER_BLOCK可
        // 计算得到当前搜索的目录项i所在目录文件中的块号，而create_block函数则可
        // 读取或创建出在设备上对应的逻辑块。
        if ((char *)de &gt;= BLOCK_SIZE+bh-&gt;b_data) {
            brelse(bh);
            bh = NULL;
            block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
            if (!block)
                return NULL;
            if (!(bh = bread(dir-&gt;i_dev,block))) {
                i += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            de = (struct dir_entry *) bh-&gt;b_data;
        }
        // 如果当前所操作的目录项序号i乘上目录结构大小所在长度值已经超过了该目录
        // i节点信息所指出的目录数据长度值i_size，则说明整个目录文件数据中没有
        // 由于删除文件留下的空目录项，因此我们只能把需要添加的新目录项附加到
        // 目录文件数据的末端处。于是对该处目录项进行设置（置该目录项的i节点指针
        // 为空），并更新该目录文件的长度值（加上一个目录项的长度），然后设置目录
        // 的i节点已修改标志，再更新该目录的改变时间为当前时间。
        if (i*sizeof(struct dir_entry) &gt;= dir-&gt;i_size) {
            de-&gt;inode=0;
            dir-&gt;i_size = (i+1)*sizeof(struct dir_entry);
            dir-&gt;i_dirt = 1;
            dir-&gt;i_ctime = CURRENT_TIME;
        }
        // 若当前搜索的目录项de的i节点为空，则表示找到一个还未使用的空闲目录项
        // 或是添加的新目录项。于是更新目录的修改时间为当前时间，并从用户数据区
        // 复制文件名到该目录项的文件名字段，置含有本目录项的相应高速缓冲块已修改
        // 标志。返回该目录项的指针以及该高速缓冲块的指针，退出。
        if (!de-&gt;inode) {
            dir-&gt;i_mtime = CURRENT_TIME;
            for (i=0; i &lt; NAME_LEN ; i++)
                de-&gt;name[i]=(i&lt;namelen)?get_fs_byte(name+i):0;
            bh-&gt;b_dirt = 1;
            *res_dir = de;
            return bh;
        }
        de++;
        i++;
    }
    // 本函数执行不到这里。这也许是Linus在写这段代码时，先复制了上面的find_entry()
    // 函数的代码，而后修改成本函数的。:-)
    brelse(bh);
    return NULL;
}</code></pre></div><p>&#160; （5）截至目前，linux文件系统涉及到好多的结构体、缓存区、磁盘(主要是inode、buffer_head、dir_entry、block等)，不熟悉的初学者看到这里估计都开始晕菜了，这里有个现成的图示（参考1），展示了各个结构体的关系：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202112/2052730-20211206105144928-638193118.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160;整个磁盘文件数据读取流程如下：</p><p>&#160; 先根据文件路径找到文件对应的inode节点。假设是个绝对路径，文件路径是/a/b/c.txt；系统初始化的时候我们已经拿到了根目录对应的inode（磁盘上第一个inode节点就是根目录所在的节点，从这里也可以看出，文件目录也必须保存在磁盘，而不仅仅是保存在内存，避免断电后丢失），把根目录文件的block内容读进来，是一系列的dir_entry结构体。然后逐个遍历，比较文件名是不是等于a，最后得到一个目录a对应的dir_entry；<br />&#160; &#160; &#160; &#160;dir_entry结构体不仅保存了文件名，还保存了对应的inode号；根据inode号把a目录文件的内容也读取进来；以此类推，得到c对应的dir_entry<br />&#160; &#160; &#160; &#160;再根据c对应的dir_entry的inode号，从磁盘把inode的内容读进来，发现就是个普通文件；至此，找到了这个文件对应的inode节点，完成fd-&gt;file结构体-&gt;inode结构体的赋值<br />&#160; &#160; &#160; &#160;最后根据fd找到对应的inode节点，根据file结构体的pos字段；根据数据在文件中的偏移，可以算出应该取i_zone[9]字段的哪个索引，文件的前7块对应索引0-6，前7到7+512对应索引7等。得到索引后，读取i_zone数组在该索引的值，即我们要读取的数据在硬盘的数据块。然后把这个数据块从硬盘读取进来。返回给用户<br />&#160; &#160; &#160; &#160; 整个流程总结：磁盘inode首节点-&gt;dir_entry-&gt;根据pathname查找目录或文件inode编号-&gt;从磁盘读取inode内容-&gt;分析i_zone得到文件内容的block编号-&gt;从磁盘读数据；整个思路流程和内存管理的CR3分页检索没有任何本质区别；<br />&#160; &#160; &#160; （6）linux常用的命令还有“cat&#160; /home/test.c”、“cd /home/jdk/java” 等目录相关的操作。通过前面的分析得知，操作文件或目录，本质就是读写其元信息，这些都存放在inode里面，所以想想办法得到inode，代码如下：</p><div class="codebox"><pre class="vscroll"><code>/*
 *    get_dir()
 *
 * Getdir traverses the pathname until it hits the topmost directory.
 * It returns NULL on failure.
 */
//// 搜寻指定路径的目录（或文件名）的i节点。
// 参数：pathname - 路径名
// 返回：目录或文件的i节点指针。
static struct m_inode * get_dir(const char * pathname)
{
    char c;
    const char * thisname;
    struct m_inode * inode;
    struct buffer_head * bh;
    int namelen,inr,idev;
    struct dir_entry * de;

    // 搜索操作会从当前任务结构中设置的根（或伪根）i节点或当前工作目录i节点
    // 开始，因此首先需要判断进程的根i节点指针和当前工作目录i节点指针是否有效。
    // 如果当前进程没有设定根i节点，或者该进程根i节点指向是一个空闲i节点（引用为0），
    // 则系统出错停机。如果进程的当前工作目录i节点指针为空，或者该当前工作目录
    // 指向的i节点是一个空闲i节点，这也是系统有问题，停机。
    if (!current-&gt;root || !current-&gt;root-&gt;i_count)
        panic(&quot;No root inode&quot;);
    if (!current-&gt;pwd || !current-&gt;pwd-&gt;i_count)
        panic(&quot;No cwd inode&quot;);
    // 如果用户指定的路径名的第1个字符是&#039;/&#039;，则说明路径名是绝对路径名。则从
    // 根i节点开始操作，否则第一个字符是其他字符，则表示给定的相对路径名。
    // 应从进程的当前工作目录开始操作。则取进程当前工作目录的i节点。如果路径
    // 名为空，则出错返回NULL退出。此时变量inode指向了正确的i节点 -- 进程的
    // 根i节点或当前工作目录i节点之一。
    if ((c=get_fs_byte(pathname))==&#039;/&#039;) {
        inode = current-&gt;root;
        pathname++;
    } else if (c)
        inode = current-&gt;pwd;
    else
        return NULL;    /* empty name is bad */
    // 然后针对路径名中的各个目录名部分和文件名进行循环出路，首先把得到的i节点
    // 引用计数增1，表示我们正在使用。在循环处理过程中，我们先要对当前正在处理
    // 的目录名部分（或文件名）的i节点进行有效性判断，并且把变量thisname指向
    // 当前正在处理的目录名部分（或文件名）。如果该i节点不是目录类型的i节点，
    // 或者没有可进入该目录的访问许可，则放回该i节点，并返回NULL退出。当然，刚
    // 进入循环时，当前的i节点就是进程根i节点或者是当前工作目录的i节点。
    inode-&gt;i_count++;
    while (1) {
        thisname = pathname;
        if (!S_ISDIR(inode-&gt;i_mode) || !permission(inode,MAY_EXEC)) {
            iput(inode);
            return NULL;
        }
        // 每次循环我们处理路径名中一个目录名（或文件名）部分。因此在每次循环中
        // 我们都要从路径名字符串中分离出一个目录名（或文件名）。方法是从当前路径名
        // 指针pathname开始处搜索检测字符，知道字符是一个结尾符（NULL）或者是一
        // 个&#039;/&#039;字符。此时变量namelen正好是当前处理目录名部分的长度，而变量thisname
        // 正指向该目录名部分的开始处。此时如果字符是结尾符NULL，则表明以及你敢搜索
        // 到路径名末尾，并已到达最后指定目录名或文件名，则返回该i节点指针退出。
        // 注意！如果路径名中最后一个名称也是一个目录名，但其后面没有加上&#039;/&#039;字符，
        // 则函数不会返回该最后目录的i节点！例如：对于路径名/usr/src/linux，该函数
        // 将只返回src/目录名的i节点。
        for(namelen=0;(c=get_fs_byte(pathname++))&amp;&amp;(c!=&#039;/&#039;);namelen++)
            /* nothing */ ;
        if (!c)
            return inode;
        // 在得到当前目录名部分（或文件名）后，我们调用查找目录项函数find_entry()在
        // 当前处理的目录中寻找指定名称的目录项。如果没有找到，则返回该i节点，并返回
        // NULL退出。然后在找到的目录项中取出其i节点号inr和设备号idev，释放包含该目录
        // 项的高速缓冲块并放回该i节点。然后去节点号inr的i节点inode，并以该目录项为
        // 当前目录继续循环处理路径名中的下一目录名部分（或文件名）。
        if (!(bh = find_entry(&amp;inode,thisname,namelen,&amp;de))) {
            iput(inode);
            return NULL;
        }
        inr = de-&gt;inode;                        // 当前目录名部分的i节点号
        idev = inode-&gt;i_dev;
        brelse(bh);
        iput(inode);
        if (!(inode = iget(idev,inr)))          // 取i节点内容。
            return NULL;
    }
}</code></pre></div><p>&#160; &#160;（7）查找目录的最高路径，比如：</p><p> cd&#160; /home/test的最高路径是test（最后一个反斜杠后面是test）：basename是test，namelen=4；<br /> cd&#160; /home/test/的最高路径是空的（最后一个反斜杠后面是空的）：basename是null，namelen=0；</p><div class="codebox"><pre class="vscroll"><code>/*
 *    dir_namei()
 *
 * dir_namei() returns the inode of the directory of the
 * specified name, and the name within that directory.
 */
// 参数：pathname - 目录路径名；namelen - 路径名长度；name - 返回的最顶层目录名。
// 返回：指定目录名最顶层目录的i节点指针和最顶层目录名称及长度。出错时返回NULL。
// 注意！！这里&quot;最顶层目录&quot;是指路径名中最靠近末端的目录。
static struct m_inode * dir_namei(const char * pathname,
    int * namelen, const char ** name)
{
    char c;
    const char * basename;
    struct m_inode * dir;

    // 首先取得指定路径名最顶层目录的i节点。然后对路径名Pathname 进行搜索检测，查出
    // 最后一个&#039;/&#039;字符后面的名字字符串，计算其长度，并且返回最顶层目录的i节点指针。
    // 注意！如果路径名最后一个字符是斜杠字符&#039;/&#039;，那么返回的目录名为空，并且长度为0.
    // 但返回的i节点指针仍然指向最后一个&#039;/&#039;字符钱目录名的i节点。
    if (!(dir = get_dir(pathname)))
        return NULL;
    basename = pathname;
    while ((c=get_fs_byte(pathname++)))
        if (c==&#039;/&#039;)
            basename=pathname;
    *namelen = pathname-basename-1;
    *name = basename;
    return dir;
}</code></pre></div><p>&#160; &#160;（8）这个可能是最有用的函数之一了：namei函数，用户传入路径，返回对应的inode节点</p><div class="codebox"><pre class="vscroll"><code>/*
 *    namei()
 *
 * is used by most simple commands to get the inode of a specified name.
 * Open, link etc use their own routines, but this is enough for things
 * like &#039;chmod&#039; etc.
 */
//// 取指定路径名的i节点。
// 参数：pathname - 路径名。
// 返回：对应的i节点。
struct m_inode * namei(const char * pathname)
{
    const char * basename;
    int inr,dev,namelen;
    struct m_inode * dir;
    struct buffer_head * bh;
    struct dir_entry * de;

    // 首先查找指定路径的最顶层目录的目录名并得到其i节点，若不存在，则返回NULL退出。
    // 如果返回的最顶层名字长度是0，则表示该路径名以一个目录名为最后一项。因此我们
    // 已经找到对应目录的i节点，可以直接返回该i节点退出。
    if (!(dir = dir_namei(pathname,&amp;namelen,&amp;basename)))
        return NULL;
    if (!namelen)            /* special case: &#039;/usr/&#039; etc */
        return dir;
    // 然后在返回的顶层目录中寻找指定文件名目录项的i节点。注意！因为如果最后也是一个
    // 目录名，但其后没有加&#039;/&#039;,则不会返回该目录的i节点！例如：/usr/src/linux,将只返回
    // src/目录名的i节点。因为函数dir_namei()把不以&#039;/&#039;结束的最后一个名字当作一个文件名
    // 来看待，所以这里需要单独对这种情况使用寻找目录项i节点函数find_entry()进行处理。
    // 此时de中含有寻找到的目录项指针，而dir是包含该目录项的目录的i节点指针。
    bh = find_entry(&amp;dir,basename,namelen,&amp;de);
    if (!bh) {
        iput(dir);
        return NULL;
    }
    // 接着取该目录项的i节点号和设备号，并释放包含该目录项的高速缓冲块并返回目录i节点。
    // 然后取对应节点号的i节点，修改其被访问时间为当前时间，并置已修改标志。最后返回
    // 该i节点指针。
    inr = de-&gt;inode;
    dev = dir-&gt;i_dev;/*子目录设备号要和父目录一致*/
    brelse(bh);
    iput(dir);
    dir=iget(dev,inr);
    if (dir) {
        dir-&gt;i_atime=CURRENT_TIME;
        dir-&gt;i_dirt=1;
    }
    return dir;
}</code></pre></div><p>&#160; &#160;namei没有做任何权限的判断，也只是查找现成的dir_entry，如果没有就返回null了，所以只能用在find -name这种命令；但实际用户使用时，还涉及到文件的权限校验，文件打开方式判断（只读？可读可写？）等，情况比find -name这种命令复杂很多，需要单独重新写个接口来实现这些需求，如下：相比namei，</p><p>&#160; 检查了权限和打开模式；<br />&#160; &#160; &#160; &#160;如果没找到对饮的inode就新建inode，而不是直接返回null；“宏观”层面感受：用户调用open函数想打开一个文件，如果该文件不存在，就新建文件，并返回文件的handler！</p><div class="codebox"><pre class="vscroll"><code>/*
 *    open_namei()
 *
 * namei for open - this is in fact almost the whole open-routine.
 */
//// 文件打开namei函数。
// 参数filename是文件名，flag是打开文件标志，他可取值：O_RDONLY(只读)、O_WRONLY(只写)
// 或O_RDWR(读写)，以及O_CREAT(创建)、O_EXCL(被创建文件必须不存在)、O_APPEND(在文件尾
// 添加数据)等其他一些标志的组合。如果本调用创建了一个新文件，则mode就用于指定文件的
// 许可属性。这些属性有S_IRWXU(文件宿主具有读、写和执行权限)、S_IRUSR(用户具有读文件
// 权限)、S_IRWXG(组成员具有读、写和执行权限)等等。对于新创建的文件，这些属性只应用于
// 将来对文件的访问，创建了只读文件的打开调用也将返回一个可读写的文件句柄。
// 返回：成功返回0，否则返回出错码；res_inode - 返回对应文件路径名的i节点指针。
int open_namei(const char * pathname, int flag, int mode,
    struct m_inode ** res_inode)
{
    const char * basename;
    int inr,dev,namelen;
    struct m_inode * dir, *inode;
    struct buffer_head * bh;
    struct dir_entry * de;

    // 首先对函数参数进行合理的处理。如果文件访问模式标志是只读(0)，但是文件截零标志
    // O_TRUNC却置位了，则在文件打开标志中添加只写O_WRONLY。这样做的原因是由于截零标志
    // O_TRUNC必须在文件可写情况下才有效。然后使用当前进程的文件访问许可屏蔽码，屏蔽掉
    // 给定模式中的相应位，并添上对普通文件标志I_REGULAR。该标志将用于打开的文件不存在
    // 而需要创建文件时，作为新文件的默认属性。
    if ((flag &amp; O_TRUNC) &amp;&amp; !(flag &amp; O_ACCMODE))
        flag |= O_WRONLY;
    mode &amp;= 0777 &amp; ~current-&gt;umask;
    mode |= I_REGULAR;
    // 然后根据指定的路径名寻找对应的i节点，以及最顶端目录名及其长度。此时如果最顶端目录
    // 名长度为0（例如&#039;/usr/&#039;这种路径名的情况），那么若操作不是读写、创建和文件长度截0，
    // 则表示是在打开一个目录名文件操作。于是直接返回该目录的i节点并返回0退出。否则说明
    // 进程操作非法，于是放回该i节点，返回出错码。
    if (!(dir = dir_namei(pathname,&amp;namelen,&amp;basename)))
        return -ENOENT;
    if (!namelen) {            /* special case: &#039;/usr/&#039; etc */
        if (!(flag &amp; (O_ACCMODE|O_CREAT|O_TRUNC))) {
            *res_inode=dir;
            return 0;
        }
        iput(dir);
        return -EISDIR;
    }
    // 接着根据上面得到的最顶层目录名的i节点dir，在其中查找取得路径名字符串中最后的文件名
    // 对应的目录项结构de，并同时得到该目录项所在的高速缓冲区指针。如果该高速缓冲指针为NULL，
    // 则表示没有找到对应文件名的目录项，因此只可能是创建文件操作。此时如果不是创建文件，则
    // 放回该目录的i节点，返回出错号退出。如果用户在该目录没有写的权力，则放回该目录的i节点，
    // 返回出错号退出。
    bh = find_entry(&amp;dir,basename,namelen,&amp;de);
    if (!bh) {
        if (!(flag &amp; O_CREAT)) {
            iput(dir);
            return -ENOENT;
        }
        if (!permission(dir,MAY_WRITE)) {
            iput(dir);
            return -EACCES;
        }
        // 现在我们确定了是创建操作并且有写操作许可。因此我们就在目录i节点对设备上申请一个
        // 新的i节点给路径名上指定的文件使用。若失败则放回目录的i节点，并返回没有空间出错码。
        // 否则使用该新i节点，对其进行初始设置：置节点的用户id；对应节点访问模式；置已修改
        // 标志。然后并在指定目录dir中添加一个新目录项。
        inode = new_inode(dir-&gt;i_dev);
        if (!inode) {
            iput(dir);
            return -ENOSPC;
        }
        inode-&gt;i_uid = current-&gt;euid;
        inode-&gt;i_mode = mode;
        inode-&gt;i_dirt = 1;
        bh = add_entry(dir,basename,namelen,&amp;de);
        // 如果返回的应该含有新目录项的高速缓冲区指针为NULL，则表示添加目录项操作失败。于是
        // 将该新i节点的引用计数减1，放回该i节点与目录的i节点并返回出错码退出。否则说明添加
        // 目录项操作成功。于是我们来设置该新目录的一些初始值：置i节点号为新申请的i节点的号
        // 码；并置高速缓冲区已修改标志。然后释放该高速缓冲区，放回目录的i节点。返回新目录
        // 项的i节点指针，并成功退出。
        if (!bh) {
            inode-&gt;i_nlinks--;
            iput(inode);
            iput(dir);
            return -ENOSPC;
        }
        de-&gt;inode = inode-&gt;i_num;
        bh-&gt;b_dirt = 1;
        brelse(bh);
        iput(dir);
        *res_inode = inode;
        return 0;
    }
    // 若上面在目录中取文件名对应目录项结构的操作成功（即bh不为NULL），则说明指定打开的文件已
    // 经存在。于是取出该目录项的i节点号和其所在设备号，并释放该高速缓冲区以及放回目录的i节点
    // 如果此时堵在操作标志O_EXCL置位，但现在文件已经存在，则返回文件已存在出错码退出。
    inr = de-&gt;inode;
    dev = dir-&gt;i_dev;
    brelse(bh);
    iput(dir);
    if (flag &amp; O_EXCL)
        return -EEXIST;
    // 然后我们读取该目录项的i节点内容。若该i节点是一个目录i节点并且访问模式是只写或读写，或者
    // 没有访问的许可权限，则放回该i节点，返回访问权限出错码退出。
    if (!(inode=iget(dev,inr)))
        return -EACCES;
    if ((S_ISDIR(inode-&gt;i_mode) &amp;&amp; (flag &amp; O_ACCMODE)) ||
        !permission(inode,ACC_MODE(flag))) {
        iput(inode);
        return -EPERM;
    }
    // 接着我们更新该i节点的访问时间字段值为当前时间。如果设立了截0标志，则将该i节点的文件长度
    // 截0.最后返回该目录项i节点的指针，并返回0（成功）。
    inode-&gt;i_atime = CURRENT_TIME;
    if (flag &amp; O_TRUNC)
        truncate(inode);
    *res_inode = inode;
    return 0;
}</code></pre></div><p>&#160; &#160;至此，不知道各读者有没有发现文件和目录相关操作的共性：全都围绕inode和dir_entry两个结构体各种操作！</p><p>&#160; dir_entry有字符串数组，存放了目录或文件的字符，可以先根据字符串找到目标dir_entry<br />&#160; &#160; &#160; &#160;取出目标dir_entry的inode字段，这个字段标时了inode节点的偏移位置（或者说在磁盘上block的位置）<br />&#160; &#160; &#160; &#160;利用inode偏移从磁盘读取inode节点，这里面包含了文件的元信息，尤其时i_zone字段，根据这个进一步从磁盘读取文件数据<br />&#160; &#160; &#160; &#160;磁盘中的inode通过inode位图标记是否使用；dir_entry在inode根节点；内存中inode存放在inode_table数组！不论是在磁盘，还是在内存，本质上都是把inode或dir_entry结构体的实例集合起来统一管理（检索查找）！<br />&#160; 两个结构体本质上都是用来做索引，dir_entry字段少，相当于简版的索引！inode字段多，相当于完整的索引！</p><p> （9）依次类推，mknod也是类似的操作（这居然还是个系统调用，级别相当的高）：</p><div class="codebox"><pre class="vscroll"><code>//// 创建一个设备特殊文件或普通文件节点(node)
// 该函数创建名称为filename,由mode和dev指定的文件系统节点（普通文件、设备特殊文件或命名管道）
// 参数：filename - 路径名；mode - 指定使用许可以及所创建节点的类型；dev - 设备号。
// 返回：成功则返回0，否则返回出错码。
int sys_mknod(const char * filename, int mode, int dev)
{
    const char * basename;
    int namelen;
    struct m_inode * dir, * inode;
    struct buffer_head * bh;
    struct dir_entry * de;

    // 首先检查操作许可和参数的有效性并取路径名中顶层目录的i节点。如果不是超级用户，则返回
    // 访问许可出错码。如果找不到对应路径名中顶层目录的i节点，则返回出错码。如果最顶端的
    // 文件名长度为0，则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回出错码退出。
    // 如果在该目录中没有写的权限，则放回该目录的i节点，返回访问许可出错码退出。如果不是超级
    // 用户，则返回访问许可出错码。
    if (!suser())
        return -EPERM;
    if (!(dir = dir_namei(filename,&amp;namelen,&amp;basename)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir,MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    // 然后我们搜索一下路径名指定的文件是否已经存在。若已经存在则不能创建同名文件节点。
    // 如果对应路径名上最后的文件名的目录项已经存在，则释放包含该目录项的缓冲区块并放回
    // 目录的i节点，放回文件已存在的出错码退出。
    bh = find_entry(&amp;dir,basename,namelen,&amp;de);
    if (bh) {
        brelse(bh);
        iput(dir);
        return -EEXIST;
    }
    // 否则我们就申请一个新的i节点，并设置该i节点的属性模式。如果要创建的是块设备文件或者是
    // 字符设备文件，则令i节点的直接逻辑块指针0等于设备号。即对于设备文件来说，其i节点的
    // i_zone[0]中存放的是该设备文件所定义设备的设备号。然后设置该i节点的修改时间、访问
    // 时间为当前时间，并设置i节点已修改标志。
    inode = new_inode(dir-&gt;i_dev);
    if (!inode) {
        iput(dir);
        return -ENOSPC;
    }
    inode-&gt;i_mode = mode;
    if (S_ISBLK(mode) || S_ISCHR(mode))
        inode-&gt;i_zone[0] = dev;
    inode-&gt;i_mtime = inode-&gt;i_atime = CURRENT_TIME;
    inode-&gt;i_dirt = 1;
    // 接着为这个新的i节点在目录中新添加一个目录项。如果失败（包含该目录项的高速缓冲块指针为
    // NULL），则放回目录的i节点，吧所申请的i节点引用连接计数复位，并放回该i节点，返回出错码退出。
    bh = add_entry(dir,basename,namelen,&amp;de);
    if (!bh) {
        iput(dir);
        inode-&gt;i_nlinks=0;
        iput(inode);
        return -ENOSPC;
    }
    // 现在添加目录项操作也成功了，于是我们来设置这个目录项内容。令该目录项的i节点字段于新i节点
    // 号，并置高速缓冲区已修改标志，放回目录和新的i节点，释放高速缓冲区，最后返回0（成功）。
    de-&gt;inode = inode-&gt;i_num;/*刚创建的inode和dir_entry做映射*/
    bh-&gt;b_dirt = 1;
    iput(dir);
    iput(inode);
    brelse(bh);
    return 0;
}</code></pre></div><p>&#160; &#160;早期连创建目录都是系统调用，只能系统管理员创建的：</p><div class="codebox"><pre class="vscroll"><code>//// 创建一个目录
// 参数：pathname - 路径名；mode - 目录使用的权限属性。
// 返回：成功则返回0，否则返回出错码。
int sys_mkdir(const char * pathname, int mode)
{
    const char * basename;
    int namelen;
    struct m_inode * dir, * inode;
    struct buffer_head * bh, *dir_block;
    struct dir_entry * de;

    // 首先检查操作许可和参数的有效性并取路径名中顶层目录的i节点。如果不是超级用户，则
    // 放回访问许可出错码。如果找不到对应路径名中顶层目录的i节点，则返回出错码。如果最
    // 顶端的文件名长度为0，则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回
    // 出错码退出。如果在该目录中没有写权限，则放回该目录的i节点，返回访问许可出错码退出。
    // 如果不是超级用户，则返回访问许可出错码。
    if (!suser())
        return -EPERM;
    if (!(dir = dir_namei(pathname,&amp;namelen,&amp;basename)))
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir,MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    // 然后我们搜索一下路径名指定的目录名是否已经存在。若已经存在则不能创建同名目录节点。
    // 如果对应路径名上最后的目录名的目录项已经存在，则释放包含该目录项的缓冲区块并放回
    // 目录的i节点，返回文件已经存在的出错码退出。否则我们就申请一个新的i节点，并设置该i
    // 节点的属性模式：置该新i节点对应的文件长度为32字节（2个目录项的大小），置节点已修改
    // 标志，以及节点的修改时间和访问时间，2个目录项分别用于‘.’和&#039;..&#039;目录。
    bh = find_entry(&amp;dir,basename,namelen,&amp;de);
    if (bh) {
        brelse(bh);
        iput(dir);
        return -EEXIST;
    }
    inode = new_inode(dir-&gt;i_dev);
    if (!inode) {
        iput(dir);
        return -ENOSPC;
    }
    inode-&gt;i_size = 32;/*目录的inode节点size是32，这个是固定的*/
    inode-&gt;i_dirt = 1;
    inode-&gt;i_mtime = inode-&gt;i_atime = CURRENT_TIME;
    // 接着为该新i节点申请一用于保存目录项数据的磁盘块，用于保存目录项结构信息。并令i节
    // 点的第一个直接块指针等于该块号。如果申请失败则放回对应目录的i节点；复位新申请的i
    // 节点连接计数；放回该新的i节点，返回没有空间出错码退出。否则置该新的i节点已修改标志。
    if (!(inode-&gt;i_zone[0]=new_block(inode-&gt;i_dev))) {
        iput(dir);
        inode-&gt;i_nlinks--;
        iput(inode);
        return -ENOSPC;
    }
    inode-&gt;i_dirt = 1;
    // 从设备上读取新申请的磁盘块（目的是吧对应块放到高速缓冲区中）。若出错，则放回对应
    // 目录的i节点；释放申请的磁盘块；复位新申请的i节点连接计数；放回该新的i节点，返回没有
    // 空间出错码退出。
    if (!(dir_block=bread(inode-&gt;i_dev,inode-&gt;i_zone[0]))) {
        iput(dir);
        free_block(inode-&gt;i_dev,inode-&gt;i_zone[0]);
        inode-&gt;i_nlinks--;
        iput(inode);
        return -ERROR;
    }
    // 然后我们在缓冲块中建立起所创建目录文件中的2个默认的新目录项(&#039;.&#039;和&#039;..&#039;)结构数据。
    // 首先令de指向存放目录项的数据块，然后置该目录项的i节点号字段等于新申请的i节点号，
    // 名字字段等于&#039;.&#039;。然后de指向下一个目录项结构，并在该结构中存放上级目录的i节点号
    // 和名字&#039;..&#039;。然后设置该高速缓冲块 已修改标志，并释放该缓冲块。再初始化设置新i节点
    // 的模式字段，并置该i节点已修改标志。
    de = (struct dir_entry *) dir_block-&gt;b_data;
    de-&gt;inode=inode-&gt;i_num;
    strcpy(de-&gt;name,&quot;.&quot;);/*新创建的目录，用ls -al查询会发现有.和..这两个目录*/
    de++;
    de-&gt;inode = dir-&gt;i_num;
    strcpy(de-&gt;name,&quot;..&quot;);
    inode-&gt;i_nlinks = 2;
    dir_block-&gt;b_dirt = 1;
    brelse(dir_block);
    inode-&gt;i_mode = I_DIRECTORY | (mode &amp; 0777 &amp; ~current-&gt;umask);
    inode-&gt;i_dirt = 1;
    // 现在我们在指定目录中新添加一个目录项，用于存放新建目录的i节点号和目录名。如果
    // 失败(包含该目录项的高速缓冲区指针为NULL)，则放回目录的i节点；所申请的i节点引用
    // 连接计数复位，并放回该i节点。返回出错码退出。
    bh = add_entry(dir,basename,namelen,&amp;de);
    if (!bh) {
        iput(dir);
        free_block(inode-&gt;i_dev,inode-&gt;i_zone[0]);
        inode-&gt;i_nlinks=0;
        iput(inode);
        return -ENOSPC;
    }
    // 最后令该新目录项的i节点字段等于新i节点号，并置高速缓冲块已修改标志，放回目录和
    // 新的i节点，是否高速缓冲块，最后返回0(成功).
    de-&gt;inode = inode-&gt;i_num;
    bh-&gt;b_dirt = 1;
    dir-&gt;i_nlinks++;
    dir-&gt;i_dirt = 1;
    iput(dir);
    iput(inode);
    brelse(bh);
    return 0;
}</code></pre></div><p>&#160; &#160;（10）创建硬链接：本质就是新建该路径的dir_entry，然后和文件原inode映射绑定！</p><p>找到指定文件的inode<br />再指定文件的路径中创建新的dir_entry<br />新创建的dir_entry映射到原inode：de-&gt;inode = oldinode-&gt;i_num</p><div class="codebox"><pre class="vscroll"><code>//// 为文件建立一个文件名目录项
// 为一个已存在的文件创建一个新链接(也称为硬链接 - hard link)
// 参数：oldname - 原路径名；newname - 新的路径名
// 返回：若成功则返回0，否则返回出错号。
int sys_link(const char * oldname, const char * newname)
{
    struct dir_entry * de;
    struct m_inode * oldinode, * dir;
    struct buffer_head * bh;
    const char * basename;
    int namelen;

    // 首先对原文件名进行有效性验证，它应该存在并且不是一个目录名。所以我们先取得原文件
    // 路径名对应的i节点oldname.若果为0，则表示出错，返回出错号。若果原路径名对应的是
    // 一个目录名，则放回该i节点，也返回出错号。
    oldinode=namei(oldname);
    if (!oldinode)
        return -ENOENT;
    if (S_ISDIR(oldinode-&gt;i_mode)) {
        iput(oldinode);
        return -EPERM;
    }
    // 然后查找新路径名的最顶层目录的i节点dir，并返回最后的文件名及其长度。如果目录的
    // i节点没有找到，则放回原路径名的i节点，返回出错号。如果新路径名中不包括文件名，
    // 则放回原路径名i节点和新路径名目录的i节点，返回出错号。
    dir = dir_namei(newname,&amp;namelen,&amp;basename);
    if (!dir) {
        iput(oldinode);
        return -EACCES;
    }
    if (!namelen) {//以反斜杠结尾，后面啥都没了，导致namelen=0；
        iput(oldinode);
        iput(dir);
        return -EPERM;
    }
    // 我们不能跨设备建立硬链接。因此如果新路径名顶层目录的设备号与原路径名的设备号不
    // 一样，则放回新路径名目录的i节点和原路径名的i节点，返回出错号。另外，如果用户没
    // 有在新目录中写的权限，则也不能建立连接，于是放回新路径名目录的i节点和原路径名
    // 的i节点，返回出错号。
    if (dir-&gt;i_dev != oldinode-&gt;i_dev) {
        iput(dir);
        iput(oldinode);
        return -EXDEV;
    }
    if (!permission(dir,MAY_WRITE)) {
        iput(dir);
        iput(oldinode);
        return -EACCES;
    }
    // 现在查询该新路径名是否已经存在，如果存在则也不能建立链接。于是释放包含该已存在
    // 目录项的高速缓冲块，放回新路径名目录的i节点和原路径名的i节点，返回出错号。
    bh = find_entry(&amp;dir,basename,namelen,&amp;de);
    if (bh) {
        brelse(bh);
        iput(dir);
        iput(oldinode);
        return -EEXIST;
    }
    // 现在所有条件都满足了，于是我们在新目录中添加一个目录项。若失败则放回该目录的
    // i节点和原路径名的i节点，返回出错号。否则初始设置该目录项的i节点号等于原路径名的
    // i节点号，并置包含该新添加目录项的缓冲块已修改标志，释放该缓冲块，放回目录的i节点。
    bh = add_entry(dir,basename,namelen,&amp;de);
    if (!bh) {
        iput(dir);
        iput(oldinode);
        return -ENOSPC;
    }
    de-&gt;inode = oldinode-&gt;i_num;/*老的inode对一个的block号给新建的dir_entry，借此建立映射*/
    bh-&gt;b_dirt = 1;
    brelse(bh);
    iput(dir);
    // 再将原节点的硬链接计数加1，修改其改变时间为当前时间，并设置i节点已修改标志。最后
    // 放回原路径名的i节点，并返回0（成功）。
    oldinode-&gt;i_nlinks++;
    oldinode-&gt;i_ctime = CURRENT_TIME;
    oldinode-&gt;i_dirt = 1;
    iput(oldinode);
    return 0;
}</code></pre></div><p>&#160; 总结：</p><p>&#160; 目录本质上就是一系列dir_entry的集合！创建/修改目录就是创建/修改dir_entry，创建/修改文件就是创建/修改inode；&#160; &#160;<br />&#160; &#160; &#160; &#160;逆向或破解时掌握dir_entry和inode，就相当于掌握了所有的目录和文件；</p><br /><br /><br /><p>参考：</p><p>1、https://zhuanlan.zhihu.com/p/76595175&#160; &#160; 深入浅出文件系统原理之文件读取（基于linux0.11）</p><p>2、https://www.bilibili.com/video/BV1tQ4y1d7mo?p=27&#160; linux内核精讲</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sat, 08 Oct 2022 04:42:01 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=453#p453</guid>
		</item>
	</channel>
</rss>
