《Linux原理與結(jié)構(gòu)》課件第3章_第1頁
《Linux原理與結(jié)構(gòu)》課件第3章_第2頁
《Linux原理與結(jié)構(gòu)》課件第3章_第3頁
《Linux原理與結(jié)構(gòu)》課件第3章_第4頁
《Linux原理與結(jié)構(gòu)》課件第3章_第5頁
已閱讀5頁,還剩197頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第三章引導(dǎo)與初始化3.1內(nèi)核引導(dǎo) 3.2實(shí)模式初始化 3.3內(nèi)核解壓縮 3.4內(nèi)核預(yù)初始化 3.5第0級初始化 3.6第1級初始化 3.7AP初始化與其它所有軟件一樣,Linux內(nèi)核也是從入口點(diǎn)開始執(zhí)行的。Linux內(nèi)核的入口點(diǎn)程序又稱為初始化程序,其任務(wù)是為Linux操作系統(tǒng)的運(yùn)行做好必要的準(zhǔn)備,如將內(nèi)核映像在物理內(nèi)存的適當(dāng)位置展開、獲取計(jì)算機(jī)各組成部分的配置參數(shù)、建立各種管理用的數(shù)據(jù)結(jié)構(gòu)、啟動各類守護(hù)進(jìn)程、建立人機(jī)交互環(huán)境等。

然而,Linux內(nèi)核是一個駐留在外存(如硬盤、光盤等)中的程序,在運(yùn)行之前必須先將其讀入物理內(nèi)存。將Linux內(nèi)核由外存讀入內(nèi)存的工作稱為引導(dǎo),完成引導(dǎo)工作的程序稱為引導(dǎo)程序。引導(dǎo)程序也是駐留在外存的程序,在運(yùn)行之前需要先將其讀入物理內(nèi)存。將引導(dǎo)程序讀入內(nèi)存的工作由BIOS(BasicInputOutputSystem)完成。BIOS駐留在非易失性內(nèi)存(如Rom、FlashMemory)中,不需要再被引導(dǎo)。引導(dǎo)程序在將Linux內(nèi)核讀入物理內(nèi)存之后,將控制權(quán)交給內(nèi)核頭部的實(shí)模式初始化程序。實(shí)模式初始化程序先完成實(shí)模式狀態(tài)下的初始化工作,再將處理器切換到保護(hù)模式,而后轉(zhuǎn)入解壓縮程序。解壓縮程序?qū)?nèi)核映像解壓到物理內(nèi)存的預(yù)定位置,而后將處理器的控制權(quán)交給內(nèi)核的首部程序。內(nèi)核首部程序完成內(nèi)核的正式初始化,引領(lǐng)內(nèi)核進(jìn)入正常工作狀態(tài)。

引導(dǎo)與初始化過程是操作系統(tǒng)中各種管理制度的創(chuàng)建過程,是運(yùn)行環(huán)境的建立過程,是理解操作系統(tǒng)的關(guān)鍵,也是分析操作系統(tǒng)的切入點(diǎn)。

當(dāng)計(jì)算機(jī)被加電或Reset時(shí),Intel處理器和系統(tǒng)芯片組會完成一系列內(nèi)建的自檢和初始化工作,如將處理器的操作模式設(shè)為實(shí)模式并關(guān)閉其分頁機(jī)制,將CS中的選擇符設(shè)為0xF000、基地址設(shè)為0xFFFF0000,將EIP的初值設(shè)為0x0000FFF0等。3.1內(nèi)核引導(dǎo)硬件初始化完成之后,處理器開始執(zhí)行指令,第一條指令的地址由CS和EIP決定,為0xFFFFFFF0。根據(jù)芯片組的初始約定,地址0xFFFFFFF0位于一塊FlashMemory中,是BIOS的入口。因此,系統(tǒng)開機(jī)以后,處理器最先執(zhí)行的是BIOS程序。

BIOS檢測整個計(jì)算機(jī)系統(tǒng),收集系統(tǒng)的基本配置信息并將其記錄在內(nèi)存的BIOS數(shù)據(jù)區(qū)中(如在物理內(nèi)存最低4KB處的基本數(shù)據(jù)、ACPI描述表、MP表等);而后根據(jù)系統(tǒng)的軟硬件配置信息,選擇引導(dǎo)設(shè)備(如硬盤、光盤、U盤等),并將其第一扇區(qū)中的內(nèi)容讀入到物理內(nèi)存的0x7C00處;最后跳轉(zhuǎn)到0x7C00,開始執(zhí)行第一扇區(qū)上的程序。由于BIOS只能讀入一個扇區(qū)的內(nèi)容,因而它無法擔(dān)負(fù)起引導(dǎo)操作系統(tǒng)的重任。

引導(dǎo)設(shè)備的第一扇區(qū)可能是硬盤的主引導(dǎo)記錄MBR(MasterBootRecord),也可能是引導(dǎo)程序(如GRUB)的首部。MBR會檢查硬盤的分區(qū)表,找到活動分區(qū),而后讀入活動分區(qū)的第一扇區(qū)(BootSector),再由該扇區(qū)上的程序讀入活動分區(qū)上的引導(dǎo)程序;引導(dǎo)程序首部會將自己的剩余部分讀入內(nèi)存??傊捎谙到y(tǒng)配置的不同,引導(dǎo)程序的讀入過程會有所不同,但從BIOS讀入的第一扇區(qū)開始,肯定能夠?qū)⑼暾囊龑?dǎo)程序讀入到物理內(nèi)存。引導(dǎo)程序的核心工作是將Linux內(nèi)核從外存讀入到內(nèi)存,并將其擺放在內(nèi)存的適當(dāng)位置。被編譯、連接后的Linux內(nèi)核是一個大的可執(zhí)行文件,由三部分組成:

(1)主體部分是將要在保護(hù)模式或64位模式中運(yùn)行的內(nèi)核映像,已被壓縮。

(2)在壓縮的內(nèi)核映像之前有一段解壓縮程序,用于解壓內(nèi)核映像。

(3)文件頭部是一段實(shí)模式初始化程序,用于將處理器切換到保護(hù)模式。由于處理器最初運(yùn)行在實(shí)模式中,能夠直接訪問的內(nèi)存空間只有1MB,因而引導(dǎo)程序必須將內(nèi)核的實(shí)模式初始化程序放在基本內(nèi)存(低640KB)中,但卻應(yīng)將內(nèi)核映像放置在1MB以上的位置。

為了縮減內(nèi)核規(guī)模,增加系統(tǒng)的靈活性和適應(yīng)性,Linux通常將其內(nèi)核分成基本部分和擴(kuò)展部分。基本內(nèi)核實(shí)現(xiàn)Linux內(nèi)核中最基本的管理功能,不隨計(jì)算機(jī)配置的變化而變化;擴(kuò)展內(nèi)核則由一些獨(dú)立的內(nèi)核模塊(如設(shè)備驅(qū)動程序、文件系統(tǒng)等)組成,用于擴(kuò)展基本內(nèi)核的功能,會隨著計(jì)算機(jī)配置的不同而變化。擴(kuò)展內(nèi)核及初始工具軟件被組織在獨(dú)立的initrd或Ramdisk映像文件中,需要引導(dǎo)程序?qū)⑵湟徊⒆x入內(nèi)存。

另外,在引導(dǎo)期間,引導(dǎo)程序還應(yīng)能與用戶交互,接受用戶提供的命令行參數(shù)(如顯示模式、內(nèi)存大小、initrd文件名等)并將它們傳遞給內(nèi)核,以指導(dǎo)內(nèi)核的初始化。

早期的Linux自己實(shí)現(xiàn)引導(dǎo)程序,如Bootsect。目前的Linux使用獨(dú)立的第三方引導(dǎo)程序,如GRUB。為了完成引導(dǎo)過程中的復(fù)雜交互,Linux專門定義了一個引導(dǎo)協(xié)議。引導(dǎo)程序根據(jù)引導(dǎo)協(xié)議的要求將內(nèi)核映像擺放在內(nèi)存中的指定位置,并向內(nèi)核傳遞必要的信息;Linux內(nèi)核根據(jù)引導(dǎo)協(xié)議和引導(dǎo)程序傳遞過來的信息,確定內(nèi)核各部分在內(nèi)存中的位置。隨著Linux的演化,它的引導(dǎo)協(xié)議也在不斷變化,最新版本(2.10)的引導(dǎo)協(xié)議所規(guī)定的內(nèi)存布局如圖3.1所示,其中的X值由引導(dǎo)程序決定,但應(yīng)盡可能小。圖3.1引導(dǎo)后的內(nèi)存布局

Linux引導(dǎo)協(xié)議由一組參數(shù)組成,位于實(shí)模式初始化程序中,讀入內(nèi)存后的位置在X+0x1F1處,其中各主要參數(shù)的意義如表3.1所示。在表3.1中,Offset是相對于X的偏移量,是各協(xié)議參數(shù)的存儲位置。

