對於普通用戶而言,日常用的都是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擴展或縮減分區大小