1. 程式人生 > 其它 >2021-2022-1-diocs-EXT2檔案系統第六週學習筆記

2021-2022-1-diocs-EXT2檔案系統第六週學習筆記

20191205 2021-2022-1-diocs-EXT2檔案系統(第六週學習筆記)

一、任務詳情

自學教材第11章,提交學習筆記(10分)

知識點歸納以及自己最有收穫的內容 (3分)

問題與解決思路(2分)

實踐內容與截圖,程式碼連結(3分)

...(知識的結構化,知識的完整性等,提交markdown文件,使用openeuler系統等)(2分)

二、教材內容歸納整理

本章討論了 EXT2檔案系統。本章首先描述了EXT2檔案系統在Linux 中的歷史地位以及 EXT3/EXT4檔案系統的當前狀況;用程式設計示例展示了各種EXT2資料結構以及如何遍歷 EXT2檔案系統樹;介紹瞭如何實現支援Linux 核心中所有檔案操作的

EXT2檔案系統;展示瞭如何通過虛擬磁碟的mount root來構建基本檔案系統;將檔案系統的實現劃分為3個級別,級別1擴充套件了基本檔案系統,以實現檔案系統樹,級別2實現了檔案內容的讀/寫操作,級別3實現了檔案系統的掛載/裝載和檔案保護;描述了各個級別檔案系統函式的演算法,並通過程式設計示例演示了它們的實現過程;將所有級別融合到一個程式設計專案中;最後,將所有程式設計示例和練習整合到一個完全有效的檔案系統中。

思維導圖

一、知識點總結

1.通過mkfs建立虛擬磁碟

Linux下,命令

mke2fs [-b blksize -N ninodes] device nblocks

在裝置上建立一個帶有

nblocks個塊(每個塊大小為blksize位元組)和 ninodes 個索引節點的EXT2檔案系統。裝置可以是真實裝置,也可以是虛擬磁碟檔案。如果未指定blksize.則預設塊大小為1KB。如果未指定ninoidesmke2fs將根據 nblocks計算一個預設的ninodes數。得到的 EXT2檔案系統可在 Linux 中使用。舉個具體的例子,下面的命令

dd if=/dev/zero of=vdisk bs=1024 count=1440

mke2fs vdisk 1440

可在一個名為vdisk的虛擬磁碟檔案上建立一個EXT2檔案系統,有1440個大小為1KB 的塊。

2.超級塊

Block#1

超級塊(在硬碟分割槽中位元組偏移量為1024B1是超級塊,用於容納整個檔案系統的資訊。

struct ext2_guper_block {

u32 s_inodes_count;           /* Inodes count */

u32 s_blocks_count;           /* Blocks count*/

u32 s_r_blocks_count;        /* Reserved blocks count */

u32 s_free_blocks_count;       / * Free blocks count */

u32 s_free_inodes_count;     /* Free inodes count */

u32 s_first__data_blook;       /* First Data Block */

u32 s_log_block_size;            /* Block size */

u32 s_log_cluster_size;           /* Allocation cluster size */

u32 s_blocks_per_group;          /* # Blocks per group*/

u32 s_clusters_per_group;             /*# Fragments per group */

u32 s_inodes_per_group;           /* # Inodes per group*/

u32 s_mtime;u32 s_wtime;        /* Mount time */

u16 s_mnt_count;             /* Write time */

s16 s_max_mnt_count;          /*_Mount_count*/L

u16 8_magic;           /* Magic signature */

// more non-essential fields

u16 s_inode_size;          /* size of inode structure*/

}

3.塊組描述符

Block#2∶塊組描述符塊(硬碟上的s first data block+1EXT2將磁碟塊分成幾個組。每個組有8192個塊(硬碟上的大小為32K)。每組用一個塊組描述符結構體來描述。

struct ext2_group_desc {

u32 bg_block_bitmap;           // Bmap block number

u32 bg_inode_bitmap;          // Imap block number

u32 bg_inode_table;        // Inodes begin block number

u16 bg_free_blocks_count;      // THESE are OBVIOUS

u16 bg_free_inodes_count;     

ul6 bg_used_dirs_count;

u16 bg_pad;                // ignore these

u32 bg_reserved[3];

};

4.塊和索引節點點陣圖

Block#8∶塊點陣圖(Bmapbg block bitmap)點陣圖是用來表示某種項的位序列,例如磁碟塊或索引節點。點陣圖用於分配和回收項。在點陣圖中,0位表示對應項處於FREE狀態,1位表示對應項處於IN USE狀態。一個軟盤有1440個塊,但是 Block#0未被檔案系統使用。所以,點陣圖只有1439個有效位。無效位被視作INUSE,設定為1

Block#9∶索引節點點陣圖Imap)(bg inode bitmap)一個索引節點就是用來代表一個檔案的資料結構。EXT2檔案系統是使用有限數量的索引節點建立的。各索引節點的狀態用B9Imap 中的一個位表示。在EXT2 FS中,前10個索引節點是預留的。所以,空 EXT2 FSImap 101開頭,然後是0。無效位再次設定為1

5.索引節點

Block#10∶索引(開始)節點bg inode table)每個檔案都用一個128位元組(EXT4中是256位元組)的唯一索引節點結構體表示。

struct ext2_inode {

u16 i_mode;       // 16 bits=|tttt |ugs|rwx|rwx|rwxl

ul6 i_uid;         // owner uid

u32 i_size;        // file size in bytes

u32 i_atime;      // time fields in seconds

u32 1_ctime;     // since 00:00:00,1-1-1970

u32 i_mtime;     

u32 i_dtime;      

i_gid;     // group ID u16

u16 i_links_count;     // hard-link count

u32 i_blocks;u32 i_flags;     // number of 512-byte sectors

u32 i_reservedl;     // IGNORE // IGNORE

u32 i_block[15];       // See details below

u32 i_pad[7];           // for inode size = 128 bytes

}

直接塊:block[0]iblock[11],指向直接磁碟塊。

間接塊:iblock[12]指向一個包含256個塊編號(對於1KB BLKSIZE)的磁碟塊,每個塊編號指向一個磁碟塊。

雙重間接塊:i block[13]指向一個指向 256個塊的塊,每個塊指向256個磁碟塊。

三重間接塊:iblock[14]是三重間接塊。對於"小型"EXT2檔案系統,可以忽略它。

6.目錄條目

目錄包含dir_entry 結構,即

struct ext2_dir_entry_2{

u32 inode;         // inode number; count from 1,NOT 0

u16 rec_len;       // this entry's length in bytes

u8 name_len;      // name length in bytes

u8 file_type;       // not used

char name[EXT2_NAME_LEN];        // name:1-255 chars,no ending NULL ;

};

dir_entry 是一種可擴充結構。名稱欄位包含1255個字元,不含終止NULL。所以dir entry rec len也各不相同。

7.郵差演算法應用

1C語言中的Test-Set-Clear

注意,一些 C語言編譯器允許在結構體中指定位,如

struct bits{

unsigned int bit0      : 1;     //bit0 field is a single bit

unsigned int bit123    : 3;     // bit123 field is a range of 3 bits

unsigned int otherbits  :27;    // other bits field has 27 bits

unsigned int bit31     :1;     // bit31 is the highest bit

}var;

該結構體將 var.定義為一個32位無符號整數,具有單獨的位或位範圍。那麼,var.bit0=0;1賦值給第0位,則有var.bit123=5;101賦值給第1位到第3位等。但是,生成的程式碼仍然依賴於郵差演算法和位遮蔽來訪問各個位。我們可以用郵差演算法直接操作點陣圖中的位,無須定義複雜的C語言結構體。

2)將索引節點號轉換為磁碟上的索引節點

EXT2檔案系統中,每個檔案都有一個唯一的索引節點結構。在檔案系統磁碟上,索引節點從inode table塊開始。每個磁碟塊包含

INODES_PER_BLOCK=BLoCK_SIZE/sizeofINODE

個索引節點。每個索引節點都有一個唯一的索引節點號,ino=12,從1開始線性計數。已知一個ino,如1234,那麼哪個磁碟塊包含該索引節點,以及哪個索引節點在該塊中呢?我們需要知道磁碟塊號,因為需要通過塊來讀/寫一個真正的磁碟。