表3.1Linux引導(dǎo)協(xié)議參數(shù)區(qū)參數(shù)表中的有些參數(shù)是Linux內(nèi)核提供給引導(dǎo)程序的信息,如setup_sects是實(shí)模式初始化部分的大小、syssize是內(nèi)核主體部分的大小、loadflags告訴引導(dǎo)程序是否要將內(nèi)核放在1MB之上、relocatable_kernel表示是否允許重定位內(nèi)核的位置等;有些參數(shù)是引導(dǎo)程序提供給Linux內(nèi)核的信息,如cmd_line_ptr是命令行參數(shù)的位置、Ramdisk_image是initrd映像的位置、type_of_loader是引導(dǎo)程序的類型等;另一些參數(shù)是Linux內(nèi)核建議但最終是由引導(dǎo)程序確定的信息,如code32_start中的內(nèi)核主體部分入口地址等。試圖引導(dǎo)Linux的引導(dǎo)程序都必須遵循Linux的引導(dǎo)協(xié)議。事實(shí)上,很多引導(dǎo)程序都可以引導(dǎo)Linux內(nèi)核,如Bootsect(軟盤引導(dǎo)程序)、Lilo(早期的硬盤引導(dǎo)程序)、GRUB(又稱GNUGRUB2,是一種通用的引導(dǎo)程序)、Xen等。

引導(dǎo)程序根據(jù)自己的配置信息和Linux引導(dǎo)協(xié)議的約定,將Linux內(nèi)核映像和initrd映像一起讀入內(nèi)存,而后跳轉(zhuǎn)到X+0x200,開始執(zhí)行實(shí)模式初始化程序。

當(dāng)處理器離開引導(dǎo)程序時(shí),它仍然處于實(shí)模式,因而內(nèi)核的實(shí)模式初始化程序運(yùn)行在實(shí)模式中。內(nèi)核實(shí)模式初始化程序大致需要完成如下三項(xiàng)工作:3.2實(shí)模式初始化

1.收集系統(tǒng)參數(shù)

BIOS已經(jīng)對整個計(jì)算機(jī)系統(tǒng)進(jìn)行了全面檢測,并已將收集到的系統(tǒng)配置信息記錄在其數(shù)據(jù)區(qū)中。通過BIOS服務(wù)可獲得大部分的系統(tǒng)配置信息。由于BIOS服務(wù)只能在實(shí)模式中使用,所以應(yīng)該在內(nèi)核的實(shí)模式初始化部分收集或轉(zhuǎn)存這些信息。

需要收集的信息包括引導(dǎo)協(xié)議參數(shù)、CPU型號、計(jì)算機(jī)物理內(nèi)存的布局、顯示器的顯示模式、APM(高級電源管理)、EDD(EnhancedDiskDevice)等。在早期的版本中,收集到的信息被暫存在實(shí)模式代碼(Bootsect和setup)中。新版本在實(shí)模式和保護(hù)模式代碼中各定義了一個結(jié)構(gòu)boot_params,用于暫存收集到的系統(tǒng)參數(shù)。在上述參數(shù)中,比較重要的是物理內(nèi)存的布局信息。目前的BIOS大都提供了多個系統(tǒng)服務(wù),用于查詢系統(tǒng)物理內(nèi)存的配置信息。其中,通過int0x15的0xe820功能可以獲得一個描述物理內(nèi)存布局的結(jié)構(gòu)數(shù)組,該數(shù)組中的一個結(jié)構(gòu)描述系統(tǒng)中的一段物理內(nèi)存,包括它的類型(常規(guī)內(nèi)存、預(yù)留內(nèi)存、不可用內(nèi)存等)、起始地址和大小等。

2.設(shè)置基本工作環(huán)境

為使初始化程序能正常工作,實(shí)模式初始化程序需設(shè)置基本的工作環(huán)境,包括實(shí)模式初始化程序?qū)⒁褂玫亩褩?、?jì)算機(jī)鍵盤的重復(fù)頻率、顯示器的顯示模式、老式的中斷控制器(即8259A)、A20門、協(xié)處理器(FPU)等。

設(shè)置中斷控制器的目的是為了關(guān)閉中斷。在內(nèi)核初始化的初期,系統(tǒng)無法正常處理中斷,因而只能將其關(guān)閉。關(guān)閉中斷的方法包括兩種,一是讓處理器不響應(yīng)中斷(CLI),二是讓控制器不產(chǎn)生中斷。不可屏蔽中斷(NMI)也要一并關(guān)閉。

使用A20門的目的是為了能夠正確地訪問所有的物理地址空間。

3.切換處理器操作模式

實(shí)模式初始化部分最重要的工作是將處理器由實(shí)模式切換到保護(hù)模式。當(dāng)然,在模式切換之前需要建立一個GDT表。系統(tǒng)最終使用的GDT表定義在內(nèi)核的主體部分,但現(xiàn)在還無法使用,因而只能先建立一個臨時(shí)的GDT表。臨時(shí)的GDT表中至少應(yīng)該有一個代碼段描述符和一個數(shù)據(jù)段描述符。代碼段應(yīng)是可讀、可執(zhí)行的,其基地址為0,大小為4GB。數(shù)據(jù)段應(yīng)是可讀、可寫的,其基地址為0,大小為4GB。如果需要,還可以設(shè)置其它的描述符,如TSS描述符等。將GDT表的基地址和大小裝入GDTR寄存器,將IDTR的基地址和界限都設(shè)為0,將CR0寄存器中的PE位置1,即可將處理器切換到保護(hù)模式。如果必要,還可以設(shè)置LDTR和TR寄存器。

進(jìn)入保護(hù)模式后,應(yīng)立刻跳轉(zhuǎn)到解壓縮程序的入口,開始內(nèi)核映像的解壓縮工作。

Linux內(nèi)核可被任何一種算法壓縮,常用的有g(shù)zip、bzip2、lzma等。

解壓縮程序的主要工作是將壓縮后的內(nèi)核映像解開并擺放在物理內(nèi)存的適當(dāng)位置。老版本Linux要求將解壓后的內(nèi)核擺放在物理內(nèi)存的0x100000(1MB)處,新版本Linux要求將解壓后的內(nèi)核擺放在物理內(nèi)存的0x1000000(16MB)處。這一改變可以節(jié)省16MB以下的物理內(nèi)存,以便為DMA騰出更大的自由空間。如果引導(dǎo)協(xié)議允許,解壓縮程序可以根據(jù)實(shí)際情況重新選擇解壓后的內(nèi)核擺放位置。3.3內(nèi)?核?解?壓?縮解壓縮后的內(nèi)核結(jié)構(gòu)如圖3.2所示。隨著Linux內(nèi)核的演化,其中的節(jié)越分越多、越分越細(xì),目前已達(dá)100多個。需加載的節(jié)已被鏈接器整合在兩個大的加載段(data和text)中,每個段中都包含若干個節(jié)。

圖3.2內(nèi)核代碼與數(shù)據(jù)的組成格式在圖3.2中,內(nèi)核頭部是定義在“.text.head”節(jié)中的代碼,負(fù)責(zé)內(nèi)核的預(yù)初始化工作;內(nèi)核主體代碼主要定義在“.text”節(jié)中,是內(nèi)核代碼的主體部分;異常列表定義在“_

_ex_table”節(jié)中,用于記錄各內(nèi)核預(yù)留異常的發(fā)生位置及處理程序入口地址;只讀數(shù)據(jù)是內(nèi)核不再修改的數(shù)據(jù),如內(nèi)核參數(shù)等;內(nèi)核主體數(shù)據(jù)是內(nèi)核定義的帶初值的全局或靜態(tài)變量,可讀、可寫,包括init_task進(jìn)程(第0號進(jìn)程)的管理結(jié)構(gòu)、IDT表等;內(nèi)核初始化代碼和數(shù)據(jù)僅在內(nèi)核初始化期間使用,在初始化工作完成之后可被釋放;BSS數(shù)據(jù)定義在“.bss”節(jié)中,是內(nèi)核使用的、未指定初值的全局和靜態(tài)變量;堆數(shù)據(jù)中包含初始的頁表。在完成解壓工作之后,系統(tǒng)跳轉(zhuǎn)到解壓后的內(nèi)核頭部,開始內(nèi)核的預(yù)初始化工作。

內(nèi)核頭部仍然是一段匯編程序,負(fù)責(zé)內(nèi)核的預(yù)初始化工作,包括切換GDT表、啟動分頁機(jī)制、切換系統(tǒng)堆棧等。按照C語言的約定,BSS節(jié)中各變量的初值應(yīng)該是0,因而應(yīng)先將內(nèi)核的BSS節(jié)全部清0。3.4內(nèi)核預(yù)初始化

1.轉(zhuǎn)移系統(tǒng)參數(shù)

實(shí)模式初始化部分收集到的系統(tǒng)參數(shù)被暫存在實(shí)模式的boot_params結(jié)構(gòu)中,需要將其拷貝到內(nèi)核的boot_params結(jié)構(gòu)中備用。如果引導(dǎo)程序傳遞了命令行參數(shù),應(yīng)將其拷貝到字符數(shù)組boot_command_line中。

2.確定處理器的型號

Intel處理器有多種型號,如386、486、Pentium等,不同型號的處理器之間有一些差別。Linux需要知道它正在使用的處理器(BSP)的型號,包括協(xié)處理器的型號。486以后的處理器提供了一條CPUID指令,可用于查詢有關(guān)處理器的信息。初始化程序?qū)@得的處理器信息記錄在一個專門的結(jié)構(gòu)cpuinfo_x86中,并根據(jù)處理器型號,設(shè)置CR0寄存器中的AM、WP、NE、MP、ET、EM等標(biāo)志。

3.啟動分頁機(jī)制

按照鏈接腳本(文件vmlinux.lds.S)的約定,Linux內(nèi)核的起始邏輯地址被定義在LOAD_OFFSET+LOAD_PHYSICAL_ADDR。由于處理器所用代碼段和數(shù)據(jù)段的基地址都是0,因而邏輯地址等于線性地址,與物理地址之間相差LOAD_OFFSET(在32位機(jī)器上為0xC0000000)。地址之間的這一差別給程序的執(zhí)行和數(shù)據(jù)的訪問都帶來了困難,經(jīng)常需要進(jìn)行手工地址轉(zhuǎn)換,因而應(yīng)盡快啟動分頁機(jī)制。

