linux源碼解讀(四):文件系統——掛載和卸載


   對於普通用戶而言,日常用的都是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文件里,接下來我們一一解析!

  1、這里再說明一下各種概念之間的關系,如下圖:

  •   linux操作系統內核或app不會直接讀寫磁盤,都是直接讀寫高速緩存區的(通過memcpy拷貝內存數據)
  •        高速緩存區調用ll_rw_block函數讀寫磁盤
  •        super_block、inode、數據塊等結構體即存磁盤,也可以存內存,通過ll_rw_block函數在內存和磁盤之間同步
  •        buffer_head的block和磁盤中的block是有一定對應關系的

   

  這個圖看起來有點復雜,其實原理很簡單:

  •   內存和磁盤本質功能都一樣:都是用來存儲數據的,唯一的區別就是掉電后數據丟不丟
  •        既然存了數據,自然也要讀取了,就涉及到尋址;內存尋址很簡單:內存最小的存儲單位是byte,每個byte都有自己的地址編號,可以根據地址編號直接讀數據,比如 int *p=0x12345678,意味着從地址0x12345678處開始依次讀取4byte的數據,數據結尾地址就是0x12345678+4=0x1234567b;
  •        相比內存,磁盤讀數據稍微要“麻煩”一點:磁盤被人為划分成了塊,每塊的大小是512byte*8=4KB;為了方便讀取塊的數據,也需要給每個塊編號,這個編號和內存的地址編號本質都是一樣的;為了標記塊是否被使用,或者被哪些文件使用,誕生了inode結構體,具體使用了i_zone字段記錄!進一步:為了記錄哪些inode結構體被使用,又誕生了inode位圖(存放inode結構體的block是有限的,導致inode結構體的數量也受限,需要省着點用!)
  •        內存、磁盤這類塊存儲設備,使用結構體管理時,結構體的字段一般都有:是否鎖定lock、是否更新update、是否有數據dirty、被使用/引用次數count、當前被那個進程在使用task、后面有哪些進程在排隊等待wait.......

   2、(1)既然super_block是“根索引”,意味着抓住了super_block,就等於抓住了整個文件系統(類似進程的內存用頁目錄表、頁表項管理和索引類似,抓住了CR3,就得到了進程的所有內存)。為了管理所有的super_block,Linux早期的0.11版本采用了最原始的數組:

   // 超級塊結構表數組(NR_SUPER = 8)
   struct super_block super_block[NR_SUPER];

  這里要吐槽了:數組只有8個元素,意味着只能同時管理8個文件系統;不過后期的linux貌似改了,用鏈表串接了所有的super_block,只要內存夠,可以存好多的super_block!

      (2)超級塊本質上也是內存的一塊,所以和其他快一樣,都涉及到鎖、等待、釋放等操作,代碼如下:可以看到和inode、block等思路完全一樣,沒任何區別!

// 以下3個函數(lock_super()、free_super()和wait_on_super())的作用與inode.c文件中頭
// 3個函數的作用雷同,只是這里操作的對象換成了超級塊。
//// 鎖定超級塊
// 如果超級塊已被鎖定,則將當前任務置為不可中斷的等待狀態,並添加到該超級塊等待隊列
// s_wait中。直到該超級塊解鎖並明確地喚醒本地任務。然后對其上鎖。
static void lock_super(struct super_block * sb)
{
    cli();                          // 關中斷
    while (sb->s_lock)              // 如果該超級塊已經上鎖,則睡眠等待。
        sleep_on(&(sb->s_wait));
    sb->s_lock = 1;                 // 會給超級塊加鎖(置鎖定標志)
    sti();                          // 開中斷
}

//// 對指定超級塊解鎖
// 復位超級塊的鎖定標志,並明確地喚醒等待在此超級塊等待隊列s_wait上的所有進程。
// 如果使用ulock_super這個名稱則可能更妥貼。
static void free_super(struct super_block * sb)
{
    cli();
    sb->s_lock = 0;             // 復位鎖定標志
    wake_up(&(sb->s_wait));     // 喚醒等待該超級塊的進程。
    sti();
}

