linux-0.11 文件系統(tǒng)


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)的流程框架:


系統(tǒng)執(zhí)行流程框架

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

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

文件系統(tǒng)包含引導(dǎo)塊、超級塊、i-node位圖、邏輯塊位圖、i節(jié)點與數(shù)據(jù)區(qū)等。

2.2 i-node節(jié)點

根據(jù)文件名獲取文件內(nèi)容步驟:


2.2-1圖.從文件名獲取文件內(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)圖

緩沖區(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_listhash表中的示意圖:

insert_into_queues()函數(shù)示意圖

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阻塞在同一個資源的情況。

我列出具體工作情況:


image.png

task1在第一次使用資源的時候,tmp=NULL,而當(dāng)前任務(wù)狀態(tài)為TASK_UNINTERRUPTIBLE,所以schedule()函數(shù)不退出。而task2則由于task1tmp=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;
    }
}

則首先task3schedule()函數(shù)返回,同時task2的任務(wù)狀態(tài)變?yōu)榭蓤?zhí)行,所以task2schedule()也返回,也導(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個。
fromto都需要是4MB對齊。

先來簡單回顧一下物理地址是怎么來的。


線性地址到物理地址的轉(zhuǎn)換

fromto的限制要求,可以知道需要拷貝的是按照頁目錄來拷貝,因為一個頁目錄項就能指向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ù)的布局:


代碼與數(shù)據(jù)的空間布局

4.基本概念

4.1 進(jìn)程組,會話

進(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()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容