<?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=445&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（四）：文件系统——挂载和卸载]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=445</link>
		<description><![CDATA[linux源码解读（四）：文件系统——挂载和卸载 最近发表的帖子。]]></description>
		<lastBuildDate>Sat, 08 Oct 2022 04:33:22 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（四）：文件系统——挂载和卸载]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=452#p452</link>
			<description><![CDATA[<p>对于普通用户而言，日常用的都是windows操作系统。windows把整个物理硬盘分成C、D、E、F.....等逻辑分区，用户可以随意在各个逻辑分区存放数据文件；逻辑分区之间是独立互不影响的，格式化某个逻辑分区，不会影响其他逻辑分区的数据，所以C、D、E、F.....等逻辑分区就是磁盘的根目录；如果要调整逻辑分区的大小，需要用专业的工具操作，比如windows自带的diskmgmt.msc工具。扩容时，需要把空闲的磁盘空间“加入”到目标逻辑分区，这个过程就是挂载，挂载后用户就能正常使用；而在linux下，所有的文件、目录、设备都有一个路径，这个路径永远以/开头，用/分隔。如果有设备加入（比如增加磁盘、U盘等），同样需要挂载到某个路径下、让linux的目录和新设备的目录合二为一后才能正常使用（谁让linux是万物皆文件了？设备都被当成是文件，自然要放在某个目录下了）；上面的说法可能有点抽象，这里举个栗子：公司跳槽来了一位新同事，这位新同事先要和公司签订劳动合同，然后去HR那里注册身份信息、提交银行卡号等，这些一切手续办理完毕后才会去业务部门报道开始干活；linux下面也类似：新增设备后，先把设备相关信息加入到某些结构体（这里用的是超级块），然后通过这些结构体去读写（检索）该设备，否则内核是没法管理新增设备的！ 前面介绍过，linux采用了super_block保存inode位图和数据块位图信息，所以super_block是整个文件系统“最上层”的结构体，所有mount、unmount等操作也都是从super_block开始的！所有相关的操作都在super.c文件里，接下来我们一一解析！</p><p>&#160; 1、这里再说明一下各种概念之间的关系，如下图：</p><p>&#160; linux操作系统内核或app不会直接读写磁盘，都是直接读写高速缓存区的（通过memcpy拷贝内存数据）<br />&#160; &#160; &#160; &#160;高速缓存区调用ll_rw_block函数读写磁盘<br />&#160; &#160; &#160; &#160;super_block、inode、数据块等结构体即存磁盘，也可以存内存，通过ll_rw_block函数在内存和磁盘之间同步<br />&#160; &#160; &#160; &#160;buffer_head的block和磁盘中的block是有一定对应关系的</p><p><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202112/2052730-20211203183132677-2000773010.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; 这个图看起来有点复杂，其实原理很简单：</p><p>&#160; 内存和磁盘本质功能都一样：都是用来存储数据的，唯一的区别就是掉电后数据丢不丢<br />&#160; &#160; &#160; &#160;既然存了数据，自然也要读取了，就涉及到寻址；内存寻址很简单：内存最小的存储单位是byte，每个byte都有自己的地址编号，可以根据地址编号直接读数据，比如 int *p=0x12345678，意味着从地址0x12345678处开始依次读取4byte的数据，数据结尾地址就是0x12345678+4=0x1234567b；<br />&#160; &#160; &#160; &#160;相比内存，磁盘读数据稍微要“麻烦”一点：磁盘被人为划分成了块，每块的大小是512byte*8=4KB；为了方便读取块的数据，也需要给每个块编号，这个编号和内存的地址编号本质都是一样的；为了标记块是否被使用，或者被哪些文件使用，诞生了inode结构体，具体使用了i_zone字段记录！进一步：为了记录哪些inode结构体被使用，又诞生了inode位图（存放inode结构体的block是有限的，导致inode结构体的数量也受限，需要省着点用！）<br />&#160; &#160; &#160; &#160;内存、磁盘这类块存储设备，使用结构体管理时，结构体的字段一般都有：是否锁定lock、是否更新update、是否有数据dirty、被使用/引用次数count、当前被那个进程在使用task、后面有哪些进程在排队等待wait.......<br />&#160; &#160;2、（1）既然super_block是“根索引”，意味着抓住了super_block，就等于抓住了整个文件系统（类似进程的内存用页目录表、页表项管理和索引类似，抓住了CR3，就得到了进程的所有内存）。为了管理所有的super_block，Linux早期的0.11版本采用了最原始的数组：</p><p>&#160; &#160;// 超级块结构表数组（NR_SUPER = 8）<br />&#160; &#160;struct super_block super_block[NR_SUPER];<br />&#160; 这里要吐槽了：数组只有8个元素，意味着只能同时管理8个文件系统；不过后期的linux貌似改了，用链表串接了所有的super_block，只要内存够，可以存好多的super_block！</p><p>&#160; &#160; &#160; （2）超级块本质上也是内存的一块，所以和其他快一样，都涉及到锁、等待、释放等操作，代码如下：可以看到和inode、block等思路完全一样，没任何区别！</p><div class="codebox"><pre class="vscroll"><code>// 以下3个函数（lock_super()、free_super()和wait_on_super()）的作用与inode.c文件中头
// 3个函数的作用雷同，只是这里操作的对象换成了超级块。
//// 锁定超级块
// 如果超级块已被锁定，则将当前任务置为不可中断的等待状态，并添加到该超级块等待队列
// s_wait中。直到该超级块解锁并明确地唤醒本地任务。然后对其上锁。
static void lock_super(struct super_block * sb)
{
    cli();                          // 关中断
    while (sb-&gt;s_lock)              // 如果该超级块已经上锁，则睡眠等待。
        sleep_on(&amp;(sb-&gt;s_wait));
    sb-&gt;s_lock = 1;                 // 会给超级块加锁（置锁定标志）
    sti();                          // 开中断
}

//// 对指定超级块解锁
// 复位超级块的锁定标志，并明确地唤醒等待在此超级块等待队列s_wait上的所有进程。
// 如果使用ulock_super这个名称则可能更妥贴。
static void free_super(struct super_block * sb)
{
    cli();
    sb-&gt;s_lock = 0;             // 复位锁定标志
    wake_up(&amp;(sb-&gt;s_wait));     // 唤醒等待该超级块的进程。
    sti();
}

//// 睡眠等待超级解锁
// 如果超级块已被锁定，则将当前任务置为不可中断的等待状态，并添加到该超级块的等待
// 队列s_wait中。知道该超级块解锁并明确的唤醒本地任务.
static void wait_on_super(struct super_block * sb)
{
    cli();
    while (sb-&gt;s_lock)
        sleep_on(&amp;(sb-&gt;s_wait));
    sti();
}1</code></pre></div><p>&#160; &#160;（3）设备是被super_block总管的，所以设备肯定要和super_block映射的，这里通过设备号在super_block数组挨个查找映射好的super_block!</p><div class="codebox"><pre><code>//// 取指定设备的超级块
// 在超级块表（数组）中搜索指定设备dev的超级块结构信息。若找到刚返回超级块的指针，
// 否则返回空指针
struct super_block * get_super(int dev)
{
    struct super_block * s;

    // 首先判断参数给出设备的有效性。若设备号为0则返回NULL，然后让s指向超级块数组
    // 起始处，开始搜索整个超级块数组，以寻找指定设备dev的超级块。
    if (!dev)
        return NULL;
    s = 0+super_block;
    while (s &lt; NR_SUPER+super_block)
        // 如果当前搜索项是指定设备的超级块，即该超级块的设备号字段值与函数参数指定的
        // 相同，则先等待该超级块解锁。在等待期间，该超级块项有可能被其他设备使用，因此
        // 等待返回之后需要再判断一次是否是指定设备的超级块，如果是则返回该超级块的指针。
        // 否则就重新对超级块数组再搜索一遍，因此此时s需重又指向超级块数组开始处。
        if (s-&gt;s_dev == dev) {
            wait_on_super(s);
            if (s-&gt;s_dev == dev)
                return s;
            s = 0+super_block;
        // 如果当前搜索项不是，则检查下一项，如果没有找到指定的超级块，则返回空指针。
        } else
            s++;
    return NULL;
}</code></pre></div><p>&#160; &#160; &#160; super_block用完后可以释放，这里把super_block结构体的dev清零，表示不和任何设备映射（或则说部管理任何设备）！同时释放位图块和逻辑块，最后释放该块上的锁，然后唤醒等待的进程；</p><div class="codebox"><pre class="vscroll"><code>//// 释放指定设备的超级块
// 释放设备所使用的超级块数组项，并释放该设备i节点的位图和逻辑块位图所占用的高速缓冲块。
// 如果超级块对应的文件系统是根文件系统，或者其某个i节点上已经安装有其他的文件系统，
// 则不能释放该超级块。
void put_super(int dev)
{
    struct super_block * sb;
    /* struct m_inode * inode;*/
    int i;

    // 首先判断参数的有效性和合法性。如果指定设备是根文件系统设备，则显示警告信息“根
    // 系统盘改变了，准备生死决战吧”，并返回。然后在超级块表现中寻找指定设备号的文件系统
    // 超级块。如果找不到指定设备的超级块，则返回。另外，如果该超级块指明该文件系统所安装
    // 到的i节点还没有被处理过，则显示警告信息并返回。在文件系统卸载操作中，s_imount会先被
    // 置成NULL以后才会调用本函数。
    if (dev == ROOT_DEV) {
        printk(&quot;root diskette changed: prepare for armageddon\n\r&quot;);
        return;
    }
    if (!(sb = get_super(dev)))
        return;
    if (sb-&gt;s_imount) {
        printk(&quot;Mounted disk changed - tssk, tssk\n\r&quot;);
        return;
    }
    // 然后在找到指定设备的超级块之后，我们先锁定该超级块，再置该超级块对应的设备号字段
    // s_dev为0，也即释放该设备上的文件系统超级块。然后释放该超级块占用的其他内核资源，
    // 即释放该设备上文件系统i节点位图和逻辑块位图在缓冲区中所占用的缓冲块。下面常数符号
    // I_MAP_SLOTS和Z_MAP_LOTS均等于8，用于分别指明i节点位图和逻辑块位图占用的磁盘逻辑块
    // 数。注意，若这些缓冲块内荣被修改过，则需要作同步操作才能把缓冲块中的数据写入设备
    // 中,函数最后对该超级块解锁，并返回。
    lock_super(sb);
    sb-&gt;s_dev = 0;
    for(i=0;i&lt;I_MAP_SLOTS;i++)
        brelse(sb-&gt;s_imap[i]);
    for(i=0;i&lt;Z_MAP_SLOTS;i++)
        brelse(sb-&gt;s_zmap[i]);
    free_super(sb);
    return;
}</code></pre></div><p>&#160; 读取指定设备上的super_block:</p><p>&#160; 传入设备号，通过设备号找到内存的super_block结构体，并初始化部分属性<br />&#160; &#160; &#160; &#160;调用bread读取指定设备的第1号block（0号block是磁盘的引导块），也就是超级块；这里不理解的小伙伴建议看看上面的图：block在磁盘内部也是顺着编号的<br />&#160; &#160; &#160; &#160;从设备读出来的super_block结构体目前还防缓存区，这里需要复制到从super_block数组中找到的super_block中<br />&#160; &#160; &#160; &#160;继续调用bread读inode位图块和数据块位图，然后存放在super_block的s_imap、s_zmap数组中<br />&#160; &#160; &#160; &#160;解锁super_block结构体，唤醒其他进程使用</p><div class="codebox"><pre class="vscroll"><code>//// 读取指定设备的超级块
// 如果指定设备dev上的文件系统超级块已经在超级块表中，则直接返回该超级块项的指针。否则
// 就从设备dev上读取超级块到缓冲块中，并复制到超级块中。并返回超级块指针。
static struct super_block * read_super(int dev)
{
    struct super_block * s;
    struct buffer_head * bh;
    int i,block;

    // 首先判断参数的有效性。如果没有指明设备，则返回空指针。然后检查该设备是否可更换过
    // 盘片（也即是否软盘设备）。如果更换盘片，则高速缓冲区有关设备的所有缓冲块均失效，
    // 需要进行失效处理，即释放原来加载的文件系统。
    if (!dev)
        return NULL;
    check_disk_change(dev);
    // 如果该设备的超级块已经在超级块表中，则直接返回该超级块的指针。否则，首先在超级块
    // 数组中找出一个空项（也即字段s_dev=0的项）。如果数组已经占满则返回空指针。
    if ((s = get_super(dev)))
        return s;
    for (s = 0+super_block ;; s++) {
        if (s &gt;= NR_SUPER+super_block)
            return NULL;
        if (!s-&gt;s_dev)
            break;
    }
    // 在超级块数组中找到空项之后，就将该超级块项用于指定设备dev上的文件系统。于是对该
    // 超级块结构中的内存字段进行部分初始化处理。这些配置项只在内存出现
    s-&gt;s_dev = dev;
    s-&gt;s_isup = NULL;
    s-&gt;s_imount = NULL;
    s-&gt;s_time = 0;
    s-&gt;s_rd_only = 0;
    s-&gt;s_dirt = 0;
    // 然后锁定该超级块，并从设备上读取超级块信息到bh指向的缓冲块中。超级块位于设备的第
    // 2个逻辑块（1号块）中，（第1个是引导盘块）。如果读超级块操作失败，则释放上面选定
    // 的超级块数组中的项（即置s_dev=0），并解锁该项，返回空指针退出。否则就将设备上读取
    // 的超级块信息从缓冲块数据区复制到超级块数组相应项结构中。并释放存放读取信息的高速
    // 缓冲块。
    lock_super(s);
    if (!(bh = bread(dev,1))) {
        s-&gt;s_dev=0;
        free_super(s);
        return NULL;
    }
    *((struct d_super_block *) s) =
        *((struct d_super_block *) bh-&gt;b_data);
    brelse(bh);
    // 现在我们从设备dev上得到了文件系统的超级块，于是开始检查这个超级块的有效性并从设备
    // 上读取i节点位图和逻辑块位图等信息。如果所读取的超级块的文件系统魔数字段不对，说明
    // 设备上不是正确的文件系统，因此同上面一样，释放上面选定的超级块数组中的项，并解锁该
    // 项，返回空指针退出。对于该版Linux内核，只支持MINIX文件系统1.0版本，其魔数是0x1371。
    if (s-&gt;s_magic != SUPER_MAGIC) {
        s-&gt;s_dev = 0;
        free_super(s);
        return NULL;
    }
    // 下面开始读取设备上i节点的位图和逻辑块位图数据。首先初始化内存超级块结构中位图空间。
    // 然后从设备上读取i节点位图和逻辑块位图信息，并存放在超级块对应字段中。i节点位图保存
    // 在设备上2号块开始的逻辑块中，共占用s_imap_blocks个块，逻辑块位图在i节点位图所在块
    // 的后续块中，共占用s_zmap_blocks个块。
    for (i=0;i&lt;I_MAP_SLOTS;i++)
        s-&gt;s_imap[i] = NULL;
    for (i=0;i&lt;Z_MAP_SLOTS;i++)
        s-&gt;s_zmap[i] = NULL;
    block=2;
    for (i=0 ; i &lt; s-&gt;s_imap_blocks ; i++)
        if ((s-&gt;s_imap[i]=bread(dev,block)))
            block++;/*block块号后移*/
        else
            break;
    for (i=0 ; i &lt; s-&gt;s_zmap_blocks ; i++)
        if ((s-&gt;s_zmap[i]=bread(dev,block)))
            block++;/*block块号后移*/
        else
            break;
    // 如果读出的位图块数不等于位图应该占有的逻辑块数，说明文件系统位图信息有问题，
    // 因此只能释放前面申请并占用的所有资源，即释放i节点位图和逻辑块位图占用
    // 的高速缓冲块、释放上面选定的超级块数组项、解锁该超级块项，并返回空指针退出。
    if (block != 2+s-&gt;s_imap_blocks+s-&gt;s_zmap_blocks) {
        for(i=0;i&lt;I_MAP_SLOTS;i++)
            brelse(s-&gt;s_imap[i]);
        for(i=0;i&lt;Z_MAP_SLOTS;i++)
            brelse(s-&gt;s_zmap[i]);
        s-&gt;s_dev=0;
        free_super(s);
        return NULL;
    }
    // 否则一切成功，另外，由于对申请空闲i节点的函数来讲，如果设备上所有的i节点已经全被使用
    // 则查找函数会返回0值。因此0号i节点是不能用的，所以这里将位图中第1块的最低bit位设置为1，
    // 以防止文件系统分配0号i节点。同样的道理，也将逻辑块位图的最低位设置为1.最后函数解锁该
    // 超级块，并放回超级块指针。
    s-&gt;s_imap[0]-&gt;b_data[0] |= 1;
    s-&gt;s_zmap[0]-&gt;b_data[0] |= 1;
    free_super(s);
    return s;
}</code></pre></div><p>&#160; 3、本文最重要的两个函数：sys_umount和sys_mount；从函数名就能看出来，这两个都是系统调用！linux的mount和unmount命令底层调用就是这两个函数。先看sys_unmount：</p><p>传入的参数是设备名称，比如这里要卸载u盘，传入u盘名称，根据名称找到inode节点<br />根据inode节点找到设备号<br />如果这个inode节点有进程正在使用（比如正在从u盘复制数据），返回BUSY<br />一切就绪后重置super_block的关键字段，同时释放super_block，同步缓存区和设备的数据；<br />&#160; 从代码看，umount做了很多判断，也释放和同步了数据，所以为什么U盘用完后建议先umount（windows下是卸载），完毕后再拔U盘，而不是复制完后直接简单粗暴地拔U盘！</p><div class="codebox"><pre class="vscroll"><code>//// 卸载文件系统（系统调用）
// 参数dev_name是文件系统所在设备的设备文件名
// 该函数首先根据参数给出的设备文件名获得设备号，然后复位文件系统超级块中的相应字段，释放超级
// 块和位图占用的缓冲块，最后对该设备执行高速缓冲与设备上数据的同步操作。若卸载操作成功则返回
// 0，否则返回出错码。
int sys_umount(char * dev_name)
{
    struct m_inode * inode;
    struct super_block * sb;
    int dev;

    // 首先根据设备文件名找到对应的i节点，并取其中的设备号。设备文件所定义设备的设备号是保存在其
    // i节点的i_zone[0]中的，参见sys_mknod()的代码。另外，由于文件系统需要存放在设备上，因此如果
    // 不是设备文件，则返回刚申请的i节点dev_i,返回出错码。
    if (!(inode=namei(dev_name)))
        return -ENOENT;
    dev = inode-&gt;i_zone[0];
    if (!S_ISBLK(inode-&gt;i_mode)) {
        iput(inode);
        return -ENOTBLK;
    }
    // OK,现在上面为了得到设备号而取得的i节点已完成了它的使命，因此这里放回该设备文件的i节点。接着
    // 我们来检查一下卸载该文件系统的条件是否满足。如果设备上是根文件系统，则不能被卸载，返回。
    iput(inode);
    if (dev==ROOT_DEV)
        return -EBUSY;
    // 如果在超级块表中没有找到该设备上文件系统的超级块，或者已找到但是该设备上文件系统
    // 没有安装过，则返回出错码。如果超级块所指明的被安装到的i节点并没有置位其安装标志
    // i_mount，则显示警告信息。然后查找一下i节点表，看看是否有进程在使用该设备上的文件（if (inode-&gt;i_dev==dev &amp;&amp; inode-&gt;i_count)），
    // 如果有则返回出错码；比如挂载的U盘正在复制数据，这时的设备是被某个进程占用的，此刻umount肯定时不行的
    if (!(sb=get_super(dev)) || !(sb-&gt;s_imount))
        return -ENOENT;
    if (!sb-&gt;s_imount-&gt;i_mount)
        printk(&quot;Mounted inode has i_mount=0\n&quot;);
    for (inode=inode_table+0 ; inode&lt;inode_table+NR_INODE ; inode++)
        if (inode-&gt;i_dev==dev &amp;&amp; inode-&gt;i_count)
                return -EBUSY;
    // 现在该设备上文件系统的卸载条件均得到满足，因此我们可以开始实施真正的卸载操作了。
    // 首先复位被安装到的i节点的安装标志，释放该i节点。然后置超级块中被安装i节点字段为
    // 空，并放回设备文件系统的根i节点。接着置超级块中被安装系统根i节点指针为空。
    sb-&gt;s_imount-&gt;i_mount=0;
    iput(sb-&gt;s_imount);
    sb-&gt;s_imount = NULL;
    iput(sb-&gt;s_isup);
    sb-&gt;s_isup = NULL;
    // 最后我们释放该设备上的超级块以及位图占用的高速缓冲块，并对该设备执行高速缓冲与
    // 设备上数据的同步操作，然后返回0，表示卸载成功。
    put_super(dev);
    sync_dev(dev);
    return 0;
}</code></pre></div><p>&#160; sys_mount的操作和sys_umount基本是相反的：由于需要把新设备挂载到某个目录下，所以这里涉及到目录的操作了，但是不复杂，目录的结构体也是inode！</p><div class="codebox"><pre class="vscroll"><code>//// 安装文件系统（系统调用）
// 参数dev_name是设备文件名，dir_name是安装到的目录名，rw_flag被安装文件系统的可
// 读写标志。将被加载的地方必须是一个目录名，并并且对应的i节点没有被其他程序占用。
// 若操作成功则返回0，否则返回出错号。
int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
    struct m_inode * dev_i, * dir_i;
    struct super_block * sb;
    int dev;

    // 首先根据设备文件名找到对应的i节点，以取得其中的设备号。对于块特殊设备文件，
    // 设备号在其i节点的i_zone[0]中。另外，由于文件凶必须在块设备中，因此如果不是
    // 块设备文件，则放回刚取得的i节点dev_i，返回出错码。
    if (!(dev_i=namei(dev_name)))
        return -ENOENT;
    dev = dev_i-&gt;i_zone[0];
    if (!S_ISBLK(dev_i-&gt;i_mode)) {
        iput(dev_i);
        return -EPERM;
    }
    // OK,现在上面为了得到设备号而取得i节点dev_i已完成了它的使命，因此这里放回该
    // 设备文件的i节点。接着我们来检查一下文件系统安装到的目录名是否有效。于是根据
    // 给定的目录文件名找到对应的i节点dir_i。如果该i节点的引用计数不为1（仅在这里引用），
    // 或者该i节点的节点号是根文件系统的节点号1，则放回该i节点的返回出错码。另外，如果
    // 该节点不是一个目录文件节点，则也放回该i节点，返回出错码。因为文件系统只能安装
    // 在一个目录名上。
    iput(dev_i);
    if (!(dir_i=namei(dir_name)))
        return -ENOENT;
    if (dir_i-&gt;i_count != 1 || dir_i-&gt;i_num == ROOT_INO) {
        iput(dir_i);
        return -EBUSY;
    }
    if (!S_ISDIR(dir_i-&gt;i_mode)) {
        iput(dir_i);
        return -EPERM;
    }
    // 现在安装点也检查完毕，我们开始读取要安装文件系统的超级块信息。如果读取超级块操
    // 作失败，则返回该安装点i节点的dir_i并返回出错码。一个文件系统的超级块首先从超级
    // 块表中进行搜索，如果不在超级块表中就从设备上读取。
    if (!(sb=read_super(dev))) {
        iput(dir_i);
        return -EBUSY;
    }
    // 在得到了文件系统超级块之后，我们对它先进行检测一番。如果将要被安装的文件系统已经
    // 安装在其他地方，则放回该i节点，返回出错码。如果将要安装到的i节点已经安装了文件系
    // 统(安装标志已经置位)，则放回该i节点，也返回出错码。
    if (sb-&gt;s_imount) {
        iput(dir_i);
        return -EBUSY;
    }
    if (dir_i-&gt;i_mount) {
        iput(dir_i);
        return -EPERM;
    }
    // 最后设置被安装文件系统超级块的“被安装到i节点”字段指向安装到的目录名的i节点。并设置
    // 安装位置i节点的安装标志和节点已修改标志。然后返回（安装成功）。
    sb-&gt;s_imount=dir_i;
    dir_i-&gt;i_mount=1;
    dir_i-&gt;i_dirt=1;        /* NOTE! we don&#039;t iput(dir_i) */
    return 0;            /* we do that in umount */
}</code></pre></div><p>&#160; &#160;4、最后一个重要的函数：挂载根文件</p><p>既然是根文件，说明文件内部还未存放任何数据，先把file_table的引用计数清零<br />接着把super_block的dev、lock、wait属性清零<br />读取设备的super_block初始化，后续会用super_block结构体管理根节点和设备<br />设置super_block的inode位图和逻辑块位图</p><div class="codebox"><pre class="vscroll"><code>//// 安装根文件系统
// 该函数属于系统初始化操作的一部分。函数首先初始化文件表数组file_table[]和超级块表（数组）
// 然后读取根文件系统超级块，并取得文件系统根i节点。最后统计并显示出根文件系统上的可用资源
// （空闲块数和空闲i节点数）。该函数会在系统开机进行初始化设置时被调用。
void mount_root(void)
{
    int i,free;
    struct super_block * p;
    struct m_inode * mi;

    // 若磁盘i节点结构不是32字节，则出错停机。该判断用于防止修改代码时出现不一致情况。
    if (32 != sizeof (struct d_inode))
        panic(&quot;bad i-node size&quot;);
    // 首先初始化文件表数组（共64项，即系统同时只能打开64个文件）和超级块表。这里将所有文件
    // 结构中的引用计数设置为0（表示空闲），并发超级块表中各项结构的设备字段初始化为0（也
    // 表示空闲）。如果根文件系统所在设备是软盘的话，就提示“插入根文件系统盘，并按回车键”，
    // 并等待按键。
    for(i=0;i&lt;NR_FILE;i++)
        file_table[i].f_count=0;                        // 初始化文件表，文件统一用这个数组表示
    if (MAJOR(ROOT_DEV) == 2) {
        printk(&quot;Insert root floppy and press ENTER&quot;);   // 提示插入根文件系统盘
        wait_for_keypress();
    }
    for(p = &amp;super_block[0] ; p &lt; &amp;super_block[NR_SUPER] ; p++) {
        p-&gt;s_dev = 0;
        p-&gt;s_lock = 0;
        p-&gt;s_wait = NULL;
    }
    // 做好以上“份外”的初始化工作之后，我们开始安装根文件系统。于是从根设备上读取文件系统
    // 超级块，并取得文件系统的根i节点（1号节点）在内存i节点表中的指针。如果读根设备上超级
    // 块是吧或取根节点失败，则都显示信息并停机。
    if (!(p=read_super(ROOT_DEV)))
        panic(&quot;Unable to mount root&quot;);
    if (!(mi=iget(ROOT_DEV,ROOT_INO)))
        panic(&quot;Unable to read root i-node&quot;);
    // 现在我们对超级块和根i节点进行设置。把根i节点引用次数递增3次。因此后面也引用了该i节点。
    // 另外，iget()函数中i节点引用计数已被设置为1。然后置该超级块的被安装文件系统i节点和被
    // 安装到i节点。再设置当前进程的当前工作目录和根目录i节点。此时当前进程是1号进程（init进程）。
    mi-&gt;i_count += 3 ;    /* NOTE! it is logically used 4 times, not 1 */
    p-&gt;s_isup = p-&gt;s_imount = mi;
    current-&gt;pwd = mi;
    current-&gt;root = mi;
    // 然后我们对根文件系统的资源作统计工作。统计该设备上空闲块数和空闲i节点数。首先令i等于
    // 超级块中表明的设备逻辑块总数。然后根据逻辑块相应bit位的占用情况统计出空闲块数。这里
    // 宏函数set_bit()只是在测试bit位，而非设置bit位。“i&amp;8191”用于取得i节点号在当前位图块中对应
    // 的bit位偏移值。&quot;i&gt;&gt;13&quot;是将i除以8192，也即除一个磁盘块包含的bit位数。
    free=0;
    i=p-&gt;s_nzones;
    while (-- i &gt;= 0)
        if (!set_bit(i&amp;8191,p-&gt;s_zmap[i&gt;&gt;13]-&gt;b_data))/*逻辑块位图*/
            free++;
    // 在显示过设备上空闲逻辑块数/逻辑块总数之后。我们再统计设备上空闲i节点数。首先令i等于超级块
    // 中表明的设备上i节点总数+1.加1是将0节点也统计进去，然后根据i节点位图相应bit位的占用情况计算
    // 出空闲i节点数。最后再显示设备上可用空闲i节点数和i节点总数
    printk(&quot;%d/%d free blocks\n\r&quot;,free,p-&gt;s_nzones);
    free=0;
    i=p-&gt;s_ninodes+1;
    while (-- i &gt;= 0)
        if (!set_bit(i&amp;8191,p-&gt;s_imap[i&gt;&gt;13]-&gt;b_data))
            free++;
    printk(&quot;%d/%d free inodes\n\r&quot;,free,p-&gt;s_ninodes);
}</code></pre></div><p>&#160; &#160;5、前几天把办公的笔记本加装了一块SSD盘，一共232GB大小，未存储任何文件，结果windows光是建system volume就耗费1.2GB的内存，大概率是用在了类似super_block、inode类似的管理结构体<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202112/2052730-20211216100137429-1091102339.png" alt="FluxBB bbcode 测试" /></span> </p><p>参考:</p><p>1、https://www.disktool.cn/content-center/resize-partition/how-to-change-partition-size-windows-10.html&#160; 如果在windows扩展或缩减分区大小</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sat, 08 Oct 2022 04:33:22 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=452#p452</guid>
		</item>
	</channel>
</rss>