//// 睡眠等待超級解鎖
// 如果超級塊已被鎖定,則將當前任務置為不可中斷的等待狀態,並添加到該超級塊的等待
// 隊列s_wait中。知道該超級塊解鎖並明確的喚醒本地任務.
static void wait_on_super(struct super_block * sb)
{
    cli();
    while (sb->s_lock)
        sleep_on(&(sb->s_wait));
    sti();
}1

   (3)設備是被super_block總管的,所以設備肯定要和super_block映射的,這里通過設備號在super_block數組挨個查找映射好的super_block!

//// 取指定設備的超級塊
// 在超級塊表(數組)中搜索指定設備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 < NR_SUPER+super_block)
        // 如果當前搜索項是指定設備的超級塊,即該超級塊的設備號字段值與函數參數指定的
        // 相同,則先等待該超級塊解鎖。在等待期間,該超級塊項有可能被其他設備使用,因此
        // 等待返回之后需要再判斷一次是否是指定設備的超級塊,如果是則返回該超級塊的指針。
        // 否則就重新對超級塊數組再搜索一遍,因此此時s需重又指向超級塊數組開始處。
        if (s->s_dev == dev) {
            wait_on_super(s);
            if (s->s_dev == dev)
                return s;
            s = 0+super_block;
        // 如果當前搜索項不是,則檢查下一項,如果沒有找到指定的超級塊,則返回空指針。
        } else
            s++;
    return NULL;
}

      super_block用完后可以釋放,這里把super_block結構體的dev清零,表示不和任何設備映射(或則說部管理任何設備)!同時釋放位圖塊和邏輯塊,最后釋放該塊上的鎖,然后喚醒等待的進程;

//// 釋放指定設備的超級塊
// 釋放設備所使用的超級塊數組項,並釋放該設備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("root diskette changed: prepare for armageddon\n\r");
        return;
    }
    if (!(sb = get_super(dev)))
        return;
    if (sb->s_imount) {
        printk("Mounted disk changed - tssk, tssk\n\r");
        return;
    }
    // 然后在找到指定設備的超級塊之后,我們先鎖定該超級塊,再置該超級塊對應的設備號字段
    // s_dev為0,也即釋放該設備上的文件系統超級塊。然后釋放該超級塊占用的其他內核資源,
    // 即釋放該設備上文件系統i節點位圖和邏輯塊位圖在緩沖區中所占用的緩沖塊。下面常數符號
    // I_MAP_SLOTS和Z_MAP_LOTS均等於8,用於分別指明i節點位圖和邏輯塊位圖占用的磁盤邏輯塊
    // 數。注意,若這些緩沖塊內榮被修改過,則需要作同步操作才能把緩沖塊中的數據寫入設備
    // 中,函數最后對該超級塊解鎖,並返回。
    lock_super(sb);
    sb->s_dev = 0;
    for(i=0;i<I_MAP_SLOTS;i++)
        brelse(sb->s_imap[i]);
    for(i=0;i<Z_MAP_SLOTS;i++)
        brelse(sb->s_zmap[i]);
    free_super(sb);
    return;
}

  讀取指定設備上的super_block:

  •   傳入設備號,通過設備號找到內存的super_block結構體,並初始化部分屬性
  •        調用bread讀取指定設備的第1號block(0號block是磁盤的引導塊),也就是超級塊;這里不理解的小伙伴建議看看上面的圖:block在磁盤內部也是順着編號的
  •        從設備讀出來的super_block結構體目前還防緩存區,這里需要復制到從super_block數組中找到的super_block中
  •        繼續調用bread讀inode位圖塊和數據塊位圖,然后存放在super_block的s_imap、s_zmap數組中
  •        解鎖super_block結構體,喚醒其他進程使用
