13.5 main.c中的初始化

 head.s在最后部分調(diào)用main.c中的start_kernel()函數(shù),從而把控制權(quán)交給了它。所以啟動(dòng)程序從start_kernel()函數(shù)繼續(xù)執(zhí)行。這個(gè)函數(shù)是main.c乃至整個(gè)操作系統(tǒng)初始化的最重要的函數(shù),一旦它執(zhí)行完了,整個(gè)操作系統(tǒng)的初始化也就完成了。

如前所述,計(jì)算機(jī)在執(zhí)行start_kernel()前處已經(jīng)進(jìn)入了386的保護(hù)模式,設(shè)立了中斷向量表并部分初始化了其中的幾項(xiàng),建立了段和頁機(jī)制,設(shè)立了九個(gè)段,把線性空間中用于存放系統(tǒng)數(shù)據(jù)和代碼的地址映射到了物理空間的頭4MB,可以說我們已經(jīng)使386處理器完全進(jìn)入了全面執(zhí)行操作系統(tǒng)代碼的狀態(tài)。但直到目前為止,我們所做的一切可以說都是針對(duì)386處理器所做的工作,也就是說幾乎所有的多任務(wù)操作系統(tǒng)只要使用386處理器,都需要作這一切。而一旦start_kernel()開始執(zhí)行,Linux內(nèi)核的真實(shí)面目就步步的展現(xiàn)在你的眼前了。start_kernel()執(zhí)行后,你就可以以一個(gè)用戶的身份登錄和使用Linux了。

讓我們來看看start_kernel到底做了些什么,這里,通過介紹start_kernel()所調(diào)用的函數(shù),我們來討論start_kernel()的流程和功能。

我們仿照C語言函數(shù)的形式來進(jìn)行這種描述,不過請(qǐng)注意,真正的start_kernel()函數(shù)調(diào)用子函數(shù)并不象我們?cè)谙旅嫠鶎懙倪@樣簡單,畢竟這本書的目的是幫助你深入分析Linux。我們只能給你提供從哪兒入手和該怎么看的建議,真正深入分析Linux,還需要你自己來研究代碼。start_kernel()這個(gè)函數(shù)是在/init/main.c中,這里也只是將main.c中較為重要的函數(shù)列舉出來。

 

start_kernel()          /*定義于init/main.c */

{

……

setup_arch(); 

}

 它主要用于對(duì)處理器、內(nèi)存等最基本的硬件相關(guān)部分的初始化,如初始化處理器的類型(是在386,486,還是586的狀態(tài)下工作,這是有必要的,比如說,Pentium芯片支持4MB大小的頁,而386就不支持),初始化RAM盤所占用的空間(如果你安裝了RAM盤的話)等。其中,setup_arch()給系統(tǒng)分配了intel系列芯片統(tǒng)一使用的幾個(gè)I/O端口的口地址。

      

paging_init();    /*該函數(shù)定義于arch/i386/mm/init.c */

 

 它的具體作用是把線性地址中尚未映射到物理地址上的部分通過頁機(jī)制進(jìn)行映射。這一部分在本書第六章有詳細(xì)的描述,在這里需要特別強(qiáng)調(diào)的是,當(dāng)paging_init()函數(shù)調(diào)用完后,頁的初始化就整個(gè)完成了。

 

  trap_init(); /*該函數(shù)在arch/i386/kernel/traps.c中定義*/

  

這個(gè)初始化程序是對(duì)中斷向量表進(jìn)行初始化,詳見第四章。它通過調(diào)用set_trap_gate(或set_system_gate等)宏對(duì)中斷向量表的各個(gè)表項(xiàng)填寫相應(yīng)的中斷響應(yīng)程序的偏移地址。

事實(shí)上,Linux操作系統(tǒng)僅僅在運(yùn)行trap_init()函數(shù)前使用BIOS的中斷響應(yīng)程序(我們這里先不考慮V86模式)。一旦真正進(jìn)入了Linux操作系統(tǒng),BIOS的中斷向量將不再使用。對(duì)于軟中斷,Linux提供一套調(diào)用十分方便的中斷響應(yīng)程序,對(duì)于硬件設(shè)備,Linux要求設(shè)備驅(qū)動(dòng)程序提供完善的中斷響應(yīng)程序,而調(diào)用使用多個(gè)參數(shù)的BIOS中斷就被這些中斷響應(yīng)程序完全代替了。

另外,在trap_init()函數(shù)里,還要初始化第一個(gè)任務(wù)的LdtTSS,把它們填入Gdt相應(yīng)的表項(xiàng)中。第一個(gè)任務(wù)就是init_task這個(gè)進(jìn)程,填寫完后,還要把init_taskTSSLDT描述符分別讀入系統(tǒng)的TSSLDT寄存器。

 

init_IRQ()   /* arch/i386/kernel/irq.c中定義*/

 

 這個(gè)函數(shù)也是與中斷有關(guān)的初始化函數(shù)。不過這個(gè)函數(shù)與硬件設(shè)備的中斷關(guān)系更密切一些。

我們知道intel80386系列采用兩片8259作為它的中斷控制器。這兩片級(jí)連的芯片一共可以提供16個(gè)引腳,其中15個(gè)與外部設(shè)備相連,一個(gè)用于級(jí)連。可是,從操作系統(tǒng)的角度來看,怎么知道這些引腳是否已經(jīng)使用;如果一個(gè)引腳已被使用,Linux操作系統(tǒng)又怎么知道這個(gè)引腳上連的是什么設(shè)備呢在內(nèi)核中,同樣是一個(gè)數(shù)組(靜態(tài)鏈表)來紀(jì)錄這些信息的。這個(gè)數(shù)組的結(jié)構(gòu)在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;}

 

具體內(nèi)容請(qǐng)參見第四章。我們來看一個(gè)例子:

 

static void math_error_irqint cpl, void *dev_id, struct pt_regs *regs

   {

       outb0,0xF0;

       if ignore_irq13 || !hard_math 

           return;

       math_error();

   }

static struct  irqaction  irq13 = { math_error_irq, 0, 0 "math error", NULL NULL };

 

該例子就是這個(gè)數(shù)組結(jié)構(gòu)的一個(gè)應(yīng)用,這個(gè)中斷是用于協(xié)處理器的。在init_irq()這個(gè)函數(shù)中,除了協(xié)處理器所占用的引腳,只初始化另外一個(gè)引腳,即用于級(jí)連的2引腳。不過,這個(gè)函數(shù)并不僅僅做這些,它還為兩片8259分配了I/O地址,對(duì)應(yīng)于連接在管腳上的硬中斷,它初始化了從0x20開始的中斷向量表的15個(gè)表項(xiàng)(386中斷門),不過,這時(shí)的中斷響應(yīng)程序由于中斷控制器的引腳還未被占用,自然是空程序了。當(dāng)我們確切地知道了一個(gè)引腳到底連接了什么設(shè)備,并知道了該設(shè)備的驅(qū)動(dòng)程序后,使用setup_x86_irq這個(gè)函數(shù)填寫該引腳對(duì)應(yīng)的386的中斷門時(shí),中斷響應(yīng)程序的偏移地址才被填寫進(jìn)中斷向量表。

 

sched_init() /*/kernel/sched.c中定義*/

 看到這個(gè)函數(shù)的名字可能令你精神一振,終于到了進(jìn)程調(diào)度部分了,但在這里,你非但看不到進(jìn)程調(diào)度程序的影子,甚至連進(jìn)程都看不到一個(gè),這個(gè)程序是名副其實(shí)的初始化程序:僅僅為進(jìn)程調(diào)度程序的執(zhí)行做準(zhǔn)備。它所做的具體工作是調(diào)用init_bh函數(shù)(在kernel/softirq.c中)把timer,tqueueimmediate三個(gè)任務(wù)隊(duì)列加入下半部分的數(shù)組。

 

time_init()/*arch/i386/kernel/time.c中定義*/

時(shí)間在操作系統(tǒng)中是個(gè)非常重要的概念。特別是在Linux,Unix這種多任務(wù)的操作系統(tǒng)中它更是作為主線索貫穿始終,之所以這樣說,是因?yàn)闊o論進(jìn)程調(diào)度(特別是時(shí)間片輪轉(zhuǎn)算法)還是各種守護(hù)進(jìn)程(也可以稱為系統(tǒng)線程,如頁表刷新的守護(hù)進(jìn)程)都是根據(jù)時(shí)間運(yùn)作的。可以說,時(shí)間是他們運(yùn)行的基準(zhǔn)。那么,在進(jìn)程和線程沒有真正啟動(dòng)之前,設(shè)定系統(tǒng)的時(shí)間就是一件理所當(dāng)然的事情了。

我們知道計(jì)算機(jī)中使用的時(shí)間一般情況下是與現(xiàn)實(shí)世界的時(shí)間一致的。當(dāng)然,為了避開CIH,把時(shí)間跳過每月26號(hào)也是種明智的選擇。不過如果你在銀行或證交所工作,你恐怕就一定要讓你計(jì)算機(jī)上的時(shí)鐘與掛在墻上的鐘表分秒不差了。還記得CMOS嗎?計(jì)算機(jī)的時(shí)間標(biāo)準(zhǔn)也是存在那里面的。所以,我們首先通過get_cmos_time()函數(shù)設(shè)定Linux的時(shí)間,不幸的是,CMOS提供的時(shí)間的最小單位是秒,這完全不能滿足需要,否則CPU的頻率1赫茲就夠了。Linux要求系統(tǒng)中的時(shí)間精確到納秒級(jí),所以,我們把當(dāng)前時(shí)間的納秒設(shè)置為0。

完成了當(dāng)前時(shí)間的基準(zhǔn)的設(shè)置,還要完成對(duì)8259的一號(hào)引腳上的8253(計(jì)時(shí)器)的中斷響應(yīng)程序的設(shè)置,即把它的偏移地址注冊(cè)到中斷向量表中去。

 

parse_options()   /*main.c中定義*/

這個(gè)函數(shù)把啟動(dòng)時(shí)得到的參數(shù)如debuginit等等從命令行的字符串中分離出來,并把這些參數(shù)賦給相應(yīng)的變量。這其實(shí)是一個(gè)簡單的詞法分析程序。

 

console_init() /*linux/drivers/char/tty_io.c中定義*/

這個(gè)函數(shù)用于對(duì)終端的初始化。在這里定義的終端并不是一個(gè)完整意義上的TTY設(shè)備,它只是一個(gè)用于打印各種系統(tǒng)信息和有可能發(fā)生的錯(cuò)誤的出錯(cuò)信息的終端。真正的TTY設(shè)備以后還會(huì)進(jìn)一步定義。

 

kmalloc_init() /*linux/mm/kmalloc.c中定義*/

kmalloc代表的是kernel_malloc的意思,它是用于內(nèi)核的內(nèi)存分配函數(shù)。而這個(gè)針對(duì)kmalloc初始化函數(shù)用來對(duì)內(nèi)存中可用內(nèi)存的大小進(jìn)行檢查,以確定kmalloc所能分配的內(nèi)存的大小。所以,這種檢查只是檢測(cè)當(dāng)前在系統(tǒng)段內(nèi)可分配的內(nèi)存塊的大小,具體內(nèi)容參見第六章內(nèi)存分配與回收一節(jié)。

 

下面的幾個(gè)函數(shù)是用來對(duì)Linux的文件系統(tǒng)進(jìn)行初始化的,為了便于理解,這里需要把Linux的文件系統(tǒng)的機(jī)制稍做介紹。不過,這里是很籠統(tǒng)的描述,目的只在于使我們對(duì)初始化的解釋工作能進(jìn)行下去,詳細(xì)內(nèi)容參見第八章的虛擬文件系統(tǒng)。

虛擬文件系統(tǒng)是一個(gè)用于消滅不同種類的實(shí)際文件系統(tǒng)間(相對(duì)于VFS而言,如ext2,fat等實(shí)際文件系統(tǒng)存在于某個(gè)磁盤設(shè)備上)差別的接口層。在這里,您不妨把它理解為一個(gè)存放在內(nèi)存中的文件系統(tǒng)。它具體的作用非常明顯:Linux對(duì)文件系統(tǒng)的所有操作都是靠VFS實(shí)現(xiàn)的。它把系統(tǒng)支持的各種以不同形式存放于磁盤上或內(nèi)存中(如proc文件系統(tǒng))的數(shù)據(jù)以統(tǒng)一的形式調(diào)入內(nèi)存,從而完成對(duì)其的讀寫操作。(Linux可以同時(shí)支持許多不同的實(shí)際文件系統(tǒng),就是說,你可以讓你的一個(gè)磁盤分區(qū)使用windowsFAT文件系統(tǒng),一個(gè)分區(qū)使用UnixSYS5文件系統(tǒng),然后可以在這兩個(gè)分區(qū)間拷貝文件)。為了完成以及加速這些操作,VFS采用了塊緩存,目錄緩存(name_cach,索引節(jié)點(diǎn)(inode緩存等各種機(jī)制,以下的這些函數(shù),就是對(duì)這些機(jī)制的初始化。

 

inode_init() /*Linux/fs/inode.c中定義*/

這個(gè)函數(shù)是對(duì)VFS的索引節(jié)點(diǎn)管理機(jī)制進(jìn)行初始化。這個(gè)函數(shù)非常簡單:把用于索引節(jié)點(diǎn)查找的哈希表置入內(nèi)存,再把指向第一個(gè)索引節(jié)點(diǎn)的全局變量置為空。

 

name_cache_init() /*linux/fs/dcache.c中定義*/

這個(gè)函數(shù)用來對(duì)VFS的目錄緩存機(jī)制進(jìn)行初始化。先初始化LRU1鏈表,再初始化LRU2鏈表。

 

Buffer_init()/*linux/fs/buffer.c中定義*/

 

這個(gè)函數(shù)用來對(duì)用于指示塊緩存的buffer free list初始化。

 

mem_init() /* arch/i386/mm/init.c中定義*/

 

啟動(dòng)到了目前這種狀態(tài),只剩下運(yùn)行/etc下的啟動(dòng)配置文件。這些文件一旦運(yùn)行,啟動(dòng)的全過程就結(jié)束了,系統(tǒng)也終將進(jìn)入我們所期待的用戶態(tài)。現(xiàn)在,讓我們回顧一下,到目前為止,我們到底做了哪些工作。

其實(shí),啟動(dòng)的每一個(gè)過程都有相應(yīng)的程序在屏幕上打印與這些過程相應(yīng)的信息。我們回顧一下這些信息,整個(gè)啟動(dòng)的過程就一目了然了。

當(dāng)然,你的計(jì)算機(jī)也許速度很快,你甚至來不及看清這些信息,系統(tǒng)就已經(jīng)就緒,“Login:”就已經(jīng)出現(xiàn)了,不要緊,登錄以后,你只要打一條dmesg | more命令,所有這些信息就會(huì)再現(xiàn)在屏幕上。

 

Loading ……】出自bootsect.S ,表明內(nèi)核正被讀入。

uncompress ……】很多情況下,內(nèi)核是以壓縮過的形式存放在磁盤上的,這里是解壓縮的過程。

 

 下面這部分信息是在main.cstart_kernel函數(shù)被調(diào)用時(shí)顯示的。

Linux version 2.2.6 root@lance) (gcc version 2.7.2.3)】Linux的版本信息和編譯該內(nèi)核時(shí)所用的gcc的版本。

Detected 199908264 Hz processor】調(diào)用init_time()時(shí)打出的信息。

Console:colour VGA+ 80x25,1 virtaul consolemax 63)】調(diào)用 console_init()打出的信息 。初始化的終端屏幕使用彩色VGA模式,最大可以支持63個(gè)終端。