在啟動分頁機(jī)制之前需要先建立頁目錄和頁表。由于目前的Intel處理器可以支持二級(32位)、三級(36位)和四級(64位)頁表,因此頁目錄、頁表的建立方式有很大區(qū)別。由于預(yù)初始化程序還未分析內(nèi)存的配置情況,所以無法建立完整的頁目錄和頁表。事實(shí)上,目前建立的頁目錄、頁表僅用于執(zhí)行內(nèi)核程序、訪問內(nèi)核數(shù)據(jù),因而只要涵蓋內(nèi)核當(dāng)前所用的線性地址空間即可。Linux2.2,僅預(yù)建了1個頁表,可用于訪問最低端的4MB內(nèi)存;Linux2.4,預(yù)建了2個頁表,可用于訪問最低端的8MB內(nèi)存;Linux2.6,預(yù)建了1個頁目錄,但可根據(jù)內(nèi)核所在位置及大小動態(tài)地建立多個頁表,可用于訪問低端的64MB、128MB或1GB的內(nèi)存。圖3.3是缺省情況下Linux為普通32位處理器所建立的二級頁表,其頁目錄稱為swapper_pg_dir。

圖3.3Linux建立的二級頁表由圖3.3可以看出,在4GB的邏輯(或虛擬)地址空間中,內(nèi)核占用了高端的1GB(對應(yīng)頁目錄中的768~1023),為用戶保留了低端的3

GB。圖3.3中的頁目錄頁表可在線性地址區(qū)間[0xC0000000,0xFFFFFFFF]與物理地址區(qū)間[0,0x3FFFFFFF]之間建立一一對應(yīng)的映射關(guān)系,相當(dāng)于自動地將線性地址減去0xC0000000。

在64位處理器上,目前可用的地址空間有256TB,內(nèi)核與用戶各占128TB,內(nèi)核的起始邏輯地址被定義在0xffffffff80000000+LOAD_PHYSICAL_ADDR處。將頁目錄swapper_pg_dir的物理地址加載進(jìn)CR3寄存器、將CR0寄存器中的PG位置1即可啟動分頁機(jī)制。如有必要,還應(yīng)設(shè)置CR4寄存器中的某些標(biāo)志位,如PSE、PAE等。如需啟動執(zhí)行禁止機(jī)制,還應(yīng)設(shè)置EFER寄存器。

4.更換系統(tǒng)堆棧

預(yù)初始化程序使用的是解壓縮程序的堆棧,即將被釋放,因而需要為系統(tǒng)更換一個堆棧。雖然可以再創(chuàng)建一個臨時(shí)的堆棧,但也可以一步到位,直接使用init_task進(jìn)程的系統(tǒng)堆棧。事實(shí)上,Linux為每個進(jìn)程都創(chuàng)建了一個系統(tǒng)堆棧,其大小是8KB(或4KB)。進(jìn)程的系統(tǒng)堆棧都是動態(tài)創(chuàng)建的,只有init_task進(jìn)程的系統(tǒng)堆棧是靜態(tài)建立的,位于一個專門的節(jié)(.data.init_task)中,其前部的thread_info結(jié)構(gòu)也已被初始化。

5.重置GDT、IDT、LDT

在進(jìn)入正常工作之前,需為各處理器創(chuàng)建永久性的GDT、IDT和LDT。

在Linux發(fā)展過程中,雖然GDT表的格式在不斷變化,但最基本的4個段始終保留,即內(nèi)核代碼與數(shù)據(jù)段和用戶代碼與數(shù)據(jù)段。在32位處理器上,這4個段的基地址都是0,大小都是4GB,可用于實(shí)現(xiàn)保護(hù)平板式內(nèi)存管理機(jī)制。當(dāng)處理器在內(nèi)核空間運(yùn)行時(shí),它的CS為內(nèi)核代碼段、SS為內(nèi)核數(shù)據(jù)段,DS和ES為用戶數(shù)據(jù)段。當(dāng)處理器在用戶空間運(yùn)行時(shí),它的CS為用戶代碼段,SS、DS、ES為用戶數(shù)據(jù)段。早期的Linux在GDT中為每個進(jìn)程預(yù)留一個TSS和一個LDT描述符。系統(tǒng)每創(chuàng)建一個進(jìn)程都要在GDT中填寫兩個描述符,因而GDT的大小限制了進(jìn)程的個數(shù)。2.4版之后的Linux修正了這一做法,僅在GDT表中保留一個TSS和一個LDT描述符,用于描述當(dāng)前進(jìn)程。在進(jìn)程切換時(shí),不再切換TR,而是修改TSS段的內(nèi)容。這一修正消除了GDT對進(jìn)程個數(shù)的限制。Linux為32位處理器定義的GDT如表3.2所示。

表3.2GDT的結(jié)構(gòu)及初值上述GDT表定義在PERCPU數(shù)據(jù)區(qū)中,每個處理器一個,其初值都一樣。AP處理器啟動之后,將使用自己的GDT表,且可根據(jù)需要對其進(jìn)行修改。段描述符per_cpu是用來定位PERCPU數(shù)據(jù)區(qū)的,段描述符stack_canary-20是用來做堆棧保護(hù)的。在不同處理器所使用的GDT表中,這兩個段描述符的定義是不同的。

缺省的IDT表(idt_table)定義在專門的節(jié)“.data.idt”中。IDT表已被初始化,其中的256個描述符已被設(shè)置成同一個缺省的中斷門。缺省中斷門指向程序ignore_int,該程序并不處理中斷,僅向用戶報(bào)告說“收到一個不可知的中斷或異?!?。

6.預(yù)留內(nèi)存空間

在進(jìn)入真正的初始化程序之前,需將已占用的物理內(nèi)存區(qū)間記錄下來,以免再被它用。預(yù)初始化程序?qū)⒁颜加玫奈锢韮?nèi)存區(qū)間記錄在數(shù)組early_res中,包括BIOS數(shù)據(jù)區(qū)、擴(kuò)展BIOS數(shù)據(jù)區(qū)(EBDA)、內(nèi)核代碼、內(nèi)核數(shù)據(jù)、initrd映像、跳板程序trampoline、直接映射頁表、Bootmem頁位圖、BADRAM、堆(BRK)等。

在多處理器環(huán)境中,當(dāng)BSP進(jìn)行內(nèi)核預(yù)初始化時(shí),各AP都停止在實(shí)模式中。當(dāng)AP被喚醒時(shí),它們只能執(zhí)行實(shí)模式程序,因此需要在基本物理內(nèi)存(<=640KB)中為AP準(zhǔn)備一個跳板,即預(yù)留一段實(shí)模式初始化程序。

真正的內(nèi)核初始化工作十分繁雜,涉及到系統(tǒng)的方方面面,需要分步實(shí)施。初始化工作應(yīng)該在進(jìn)程中完成,不應(yīng)獨(dú)立于進(jìn)程之外。為此,Linux創(chuàng)建了兩個進(jìn)程,用于完成真正的系統(tǒng)初始化工作。第0號進(jìn)程(即init_task)是靜態(tài)建立的,它完成最基本的內(nèi)核初始化工作,稱為第0級初始化。第1號進(jìn)程是由第0號進(jìn)程動態(tài)創(chuàng)建的,完成剩余的內(nèi)核初始化工作,而后將自己轉(zhuǎn)變成系統(tǒng)中的第一個用戶態(tài)進(jìn)程,進(jìn)而完成用戶態(tài)的初始化工作。第1號進(jìn)程完成的初始化稱為第1級初始化。3.5第0級初始化第0級初始化完成的主要工作包括獲取系統(tǒng)配置信息、設(shè)置IDT表、啟動內(nèi)存管理器、啟動第0號進(jìn)程、創(chuàng)建第1號進(jìn)程等。

1.建立處理器管理結(jié)構(gòu)

在多處理器環(huán)境中,需要為每個邏輯處理器建立一個cpuinfo_x86結(jié)構(gòu),用于記錄處理器的廠家、型號、處理能力、已知Bug等信息。除此之外,還需要定義其它幾個結(jié)構(gòu)用以描述各邏輯處理器的配置情況,包括:

位圖cpu_possible_map,描述各邏輯處理器的存在情況(系統(tǒng)中有哪些處理器)。位圖cpu_present_map,描述各邏輯處理器的使能情況(哪些處理器可用)。

位圖cpu_online_map,描述各邏輯處理器的在線情況(哪些處理器可用于調(diào)度)。

位圖cpu_active_map,描述各邏輯處理器的活動情況(哪些處理器可用于遷移)。另外,結(jié)構(gòu)cpu_dev用于描述處理器家族信息,其中記錄著處理器的廠家、標(biāo)識、家族及家族中所有產(chǎn)品的型號,還包含四個函數(shù),用于實(shí)現(xiàn)處理器的預(yù)初始化和初始化操作。Linux已為各廠家(如Intel、AMD、Cyrix、Transmeta等)的處理器預(yù)定義了cpu_dev結(jié)構(gòu),它們的初值被集中在“.x86_cpu_dev.init”節(jié)中。根據(jù)當(dāng)前處理器的標(biāo)識信息確定它所屬的家族,為其選擇一個cpu_dev結(jié)構(gòu),將其稱為this_cpu。

2.獲取物理內(nèi)存配置信息