8.遍歷EXT2檔案系統樹

1.遍歷演算法

1)讀取超級塊。檢查幻數s magic0xEF53),驗證它確實是 EXT2 FS

2)讀取塊組描述符塊(1+s first data block),以訪問組0描述符。從塊組描述符的bg_ inode_table條目中找到索引節點的起始塊編號,並將其稱為InodesBeginBlock

3)讀取InodeBeginBlock,獲取/的索引節點,即INODE #2

4)將路徑名標記為元件字串,假設元件數量為 n。例如,如果路徑名=/a/b/c,則元件字串是"a""b""c",其中n=3。用name0】,name【【1】,…,namen-1來表示元件。

5)從(3)中的根索引節點開始,在其資料塊中搜索 name0】。為簡單起見,我們可以假設某個目錄中的條目數量很少,因此一個目錄索引節點只有12個直接資料塊。有了這

個假設,就可以在12個(非零)直接塊中搜索 name0】。目錄索引節點的每個資料塊都包含以下形式的 dir_entry結構體:

[ino rec_len name_len NAME] [ino rec_len name_len NAME] . . . . .

6)使用索引節點號ino 來定位相應的索引節點。回想前面的內容,ino 1開始計數。使用郵差演算法計算包含索引節點的磁碟塊及其在該塊中的偏移量。

9.檔案系統的結構

(1)是當前執行程序的PROC結構體;

(2)是檔案系統的根指標;

(3)是一個openTable條目;

(4)是記憶體索引節點;

(5)是已掛載的檔案系統表。

10.基本檔案系統

  1. type.h檔案

這類檔案包含EXT2檔案系統的資料結構型別,比如超塊、組描述符、索引節點和目錄條目結構。此外,它還包含開啟檔案表、掛載表、PROC結構體和檔案系統常數。

  1. global.c檔案

這類檔案包含檔案系統的全域性變數。全域性變數的例子有

MINODE minode [NMINODE];       // in memory INODEs

MTABLE mtable [NMTABLE];        // mount tables

OFT    oft [NOFT];              // Opened file instance

PROC   proc[NPROC]PROC ]       // PROC structures

PROC      *running;            // current executing

11.檔案系統專案的擴充套件

1)多個組組描述符的大小為32位元組。對於1KB大小的塊,一個塊可能包含1024/32=32組描述符。32個組的檔案系統大小可以擴充套件為32*8=256MB

24KB大小的塊對於4KB大小的塊和一個組,檔案系統大小應為4*8=32MB。對於一個組描述符塊,檔案系統可能有128個組,可將檔案系統大小擴充套件到128*32=4GB。對於2個組描述符塊,檔案系統大小為8GB等。大多數擴充套件都很簡單,適合用於程式設計專案。

3)管道檔案管道可實現為普通檔案,這些檔案遵循管道的讀/寫協議。此方案的優點是;它統一了管道和檔案索引節點,並允許可被不相關程序使用的命名管道。為支援快速讀/寫操作,管道內容應在記憶體中,比如在 RAMdisk中。必要時,讀者可將命名管道實現為FIFO檔案。

4I/O緩衝在程式設計專案中,每個磁碟塊都是直接讀寫的。這會產生過多的物理磁碟I/O操作。為提高效率,實際檔案系統通常使用一系列I/O緩衝區作為磁碟塊的快取記憶體。檔案系統的I/O緩衝將會在第12章中討論,但是可以把它合併到檔案系統專案中。

二、最有收穫的內容

程式設計專案:1級檔案系統的實現&&2級檔案系統的實現

1.1級函式包括

mkdir演算法、creat演算法、mkdir-creat演算法、rmdir演算法、link演算法、unlink演算法、symlink演算法、readlink演算法,其他1級函式(訪問、chmodchown、更改檔案的時間欄位等)

所有這些函式的操作方式均相同。

操作示例:

(1)chmod oct檔名:將檔名的許可權位更改為八進位制值;