//// 讀取指定設備的超級塊
// 如果指定設備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 >= NR_SUPER+super_block)
            return NULL;
        if (!s->s_dev)
            break;
    }
    // 在超級塊數組中找到空項之后,就將該超級塊項用於指定設備dev上的文件系統。於是對該
    // 超級塊結構中的內存字段進行部分初始化處理。這些配置項只在內存出現
    s->s_dev = dev;
    s->s_isup = NULL;
    s->s_imount = NULL;
    s->s_time = 0;
    s->s_rd_only = 0;
    s->s_dirt = 0;
    // 然后鎖定該超級塊,並從設備上讀取超級塊信息到bh指向的緩沖塊中。超級塊位於設備的第
    // 2個邏輯塊(1號塊)中,(第1個是引導盤塊)。如果讀超級塊操作失敗,則釋放上面選定
    // 的超級塊數組中的項(即置s_dev=0),並解鎖該項,返回空指針退出。否則就將設備上讀取
    // 的超級塊信息從緩沖塊數據區復制到超級塊數組相應項結構中。並釋放存放讀取信息的高速
    // 緩沖塊。
    lock_super(s);
    if (!(bh = bread(dev,1))) {
        s->s_dev=0;
        free_super(s);
        return NULL;
    }
    *((struct d_super_block *) s) =
        *((struct d_super_block *) bh->b_data);
    brelse(bh);
    // 現在我們從設備dev上得到了文件系統的超級塊,於是開始檢查這個超級塊的有效性並從設備
    // 上讀取i節點位圖和邏輯塊位圖等信息。如果所讀取的超級塊的文件系統魔數字段不對,說明
    // 設備上不是正確的文件系統,因此同上面一樣,釋放上面選定的超級塊數組中的項,並解鎖該
    // 項,返回空指針退出。對於該版Linux內核,只支持MINIX文件系統1.0版本,其魔數是0x1371。
    if (s->s_magic != SUPER_MAGIC) {
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    // 下面開始讀取設備上i節點的位圖和邏輯塊位圖數據。首先初始化內存超級塊結構中位圖空間。
    // 然后從設備上讀取i節點位圖和邏輯塊位圖信息,並存放在超級塊對應字段中。i節點位圖保存
    // 在設備上2號塊開始的邏輯塊中,共占用s_imap_blocks個塊,邏輯塊位圖在i節點位圖所在塊
    // 的后續塊中,共占用s_zmap_blocks個塊。
    for (i=0;i<I_MAP_SLOTS;i++)
        s->s_imap[i] = NULL;
    for (i=0;i<Z_MAP_SLOTS;i++)
        s->s_zmap[i] = NULL;
    block=2;
    for (i=0 ; i < s->s_imap_blocks ; i++)
        if ((s->s_imap[i]=bread(dev,block)))
            block++;/*block塊號后移*/
        else
            break;
    for (i=0 ; i < s->s_zmap_blocks ; i++)
        if ((s->s_zmap[i]=bread(dev,block)))
            block++;/*block塊號后移*/
        else
            break;
    // 如果讀出的位圖塊數不等於位圖應該占有的邏輯塊數,說明文件系統位圖信息有問題,
    // 因此只能釋放前面申請並占用的所有資源,即釋放i節點位圖和邏輯塊位圖占用
    // 的高速緩沖塊、釋放上面選定的超級塊數組項、解鎖該超級塊項,並返回空指針退出。
    if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {
        for(i=0;i<I_MAP_SLOTS;i++)
            brelse(s->s_imap[i]);
        for(i=0;i<Z_MAP_SLOTS;i++)
            brelse(s->s_zmap[i]);
        s->s_dev=0;
        free_super(s);
        return NULL;
    }
    // 否則一切成功,另外,由於對申請空閑i節點的函數來講,如果設備上所有的i節點已經全被使用
    // 則查找函數會返回0值。因此0號i節點是不能用的,所以這里將位圖中第1塊的最低bit位設置為1,
    // 以防止文件系統分配0號i節點。同樣的道理,也將邏輯塊位圖的最低位設置為1.最后函數解鎖該
    // 超級塊,並放回超級塊指針。
    s->s_imap[0]->b_data[0] |= 1;
    s->s_zmap[0]->b_data[0] |= 1;
    free_super(s);
    return s;
}

  3、本文最重要的兩個函數:sys_umount和sys_mount;從函數名就能看出來,這兩個都是系統調用!linux的mount和unmount命令底層調用就是這兩個函數。先看sys_unmount:

  • 傳入的參數是設備名稱,比如這里要卸載u盤,傳入u盤名稱,根據名稱找到inode節點
  • 根據inode節點找到設備號
  • 如果這個inode節點有進程正在使用(比如正在從u盤復制數據),返回BUSY
  • 一切就緒后重置super_block的關鍵字段,同時釋放super_block,同步緩存區和設備的數據

  從代碼看,umount做了很多判斷,也釋放和同步了數據,所以為什么U盤用完后建議先umount(windows下是卸載),完畢后再拔U盤,而不是復制完后直接簡單粗暴地拔U盤