Memory: 63396k/65536k available 848k kernel code 408k reserved, 856k data)】調(diào)用 init_mem()時(shí)打印的信息。內(nèi)存共計(jì)65536K,其中空閑內(nèi)存為63396K,已經(jīng)使用的內(nèi)存中,有848K用于存放內(nèi)核代碼,404K保留,856K用于內(nèi)核數(shù)據(jù)。

VFS:Diskquotas version dquot_6.4.0 initialized】調(diào)用dquote_init()打出的信息 。quota是用來分配用戶磁盤定額的程序。關(guān)于這個(gè)程序請(qǐng)參看第八章。

 

 以下是對(duì)設(shè)備的初始化 :

PCI: PCI BIOS revision 2.10 entry at 0xfd8d1         |

 PCI: Using configuration type 1                      |

 PCI: Probing PCI hardware 調(diào)用pci_init()函數(shù)時(shí)顯示的信息。

Linux NET4.0 for Linux 2.2                                  

 Based upon Swansea University Computer Society NET3.039      

 NET4: Unix domain sockets 1.0 for Linux NET4.0.            

 NET4: Linux TCP/IP 1.0 for NET4.0                            

 IP Protocols: ICMP, UDP TCP調(diào)用socket_init()函數(shù)時(shí)打印的信息。使用Linux4.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 drives: fd0 is 1.44M                                           

 Floppy drives: fd0 is 1.44M           

 FDC 0 is a National Semiconductor PC87306 調(diào)用device_setup()函數(shù)時(shí)打印的信息。包括對(duì)ps/2型鼠標(biāo),聲卡和軟驅(qū)的初始化。

 

看完上面這一部分代碼和與之相應(yīng)的信息,你應(yīng)該發(fā)現(xiàn),這些初始化程序并沒有完成操作系統(tǒng)的各個(gè)部分的初始化,比如說,文件系統(tǒng)的初始化只是初始化了幾個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),而更關(guān)鍵的文件系統(tǒng)的安裝還沒有涉及,其實(shí),這是在init進(jìn)程建立后完成的。下面,就是start_kernel()的最后一部分內(nèi)容。

 






Linux學(xué)習(xí)筆記(1)

os 啟動(dòng)第一步
1. bios
cpu 的初始化
主機(jī)加電后,啟動(dòng)時(shí)鐘發(fā)生器,在總線上產(chǎn)生powergood信號(hào)
cpu收到reset信號(hào),進(jìn)入初始化過程
cpu轉(zhuǎn)入8086實(shí)模式
ds = es = fs = gs = ss = 0
cs = 0xffff
ip = 0xfff0
進(jìn)入bios加電自檢過程(power on self test)
bios初始化
關(guān)中斷,進(jìn)行所有的post檢測(cè)
將中斷向量表的起始地址設(shè)為0x0000h
0x0000h~0x03ffh中存放了256個(gè)中斷
建立了實(shí)模式下的中斷向量表
bios的啟動(dòng)程序調(diào)用int 19h中斷
將控制權(quán)轉(zhuǎn)移給boot loader
int 19h中斷的功能
 int? 19h按照bios中的啟動(dòng)設(shè)備順序查詢每個(gè)啟動(dòng)設(shè)備
 在軟盤的啟動(dòng)扇區(qū)或者硬盤的mbr中有boot? loader,那么這個(gè)扇區(qū)的最后兩個(gè)字節(jié)必然為0xaa55。bios將這個(gè)扇區(qū)(512個(gè)字節(jié))讀入內(nèi)存的0000:7c00開始的位置,然后跳轉(zhuǎn)到內(nèi)存0000:7c00的地方開始執(zhí)行
如果在所有的啟動(dòng)設(shè)備上都找不到boot loader,那么就調(diào)用int 18h,將控制權(quán)交給bios rom basic,鎖定機(jī)器,并且在屏幕上顯示no boot device ***ailable
結(jié)論
 boot? loader應(yīng)當(dāng)存放在啟動(dòng)設(shè)備的第一個(gè)扇區(qū)中,對(duì)于硬盤是mbr,對(duì)于軟盤是啟動(dòng)扇區(qū)
 安裝了boot? loader的啟動(dòng)扇區(qū)的最后兩個(gè)字節(jié)必須為0xaa55
 bios在post過程結(jié)束以后,調(diào)用int 19h中斷,將boot? loader讀入內(nèi)存0000:7c00處。然后釋放控制權(quán),跳轉(zhuǎn)到0000:7c00開始執(zhí)行boot loader的代碼
2. 最簡單的boot loader:fdisk /mbr
硬盤的分區(qū)表結(jié)構(gòu)
a) 第一個(gè)扇區(qū)為mbr
b) 一個(gè)硬盤上最多有4個(gè)主分區(qū)
c) 第一個(gè)主分區(qū)一般從cylinder 0, head 1, sector 1開始
d) cylinder 0, head 0, sector 2~n一般來說保留不用
e) 其余的主分區(qū)一般從cylinder x, head 0, sector 1開始

master boot record
f) 位于硬盤的cylinder 0, head 0, sector 1的位置
g) 大小為512字節(jié)
h) 其中存放了4個(gè)主分區(qū)的入口,每個(gè)入口占16個(gè)字節(jié)(這就是為什么一個(gè)磁盤最多只能有4個(gè)主分區(qū)的原因)
i) 最后兩位為啟動(dòng)標(biāo)志,如果mbr中有boot loader的話,則為0xaa55
j) 留給boot loader的空間為512-16x4-2=446字節(jié)
mbr中的boot loader的功能
k) 初始化,將自身搬移到0000:0600的位置
l) 在主分區(qū)入口表中尋找活動(dòng)的分區(qū)
m) 調(diào)用int 13h ah=02將活動(dòng)的分區(qū)的啟動(dòng)扇區(qū)讀入內(nèi)存0000:7c00處
n) 跳轉(zhuǎn)到0000:7c00處執(zhí)行活動(dòng)分區(qū)的啟動(dòng)扇區(qū)中的代碼
初始化,將自身搬移到0000:0600的位置

0000:7c00 cli 關(guān)中斷
0000:7c01 xor ax,ax
0000:7c03 mov ss,ax 將堆棧段(ss)設(shè)為0
0000:7c05 mov sp,7c00 將棧頂指針(sp)設(shè)置為7c00
0000:7c08 mov si,sp 將si也設(shè)為7c00
0000:7c0a push ax
0000:7c0b pop es 將es設(shè)為0000
0000:7c0c push ax
0000:7c0d pop ds 將ds設(shè)為0000
0000:7c0e sti 開中斷
0000:7c0f cld
0000:7c10 mov di,0600 將di設(shè)為0600
0000:7c13 mov cx,0100 準(zhǔn)備移動(dòng)256個(gè)字(512字節(jié))
0000:7c16 repnz
0000:7c17 movsw 將mbr從0000:7c00移動(dòng)到0000:0600
0000:7c18 jmp 0000:061d 開始搜索主分區(qū)入口表
在主分區(qū)入口表中尋找活動(dòng)的分區(qū)

0000:061d mov si,07besi指向分區(qū)表入口(在總共512byte的主引導(dǎo)記錄中,mbr的引導(dǎo)程序占了其中的前446個(gè)字節(jié)(偏移0h~偏移1bdh),隨后的64個(gè)字節(jié)(偏移1beh~偏移1fdh)為dpt(disk partitiontable,硬盤分區(qū)表),最后的兩個(gè)字節(jié)“55aa”(偏移1feh~偏移1ffh)是分區(qū)有效結(jié)束標(biāo)志)
0000:0620 mov bl,04 一共有4個(gè)表項(xiàng)
0000:0622 cmp byte ptr [si],80 是否為活動(dòng)分區(qū)
0000:0625 jz found_active 找到了一個(gè)活動(dòng)表項(xiàng)
0000:0627 cmp byte ptr [si],00 是否為非活動(dòng)分區(qū)
0000:062a jnz not_active 不可識(shí)別的分區(qū)標(biāo)識(shí)
0000:062c add si,+10 指向下一個(gè)表項(xiàng)(+16)
0000:062f dec bl 循環(huán)標(biāo)志減一
0000:0631 jnz 0000:061d 繼續(xù)循環(huán)
0000:0633 int 18 未找到可啟動(dòng)的分區(qū),轉(zhuǎn)到rom basic

對(duì)于磁盤結(jié)構(gòu)可以詳看 http://www.datasoon.com/fat32-1.htm


調(diào)用int13 ah = 02h讀取啟動(dòng)扇區(qū)

0000:0635 mov dx,[si] 設(shè)置int 13調(diào)用中head和driver的值
0000:0637 mov cx,[si+02] 設(shè)置int 13調(diào)用中cylinder的值
0000:063a mov bp,si 保存活動(dòng)分區(qū)入口表項(xiàng)地址

0000:065d mov di,0005 設(shè)置讀取的重試次數(shù)
0000:0660 mov bx,7c00 將啟動(dòng)扇區(qū)讀取到0000:7c00(es:bx)
0000:0663 mov ax,0201 準(zhǔn)備調(diào)用int 13讀取一個(gè)扇區(qū)al = 01
0000:0666 push di 保存重試次數(shù)di
0000:0667 int 13 調(diào)用int 13讀取一個(gè)扇區(qū)到0000:7c00
0000:0669 pop di 恢復(fù)重試次數(shù)di
0000:066a jnb int13ok 如果讀取成功,則跳轉(zhuǎn)至啟動(dòng)代碼
0000:066c xor ax,ax 準(zhǔn)備int 13 ah = 0復(fù)位磁盤
0000:066e int 13 調(diào)用int 13復(fù)位磁盤
0000:0670 dec di 重試次數(shù)減一
0000:0671 jnz 0000:0660 進(jìn)行下一次重試

運(yùn)行啟動(dòng)扇區(qū)中的代碼

0000:067b mov di,7dfe 指向啟動(dòng)扇區(qū)中的啟動(dòng)標(biāo)識(shí)
0000:067e cmp word ptr [di],aa55 檢查啟動(dòng)表識(shí)是否為0xaa55
0000:0682 jnz display_msg 如果不是,則保錯(cuò)
0000:0684 mov si,bp 恢復(fù)si,指向活動(dòng)分區(qū)入口表項(xiàng)
0000:0686 jmp 0000:7c00 跳轉(zhuǎn)至啟動(dòng)扇區(qū)的代碼

結(jié)論
 boot loader放在可啟動(dòng)設(shè)備的第一個(gè)扇區(qū)中?
 boot? loader的大小受扇區(qū)大小和其他附加信息的限制。在mbr中,為446字節(jié)
 boot? loader在從bios中接手cpu的控制權(quán)時(shí),位于內(nèi)存地址0000:7c00處
 fdisk /mbr產(chǎn)生的boot? loader不具備啟動(dòng)os的能力,其本質(zhì)上是一個(gè)chain loader,用于引導(dǎo)一個(gè)有啟動(dòng)os能力的boot loader
3.如何引導(dǎo)os:dos boot loader
boot sector的結(jié)構(gòu)
 boot sector位于每個(gè)分區(qū)的第一個(gè)扇區(qū)?
 boot? sector的第一個(gè)部分是一個(gè)跳轉(zhuǎn)指令和一個(gè)nop,以跳轉(zhuǎn)實(shí)際的boot loader的代碼中
 bios parameter? block中存放了和這個(gè)分區(qū)相關(guān)的一系列參數(shù)
 bpb之后就是實(shí)際的boot loader的代碼?
最后是一個(gè)可啟動(dòng)分區(qū)標(biāo)識(shí)0xaa55
使用format /s對(duì)boot sector做的修改
 在0x000h處寫入bpb?
在0x03eh處寫入dos boot loader的代碼
 在0x1feh處寫入0xaa55標(biāo)識(shí)?
此外,format /s還要
初始化fat表
 將io.sys和msdos.sys寫入,占據(jù)fat表前兩個(gè)表項(xiàng)?
dos boot loader的功能
初始化
 計(jì)算根目錄所在的fat表的扇區(qū)號(hào)?
 讀取根目錄的第一個(gè)扇區(qū)到0000:0500?
檢查前兩個(gè)表項(xiàng)是否為io.sys和msdos.sys
 將io.sys的前3個(gè)扇區(qū)讀入0000:0700或者0070:0000的位置中?
在寄存器中保留一些信息,然后跳轉(zhuǎn)到0070:0000處執(zhí)行操作系統(tǒng)代碼
 dos boot loader和mbr中的boot? loader的最大區(qū)別在于對(duì)于文件系統(tǒng)的理解
mbr中的boot loader不理解文件系統(tǒng),所以無法啟動(dòng)特定的os
dos boot loader提供了對(duì)于fat文件系統(tǒng)的支持,所以能夠啟動(dòng)在fat文件系統(tǒng)上的dos
dos boot loader知道os的內(nèi)核文件的位置,其主要的工作就是將內(nèi)核文件讀入內(nèi)存,然后將控制權(quán)轉(zhuǎn)交給os
4.硬盤尋址方式:chs vs lba
最早期的chs尋址
 最早期的磁盤尋址是通過cylinder/head/sector進(jìn)行的,int? 13h中給出的chs參數(shù)直接指定了數(shù)據(jù)的物理位置
 例:int 13h ah = 02?