由于歷史的原因,在Intel系列的計(jì)算機(jī)系統(tǒng)中,物理內(nèi)存空間中640KB~1MB之間的一段已被占用,其中包含影子BIOS、設(shè)備擴(kuò)展ROM、顯示緩存等。在隨后的發(fā)展中,BIOS在物理內(nèi)存空間中預(yù)留的信息越來越多,如ACPI描述表、MP表等,進(jìn)一步加劇了物理內(nèi)存空間的碎化。為了讓操作系統(tǒng)能夠正確地使用物理內(nèi)存,BIOS提供了e820服務(wù),用于向操作系統(tǒng)報(bào)告物理內(nèi)存空間的詳細(xì)布局信息。在實(shí)模式初始化程序中,已經(jīng)通過BIOS的int15h服務(wù)對系統(tǒng)的物理內(nèi)存空間進(jìn)行了檢測,所獲得的信息已被轉(zhuǎn)存到boot_params的e820_map數(shù)組中。由于BIOS的報(bào)告可能不太準(zhǔn)確,因而需要對其進(jìn)行一致性檢查和修正(如去除重疊部分、進(jìn)行重新排序等)。修正后的內(nèi)存布局信息保存在e820map類型的結(jié)構(gòu)變量e820中。結(jié)構(gòu)e820map的主體部分是一個類型為e820entry的數(shù)組,其中的每一項(xiàng)描述一段連續(xù)的物理內(nèi)存區(qū)間,包括它的開始地址、大小及類型。結(jié)構(gòu)e820map的定義如下:

structe820map{

intnr_map; //e820entry結(jié)構(gòu)的實(shí)際數(shù)量

structe820entry{

unsignedlonglongaddr; //物理內(nèi)存區(qū)間的開始地址

unsignedlonglongsize; //物理內(nèi)存區(qū)間的大小

unsignedlong type; //物理內(nèi)存區(qū)間的類型

}map[E820MAX]; //最多128

};

BIOS將系統(tǒng)的物理內(nèi)存區(qū)間分為四大類,分別是可用RAM(E820_RAM)、保留RAM(E820_RESERVED)、ACPI數(shù)據(jù)(E820_ACPI,保存ACPI描述表)和ACPI非易失數(shù)據(jù)(E820_NVS,暫存進(jìn)入睡眠狀態(tài)之前的系統(tǒng)信息以便恢復(fù))。

如果引導(dǎo)協(xié)議中有setup_data參數(shù)(e820擴(kuò)展信息),則應(yīng)根據(jù)該參數(shù)修正變量e820;如果命令行參數(shù)中有對物理內(nèi)存的設(shè)置信息,如可用物理內(nèi)存大小、高端內(nèi)存大小、valloc區(qū)大小、ACPI參數(shù)等,則應(yīng)根據(jù)這些信息再次修正變量e820。在Bootmem啟動之前,Linux將利用變量e820分配物理內(nèi)存空間。方法是搜索e820中的map數(shù)組,找一塊類型為E820_RAM、大小合適且未被使用的物理內(nèi)存區(qū)間,將其分配給用戶,同時(shí)將該區(qū)間的信息記錄在數(shù)組early_res中。

3.轉(zhuǎn)存系統(tǒng)參數(shù)

Linux內(nèi)核的系統(tǒng)參數(shù)已被轉(zhuǎn)存到結(jié)構(gòu)變量boot_params中,其中包括引導(dǎo)程序傳遞過來的參數(shù)和實(shí)模式初始化程序檢測到的參數(shù),如根設(shè)備的設(shè)備號、顯示器配置信息、APM信息、引導(dǎo)程序類型、initrd信息等,需將這些參數(shù)分別轉(zhuǎn)移到相應(yīng)的內(nèi)核變量中備用。此后,變量boot_params所占用的內(nèi)存空間被釋放。

4.記錄內(nèi)核位置

Linux的所有程序都運(yùn)行在進(jìn)程中,包括它的內(nèi)核。事實(shí)上,每個進(jìn)程的地址空間中都包含一個內(nèi)核,或者說Linux內(nèi)核可運(yùn)行在任何一個進(jìn)程的地址空間中。在進(jìn)行第0級初始化時(shí),系統(tǒng)中僅有1個進(jìn)程,即第0號進(jìn)程或init_task進(jìn)程,可以認(rèn)為當(dāng)前的內(nèi)核正運(yùn)行在第0號進(jìn)程的地址空間中。

Linux的進(jìn)程由task_struct結(jié)構(gòu)描述,每個進(jìn)程1個。第0號進(jìn)程的init_task結(jié)構(gòu)是靜態(tài)建立的,定義在專門的節(jié)中。在task_struct結(jié)構(gòu)中記錄了進(jìn)程使用的虛擬內(nèi)存信息,包括它的代碼開始位置、終止位置,數(shù)據(jù)開始位置、終止位置,堆的開始位置等。對用戶進(jìn)程來說,這些信息描述了它的應(yīng)用程序及其數(shù)據(jù)在虛擬地址空間中的位置。由于第0號進(jìn)程只在內(nèi)核中運(yùn)行,永遠(yuǎn)都不會執(zhí)行應(yīng)用程序,因而可在它的task_struct結(jié)構(gòu)中記錄內(nèi)核的位置。內(nèi)核各部分的位置已被鏈接器記錄在特定的變量中,如_text、_etext、_edata、_brk_end等,將它們轉(zhuǎn)存到第0號進(jìn)程的task_struct結(jié)構(gòu)中即可。值得注意的是,內(nèi)核的堆被定義在一個專門的節(jié)中,位于內(nèi)核映像的尾部。

5.映射低端物理內(nèi)存

映射低端物理內(nèi)存的目的是給它們分配內(nèi)核線性地址,以便能在內(nèi)核中訪問它們。

早期的Linux所面對的物理內(nèi)存較小(數(shù)兆字節(jié)到數(shù)百兆字節(jié)),可以將所有的物理頁直接映射在內(nèi)核線性地址空間(3GB到4GB)中,即可以在內(nèi)核線性地址空間中為每個物理地址預(yù)先分配一個線性地址,因而在內(nèi)核中可以直接訪問到所有的物理內(nèi)存。當(dāng)物理內(nèi)存量超過1GB時(shí),無法再將它們?nèi)坑成涞絻?nèi)核線性地址空間中,也就是說必然會存在一部分沒有分配到內(nèi)核線性地址的物理內(nèi)存。當(dāng)內(nèi)核需要訪問這部分物理內(nèi)存時(shí),只能臨時(shí)給它們分配一個內(nèi)核線性地址,因此在內(nèi)核線性地址空間中需為這部分內(nèi)存預(yù)留一部分線性地址。另外,在內(nèi)核線性地址空間中還要為一些常用的固定物理地址(如APIC、HPET的寄存器等)預(yù)留線性地址。因而,在32位處理器上,能夠直接映射到內(nèi)核線性地址空間中的物理內(nèi)存量要遠(yuǎn)小于1GB。

當(dāng)然,由于64位處理器的線性地址空間十分巨大,可以為內(nèi)核預(yù)留足夠大的線性地址空間(如128TB),為每個物理地址預(yù)分配一個內(nèi)核線性地址已經(jīng)不再成為問題。在32位處理器上,由于線性地址空間的限制,Linux只能將物理內(nèi)存分成兩部分。有永久內(nèi)核線性地址的部分稱為低端內(nèi)存,無永久內(nèi)核線性地址的部分稱為高端內(nèi)存。Linux定義了兩個宏和多個全局變量來描述物理內(nèi)存的配置情況:

MAXMEM:可直接映射到內(nèi)核線性地址空間的最大物理內(nèi)存容量,不超過896MB。

MAXMEM_PFN:可直接映射到內(nèi)核線性地址空間的最大物理內(nèi)存頁數(shù)。

max_pfn:系統(tǒng)可用的最大一個物理內(nèi)存頁的頁號+1。

max_low_pfn:可直接映射到內(nèi)核線性地址空間的最大物理內(nèi)存頁數(shù),也就是可直接映射到內(nèi)核線性地址空間的最大物理內(nèi)存頁的頁號+1。如物理內(nèi)存較多,max_low_pfn就是MAXMEM_PFN;否則,max_low_pfn應(yīng)小于或等于max_pfn。

highstart_pfn:高端內(nèi)存的開始頁號,通常就是max_low_pfn。

highend_pfn:高端內(nèi)存的終止頁號,通常就是max_pfn。

high_memory:高端內(nèi)存的開始線性地址,就是第highstart_pfn頁的線性地址。

通過設(shè)置頁目錄swapper_pg_dir及其頁表,可以為各低端物理內(nèi)存頁(在0~max_low_pfn之間)建立永久性的直接映射關(guān)系,即將這部分物理內(nèi)存直接映射到線性地址空間的3GB以上位置(頁目錄的768~1023之間)。做這種直接映射的目的僅僅是為了能在內(nèi)核中直接訪問這部分物理內(nèi)存空間,并不涉及到頁面的交換,因而可以將它們都設(shè)置成大尺寸頁面(4MB或2MB頁),以減少TLB的消耗,加快地址轉(zhuǎn)換的速度。但最低端的4MB或2MB物理內(nèi)存是一個例外,仍然應(yīng)該使用4KB頁。各頁的屬性中應(yīng)包括P、RW、D、A、G等標(biāo)志,大頁面中還應(yīng)包括PS標(biāo)志。除內(nèi)核代碼之外,其它頁都應(yīng)該是禁止執(zhí)行的數(shù)據(jù)頁。