//// 卸載文件系統(系統調用)
// 參數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->i_zone[0];
    if (!S_ISBLK(inode->i_mode)) {
        iput(inode);
        return -ENOTBLK;
    }
    // OK,現在上面為了得到設備號而取得的i節點已完成了它的使命,因此這里放回該設備文件的i節點。接着
    // 我們來檢查一下卸載該文件系統的條件是否滿足。如果設備上是根文件系統,則不能被卸載,返回。
    iput(inode);
    if (dev==ROOT_DEV)
        return -EBUSY;
    // 如果在超級塊表中沒有找到該設備上文件系統的超級塊,或者已找到但是該設備上文件系統
    // 沒有安裝過,則返回出錯碼。如果超級塊所指明的被安裝到的i節點並沒有置位其安裝標志
    // i_mount,則顯示警告信息。然后查找一下i節點表,看看是否有進程在使用該設備上的文件(if (inode->i_dev==dev && inode->i_count)),
    // 如果有則返回出錯碼;比如掛載的U盤正在復制數據,這時的設備是被某個進程占用的,此刻umount肯定時不行的
    if (!(sb=get_super(dev)) || !(sb->s_imount))
        return -ENOENT;
    if (!sb->s_imount->i_mount)
        printk("Mounted inode has i_mount=0\n");
    for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++)
        if (inode->i_dev==dev && inode->i_count)
                return -EBUSY;
    // 現在該設備上文件系統的卸載條件均得到滿足,因此我們可以開始實施真正的卸載操作了。
    // 首先復位被安裝到的i節點的安裝標志,釋放該i節點。然后置超級塊中被安裝i節點字段為
    // 空,並放回設備文件系統的根i節點。接着置超級塊中被安裝系統根i節點指針為空。
    sb->s_imount->i_mount=0;
    iput(sb->s_imount);
    sb->s_imount = NULL;
    iput(sb->s_isup);
    sb->s_isup = NULL;
    // 最后我們釋放該設備上的超級塊以及位圖占用的高速緩沖塊,並對該設備執行高速緩沖與
    // 設備上數據的同步操作,然后返回0,表示卸載成功。
    put_super(dev);
    sync_dev(dev);
    return 0;
}

  sys_mount的操作和sys_umount基本是相反的:由於需要把新設備掛載到某個目錄下,所以這里涉及到目錄的操作了,但是不復雜,目錄的結構體也是inode!