ah = 02 al = 讀入的扇區(qū)數(shù)目
ch = cylinder低8位 cl = cylinder高兩位
dh = head dl = 操作的磁盤(80h for c:)
限制:
cylinder <= 1024
head <= 16
sector <= 63
總?cè)萘?< 1024 x 16 x 63 x 512b = 528mb
l-chs & p-chs
為了解決直接尋址的問題,現(xiàn)代的hd中,chs只有邏輯上的意義,不表達(dá)物理上的實(shí)際位置
 l-chs用在支持chs? translation的bois的int 13 ah = 0xh的調(diào)用中
cylinder <= 1024
head <= 256
sector <= 63
總?cè)萘?<= 1024 x 256 x 63 x 512b = 8gb
p-chs用在hd的接口上
cylinder <= 65535
head <= 16
sector <= 63
總?cè)萘?<= 65535 x 16 x 63 x 512b = 136gb
lba:large block addressing
lba的出現(xiàn)是為了解決chs模式地址受限的問題
lba提供了一種線性的尋址方式:cylinder 0,head 0,sector 1相當(dāng)于lba地址0。往后每增加一個(gè)扇區(qū),lba地址加一
lba地址和chs地址可以通過如下公式轉(zhuǎn)換
lba = ( (cylinder * heads_per_cylinder + heads ) * sectors_per_track ) + sector - 1
新型的bois提供了int 13h ah = 4xh的擴(kuò)展磁盤調(diào)用以支持lba模式
lba:large block addressing
 int 13h ah = 42h?
ah = 42h dl = 操作的磁盤(80h for c:)
ds:si = disk address packet
 disk address packet的格式?


結(jié)論:尋址方式和boot loader的關(guān)系
 bios在post過程以后調(diào)用int 19h,使用的是chs尋址?
 如果一個(gè)os自身的boot? loader使用的是chs尋址模式,那么在老式bios上不能啟動(dòng)528mb以后的分區(qū),在新式bios上不能啟動(dòng)8g以后的分區(qū)(lilo的問題)
只有支持lba的boot loader才能支持啟動(dòng)8g以后分區(qū)上的os
4.支持多os引導(dǎo)的boot loader
支持多os引導(dǎo)的boot loader是:
 一段在啟動(dòng)時(shí)被bios int 19調(diào)用的代碼?
 能夠理解系統(tǒng)中安裝的多個(gè)不同的os?
用戶可以選擇啟動(dòng)特定的os
 程序根據(jù)用戶選擇,通過直接載入或者chain load的方法,啟動(dòng)選中的os?
支持多os引導(dǎo)的boot loader需要:
 自身足夠的小,或者支持多階段載入,以能夠安裝在mbr或者boot sector中?
能夠理解多種文件系統(tǒng)的格式,以便能夠找到存放在不同文件系統(tǒng)下的系統(tǒng)內(nèi)核
能夠理解多種文件格式(elf/a.out),以便能夠正確的載入不同的系統(tǒng)內(nèi)核
支持chs和lba兩種磁盤訪問模式,以便能夠正確的啟動(dòng)8g以后的分區(qū)
 支持chain loader,以便通過特定的boot? loader載入os
支持多os引導(dǎo)的boot loader在執(zhí)行32位os的內(nèi)核代碼前,需要構(gòu)建的機(jī)器狀態(tài):
cs必須是一個(gè)32位的可讀可執(zhí)行的代碼段,偏移為0,上限為0xffffffff
 ds, es, fs,? gs和ss必須為32位的可讀寫段,偏移為0,上限為0xffffffff
 2?0號(hào)地址線必須在32位地址空間中可用(初始固定為0)
分頁機(jī)制必須被關(guān)閉
 處理器中斷標(biāo)記被關(guān)閉?
 eax的值為0x2badb002?
 ebx中存放了一個(gè)32位的地址,指向由boot? loader填充的一系列啟動(dòng)信息
引自:multiboot specification

5.實(shí)例分析:grub
grub是:
grand unified bootloader的縮寫
 是一個(gè)靈活而強(qiáng)大的boot loader?
其能夠理解多種不同的文件系統(tǒng)和可執(zhí)行文件格式,從而能夠引導(dǎo)多種os
 通過將boot? loader所需要的功能封裝成一套腳本語言,從而能夠按照特定的方式引導(dǎo)os
grub的i/o
 支持chs和lba兩種磁盤訪問模式?
(device[,part-num][,bsd-subpart-letter])的方式訪問設(shè)備:(hd0), (hd1, 0), (hd0, a), (hd0, 1, a)
 文件訪問?
通過路徑形式訪問:/boot/grub/menu.lst
通過扇區(qū)形式訪問:0+1,200+1,300+300
grub的腳本:
 root指定一個(gè)啟動(dòng)的設(shè)備?
kernel指定操作系統(tǒng)的內(nèi)核
 boot正式啟動(dòng)一個(gè)os?
 makeactive激活一個(gè)分區(qū)?
chainloader調(diào)用啟動(dòng)設(shè)備上的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中
用于引導(dǎo)stage2或者stage1.5
 stage2?
grub的核心影像,用于提供grub的主要功能
 stage1.5?
stage1與stage2之間的橋梁,安裝在0磁道上第一個(gè)扇區(qū)之后
stage1不理解文件系統(tǒng),但是stage1.5可以
stage1.5最終調(diào)用stage2
 nbgrub/pxegrub?
grub的網(wǎng)絡(luò)啟動(dòng)模塊
stage1的結(jié)構(gòu)
為了保持和fat/hpfs bios的兼容性,所以保存bpb
 在bpb之后的stage1配置數(shù)據(jù)區(qū),在安裝的時(shí)候被填寫?
在數(shù)據(jù)區(qū)之后,才是代碼段
 最后是0xaa55啟動(dòng)扇區(qū)標(biāo)志?

stage1的流程
在stage1的配置數(shù)據(jù)區(qū)中存放了stage2所在的磁盤號(hào)、lba地址以及stage2的載入地址
stage1不需要理解任何的文件系統(tǒng),只需要根據(jù)給出的扇區(qū)號(hào),讀入stage2的第一個(gè)扇區(qū)即可
 stage1相當(dāng)于前面分析的mbr中的boot? loader
stage2的第一部分start.s
 start.s存放在stage2文件的第一個(gè)扇區(qū)里面?
stage2剩余部分的lba地址和內(nèi)存的載入地址是放在start.s的firstlist和lastlist之間的,這個(gè)數(shù)據(jù)段位于start.s代碼的尾部,在安裝的時(shí)候被寫入,稱為block list
 block list以全0項(xiàng)結(jié)尾?
stage2的第一部分start.s
在start.s的代碼開始執(zhí)行的時(shí)候,ds:si所指向的內(nèi)存地址的內(nèi)容是stage1中準(zhǔn)備好的,用于為int 13h調(diào)用準(zhǔn)備參數(shù)
start.s的功能就是根據(jù)block list,將stage2剩余的部分讀入內(nèi)存,然后跳轉(zhuǎn)到0x8200h處執(zhí)行stage2的功能代碼
stage2的第二部分asm.s
 在asm.s中定義了一系列的函數(shù)的實(shí)現(xiàn),包括grub得主入口函數(shù)main?
在main函數(shù)中,完成了如下的工作:
ds = es = ss = 0
建立實(shí)模式/bios棧,esp = 0x2000 - 0x10,向低地址方向增長
轉(zhuǎn)入保護(hù)模式
建立并清空保護(hù)模式棧
調(diào)用cmain,進(jìn)入grub的c代碼中(stage2.c)
在cmain中,完成了如下的工作:
設(shè)法打開/boot/grub/menu.lst這個(gè)配置文件
 根據(jù)配置文件,構(gòu)建用戶菜單?
 如果菜單構(gòu)建成功,則調(diào)用run_menu?
如果菜單構(gòu)建失敗,則調(diào)用enter_cmdline
問題:文件系統(tǒng)
 在這個(gè)時(shí)候,grub已經(jīng)開始訪問文件系統(tǒng)?
grub如何對(duì)付不同的文件系統(tǒng)?
grub中的文件系統(tǒng)層:disk_io.c
 grub中為每一個(gè)文件系統(tǒng)提供了一個(gè)抽象層?
文件系統(tǒng)用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支持所有文件系統(tǒng),通過fsys_table和fsys_type,從而可以以統(tǒng)一的方式訪問不同的文件系統(tǒng)
grub中的命令處理:buildin
 grub支持的每個(gè)命令,均有一個(gè)buildin和其對(duì)應(yīng)?
這些buildin被定義在buildins.c中
 分析以下3個(gè)命令:?
 chainloader?
 kernel?
boot

chainloader
 檢查--force標(biāo)記?
 調(diào)用grub_open打開文件,這里的文件用block? list表示(+1)
 調(diào)用grub_read讀入一個(gè)扇區(qū)到0000:7c00的位置?
 檢查啟動(dòng)扇區(qū)標(biāo)志0xaa55?
kernel
 檢查--type和--no_mem_option標(biāo)志?
 調(diào)用load_image,讀入指定的內(nèi)核文件?
load_image中處理了elf和a.out的各種變形
load_image通過對(duì)于內(nèi)核文件的分析,識(shí)別出被啟動(dòng)的os的種類(通過內(nèi)核文件的magic number)
load_image針對(duì)不同種類的os的內(nèi)核提供了特定的載入代碼(這里的代碼異常復(fù)雜,牽涉到了不同的os的實(shí)現(xiàn)細(xì)節(jié),未作分析)
最終填充mbi (multiboot information)結(jié)構(gòu)
boot
通過執(zhí)行chainloader或者kernel以后,當(dāng)前需要啟動(dòng)的內(nèi)核的類型已經(jīng)確定了
在執(zhí)行boot的時(shí)候,根據(jù)確定的內(nèi)核類型,每一種內(nèi)核均有一種啟動(dòng)的方法
對(duì)于linux,最后調(diào)用了stop函數(shù)實(shí)現(xiàn)控制權(quán)的轉(zhuǎn)移
對(duì)于chainloader,最后也是調(diào)用了stop函數(shù)實(shí)現(xiàn)控制權(quán)的轉(zhuǎn)移
boot
通過執(zhí)行chainloader或者kernel以后,當(dāng)前需要啟動(dòng)的內(nèi)核的類型已經(jīng)確定了
在執(zhí)行boot的時(shí)候,根據(jù)確定的內(nèi)核類型,每一種內(nèi)核均有一種啟動(dòng)的方法
對(duì)于linux,最后調(diào)用了stop函數(shù)實(shí)現(xiàn)控制權(quán)的轉(zhuǎn)移
對(duì)于chainloader,最后也是調(diào)用了stop函數(shù)實(shí)現(xiàn)控制權(quán)的轉(zhuǎn)移

 

 

內(nèi)存管理之段頁機(jī)制 by lxwpp


內(nèi)存管理(memory management)
內(nèi)存地址空間
分段機(jī)制
分頁機(jī)制
cpu多任務(wù)和保護(hù)模式

內(nèi)存地址空間概念

->邏輯地址:是指由程序產(chǎn)生的由段選擇符和段內(nèi)偏移地址兩個(gè)部分組成的地址。
->線形地址:是邏輯地址到物理地址變換之間的中間層,是處理器可尋址的內(nèi)存空間(稱為線性地址空間)中的地址。32位,4gb
->物理地址:是指出現(xiàn)在cpu 外部地址總線上的尋址物理內(nèi)存的地址信號(hào),是地址變換的最終結(jié)果地址。32位無符號(hào)整數(shù)表示 n虛擬內(nèi)存:是指計(jì)算機(jī)呈現(xiàn)出要比實(shí)際擁有的內(nèi)存大得多的內(nèi)存量。

 

分段機(jī)制
段是定義內(nèi)存區(qū)域的另一種機(jī)制,與頁類似。這兩種機(jī)制可以重疊:地址一定在某一頁面內(nèi),也可能處于段內(nèi)。 n

段的描述符:描述段的屬性的一個(gè)8字節(jié)存儲(chǔ)單元

段描述符的一般格式
 
保存描述符項(xiàng)的描述符表
gdt:主要的基本描述符表,該表可被所有程序用于引用訪問一個(gè)內(nèi)存段。
idt:保存有定義中斷或異常處理過程的段描述符
ldt:該表應(yīng)用于多任務(wù)系統(tǒng)中,通常每個(gè)任務(wù)使用一個(gè)ldt 表

段選擇符
 
分段機(jī)制
邏輯地址通過分段機(jī)制自動(dòng)映射(變換)到中間層4g線形地址空間(32位的)


尋址的步驟:
1)在段選擇符中裝入16位數(shù),同時(shí)給出32位地址偏移量(比如在es1,edi中)
2)根據(jù)段選擇符中的索引值、ti及rpl值,再根據(jù)相應(yīng)描述符表寄存器中的段地址和段界限,進(jìn)行一系列合法性檢查(如特權(quán)級(jí)、界限檢查)
如果段無問題,就取出相應(yīng)的描述符放入段描述符高速緩沖寄存器中
3)將描述符中的32位段基地址和放在 esi、edi等中的32位有效地址相加,形成了32位物理地址


cpu 進(jìn)行地址變換(映射)的主要目的是為了解決虛擬內(nèi)存空間到物理內(nèi)存空間的映射問題。

實(shí)模式和保護(hù)模式尋址
實(shí)模式:尋址一個(gè)內(nèi)存地址主要是使用段和偏移值,段值被存放在段寄存器中(例如ds),并且段的長度被固定為64kb。段內(nèi)偏移地址存放在任意一個(gè)可用于尋址的寄存器中(例如si)。因此,根據(jù)段寄存器和偏移寄存器中的值,就可以算出實(shí)際指向的內(nèi)存地址
 

相比:
1)和實(shí)模式下的尋址相比,段寄存器值換成了段描述符表中相應(yīng)段描述符的索引值以及段表選擇位和特權(quán)級(jí)--段選擇符
2)偏移值還是使用了原實(shí)模式下的概念

保護(hù)模式
1)段寄存器中存放的不再是被尋址段的基地址,而是一個(gè)段描述符表(segment descriptor table)中某一描述符項(xiàng)在表中的索引值
2)段的長度可變,由描述符中的內(nèi)容指定
 