3~4GB之間的內(nèi)核線性地址空間的布局情況如圖3.4所示。如此設(shè)置的頁目錄swapper_pg_dir是BSP目前使用的頁目錄,也是第0號進(jìn)程的頁目錄。

圖3.4內(nèi)核線性地址空間的布局在圖3.4中,“FixedVirtualAddress”是為固定物理地址預(yù)留的內(nèi)核線性地址(內(nèi)核通過它們可訪問到某些固定的物理地址,如APIC寄存器等),“kmap空間”是為高端內(nèi)存預(yù)留的臨時(shí)內(nèi)核線性地址,“vmalloc空間”是為邏輯內(nèi)存管理器預(yù)留的內(nèi)核線性地址,灰色部分是空洞,用于各地址空間之間的隔離??斩错摰捻摫砘蝽撃夸涰?xiàng)是空的,對它們的訪問會引起頁故障異常。

“引導(dǎo)時(shí)映射空間”的作用與“kmap空間”相同。在系統(tǒng)初始化完成之前,Linux用“引導(dǎo)時(shí)映射空間”為高端物理內(nèi)存臨時(shí)分配內(nèi)核線性地址。需要為FixedVirtualAddress單獨(dú)建立一個頁表,其頁表項(xiàng)都是全局的,不會被刷新。

需要對BSP處理器的EFER、CR4寄存器進(jìn)行適當(dāng)?shù)脑O(shè)置,以便使能寄存器執(zhí)行禁止、大物理頁(4MB或2MB頁)、全局頁等。

6.預(yù)留initrd空間

如果引導(dǎo)程序?qū)nitrd映像放在了高端物理內(nèi)存空間中,則應(yīng)將它移到低端內(nèi)存空間中。全局變量initrd_start和initrd_end指向initrd映像在低端內(nèi)存空間中的位置。

7.確定系統(tǒng)配置信息的獲取方法

除了處理器和物理內(nèi)存之外,操作系統(tǒng)還需要知道計(jì)算機(jī)系統(tǒng)的其它配置信息。大部分這類的配置信息都由BIOS提供,BIOS按標(biāo)準(zhǔn)組織它們,如DMI、MP、ACPI等。

DMI是由行業(yè)指導(dǎo)機(jī)構(gòu)DMTF起草的開放性技術(shù)標(biāo)準(zhǔn),可以向操作系統(tǒng)提供BIOS、主板、機(jī)箱、設(shè)備、整機(jī)等方面的配置信息,目前已基本不再使用。從P6開始,Intel在其IA-32體系結(jié)構(gòu)中引入了一個多處理器初始化協(xié)議規(guī)范(IntelMultiprocessingSpecification,MP),定義了多處理機(jī)系統(tǒng)中的配置信息存儲格式。SMP系統(tǒng)中的BIOS已經(jīng)在物理內(nèi)存中創(chuàng)建了一個多處理機(jī)配置表,其表頭稱為“MPFloatingPointerStructure”,可能出現(xiàn)在物理內(nèi)存的0~1KB處、639~640KB處、0xF0000~0xFFFFF處,或EBDA的最初1KB處。通過配置表頭可以找到MP的基本配置表,其中包含有處理器、總線、I/OAPIC、LocalAPIC等的配置信息?;九渲帽砗笫菙U(kuò)展配置表,其中包含各總線上的地址空間配置信息、總線層次結(jié)構(gòu)等。

MP依然存在,但主要用于多處理器的初始化,它在配置管理方面的工作正逐漸被ACPI取代。ACPI(AdvancedConfigurationandPowerInterface)是由HP、Intel、Microsoft、Phoenix等公司開發(fā)的高級配置與電源管理接口,是MP和APM等標(biāo)準(zhǔn)的升級。

ACPI的作用主要有兩個:一是電源管理,二是配置管理。與APM不同,ACPI將電源管理工作完全交給了操作系統(tǒng),BIOS已基本不再參與。操作系統(tǒng)可以根據(jù)某種策略動態(tài)地調(diào)整處理器、設(shè)備乃至整個系統(tǒng)的運(yùn)行狀態(tài),以便在保證服務(wù)質(zhì)量的前提下盡可能地節(jié)約電能。ACPI提供的配置管理包括兩方面的內(nèi)容,一是向操作系統(tǒng)提供系統(tǒng)的配置信息,包括靜態(tài)配置信息(如系統(tǒng)的組成、組織關(guān)系、資源情況等)和動態(tài)配置信息(如設(shè)備的插拔情況等),二是允許操作系統(tǒng)動態(tài)地枚舉和配置主板上的設(shè)備。另外,ACPI還提供了一個事件模型,用于將即插即用、溫控、電源管理等事件及時(shí)地通知操作系統(tǒng),通知的手段是系統(tǒng)控制中斷SCI(SystemControlInterrupt)。

圖3.5ACPI體系結(jié)構(gòu)

ACPI硬件包括集成在芯片組(如南橋)中的控制邏輯和集成在設(shè)備中的接口電路,它們被抽象成不同種類的寄存器塊。ACPI的寄存器可大致分成兩大類。狀態(tài)寄存器中記錄硬件的當(dāng)前狀態(tài),它與使能寄存器一起決定何時(shí)生成通知事件(SCI中斷);控制寄存器用于接收操作命令,并根據(jù)命令控制硬件的行為。在所有的硬件中,有些是ACPI必須的,且特性已明確定義,如電源管理定時(shí)器、電源按鈕等,這類硬件稱為固定(Fixed)硬件。對固定硬件的管理可通過直接操作其寄存器塊實(shí)現(xiàn)。固定硬件的寄存器塊包括PM1事件寄存器塊、PM1控制寄存器塊、PM2控制寄存器塊、PM定時(shí)器寄存器塊、處理器控制寄存器塊等。

大部分硬件的特性是由其它標(biāo)準(zhǔn)定義的,ACPI所關(guān)心的僅是這些硬件中與電源管理相關(guān)的增值部分,這類硬件稱為通用(Generic)硬件。ACPI利用對象描述通用硬件的特性,并通過名字空間將各對象組織起來。通用硬件的狀態(tài)寄存器被分層組織,并最終連接到位于頂層的通用事件寄存器塊中,用于生成通知事件。通用硬件中的控制寄存器只能通過對象中的控制方法訪問。當(dāng)收到通知時(shí),操作系統(tǒng)查閱通用事件寄存器塊中的狀態(tài)寄存器可獲得事件產(chǎn)生的原因,查閱名字空間可找到事件源的描述對象,調(diào)用與之關(guān)聯(lián)的控制方法即可訪問控制寄存器,從而完成對通用硬件的控制操作。通用硬件的特性及控制方法由廠商提供,由AML(ACPIMachineLanguage)代碼描述,被組織在特定的描述表中。

ACPI固件(Firmware)主要由支持ACPI的BIOS組成。在系統(tǒng)啟動時(shí),ACPIBIOS會按照已有的標(biāo)準(zhǔn)檢測整個計(jì)算機(jī)系統(tǒng),收集、整理有關(guān)的配置信息,并將其保存在內(nèi)存中的一組ACPI描述表中。

ACPI描述表是ACPI機(jī)制的核心,它描述系統(tǒng)的信息、特性及這些特性的控制方法,是操作系統(tǒng)獲取配置信息的主要途徑。ACPI的描述表很多,包括RSDT(RootSystemDescriptionTable)、XSDT(ExtendedSystemDescriptionTable)、FADT(FixedACPIDescriptiontable)、FACS(FirmwareACPIControlStructure)、DSDT(DifferentiatedSystemDescriptionTable)、SSDT(SecondarySystemDescriptionTable)、MADT(MultipleAPICDescriptionTable)等,它們被組織成一個樹形結(jié)構(gòu),如圖3.6所示。

圖3.6ACPI描述表結(jié)構(gòu)表XSDT或RSDT中包含一個指針數(shù)組,其中的每個指針都指向另一個描述表,如FADT、MADT等。描述表XSDT是RSDT的擴(kuò)展,兩者的格式完全一樣,所不同的是RSDT中的地址是32位的,而XSDT中的地址是64位的。目前通常用XSDT。

FADT表中包含一些靜態(tài)的ACPI硬件特性,如SCI的中斷向量號、硬件寄存器塊的大小與位置、FACS與DSDT表的開始地址等。FADT所描述的硬件寄存器塊包括兩組事件寄存器塊(PM1a和PM1b)、三組控制寄存器塊(PM1a、PM1b和PM2)、定時(shí)器寄存器塊、處理器控制寄存器塊(包括處理器性能控制和睡眠狀態(tài)控制寄存器)、通用事件寄存器塊(GPE0、GPE1)等,這些寄存器塊的意義已經(jīng)預(yù)定,開始地址也不許再改變。

ACPI描述表在內(nèi)存中的位置由結(jié)構(gòu)RSDP描述。如果系統(tǒng)支持ACPI,搜索物理內(nèi)存空間肯定能找到結(jié)構(gòu)RSDP(在EBDA的前1KB或0xE0000~0xFFFFF之間)。分析RSDP結(jié)構(gòu)可獲得XSDT或RSDT表的開始地址(如有XSDT則不用RSDT)。分析XSDT、RSDT和FADT可獲得其余各表的開始地址,進(jìn)而可獲得各表的描述信息。

為便于使用,Linux將各ACPI描述表的表頭信息全部轉(zhuǎn)存到結(jié)構(gòu)數(shù)組initial_tables[]中。數(shù)組initial_tables的定義如下:

structacpi_table_desc{

acpi_physical_address address; //表的開始地址

structacpi_table_header*pointer; //表頭結(jié)構(gòu),通常是臨時(shí)分配的

u32 length; //表長

unionacpi_name_unionsignature; //表的識別標(biāo)志,如“DSDT”

acpi_owner_id owner_id; //擁有者

u8 flags; //標(biāo)志

};

structacpi_table_descinitial_tables[ACPI_MAX_TABLES]_initdata;

操作系統(tǒng)內(nèi)核需要實(shí)現(xiàn)AML解釋器、ACPI驅(qū)動、ACPI表管理、名字空間管理、ACPI硬件管理、資源管理、事件管理等模塊,以支持ACPI規(guī)范。

8.啟動Bootmem內(nèi)存管理器

Bootmem是在Linux2.4中引入的一種物理內(nèi)存管理器,目的是為內(nèi)核初始化提供內(nèi)存支持。在此前的版本中,內(nèi)核初始化期間未使用內(nèi)存管理器,所需內(nèi)存都是從已知物理內(nèi)存空間中直接劃出來的,不方便也不靈活。由于Bootmem僅用于系統(tǒng)初始化,因而只需管理低端物理內(nèi)存即可。

Bootmem使用的管理結(jié)構(gòu)如下:

typedefstructbootmem_data{

unsignedlong node_min_pfn; //開始位置的物理頁號,如0

unsignedlong node_low_pfn; //終止位置的物理頁號,如max_low_pfn

void *node_bootmem_map; //頁位圖

unsignedlong last_end_off; //下次分配的建議開始地址

unsignedlong hint_idx; //下次分配的建議開始頁

structlist_head list;

}bootmem_data_t;

在bootmem_data結(jié)構(gòu)中,最主要的是頁位圖node_bootmem_map,其中的每一位對應(yīng)低端內(nèi)存中的一頁。Bootmem根據(jù)e820結(jié)構(gòu)和預(yù)留內(nèi)存區(qū)間結(jié)構(gòu)early_res初始化它的頁位圖。低端內(nèi)存中非保留且未被使用的頁都應(yīng)該是可用的,它們在頁位圖中對應(yīng)的位應(yīng)該被清0,已用掉或已預(yù)留的低端頁所對應(yīng)的位應(yīng)該被置1。對Bootmem的唯一要求是簡單,因而Bootmem采用基于最先適應(yīng)(first-fit)算法的動態(tài)分區(qū)技術(shù)來管理內(nèi)存的分配和釋放。在向Bootmem申請內(nèi)存時(shí),申請者需要指出內(nèi)存的大小、對齊方式(如頁對齊或L1Cache對齊等)、期望的開始位置(如16MB或0等)、期望的終止位置等。Bootmem管理器從頭開始搜索頁位圖,找到滿足要求的第一塊空閑內(nèi)存(由一到多個連續(xù)的空閑頁組成),設(shè)置頁位圖并將其分配出去。如所分配內(nèi)存的終止位置不是頁邊界,則讓last_end_off指向剩余碎片的開始位置。如果可能,下一次分配應(yīng)從last_end_off處開始,以便將碎片利用起來。

9.創(chuàng)建“kmap空間”頁表

“kmap空間”的起始線性地址是PKMAP_BASE(從0xFFFFFFFF倒推出來的一個內(nèi)核線性地址,是4M的倍數(shù)),大小是4MB,因而需要一個頁表來描述kmap頁到高端物理頁的映射,雖然這種映射是臨時(shí)的、短暫的。

“kmap空間”的頁表需要一頁的物理內(nèi)存,該內(nèi)存是向Bootmem申請的。變量pkmap_page_table指向kmap頁表中的第一個頁表項(xiàng)。

10.獲取外部中斷配置信息

目前的計(jì)算機(jī)系統(tǒng)大都采用APIC來分發(fā)外部中斷。APIC有多種類型,如NUMAQ、SUMMIT、VISWS、ES7000、BIGSMP等。為了便于管理,Linux為每一種類型的APIC定義了一個apic結(jié)構(gòu),其中包含一組對該類APIC的操作函數(shù),如APIC探測函數(shù)、APIC寄存器的讀寫函數(shù)、處理器喚醒函數(shù)、處理器間中斷的發(fā)送函數(shù)等。因而,一個apic結(jié)構(gòu)實(shí)際就是一類APIC設(shè)備的驅(qū)動程序。在外部中斷初始化之前,Linux需要根據(jù)系統(tǒng)的配置信息確定與之對應(yīng)的apic結(jié)構(gòu)。缺省的apic結(jié)構(gòu)是apic_default。

系統(tǒng)中有關(guān)外部中斷(尤其是APIC)的配置信息是由BIOS提供的,提供的方式有兩種,MP配置表和ACPI描述表。Linux以ACPI為準(zhǔn),輔之以MP。

在ACPI的描述表中,MADT描述的是系統(tǒng)中APIC的配置信息。MADT由許多子表組成,這些子表描述了各邏輯處理器的使能情況和各種外部中斷的配置情況。如果系統(tǒng)支持LocalAPIC,在MADT中肯定包含LocalAPIC寄存器的內(nèi)存映射物理基地址(缺省值是0xFEE00000)。為了便于在內(nèi)核中直接訪問LocalAPIC的寄存器,應(yīng)設(shè)置頁目錄、頁表項(xiàng),將LocalAPIC的寄存器頁映射到“FixedVirtualAddress”區(qū)域的預(yù)定位置,也就是為LocalAPIC的各寄存器分配內(nèi)核線性地址。

MADT為每個邏輯處理器定義了一個子表,用于記錄它們的ID號、與之關(guān)聯(lián)的LocalAPIC的ID號及使能情況等。ID號可能不連續(xù),為了便于使用,Linux給每個使能的邏輯處理器另外分配了一個邏輯號。邏輯號從0開始,依據(jù)處理器在MADT中出現(xiàn)的順序編排,其中BSP的邏輯號是0。Linux將邏輯號與LocalAPICID的對應(yīng)關(guān)系記錄在數(shù)組x86_cpu_to_apicid[]中。位圖cpu_possible_map和cpu_present_map是按處理器的邏輯號設(shè)置的,1表示對應(yīng)的處理器存在且可用。如果LocalAPIC上連接有NMI,該NMI必須連接在管腳LINT1上。

如果系統(tǒng)中配置了I/OAPIC,那么每個I/OAPIC在MADT中都有一個描述子表,用于記錄它的ID號、物理基地址(寄存器IOREGSEL的物理地址)及全局中斷(GlobalSystemInterrupt,GSI)基號。Linux將各I/OAPIC的描述信息匯總在結(jié)構(gòu)數(shù)組mp_ioapics[]中。為了便于在內(nèi)核中直接訪問I/OAPIC的寄存器,需要將I/OAPIC的物理基地址映射到“FixedVirtualAddress”區(qū)域的預(yù)定位置,即為I/OAPIC的寄存器分配內(nèi)核線性地址,每個I/OAPIC1頁。假如一個I/OAPIC的開始虛擬地址是vaddr,則通過vaddr訪問到的是它的IOREGSEL寄存器,vaddr+0x10訪問到的是它的IOWIN寄存器,vaddr+0x40訪問到的是它的EOI寄存器。

系統(tǒng)中可能包含多個I/OAPIC。所有I/OAPIC的輸入管腳已被統(tǒng)一編號,稱為GSI號。缺省情況下,I/OAPIC的第N個管腳所對應(yīng)的GSI號為該I/OAPIC的GSI基號+N。特別地,8259A類PIC的輸入管腳與系統(tǒng)中第一個I/OAPIC的輸入管腳應(yīng)該是一一對應(yīng)的,也就是說,GSI0~15應(yīng)該分配給IRQ0~15,但也可能有例外。MADT中可能包含若干描述子表,用來記錄IRQ與GSI號之間的這種意外的映射關(guān)系。Linux將IRQ與GSI號的映射關(guān)系記錄在結(jié)構(gòu)數(shù)組mp_irqs[]中。

structmpc_intsrc{

unsignedchartype; //MP_INTSRC

unsignedcharirqtype; //可能是INT、NMI、SMI或ExtINT

unsignedshortirqflag; //觸發(fā)方式和極性

unsignedcharsrcbus; //總線號,如MP_ISA_BUS

unsignedcharsrcbusirq; //IRQ號

unsignedchardstapic; //I/OAPICID

unsignedchardstirq; //GSI號

};

structmpc_intsrcmp_irqs[256];

如果ACPI中沒有提供有關(guān)處理器和外部中斷的配置信息,則需要分析MP表,從中獲取各邏輯處理器和外部中斷的配置信息,并將它們記錄在數(shù)組x86_cpu_to_apicid[]、位圖cpu_possible_map與cpu_present_map及結(jié)構(gòu)數(shù)組mp_irqs[]中。

11.建立PERCPU數(shù)據(jù)區(qū)

毫無疑問,在內(nèi)核中需要建立許多管理用的數(shù)據(jù)結(jié)構(gòu)。在單處理器環(huán)境中,一套數(shù)據(jù)結(jié)構(gòu)就足夠了。但在多處理器環(huán)境中,卻需要對這些數(shù)據(jù)結(jié)構(gòu)做一些區(qū)分。有些數(shù)據(jù)結(jié)構(gòu)是全局共享的,僅需要建立一套;另一些數(shù)據(jù)結(jié)構(gòu)是每個處理器都需要使用但又不能共享的,如GDT、TSS、處理器狀態(tài)等,必須為每個處理器都建立一套。Linux稱處理器專有的數(shù)據(jù)為PERCPU數(shù)據(jù)或?qū)S袛?shù)據(jù),且定義了一套專門的機(jī)制來管理PERCPU數(shù)據(jù)。PERCPU數(shù)據(jù)區(qū)中包含很多數(shù)據(jù)結(jié)構(gòu),它們的初值被集中存放幾個專用的數(shù)據(jù)節(jié)中,如“.data.percpu”、“.data.percpu.first”、“.data.percpu.page_aligned”等。

