我們一起學學加載根文件系統
書接上回,上回書咱們說到,我們已經把硬盤的基本信息存入了 hd_info[]。
把硬盤的分區信息存入了 hd[]。
并且留了個讀取硬盤數據的 bread 函數沒有講,等主流程講完再展開這些函數的細節,我知道這是你們關心的內容。
這些都是 setup 方法里做的事情,也就是進程 0 fork 出的進程 1 所執行的第一個方法。
今天我們說 setup 方法中的最后一個函數 mount_root。
int sys_setup(void * BIOS) {
...
mount_root();
}
mount_root 直譯過來就是加載根。
再多說幾個字是加載根文件系統,有了它之后,操作系統才能從一個根兒開始找到所有存儲在硬盤中的文件,所以它是文件系統的基石,很重要。
我們翻開看看。
void mount_root(void) {
int i,free;
struct super_block * p;
struct m_inode * mi;
for(i=0;i<64;i++)
file_table[i].f_count=0;
for(p = &super_block[0] ; p < &super_block[8] ; p++) {
p->s_dev = 0;
p->s_lock = 0;
p->s_wait = NULL;
}
p=read_super(0);
mi=iget(0,1);
mi->i_count += 3 ;
p->s_isup = p->s_imount = mi;
current->pwd = mi;
current->root = mi;
free=0;
i=p->s_nzones;
while (-- i >= 0)
if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
free++;
free=0;
i=p->s_ninodes+1;
while (-- i >= 0)
if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
free++;
}
很簡單。
從整體上說,它就是要把硬盤中的數據,以文件系統的格式進行解讀,加載到內存中設計好的數據結構,這樣操作系統就可以通過內存中的數據,以文件系統的方式訪問硬盤中的一個個文件了。
那其實搞清楚兩個事情即可:
第一,硬盤中的文件系統格式是怎樣的?
第二,內存中用于文件系統的數據結構有哪些?
我們一個個來。
硬盤中的文件系統格式是怎樣的?
首先硬盤中的文件系統,無非就是硬盤中的一堆數據,我們按照一定格式去解析罷了。Linux-0.11 中的文件系統是 MINIX 文件系統,它就長成這個樣子。
每一個塊結構的大小是 1024 字節,也就是 1KB,硬盤里的數據就按照這個結構,妥善地安排在硬盤里。
可是硬盤中憑什么就有了這些信息呢?這就是個雞生蛋蛋生雞的問題了。你可以先寫一個操作系統,然后給一個硬盤做某種文件系統類型的格式化,這樣你就得到一個有文件系統的硬盤了,有了這個硬盤,你的操作系統就可以成功啟動了。
總之,想個辦法給這個硬盤寫上數據唄。
好了,現在我們簡單看看 MINIX 文件系統的格式。
引導塊就是我們系列最開頭說的啟動區,當然不一定所有的硬盤都有啟動區,但我們還是得預留出這個位置,以保持格式的統一。
超級塊用于描述整個文件系統的整體信息,我們看它的字段就知道了,有后面的 inode 數量,塊數量,第一個塊在哪里等信息。有了它,整個硬盤的布局就清晰了。
inode 位圖和塊位圖,就是位圖的基本操作和作用了,表示后面 inode 和塊的使用情況,和我們之前講的內存占用位圖 mem_map[] 是類似的。
再往后,inode 存放著每個文件或目錄的元信息和索引信息,元信息就是文件類型、文件大小、修改時間等,索引信息就是大小為 9 的 i_zone[9] 塊數組,表示這個文件或目錄的具體數據占用了哪些塊。
其中塊數組里,0~6 表示直接索引,7 表示一次間接索引,8 表示二次間接索引。當文件比較小時,比如只占用 2 個塊就夠了,那就只需要 zone[0] 和 zone[1] 兩個直接索引即可。
再往后,就都是存放具體文件或目錄實際信息的塊了。如果是一個普通文件類型的 inode 指向的塊,那里面就直接是文件的二進制信息。如果是一個目錄類型的 inode 指向的塊,那里面存放的就是這個目錄下的文件和目錄的 inode 索引以及文件或目錄名稱等信息。
好了,文件系統格式的說明,我們就簡單說明完畢了,MINIX 文件系統已經過時。
內存中用于文件系統的數據結構有哪些?
趕緊回過頭來看我們的代碼,是如何加載以這樣一種格式存放在硬盤里的數據,以被我們操作系統所管控的。
從頭看。
struct file {
unsigned short f_mode;
unsigned short f_flags;
unsigned short f_count;
struct m_inode * f_inode;
off_t f_pos;
};
void mount_root(void) {
for(i=0;i<64;i++)
file_table[i].f_count=0;
...
}
把 64 個 file_table 里的 f_count 清零。
這個 file_table 表示進程所使用的文件,進程每使用一個文件,都需要記錄在這里,包括文件類型、文件 inode 索引信息等,而這個 f_count 表示被引用的次數,此時還沒有引用,所以設置為零。
而這個 file_table 的索引,就是我們通常說的文件描述符。比如有如下命令。
echo "hello" > 0
就表示把 hello 輸出到 0 號文件描述符。
0 號文件描述符是哪個文件呢?就是 file_table[0] 所表示的文件。
這個文件在哪里呢?注意到 file 結構里有個 f_inode 字段,通過 f_inode 即可找到它的 inode 信息,inode 信息包含了一個文件所需要的全部信息,包括文件的大小、文件的類型、文件所在的硬盤塊號,這個所在硬盤塊號,就是文件的位置咯。
接著看。
struct super_block super_block[8];
void mount_root(void) {
...
struct super_block * p;
for(p = &super_block[0] ; p < &super_block[8] ; p++) {
p->s_dev = 0;
p->s_lock = 0;
p->s_wait = NULL;
}
...
}
又是把一個數組 super_block 做清零工作。
這個 super_block 存在的意義是,操作系統與一個設備以文件形式進行讀寫訪問時,就需要把這個設備的超級塊信息放在這里。
這樣通過這個超級塊,就可以掌控這個設備的文件系統全局了。
果然,接下來的操作,就是讀取硬盤的超級塊信息到內存中來。
void mount_root(void) {
...
p=read_super(0);
...
}
read_super 就是讀取硬盤中的超級塊。
接下來,讀取根 inode 信息。
struct m_inode * mi;
void mount_root(void) {
...
mi=iget(0,1);
...
}
然后把該 inode 設置為當前進程(也就是進程 1)的當前工作目錄和根目錄。
void mount_root(void) {
...
current->pwd = mi;
current->root = mi;
...
}
然后記錄塊位圖信息。
void mount_root(void) {
...
i=p->s_nzones;
while (-- i >= 0)
set_bit(i&8191, p->s_zmap[i>>13]->b_data);
...
}
最后記錄 inode 位圖信息。
void mount_root(void) {
...
i=p->s_ninodes+1;
while (-- i >= 0)
set_bit(i&8191, p->s_imap[i>>13]->b_data);
}
就完事了。
其實整體上就是把硬盤中文件系統的各個信息,搬到內存中。之前的圖可以說非常直觀了。
有了內存中的這些結構,我們就可以順著根 inode,找到所有的文件了。
至此,加載根文件系統的 mount_root 函數就全部結束了。同時,讓我們回到全局視野,發現 setup 函數也一并結束了。
void main(void) {
...
move_to_user_mode();
if (!fork()) {
init();
}
for(;;) pause();
}
void init(void) {
setup((void *) &drive_info);
...
}
int sys_setup(void * BIOS) {
...
mount_root();
}
setup 的主要工作就是我們今天所講的,加載根文件系統。
我們繼續往下看 init 函數。
void init(void) {
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
}
看到這相信你也明白了。
之前 setup 函數的一番折騰,加載了根文件系統,順著根 inode 可以找到所有文件,就是為了下一行 open 函數可以通過文件路徑,從硬盤中把一個文件的信息方便地拿到。
在這里,我們 open 了一個 /dev/tty0 的文件,那我們接下來的焦點就在這個 /dev/tty0 是個啥?