分頁機(jī)制
分頁機(jī)制功能是把由段機(jī)制轉(zhuǎn)換而來的線性地址轉(zhuǎn)換位物理地址

轉(zhuǎn)化過程如圖:
 

轉(zhuǎn)換步驟
1)cr3包含著頁目錄的起始地址,用32位線性地址的最高10位a31~a22作為頁目錄的頁目錄項(xiàng)的索引,將它乘以4,與cr3中的頁目錄的起始地址相加,形成相應(yīng)頁表的地址
2)從指定地址中取出32位頁目錄項(xiàng),它的低12位為00,這32位是頁表的起始地址用32位線性地址中的a21-a22位作為頁表中的頁面索引,將它乘以4,與頁表的起始地址相加,形成32位頁面地址。
3)將a11-a0作為相對(duì)于頁面地址的偏移量,與32位頁面地址相加,形成23位頁面物理地址。

擴(kuò)展分頁
頁的大小為4mb(2的22次方),擴(kuò)展前是4kb
線性地址分為兩個(gè)域:最高10位的目錄域和其余22位的偏移量,如圖
【圖】
 

虛擬內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)
linux中頁目錄(pgd) 、頁面中間目錄(pmd)、頁表(pt),分別由數(shù)據(jù)結(jié)構(gòu)pgd_t,pmd_t,pte_t來表示。
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
//偏移量的位數(shù),因此頁面大小為4kb n#define page_size(1ul<<page_shift) n#define page_mask (~(page_size-1)
//值定義為0xfffff000,用以屏蔽掉偏移量域的所有位

定義頁目錄大小、頁目錄掩碼等信息的代碼如下:
#define pgdir_shift 22 //線性地址中頁目錄偏移量位數(shù)
#define pgdir_size (1ul<< pgdir_shift) ?//一個(gè)頁目錄項(xiàng)的地址空間(4m) ?#define pgdir_mask (~(pgdir_size-1)) ?//頁目錄掩碼
#define ptrs_per_pgd 1024 ?//頁目錄表項(xiàng)個(gè)數(shù)

其中當(dāng)時(shí)提到的一個(gè)問題就是:虛擬地址和邏輯地址究竟有和區(qū)別?

 

 

 


內(nèi)存初始化和分配回收

1 內(nèi)存初始化
2 內(nèi)存的分配和回收(伙伴算法,slab,cache)

內(nèi)存初始化

startup_32 函數(shù)
start_kernel 函數(shù)

startup_32 函數(shù)
本函數(shù)完全用匯編語言實(shí)現(xiàn),主要功能是啟用分頁機(jī)制。l當(dāng)linux啟動(dòng)時(shí),首先運(yùn)行在實(shí)模式下(對(duì)物理地址操作),隨后就要轉(zhuǎn)到保護(hù)模式下(對(duì)虛擬地址操作)運(yùn)行,linux內(nèi)核代碼的入口點(diǎn)就是/arch/i386/kernel/head.s中的startup_32。


startup_32主要做的工作:l

編譯內(nèi)核過程中,首先初始化一個(gè)頁目錄swapper_pg_dir,和前8m物理地址空間的頁表pg0,pg1。這樣,初始狀態(tài)下,用戶空間和內(nèi)核空間都只映射了開頭的兩個(gè)目錄項(xiàng),即8m的空間,并且有著相同的映射。l內(nèi)核開始運(yùn)行后運(yùn)行在內(nèi)核空間,那末,為什么把用戶空間的低區(qū)(8m)也進(jìn)行映射呢?簡而言之,[為了實(shí)模式到保護(hù)模式的平穩(wěn)過渡]這是因?yàn)閯傞_啟頁面映射時(shí),(并不說明linux內(nèi)核真正進(jìn)入了保護(hù)模式)指令寄存器eip仍指向地址,仍會(huì)以物理地址取指令,如果頁目錄只映射內(nèi)核空間,而不映射用戶空間的低區(qū),則一旦開啟頁映射機(jī)制后就不能繼續(xù)執(zhí)行了。

初始頁目錄swapper_pg_dir的映射圖
【圖,要插入】

當(dāng)startup_32將頁目錄swapper_pg_dir的物理地址裝入控制寄存器cr3,并把cr0的最高位置1,即開啟了分頁機(jī)制。隨后,編譯程序使所有的符號(hào)地址都在虛擬地址空間中,指令寄存器eip指向虛擬地址空間的某個(gè)地址,就使cpu轉(zhuǎn)入了內(nèi)核空間,完成從實(shí)模式到保護(hù)模式的過渡。函數(shù)setup_arch確定start_kernel中內(nèi)存初 始化的起始地址start_mem和結(jié)束地址end_mem。start_mem是內(nèi)存映像的結(jié)束地址_end對(duì)應(yīng)的核心態(tài)當(dāng)startup_32將頁目錄swapper_pg_dir的物理地址裝入控制寄存器cr3,并把cr0的最高位置1,即開啟了分頁機(jī)制。隨后,編譯程序使所有的符號(hào)地址都在虛擬地址空間中,指令寄存器eip指向虛擬地址空間的某個(gè)地址,就使cpu轉(zhuǎn)入了內(nèi)核空間,完成從實(shí)模式到保護(hù)模式的過渡。 函數(shù)setup_arch確定start_kernel中內(nèi)存初始化的起始地址start_mem和結(jié)束地址end_mem。start_mem是內(nèi)存映像的結(jié)束地址_end對(duì)應(yīng)的核心態(tài)虛擬地址,end_mem是將bios自檢時(shí)確定的物理內(nèi)存大小轉(zhuǎn)換成核心態(tài)的虛擬地址

ps:在pc中,最初1mb的存儲(chǔ)空間的使用很特殊。開頭640kb(0x0~0x9ffff)為ram,從0xa0000開始的空間用于cga、ega、vga等各種圖形卡。從0xf0000到0xfffff(最高的4kb)是在eprom或flash存儲(chǔ)器中的bios。 ll這個(gè)階段初始化后的,物理內(nèi)存初始化的內(nèi)核映像見后圖:

start_kernel函數(shù)l

本函數(shù)實(shí)現(xiàn)整個(gè)linux內(nèi)核的初始化,有關(guān)內(nèi)存初始化的部分介紹如下:paging_init函數(shù)初始化頁表;頁描述符的建立由free_area_init函數(shù)完成;mem_init函數(shù)設(shè)置物理頁面的標(biāo)志位,整理空閑區(qū); kmem_cache_initkmem_cache_sizes_init函數(shù)初始化cache_cache,通用cache,slab分配器 startup_32和start_kernel函數(shù)完成后物理內(nèi)存的初始化情況:
【圖要插入】

內(nèi)存分配和回收之伙伴算法(buddy)
產(chǎn)生背景:內(nèi)核應(yīng)該為分配一組連續(xù)的物理頁而建立一種穩(wěn)定、高效的分配策略。為此,必須解決內(nèi)存管理中的外部碎片問題。頻繁的請(qǐng)求和釋放不同大小的一組連續(xù)頁,必然導(dǎo)致在已分配物理頁的塊內(nèi)分散許多小塊的空閑頁。這樣,即使有足夠的頁滿足請(qǐng)求,要分配一個(gè)大塊的連續(xù)頁就可能無法滿足?!緜渥ⅰ窟@是在物理存儲(chǔ)器上分配內(nèi)存【/備注】
算法工作原理:linux的伙伴算法把所有的空閑物理頁分組為10個(gè)塊鏈表,每個(gè)塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256和512個(gè)連續(xù)的頁(2的冪次方)。每個(gè)塊的第一個(gè)頁的物理地址也是該塊大小的整數(shù)倍。分配一個(gè)塊(例128個(gè)連續(xù)頁)時(shí),首先在對(duì)應(yīng)的鏈表中查找空閑塊。如果找到,則直接分配;否則,查找下一個(gè)更大的塊,將其中的一部分分配給進(jìn)程,剩余部分插入相應(yīng)的塊鏈表中。

滿足以下條件的兩個(gè)塊就稱為伙伴:
1)兩個(gè)塊的大小相同 l2)兩個(gè)塊的物理地址連續(xù) l伙伴算法把滿足以上條件的兩個(gè)塊合并為一個(gè)塊,算法是迭代算法

伙伴算法的數(shù)據(jù)結(jié)構(gòu)