在早期的版本中,PERCPU區(qū)中的數(shù)據(jù)都是在編譯時(shí)靜態(tài)建立的,在系統(tǒng)運(yùn)行過程中不會再向其中增加新的數(shù)據(jù)結(jié)構(gòu)。宏DEFINE_PER_CPU(type,name)用于在PERCPU數(shù)據(jù)區(qū)中聲明一個類型為type、名稱為per_cpu_##name的變量。在新版本中,Linux允許動態(tài)地調(diào)整PERCPU數(shù)據(jù)區(qū),即可以在系統(tǒng)運(yùn)行過程中新建PERCPU數(shù)據(jù)結(jié)構(gòu),因而各處理器的PERCPU數(shù)據(jù)被分成三部分,即靜態(tài)部分(已預(yù)定義)、預(yù)留部分(留給內(nèi)核和模塊使用)、動態(tài)部分(在系統(tǒng)運(yùn)行過程中動態(tài)創(chuàng)建,初值為空)。

PERCPU數(shù)據(jù)區(qū)是臨時(shí)創(chuàng)建的,系統(tǒng)中每個存在的處理器都有一個,其內(nèi)存空間是向Bootmem申請的。在各PERCPU數(shù)據(jù)區(qū)中,靜態(tài)部分的初值都來源于“.percpu”節(jié),并經(jīng)過了修正,如第i數(shù)據(jù)區(qū)中的變量x86_cpu_to_apicid記錄的是邏輯處理器i的LocalAPICID等。Linux定義了數(shù)組_

_per_cpu_offset,專門用于記錄各PERCPU數(shù)據(jù)區(qū)的開始位置,如圖3.7所示。圖3.7PERCPU數(shù)據(jù)區(qū)的結(jié)構(gòu)為了便于尋址,Linux在各處理器的GDT表中都定義了一個per-cpu段描述符,其基地址被設(shè)為:處理器在PERCPU數(shù)據(jù)區(qū)的開始地址-_

_per_cpu_start。只要處理器在核心態(tài)運(yùn)行,它的段寄存器FS中就永遠(yuǎn)保存該per-cpu段的描述符。如此以來,處理器通過FS:per_cpu_xx訪問的就是自己的專有變量xx。

12.初始化伙伴內(nèi)存管理結(jié)構(gòu)

早期的Linux未細(xì)分物理內(nèi)存,只在有些頁的管理結(jié)構(gòu)(page)上加入了PG_DMA標(biāo)志,表示它可用于DMA。新版本的Linux將物理內(nèi)存按位置劃分成一到多個節(jié)點(diǎn)(Node),并將每個節(jié)點(diǎn)中的物理內(nèi)存再細(xì)分成若干個管理區(qū)(Zone)。Linux用結(jié)構(gòu)pglist_data描述節(jié)點(diǎn),用結(jié)構(gòu)zone描述管理區(qū)(參見6.2節(jié))。

Linux采用伙伴算法管理其物理內(nèi)存,相應(yīng)的管理子系統(tǒng)可稱為伙伴內(nèi)存管理器。初始化伙伴內(nèi)存管理結(jié)構(gòu)的主要工作是設(shè)置系統(tǒng)中的節(jié)點(diǎn)結(jié)構(gòu)pglist_data和節(jié)點(diǎn)中的管理區(qū)結(jié)構(gòu)zone,依據(jù)是物理內(nèi)存的配置情況,包括記錄在變量e820、max_low_pfn和highend_pfn中的信息,內(nèi)容包括節(jié)點(diǎn)中物理內(nèi)存的總頁數(shù)(包括空洞)、實(shí)際頁數(shù)(不含空洞)、開始頁號、管理區(qū)個數(shù)等,各管理區(qū)所管理內(nèi)存的總頁數(shù)(包括空洞)、實(shí)際頁數(shù)(不含空洞)、開始頁號等。節(jié)點(diǎn)結(jié)構(gòu)中的一個重要子結(jié)構(gòu)是頁結(jié)構(gòu)(page)數(shù)組node_mem_map,該數(shù)組是根據(jù)節(jié)點(diǎn)所管理物理內(nèi)存的總頁數(shù)(包括空洞頁)臨時(shí)創(chuàng)建的,其中的一個page結(jié)構(gòu)唯一地描述了一個物理頁。初始情況下,各page結(jié)構(gòu)的_mapcount被設(shè)為-1、_count被設(shè)為1、遷移類型都被設(shè)為MIGRATE_MOVABLE,所有的頁都被設(shè)為保留的。

在初始化內(nèi)存管理結(jié)構(gòu)的過程中,還需要確定幾個全局變量,其中nr_all_pages中記錄物理內(nèi)存的總頁數(shù),totalram_pages中記錄空閑的物理內(nèi)存總頁數(shù)。

13.處理命令行參數(shù)

Linux內(nèi)核是高度可配置的。編譯時(shí),可通過配置文件和條件編譯對內(nèi)核中的功能模塊進(jìn)行不同形式的取舍;運(yùn)行時(shí),可通過sysfs、proc等虛擬文件系統(tǒng)修改內(nèi)核中的參數(shù),通過內(nèi)核模塊機(jī)制動態(tài)地向內(nèi)核中插入模塊或從內(nèi)核中拔出模塊。在內(nèi)核啟動過程中,也可以設(shè)置或修正某些內(nèi)核參數(shù),從而指導(dǎo)系統(tǒng)的初始化和隨后的正常運(yùn)行。

Linux內(nèi)核中的有些參數(shù)是對系統(tǒng)的基本假定,如處理器個數(shù)、內(nèi)存大小、是否支持APIC等,可通過某種方法檢測到或已假定了初值,但允許用戶指定或修正。Linux為每個允許指定或修正的參數(shù)定義了一個obs_kernel_param結(jié)構(gòu),其中包括參數(shù)名str和處理函數(shù)setup_func。所有的obs_kernel_param結(jié)構(gòu)已被組織在數(shù)組_

_setup_start中。如果想在初始化過程中修正某個內(nèi)核參數(shù),可通過引導(dǎo)程序向內(nèi)核傳遞一個字符串,稱為命令行參數(shù)。早期的引導(dǎo)協(xié)議規(guī)定命令行參數(shù)不能超過256個字節(jié),新協(xié)議允許傳遞長達(dá)2KB的命令行參數(shù)。命令行參數(shù)已被轉(zhuǎn)移到字符數(shù)組boot_command_line中。命令行參數(shù)中包含若干個形式為“param”或“param=value”的子串,子串之間以空格分隔,整個命令行參數(shù)以‘\0’結(jié)尾。子串中的“param”是要指定或修正的內(nèi)核參數(shù)的名稱,“value”是內(nèi)核參數(shù)的新值。不帶值的子串是對內(nèi)核的指示,如“disableapic”指示內(nèi)核不要使用LocalAPIC;帶值的子串是向內(nèi)核傳遞的參數(shù)值或環(huán)境變量,如“highmem=256M”告訴內(nèi)核設(shè)置256MB的高端物理內(nèi)存。

在初始化過程中,Linux分析并處理命令行參數(shù),做法是找出與各參數(shù)名匹配的obs_kernel_param結(jié)構(gòu)并執(zhí)行其中的處理函數(shù)setup_func,以完成對內(nèi)核參數(shù)的修正。有些內(nèi)核模塊也需要參數(shù),這些參數(shù)可在插入過程中通過insmod命令傳遞,如命令insmodhello.ocount=0name="Hello"在將模塊hello.o插入內(nèi)核的同時(shí)向其傳遞了兩個參數(shù)count和name。然而,被直接集成在內(nèi)核映像中的模塊不需要再插入,它們所需要的參數(shù)只能通過命令行參數(shù)傳遞。與內(nèi)核參數(shù)相似,Linux為每個模塊參數(shù)定義了一個kernel_param結(jié)構(gòu),其中包括參數(shù)名、訪問權(quán)限、訪問標(biāo)志、設(shè)置函數(shù)set、獲取函數(shù)get等。所有的kernel_param結(jié)構(gòu)被組織成一個數(shù)組_

_start_

_

_param。在處理命令行參數(shù)時(shí),Linux會查找_

_start_

_

_param數(shù)組,獲得與參數(shù)名匹配的所有kernel_param結(jié)構(gòu),并逐個執(zhí)行其中的set函數(shù),以完成對模塊參數(shù)的設(shè)置。

即非內(nèi)核參數(shù)又非模塊參數(shù)的剩余命令行子串被分成兩類,“param=value”格式的是環(huán)境變量,Linux將其暫時(shí)記錄在數(shù)組envp_init中;“param”格式的是傳遞給第一個用戶程序的參數(shù),Linux將其暫時(shí)記錄在數(shù)組argv_init中。

14.設(shè)置異常和系統(tǒng)調(diào)用入口

Linux內(nèi)核為每一類異常都定義了一個處理程序,如為頁故障異常定義的處理程序?yàn)閜age_fault等。為了將異常與處理程序關(guān)聯(lián)起來,需要為各異常處理程序定義中斷門、陷阱門或任務(wù)門,并將它們按異常向量號填入IDT表idt_table,如表4.1所示。與GDT不同,Linux僅定義了一個IDT表,各處理器共用同一個IDT。

