1.簡介
文件系統(tǒng)是數(shù)據(jù)的組織方式,也就是將它們組織的符合一定的格式或者規(guī)律,就命名為文件系統(tǒng)了,并不神秘。
linux-0.11 將文件系統(tǒng)分成幾個部分,分別為:
超級塊,i-node節(jié)點位圖,塊位圖,數(shù)據(jù)塊。
先說明幾個講解linux-0.11書籍:
1.linux-0.11內(nèi)核完全注釋
2.linux內(nèi)核設(shè)計的藝術(shù)
linux內(nèi)核設(shè)計的藝術(shù)寫的挺不錯的??梢韵群唵蔚拈喿x一遍這本書然后再看linux-0.11內(nèi)核完全注釋,然后再回顧linux內(nèi)核設(shè)計的藝術(shù),那linux-0.11基本上能很好的掌握了。
另外是關(guān)于文件系統(tǒng)的相關(guān)書籍:
1.minix操作系統(tǒng)設(shè)計與實現(xiàn)
2.unix操作系統(tǒng)設(shè)計
2.基礎(chǔ)知識
先看整個系統(tǒng)的流程框架:

2.1 文件系統(tǒng)結(jié)構(gòu)

文件系統(tǒng)包含引導(dǎo)塊、超級塊、i-node位圖、邏輯塊位圖、i節(jié)點與數(shù)據(jù)區(qū)等。
2.2 i-node節(jié)點
根據(jù)文件名獲取文件內(nèi)容步驟:

我來簡單說一下尋找文件的步驟:
a.尋找hello.txt文件
由于根目錄i節(jié)點是確定的,通過這個節(jié)點信息可以知道i_size,也就是目錄數(shù)目,也知道i_zone[9],也就是存放的目錄的塊的塊位置,那么就可以定位到要找的位置,然后就可以通過文件名獲取到i節(jié)點了,根據(jù)i節(jié)點也就能定位到hello.txt內(nèi)容了。
b.尋找/mnt/hello.txt文件
根據(jù)上面的步驟,先找到mnt目錄的節(jié)點,然后找到其目錄,獲取到大小i_size,如果文件系統(tǒng)是干凈的,i_size值應(yīng)該為3,然后就可以按照上面的a步驟獲取到hello.txt了。
總結(jié):還是要多看2.2-1圖。
2.3 高速緩沖區(qū)
先看一下高速緩沖的布局圖:

緩沖區(qū)結(jié)構(gòu)圖:

緩沖區(qū)鏈表結(jié)構(gòu):

根據(jù)上圖,可以從內(nèi)核中找到在hash數(shù)組中找到某一個緩沖頭的代碼:
static struct buffer_head * find_buffer(int dev, int block)
{
struct buffer_head * tmp;
for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)
if (tmp->b_dev==dev && tmp->b_blocknr==block)
return tmp;
return NULL;
}
然后是將得到的塊插入到free_list和hash表中的示意圖:

3.內(nèi)核重要函數(shù)分析
3.1 內(nèi)核同步函數(shù)
3.1.1 wake_up()與sleep_on()
sleep_on()函數(shù)是用來等待資源是否可用,如果可用,則該函數(shù)退出,否則一直阻塞,最終是阻塞在函數(shù)schedule()中。
現(xiàn)在來分析3個進(jìn)程task1,task2,task3阻塞在同一個資源的情況。
我列出具體工作情況:

task1在第一次使用資源的時候,tmp=NULL,而當(dāng)前任務(wù)狀態(tài)為TASK_UNINTERRUPTIBLE,所以schedule()函數(shù)不退出。而task2則由于task1,tmp=task1,同樣task2也被掛住,task3任務(wù)也跟task2一樣。最終,3個任務(wù)由于同一個資源不可用,而全部掛起。
而一旦wake_up()被調(diào)用:
void wake_up (struct task_struct **p)
{
if (p && *p)
{
(**p).state = 0;
*p = NULL;
}
}
則首先task3的schedule()函數(shù)返回,同時task2的任務(wù)狀態(tài)變?yōu)榭蓤?zhí)行,所以task2的schedule()也返回,也導(dǎo)致task1的任務(wù)狀態(tài)變?yōu)榭蓤?zhí)行,所以最后task1也返回。
3.1.2 鎖lock_buffer()和unlock_buffer()
有關(guān)鎖,只需要注意一件事情就好:
cli ();是清中斷許可,sti ();開中斷。它們針對的是本進(jìn)程的EFLAGS寄存器,所以說如果調(diào)度到其他進(jìn)程中,其他進(jìn)程的EFLAGS是使能的,則它可以接受中斷,并能進(jìn)入中斷服務(wù)函數(shù)的。
展開說一點,從A進(jìn)程調(diào)度到B進(jìn)程,A進(jìn)程是關(guān)閉中斷的,B進(jìn)程是開啟中斷的,則在調(diào)度到B并執(zhí)行B之前會加載相應(yīng)的寄存器,所以EFLAGS被更新,從而是可以被中斷的。
3.2 任務(wù)調(diào)度函數(shù)schedule()
看網(wǎng)上都說任務(wù)調(diào)度函數(shù)比較難,但是我看了一下,其實懂一些嵌入式,基本上理解起來不難,只是這個函數(shù)比較有技巧。
void schedule (void)
{
int i, next, c;
struct task_struct **p;
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
{
if ((*p)->alarm && (*p)->alarm < jiffies)
{
(*p)->signal |= (1 << (SIGALRM - 1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state == TASK_INTERRUPTIBLE)
(*p)->state = TASK_RUNNING;
}
while (1)
{
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i)
{
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c)
break;
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
switch_to (next);
}
調(diào)度函數(shù)分為2個部分,第一個部分是檢查是否有相應(yīng)的信號,另一個部分則是真正的調(diào)度算法。
先說第一個部分。如果設(shè)置了定時器值并且系統(tǒng)運行超過了定時值,則需要置位信號位圖的SIGALRM值,如果任務(wù)可中斷,并且設(shè)置了除_BLOCKABLE和(*p)->blocked的值,則說明任務(wù)可以進(jìn)入執(zhí)行態(tài)。
再說第二個部分。它主要是說任務(wù)需要在運行態(tài),才能進(jìn)行調(diào)度。否則就在內(nèi)核任務(wù)0執(zhí)行。
3.3 復(fù)制頁表函數(shù)copy_page_tables()
這個函數(shù)據(jù)說也比較復(fù)雜。
先看這個函數(shù)的參數(shù):
from--->線性地址(邏輯地址)
to--->線性地址(邏輯地址)
size--->頁目錄數(shù),總共1024個頁目錄數(shù),但是有效的只有4個。
from和to都需要是4MB對齊。
先來簡單回顧一下物理地址是怎么來的。

從from和to的限制要求,可以知道需要拷貝的是按照頁目錄來拷貝,因為一個頁目錄項就能指向4096B的頁表大小,相當(dāng)于1024個頁表項*4096B=4MB大小。
要注意幾項事情:
1.線性地址中的頁目錄項的值占10位,所以指向頁目錄項(找到頁表的地址)是CR3+線性地址表
的頁目錄值*4。
2.復(fù)制頁表需要按照頁目錄來復(fù)制,具體項數(shù)則由size來指定。
3.頁目錄地址值必定是4B對齊。
4.size具體含義其實也是頁目錄個數(shù)。
再來看指向頁目錄地址的值,也就是頁表地址的構(gòu)成:

由于一個頁目錄能指向4MB對齊的物理地址,所以指向頁表的地址中,其實有12位是用不上的,所以用其他的含義位代替了。
p--用于指明表項對地址轉(zhuǎn)換是否有效。p=1,表示有效。p=0,表示無效。
r/w--讀寫標(biāo)志。r/w=1,表示頁面可被讀、寫或執(zhí)行。r/w=0,表示頁面只讀或可執(zhí)行。
u/s--用戶/超級用戶標(biāo)志。u/s=1,表示運行在任何特權(quán)級上的程序都可以訪問該頁面。u/s=0,
頁面只能被運行在超級用戶特權(quán)級上的程序訪問。
在復(fù)制頁表時,目的頁表對地址轉(zhuǎn)換是不能有效的,有效則說明被其他程序或數(shù)據(jù)占用了。
經(jīng)過上面的說明,具體到代碼中,應(yīng)該這個函數(shù)就很容易明白了。
有一個特殊情況是:
從進(jìn)程0創(chuàng)建進(jìn)程1,而進(jìn)程0屬于內(nèi)核進(jìn)程,在640KB以下,所以在該函數(shù)中有想應(yīng)的判斷語句。
3.4 復(fù)制進(jìn)程信息 copy_process()
該函數(shù)是用來復(fù)制進(jìn)程的關(guān)鍵信息,主要是設(shè)置結(jié)構(gòu)體task_struct。并且復(fù)制頁表信息。
該函數(shù)中有兩個2個很重要的函數(shù),分別為:copy_mem()和copy_page_tables()
3.5 execve()
看這個函數(shù)則需要先理解代碼與數(shù)據(jù)的布局:

4.基本概念
4.1 進(jìn)程組,會話

通過這張圖,同時說1個例子,則上面的概念就比較好理解了。
$ cat test.txt | grep for
上面這個例子展示的就是一個進(jìn)程組。
5.實際場景與內(nèi)核分析
作為內(nèi)核的編寫者,都是以實際應(yīng)用場景出發(fā)并編碼。作為分析者,則只能通過讀內(nèi)核之后,反著去分析作者為什么這么寫,這樣才會更好的理解內(nèi)核,也不至于看著一堆代碼頭痛。
所以,下面的內(nèi)容是從應(yīng)用場景來分析內(nèi)核代碼。
5.1 打開文件
其實前面已經(jīng)說過打開文件、找到文件的步驟,現(xiàn)在再通過代碼的分析大致講解一下:
sys_open()
--->open_namei()
--->dir_namei()
--->get_dir()
--->find_entry()
--->iget()