伙伴系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)是free_area_struct: l struct free_area_struct{ l struct page*next; //雙向鏈表指針,鏈 l //表每個(gè)成員是一個(gè)塊的第一個(gè)物理頁描述符 l struct page *prev; lunsigned int *map; //指向?qū)?yīng)的位圖 l unsigned long count;//鏈表元素個(gè)數(shù) l }

數(shù)據(jù)結(jié)構(gòu)的分析:free_area[0]到free_area[9]都是free_area_struct類型的結(jié)構(gòu),分別對(duì)應(yīng)不同大小塊的鏈表,并且分別對(duì)應(yīng)一個(gè)位圖。
位圖的大小取決于系統(tǒng)物理頁面的數(shù)量。第k項(xiàng)位圖的每一位描述大小為2k的物理頁的兩個(gè)伙伴塊的狀態(tài)。如果位圖的某一位為0,表示一對(duì)伙伴塊中或者都忙或者都空閑;如果為1,肯定有一塊為忙。當(dāng)伙伴塊都空閑時(shí),內(nèi)核把他們當(dāng)作一個(gè)大小為2k+1的塊來處理。例如,一個(gè)128mb的ram,可以分成32768個(gè)頁,16384個(gè)大小為2個(gè)頁的塊,…,則free_area[0]的位圖有16384位,free_area[1]的有8192位…

伙伴系統(tǒng)數(shù)據(jù)結(jié)構(gòu)圖
【圖】

slab分配機(jī)制

為什么要提出slab機(jī)制l原因:
伙伴系統(tǒng)采用頁作為基本內(nèi)存區(qū),適合于對(duì)大塊內(nèi)存的請(qǐng)求。但是如果請(qǐng)求內(nèi)存的大小與分配給它的大小不匹配,容易產(chǎn)生內(nèi)部碎片。 l

解決之道:
linux2.0采用的方法是:linux提供按幾何分布的內(nèi)存區(qū)大小,即建立了13個(gè)按幾何分布的空閑內(nèi)存鏈表,大小從32到131056字節(jié)。伙伴系統(tǒng)的調(diào)用,既為了獲得額外所需的物理頁以存放新的內(nèi)存區(qū),也為了釋放不再包含內(nèi)存區(qū)的物理頁。
linux2.2起采用了slab分配模式
linux采用slab分配器模式管理內(nèi)存區(qū)。它把內(nèi)存區(qū)看作對(duì)象,對(duì)象有一組數(shù)據(jù)結(jié)構(gòu)和構(gòu)造、析構(gòu)函數(shù)組成,但為提高效率,指向構(gòu)造和析構(gòu)函數(shù)的指針都為null。slab分配器把對(duì)象分組放進(jìn)高速緩存。每個(gè)高速緩存存放著同一種類型的對(duì)象。高速緩存被劃分成多個(gè)slab,每個(gè)slab由一個(gè)或多個(gè)連續(xù)的物理頁組成。這些頁中包含已分配的對(duì)象,也包含空閑對(duì)象 linux中引入slab主要目的是減少對(duì)伙伴算法的調(diào)用次數(shù)

slab分配器的組成:
注意:這里的高速緩存不是cpu中的高速緩存。它實(shí)際存在于物理內(nèi)存中,由于分配速度相對(duì)于伙伴系統(tǒng)比較快,稱謂高速緩存。
【圖】
實(shí)際上,緩沖區(qū)就是主存中的一片區(qū)域,把這片區(qū)域劃分為多個(gè)塊,每塊就是一個(gè)slab,每個(gè)slab由一個(gè)或多個(gè)頁面組成,每個(gè)slab中存放的就是對(duì)象

cache的數(shù)據(jù)結(jié)構(gòu)l l
struct kmem_cache_s { l kmem_slab_t *c_freep;//指向第一個(gè)含有 l //空閑對(duì)象的slab的指針 l
unsigned long c_flags; //描述cache靜態(tài)特性的標(biāo)志,例如slab(object)描述符的 存儲(chǔ)位置,外部存儲(chǔ)還是內(nèi)部存儲(chǔ) l
unsigned long c_offset; //cache中對(duì)象的大 ?。ㄈ绻髮?duì)象的起始地址必須是在 l //內(nèi)存對(duì)齊,就可以對(duì)這個(gè)大小取整)

lunsigned long c_magic; //從一組預(yù)先定義好的數(shù)值中選出的一個(gè)魔數(shù),用于檢查高速緩存的當(dāng)前狀態(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í)訪問cache 時(shí)起保護(hù)作用的旋轉(zhuǎn)鎖 lunsigned long c_growing;lunsigned long c_dflags;//描述cache動(dòng)態(tài)特性的標(biāo)志,例如內(nèi)核是否正在為cache分配新的slab

size_t c_org_size; lunsigned longc_gfporder;//每個(gè)slab包含的連續(xù)頁面數(shù)目為2c_gfporder個(gè) lvoid (*c_ctor)(void *,kmem_cache_t *, unsigned long); //構(gòu)造函數(shù) lvoid (*c_dtor)(void*,kmem_cache_t*, unsigned long); //析構(gòu)函數(shù) lunsigned long c_align; //對(duì)象的對(duì)齊因子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//指向外部對(duì)象描述符所在的高速緩存 l //的高速緩存描述符 l};

高速緩存分為兩種類型:通用和專用。前者只由slab分配器用于自己使用,后者由內(nèi)核其余部分使用。 l 通用高速緩存是: l(1)第一個(gè)高速緩存包含由內(nèi)核使用的其余高速緩存的高速緩存描述符。cache_cache變量包含第一個(gè)高速緩存的描述符。 l(2)第二個(gè)高速緩存包含沒有存放在slab內(nèi)的slab描述符。cache_slabp變量指向第二個(gè)高速緩存描述符。

(3)13個(gè)高速緩存包含幾何分布的內(nèi)存區(qū)。cache_sizes數(shù)組元素分別指向13個(gè)高速緩存描述符,與其相關(guān)的內(nèi)存區(qū)大小為32,64,…,131072字節(jié)。 l系統(tǒng)初始化時(shí)調(diào)用kmem_cache_init和kmem_cache_sizes_init建立通用高速緩存,調(diào)用kmem_cache_create創(chuàng)建專用高速緩存。

slab的數(shù)據(jù)結(jié)構(gòu)
typedef struct kmem_slab_s { struct kmem_bufctl_s*s_freep; //指向slab //中第一個(gè)空閑對(duì)象的指針 struct kmem_bufctl_s *s_index;//指向外//部對(duì)象描述符所在的內(nèi)存區(qū) unsigned long s_magic; unsigned long s_inuse;//slab中當(dāng)前分配//的對(duì)象數(shù)目 struct kmem_slab_s *s_nextp;//指向同一 //個(gè)cache中與該slab相鄰的兩個(gè)slab的指針struct kmem_slab_s *s_prevp;//構(gòu)成雙向鏈表

l unsigned long _offset:slab_offset_bits, l s_dma:1; l} kmem_slab_t;

對(duì)象的數(shù)據(jù)結(jié)構(gòu)l對(duì)象數(shù)據(jù)結(jié)構(gòu)如下: ltypedef struct kmem_bufctl_s { l union { l structkmem_bufctl_s *buf_nextp; l //指向下一個(gè)空閑對(duì)象 l kmem_slab_t *buf_slabp;//對(duì)象被分配, l //且對(duì)象描述符在外面,指向?qū)ο笏诘?l //slab的slab描述符 l void * buf_objp;//對(duì)象被分配,且對(duì)象描 l //述符在外面,則指向該對(duì)象 l } u; l} kmem_bufctl_t;

對(duì)象描述符可以存放在slab內(nèi)部,緊接描述符所描述的對(duì)象之后;如果存放在slab之外,存放在由cache_sizes指向的一個(gè)通用高速緩存中。對(duì)象本身所在的高速緩存,通過cache的c_index_cachep和slab的s_index兩個(gè)域被連到它們的描述符所在的高速緩存。

slab分配器所管理的對(duì)象可以在內(nèi)存對(duì)齊,也就是說,存放它們的起始物理地址是一個(gè)給定常數(shù)的倍數(shù),這個(gè)常數(shù)叫做對(duì)齊因子,存放在cache的c_align域中。存放對(duì)象大小的c_offset域要考慮增加填充字節(jié)數(shù)來對(duì)齊。如果對(duì)象大小大于高速緩存行的一半,就在ram中把這個(gè)對(duì)象的大小對(duì)齊到ll_cache_bytes的倍數(shù),也就是行的開始。否則,這個(gè)對(duì)象的大小就是ll_cache_bytes的因子取整。

slab著色的產(chǎn)生背景:由于相同大小的對(duì)象很可能存放于高速緩存的相同的偏移量處,在不同slab內(nèi)具有相同偏移量的對(duì)象最終可能映射在同一高速緩存行中。slab分配器使用slab著色的策略解決這個(gè)問題。slab分配器利用空閑未用的字節(jié)來對(duì)slab著色,可用顏色個(gè)數(shù)為空閑字節(jié)數(shù)/c_align+1。

如果用顏色col對(duì)一個(gè)slab著色,那末,第一個(gè)對(duì)象偏移量(相對(duì)于slab起始地址)等于col*c_align。這個(gè)值存放在slab的s_offset域。
【圖】

 


slab內(nèi)存分配機(jī)制:
作用:分配內(nèi)核內(nèi)存(小內(nèi)存)
幾何分布的內(nèi)存區(qū)大小
內(nèi)碎片小于50%
32-131072 共13級(jí)
1. 所存放數(shù)據(jù)類型影響分配區(qū)
2. 內(nèi)核函數(shù)傾向于反復(fù)請(qǐng)求同一類型內(nèi)存區(qū)
3. 請(qǐng)求根據(jù)發(fā)生頻率分類
4. 引入對(duì)象若不是按幾何分布,可以用硬件高速緩存
5. 盡量限制使用伙伴系統(tǒng)

3種相關(guān)數(shù)據(jù)結(jié)構(gòu)
slab_s 一個(gè)slab塊的管理信息
list: slab鏈表
colouroff: 染色偏移
s_mem: 指向第一個(gè)對(duì)象
inuse: 活動(dòng)的對(duì)象數(shù)(已分配)
free: 第一個(gè)空閑對(duì)象號(hào)

kmem_bufctl_t 其實(shí)就是unsigned int,形成數(shù)組,用于標(biāo)識(shí)slab中的每個(gè)對(duì)象是否已用,并形成空閑對(duì)象鏈

kmeme_cache_s 緩存塊管理信息
slabs
firstnotfull
objsize 對(duì)象大小
flags
num 一個(gè)slab中對(duì)象個(gè)數(shù)
spinlock
gfporder每個(gè)slab的頁面數(shù)的對(duì)數(shù)
colour colour_off colour_next 染色相關(guān)變量
slabp_cache 對(duì)off slab方式使用,指鄉(xiāng)向公共的cache_slabp,它存放每個(gè)slab的slab_t
構(gòu)造,析構(gòu)函數(shù)
name
next

結(jié)構(gòu):在cache_cache中分配,存放所有的cache,這些cache中,有cache_slabp,專門分配offslab模式的slab_t與kmem_bufctl_t數(shù)組,也有由cache_sizes指針數(shù)組所指向的一些通用cache,另有一些獨(dú)立的專用cache
每個(gè)cache管理一組slab,每個(gè)slab管理一組對(duì)象,這些slab中的對(duì)象大小,個(gè)數(shù),都相同,占用的空間也相同,只是存放的起示位置不同N兩種方式:
on slab 小對(duì)象用,<512
偏移 | slab_t | kmem_bufctl_t數(shù)組 | 對(duì)象 | 剩余空間
off slab >=512 slab_t kmem_bufctl_t數(shù)組都由cache_slabp統(tǒng)一分配,于是,結(jié)構(gòu)為
染色機(jī)制:
頁的統(tǒng)一偏移,可能映射到硬件cache的同一塊,而每個(gè)slab中的開始部分,是訪問頻率最高的,所以使用染色機(jī)制,使每個(gè)slab中起始部分偏移不一樣,從而充分利用硬件cache

cache結(jié)構(gòu)中的colour為最大偏移調(diào)整計(jì)數(shù),colour_off為每次調(diào)整的數(shù)值,colour_next為計(jì)數(shù)值,0到colour

對(duì)齊
字對(duì)齊,高速緩存行對(duì)齊

緩存區(qū)創(chuàng)建
kmem_cache_create()
1. 錯(cuò)誤檢查
2. 從cache_cache中分配一個(gè)對(duì)象做為新建的這個(gè)cache
3. 完成對(duì)齊,字對(duì)齊,高速緩存行對(duì)齊
4. >512,選off slab方式,反之為on slab
5. 找出一個(gè)slab的大?。摰谋稊?shù))滿足3個(gè)條件,至少可以容納一個(gè)對(duì)象,內(nèi)存浪費(fèi)不至于太大,slab塊不至于太大
6. 可能因?yàn)槭S嗫臻g夠大,能放入slab_t和kmem_bufctl_t,于是改回on slab方式
7. 設(shè)置染色相關(guān)數(shù)據(jù)。colour與剩余空間大小有關(guān)
8. 設(shè)置參數(shù),slab鏈表構(gòu)造(指向自身),firstnotfull指向這個(gè)slab
9. 將這個(gè)cache插入cache_sizes的指針表,并插入管理chche的鏈表cache_chain

緩存區(qū)釋放,擴(kuò)充與收縮

對(duì)象分配與釋放

 


內(nèi)核空間
2005-07-07

物理內(nèi)存區(qū):
簡單的說:可以減去一個(gè)偏移或使用virt_to_phys()函數(shù)完成。
由get_free_page或kmalloc函數(shù)所分配的連續(xù)內(nèi)存都陷于物理映射區(qū)域,所以它們返回的內(nèi)核虛擬地址和實(shí)際物理地址僅僅是相差一個(gè)偏移量(page_offset),你可以很方便的將其轉(zhuǎn)化為物理內(nèi)存地址,同時(shí)內(nèi)核也提供了virt_to_phys()函數(shù)將內(nèi)核虛擬空間中的物理映射區(qū)地址轉(zhuǎn)化為物理地址。要知道,物理內(nèi)存映射區(qū)中的地址與內(nèi)核頁表是有序?qū)?yīng),系統(tǒng)中的每個(gè)物理頁框都可以找到它對(duì)應(yīng)的內(nèi)核虛擬地址(在物理內(nèi)存映射區(qū)中的)。

虛擬內(nèi)存分配區(qū)
它的問題相對(duì)要復(fù)雜些,這是因?yàn)槠浞峙涞膬?nèi)核虛擬內(nèi)存空間并非直接操作頁框,而是分配的是vm_struct結(jié)構(gòu)。該結(jié)構(gòu)邏輯上連續(xù)但對(duì)應(yīng)的物理內(nèi)存并非連續(xù),也就是說它vamlloc分配的內(nèi)核空間地址所對(duì)應(yīng)的物理地址并非可通過簡單線性運(yùn)算獲得。

如何映射?
每個(gè)非連續(xù)區(qū)的描述符
struct vm_struct{ n unsigned long flags;
void *addr;
unsigned long size;
struct vm_struct *next; n};

vmlist
物理內(nèi)存區(qū) 8m 內(nèi)存區(qū)4k內(nèi)存區(qū)4k

vm_struct->get_vm_area()

nstruct vm_struct *get_vm_area(unsigned long size)
{
};

函數(shù)首先調(diào)用kmalloc()為描述符分配一個(gè)內(nèi)存區(qū),然后查找一個(gè)可用的的線性地址區(qū)間,如果存在,就初始化這個(gè)描述符的所有域,返回內(nèi)存區(qū)的起始地址。

vm_struct->get_vm_area->vmalloc()
分配內(nèi)存區(qū)
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);//導(dǎo)出內(nèi)存區(qū)起始地址在頁目錄中的目錄項(xiàng);
spin_lock(&init_mm.page_table_lock);
do{ pmd_t *pmd;
pmd=pmd_alloc(&init_mm,dir,address);//為新的內(nèi)存區(qū)創(chuàng)建一個(gè)中間頁目錄項(xiàng)
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為新的中間頁目錄分配所有相關(guān)頁表,并更新頁的總目錄,調(diào)用pte_alloc_kernel()函數(shù)來分配一個(gè)新的頁表,之后調(diào)用alloc_area_pte()為頁表項(xiàng)分配具體的物理頁面 n

至此,完成了非連續(xù)內(nèi)存區(qū)到物理頁面的映射

 

固定映射
vaddr=_fix_to_virt(_end_of_fixed_addresses-1)&pmd_mask; nfixrange_init(vaddr,0,pgdbase);
枚舉類型_end_of_fixed_addresses用作索引, _fix_to_virt宏返回給定索引的虛地址。函數(shù)fixrange_init()為這些虛地址創(chuàng)建合適的頁表項(xiàng),再由set_fixmap()函數(shù)完成地址的映射

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內(nèi)核使用highmem接口將高位內(nèi)存動(dòng)態(tài)映射到內(nèi)核地址空間的一小部分(這部分就是4m專門保留用來實(shí)現(xiàn)這個(gè)目的),以此提供間接訪問。這部分內(nèi)核地址空間就是所謂的kmap段

include/linux/highmem.h
void *kmap(struct page *page)
{ return page_address(page);}
#define page_address(page) ((page)->virtual)

 

 

管理區(qū)zone
為了對(duì)物理頁面進(jìn)行有效的管理,linux把物理頁面劃為3個(gè)區(qū)

專供dma使用的zone_dma區(qū)(小于16mb)
常規(guī)的zone_normal區(qū)(大于16mb小于896mb)
內(nèi)核不能直接映射的zone_highme區(qū)(大于896mb)

【圖】
n2)在標(biāo)準(zhǔn)配置下,物理區(qū)最大長度為896m,系統(tǒng)的物理內(nèi)存被順序映射在物理區(qū)中,在支持?jǐn)U展頁長(pse)和全局頁面(pge)的機(jī)器上,物理區(qū)使用4m頁面并作為全局頁面來處理.當(dāng)系統(tǒng)物理內(nèi)存大于896m時(shí),超過物理區(qū)的那部分內(nèi)存稱為高端內(nèi)存,低端內(nèi)存和高端內(nèi)存用highmem_start_page變量來定界,內(nèi)核在存取高端內(nèi)存時(shí)必須將它們映射到"高端頁面映射區(qū)".

3)linux保留內(nèi)核空間最頂部128k區(qū)域作為保留區(qū),緊接保留區(qū)以下的一段區(qū)域?yàn)閷S庙撁嬗成鋮^(qū),它的總尺寸和每一頁的用途由fixed_address枚舉結(jié)構(gòu)在編繹時(shí)預(yù)定義,用__fix_to_virt(index)可獲取專用區(qū)內(nèi)預(yù)定義頁面的邏輯地址.在專用頁面區(qū)內(nèi)為每個(gè)cpu預(yù)定義了一張高端內(nèi)存映射頁,用于在中斷處理中高端頁面的映射操作.(高端內(nèi)存映射區(qū)屬于固定內(nèi)存區(qū)的一種,見下頁代碼)
4)距離內(nèi)核空間頂部32m, 長度為4m的一段區(qū)域?yàn)楦叨藘?nèi)存映射區(qū),它正好占用1個(gè)頁幀表所表示的物理內(nèi)存總量,它可以緩沖1024個(gè)高端頁面的映射.在物理區(qū)和高端映射區(qū)之間為虛存內(nèi)存分配區(qū), 用于vmalloc()函數(shù),它的前部與物理區(qū)有8m隔離帶,后部與高端映射區(qū)有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í),總是盡可能的將所有的物理內(nèi)存映射到內(nèi)核地址空間中去。如果內(nèi)核地址空間起始于0xc0000000,為vmalloc保留的虛擬地址空間是128m,那么最多只能有(1g-128m)的物理內(nèi)存直接映射到內(nèi)核空間中,內(nèi)核可以直接訪問。如果還有更多的內(nèi)存,就稱為高端內(nèi)存,內(nèi)核不能直接訪問,只能通過修改頁表映射后才能進(jìn)行訪問。

內(nèi)存分區(qū)可以使內(nèi)核頁分配更加合理。當(dāng)系統(tǒng)物理內(nèi)存大于1g時(shí),內(nèi)核不能將所有的物理內(nèi)存都預(yù)先映射到內(nèi)核空間中,這樣就產(chǎn)生了高端內(nèi)存,高端內(nèi)存最適于映射到用戶進(jìn)程空間中。預(yù)映射的部分可直接用于內(nèi)核緩沖區(qū),其中有一小塊可用于dma操作的內(nèi)存,留給dma操作分配用,一般不會(huì)輕易分配。內(nèi)存分區(qū)還可以適應(yīng)不連續(xù)的物理內(nèi)存分布,是非一致性內(nèi)存存取體系(numa)的基礎(chǔ)

先看看代碼中的注釋:
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高端頁面的映射
1)
高端物理頁面共享一塊4m的映射區(qū)域,該區(qū)域?qū)R于4m頁邊界,并用一張頁表(pkmap_page_table)來完成映射操作。高端頁面的映射地址由其頁結(jié)構(gòu)中virtual成員給出。
(void *virtual)(page結(jié)構(gòu))
2)
高端映射區(qū)邏輯頁面的分配結(jié)構(gòu)用分配表(pkmap_count)來描述,它有1024項(xiàng),對(duì)應(yīng)于映射區(qū)內(nèi)不同的邏輯頁面。當(dāng)分配項(xiàng)的值等于零時(shí)為自由項(xiàng),等于1時(shí)為緩沖項(xiàng),大于1時(shí)為映射項(xiàng)。映射頁面的分配基于分配表的掃描,當(dāng)所有的自由項(xiàng)都用完時(shí),系統(tǒng)將清除所有的緩沖項(xiàng),如果連緩沖項(xiàng)都用完時(shí),系統(tǒng)將進(jìn)入等待狀態(tài)。
3)
頁緩沖盡可能地使用高端頁面,當(dāng)通過塊結(jié)構(gòu)刷新高端頁面時(shí),系統(tǒng)會(huì)在提交塊設(shè)備<br>> ,原請(qǐng)求塊,同時(shí)中轉(zhuǎn)塊被釋放。

 

 


tlb(翻譯后援存儲(chǔ)器)

時(shí)間:2005-07-05
地點(diǎn):同濟(jì)大學(xué)科技園

by lxwpp Z7br" SS{ +f lV[ 本_資_料_來_源_于_貴_州_學(xué)_習(xí)_網(wǎng) 電腦課堂LINUX教程 Http://wwW.gzU521.coM )Z7br" SS{ +f lV

reson
由于頁表尺寸較大,因此許多分頁方案都只能把它保存在內(nèi)存中,但這種設(shè)計(jì)對(duì)性能有很大的影響。(分頁的時(shí)候,系統(tǒng)性能下降2/3 n 計(jì)算機(jī)設(shè)計(jì)者們發(fā)現(xiàn):大部分程序傾向于對(duì)較少的頁面進(jìn)行大量的訪問。因此只有一小部分頁表項(xiàng)經(jīng)常被用到,其他的很少被使用。

concept
tlb使個(gè)小的虛擬尋址的緩存,不需要經(jīng)過頁表就能把虛擬地址轉(zhuǎn)換為物理地址的小的硬件設(shè)備

components
一個(gè)用來訪問tlb的虛擬地址組成部分
【圖】
tlb寄存器是由虛擬頁號(hào),有效位,保護(hù)位等組成

steps
1 cpu產(chǎn)生一個(gè)虛擬地址
2 mmu從tlb中取出相應(yīng)的pte,如果命中轉(zhuǎn)3;如果不命中,進(jìn)行常規(guī)頁表查找,用找到的pte淘汰tlb中的一個(gè)條目
3 mmu將這個(gè)虛擬地址翻譯成物理地址并且把它發(fā)送到高速緩存/主存
4 高速緩存/主存將所有的請(qǐng)求的數(shù)據(jù)字節(jié)返回給cpu


figure1(tlb命中)
 
figure2(tlb不命中)

example
前提:虛擬地址14位,物理地址12位,頁面大小64字節(jié),tlb共有16個(gè)條目,緩存16個(gè)組 n給定虛擬地址0x3d4

flush-software
軟件管理tlb
剛剛介紹的tlb故障處理都是由mmu硬件完成的。
現(xiàn)在的管理工作是由軟件完成的。tlb條目由os顯式裝入,在沒有命中的時(shí)候,mmu不是到頁表中找到并裝入信息而是產(chǎn)生一個(gè)故障交給os,os找到頁面,淘汰一個(gè)條目,裝入新條目 ntlb取一個(gè)合理的尺寸以減少不命中的頻率,software管理tlb的效率高 n性能提高:實(shí)行預(yù)裝入機(jī)制

essence
無論軟件管理還是硬件管理,在tlb沒命中的時(shí)候,都是對(duì)頁表執(zhí)行索引操作找出所引用的頁面,然后把pte加入到tlb中。

flush
完成的工作:
(1)保證在任何時(shí)刻內(nèi)存管理硬件所看到的進(jìn)程的內(nèi)核映射和內(nèi)核頁表一致
(2)如果負(fù)責(zé)內(nèi)核管理的內(nèi)核代碼對(duì)于用戶進(jìn)程頁面進(jìn)行了修改,那么用戶的進(jìn)程在被允許繼續(xù)執(zhí)行前,要求必須在緩存中看到正確的數(shù)據(jù)

function
flush_cache_foo()
flush_tlb_foo()
地址空間改變前必須刷新緩存,防止緩存中存在非法的空映射。在刷新地址后,由于頁表的改變,必須刷新tlb以便硬件可以把新的頁表信息裝入tlb nvoid flush_cache_all(void)
void flush_tlb_all(void)
用來通知相應(yīng)機(jī)制,內(nèi)核地址空間的映射已經(jīng)被改變,意味著所有的進(jìn)程都已經(jīng)改變

 

 

vfs概述 nvfs的的數(shù)據(jù)結(jié)構(gòu)
超級(jí)塊(super_block)
索引節(jié)點(diǎn)(inode)
目錄項(xiàng)(dentry)
文件(file) n與進(jìn)程相關(guān)的文件結(jié)構(gòu) n
文件系統(tǒng)的安裝
n文件系統(tǒng)的注冊(cè)
根文件系統(tǒng)的安裝
普通文件系統(tǒng)的安裝
n目錄項(xiàng)的緩沖
路徑名到目標(biāo)節(jié)點(diǎn)的轉(zhuǎn)換

vfs概述
虛擬文件系統(tǒng)(vfs)的所有數(shù)據(jù)結(jié)構(gòu)都是在運(yùn)行之后建立的,在卸載時(shí)刪除,而在磁盤上沒有這些數(shù)據(jù)結(jié)構(gòu)。
vfs不是真正的文件系統(tǒng)。與vfs相對(duì)應(yīng)的ext2,msdos等稱為具體的文件系統(tǒng)

linux系統(tǒng)中vfs和具體文件系統(tǒng)關(guān)系圖
[圖]

vfs的作用:
對(duì)具體文件系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)進(jìn)行抽象,以一種統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)進(jìn)行管理
接受用戶層的系統(tǒng)調(diào)用,如read(),write(),open()等
支持多種具體文件系統(tǒng)之間的相互訪問
接受內(nèi)核其他子系統(tǒng)的操作請(qǐng)求,比如內(nèi)存管理子系統(tǒng),進(jìn)程管理子系統(tǒng)

超級(jí)塊(super_block)

對(duì)具體文件系統(tǒng)的超級(jí)塊是文件系統(tǒng)中最重要的數(shù)據(jù)結(jié)構(gòu),它用來描述整個(gè)文件系統(tǒng)信息【組織結(jié)構(gòu)和管理信息】。不涉及文件系統(tǒng)的內(nèi)容
vfs超級(jí)塊是各種具體文件系統(tǒng)在安裝時(shí)建立的,并在這些文件系統(tǒng)卸載時(shí)自動(dòng)刪除。
vfs超級(jí)塊實(shí)際上應(yīng)該說成是某個(gè)具體文件系統(tǒng)的vfs超級(jí)塊
超級(jí)塊對(duì)象由super_block結(jié)構(gòu)組成

super_block結(jié)構(gòu)主要包括以下一些域:

描述文件系統(tǒng)整體信息的域
用于管理超級(jí)塊的域 n
與具體文件系統(tǒng)相聯(lián)系的域

描述文件系統(tǒng)整體信息的域:

kdev_t s_dev 具體文件系統(tǒng)的塊設(shè)備標(biāo)識(shí)符 n
unsigned long s_blocksize 具體文件系統(tǒng)的數(shù)據(jù)塊大小
unsigned char s_blocksize_bits
塊大小占用的位數(shù),例如,塊大小1024字節(jié),則該值為10
unsigned long long s_maxbytes 文件最大長度
unsigned long s_flags 超級(jí)塊標(biāo)識(shí)
unsigned long s_magic 魔數(shù)

用于管理超級(jí)塊的域:
struct list_head s_list 用于形成超級(jí)塊鏈表
struct semaphore s_lock 鎖
struct rw_semaphore s_umount 讀寫超級(jí)塊時(shí)進(jìn)行同步
unsigned char s_dirt 該超級(jí)塊修改標(biāo)識(shí)
struct dentry *s_root 指向該文件系統(tǒng)安裝目錄的dentry結(jié)構(gòu)
int s_count 對(duì)超級(jí)塊的使用記數(shù)
struct list_head s_dirty “臟”索引節(jié)點(diǎn)鏈表的頭節(jié)點(diǎn)
struct list_head s_locked_inodes 要同步的索引節(jié)點(diǎn)鏈表的頭節(jié)點(diǎn)
struct list_head s_files 該超級(jí)塊上的所有打開文件鏈表頭節(jié)點(diǎn)

與具體文件系統(tǒng)相聯(lián)系的域:
struct file_system_type *s_type指向具體文件系統(tǒng)file_system_struct結(jié)構(gòu)的指針 struct super_operations *s_op指向具體文件系統(tǒng)super_operations結(jié)構(gòu)的指針 n struct dquot_operations *dq_op指向具體文件系統(tǒng)用于限額操作的dquot_operations結(jié)構(gòu)的指針
union u 各種文件系統(tǒng)的特定信息。

超級(jí)塊鏈表
所有超級(jí)塊對(duì)象,以雙向循環(huán)鏈表的形式連接在一起,鏈表頭節(jié)點(diǎn)保存在super_blocks變量(定義在fs/super.c):
[圖]

索引節(jié)點(diǎn)對(duì)象(inode)

文件系統(tǒng)處理文件所需的信息
索引節(jié)點(diǎn)對(duì)文件是唯一的
具體文件系統(tǒng)的索引節(jié)點(diǎn)存儲(chǔ)在磁盤上,使用的時(shí)候,必須調(diào)入內(nèi)存,填寫vfs的索引節(jié)點(diǎn)。所以vfs的索引節(jié)點(diǎn)是動(dòng)態(tài)節(jié)點(diǎn)。

inode結(jié)構(gòu)主要包括以下一些域

描述文件信息的域
用于索引節(jié)點(diǎn)管理的域
用于索引節(jié)點(diǎn)操作的域
其他

kdev_t i_dev 設(shè)備標(biāo)識(shí)號(hào)
umode_t i_mode 文件類型和訪問權(quán)限
link_t i_nlink 與該索引節(jié)點(diǎn)建立的連接數(shù)
uid_t i_uid 文件所有者號(hào)
gid_t i_gid 所有者所在組號(hào)
kdev_t i_rdev 實(shí)際設(shè)備號(hào)
off_t i_size 文件大?。ㄗ止?jié))
unsigned long i_blksize 塊大小
unsigned long i_blocks 該文件占用塊數(shù)
time_t i_atime 文件最后訪問時(shí)間
time_t i_mtime 文件最后修改時(shí)間
time_t i_ctime 節(jié)點(diǎn)修改時(shí)間
unsigned long i_version 版本號(hào)
struct semaphore i_zombie 僵死索引節(jié)點(diǎn)的信號(hào)量 JCV x8c !y!h-[轉(zhuǎn) 貼 于 我 的 學(xué) 習(xí) 網(wǎng) 電腦課堂LINUX教程 HTtp://wwW.gzU521.coM)JCV x8c !y!h-

用于索引節(jié)點(diǎn)管理的域
struct list_head i_hash 形成哈希鏈表
struct list_head i_list 形成索引節(jié)點(diǎn)鏈表
struct list_head i_dentry 目錄項(xiàng)鏈表的頭節(jié)點(diǎn)

用于索引節(jié)點(diǎn)操作的域:
struct inode_operations *i_op 索引節(jié)點(diǎn)的操作
struct super_block *i_sb 指向該文件系統(tǒng)的超級(jí)塊
atomic_t i_count 使用該節(jié)點(diǎn)的進(jìn)程數(shù)
struct file_operations *i_fop 文件操作
unsigned char i_lock 用于同步操作,鎖定節(jié)點(diǎn)
struct semaphore i_sem 用于同步的信號(hào)量
wait_queue_head_t *i_wait 指向索引節(jié)點(diǎn)等待隊(duì)列
unsigned char i_dirt 修改標(biāo)識(shí)
struct file_lock *i_flock 指向文件加鎖鏈表的指針
struct dquot *i_dquot[maxquotas] 索引節(jié)點(diǎn)的磁盤限額

其它域
unsigned long i_state 索引節(jié)點(diǎn)的狀態(tài)標(biāo)識(shí)
unsigned int i_flags 文件系統(tǒng)的安裝標(biāo)識(shí)
atomic_t i_writecount 寫進(jìn)程的引用計(jì)數(shù)
unsigned int i_attr_flags 文件創(chuàng)建標(biāo)識(shí)
union u 共同體

索引節(jié)點(diǎn)鏈表

索引節(jié)點(diǎn)至少在下列鏈表之一:
(1)未用索引節(jié)點(diǎn)鏈表 鏈表頭節(jié)點(diǎn)保存在inode_unused變量,i_count=0; n (2)正在使用索引節(jié)點(diǎn)鏈表 鏈表頭節(jié)點(diǎn)保存在inode_in_use變量,i_count>0, i_nlink>0
(3)每個(gè)超級(jí)塊的臟索引節(jié)點(diǎn)鏈表
鏈表頭節(jié)點(diǎn)保存在相應(yīng)超級(jí)塊的s_dirty,i_count>0, i_nlink>0, i_state & i_dirty

未用索引節(jié)點(diǎn)鏈表[圖]
正在使用索引節(jié)點(diǎn)鏈表[圖]
臟索引節(jié)點(diǎn)鏈表[圖]


目錄項(xiàng)(dentry)

每個(gè)文件除了有一個(gè)索引節(jié)點(diǎn)結(jié)構(gòu)外,還有目錄項(xiàng)dentry結(jié)構(gòu)。

dentry結(jié)構(gòu)代表的是邏輯意義上的文件,在磁盤上沒有對(duì)應(yīng)的映象。而inode結(jié)構(gòu)代表的是物理意義上的文件,對(duì)于一個(gè)具體的文件系統(tǒng),在磁盤上有對(duì)應(yīng)的映象。

一個(gè)dentry結(jié)構(gòu)必有一個(gè)inode結(jié)構(gòu),而一個(gè)inode可能對(duì)應(yīng)多個(gè)dentry結(jié)構(gòu)。
由于從磁盤讀入一個(gè)文件并構(gòu)造相應(yīng)的目錄項(xiàng)需要花費(fèi)大量的時(shí)間,而在完成對(duì)目錄項(xiàng)的操作后,可能后面還會(huì)用到,所以在內(nèi)存中要保留它。

dentry結(jié)構(gòu)定義在include/linux/dcache.h中

dentry的域

atomic_t d_count 目錄項(xiàng)引用計(jì)數(shù)
unsigned int d_flags 目錄項(xiàng)標(biāo)識(shí)
struct inode * d_inode 與文件名關(guān)聯(lián)的索引節(jié)點(diǎn)
struct dentry * d_parent 父目錄的目錄項(xiàng)
struct list_head d_hash 形成目錄項(xiàng)哈希鏈表
struct list_head d_lru 未使用的目錄項(xiàng)鏈表
struct list_head d_child 形成父目錄的子目錄項(xiàng)鏈表
struct list_head d_subdirs 子目錄鏈表的頭節(jié)點(diǎn)
struct list_head d_alias 形成索引節(jié)點(diǎn)別名鏈表
int d_mounted 該目錄項(xiàng)安裝文件系統(tǒng)數(shù)目
struct qstr d_name 目錄項(xiàng)名(快速查找)
unsigned long d_time 由d_revalidate函數(shù)使用
struct dentry_operations *d_op 目錄項(xiàng)操作函數(shù)
struct super_block * d_sb 所在文件系統(tǒng)的
void * d_fsdata 具體文件系統(tǒng)的數(shù)據(jù),目前只為nfs所用
unsigned char d_iname[dname_inline_len] 短文件名,24個(gè)字符

目錄項(xiàng)的狀態(tài)

每個(gè)目錄項(xiàng)屬于以下4種狀態(tài)之一:
空閑狀態(tài):目錄項(xiàng)不包含有效信息,未被vfs使用,由slab分配器進(jìn)行管理。
未使用狀態(tài):目錄項(xiàng)還沒有被使用,d_count為null,但是其d_inode仍然指向相關(guān)的索引節(jié)點(diǎn)。該目錄項(xiàng)包含有效信息,但必要時(shí),其內(nèi)容可以被丟棄。
正在使用狀態(tài):目錄項(xiàng)正在被使用,d_count>0,d_inode指向相關(guān)的索引節(jié)點(diǎn),包含有效信息,不能被丟棄。
負(fù)狀態(tài):與目錄項(xiàng)相關(guān)索引節(jié)點(diǎn)已不存在,d_inode為null

未用目錄項(xiàng)鏈表
目錄項(xiàng)哈希管理
父子目錄關(guān)系
目錄項(xiàng)與索引節(jié)點(diǎn)


file對(duì)象

在include/linux/fs.h中定義了file結(jié)構(gòu),用于保存文件位置等對(duì)文件的操作信息。描述的是進(jìn)程怎樣與一個(gè)打開文件的交互過程
file對(duì)象是在文件打開描述符創(chuàng)建。
多個(gè)file結(jié)構(gòu)可以指向同一個(gè)文件。

file域

struct file { /* 定義在include/linux/fs.h */
struct list_head f_list; /*形成鏈表*/
struct dentry *f_dentry; /*文件對(duì)應(yīng)的dentry結(jié)構(gòu)*/
struct vfsmount *f_vfsmnt; /*文件所在文件系統(tǒng)的vfsmount結(jié)構(gòu)*/
struct file_operations *f_op; /*文件操作函數(shù)結(jié)構(gòu)*/
atomic_t f_count; /*引用計(jì)數(shù)*/ n unsigned int f_flags; /*文件標(biāo)識(shí)*/
mode_t f_mode; /*文件打開方式*/
loff_t f_pos; /*文件當(dāng)前位置*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; n /*預(yù)讀標(biāo)識(shí)、預(yù)讀最多頁面數(shù),上次預(yù)讀后的文件指針、預(yù)讀字節(jié)數(shù)、預(yù)讀頁面數(shù)*/
struct fown_struct f_owner; /*異步i/o消息的文件擁有者*/
unsigned int f_uid, f_gid; /*賦予當(dāng)前進(jìn)程的uid和gid*/
int f_error; /*網(wǎng)絡(luò)寫操作的錯(cuò)誤碼*/
unsigned long f_version; /*版本號(hào)*/
void *private_data; /*文件私有數(shù)據(jù)區(qū)*/

file 對(duì)象

每個(gè)文件對(duì)象總是包含在下列鏈表之一:

以打開的文件鏈表:超級(jí)塊的s_files作為頭節(jié)點(diǎn),鏈入屬于該文件系統(tǒng)的已打開的文件
“未使用”文件對(duì)象鏈表:既可以用作文件對(duì)象的內(nèi)存高速緩存,又可以當(dāng)作超級(jí)用戶的備用存儲(chǔ)器,即使系統(tǒng)動(dòng)態(tài)內(nèi)存用完,也允許超級(jí)用戶打開文件。

在這個(gè)鏈表中的對(duì)象,f_count總是null,鏈表首元素地址放在free_list(定義在fs/file_table.c中)。該鏈表至少包含nr_reserved_files(定義在include/linux/fs.h,默認(rèn)為10)個(gè)對(duì)象。 n

新分配文件對(duì)象鏈表:每個(gè)元素至少有一個(gè)進(jìn)程使用,f_count!= n null,首元素地址存放在anon_list(定義在fs/file_table.c中)。

當(dāng)需要分配一個(gè)新文件對(duì)象時(shí),調(diào)用get_empty_filp (定義在fs/ file_table.c中) ,如果未使用鏈表中的元素個(gè)數(shù)多于nr_reserved_files,則為新文件使用其中一個(gè)元素,否則進(jìn)入正常內(nèi)存分配。

文件系統(tǒng)打開的文件鏈表

與進(jìn)程相關(guān)的文件結(jié)構(gòu)
files_struct
fs_struct

files_struct:進(jìn)程打開文件表

對(duì)于每個(gè)進(jìn)程,包含一個(gè)files_struct結(jié)構(gòu),用來記錄文件描述符的使用情況,定義在include/linux/sched.h

files_struct

struct files_struct {
atomic_t count; /*使用該表的進(jìn)程數(shù)*/
rwlock_t file_lock; /*鎖*/
int max_fds; /*當(dāng)前文件對(duì)象的最大數(shù)*/
int max_fdset; /*當(dāng)前文件描述符最大數(shù)*/
int next_fd; /*數(shù)值最小的最近關(guān)閉文件的文件描述符*/
struct file ** fd; /*指向文件對(duì)象數(shù)組的指針*/
fd_set *close_on_exec; /*指向執(zhí)行exec時(shí)需要關(guān)閉的文件描述符*/
fd_set *open_fds; /*指向文件描述符屏蔽字集合*/
fd_set close_on_exec_init;/*執(zhí)行exec時(shí)需要關(guān)閉的文件描述符初值集合*/
fd_set open_fds_init; /*文件描述符的屏蔽字集合*/
struct file * fd_array[nr_open_default];/*文件對(duì)象指針數(shù)組*/
}; /*nr_open_default=32*/

fd指向文件對(duì)象的指針數(shù)組,數(shù)組長度存放在max_fds中。通常,fd指向fd_array,fd_array大小為32。如果進(jìn)程打開的文件數(shù)多于32,內(nèi)核就分配一個(gè)新的更大的文件指針數(shù)組,其地址放入fd,同時(shí)更新max_fds。
open_fds指向open_fds_init,open_fds_init表示當(dāng)前打開文件的文件描述符屏蔽字。max_fdset存放屏蔽字的位數(shù),默認(rèn)是1024,需要的時(shí)候可以通過expand_fdset( )函數(shù)擴(kuò)展到1024*1024。

可以通過fget( )函數(shù),將文件對(duì)象與描述符鏈接起來

文件系統(tǒng)信息fs_struct

用來描述文件系統(tǒng)信息的fs_struct結(jié)構(gòu),定義在include/linux/fs_struct.h:
struct fs_struct {
atomic_t count; /*使用該結(jié)構(gòu)的進(jìn)程數(shù)*/
rwlock_t lock; /*讀寫鎖*/
int umask; /*初始文件許可權(quán)*/
struct dentry * root, * pwd, * altroot;
/*根目錄、當(dāng)前目錄、替換根目錄的目錄項(xiàng)*/
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
/*根目錄、當(dāng)前目錄、替換根目錄的vfsmount結(jié)構(gòu)*/ n};
umask由umask( )系統(tǒng)調(diào)用使用,文件打開的實(shí)際權(quán)限為期望的權(quán)限&umask。 n
該結(jié)構(gòu)的一個(gè)作用是用于路徑定位時(shí),提供根目錄、當(dāng)前目錄和替換根目錄。

與進(jìn)程有關(guān)的文件結(jié)構(gòu)間的關(guān)系


文件系統(tǒng)注冊(cè)

文件系統(tǒng)是指可能會(huì)被掛載到目錄樹中的各個(gè)實(shí)際文件系統(tǒng)
注冊(cè)過程實(shí)際上將表示各實(shí)際文件系統(tǒng)的 struct file_system_type 數(shù)據(jù)結(jié)構(gòu)的實(shí)例化,然后形成一個(gè)鏈表,內(nèi)核中用一個(gè)名為 file_systems 的全局變量來指向該鏈表的表頭

file_system_type
struct file_system_type {
const char *name; /*文件系統(tǒng)名,出現(xiàn)在/proc/filesystems中,且唯一*/
int fs_flags; /*文件系統(tǒng)標(biāo)識(shí)*/
struct super_block *(*read_super) (struct super_block *, void *, int); /*讀取該文件系統(tǒng)在設(shè)備上的超級(jí)塊*/
struct module *owner; /*如果是由模塊載入的,則指向該模塊;否則,為null*/ n
struct file_system_type * next; /*用于形成注冊(cè)文件系統(tǒng)鏈表*/
struct list_head fs_supers; /*超級(jí)塊鏈表*/

文件系統(tǒng)的安裝
根文件系統(tǒng)的安裝

1)建立臨時(shí)的inode和file對(duì)象,在其中填入信息,如設(shè)備號(hào),根據(jù)文件系統(tǒng)安裝標(biāo)志設(shè)置file的f_mode域等
->調(diào)用blkdev_open()函數(shù),完成設(shè)備的準(zhǔn)備工作
->順序搜索表file_systems中的每一個(gè)注冊(cè)的文件系統(tǒng),用自己的read_super函數(shù)讀取root_dev超級(jí)塊,如果成功,獲得根文件系統(tǒng)的超級(jí)塊和根目錄的inode和其目錄結(jié)構(gòu)
->把pwd設(shè)置為根目錄的目錄項(xiàng)對(duì)象
->調(diào)用add_vfsmnt()把第一個(gè)元素插入到已安裝文件系統(tǒng)鏈表中

安裝常規(guī)文件系統(tǒng)
系統(tǒng)安裝了根文件系統(tǒng)后,就可以安裝其他文件系統(tǒng)。每個(gè)文件系統(tǒng)安裝在系統(tǒng)目錄樹的一個(gè)目錄上。
安裝一個(gè)文件系統(tǒng)可以通過系統(tǒng)調(diào)用mount實(shí)現(xiàn)。
mount系統(tǒng)調(diào)用在內(nèi)核的實(shí)現(xiàn)函數(shù)是sys_mount( ) 。(fs/namespace.c)


整個(gè)安裝過程中最核心的函數(shù)就是do_kern_mount()了,為了創(chuàng)建一個(gè)新安裝點(diǎn)(vfsmount),該函數(shù)需要做一下幾件事情:

1 檢查安裝設(shè)備的權(quán)利,只有root權(quán)限才有能力執(zhí)行該操作。
2 get_fs_type()在文件鏈表中取得相應(yīng)文件系統(tǒng)類型(注冊(cè)時(shí)被填加到練表中)。
3 alloc_vfsmnt()調(diào)用slab分配器為vfsmount結(jié)構(gòu)體分配存儲(chǔ)空間,并把它的地址存放在mnt局部變量中。
4 初始化mnt->mnt_devname域

5 5 分配新的超級(jí)塊并初始化它。do_kern_mount()檢查file_system_type描述符中的標(biāo)志以決定如何進(jìn)行如下操作:根據(jù)文件系統(tǒng)的標(biāo)志位,選擇相應(yīng)的方法讀取超級(jí)塊(比如對(duì)ext2,romfs這類文件系統(tǒng)調(diào)用get_sb_dev();對(duì)于這種沒有實(shí)際設(shè)備的虛擬文件系統(tǒng)如ramfs調(diào)用get_sb_nodev())——讀取超級(jí)塊最終要使用文件系統(tǒng)類型中的read_super方法


目錄項(xiàng)緩存
引言
/home/user/src/foo.c
每次對(duì)foo.c訪問,vfs都要沿著嵌套目錄依次解析全部路徑:/、home、user、src和foo.c
為了避免每次訪問該路徑名都要進(jìn)行此操作,vfs會(huì)在目錄項(xiàng)緩存中搜索路徑名,如果找到了就無需花費(fèi)力氣解析
相反如果節(jié)點(diǎn)該目錄項(xiàng)緩存中不存在,vfs就必須解析,完畢后將目錄項(xiàng)對(duì)象加入到dcache中

目錄項(xiàng)緩存包括的主要部分
被使用的目錄項(xiàng)鏈表
該鏈表通過索引節(jié)點(diǎn)對(duì)象i_dentry項(xiàng)鏈接,因?yàn)橐粋€(gè)給定的索引節(jié)點(diǎn)可能有多個(gè)鏈接,所以可能就有多個(gè)目錄項(xiàng)對(duì)象,因此用一個(gè)鏈表鏈接它們 最近使用的雙向鏈表
該鏈表含有未被使用的和負(fù)狀態(tài)的目錄項(xiàng)對(duì)象。由于該鏈以時(shí)間順序插入,所以最后釋放的目錄項(xiàng)對(duì)象放在鏈表首部,最近最少使用的目錄項(xiàng)對(duì)象總是靠近鏈表尾部。一旦目錄項(xiàng)的告訴緩存空間開始變小的的時(shí)候,內(nèi)核就從鏈表的尾部刪除元素,使得多數(shù)最近經(jīng)常使用的對(duì)象得以保留。哈希表和相應(yīng)的哈希函數(shù)快速地將給定的路徑名解析未相關(guān)的目錄項(xiàng)對(duì)象。

哈希表石由數(shù)組dentry_hashtable表示,其中每一個(gè)元素都是指向一組具有相同鏈值的目錄項(xiàng)對(duì)象的指針。通過d_lookup()函數(shù)在緩存中查找,如果訪問的對(duì)象不在目錄項(xiàng)高速緩存中,哈希函數(shù)返回一個(gè)空值


從路徑名到目標(biāo)節(jié)點(diǎn)CSAyD VGuf3$",XY(本 文來 源于 我 的學(xué) 習(xí)網(wǎng)電腦課堂LINUX教程 htTP://WWw.GZu521.COm]CSAyD VGuf3$",XY

ext2

ext2磁盤布局在邏輯空間中的映象

超級(jí)塊
ext2超級(jí)塊是用來描述ext2文件系統(tǒng)整體信息的數(shù)據(jù)結(jié)構(gòu),是ext2的核心所在。它是個(gè)ext2_super_block數(shù)據(jù)結(jié)構(gòu)

一些域的解釋

文件系統(tǒng)中并非所有的塊都可以使用,有些塊是保留的,給超級(jí)塊專用,塊的數(shù)目在i_r_blocks_count定義。一旦空閑塊總數(shù)等于保留塊,普通用戶就無法申請(qǐng)數(shù)據(jù)快了。如果保留塊也被使用,則系統(tǒng)就可能無法使用了。邏輯快是從0號(hào)開始,對(duì)塊大小為1kb的文件系統(tǒng),s_first_block為1,對(duì)其它文件系統(tǒng),則為0s_log_block_size以2的冪次方表示塊的大小,單位1024字節(jié)ext2要定期檢查自己的狀態(tài),s_lastcheck記錄最近一次檢查狀態(tài)的時(shí)間,而s_checkinterval則規(guī)定了兩次檢查狀態(tài)的最大允許時(shí)間間隔 如果檢查到文件系統(tǒng)有錯(cuò)誤,則對(duì)s_errors賦一個(gè)錯(cuò)誤的值

ext2_sb_info
每個(gè)文件系統(tǒng)自己的特性信息
[圖]

ext2索引節(jié)點(diǎn)
[圖]

解釋:
邏輯塊->物理塊
ext2通過索引節(jié)點(diǎn)中的數(shù)據(jù)塊指針數(shù)組進(jìn)行邏輯塊到物理塊的映射。指針數(shù)組15項(xiàng),前12個(gè)為直接塊指針,后3個(gè)分別為“一次間接塊指針”,“二次間接塊指針”,“三次間接塊指針”。

索引12中的元素包含一個(gè)塊的邏輯號(hào),這個(gè)塊代表邏輯塊號(hào)的一個(gè)二級(jí)數(shù)組。數(shù)組對(duì)應(yīng)的文件快號(hào)從12到b/4+11,b是文件系統(tǒng)塊的大小索引13中的元素包含一個(gè)塊的邏輯號(hào),這個(gè)塊代表邏輯塊號(hào)的一個(gè)三級(jí)數(shù)組。數(shù)組對(duì)應(yīng)的文件快號(hào)從b/4+12到(b/4)^2+(b/4)+11索引14中的元素包含一個(gè)塊的邏輯號(hào),這個(gè)塊代表邏輯塊號(hào)的一個(gè)四級(jí)數(shù)組。數(shù)組對(duì)應(yīng)的文件快號(hào)從(b/4)^2+(b/4)+12到(b/4)^3+(b/4)^2+(b/4)+11

組描述符

ext2_group_desc的數(shù)據(jù)結(jié)構(gòu)
struct ext2_group_desc{ ? _u32 bg_block_bitmap;//塊位圖所在的塊號(hào)_u32 bg_inode_bitmap;索引節(jié)點(diǎn)位圖所在的塊號(hào)
_u32 bg_inode_table; 索引節(jié)點(diǎn)表的首塊號(hào)
_u16 bg_free_blocks_count;空閑塊號(hào)
_u16 bg_free_inodes_count; 空閑索引節(jié)點(diǎn)數(shù)
_u16 bg_used_dirs_count; 分配給目錄的節(jié)點(diǎn)數(shù)
_u16 bg_pad; 填充,對(duì)齊到字;
_32 [3] bg_reserved;用null填充12個(gè)字節(jié) ?}

每個(gè)塊組都有一個(gè)相應(yīng)的組描述符描述它,所有組描述符形成一個(gè)組描述符表,組描述符可能占多個(gè)數(shù)據(jù)塊。 作用大。一旦描述符破壞,整個(gè)組塊無法使用,再每個(gè)組塊中備份

位圖
數(shù)據(jù)塊位圖:每一位表示數(shù)據(jù)塊的使用情況,1表示已分配,0表示空閑 索引位圖也類似

用高速緩存管理位圖塊 每個(gè)高速緩存最多同時(shí)只能裝入ext2_max_grop_loaded個(gè)位圖塊或索引塊采用類似lru算法管理高速緩存ext2_sb_info中的四個(gè)域來管理這兩個(gè)高速緩存s_block_bitmap_number[]存放進(jìn)入高速緩存的塊號(hào)(塊組號(hào)),s_block_bitmap[]存放了相應(yīng)塊在高速緩存中的地址


load_block_bitmap()調(diào)入指定的數(shù)據(jù)塊位圖

1)如果指定的塊組號(hào)大于塊組數(shù),出錯(cuò),結(jié)束
2)通過搜索s_block_bitmap_number[]數(shù)組可知位圖塊是否進(jìn)入了高速緩存,如果進(jìn)入,則結(jié)束,否則,繼續(xù) ?
3)如果塊組數(shù)不大于ext2_max_grop_loaded,高速緩存就可以同時(shí)裝入所有塊組數(shù)據(jù)塊位圖,無論采用什么算法,只要從s_block_bitmap_number[]找到一個(gè)空閑的元素,將塊組號(hào)寫入,然后將位圖塊調(diào)入高速緩存,最后將它在高速緩存中的地址寫入s_block_bitmap[]數(shù)組中

4)如果塊組數(shù)大于ext2_max_grop_loaded,則需要采用一下算法:
首先通過s_block_bitmap_number[]數(shù)組判斷高速緩存是否已滿,若未滿,則操作過程類似上面的步驟,不同的事要將s_block_bitmap_number[]數(shù)組各元素后移一位,用空出的第一個(gè)元素存儲(chǔ)塊組號(hào),s_block_bitmap[]做同樣的處理如果高速緩存已滿,將s_block_bitmap[]數(shù)組最后一項(xiàng)所指的位圖塊從高速緩存中交換出去,然后調(diào)入指定的位圖塊,最后對(duì)這兩個(gè)數(shù)組操作類似上面相同的操作

索引節(jié)點(diǎn)表
每個(gè)塊組中索引節(jié)點(diǎn)都存儲(chǔ)在各自的索引節(jié)點(diǎn)表中,并且按照索引節(jié)點(diǎn)號(hào)依次存儲(chǔ)。索引節(jié)點(diǎn)表一般要占好幾個(gè)數(shù)據(jù)塊 所有索引節(jié)點(diǎn)的大小,128字節(jié)。所以1024字節(jié)的數(shù)據(jù)塊可以包含8個(gè)索引節(jié)點(diǎn) 計(jì)算索引節(jié)點(diǎn)表占用的塊數(shù):一個(gè)塊組中索引節(jié)點(diǎn)總數(shù)/每個(gè)塊中的索引節(jié)點(diǎn)數(shù)。
塊組中索引節(jié)點(diǎn)總數(shù)放在超級(jí)塊中的s_inode_per_group域

索引節(jié)點(diǎn)的域
_u16 i_mode 文件類型和訪問權(quán)限
_u16 i_uid 文件所有者的標(biāo)志符 ?
_u32 i_size 以字節(jié)為單位的文件長度
_u32 i_atime
_u32 i_ctime 時(shí)間信息 ?
_u32 i_dtime
_u16 i_links_count 硬鏈接計(jì)數(shù)器
_u32 i_blocks 文件的數(shù)據(jù)塊數(shù)

ext2目錄項(xiàng)
目錄是一種特殊的文件,它是由ext2_dir_entry這個(gè)結(jié)構(gòu)組成的列表。 結(jié)構(gòu)是變長的,這樣可以減少磁盤空間的浪費(fèi)。 長度有限制:
文件名最長只能為255個(gè)字符
一般自動(dòng)變成為4的倍數(shù),不足補(bǔ)null 目錄中有文件和子目錄,每一項(xiàng)對(duì)應(yīng)一個(gè)ext2_dir_entry

ext2_dir_entry結(jié)構(gòu)

struct ext2_dir_entry{
_u32 inode; //節(jié)點(diǎn)號(hào)
_u16 rec_len; // 目錄項(xiàng)的長度
_u8 name_len; //名字長度
char name[ext2_name_len];//文件名 };

各類文件使用數(shù)據(jù)塊

常規(guī)文件
在創(chuàng)建的時(shí)候是空的,不需要數(shù)據(jù)塊,只有在開始有數(shù)據(jù)的時(shí)候才需要數(shù)據(jù)塊 目錄
ext2以一種特殊的文件實(shí)現(xiàn)了目錄,文件的數(shù)據(jù)塊存放了文件名和相應(yīng)的索引節(jié)點(diǎn)號(hào) 符號(hào)鏈:如果路徑名不大于60個(gè)字符,就把它存放在索引節(jié)點(diǎn)的i_blocks域中,因此無需數(shù)據(jù)塊。否則就需要一個(gè)單獨(dú)的數(shù)據(jù)塊

設(shè)備文件,管道和套接字
不需要數(shù)據(jù)塊,所有必要的信息都存放在索引節(jié)點(diǎn)中。

ext2的文件類型

文件類型 描述
0 未知
1 正規(guī)文件
2 目錄
3 字符設(shè)備
4 塊設(shè)備
5 命名管道
6 套接字
7 符號(hào)鏈


創(chuàng)建索引節(jié)點(diǎn)

函數(shù)為ext2_new_inode()
參數(shù)為:dir,mode
新創(chuàng)建節(jié)點(diǎn)必須插到一個(gè)目錄中,參數(shù)dir指的是這個(gè)目錄的索引節(jié)點(diǎn)對(duì)象的地址 -----const struct inode *dir
mode指的是創(chuàng)建索引節(jié)點(diǎn)的類型

步驟
調(diào)用get_empty_inode()分配一個(gè)新的索引節(jié)點(diǎn)對(duì)象分配一個(gè)新的索引節(jié)點(diǎn)對(duì)象,并sb=dir->i_sb;調(diào)用lock_super()獲得超級(jí)塊對(duì)象的互斥訪問如果新創(chuàng)建的索引節(jié)點(diǎn)是目錄,,則要考慮將來是否能將其屬下的文件都容納在一個(gè)塊組中。所以應(yīng)該找個(gè)其空閑索引節(jié)點(diǎn)的數(shù)量超過整個(gè)設(shè)備上的平均值這么一個(gè)塊組,而不惜離開父節(jié)點(diǎn)所在的塊組,另起爐灶^_^如果新創(chuàng)建的節(jié)點(diǎn)是文件,首先考慮將其索引節(jié)點(diǎn)分配在其目錄所在的塊組,若無空閑索引節(jié)點(diǎn),則沿著此塊組往下繼續(xù)查找。若還是沒有,則從第一個(gè)塊組從頭開始查找。


確定了索引節(jié)點(diǎn)分配在哪個(gè)的塊組,就要從索引節(jié)點(diǎn)位圖中分配一個(gè)節(jié)點(diǎn),(調(diào)用load_inode_bitmap()),從中尋找第一個(gè)空位,這樣就得到了第一個(gè)空閑磁盤索引節(jié)點(diǎn)號(hào) 分配磁盤索引節(jié)點(diǎn):把索引節(jié)點(diǎn)位圖的相應(yīng)位設(shè)置好,并把含有這個(gè)位圖的緩沖區(qū)標(biāo)記為“臟”把塊描述符的bg_free_inodes_count域減1。如果新的索引節(jié)點(diǎn)是個(gè)目錄,則增加bg_used_dirs_count

把磁盤超級(jí)塊的s_free_inodes_count減1, 初始化索引節(jié)點(diǎn)的域,如設(shè)置i_ino,把新的索引節(jié)點(diǎn)插入到inode_hashtable。 調(diào)用mark_inode_dirty()把這個(gè)索引節(jié)點(diǎn)對(duì)象移到超級(jí)塊的臟索引節(jié)點(diǎn)鏈表調(diào)用unlock_super()釋放超級(jí)塊對(duì)象 返回新索引節(jié)點(diǎn)對(duì)象的地址

創(chuàng)建文件系統(tǒng)ext2

初始化超級(jí)塊和組描述符 檢查是否有缺陷的塊,如果有,創(chuàng)建有缺陷塊的鏈表對(duì)每個(gè)塊組,保留存放超級(jí)塊、組描述符、索引節(jié)點(diǎn)表以及兩個(gè)位圖所需要的磁盤 把索引節(jié)點(diǎn)位圖和每個(gè)塊組的數(shù)據(jù)映射位圖初始化為0初始化每個(gè)塊組的索引節(jié)點(diǎn)鏈表 創(chuàng)建/root目錄 創(chuàng)建lost+found目錄(有缺失和缺陷的) 更新這個(gè)兩個(gè)塊組中的位圖把有缺陷的塊組織起來放在lost+found目錄中