Linux的系統(tǒng)調(diào)用通常由陷入指令(如int$0x80)實(shí)現(xiàn),其處理程序由IDT表中的一個陷阱門描述,入口程序?yàn)閟ystem_call,特權(quán)級為3。

15.啟動BSP上的第0號進(jìn)程

BSP是系統(tǒng)的引導(dǎo)處理器,也是執(zhí)行初始化程序的處理器,邏輯號為0。雖然BSP正在第0號進(jìn)程的地址空間中運(yùn)行,但它的寄存器還未對此進(jìn)行設(shè)置,BSP還未意識到自己正在運(yùn)行第0號進(jìn)程。啟動BSP上的第0號進(jìn)程的過程如下:

(1)根據(jù)boot_cpu_data中的處理器配置信息設(shè)置BSP的CR4寄存器。

(2)將BSP的所有Debug寄存器清0。

(3)將公共IDT表idt_table加載到BSP的IDTR寄存器中。

(4)將第0號處理器專有的GDT表(在PERCPU數(shù)據(jù)區(qū)中)加載到BSP的GDTR寄存器中,將其中的per-cpu段描述符加載到BSP的FS寄存器中。

(5)將init_task.active_mm設(shè)為初始的內(nèi)存管理結(jié)構(gòu)init_mm;利用init_mm中的context創(chuàng)建一個LDT描述符,將其填入BSP的GDT表中;將新建的LDT描述符加載到BSP的LDTR寄存器中。

(6)設(shè)置第0號處理器的任務(wù)狀態(tài)段(在PERCPU數(shù)據(jù)區(qū)中已為每個處理器都定義了一個任務(wù)狀態(tài)段,即一個tss_struct類型的結(jié)構(gòu)變量init_tss,其中的sp0指向當(dāng)前進(jìn)程系統(tǒng)堆棧的棧底。將BSP所用任務(wù)狀態(tài)段中的sp0設(shè)為第0號進(jìn)程的棧底);根據(jù)init_tss的位置和大小構(gòu)造一個TSS描述符,將其填入BSP的GDT表中,并加載到BSP的TR寄存器中。

(7)利用全局任務(wù)狀態(tài)段doublefault_tss構(gòu)造一個TSS描述符,將其填入BSP的GDT表中,用于處理雙故障異常。doublefault_tss.ip被設(shè)為函數(shù)doublefault_fn。

(8)初始化BSP的FPU,設(shè)置相關(guān)的寄存器,如CR4.OSXSAVE等。

到此為止,BSP的各大管理寄存器(GDTR、IDTR、LDTR、TR、CR等)已設(shè)置完畢,BSP使用第0號進(jìn)程的頁目錄和系統(tǒng)堆棧,執(zhí)行第0號進(jìn)程的代碼,因而正在運(yùn)行第0號進(jìn)程,或者說第0號進(jìn)程已在BSP上靜態(tài)啟動。

16.啟動伙伴內(nèi)存管理器

Bootmem是一個臨時(shí)性質(zhì)的內(nèi)存管理器,其功能有限,不適宜長期使用,應(yīng)盡快啟動功能完備的內(nèi)存管理器,即Linux的伙伴內(nèi)存管理器。伙伴內(nèi)存管理器所用的數(shù)據(jù)結(jié)構(gòu)已基本設(shè)置完畢,所欠缺的是將要管理的物理內(nèi)存資源。只要將系統(tǒng)中的空閑物理內(nèi)存頁全部交給伙伴內(nèi)存管理器,它即可接替Bootmem投入正常運(yùn)行。由于低端內(nèi)存的使用情況全部記錄在Bootmem的位圖node_bootmem_map中,因而搜索該位圖即可獲得所有的低端空閑頁。各空閑物理頁上的保留標(biāo)志PG_reserved應(yīng)被清除,表示它們已可用于分存分配。各空閑物理頁應(yīng)按伙伴關(guān)系組合成大頁塊,并加入伙伴內(nèi)存管理器的空閑頁塊隊(duì)列中。當(dāng)把所有的低端空閑頁全部交給伙伴內(nèi)存管理器之后,位圖node_bootmem_map也就無用了,應(yīng)將它占用的物理頁也交給伙伴內(nèi)存管理器。

系統(tǒng)中的高端物理內(nèi)存都是空閑的,應(yīng)將它們?nèi)拷唤o伙伴內(nèi)存管理器。將頁目錄swapper_pg_dir中的用戶部分(0~767)清空并刷新TLB。

至此,伙伴內(nèi)存管理器已接管了所有的空閑物理內(nèi)存頁,它的管理結(jié)構(gòu)已全部初始化完畢。此后,伙伴內(nèi)存管理器將負(fù)責(zé)物理內(nèi)存的管理。

17.啟動對象內(nèi)存管理器

伙伴內(nèi)存管理器以頁為單位管理物理內(nèi)存,無法滿足內(nèi)核對小內(nèi)存的需求,如建立小的數(shù)據(jù)結(jié)構(gòu)等,因而需要建立另外一個內(nèi)存管理器,專門為內(nèi)核提供小內(nèi)存服務(wù)。

在最早的版本中,Linux采用存儲桶(bucket)算法管理小內(nèi)存,后來換成了SUN公司的Slab算法,稱為對象內(nèi)存管理器。新版本的Linux又引入了Slub和Slob算法,以滿足不同種類內(nèi)核對小內(nèi)存的需求,但其核心仍然是Slab。對象內(nèi)存管理的基本思路是將來自伙伴內(nèi)存管理器的一大塊內(nèi)存劃分成一組等尺寸的小對象,由一個Slab管理。具有相同屬性的一組Slab構(gòu)成一個緩存,由一個Cache管理。對象內(nèi)存管理器以小對象為單位分配和釋放內(nèi)存。

對象內(nèi)存管理器中的Cache由結(jié)構(gòu)kmem_cache描述,是一種特殊尺寸的小對象,也應(yīng)該由一個Cache管理。管理kmem_cache結(jié)構(gòu)的Cache稱為cache_cache,是需要預(yù)先建立的管理結(jié)構(gòu)。以cache_cache為基礎(chǔ),可以創(chuàng)建若干個通用的Cache,如對象尺寸為32、64、96、128、192、256、512、1024、2048字節(jié)的Cache。各通用Cache的位置記錄在數(shù)組malloc_sizes中。Linux還為一些常用的結(jié)構(gòu)建立了專用的Cache,如為結(jié)構(gòu)anon_vma、task_struct、sighand_struct、signal_struct、files_struct、fs_struct、mm_struct、vm_area_struct、dentry、inode、file、vfsmount、sigqueue等建立了Cache。

在完成上述初始化工作之后,對象內(nèi)存管理器已可正常工作。

18.初始化調(diào)度器

調(diào)度器負(fù)責(zé)進(jìn)程的調(diào)度,是操作系統(tǒng)的核心。在Linux0.11版中,Linus設(shè)計(jì)了一個基于單就緒隊(duì)列的調(diào)度器,十分簡潔有力,以至于Linus認(rèn)為它能夠適應(yīng)所有的情況,已沒有再改變的必要。事實(shí)上,在Linux2.6版推出之前,Linux一直使用的都是這一調(diào)度器。但隨著Linux所面對環(huán)境的復(fù)雜化,人們逐漸發(fā)現(xiàn)了老調(diào)度器的不足,如擴(kuò)展性、交互性、實(shí)時(shí)性不好等。在Linux2.6中,IngoMolnar設(shè)計(jì)了一個基于多就緒隊(duì)列的O(1)調(diào)度器,能夠在常數(shù)時(shí)間內(nèi)完成進(jìn)程調(diào)度,但比較復(fù)雜,且會在許多情況下失效。ConKolivas設(shè)計(jì)出了基于多就緒隊(duì)列的樓梯調(diào)度器SD及改進(jìn)版本RSDL(TheRotatingStaircaseDeadlineSchedule),轉(zhuǎn)而采用一種公平的調(diào)度思路,改善了系統(tǒng)的交互性,簡化了設(shè)計(jì)的復(fù)雜度。受RSDL啟發(fā),IngoMolnar又設(shè)計(jì)了基于紅黑樹的完全公平調(diào)度器CFS(CompletelyFairScheduler)。CFS是目前Linux的主流調(diào)度器。

調(diào)度器初始化的主要工作包括創(chuàng)建初始任務(wù)組init_task_group以便支持組調(diào)度、初始化根處理器域def_root_domain用以描述各處理器的優(yōu)先級、初始化實(shí)時(shí)進(jìn)程帶寬def_rt_bandwidth以限制實(shí)時(shí)進(jìn)程的單次運(yùn)行時(shí)間、初始化各處理器的就緒進(jìn)程隊(duì)列runqueues、創(chuàng)建名為pidhash的Hash表以加速進(jìn)程PID到task_struct的轉(zhuǎn)換等。

每個處理器都有一個空閑進(jìn)程,在完成初始化工作之后,第0號進(jìn)程將變成BSP的空閑進(jìn)程Idle。將BSP的當(dāng)前進(jìn)程和空閑進(jìn)程都設(shè)為第0號進(jìn)程。

至此,CFS調(diào)度器已可正常工作。

19.初始化外部中斷

為了維護(hù)兼容性,目前的計(jì)算機(jī)系統(tǒng)中通常同時(shí)配置有8259A類的PIC和APIC,且兩者可配合工作。PIC與APIC的組合模式有三種,即PIC模式(完全繞過APIC,PIC的輸出直接連接到BSP的

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論