13.5 main.c中的初始化
head.s在最后部分調用main.c中的start_kernel()函數,從而把控制權交給了它。所以啟動(dòng)程序從start_kernel()函數繼續執行。這個(gè)函數是main.c乃至整個(gè)操作系統初始化的最重要的函數,一旦它執行完了,整個(gè)操作系統的初始化也就完成了。
如前所述,計算機在執行start_kernel()前處已經(jīng)進(jìn)入了386的保護模式,設立了中斷向量表并部分初始化了其中的幾項,建立了段和頁(yè)機制,設立了九個(gè)段,把線(xiàn)性空間中用于存放系統數據和代碼的地址映射到了物理空間的頭4MB,可以說(shuō)我們已經(jīng)使386處理器完全進(jìn)入了全面執行操作系統代碼的狀態(tài)。但直到目前為止,我們所做的一切可以說(shuō)都是針對386處理器所做的工作,也就是說(shuō)幾乎所有的多任務(wù)操作系統只要使用386處理器,都需要作這一切。而一旦start_kernel()開(kāi)始執行,Linux內核的真實(shí)面目就一步步的展現在你的眼前了。start_kernel()執行后,你就可以以一個(gè)用戶(hù)的身份登錄和使用Linux了。
讓我們來(lái)看看start_kernel到底做了些什么,這里,通過(guò)介紹start_kernel()所調用的函數,我們來(lái)討論start_kernel()的流程和功能。
我們仿照C語(yǔ)言函數的形式來(lái)進(jìn)行這種描述,不過(guò)請注意,真正的start_kernel()函數調用子函數并不象我們在下面所寫(xiě)的這樣簡(jiǎn)單,畢竟這本書(shū)的目的是幫助你深入分析Linux。我們只能給你提供從哪兒入手和該怎么看的建議,真正深入分析Linux,還需要你自己來(lái)研究代碼。start_kernel()這個(gè)函數是在/init/main.c中,這里也只是將main.c中較為重要的函數列舉出來(lái)。
start_kernel() /*定義于init/main.c */
{
……
setup_arch();
}
它主要用于對處理器、內存等最基本的硬件相關(guān)部分的初始化,如初始化處理器的類(lèi)型(是在386,486,還是586的狀態(tài)下工作,這是有必要的,比如說(shuō),Pentium芯片支持4MB大小的頁(yè),而386就不支持),初始化RAM盤(pán)所占用的空間(如果你安裝了RAM盤(pán)的話(huà))等。其中,setup_arch()給系統分配了intel系列芯片統一使用的幾個(gè)I/O端口的口地址。
paging_init(); /*該函數定義于arch/i386/mm/init.c */
它的具體作用是把線(xiàn)性地址中尚未映射到物理地址上的部分通過(guò)頁(yè)機制進(jìn)行映射。這一部分在本書(shū)第六章有詳細的描述,在這里需要特別強調的是,當paging_init()函數調用完后,頁(yè)的初始化就整個(gè)完成了。
trap_init(); /*該函數在arch/i386/kernel/traps.c中定義*/
這個(gè)初始化程序是對中斷向量表進(jìn)行初始化,詳見(jiàn)第四章。它通過(guò)調用set_trap_gate(或set_system_gate等)宏對中斷向量表的各個(gè)表項填寫(xiě)相應的中斷響應程序的偏移地址。
事實(shí)上,Linux操作系統僅僅在運行trap_init()函數前使用BIOS的中斷響應程序(我們這里先不考慮V86模式)。一旦真正進(jìn)入了Linux操作系統,BIOS的中斷向量將不再使用。對于軟中斷,Linux提供一套調用十分方便的中斷響應程序,對于硬件設備,Linux要求設備驅動(dòng)程序提供完善的中斷響應程序,而調用使用多個(gè)參數的BIOS中斷就被這些中斷響應程序完全代替了。
另外,在trap_init()函數里,還要初始化第一個(gè)任務(wù)的Ldt和TSS,把它們填入Gdt相應的表項中。第一個(gè)任務(wù)就是init_task這個(gè)進(jìn)程,填寫(xiě)完后,還要把init_task的TSS和LDT描述符分別讀入系統的TSS和LDT寄存器。
init_IRQ() /* 在arch/i386/kernel/irq.c中定義*/
這個(gè)函數也是與中斷有關(guān)的初始化函數。不過(guò)這個(gè)函數與硬件設備的中斷關(guān)系更密切一些。
我們知道intel的80386系列采用兩片8259作為它的中斷控制器。這兩片級連的芯片一共可以提供16個(gè)引腳,其中15個(gè)與外部設備相連,一個(gè)用于級連??墒?,從操作系統的角度來(lái)看,怎么知道這些引腳是否已經(jīng)使用;如果一個(gè)引腳已被使用,Linux操作系統又怎么知道這個(gè)引腳上連的是什么設備呢在內核中,同樣是一個(gè)數組(靜態(tài)鏈表)來(lái)紀錄這些信息的。這個(gè)數組的結構在irq.h中定義:
struct irqaction {
void (*handler)(int, void *, struct pt_regs *);
unsigned long flags;
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;}
具體內容請參見(jiàn)第四章。我們來(lái)看一個(gè)例子:
static void math_error_irq(int cpl, void *dev_id, struct pt_regs *regs)
{
outb(0,0xF0);
if (ignore_irq13 || !hard_math)
return;
math_error();
}
static struct irqaction irq13 = { math_error_irq, 0, 0, "math error", NULL, NULL };
該例子就是這個(gè)數組結構的一個(gè)應用,這個(gè)中斷是用于協(xié)處理器的。在init_irq()這個(gè)函數中,除了協(xié)處理器所占用的引腳,只初始化另外一個(gè)引腳,即用于級連的2引腳。不過(guò),這個(gè)函數并不僅僅做這些,它還為兩片8259分配了I/O地址,對應于連接在管腳上的硬中斷,它初始化了從0x20開(kāi)始的中斷向量表的15個(gè)表項(386中斷門(mén)),不過(guò),這時(shí)的中斷響應程序由于中斷控制器的引腳還未被占用,自然是空程序了。當我們確切地知道了一個(gè)引腳到底連接了什么設備,并知道了該設備的驅動(dòng)程序后,使用setup_x86_irq這個(gè)函數填寫(xiě)該引腳對應的386的中斷門(mén)時(shí),中斷響應程序的偏移地址才被填寫(xiě)進(jìn)中斷向量表。
sched_init() /*在/kernel/sched.c中定義*/
看到這個(gè)函數的名字可能令你精神一振,終于到了進(jìn)程調度部分了,但在這里,你非但看不到進(jìn)程調度程序的影子,甚至連進(jìn)程都看不到一個(gè),這個(gè)程序是名副其實(shí)的初始化程序:僅僅為進(jìn)程調度程序的執行做準備。它所做的具體工作是調用init_bh函數(在kernel/softirq.c中)把timer,tqueue,immediate三個(gè)任務(wù)隊列加入下半部分的數組。
time_init()/*在arch/i386/kernel/time.c中定義*/
時(shí)間在操作系統中是個(gè)非常重要的概念。特別是在Linux,Unix這種多任務(wù)的操作系統中它更是作為主線(xiàn)索貫穿始終,之所以這樣說(shuō),是因為無(wú)論進(jìn)程調度(特別是時(shí)間片輪轉算法)還是各種守護進(jìn)程(也可以稱(chēng)為系統線(xiàn)程,如頁(yè)表刷新的守護進(jìn)程)都是根據時(shí)間運作的??梢哉f(shuō),時(shí)間是他們運行的基準。那么,在進(jìn)程和線(xiàn)程沒(méi)有真正啟動(dòng)之前,設定系統的時(shí)間就是一件理所當然的事情了。
我們知道計算機中使用的時(shí)間一般情況下是與現實(shí)世界的時(shí)間一致的。當然,為了避開(kāi)CIH,把時(shí)間跳過(guò)每月26號也是種明智的選擇。不過(guò)如果你在銀行或證交所工作,你恐怕就一定要讓你計算機上的時(shí)鐘與掛在墻上的鐘表分秒不差了。還記得CMOS嗎?計算機的時(shí)間標準也是存在那里面的。所以,我們首先通過(guò)get_cmos_time()函數設定Linux的時(shí)間,不幸的是,CMOS提供的時(shí)間的最小單位是秒,這完全不能滿(mǎn)足需要,否則CPU的頻率1赫茲就夠了。Linux要求系統中的時(shí)間精確到納秒級,所以,我們把當前時(shí)間的納秒設置為0。
完成了當前時(shí)間的基準的設置,還要完成對8259的一號引腳上的8253(計時(shí)器)的中斷響應程序的設置,即把它的偏移地址注冊到中斷向量表中去。
parse_options() /*在main.c中定義*/
這個(gè)函數把啟動(dòng)時(shí)得到的參數如debug,init等等從命令行的字符串中分離出來(lái),并把這些參數賦給相應的變量。這其實(shí)是一個(gè)簡(jiǎn)單的詞法分析程序。
console_init() /*在linux/drivers/char/tty_io.c中定義*/
這個(gè)函數用于對終端的初始化。在這里定義的終端并不是一個(gè)完整意義上的TTY設備,它只是一個(gè)用于打印各種系統信息和有可能發(fā)生的錯誤的出錯信息的終端。真正的TTY設備以后還會(huì )進(jìn)一步定義。
kmalloc_init() /*在linux/mm/kmalloc.c中定義*/
kmalloc代表的是kernel_malloc的意思,它是用于內核的內存分配函數。而這個(gè)針對kmalloc的初始化函數用來(lái)對內存中可用內存的大小進(jìn)行檢查,以確定kmalloc所能分配的內存的大小。所以,這種檢查只是檢測當前在系統段內可分配的內存塊的大小,具體內容參見(jiàn)第六章內存分配與回收一節。
下面的幾個(gè)函數是用來(lái)對Linux的文件系統進(jìn)行初始化的,為了便于理解,這里需要把Linux的文件系統的機制稍做介紹。不過(guò),這里是很籠統的描述,目的只在于使我們對初始化的解釋工作能進(jìn)行下去,詳細內容參見(jiàn)第八章的虛擬文件系統。
虛擬文件系統是一個(gè)用于消滅不同種類(lèi)的實(shí)際文件系統間(相對于VFS而言,如ext2,fat等實(shí)際文件系統存在于某個(gè)磁盤(pán)設備上)差別的接口層。在這里,您不妨把它理解為一個(gè)存放在內存中的文件系統。它具體的作用非常明顯:Linux對文件系統的所有操作都是靠VFS實(shí)現的。它把系統支持的各種以不同形式存放于磁盤(pán)上或內存中(如proc文件系統)的數據以統一的形式調入內存,從而完成對其的讀寫(xiě)操作。(Linux可以同時(shí)支持許多不同的實(shí)際文件系統,就是說(shuō),你可以讓你的一個(gè)磁盤(pán)分區使用windows的FAT文件系統,一個(gè)分區使用Unix的SYS5文件系統,然后可以在這兩個(gè)分區間拷貝文件)。為了完成以及加速這些操作,VFS采用了塊緩存,目錄緩存(name_cach),索引節點(diǎn)(inode)緩存等各種機制,以下的這些函數,就是對這些機制的初始化。
inode_init() /*在Linux/fs/inode.c中定義*/
這個(gè)函數是對VFS的索引節點(diǎn)管理機制進(jìn)行初始化。這個(gè)函數非常簡(jiǎn)單:把用于索引節點(diǎn)查找的哈希表置入內存,再把指向第一個(gè)索引節點(diǎn)的全局變量置為空。
name_cache_init() /*在linux/fs/dcache.c中定義*/
這個(gè)函數用來(lái)對VFS的目錄緩存機制進(jìn)行初始化。先初始化LRU1鏈表,再初始化LRU2鏈表。
Buffer_init()/*在linux/fs/buffer.c中定義*/
這個(gè)函數用來(lái)對用于指示塊緩存的buffer free list初始化。
mem_init() /* 在arch/i386/mm/init.c中定義*/
啟動(dòng)到了目前這種狀態(tài),只剩下運行/etc下的啟動(dòng)配置文件。這些文件一旦運行,啟動(dòng)的全過(guò)程就結束了,系統也終將進(jìn)入我們所期待的用戶(hù)態(tài)?,F在,讓我們回顧一下,到目前為止,我們到底做了哪些工作。
其實(shí),啟動(dòng)的每一個(gè)過(guò)程都有相應的程序在屏幕上打印與這些過(guò)程相應的信息。我們回顧一下這些信息,整個(gè)啟動(dòng)的過(guò)程就一目了然了。
當然,你的計算機也許速度很快,你甚至來(lái)不及看清這些信息,系統就已經(jīng)就緒,“Login:”就已經(jīng)出現了,不要緊,登錄以后,你只要打一條dmesg | more命令,所有這些信息就會(huì )再現在屏幕上。
【Loading ……】出自bootsect.S ,表明內核正被讀入。
【uncompress ……】很多情況下,內核是以壓縮過(guò)的形式存放在磁盤(pán)上的,這里是解壓縮的過(guò)程。
下面這部分信息是在main.c的start_kernel函數被調用時(shí)顯示的。
【 Linux version
【 Detected 199908264 Hz processor】調用init_time()時(shí)打出的信息。
【Console:colour VGA+ 80x25,1 virtaul console(max 63)】調用 console_init()打出的信息 。初始化的終端屏幕使用彩色VGA模式,最大可以支持63個(gè)終端。
【 Memory: 63396k/65536k available (848k kernel code, 408k reserved, 856k data)】調用 init_mem()時(shí)打印的信息。內存共計65536K,其中空閑內存為63396K,已經(jīng)使用的內存中,有848K用于存放內核代碼,404K保留,856K用于內核數據。
【 VFS:Diskquotas version dquot_
以下是對設備的初始化 :
【 PCI: PCI BIOS revision 2.10 entry at 0xfd8d1 |
PCI: Using configuration type 1 |
PCI: Probing PCI hardware 】調用pci_init()函數時(shí)顯示的信息。
【 Linux NET4.0 for Linux 2.2
Based upon
NET4: Unix domain sockets 1.0 for Linux NET4.0.
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP】調用socket_init()函數時(shí)打印的信息。使用Linux的4.0版本的網(wǎng)絡(luò )包,采用sockets 1.0 和1.0版本的TCP/IP協(xié)議,TCP/IP協(xié)議中包含有ICMP,UDP,TCP三組協(xié)議。
【 Detected PS/2 Mouse Port。
Sound initialization started
Sound initialization complete
Floppy drive(s): fd0 is
Floppy drive(s): fd0 is
FDC 0 is a National Semiconductor PC87306 】調用device_setup()函數時(shí)打印的信息。包括對ps/2型鼠標,聲卡和軟驅的初始化。
看完上面這一部分代碼和與之相應的信息,你應該發(fā)現,這些初始化程序并沒(méi)有完成操作系統的各個(gè)部分的初始化,比如說(shuō),文件系統的初始化只是初始化了幾個(gè)內存中的數據結構,而更關(guān)鍵的文件系統的安裝還沒(méi)有涉及,其實(shí),這是在init進(jìn)程建立后完成的。下面,就是start_kernel()的最后一部分內容。
Linux學(xué)習筆記(1)
os 啟動(dòng)第一步
1. bios
cpu 的初始化
主機加電后,啟動(dòng)時(shí)鐘發(fā)生器,在總線(xiàn)上產(chǎn)生powergood信號
cpu收到reset信號,進(jìn)入初始化過(guò)程
cpu轉入8086實(shí)模式
ds = es = fs = gs = ss = 0
cs = 0xffff
ip = 0xfff0
進(jìn)入bios加電自檢過(guò)程(power on self test)
bios初始化
關(guān)中斷,進(jìn)行所有的post檢測
將中斷向量表的起始地址設為0x0000h
0x0000h~0x03ffh中存放了256個(gè)中斷
建立了實(shí)模式下的中斷向量表
bios的啟動(dòng)程序調用int 19h中斷
將控制權轉移給boot loader
int 19h中斷的功能
int? 19h按照bios中的啟動(dòng)設備順序查詢(xún)每個(gè)啟動(dòng)設備
在軟盤(pán)的啟動(dòng)扇區或者硬盤(pán)的mbr中有boot? loader,那么這個(gè)扇區的最后兩個(gè)字節必然為0xaa55。bios將這個(gè)扇區(512個(gè)字節)讀入內存的0000:7c00開(kāi)始的位置,然后跳轉到內存0000:7c00的地方開(kāi)始執行
如果在所有的啟動(dòng)設備上都找不到boot loader,那么就調用int 18h,將控制權交給bios rom basic,鎖定機器,并且在屏幕上顯示no boot device ***ailable
結論
boot? loader應當存放在啟動(dòng)設備的第一個(gè)扇區中,對于硬盤(pán)是mbr,對于軟盤(pán)是啟動(dòng)扇區
安裝了boot? loader的啟動(dòng)扇區的最后兩個(gè)字節必須為0xaa55
bios在post過(guò)程結束以后,調用int 19h中斷,將boot? loader讀入內存0000:7c00處。然后釋放控制權,跳轉到0000:7c00開(kāi)始執行boot loader的代碼
2. 最簡(jiǎn)單的boot loader:fdisk /mbr
硬盤(pán)的分區表結構
a) 第一個(gè)扇區為mbr
b) 一個(gè)硬盤(pán)上最多有4個(gè)主分區
c) 第一個(gè)主分區一般從cylinder 0, head 1, sector 1開(kāi)始
d) cylinder 0, head 0, sector 2~n一般來(lái)說(shuō)保留不用
e) 其余的主分區一般從cylinder x, head 0, sector 1開(kāi)始
master boot record
f) 位于硬盤(pán)的cylinder 0, head 0, sector 1的位置
g) 大小為512字節
h) 其中存放了4個(gè)主分區的入口,每個(gè)入口占16個(gè)字節(這就是為什么一個(gè)磁盤(pán)最多只能有4個(gè)主分區的原因)
i) 最后兩位為啟動(dòng)標志,如果mbr中有boot loader的話(huà),則為0xaa55
j) 留給boot loader的空間為512-16x4-2=446字節
mbr中的boot loader的功能
k) 初始化,將自身搬移到0000:0600的位置
l) 在主分區入口表中尋找活動(dòng)的分區
m) 調用int 13h ah=02將活動(dòng)的分區的啟動(dòng)扇區讀入內存0000:7c00處
n) 跳轉到0000:7c00處執行活動(dòng)分區的啟動(dòng)扇區中的代碼
初始化,將自身搬移到0000:0600的位置
0000:7c00 cli 關(guān)中斷
0000:7c01 xor ax,ax
0000:7c03 mov ss,ax 將堆棧段(ss)設為0
0000:7c05 mov sp,7c00 將棧頂指針(sp)設置為7c00
0000:7c08 mov si,sp 將si也設為7c00
0000:7c0a push ax
0000:7c0b pop es 將es設為0000
0000:7c0c push ax
0000:7c0d pop ds 將ds設為0000
0000:7c0e sti 開(kāi)中斷
0000:7c0f cld
0000:7c10 mov di,0600 將di設為0600
0000:7c13 mov cx,0100 準備移動(dòng)256個(gè)字(512字節)
0000:7c16 repnz
0000:7c17 movsw 將mbr從0000:7c00移動(dòng)到0000:0600
0000:7c18 jmp 0000:061d 開(kāi)始搜索主分區入口表
在主分區入口表中尋找活動(dòng)的分區
0000:061d mov si,07besi指向分區表入口(在總共512byte的主引導記錄中,mbr的引導程序占了其中的前446個(gè)字節(偏移0h~偏移1bdh),隨后的64個(gè)字節(偏移1beh~偏移1fdh)為dpt(disk partitiontable,硬盤(pán)分區表),最后的兩個(gè)字節“55aa”(偏移1feh~偏移1ffh)是分區有效結束標志)
0000:0620 mov bl,04 一共有4個(gè)表項
0000:0622 cmp byte ptr [si],80 是否為活動(dòng)分區
0000:0625 jz found_active 找到了一個(gè)活動(dòng)表項
0000:0627 cmp byte ptr [si],00 是否為非活動(dòng)分區
0000:062a jnz not_active 不可識別的分區標識
0000:062c add si,+10 指向下一個(gè)表項(+16)
0000:062f dec bl 循環(huán)標志減一
0000:0631 jnz 0000:061d 繼續循環(huán)
0000:0633 int 18 未找到可啟動(dòng)的分區,轉到rom basic
對于磁盤(pán)結構可以詳看 http://www.datasoon.com/fat32-1.htm
調用int13 ah = 02h讀取啟動(dòng)扇區
0000:0635 mov dx,[si] 設置int 13調用中head和driver的值
0000:0637 mov cx,[si+02] 設置int 13調用中cylinder的值
0000:063a mov bp,si 保存活動(dòng)分區入口表項地址
…
0000:065d mov di,0005 設置讀取的重試次數
0000:0660 mov bx,7c00 將啟動(dòng)扇區讀取到0000:7c00(es:bx)
0000:0663 mov ax,0201 準備調用int 13讀取一個(gè)扇區al = 01
0000:0666 push di 保存重試次數di
0000:0667 int 13 調用int 13讀取一個(gè)扇區到0000:7c00
0000:0669 pop di 恢復重試次數di
0000:066a jnb int13ok 如果讀取成功,則跳轉至啟動(dòng)代碼
0000:066c xor ax,ax 準備int 13 ah = 0復位磁盤(pán)
0000:066e int 13 調用int 13復位磁盤(pán)
0000:0670 dec di 重試次數減一
0000:0671 jnz 0000:0660 進(jìn)行下一次重試
運行啟動(dòng)扇區中的代碼
0000:067b mov di,7dfe 指向啟動(dòng)扇區中的啟動(dòng)標識
0000:067e cmp word ptr [di],aa55 檢查啟動(dòng)表識是否為0xaa55
0000:0682 jnz display_msg 如果不是,則保錯
0000:0684 mov si,bp 恢復si,指向活動(dòng)分區入口表項
0000:0686 jmp 0000:7c00 跳轉至啟動(dòng)扇區的代碼
結論
boot loader放在可啟動(dòng)設備的第一個(gè)扇區中?
boot? loader的大小受扇區大小和其他附加信息的限制。在mbr中,為446字節
boot? loader在從bios中接手cpu的控制權時(shí),位于內存地址0000:7c00處
fdisk /mbr產(chǎn)生的boot? loader不具備啟動(dòng)os的能力,其本質(zhì)上是一個(gè)chain loader,用于引導一個(gè)有啟動(dòng)os能力的boot loader
3.如何引導os:dos boot loader
boot sector的結構
boot sector位于每個(gè)分區的第一個(gè)扇區?
boot? sector的第一個(gè)部分是一個(gè)跳轉指令和一個(gè)nop,以跳轉實(shí)際的boot loader的代碼中
bios parameter? block中存放了和這個(gè)分區相關(guān)的一系列參數
bpb之后就是實(shí)際的boot loader的代碼?
最后是一個(gè)可啟動(dòng)分區標識0xaa55
使用format /s對boot sector做的修改
在0x000h處寫(xiě)入bpb?
在0x03eh處寫(xiě)入dos boot loader的代碼
在0x1feh處寫(xiě)入0xaa55標識?
此外,format /s還要
初始化fat表
將io.sys和msdos.sys寫(xiě)入,占據fat表前兩個(gè)表項?
dos boot loader的功能
初始化
計算根目錄所在的fat表的扇區號?
讀取根目錄的第一個(gè)扇區到0000:0500?
檢查前兩個(gè)表項是否為io.sys和msdos.sys
將io.sys的前3個(gè)扇區讀入0000:0700或者0070:0000的位置中?
在寄存器中保留一些信息,然后跳轉到0070:0000處執行操作系統代碼
dos boot loader和mbr中的boot? loader的最大區別在于對于文件系統的理解
mbr中的boot loader不理解文件系統,所以無(wú)法啟動(dòng)特定的os
dos boot loader提供了對于fat文件系統的支持,所以能夠啟動(dòng)在fat文件系統上的dos
dos boot loader知道os的內核文件的位置,其主要的工作就是將內核文件讀入內存,然后將控制權轉交給os
4.硬盤(pán)尋址方式:chs vs lba
最早期的chs尋址
最早期的磁盤(pán)尋址是通過(guò)cylinder/head/sector進(jìn)行的,int? 13h中給出的chs參數直接指定了數據的物理位置
例:int 13h ah = 02?
ah = 02 al = 讀入的扇區數目
ch = cylinder低8位 cl = cylinder高兩位
dh = head dl = 操作的磁盤(pán)(80h for c:)
限制:
cylinder <= 1024
head <= 16
sector <= 63
總容量 < 1024 x 16 x 63 x 512b = 528mb
l-chs & p-chs
為了解決直接尋址的問(wèn)題,現代的hd中,chs只有邏輯上的意義,不表達物理上的實(shí)際位置
l-chs用在支持chs? translation的bois的int 13 ah = 0xh的調用中
cylinder <= 1024
head <= 256
sector <= 63
總容量 <= 1024 x 256 x 63 x 512b = 8gb
p-chs用在hd的接口上
cylinder <= 65535
head <= 16
sector <= 63
總容量 <= 65535 x 16 x 63 x 512b = 136gb
lba:large block addressing
lba的出現是為了解決chs模式地址受限的問(wèn)題
lba提供了一種線(xiàn)性的尋址方式:cylinder 0,head 0,sector 1相當于lba地址0。往后每增加一個(gè)扇區,lba地址加一
lba地址和chs地址可以通過(guò)如下公式轉換
lba = ( (cylinder * heads_per_cylinder + heads ) * sectors_per_track ) + sector - 1
新型的bois提供了int 13h ah = 4xh的擴展磁盤(pán)調用以支持lba模式
lba:large block addressing
int 13h ah = 42h?
ah = 42h dl = 操作的磁盤(pán)(80h for c:)
ds:si = disk address packet
disk address packet的格式?
結論:尋址方式和boot loader的關(guān)系
bios在post過(guò)程以后調用int 19h,使用的是chs尋址?
如果一個(gè)os自身的boot? loader使用的是chs尋址模式,那么在老式bios上不能啟動(dòng)528mb以后的分區,在新式bios上不能啟動(dòng)8g以后的分區(lilo的問(wèn)題)
只有支持lba的boot loader才能支持啟動(dòng)8g以后分區上的os
4.支持多os引導的boot loader
支持多os引導的boot loader是:
一段在啟動(dòng)時(shí)被bios int 19調用的代碼?
能夠理解系統中安裝的多個(gè)不同的os?
用戶(hù)可以選擇啟動(dòng)特定的os
程序根據用戶(hù)選擇,通過(guò)直接載入或者chain load的方法,啟動(dòng)選中的os?
支持多os引導的boot loader需要:
自身足夠的小,或者支持多階段載入,以能夠安裝在mbr或者boot sector中?
能夠理解多種文件系統的格式,以便能夠找到存放在不同文件系統下的系統內核
能夠理解多種文件格式(elf/a.out),以便能夠正確的載入不同的系統內核
支持chs和lba兩種磁盤(pán)訪(fǎng)問(wèn)模式,以便能夠正確的啟動(dòng)8g以后的分區
支持chain loader,以便通過(guò)特定的boot? loader載入os
支持多os引導的boot loader在執行32位os的內核代碼前,需要構建的機器狀態(tài):
cs必須是一個(gè)32位的可讀可執行的代碼段,偏移為0,上限為0xffffffff
ds, es, fs,? gs和ss必須為32位的可讀寫(xiě)段,偏移為0,上限為0xffffffff
2?0號地址線(xiàn)必須在32位地址空間中可用(初始固定為0)
分頁(yè)機制必須被關(guān)閉
處理器中斷標記被關(guān)閉?
eax的值為0x2badb002?
ebx中存放了一個(gè)32位的地址,指向由boot? loader填充的一系列啟動(dòng)信息
引自:multiboot specification
5.實(shí)例分析:grub
grub是:
grand unified bootloader的縮寫(xiě)
是一個(gè)靈活而強大的boot loader?
其能夠理解多種不同的文件系統和可執行文件格式,從而能夠引導多種os
通過(guò)將boot? loader所需要的功能封裝成一套腳本語(yǔ)言,從而能夠按照特定的方式引導os
grub的i/o
支持chs和lba兩種磁盤(pán)訪(fǎng)問(wèn)模式?
(device[,part-num][,bsd-subpart-letter])的方式訪(fǎng)問(wèn)設備:(hd0), (hd1, 0), (hd0, a), (hd0, 1, a)
文件訪(fǎng)問(wèn)?
通過(guò)路徑形式訪(fǎng)問(wèn):/boot/grub/menu.lst
通過(guò)扇區形式訪(fǎng)問(wèn):0+1,200+1,300+300
grub的腳本:
root指定一個(gè)啟動(dòng)的設備?
kernel指定操作系統的內核
boot正式啟動(dòng)一個(gè)os?
makeactive激活一個(gè)分區?
chainloader調用啟動(dòng)設備上的boot loader
啟動(dòng)linux
root (hd0,0)?
kernel? /vmlinuz root=/dev/hda1
boot?
啟動(dòng)windows
root (hd0,0)?
chainloader +1
makeactive?
boot?
grub的組成
stage1?
grub的第一部分,安裝在mbr或者boot sector中
用于引導stage2或者stage1.5
stage2?
grub的核心影像,用于提供grub的主要功能
stage1.5?
stage1與stage2之間的橋梁,安裝在0磁道上第一個(gè)扇區之后
stage1不理解文件系統,但是stage1.5可以
stage1.5最終調用stage2
nbgrub/pxegrub?
grub的網(wǎng)絡(luò )啟動(dòng)模塊
stage1的結構
為了保持和fat/hpfs bios的兼容性,所以保存bpb
在bpb之后的stage1配置數據區,在安裝的時(shí)候被填寫(xiě)?
在數據區之后,才是代碼段
最后是0xaa55啟動(dòng)扇區標志?
stage1的流程
在stage1的配置數據區中存放了stage2所在的磁盤(pán)號、lba地址以及stage2的載入地址
stage1不需要理解任何的文件系統,只需要根據給出的扇區號,讀入stage2的第一個(gè)扇區即可
stage1相當于前面分析的mbr中的boot? loader
stage2的第一部分start.s
start.s存放在stage2文件的第一個(gè)扇區里面?
stage2剩余部分的lba地址和內存的載入地址是放在start.s的firstlist和lastlist之間的,這個(gè)數據段位于start.s代碼的尾部,在安裝的時(shí)候被寫(xiě)入,稱(chēng)為block list
block list以全0項結尾?
stage2的第一部分start.s
在start.s的代碼開(kāi)始執行的時(shí)候,ds:si所指向的內存地址的內容是stage1中準備好的,用于為int 13h調用準備參數
start.s的功能就是根據block list,將stage2剩余的部分讀入內存,然后跳轉到0x8200h處執行stage2的功能代碼
stage2的第二部分asm.s
在asm.s中定義了一系列的函數的實(shí)現,包括grub得主入口函數main?
在main函數中,完成了如下的工作:
ds = es = ss = 0
建立實(shí)模式/bios棧,esp = 0x2000 - 0x10,向低地址方向增長(cháng)
轉入保護模式
建立并清空保護模式棧
調用cmain,進(jìn)入grub的c代碼中(stage2.c)
在cmain中,完成了如下的工作:
設法打開(kāi)/boot/grub/menu.lst這個(gè)配置文件
根據配置文件,構建用戶(hù)菜單?
如果菜單構建成功,則調用run_menu?
如果菜單構建失敗,則調用enter_cmdline
問(wèn)題:文件系統
在這個(gè)時(shí)候,grub已經(jīng)開(kāi)始訪(fǎng)問(wèn)文件系統?
grub如何對付不同的文件系統?
grub中的文件系統層:disk_io.c
grub中為每一個(gè)文件系統提供了一個(gè)抽象層?
文件系統用fsys_entry描述(filesys.h)
struct fsys_entry
{
char *name;
int (*mount_func) (void);
int (*read_func) (char *buf, int len);
int (*dir_func) (char *dirname);
void (*close_func) (void);
int (*embed_func) (int *start_sector, int needed_sectors);
};
全局變量fsys_table包含了grub支持所有文件系統,通過(guò)fsys_table和fsys_type,從而可以以統一的方式訪(fǎng)問(wèn)不同的文件系統
grub中的命令處理:buildin
grub支持的每個(gè)命令,均有一個(gè)buildin和其對應?
這些buildin被定義在buildins.c中
分析以下3個(gè)命令:?
chainloader?
kernel?
boot
chainloader
檢查--force標記?
調用grub_open打開(kāi)文件,這里的文件用block? list表示(+1)
調用grub_read讀入一個(gè)扇區到0000:7c00的位置?
檢查啟動(dòng)扇區標志0xaa55?
kernel
檢查--type和--no_mem_option標志?
調用load_image,讀入指定的內核文件?
load_image中處理了elf和a.out的各種變形
load_image通過(guò)對于內核文件的分析,識別出被啟動(dòng)的os的種類(lèi)(通過(guò)內核文件的magic number)
load_image針對不同種類(lèi)的os的內核提供了特定的載入代碼(這里的代碼異常復雜,牽涉到了不同的os的實(shí)現細節,未作分析)
最終填充mbi (multiboot information)結構
boot
通過(guò)執行chainloader或者kernel以后,當前需要啟動(dòng)的內核的類(lèi)型已經(jīng)確定了
在執行boot的時(shí)候,根據確定的內核類(lèi)型,每一種內核均有一種啟動(dòng)的方法
對于linux,最后調用了stop函數實(shí)現控制權的轉移
對于chainloader,最后也是調用了stop函數實(shí)現控制權的轉移
boot
通過(guò)執行chainloader或者kernel以后,當前需要啟動(dòng)的內核的類(lèi)型已經(jīng)確定了
在執行boot的時(shí)候,根據確定的內核類(lèi)型,每一種內核均有一種啟動(dòng)的方法
對于linux,最后調用了stop函數實(shí)現控制權的轉移
對于chainloader,最后也是調用了stop函數實(shí)現控制權的轉移
內存管理之段頁(yè)機制 by lxwpp
內存管理(memory management)
內存地址空間
分段機制
分頁(yè)機制
cpu多任務(wù)和保護模式
內存地址空間概念
->邏輯地址:是指由程序產(chǎn)生的由段選擇符和段內偏移地址兩個(gè)部分組成的地址。
->線(xiàn)形地址:是邏輯地址到物理地址變換之間的中間層,是處理器可尋址的內存空間(稱(chēng)為線(xiàn)性地址空間)中的地址。32位,4gb
->物理地址:是指出現在cpu 外部地址總線(xiàn)上的尋址物理內存的地址信號,是地址變換的最終結果地址。32位無(wú)符號整數表示 n虛擬內存:是指計算機呈現出要比實(shí)際擁有的內存大得多的內存量。
分段機制
段是定義內存區域的另一種機制,與頁(yè)類(lèi)似。這兩種機制可以重疊:地址一定在某一頁(yè)面內,也可能處于段內。 n
段的描述符:描述段的屬性的一個(gè)8字節存儲單元
段描述符的一般格式
保存描述符項的描述符表
gdt:主要的基本描述符表,該表可被所有程序用于引用訪(fǎng)問(wèn)一個(gè)內存段。
idt:保存有定義中斷或異常處理過(guò)程的段描述符
ldt:該表應用于多任務(wù)系統中,通常每個(gè)任務(wù)使用一個(gè)ldt 表
段選擇符
分段機制
邏輯地址通過(guò)分段機制自動(dòng)映射(變換)到中間層4g線(xiàn)形地址空間(32位的)
尋址的步驟:
1)在段選擇符中裝入16位數,同時(shí)給出32位地址偏移量(比如在es1,edi中)
2)根據段選擇符中的索引值、ti及rpl值,再根據相應描述符表寄存器中的段地址和段界限,進(jìn)行一系列合法性檢查(如特權級、界限檢查)
如果段無(wú)問(wèn)題,就取出相應的描述符放入段描述符高速緩沖寄存器中
3)將描述符中的32位段基地址和放在 esi、edi等中的32位有效地址相加,形成了32位物理地址
cpu 進(jìn)行地址變換(映射)的主要目的是為了解決虛擬內存空間到物理內存空間的映射問(wèn)題。
實(shí)模式和保護模式尋址
實(shí)模式:尋址一個(gè)內存地址主要是使用段和偏移值,段值被存放在段寄存器中(例如ds),并且段的長(cháng)度被固定為64kb。段內偏移地址存放在任意一個(gè)可用于尋址的寄存器中(例如si)。因此,根據段寄存器和偏移寄存器中的值,就可以算出實(shí)際指向的內存地址
相比:
1)和實(shí)模式下的尋址相比,段寄存器值換成了段描述符表中相應段描述符的索引值以及段表選擇位和特權級--段選擇符
2)偏移值還是使用了原實(shí)模式下的概念
保護模式
1)段寄存器中存放的不再是被尋址段的基地址,而是一個(gè)段描述符表(segment descriptor table)中某一描述符項在表中的索引值
2)段的長(cháng)度可變,由描述符中的內容指定
分頁(yè)機制
分頁(yè)機制功能是把由段機制轉換而來(lái)的線(xiàn)性地址轉換位物理地址
轉化過(guò)程如圖:
轉換步驟
1)cr3包含著(zhù)頁(yè)目錄的起始地址,用32位線(xiàn)性地址的最高10位a31~a22作為頁(yè)目錄的頁(yè)目錄項的索引,將它乘以4,與cr3中的頁(yè)目錄的起始地址相加,形成相應頁(yè)表的地址
2)從指定地址中取出32位頁(yè)目錄項,它的低12位為00,這32位是頁(yè)表的起始地址用32位線(xiàn)性地址中的a21-a22位作為頁(yè)表中的頁(yè)面索引,將它乘以4,與頁(yè)表的起始地址相加,形成32位頁(yè)面地址。
3)將a11-a0作為相對于頁(yè)面地址的偏移量,與32位頁(yè)面地址相加,形成23位頁(yè)面物理地址。
擴展分頁(yè)
頁(yè)的大小為4mb(2的22次方),擴展前是4kb
線(xiàn)性地址分為兩個(gè)域:最高10位的目錄域和其余22位的偏移量,如圖
【圖】
虛擬內存管理的數據結構
linux中頁(yè)目錄(pgd) 、頁(yè)面中間目錄(pmd)、頁(yè)表(pt),分別由數據結構pgd_t,pmd_t,pte_t來(lái)表示。
typedef struct { unsigned long pmd; }pmd_t;
typedef struct { unsigned long pte; } pte_t;
typedef struct { unsigned long pgd; } pgd_t;
typedef struct { unsigned long pgprot; }pgprot_t;
#define page_shift 12
//偏移量的位數,因此頁(yè)面大小為4kb n#define page_size(1ul<<page_shift) n#define page_mask (~(page_size-1)
//值定義為0xfffff000,用以屏蔽掉偏移量域的所有位
定義頁(yè)目錄大小、頁(yè)目錄掩碼等信息的代碼如下:
#define pgdir_shift 22 //線(xiàn)性地址中頁(yè)目錄偏移量位數
#define pgdir_size (1ul<< pgdir_shift) ?//一個(gè)頁(yè)目錄項的地址空間(4m) ?#define pgdir_mask (~(pgdir_size-1)) ?//頁(yè)目錄掩碼
#define ptrs_per_pgd 1024 ?//頁(yè)目錄表項個(gè)數
其中當時(shí)提到的一個(gè)問(wèn)題就是:虛擬地址和邏輯地址究竟有和區別?
內存初始化和分配回收
1 內存初始化
2 內存的分配和回收(伙伴算法,slab,cache)
內存初始化
startup_32 函數
start_kernel 函數
startup_32 函數
本函數完全用匯編語(yǔ)言實(shí)現,主要功能是啟用分頁(yè)機制。l當linux啟動(dòng)時(shí),首先運行在實(shí)模式下(對物理地址操作),隨后就要轉到保護模式下(對虛擬地址操作)運行,linux內核代碼的入口點(diǎn)就是/arch/i386/kernel/head.s中的startup_32。
startup_32主要做的工作:l
編譯內核過(guò)程中,首先初始化一個(gè)頁(yè)目錄swapper_pg_dir,和前8m物理地址空間的頁(yè)表pg0,pg1。這樣,初始狀態(tài)下,用戶(hù)空間和內核空間都只映射了開(kāi)頭的兩個(gè)目錄項,即8m的空間,并且有著(zhù)相同的映射。l內核開(kāi)始運行后運行在內核空間,那末,為什么把用戶(hù)空間的低區(8m)也進(jìn)行映射呢?簡(jiǎn)而言之,[為了實(shí)模式到保護模式的平穩過(guò)渡]這是因為剛開(kāi)啟頁(yè)面映射時(shí),(并不說(shuō)明linux內核真正進(jìn)入了保護模式)指令寄存器eip仍指向地址,仍會(huì )以物理地址取指令,如果頁(yè)目錄只映射內核空間,而不映射用戶(hù)空間的低區,則一旦開(kāi)啟頁(yè)映射機制后就不能繼續執行了。
初始頁(yè)目錄swapper_pg_dir的映射圖
【圖,要插入】
當startup_32將頁(yè)目錄swapper_pg_dir的物理地址裝入控制寄存器cr3,并把cr0的最高位置1,即開(kāi)啟了分頁(yè)機制。隨后,編譯程序使所有的符號地址都在虛擬地址空間中,指令寄存器eip指向虛擬地址空間的某個(gè)地址,就使cpu轉入了內核空間,完成從實(shí)模式到保護模式的過(guò)渡。函數setup_arch確定start_kernel中內存初 始化的起始地址start_mem和結束地址end_mem。start_mem是內存映像的結束地址_end對應的核心態(tài)當startup_32將頁(yè)目錄swapper_pg_dir的物理地址裝入控制寄存器cr3,并把cr0的最高位置1,即開(kāi)啟了分頁(yè)機制。隨后,編譯程序使所有的符號地址都在虛擬地址空間中,指令寄存器eip指向虛擬地址空間的某個(gè)地址,就使cpu轉入了內核空間,完成從實(shí)模式到保護模式的過(guò)渡。 函數setup_arch確定start_kernel中內存初始化的起始地址start_mem和結束地址end_mem。start_mem是內存映像的結束地址_end對應的核心態(tài)虛擬地址,end_mem是將bios自檢時(shí)確定的物理內存大小轉換成核心態(tài)的虛擬地址
ps:在pc中,最初1mb的存儲空間的使用很特殊。開(kāi)頭640kb(0x0~0x9ffff)為ram,從0xa0000開(kāi)始的空間用于cga、ega、vga等各種圖形卡。從0xf0000到0xfffff(最高的4kb)是在eprom或flash存儲器中的bios。 ll這個(gè)階段初始化后的,物理內存初始化的內核映像見(jiàn)后圖:
start_kernel函數l
本函數實(shí)現整個(gè)linux內核的初始化,有關(guān)內存初始化的部分介紹如下:paging_init函數初始化頁(yè)表;頁(yè)描述符的建立由free_area_init函數完成;mem_init函數設置物理頁(yè)面的標志位,整理空閑區; kmem_cache_initkmem_cache_sizes_init函數初始化cache_cache,通用cache,slab分配器 startup_32和start_kernel函數完成后物理內存的初始化情況:
【圖要插入】
內存分配和回收之伙伴算法(buddy)
產(chǎn)生背景:內核應該為分配一組連續的物理頁(yè)而建立一種穩定、高效的分配策略。為此,必須解決內存管理中的外部碎片問(wèn)題。頻繁的請求和釋放不同大小的一組連續頁(yè),必然導致在已分配物理頁(yè)的塊內分散許多小塊的空閑頁(yè)。這樣,即使有足夠的頁(yè)滿(mǎn)足請求,要分配一個(gè)大塊的連續頁(yè)就可能無(wú)法滿(mǎn)足?!緜渥ⅰ窟@是在物理存儲器上分配內存【/備注】
算法工作原理:linux的伙伴算法把所有的空閑物理頁(yè)分組為10個(gè)塊鏈表,每個(gè)塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256和512個(gè)連續的頁(yè)(2的冪次方)。每個(gè)塊的第一個(gè)頁(yè)的物理地址也是該塊大小的整數倍。分配一個(gè)塊(例128個(gè)連續頁(yè))時(shí),首先在對應的鏈表中查找空閑塊。如果找到,則直接分配;否則,查找下一個(gè)更大的塊,將其中的一部分分配給進(jìn)程,剩余部分插入相應的塊鏈表中。
滿(mǎn)足以下條件的兩個(gè)塊就稱(chēng)為伙伴:
1)兩個(gè)塊的大小相同 l2)兩個(gè)塊的物理地址連續 l伙伴算法把滿(mǎn)足以上條件的兩個(gè)塊合并為一個(gè)塊,算法是迭代算法
伙伴算法的數據結構
伙伴系統的數據結構是free_area_struct: l struct free_area_struct{ l struct page*next; //雙向鏈表指針,鏈 l //表每個(gè)成員是一個(gè)塊的第一個(gè)物理頁(yè)描述符 l struct page *prev; lunsigned int *map; //指向對應的位圖 l unsigned long count;//鏈表元素個(gè)數 l }
數據結構的分析:free_area[0]到free_area[9]都是free_area_struct類(lèi)型的結構,分別對應不同大小塊的鏈表,并且分別對應一個(gè)位圖。
位圖的大小取決于系統物理頁(yè)面的數量。第k項位圖的每一位描述大小為2k的物理頁(yè)的兩個(gè)伙伴塊的狀態(tài)。如果位圖的某一位為0,表示一對伙伴塊中或者都忙或者都空閑;如果為1,肯定有一塊為忙。當伙伴塊都空閑時(shí),內核把他們當作一個(gè)大小為2k+1的塊來(lái)處理。例如,一個(gè)128mb的ram,可以分成32768個(gè)頁(yè),16384個(gè)大小為2個(gè)頁(yè)的塊,…,則free_area[0]的位圖有16384位,free_area[1]的有8192位…
伙伴系統數據結構圖
【圖】
slab分配機制
為什么要提出slab機制l原因:
伙伴系統采用頁(yè)作為基本內存區,適合于對大塊內存的請求。但是如果請求內存的大小與分配給它的大小不匹配,容易產(chǎn)生內部碎片。 l
解決之道:
linux2.0采用的方法是:linux提供按幾何分布的內存區大小,即建立了13個(gè)按幾何分布的空閑內存鏈表,大小從32到131056字節?;锇橄到y的調用,既為了獲得額外所需的物理頁(yè)以存放新的內存區,也為了釋放不再包含內存區的物理頁(yè)。
linux2.2起采用了slab分配模式
linux采用slab分配器模式管理內存區。它把內存區看作對象,對象有一組數據結構和構造、析構函數組成,但為提高效率,指向構造和析構函數的指針都為null。slab分配器把對象分組放進(jìn)高速緩存。每個(gè)高速緩存存放著(zhù)同一種類(lèi)型的對象。高速緩存被劃分成多個(gè)slab,每個(gè)slab由一個(gè)或多個(gè)連續的物理頁(yè)組成。這些頁(yè)中包含已分配的對象,也包含空閑對象 linux中引入slab主要目的是減少對伙伴算法的調用次數
slab分配器的組成:
注意:這里的高速緩存不是cpu中的高速緩存。它實(shí)際存在于物理內存中,由于分配速度相對于伙伴系統比較快,稱(chēng)謂高速緩存。
【圖】
實(shí)際上,緩沖區就是主存中的一片區域,把這片區域劃分為多個(gè)塊,每塊就是一個(gè)slab,每個(gè)slab由一個(gè)或多個(gè)頁(yè)面組成,每個(gè)slab中存放的就是對象
cache的數據結構l l
struct kmem_cache_s { l kmem_slab_t *c_freep;//指向第一個(gè)含有 l //空閑對象的slab的指針 l
unsigned long c_flags; //描述cache靜態(tài)特性的標志,例如slab(object)描述符的 存儲位置,外部存儲還是內部存儲 l
unsigned long c_offset; //cache中對象的大 ?。ㄈ绻髮ο蟮钠鹗嫉刂繁仨毷窃?l //內存對齊,就可以對這個(gè)大小取整)
lunsigned long c_magic; //從一組預先定義好的數值中選出的一個(gè)魔數,用于檢查高速緩存的當前狀態(tài)和一致性
lunsigned long c_inuse; /* kept at zero */ lkmem_slab_t *c_firstp; //slab鏈表表頭 lkmem_slab_t *c_lastp; //slab鏈表表尾
lspinlock_tc_spinlock;//多個(gè)處理器同時(shí)訪(fǎng)問(wèn)cache 時(shí)起保護作用的旋轉鎖 lunsigned long c_growing;lunsigned long c_dflags;//描述cache動(dòng)態(tài)特性的標志,例如內核是否正在為cache分配新的slab
size_t c_org_size; lunsigned longc_gfporder;//每個(gè)slab包含的連續頁(yè)面數目為2c_gfporder個(gè) lvoid (*c_ctor)(void *,kmem_cache_t *, unsigned long); //構造函數 lvoid (*c_dtor)(void*,kmem_cache_t*, unsigned long); //析構函數 lunsigned long c_align; //對象的對齊因子lsize_t c_colour; /* cache colouring range */ lsize_t c_colour_next;/*cache colouring */
unsigned long c_failures; lconst char *c_name; //cache名 lstructkmem_cache_s *c_nextp; l //指向下一個(gè)cache l kmem_cache_t *c_index_cachep; l//指向外部對象描述符所在的高速緩存 l //的高速緩存描述符 l};
高速緩存分為兩種類(lèi)型:通用和專(zhuān)用。前者只由slab分配器用于自己使用,后者由內核其余部分使用。 l 通用高速緩存是: l(1)第一個(gè)高速緩存包含由內核使用的其余高速緩存的高速緩存描述符。cache_cache變量包含第一個(gè)高速緩存的描述符。 l(2)第二個(gè)高速緩存包含沒(méi)有存放在slab內的slab描述符。cache_slabp變量指向第二個(gè)高速緩存描述符。
(3)13個(gè)高速緩存包含幾何分布的內存區。cache_sizes數組元素分別指向13個(gè)高速緩存描述符,與其相關(guān)的內存區大小為32,64,…,131072字節。 l系統初始化時(shí)調用kmem_cache_init和kmem_cache_sizes_init建立通用高速緩存,調用kmem_cache_create創(chuàng )建專(zhuān)用高速緩存。
slab的數據結構
typedef struct kmem_slab_s { struct kmem_bufctl_s*s_freep; //指向slab //中第一個(gè)空閑對象的指針 struct kmem_bufctl_s *s_index;//指向外//部對象描述符所在的內存區 unsigned long s_magic; unsigned long s_inuse;//slab中當前分配//的對象數目 struct kmem_slab_s *s_nextp;//指向同一 //個(gè)cache中與該slab相鄰的兩個(gè)slab的指針struct kmem_slab_s *s_prevp;//構成雙向鏈表
l unsigned long _offset:slab_offset_bits, l s_dma:1; l} kmem_slab_t;
對象的數據結構l對象數據結構如下: ltypedef struct kmem_bufctl_s { l union { l structkmem_bufctl_s *buf_nextp; l //指向下一個(gè)空閑對象 l kmem_slab_t *buf_slabp;//對象被分配, l //且對象描述符在外面,指向對象所在的 l //slab的slab描述符 l void * buf_objp;//對象被分配,且對象描 l //述符在外面,則指向該對象 l } u; l} kmem_bufctl_t;
對象描述符可以存放在slab內部,緊接描述符所描述的對象之后;如果存放在slab之外,存放在由cache_sizes指向的一個(gè)通用高速緩存中。對象本身所在的高速緩存,通過(guò)cache的c_index_cachep和slab的s_index兩個(gè)域被連到它們的描述符所在的高速緩存。
slab分配器所管理的對象可以在內存對齊,也就是說(shuō),存放它們的起始物理地址是一個(gè)給定常數的倍數,這個(gè)常數叫做對齊因子,存放在cache的c_align域中。存放對象大小的c_offset域要考慮增加填充字節數來(lái)對齊。如果對象大小大于高速緩存行的一半,就在ram中把這個(gè)對象的大小對齊到ll_cache_bytes的倍數,也就是行的開(kāi)始。否則,這個(gè)對象的大小就是ll_cache_bytes的因子取整。
slab著(zhù)色的產(chǎn)生背景:由于相同大小的對象很可能存放于高速緩存的相同的偏移量處,在不同slab內具有相同偏移量的對象最終可能映射在同一高速緩存行中。slab分配器使用slab著(zhù)色的策略解決這個(gè)問(wèn)題。slab分配器利用空閑未用的字節來(lái)對slab著(zhù)色,可用顏色個(gè)數為空閑字節數/c_align+1。
如果用顏色col對一個(gè)slab著(zhù)色,那末,第一個(gè)對象偏移量(相對于slab起始地址)等于col*c_align。這個(gè)值存放在slab的s_offset域。
【圖】
slab內存分配機制:
作用:分配內核內存(小內存)
幾何分布的內存區大小
內碎片小于50%
32-131072 共13級
1. 所存放數據類(lèi)型影響分配區
2. 內核函數傾向于反復請求同一類(lèi)型內存區
3. 請求根據發(fā)生頻率分類(lèi)
4. 引入對象若不是按幾何分布,可以用硬件高速緩存
5. 盡量限制使用伙伴系統
3種相關(guān)數據結構
slab_s 一個(gè)slab塊的管理信息
list: slab鏈表
colouroff: 染色偏移
s_mem: 指向第一個(gè)對象
inuse: 活動(dòng)的對象數(已分配)
free: 第一個(gè)空閑對象號
kmem_bufctl_t 其實(shí)就是unsigned int,形成數組,用于標識slab中的每個(gè)對象是否已用,并形成空閑對象鏈
kmeme_cache_s 緩存塊管理信息
slabs
firstnotfull
objsize 對象大小
flags
num 一個(gè)slab中對象個(gè)數
spinlock
gfporder每個(gè)slab的頁(yè)面數的對數
colour colour_off colour_next 染色相關(guān)變量
slabp_cache 對off slab方式使用,指鄉向公共的cache_slabp,它存放每個(gè)slab的slab_t
構造,析構函數
name
next
結構:在cache_cache中分配,存放所有的cache,這些cache中,有cache_slabp,專(zhuān)門(mén)分配offslab模式的slab_t與kmem_bufctl_t數組,也有由cache_sizes指針數組所指向的一些通用cache,另有一些獨立的專(zhuān)用cache
每個(gè)cache管理一組slab,每個(gè)slab管理一組對象,這些slab中的對象大小,個(gè)數,都相同,占用的空間也相同,只是存放的起示位置不同N兩種方式:
on slab 小對象用,<512
偏移 | slab_t | kmem_bufctl_t數組 | 對象 | 剩余空間
off slab >=512 slab_t kmem_bufctl_t數組都由cache_slabp統一分配,于是,結構為
染色機制:
頁(yè)的統一偏移,可能映射到硬件cache的同一塊,而每個(gè)slab中的開(kāi)始部分,是訪(fǎng)問(wèn)頻率最高的,所以使用染色機制,使每個(gè)slab中起始部分偏移不一樣,從而充分利用硬件cache
cache結構中的colour為最大偏移調整計數,colour_off為每次調整的數值,colour_next為計數值,0到colour
對齊
字對齊,高速緩存行對齊
緩存區創(chuàng )建
kmem_cache_create()
1. 錯誤檢查
2. 從cache_cache中分配一個(gè)對象做為新建的這個(gè)cache
3. 完成對齊,字對齊,高速緩存行對齊
4. >512,選off slab方式,反之為on slab
5. 找出一個(gè)slab的大?。?yè)的倍數)滿(mǎn)足3個(gè)條件,至少可以容納一個(gè)對象,內存浪費不至于太大,slab塊不至于太大
6. 可能因為剩余空間夠大,能放入slab_t和kmem_bufctl_t,于是改回on slab方式
7. 設置染色相關(guān)數據。colour與剩余空間大小有關(guān)
8. 設置參數,slab鏈表構造(指向自身),firstnotfull指向這個(gè)slab
9. 將這個(gè)cache插入cache_sizes的指針表,并插入管理chche的鏈表cache_chain
緩存區釋放,擴充與收縮
對象分配與釋放
內核空間
2005-07-07
物理內存區:
簡(jiǎn)單的說(shuō):可以減去一個(gè)偏移或使用virt_to_phys()函數完成。
由get_free_page或kmalloc函數所分配的連續內存都陷于物理映射區域,所以它們返回的內核虛擬地址和實(shí)際物理地址僅僅是相差一個(gè)偏移量(page_offset),你可以很方便的將其轉化為物理內存地址,同時(shí)內核也提供了virt_to_phys()函數將內核虛擬空間中的物理映射區地址轉化為物理地址。要知道,物理內存映射區中的地址與內核頁(yè)表是有序對應,系統中的每個(gè)物理頁(yè)框都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。
虛擬內存分配區
它的問(wèn)題相對要復雜些,這是因為其分配的內核虛擬內存空間并非直接操作頁(yè)框,而是分配的是vm_struct結構。該結構邏輯上連續但對應的物理內存并非連續,也就是說(shuō)它vamlloc分配的內核空間地址所對應的物理地址并非可通過(guò)簡(jiǎn)單線(xiàn)性運算獲得。
如何映射?
每個(gè)非連續區的描述符
struct vm_struct{ n unsigned long flags;
void *addr;
unsigned long size;
struct vm_struct *next; n};
vmlist
物理內存區 8m 內存區4k內存區4k
vm_struct->get_vm_area()
nstruct vm_struct *get_vm_area(unsigned long size)
{
};
函數首先調用kmalloc()為描述符分配一個(gè)內存區,然后查找一個(gè)可用的的線(xiàn)性地址區間,如果存在,就初始化這個(gè)描述符的所有域,返回內存區的起始地址。
vm_struct->get_vm_area->vmalloc()
分配內存區
void * vmalloc(unsigned long size)
{ n void *addr;
struct vm_struct *area;
size=(size+page_size-1)&page_mask;
if(!seze||size>(num_physpages<<page_shift))
return null;
area=get_vm_area(size);
if(!area)
return null;
if (vmalloc_area_pages(unsigned long)addr,size)
{
vfree(addr);
return null;
}
return addr;
}
vm_struct->get_vm_area()->vmalloc()->vmalloc_area_pages()
inline int vmalloc_area_pages(unsigned long address,unsigned long size,int gfp_mask,pgprot_t prot)
{
pgd_t *dir;
unsigned long end=address+size;
int ret;
dir=pgd_offset_k(address);//導出內存區起始地址在頁(yè)目錄中的目錄項;
spin_lock(&init_mm.page_table_lock);
do{ pmd_t *pmd;
pmd=pmd_alloc(&init_mm,dir,address);//為新的內存區創(chuàng )建一個(gè)中間頁(yè)目錄項
ret=-enomem;
if(alloc_area_pmd(pmd,address,end-address,gfp_mask,prot)) n break;
address=(address+pgdir_size)&pgdir_mask;
dir++; n ….
}while(address&& (address<end)); n nreturn ret; n}nalloc_area_pmd為新的中間頁(yè)目錄分配所有相關(guān)頁(yè)表,并更新頁(yè)的總目錄,調用pte_alloc_kernel()函數來(lái)分配一個(gè)新的頁(yè)表,之后調用alloc_area_pte()為頁(yè)表項分配具體的物理頁(yè)面 n
至此,完成了非連續內存區到物理頁(yè)面的映射
固定映射
vaddr=_fix_to_virt(_end_of_fixed_addresses-1)&pmd_mask; nfixrange_init(vaddr,0,pgdbase);
枚舉類(lèi)型_end_of_fixed_addresses用作索引, _fix_to_virt宏返回給定索引的虛地址。函數fixrange_init()為這些虛地址創(chuàng )建合適的頁(yè)表項,再由set_fixmap()函數完成地址的映射
vaddr=pkmap_base(0xfe000000)4g-32mb
fixrange_init(vaddr,vaddr+page_size*last_pkmap,pgd_base);
pgd=swapper_pg_dir+_pgd_offset(vaddr);
pmd=pmd_offset(pgd,vaddr);
pte=pte_offset(pmd,vaddr);
highmem映射nlinux內核使用highmem接口將高位內存動(dòng)態(tài)映射到內核地址空間的一小部分(這部分就是4m專(zhuān)門(mén)保留用來(lái)實(shí)現這個(gè)目的),以此提供間接訪(fǎng)問(wèn)。這部分內核地址空間就是所謂的kmap段
include/linux/highmem.h
void *kmap(struct page *page)
{ return page_address(page);}
#define page_address(page) ((page)->virtual)
管理區zone
為了對物理頁(yè)面進(jìn)行有效的管理,linux把物理頁(yè)面劃為3個(gè)區
專(zhuān)供dma使用的zone_dma區(小于16mb)
常規的zone_normal區(大于16mb小于896mb)
內核不能直接映射的zone_highme區(大于896mb)
【圖】
n2)在標準配置下,物理區最大長(cháng)度為896m,系統的物理內存被順序映射在物理區中,在支持擴展頁(yè)長(cháng)(pse)和全局頁(yè)面(pge)的機器上,物理區使用4m頁(yè)面并作為全局頁(yè)面來(lái)處理.當系統物理內存大于896m時(shí),超過(guò)物理區的那部分內存稱(chēng)為高端內存,低端內存和高端內存用highmem_start_page變量來(lái)定界,內核在存取高端內存時(shí)必須將它們映射到"高端頁(yè)面映射區".
3)linux保留內核空間最頂部128k區域作為保留區,緊接保留區以下的一段區域為專(zhuān)用頁(yè)面映射區,它的總尺寸和每一頁(yè)的用途由fixed_address枚舉結構在編繹時(shí)預定義,用__fix_to_virt(index)可獲取專(zhuān)用區內預定義頁(yè)面的邏輯地址.在專(zhuān)用頁(yè)面區內為每個(gè)cpu預定義了一張高端內存映射頁(yè),用于在中斷處理中高端頁(yè)面的映射操作.(高端內存映射區屬于固定內存區的一種,見(jiàn)下頁(yè)代碼)
4)距離內核空間頂部32m, 長(cháng)度為4m的一段區域為高端內存映射區,它正好占用1個(gè)頁(yè)幀表所表示的物理內存總量,它可以緩沖1024個(gè)高端頁(yè)面的映射.在物理區和高端映射區之間為虛存內存分配區, 用于vmalloc()函數,它的前部與物理區有8m隔離帶,后部與高端映射區有8k的隔離帶
enum fixed_addresses
{
#ifdef config_x86_local_apic nfix_apic_base, /* local (cpu) apic) -- required for smp or not */
#endif
#ifdef config_x86_io_apic nfix_io_apic_base_0, nfix_io_apic_base_end = fix_io_apic_base_0 + max_io_apics-1,
#endif
#ifdefconfig_x86_visws_apic nfix_co_cpu, /* cobalt timer */ fix_co_apic, /*cobalt apic redirection table */ nfix_li_pcia, /* lithium pci bridge a*/ nfix_li_pcib, /* lithium pci bridge b */
#endif
#ifdefconfig_highmem nfix_kmap_begin, /* reserved pte’s for temporary kernelmappings */ nfix_kmap_end = fix_kmap_begin+(km_type_nr*nr_cpus)-1
#endif n__end_of_fixed_addresses
在一般情況下,linux在初始化時(shí),總是盡可能的將所有的物理內存映射到內核地址空間中去。如果內核地址空間起始于0xc0000000,為vmalloc保留的虛擬地址空間是128m,那么最多只能有(1g-128m)的物理內存直接映射到內核空間中,內核可以直接訪(fǎng)問(wèn)。如果還有更多的內存,就稱(chēng)為高端內存,內核不能直接訪(fǎng)問(wèn),只能通過(guò)修改頁(yè)表映射后才能進(jìn)行訪(fǎng)問(wèn)。
內存分區可以使內核頁(yè)分配更加合理。當系統物理內存大于1g時(shí),內核不能將所有的物理內存都預先映射到內核空間中,這樣就產(chǎn)生了高端內存,高端內存最適于映射到用戶(hù)進(jìn)程空間中。預映射的部分可直接用于內核緩沖區,其中有一小塊可用于dma操作的內存,留給dma操作分配用,一般不會(huì )輕易分配。內存分區還可以適應不連續的物理內存分布,是非一致性?xún)却娲嫒◇w系(numa)的基礎
先看看代碼中的注釋:
in linux\include\linux\mmzone.h(version 2.4.16, line 67)
/*
* on machines where it is needed (eg pcs) we divide physical memory
* into multiple physical zones. on a pc we have 3 zones:
*
* zone_dma < 16 mb isa dma capable memory
* zone_normal16-896 mb direct mapped by the kernel
* zone_highmem > 896 mb only page cache and user processes
*/ n高端頁(yè)面的映射
1)
高端物理頁(yè)面共享一塊4m的映射區域,該區域對齊于4m頁(yè)邊界,并用一張頁(yè)表(pkmap_page_table)來(lái)完成映射操作。高端頁(yè)面的映射地址由其頁(yè)結構中virtual成員給出。
(void *virtual)(page結構)
2)
高端映射區邏輯頁(yè)面的分配結構用分配表(pkmap_count)來(lái)描述,它有1024項,對應于映射區內不同的邏輯頁(yè)面。當分配項的值等于零時(shí)為自由項,等于1時(shí)為緩沖項,大于1時(shí)為映射項。映射頁(yè)面的分配基于分配表的掃描,當所有的自由項都用完時(shí),系統將清除所有的緩沖項,如果連緩沖項都用完時(shí),系統將進(jìn)入等待狀態(tài)。
3)
頁(yè)緩沖盡可能地使用高端頁(yè)面,當通過(guò)塊結構刷新高端頁(yè)面時(shí),系統會(huì )在提交塊設備<br>> ,原請求塊,同時(shí)中轉塊被釋放。
tlb(翻譯后援存儲器)
時(shí)間:2005-07-05
地點(diǎn):同濟大學(xué)科技園
by lxwpp Z7br" SS{ +f lV[ 本_資_料_來(lái)_源_于_貴_州_學(xué)_習_網(wǎng) 電腦課堂LINUX教程 Http://wwW.gzU521.coM )Z7br" SS{ +f lV
reson
由于頁(yè)表尺寸較大,因此許多分頁(yè)方案都只能把它保存在內存中,但這種設計對性能有很大的影響。(分頁(yè)的時(shí)候,系統性能下降2/3 n 計算機設計者們發(fā)現:大部分程序傾向于對較少的頁(yè)面進(jìn)行大量的訪(fǎng)問(wèn)。因此只有一小部分頁(yè)表項經(jīng)常被用到,其他的很少被使用。
concept
tlb使個(gè)小的虛擬尋址的緩存,不需要經(jīng)過(guò)頁(yè)表就能把虛擬地址轉換為物理地址的小的硬件設備
components
一個(gè)用來(lái)訪(fǎng)問(wèn)tlb的虛擬地址組成部分
【圖】
tlb寄存器是由虛擬頁(yè)號,有效位,保護位等組成
steps
1 cpu產(chǎn)生一個(gè)虛擬地址
2 mmu從tlb中取出相應的pte,如果命中轉3;如果不命中,進(jìn)行常規頁(yè)表查找,用找到的pte淘汰tlb中的一個(gè)條目
3 mmu將這個(gè)虛擬地址翻譯成物理地址并且把它發(fā)送到高速緩存/主存
4 高速緩存/主存將所有的請求的數據字節返回給cpu
figure1(tlb命中)
figure2(tlb不命中)
example
前提:虛擬地址14位,物理地址12位,頁(yè)面大小64字節,tlb共有16個(gè)條目,緩存16個(gè)組 n給定虛擬地址0x3d4
flush-software
軟件管理tlb
剛剛介紹的tlb故障處理都是由mmu硬件完成的。
現在的管理工作是由軟件完成的。tlb條目由os顯式裝入,在沒(méi)有命中的時(shí)候,mmu不是到頁(yè)表中找到并裝入信息而是產(chǎn)生一個(gè)故障交給os,os找到頁(yè)面,淘汰一個(gè)條目,裝入新條目 ntlb取一個(gè)合理的尺寸以減少不命中的頻率,software管理tlb的效率高 n性能提高:實(shí)行預裝入機制
essence
無(wú)論軟件管理還是硬件管理,在tlb沒(méi)命中的時(shí)候,都是對頁(yè)表執行索引操作找出所引用的頁(yè)面,然后把pte加入到tlb中。
flush
完成的工作:
(1)保證在任何時(shí)刻內存管理硬件所看到的進(jìn)程的內核映射和內核頁(yè)表一致
(2)如果負責內核管理的內核代碼對于用戶(hù)進(jìn)程頁(yè)面進(jìn)行了修改,那么用戶(hù)的進(jìn)程在被允許繼續執行前,要求必須在緩存中看到正確的數據
function
flush_cache_foo()
flush_tlb_foo()
地址空間改變前必須刷新緩存,防止緩存中存在非法的空映射。在刷新地址后,由于頁(yè)表的改變,必須刷新tlb以便硬件可以把新的頁(yè)表信息裝入tlb nvoid flush_cache_all(void)
void flush_tlb_all(void)
用來(lái)通知相應機制,內核地址空間的映射已經(jīng)被改變,意味著(zhù)所有的進(jìn)程都已經(jīng)改變
vfs概述 nvfs的的數據結構
超級塊(super_block)
索引節點(diǎn)(inode)
目錄項(dentry)
文件(file) n與進(jìn)程相關(guān)的文件結構 n
文件系統的安裝
n文件系統的注冊
根文件系統的安裝
普通文件系統的安裝
n目錄項的緩沖
路徑名到目標節點(diǎn)的轉換
vfs概述
虛擬文件系統(vfs)的所有數據結構都是在運行之后建立的,在卸載時(shí)刪除,而在磁盤(pán)上沒(méi)有這些數據結構。
vfs不是真正的文件系統。與vfs相對應的ext2,msdos等稱(chēng)為具體的文件系統
linux系統中vfs和具體文件系統關(guān)系圖
[圖]
vfs的作用:
對具體文件系統的數據結構進(jìn)行抽象,以一種統一的數據結構進(jìn)行管理
接受用戶(hù)層的系統調用,如read(),write(),open()等
支持多種具體文件系統之間的相互訪(fǎng)問(wèn)
接受內核其他子系統的操作請求,比如內存管理子系統,進(jìn)程管理子系統
超級塊(super_block)
對具體文件系統的超級塊是文件系統中最重要的數據結構,它用來(lái)描述整個(gè)文件系統信息【組織結構和管理信息】。不涉及文件系統的內容
vfs超級塊是各種具體文件系統在安裝時(shí)建立的,并在這些文件系統卸載時(shí)自動(dòng)刪除。
vfs超級塊實(shí)際上應該說(shuō)成是某個(gè)具體文件系統的vfs超級塊
超級塊對象由super_block結構組成
super_block結構主要包括以下一些域:
描述文件系統整體信息的域
用于管理超級塊的域 n
與具體文件系統相聯(lián)系的域
描述文件系統整體信息的域:
kdev_t s_dev 具體文件系統的塊設備標識符 n
unsigned long s_blocksize 具體文件系統的數據塊大小
unsigned char s_blocksize_bits
塊大小占用的位數,例如,塊大小1024字節,則該值為10
unsigned long long s_maxbytes 文件最大長(cháng)度
unsigned long s_flags 超級塊標識
unsigned long s_magic 魔數
用于管理超級塊的域:
struct list_head s_list 用于形成超級塊鏈表
struct semaphore s_lock 鎖
struct rw_semaphore s_umount 讀寫(xiě)超級塊時(shí)進(jìn)行同步
unsigned char s_dirt 該超級塊修改標識
struct dentry *s_root 指向該文件系統安裝目錄的dentry結構
int s_count 對超級塊的使用記數
struct list_head s_dirty “臟”索引節點(diǎn)鏈表的頭節點(diǎn)
struct list_head s_locked_inodes 要同步的索引節點(diǎn)鏈表的頭節點(diǎn)
struct list_head s_files 該超級塊上的所有打開(kāi)文件鏈表頭節點(diǎn)
與具體文件系統相聯(lián)系的域:
struct file_system_type *s_type指向具體文件系統file_system_struct結構的指針 struct super_operations *s_op指向具體文件系統super_operations結構的指針 n struct dquot_operations *dq_op指向具體文件系統用于限額操作的dquot_operations結構的指針
union u 各種文件系統的特定信息。
超級塊鏈表
所有超級塊對象,以雙向循環(huán)鏈表的形式連接在一起,鏈表頭節點(diǎn)保存在super_blocks變量(定義在fs/super.c):
[圖]
索引節點(diǎn)對象(inode)
文件系統處理文件所需的信息
索引節點(diǎn)對文件是唯一的
具體文件系統的索引節點(diǎn)存儲在磁盤(pán)上,使用的時(shí)候,必須調入內存,填寫(xiě)vfs的索引節點(diǎn)。所以vfs的索引節點(diǎn)是動(dòng)態(tài)節點(diǎn)。
inode結構主要包括以下一些域
描述文件信息的域
用于索引節點(diǎn)管理的域
用于索引節點(diǎn)操作的域
其他
kdev_t i_dev 設備標識號
umode_t i_mode 文件類(lèi)型和訪(fǎng)問(wèn)權限
link_t i_nlink 與該索引節點(diǎn)建立的連接數
uid_t i_uid 文件所有者號
gid_t i_gid 所有者所在組號
kdev_t i_rdev 實(shí)際設備號
off_t i_size 文件大?。ㄗ止潱?
unsigned long i_blksize 塊大小
unsigned long i_blocks 該文件占用塊數
time_t i_atime 文件最后訪(fǎng)問(wèn)時(shí)間
time_t i_mtime 文件最后修改時(shí)間
time_t i_ctime 節點(diǎn)修改時(shí)間
unsigned long i_version 版本號
struct semaphore i_zombie 僵死索引節點(diǎn)的信號量 JCV x8c !y!h-[轉 貼 于 我 的 學(xué) 習 網(wǎng) 電腦課堂LINUX教程 HTtp://wwW.gzU521.coM)JCV x8c !y!h-
用于索引節點(diǎn)管理的域
struct list_head i_hash 形成哈希鏈表
struct list_head i_list 形成索引節點(diǎn)鏈表
struct list_head i_dentry 目錄項鏈表的頭節點(diǎn)
用于索引節點(diǎn)操作的域:
struct inode_operations *i_op 索引節點(diǎn)的操作
struct super_block *i_sb 指向該文件系統的超級塊
atomic_t i_count 使用該節點(diǎn)的進(jìn)程數
struct file_operations *i_fop 文件操作
unsigned char i_lock 用于同步操作,鎖定節點(diǎn)
struct semaphore i_sem 用于同步的信號量
wait_queue_head_t *i_wait 指向索引節點(diǎn)等待隊列
unsigned char i_dirt 修改標識
struct file_lock *i_flock 指向文件加鎖鏈表的指針
struct dquot *i_dquot[maxquotas] 索引節點(diǎn)的磁盤(pán)限額
其它域
unsigned long i_state 索引節點(diǎn)的狀態(tài)標識
unsigned int i_flags 文件系統的安裝標識
atomic_t i_writecount 寫(xiě)進(jìn)程的引用計數
unsigned int i_attr_flags 文件創(chuàng )建標識
union u 共同體
索引節點(diǎn)鏈表
索引節點(diǎn)至少在下列鏈表之一:
(1)未用索引節點(diǎn)鏈表 鏈表頭節點(diǎn)保存在inode_unused變量,i_count=0; n (2)正在使用索引節點(diǎn)鏈表 鏈表頭節點(diǎn)保存在inode_in_use變量,i_count>0, i_nlink>0
(3)每個(gè)超級塊的臟索引節點(diǎn)鏈表
鏈表頭節點(diǎn)保存在相應超級塊的s_dirty,i_count>0, i_nlink>0, i_state & i_dirty
未用索引節點(diǎn)鏈表[圖]
正在使用索引節點(diǎn)鏈表[圖]
臟索引節點(diǎn)鏈表[圖]
目錄項(dentry)
每個(gè)文件除了有一個(gè)索引節點(diǎn)結構外,還有目錄項dentry結構。
dentry結構代表的是邏輯意義上的文件,在磁盤(pán)上沒(méi)有對應的映象。而inode結構代表的是物理意義上的文件,對于一個(gè)具體的文件系統,在磁盤(pán)上有對應的映象。
一個(gè)dentry結構必有一個(gè)inode結構,而一個(gè)inode可能對應多個(gè)dentry結構。
由于從磁盤(pán)讀入一個(gè)文件并構造相應的目錄項需要花費大量的時(shí)間,而在完成對目錄項的操作后,可能后面還會(huì )用到,所以在內存中要保留它。
dentry結構定義在include/linux/dcache.h中
dentry的域
atomic_t d_count 目錄項引用計數
unsigned int d_flags 目錄項標識
struct inode * d_inode 與文件名關(guān)聯(lián)的索引節點(diǎn)
struct dentry * d_parent 父目錄的目錄項
struct list_head d_hash 形成目錄項哈希鏈表
struct list_head d_lru 未使用的目錄項鏈表
struct list_head d_child 形成父目錄的子目錄項鏈表
struct list_head d_subdirs 子目錄鏈表的頭節點(diǎn)
struct list_head d_alias 形成索引節點(diǎn)別名鏈表
int d_mounted 該目錄項安裝文件系統數目
struct qstr d_name 目錄項名(快速查找)
unsigned long d_time 由d_revalidate函數使用
struct dentry_operations *d_op 目錄項操作函數
struct super_block * d_sb 所在文件系統的
void * d_fsdata 具體文件系統的數據,目前只為nfs所用
unsigned char d_iname[dname_inline_len] 短文件名,24個(gè)字符
目錄項的狀態(tài)
每個(gè)目錄項屬于以下4種狀態(tài)之一:
空閑狀態(tài):目錄項不包含有效信息,未被vfs使用,由slab分配器進(jìn)行管理。
未使用狀態(tài):目錄項還沒(méi)有被使用,d_count為null,但是其d_inode仍然指向相關(guān)的索引節點(diǎn)。該目錄項包含有效信息,但必要時(shí),其內容可以被丟棄。
正在使用狀態(tài):目錄項正在被使用,d_count>0,d_inode指向相關(guān)的索引節點(diǎn),包含有效信息,不能被丟棄。
負狀態(tài):與目錄項相關(guān)索引節點(diǎn)已不存在,d_inode為null
未用目錄項鏈表
目錄項哈希管理
父子目錄關(guān)系
目錄項與索引節點(diǎn)
file對象
在include/linux/fs.h中定義了file結構,用于保存文件位置等對文件的操作信息。描述的是進(jìn)程怎樣與一個(gè)打開(kāi)文件的交互過(guò)程
file對象是在文件打開(kāi)描述符創(chuàng )建。
多個(gè)file結構可以指向同一個(gè)文件。
file域
struct file { /* 定義在include/linux/fs.h */
struct list_head f_list; /*形成鏈表*/
struct dentry *f_dentry; /*文件對應的dentry結構*/
struct vfsmount *f_vfsmnt; /*文件所在文件系統的vfsmount結構*/
struct file_operations *f_op; /*文件操作函數結構*/
atomic_t f_count; /*引用計數*/ n unsigned int f_flags; /*文件標識*/
mode_t f_mode; /*文件打開(kāi)方式*/
loff_t f_pos; /*文件當前位置*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; n /*預讀標識、預讀最多頁(yè)面數,上次預讀后的文件指針、預讀字節數、預讀頁(yè)面數*/
struct fown_struct f_owner; /*異步i/o消息的文件擁有者*/
unsigned int f_uid, f_gid; /*賦予當前進(jìn)程的uid和gid*/
int f_error; /*網(wǎng)絡(luò )寫(xiě)操作的錯誤碼*/
unsigned long f_version; /*版本號*/
void *private_data; /*文件私有數據區*/
file 對象
每個(gè)文件對象總是包含在下列鏈表之一:
以打開(kāi)的文件鏈表:超級塊的s_files作為頭節點(diǎn),鏈入屬于該文件系統的已打開(kāi)的文件
“未使用”文件對象鏈表:既可以用作文件對象的內存高速緩存,又可以當作超級用戶(hù)的備用存儲器,即使系統動(dòng)態(tài)內存用完,也允許超級用戶(hù)打開(kāi)文件。
在這個(gè)鏈表中的對象,f_count總是null,鏈表首元素地址放在free_list(定義在fs/file_table.c中)。該鏈表至少包含nr_reserved_files(定義在include/linux/fs.h,默認為10)個(gè)對象。 n
新分配文件對象鏈表:每個(gè)元素至少有一個(gè)進(jìn)程使用,f_count!= n null,首元素地址存放在anon_list(定義在fs/file_table.c中)。
當需要分配一個(gè)新文件對象時(shí),調用get_empty_filp (定義在fs/ file_table.c中) ,如果未使用鏈表中的元素個(gè)數多于nr_reserved_files,則為新文件使用其中一個(gè)元素,否則進(jìn)入正常內存分配。
文件系統打開(kāi)的文件鏈表
與進(jìn)程相關(guān)的文件結構
files_struct
fs_struct
files_struct:進(jìn)程打開(kāi)文件表
對于每個(gè)進(jìn)程,包含一個(gè)files_struct結構,用來(lái)記錄文件描述符的使用情況,定義在include/linux/sched.h
files_struct
struct files_struct {
atomic_t count; /*使用該表的進(jìn)程數*/
rwlock_t file_lock; /*鎖*/
int max_fds; /*當前文件對象的最大數*/
int max_fdset; /*當前文件描述符最大數*/
int next_fd; /*數值最小的最近關(guān)閉文件的文件描述符*/
struct file ** fd; /*指向文件對象數組的指針*/
fd_set *close_on_exec; /*指向執行exec時(shí)需要關(guān)閉的文件描述符*/
fd_set *open_fds; /*指向文件描述符屏蔽字集合*/
fd_set close_on_exec_init;/*執行exec時(shí)需要關(guān)閉的文件描述符初值集合*/
fd_set open_fds_init; /*文件描述符的屏蔽字集合*/
struct file * fd_array[nr_open_default];/*文件對象指針數組*/
}; /*nr_open_default=32*/
fd指向文件對象的指針數組,數組長(cháng)度存放在max_fds中。通常,fd指向fd_array,fd_array大小為32。如果進(jìn)程打開(kāi)的文件數多于32,內核就分配一個(gè)新的更大的文件指針數組,其地址放入fd,同時(shí)更新max_fds。
open_fds指向open_fds_init,open_fds_init表示當前打開(kāi)文件的文件描述符屏蔽字。max_fdset存放屏蔽字的位數,默認是1024,需要的時(shí)候可以通過(guò)expand_fdset( )函數擴展到1024*1024。
可以通過(guò)fget( )函數,將文件對象與描述符鏈接起來(lái)
文件系統信息fs_struct
用來(lái)描述文件系統信息的fs_struct結構,定義在include/linux/fs_struct.h:
struct fs_struct {
atomic_t count; /*使用該結構的進(jìn)程數*/
rwlock_t lock; /*讀寫(xiě)鎖*/
int umask; /*初始文件許可權*/
struct dentry * root, * pwd, * altroot;
/*根目錄、當前目錄、替換根目錄的目錄項*/
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
/*根目錄、當前目錄、替換根目錄的vfsmount結構*/ n};
umask由umask( )系統調用使用,文件打開(kāi)的實(shí)際權限為期望的權限&umask。 n
該結構的一個(gè)作用是用于路徑定位時(shí),提供根目錄、當前目錄和替換根目錄。
與進(jìn)程有關(guān)的文件結構間的關(guān)系
文件系統注冊
文件系統是指可能會(huì )被掛載到目錄樹(shù)中的各個(gè)實(shí)際文件系統
注冊過(guò)程實(shí)際上將表示各實(shí)際文件系統的 struct file_system_type 數據結構的實(shí)例化,然后形成一個(gè)鏈表,內核中用一個(gè)名為 file_systems 的全局變量來(lái)指向該鏈表的表頭
file_system_type
struct file_system_type {
const char *name; /*文件系統名,出現在/proc/filesystems中,且唯一*/
int fs_flags; /*文件系統標識*/
struct super_block *(*read_super) (struct super_block *, void *, int); /*讀取該文件系統在設備上的超級塊*/
struct module *owner; /*如果是由模塊載入的,則指向該模塊;否則,為null*/ n
struct file_system_type * next; /*用于形成注冊文件系統鏈表*/
struct list_head fs_supers; /*超級塊鏈表*/
文件系統的安裝
根文件系統的安裝
1)建立臨時(shí)的inode和file對象,在其中填入信息,如設備號,根據文件系統安裝標志設置file的f_mode域等
->調用blkdev_open()函數,完成設備的準備工作
->順序搜索表file_systems中的每一個(gè)注冊的文件系統,用自己的read_super函數讀取root_dev超級塊,如果成功,獲得根文件系統的超級塊和根目錄的inode和其目錄結構
->把pwd設置為根目錄的目錄項對象
->調用add_vfsmnt()把第一個(gè)元素插入到已安裝文件系統鏈表中
安裝常規文件系統
系統安裝了根文件系統后,就可以安裝其他文件系統。每個(gè)文件系統安裝在系統目錄樹(shù)的一個(gè)目錄上。
安裝一個(gè)文件系統可以通過(guò)系統調用mount實(shí)現。
mount系統調用在內核的實(shí)現函數是sys_mount( ) 。(fs/namespace.c)
整個(gè)安裝過(guò)程中最核心的函數就是do_kern_mount()了,為了創(chuàng )建一個(gè)新安裝點(diǎn)(vfsmount),該函數需要做一下幾件事情:
1 檢查安裝設備的權利,只有root權限才有能力執行該操作。
2 get_fs_type()在文件鏈表中取得相應文件系統類(lèi)型(注冊時(shí)被填加到練表中)。
3 alloc_vfsmnt()調用slab分配器為vfsmount結構體分配存儲空間,并把它的地址存放在mnt局部變量中。
4 初始化mnt->mnt_devname域
5 5 分配新的超級塊并初始化它。do_kern_mount()檢查file_system_type描述符中的標志以決定如何進(jìn)行如下操作:根據文件系統的標志位,選擇相應的方法讀取超級塊(比如對ext2,romfs這類(lèi)文件系統調用get_sb_dev();對于這種沒(méi)有實(shí)際設備的虛擬文件系統如ramfs調用get_sb_nodev())——讀取超級塊最終要使用文件系統類(lèi)型中的read_super方法
目錄項緩存
引言
/home/user/src/foo.c
每次對foo.c訪(fǎng)問(wèn),vfs都要沿著(zhù)嵌套目錄依次解析全部路徑:/、home、user、src和foo.c
為了避免每次訪(fǎng)問(wèn)該路徑名都要進(jìn)行此操作,vfs會(huì )在目錄項緩存中搜索路徑名,如果找到了就無(wú)需花費力氣解析
相反如果節點(diǎn)該目錄項緩存中不存在,vfs就必須解析,完畢后將目錄項對象加入到dcache中
目錄項緩存包括的主要部分
被使用的目錄項鏈表
該鏈表通過(guò)索引節點(diǎn)對象i_dentry項鏈接,因為一個(gè)給定的索引節點(diǎn)可能有多個(gè)鏈接,所以可能就有多個(gè)目錄項對象,因此用一個(gè)鏈表鏈接它們 最近使用的雙向鏈表
該鏈表含有未被使用的和負狀態(tài)的目錄項對象。由于該鏈以時(shí)間順序插入,所以最后釋放的目錄項對象放在鏈表首部,最近最少使用的目錄項對象總是靠近鏈表尾部。一旦目錄項的告訴緩存空間開(kāi)始變小的的時(shí)候,內核就從鏈表的尾部刪除元素,使得多數最近經(jīng)常使用的對象得以保留。哈希表和相應的哈希函數快速地將給定的路徑名解析未相關(guān)的目錄項對象。
哈希表石由數組dentry_hashtable表示,其中每一個(gè)元素都是指向一組具有相同鏈值的目錄項對象的指針。通過(guò)d_lookup()函數在緩存中查找,如果訪(fǎng)問(wèn)的對象不在目錄項高速緩存中,哈希函數返回一個(gè)空值
從路徑名到目標節點(diǎn)CSAyD VGuf3$",XY(本 文來(lái) 源于 我 的學(xué) 習網(wǎng)電腦課堂LINUX教程 htTP://WWw.GZu521.COm]CSAyD VGuf3$",XY
ext2
ext2磁盤(pán)布局在邏輯空間中的映象
超級塊
ext2超級塊是用來(lái)描述ext2文件系統整體信息的數據結構,是ext2的核心所在。它是個(gè)ext2_super_block數據結構
一些域的解釋
文件系統中并非所有的塊都可以使用,有些塊是保留的,給超級塊專(zhuān)用,塊的數目在i_r_blocks_count定義。一旦空閑塊總數等于保留塊,普通用戶(hù)就無(wú)法申請數據快了。如果保留塊也被使用,則系統就可能無(wú)法使用了。邏輯快是從0號開(kāi)始,對塊大小為1kb的文件系統,s_first_block為1,對其它文件系統,則為0s_log_block_size以2的冪次方表示塊的大小,單位1024字節ext2要定期檢查自己的狀態(tài),s_lastcheck記錄最近一次檢查狀態(tài)的時(shí)間,而s_checkinterval則規定了兩次檢查狀態(tài)的最大允許時(shí)間間隔 如果檢查到文件系統有錯誤,則對s_errors賦一個(gè)錯誤的值
ext2_sb_info
每個(gè)文件系統自己的特性信息
[圖]
ext2索引節點(diǎn)
[圖]
解釋:
邏輯塊->物理塊
ext2通過(guò)索引節點(diǎn)中的數據塊指針數組進(jìn)行邏輯塊到物理塊的映射。指針數組15項,前12個(gè)為直接塊指針,后3個(gè)分別為“一次間接塊指針”,“二次間接塊指針”,“三次間接塊指針”。
索引12中的元素包含一個(gè)塊的邏輯號,這個(gè)塊代表邏輯塊號的一個(gè)二級數組。數組對應的文件快號從12到b/4+11,b是文件系統塊的大小索引13中的元素包含一個(gè)塊的邏輯號,這個(gè)塊代表邏輯塊號的一個(gè)三級數組。數組對應的文件快號從b/4+12到(b/4)^2+(b/4)+11索引14中的元素包含一個(gè)塊的邏輯號,這個(gè)塊代表邏輯塊號的一個(gè)四級數組。數組對應的文件快號從(b/4)^2+(b/4)+12到(b/4)^3+(b/4)^2+(b/4)+11
組描述符
ext2_group_desc的數據結構
struct ext2_group_desc{ ? _u32 bg_block_bitmap;//塊位圖所在的塊號_u32 bg_inode_bitmap;索引節點(diǎn)位圖所在的塊號
_u32 bg_inode_table; 索引節點(diǎn)表的首塊號
_u16 bg_free_blocks_count;空閑塊號
_u16 bg_free_inodes_count; 空閑索引節點(diǎn)數
_u16 bg_used_dirs_count; 分配給目錄的節點(diǎn)數
_u16 bg_pad; 填充,對齊到字;
_32 [3] bg_reserved;用null填充12個(gè)字節 ?}
每個(gè)塊組都有一個(gè)相應的組描述符描述它,所有組描述符形成一個(gè)組描述符表,組描述符可能占多個(gè)數據塊。 作用大。一旦描述符破壞,整個(gè)組塊無(wú)法使用,再每個(gè)組塊中備份
位圖
數據塊位圖:每一位表示數據塊的使用情況,1表示已分配,0表示空閑 索引位圖也類(lèi)似
用高速緩存管理位圖塊 每個(gè)高速緩存最多同時(shí)只能裝入ext2_max_grop_loaded個(gè)位圖塊或索引塊采用類(lèi)似lru算法管理高速緩存ext2_sb_info中的四個(gè)域來(lái)管理這兩個(gè)高速緩存s_block_bitmap_number[]存放進(jìn)入高速緩存的塊號(塊組號),s_block_bitmap[]存放了相應塊在高速緩存中的地址
load_block_bitmap()調入指定的數據塊位圖
1)如果指定的塊組號大于塊組數,出錯,結束
2)通過(guò)搜索s_block_bitmap_number[]數組可知位圖塊是否進(jìn)入了高速緩存,如果進(jìn)入,則結束,否則,繼續 ?
3)如果塊組數不大于ext2_max_grop_loaded,高速緩存就可以同時(shí)裝入所有塊組數據塊位圖,無(wú)論采用什么算法,只要從s_block_bitmap_number[]找到一個(gè)空閑的元素,將塊組號寫(xiě)入,然后將位圖塊調入高速緩存,最后將它在高速緩存中的地址寫(xiě)入s_block_bitmap[]數組中
4)如果塊組數大于ext2_max_grop_loaded,則需要采用一下算法:
首先通過(guò)s_block_bitmap_number[]數組判斷高速緩存是否已滿(mǎn),若未滿(mǎn),則操作過(guò)程類(lèi)似上面的步驟,不同的事要將s_block_bitmap_number[]數組各元素后移一位,用空出的第一個(gè)元素存儲塊組號,s_block_bitmap[]做同樣的處理如果高速緩存已滿(mǎn),將s_block_bitmap[]數組最后一項所指的位圖塊從高速緩存中交換出去,然后調入指定的位圖塊,最后對這兩個(gè)數組操作類(lèi)似上面相同的操作
索引節點(diǎn)表
每個(gè)塊組中索引節點(diǎn)都存儲在各自的索引節點(diǎn)表中,并且按照索引節點(diǎn)號依次存儲。索引節點(diǎn)表一般要占好幾個(gè)數據塊 所有索引節點(diǎn)的大小,128字節。所以1024字節的數據塊可以包含8個(gè)索引節點(diǎn) 計算索引節點(diǎn)表占用的塊數:一個(gè)塊組中索引節點(diǎn)總數/每個(gè)塊中的索引節點(diǎn)數。
塊組中索引節點(diǎn)總數放在超級塊中的s_inode_per_group域
索引節點(diǎn)的域
_u16 i_mode 文件類(lèi)型和訪(fǎng)問(wèn)權限
_u16 i_uid 文件所有者的標志符 ?
_u32 i_size 以字節為單位的文件長(cháng)度
_u32 i_atime
_u32 i_ctime 時(shí)間信息 ?
_u32 i_dtime
_u16 i_links_count 硬鏈接計數器
_u32 i_blocks 文件的數據塊數
ext2目錄項
目錄是一種特殊的文件,它是由ext2_dir_entry這個(gè)結構組成的列表。 結構是變長(cháng)的,這樣可以減少磁盤(pán)空間的浪費。 長(cháng)度有限制:
文件名最長(cháng)只能為255個(gè)字符
一般自動(dòng)變成為4的倍數,不足補null 目錄中有文件和子目錄,每一項對應一個(gè)ext2_dir_entry
ext2_dir_entry結構
struct ext2_dir_entry{
_u32 inode; //節點(diǎn)號
_u16 rec_len; // 目錄項的長(cháng)度
_u8 name_len; //名字長(cháng)度
char name[ext2_name_len];//文件名 };
各類(lèi)文件使用數據塊
常規文件
在創(chuàng )建的時(shí)候是空的,不需要數據塊,只有在開(kāi)始有數據的時(shí)候才需要數據塊 目錄
ext2以一種特殊的文件實(shí)現了目錄,文件的數據塊存放了文件名和相應的索引節點(diǎn)號 符號鏈:如果路徑名不大于60個(gè)字符,就把它存放在索引節點(diǎn)的i_blocks域中,因此無(wú)需數據塊。否則就需要一個(gè)單獨的數據塊
設備文件,管道和套接字
不需要數據塊,所有必要的信息都存放在索引節點(diǎn)中。
ext2的文件類(lèi)型
文件類(lèi)型 描述
0 未知
1 正規文件
2 目錄
3 字符設備
4 塊設備
5 命名管道
6 套接字
7 符號鏈
創(chuàng )建索引節點(diǎn)
函數為ext2_new_inode()
參數為:dir,mode
新創(chuàng )建節點(diǎn)必須插到一個(gè)目錄中,參數dir指的是這個(gè)目錄的索引節點(diǎn)對象的地址 -----const struct inode *dir
mode指的是創(chuàng )建索引節點(diǎn)的類(lèi)型
步驟
調用get_empty_inode()分配一個(gè)新的索引節點(diǎn)對象分配一個(gè)新的索引節點(diǎn)對象,并sb=dir->i_sb;調用lock_super()獲得超級塊對象的互斥訪(fǎng)問(wèn)如果新創(chuàng )建的索引節點(diǎn)是目錄,,則要考慮將來(lái)是否能將其屬下的文件都容納在一個(gè)塊組中。所以應該找個(gè)其空閑索引節點(diǎn)的數量超過(guò)整個(gè)設備上的平均值這么一個(gè)塊組,而不惜離開(kāi)父節點(diǎn)所在的塊組,另起爐灶^_^如果新創(chuàng )建的節點(diǎn)是文件,首先考慮將其索引節點(diǎn)分配在其目錄所在的塊組,若無(wú)空閑索引節點(diǎn),則沿著(zhù)此塊組往下繼續查找。若還是沒(méi)有,則從第一個(gè)塊組從頭開(kāi)始查找。
確定了索引節點(diǎn)分配在哪個(gè)的塊組,就要從索引節點(diǎn)位圖中分配一個(gè)節點(diǎn),(調用load_inode_bitmap()),從中尋找第一個(gè)空位,這樣就得到了第一個(gè)空閑磁盤(pán)索引節點(diǎn)號 分配磁盤(pán)索引節點(diǎn):把索引節點(diǎn)位圖的相應位設置好,并把含有這個(gè)位圖的緩沖區標記為“臟”把塊描述符的bg_free_inodes_count域減1。如果新的索引節點(diǎn)是個(gè)目錄,則增加bg_used_dirs_count
把磁盤(pán)超級塊的s_free_inodes_count減1, 初始化索引節點(diǎn)的域,如設置i_ino,把新的索引節點(diǎn)插入到inode_hashtable。 調用mark_inode_dirty()把這個(gè)索引節點(diǎn)對象移到超級塊的臟索引節點(diǎn)鏈表調用unlock_super()釋放超級塊對象 返回新索引節點(diǎn)對象的地址
創(chuàng )建文件系統ext2
初始化超級塊和組描述符 檢查是否有缺陷的塊,如果有,創(chuàng )建有缺陷塊的鏈表對每個(gè)塊組,保留存放超級塊、組描述符、索引節點(diǎn)表以及兩個(gè)位圖所需要的磁盤(pán) 把索引節點(diǎn)位圖和每個(gè)塊組的數據映射位圖初始化為0初始化每個(gè)塊組的索引節點(diǎn)鏈表 創(chuàng )建/root目錄 創(chuàng )建lost+found目錄(有缺失和缺陷的) 更新這個(gè)兩個(gè)塊組中的位圖把有缺陷的塊組織起來(lái)放在lost+found目錄中