(2)utime檔名:將檔案的訪問時間更改為當前時間。

2.2級函式包括

Open演算法、lseekclose演算法、讀/寫普通檔案、opendir-readdir

操作示例:

3.3級函式包括

掛載演算法、解除安裝演算法、交叉掛載點、檔案保護、檔案鎖定、實際uid和有效uid

操作示例:

三、問題與解決思路

通過學習本章知識,深刻地感知到了LinuxEXT2檔案系統的操作複雜,所以一直在思考一個問題:Linux系統是如何讀取一個檔案的?

解決思路:下面分別針對目錄和檔案來說明:

1.目錄

ext2檔案系統建立一個目錄時,會給該目錄分配一個indoe和至少一個塊。inode記錄該目錄的相關屬性,並指向分配到的那個塊。這個塊記錄了這個目錄下的相關檔案或目錄的關聯性。

2.檔案

建立普通檔案時,會給該檔案分配一個inode與相對於該檔案大小的塊數量。如一個塊大小為4KB,建立一個100KB的檔案,linux將分配一個inode25個塊來儲存該檔案。

其中inode本省不記錄檔名,而是記錄檔案的相關屬性,檔名是記錄在目錄所屬的塊區域中的。

因此要讀取一個檔案的內容時,Linux會從/開始,一直獲取到該檔案的上層目錄所在的inode,再由該目錄的塊區域中的檔名對應的inode號來找到對應的檔案,最後根據inode中的指標找到最終的檔案內容。

如上圖,讀取/etc/crontab的流程為:

1.根據/根目錄的塊中找到/etc對應的inode

2.根據/etc 目錄的塊中的inode資料,查詢到crontabinode

3.根據查到的inode號來獲取該檔案的屬性,並且前往該inode所指的塊,順利獲取crontab 的檔案內容。

3.具體操作:編譯程式碼並執行

程式碼連結:

https://gitee.com/two_thousand_and_thirteen/codes/1i07cq8ag6po4zswjdmth16

執行截圖:

四、實踐內容(截圖、程式碼連結)本次實踐是基於OpenEuler系統下實現的

通過編寫C語言實現mkdir功能

一、背景

linux 中的mkdir命令是用來建立一個目錄的,相應的就需要使用到linux中的系統呼叫函式mkdir來實現目錄建立的功能。單單只是建立目錄的話一個系統呼叫足以,本文是使用mkdir函式來實現mkdir -p這個選項的功能,對其不存在的父目錄實現建立。

二、思路

對於一個a/b/c這樣的一個多級目錄,要想實現父目錄的建立方法和思路有很多,可以進行字串處理分出一級一級目錄來,但是這樣實現很是繁瑣,以至於我想到了遞迴實現。

思路如下:

1.先判斷a/b/c是否存在,不存在獲取其父目錄判斷。若存在直接退出

2.判斷a/b是否存在,不存在就獲取其父目錄,若存在退出

3.判斷a/是否存在,不存在就獲取其父目錄,若存在退出

4.如果其父目錄為./時退出

為了實現以上過程,需要一個可以獲得一個目錄的父目錄的函式。通過man可以清楚這個函式要求

最終定位到dirname函式非常符合我的要求。函式宣告如下:

#include <libgen.h>

char *dirname(char *path);

三、程式碼實現

程式碼連結:

https://gitee.com/two_thousand_and_thirteen/codes/a9qhy5pizk6grsdfcvot856

功能實現截圖:

四、總結

在寫mkdir -p這個功能的時候,思路很明確,程式碼也基本上早就寫好了,但是除錯花了很長時間。究其原因是在於dirname這個函式,看其宣告很明顯就是給一個目錄的path字串指標,返回一個指向其目錄的字串指標,但是其實不然。dirname不僅返回一個指向其父目錄的字串指標還可能修改傳入的引數path的值為父目錄字串。man文件中說明如下:

The  dirname()  function  may  modify  the string pointed to by path, and may return a

 pointer to static storage that may then be overwritten by subsequent calls to dirname().

最終還是通過printf列印除錯的,沒有藉助gdb,主要還是認為gdb用起來不太方便