//// 安裝文件系統(系統調用)
// 參數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->i_zone[0];
    if (!S_ISBLK(dev_i->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->i_count != 1 || dir_i->i_num == ROOT_INO) {
        iput(dir_i);
        return -EBUSY;
    }
    if (!S_ISDIR(dir_i->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->s_imount) {
        iput(dir_i);
        return -EBUSY;
    }
    if (dir_i->i_mount) {
        iput(dir_i);
        return -EPERM;
    }
    // 最后設置被安裝文件系統超級塊的“被安裝到i節點”字段指向安裝到的目錄名的i節點。並設置
    // 安裝位置i節點的安裝標志和節點已修改標志。然后返回(安裝成功)。
    sb->s_imount=dir_i;
    dir_i->i_mount=1;
    dir_i->i_dirt=1;        /* NOTE! we don't iput(dir_i) */
    return 0;            /* we do that in umount */
}

   4、最后一個重要的函數:掛載根文件

  • 既然是根文件,說明文件內部還未存放任何數據,先把file_table的引用計數清零
  • 接着把super_block的dev、lock、wait屬性清零
  • 讀取設備的super_block初始化,后續會用super_block結構體管理根節點和設備
  • 設置super_block的inode位圖和邏輯塊位圖
//// 安裝根文件系統
// 該函數屬於系統初始化操作的一部分。函數首先初始化文件表數組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("bad i-node size");
    // 首先初始化文件表數組(共64項,即系統同時只能打開64個文件)和超級塊表。這里將所有文件
    // 結構中的引用計數設置為0(表示空閑),並發超級塊表中各項結構的設備字段初始化為0(也
    // 表示空閑)。如果根文件系統所在設備是軟盤的話,就提示“插入根文件系統盤,並按回車鍵”,
    // 並等待按鍵。
    for(i=0;i<NR_FILE;i++)
        file_table[i].f_count=0;                        // 初始化文件表,文件統一用這個數組表示
    if (MAJOR(ROOT_DEV) == 2) {
        printk("Insert root floppy and press ENTER");   // 提示插入根文件系統盤
        wait_for_keypress();
    }
    for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {
        p->s_dev = 0;
        p->s_lock = 0;
        p->s_wait = NULL;
    }
    // 做好以上“份外”的初始化工作之后,我們開始安裝根文件系統。於是從根設備上讀取文件系統
    // 超級塊,並取得文件系統的根i節點(1號節點)在內存i節點表中的指針。如果讀根設備上超級
    // 塊是吧或取根節點失敗,則都顯示信息並停機。
    if (!(p=read_super(ROOT_DEV)))
        panic("Unable to mount root");
    if (!(mi=iget(ROOT_DEV,ROOT_INO)))
        panic("Unable to read root i-node");
    // 現在我們對超級塊和根i節點進行設置。把根i節點引用次數遞增3次。因此后面也引用了該i節點。
    // 另外,iget()函數中i節點引用計數已被設置為1。然后置該超級塊的被安裝文件系統i節點和被
    // 安裝到i節點。再設置當前進程的當前工作目錄和根目錄i節點。此時當前進程是1號進程(init進程)。
    mi->i_count += 3 ;    /* NOTE! it is logically used 4 times, not 1 */
    p->s_isup = p->s_imount = mi;
    current->pwd = mi;
    current->root = mi;
    // 然后我們對根文件系統的資源作統計工作。統計該設備上空閑塊數和空閑i節點數。首先令i等於
    // 超級塊中表明的設備邏輯塊總數。然后根據邏輯塊相應bit位的占用情況統計出空閑塊數。這里
    // 宏函數set_bit()只是在測試bit位,而非設置bit位。“i&8191”用於取得i節點號在當前位圖塊中對應
    // 的bit位偏移值。"i>>13"是將i除以8192,也即除一個磁盤塊包含的bit位數。
    free=0;
    i=p->s_nzones;
    while (-- i >= 0)
        if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))/*邏輯塊位圖*/
            free++;
    // 在顯示過設備上空閑邏輯塊數/邏輯塊總數之后。我們再統計設備上空閑i節點數。首先令i等於超級塊
    // 中表明的設備上i節點總數+1.加1是將0節點也統計進去,然后根據i節點位圖相應bit位的占用情況計算
    // 出空閑i節點數。最后再顯示設備上可用空閑i節點數和i節點總數
    printk("%d/%d free blocks\n\r",free,p->s_nzones);
    free=0;
    i=p->s_ninodes+1;
    while (-- i >= 0)
        if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
            free++;
    printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}

   5、前幾天把辦公的筆記本加裝了一塊SSD盤,一共232GB大小,未存儲任何文件,結果windows光是建system volume就耗費1.2GB的內存,大概率是用在了類似super_block、inode類似的管理結構體

     

 

 

 

參考:

1、https://www.disktool.cn/content-center/resize-partition/how-to-change-partition-size-windows-10.html  如果在windows擴展或縮減分區大小


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM