版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第6章嵌入式Linux內(nèi)核
6.1嵌入式Linux概述
6.2嵌入式Linux的版本控制6.3嵌入式Linux的代碼結(jié)構(gòu)
6.4ARM嵌入式Linux的內(nèi)存管理 6.5ARM嵌入式Linux的進程管理與調(diào)度6.6ARM嵌入式Linux的中斷響應(yīng)與處理 6.7嵌入式Linux的模塊化機制 6.8嵌入式Linux內(nèi)核的配置
6.9嵌入式Linux內(nèi)核啟動分析
6.1嵌入式Linux概述6.1.1嵌入式操作系統(tǒng)的分類嵌入式操作系統(tǒng)的分類方法有多種,第1章已經(jīng)提到可以從實時性的角度分為軟實時操作系統(tǒng)和硬實時操作系統(tǒng),此外也可以通過對內(nèi)存管理單元(MemoryManagementUnit)的支持情況來區(qū)分。通過內(nèi)存的搶占類型分為可搶占型和不可搶占型。不支持MMU的嵌入式操作系統(tǒng)有μCLinux、μC/OS-Ⅱ,支持MMU的嵌入式操作系統(tǒng)有嵌入式Linux、WindowsCE和EmbeddedLinux等??蓳屨夹颓度胧讲僮飨到y(tǒng)是指內(nèi)核可以搶占正在運行任務(wù)的CPU的使用權(quán)并將使用權(quán)交給進入就緒態(tài)的優(yōu)先級更高的任務(wù)。
不可搶占型嵌入式操作系統(tǒng)使用某種算法并決定讓某個任務(wù)運行后,把CPU的控制權(quán)交給該任務(wù),直到它主動釋放CPU控制權(quán)。中斷由中斷服務(wù)程序來處理,可以激活休眠態(tài)的任務(wù),使之進入就緒態(tài);而這個進入就緒態(tài)的任務(wù)還不一定能馬上運行,一直要等到當(dāng)前運行的任務(wù)主動交出CPU的控制權(quán)時才運行。使用這種嵌入式操作系統(tǒng)的實時性比不使用嵌入式操作系統(tǒng)的好,其實時性取決于最長任務(wù)的執(zhí)行時間。不可搶占型嵌入式操作系統(tǒng)的缺點同樣突出,如果最長任務(wù)的執(zhí)行時間不能確定,系統(tǒng)的實時性就不能確定??蓳屨夹颓度胧讲僮飨到y(tǒng)的實時性好,優(yōu)先級高的任務(wù)只要具備了運行的條件,或者說進入了就緒態(tài),就可以立即運行。也就是說,除了優(yōu)先級最高的任務(wù),其他任務(wù)在運行過程中都可能隨時被比它優(yōu)先級高的任務(wù)中斷。通過這種方式的任務(wù)調(diào)度保證了系統(tǒng)的實時性,因此現(xiàn)在大多數(shù)的嵌入式操作系統(tǒng)都是可搶占型的。6.1.2嵌入式Linux
隨著嵌入式Linux的推廣和嵌入式系統(tǒng)的發(fā)展,嵌入式Linux也出現(xiàn)了多種版本。針對不同類型的嵌入式Linux操作系統(tǒng),在商業(yè)和教學(xué)中均取得了較大的成功。
商業(yè)化的嵌入式Linux中比較知名的有:MontavistaLinux、MiziLinux(Korea)、Vlinux(Korea)、Bluecat(Lynuxwork)、Lineo(Metroworks)、Timesys。上述商業(yè)化嵌入式Linux中最有名也是最成功的是MontavistaLinux,由Montavista軟件公司提供。Montavista軟件公司是一個世界領(lǐng)先的智能設(shè)備和相應(yīng)基礎(chǔ)部件的系統(tǒng)軟件供應(yīng)商。Montavista以提供基于GNU/Linux的開放源碼軟件解決方案來推動嵌入式系統(tǒng)革命。由實時操作系統(tǒng)(RTOS)的倡導(dǎo)者JamesReady在1999年創(chuàng)立。Montavista提供的MontavistaLinux家族系列產(chǎn)品滿足了廣泛的軟件開發(fā)商的需要,包含從通信基礎(chǔ)設(shè)施到消費電子的應(yīng)用。
Montavista發(fā)布的多種MontavistaLinux版本包括專業(yè)版(ProfessionalEdition)、消費電子版(ConsumerElectronicsEdition)和電信運營級版(CarrierGradeEdition),目前這些版本的最新版都是3.1。同時,附加技術(shù)產(chǎn)品提供了功能強大的圖形開發(fā)功能。
Montavista已經(jīng)被驗證并在新一輪裝載Oracle9i數(shù)據(jù)庫的通信服務(wù)器和MotorolaA760智能手機中使用。在2003年,一系列著名的產(chǎn)品,如夏普(Sharp)家庭服務(wù)器(HomeServer)、沃爾沃(VOLVO)汽車的電子系統(tǒng)、NEC的ATCA電信平臺、飛利浦(Philips)的通用遙控器、松下(Panasonic)電器的寬帶終端和TV接收器也使用了MontavistaLinux產(chǎn)品。
Montavista商業(yè)產(chǎn)品的成功標志著Linux已經(jīng)從學(xué)習(xí)準備階段進入到成熟應(yīng)用階段,真正屬于嵌入式Linux的時代已經(jīng)到來了。6.2嵌入式Linux的版本控制在眾多的嵌入式Linux中有一個特殊的成員,它就是基于ARM(AdvancedRISCMahine,一種精簡指令集的CPU)的Linux。隨著近幾年ARM席卷全球的應(yīng)用熱潮,基于ARM的Linux也得到迅速發(fā)展,成為一個獨特的成員,是由ARM公司的RussellKing最早移植并維護的。本章重點是介紹ARMLinux,為了方便起見,以后提到的嵌入式Linux或Linux在不特殊聲明的情況下特指ARMLinux。嵌入式Linux的版本控制類似于普通的Linux,即版本號的格式為“x.yy.zz”。其中x介于0~9之間,表示的是主要版本,它的變化意味著內(nèi)核在設(shè)計或?qū)崿F(xiàn)上有重大改變。yy介于0~99之間,表示的是次版本號。yy一方面表示版本的變遷,一方面標志著版本的種類,即“發(fā)行版”或“開發(fā)版”。如果yy為偶數(shù)則表示一個相對穩(wěn)定的、已經(jīng)發(fā)行的版本;如果為奇數(shù)則表示還在開發(fā)中,目前不太穩(wěn)定,或在運行中可能出現(xiàn)比較大問題的版本。zz則表示在內(nèi)核增加的內(nèi)容不是很多、改動不是很大時的變遷,只能算同一個版本?,F(xiàn)在的嵌入式Linux同時支持帶MMU的CPU(如ARM920T)和不帶MMU的CPU(如ARM7TDMI),但是對早期的Linux來說,這有很大的難度。因此早期的時候,運行在不支持MMU的CPU的嵌入式Linux稱為μCLinux。
由于存在這種區(qū)別,基于嵌入式Linux的開發(fā)又分為兩大陣營:基于μCLinux的開發(fā)和基于標準Linux的開發(fā)。為了改變這種不利的狀態(tài),Linux內(nèi)核從2.6.xx版本以后就將μCLinux和標準Linux整合了起來。也就是說,2.6.xx以后的版本既支持帶MMU的CPU,同時又支持不帶MMU的CPU。這是嵌入式Linux發(fā)展的一個偉大的里程碑。
6.3嵌入式Linux的代碼結(jié)構(gòu)
Linux的最大優(yōu)點就是它的源碼公開。同時,公開的核心源碼也吸引著無數(shù)的電腦愛好者和程序員,他們把解讀和分析Linux的核心源碼作為最大興趣,把修改Linux源碼和改造Linux系統(tǒng)作為對計算機技術(shù)追求的目標。
Linux的內(nèi)核源碼很具有吸引力,這種吸引力不僅來源于Linux內(nèi)核源碼的高水平和高層次,而且來自于這種令人生畏的勞動所帶來的令人癡迷的回報?!裢ㄟ^對內(nèi)核代碼的分析,不但可以學(xué)習(xí)到很多和硬件底層相關(guān)的知識,同時還可以通過實戰(zhàn)更深層次地理解虛擬存儲的實現(xiàn)機制、多任務(wù)機制、系統(tǒng)保護機制等。
●可以學(xué)習(xí)到一個優(yōu)秀操作系統(tǒng)的整體結(jié)構(gòu)以及宏觀設(shè)計的方法和技巧。
●通過學(xué)習(xí)內(nèi)核代碼可以明確地了解內(nèi)核如何為上層應(yīng)用提供一個與具體硬件不相關(guān)的平臺。
●可以學(xué)習(xí)內(nèi)核代碼如何將代碼分為與體系結(jié)構(gòu)和硬件相關(guān)的部分和可移植的部分?!窨梢詫W(xué)習(xí)Linux內(nèi)核是如何把大部分的設(shè)備驅(qū)動處理成相對獨立的內(nèi)核模塊的,這樣不但減少了內(nèi)核運行的開銷,而且增強了內(nèi)核代碼的模塊獨立性。
●?Linux是全世界計算機高手的杰作,通過學(xué)習(xí)它的內(nèi)核,可以學(xué)習(xí)到很多具體問題實現(xiàn)時的巧妙方法。
●通過學(xué)習(xí)內(nèi)核代碼,可以學(xué)習(xí)到如何來保證龐大代碼的清晰性、兼容性、可移植性和可維護、可升級性。
但是,隨著Linux的發(fā)展,現(xiàn)在的Linux內(nèi)核代碼高達上百萬行,如果對內(nèi)核的代碼結(jié)構(gòu)一無所知的話,那么這個工作是不可能完成的。因此對內(nèi)核代碼結(jié)構(gòu)的了解非常重要。安裝好的Linux或從等內(nèi)核網(wǎng)站下載的Linux內(nèi)核,展開后都在一個名為Linux的目錄中。該目錄下的文件和代碼的功能如下所示:
●?COPYING:GPL版權(quán)申明。對具有GPL版權(quán)的源代碼改動而形成的程序,或使用GPL工具產(chǎn)生的程序,具有使用GPL發(fā)表的義務(wù),如公開源代碼。
●?CREDITS:光榮榜。對Linux做出過很大貢獻的一些人的信息。
●?MAINTAINERS:維護人員列表,當(dāng)前版本的內(nèi)核各部分都由誰負責(zé)。
●?ReadMe:Linux內(nèi)核安裝、編譯、配置方法等的簡單介紹。
●?Makefile:第一個Makefile文件。用來組織內(nèi)核的各模塊,記錄各模塊相互之間的聯(lián)系和依賴關(guān)系,編譯時使用。仔細閱讀各子目錄下的Makefile文件對弄清各個文件之間的聯(lián)系和依賴關(guān)系很有幫助。
●?Rules.make:各種Makefile所使用的一些共同規(guī)則。
●?documentation/:文檔目錄,沒有內(nèi)核代碼。
●?arch/:包括了所有和體系結(jié)構(gòu)相關(guān)的核心代碼。每一個子目錄都代表一種支持的體系結(jié)構(gòu),例如m68k就代表由Freesale開發(fā)的68000系列CPU?!?drivers/:放置系統(tǒng)所有的設(shè)備驅(qū)動程序,包括各種塊設(shè)備和字符設(shè)備的驅(qū)動程序。每種驅(qū)動程序各占用一個子目錄,如/block下為塊設(shè)備驅(qū)動程序,即ide(ide.c)。
●?fs/:所有的文件系統(tǒng)代碼和各種類型的文件操作代碼,它的每一個子目錄都支持一個文件系統(tǒng),如FAT和Ext2。還有一些共同的源程序則用于“虛擬文件系統(tǒng)VFS”。
●?include/:包括編譯核心所需要的大部分頭文件。與平臺無關(guān)的頭文件在include/linux子目錄下,與IntelCPU相關(guān)的頭文件在include/asm-i386子目錄下,而include/scsi目錄則是有關(guān)scsi設(shè)備的頭文件目錄。通用的子目錄asm則根據(jù)系統(tǒng)的配置而“符號連接”到具體CPU的專用子目錄,如asm-i386、asm-m68k等。除此之外,還有通用的子目錄Linux、net等。
●?init/:包含核心的初始化代碼(注意:不是系統(tǒng)的引導(dǎo)代碼),即內(nèi)核的main()函數(shù)及其初始化過程,包含兩個文件main.c和Version.c,這是研究核心如何工作的起點之一?!?ipc/:包含核心的進程間通信的代碼,包括util.c、sem.c、msg.c等文件。
●?kernel/:主要的核心代碼,此目錄下的文件實現(xiàn)了大多數(shù)Linux系統(tǒng)的內(nèi)核函數(shù),其中最重要的文件當(dāng)屬sched.c;同樣,和體系結(jié)構(gòu)相關(guān)的代碼在arch/*/kernel中。
●?lib/:放置核心的庫代碼。
●?mm/:包括所有獨立于CPU體系結(jié)構(gòu)的內(nèi)存管理代碼,如頁式存儲管理內(nèi)存的分配和釋放等;而和體系結(jié)構(gòu)相關(guān)的內(nèi)存管理代碼則位于arch/*/mm/,如arch/i386/mm/Fault.c?!?modules/:模塊文件目錄,一般為空目錄,用于存放編譯時產(chǎn)生的模塊目錄文件。
●?net/:內(nèi)核與網(wǎng)絡(luò)相關(guān)的代碼,包含了各種不同網(wǎng)卡和網(wǎng)絡(luò)規(guī)程的驅(qū)動程序。
●?scripts/:用于系統(tǒng)配置的命令文件,為腳本文件,用于對核心的配置。6.4ARM嵌入式Linux的內(nèi)存管理6.4.1內(nèi)存管理單元MMU與mCLinux工作的系統(tǒng)結(jié)構(gòu)不同,ARM系統(tǒng)結(jié)構(gòu)具有單獨的內(nèi)存管理單元。內(nèi)存管理單元在整個ARM體系結(jié)構(gòu)中起著重要作用。它的功能主要包括兩個方面:一個是實現(xiàn)地址映射,另一個是實現(xiàn)地址訪問的保護和限制。通常,MMU會提供一組寄存器,使用這些寄存器來實現(xiàn)上述兩個功能。MMU可以位于芯片中,也可以作為協(xié)處理器(在傳統(tǒng)的單芯片CPU基礎(chǔ)上,集成其他的硬件單元)。不過,在嵌入式系統(tǒng)領(lǐng)域,基于成本和功耗的考慮,系統(tǒng)中往往會有多個協(xié)處理器,所以ARM的MMU通常都是由協(xié)處理器來控制的。
6.4.2ARM嵌入式Linux的存儲管理機制
在ARM系統(tǒng)結(jié)構(gòu)中,內(nèi)存的管理機制可以采取兩種模式:一種是按段來進行管理,另外一種是按照兩層的頁式管理方式來管理。在段式管理方式中,一般段的大小定義為1?MB;在兩層的頁式管理方式中,頁的大小可以定義為64?KB或者4?KB。在原來的ARMv5中,可以采取更小的頁面。與普通Linux的地址映射一樣,虛擬地址和物理地址的映射關(guān)系仍然保存在映射表中。在ARM嵌入式Linux中,映射表中可以有4096個表項,每個表項占用4?B,存放了虛擬地址的物理段地址以及對該地址的訪問權(quán)限等。當(dāng)CPU進行數(shù)據(jù)處理要訪問內(nèi)存的內(nèi)容時,虛擬地址的前12位用來定位段表中的項,從段表中讀取該虛擬地址的物理段地址,然后與虛擬地址的后20位組成實際的物理地址,這也就使該虛擬地址映射到物理地址。在地址映射過程中,這些映射工作均由內(nèi)存管理單元負責(zé)進行處理。另外,在所有的ARM體系結(jié)構(gòu)中,添加了TLB
(TranslationLookasideBuffer),也就是高速后備緩沖區(qū),在TLB中可以存放一部分段表中的內(nèi)容。當(dāng)CPU訪問內(nèi)存時,先訪問TLB,查找訪問的內(nèi)存映射關(guān)系是否存放在TLB中,如果在,則從TLB中讀取映射關(guān)系,否則再去訪問內(nèi)存中的段表,從段表中讀取映射關(guān)系,并把相應(yīng)的結(jié)果添加到TLB中,更新它的內(nèi)容,這樣CPU下次需要該地址映射關(guān)系時,可以直接從TLB取得。因為TLB的訪問速度比內(nèi)存的訪問速度要快得多,如果采取相應(yīng)技術(shù),使訪問TLB的命中率比較高,就可以大大提高訪問內(nèi)存的速度。在兩層頁面映射中,映射表有兩層,第一層頁面映射表中保存的是第二層映射表的地址,其中每一項有兩位標識位,用來表示該表項的作用:00表示沒有到物理地址的映射,01表示指向粗頁面表,即頁面大小是64?KB或4?KB的二層頁表;10表示段映射;11表示指向細頁面,即頁面大小是1?KB的二層頁表。第二層映射表存放的才是該頁面所在的物理頁面的地址。采取該種映射方式的具體映射過程可以參見圖6-1。映射過程由MMU硬件來完成。圖6-1二級頁表映射過程6.4.3ARM嵌入式Linux存儲機制的建立
目前大部分ARM處理器采用了32位的地址結(jié)構(gòu)。對于32位地址結(jié)構(gòu)的ARM處理器來說,能夠支持的虛擬地址大小為,也就是4?G。ARM嵌入式Linux內(nèi)核將這4?G大小的虛擬空間劃分成兩部分:內(nèi)核空間和用戶空間。在用戶態(tài)工作的進程只能使用用戶空間的地址,只有內(nèi)核態(tài)進程才能訪問內(nèi)核空間地址。在ARM嵌入式Linux中,對于內(nèi)核空間和用戶空間大小的劃分,根據(jù)CPU芯片和開發(fā)板的不同而有所不同,其大小可以通過宏定義反映。普通Linux內(nèi)核的存儲管理機制采用了頁面映射的方式,而且采用了三層映射模型。但是,在ARM處理器上的實現(xiàn)和X86有許多不同,在ARM處理器上,如果整個段(1?MB,并且和1?MB邊界對齊)都有映射,就采用單層映射的方式,這一點跟X86有所不同,X86都是采用二層映射。另外,這里提到的段,在ARM處理器上是固定長度的,而在X86系統(tǒng)中通常是可變的,是根據(jù)程序的大小來分配的。在ARM嵌入式Linux代碼中,頁面的大小為4?KB,段區(qū)的大小為1?MB。其中,最高層為PGDIR,第二層為PMD,第三層為頁面映射表。Linux在啟動之初,依次執(zhí)行以下函數(shù):start_kernel()、setup_arch、pageing_init()、memtable_init()和creat_mapping(),當(dāng)這些函數(shù)執(zhí)行完畢后,即建立起了內(nèi)存區(qū)間映射機制??蓮南旅鎸reat_
mapping()函數(shù)的描述來了解如何對給定的區(qū)間建立映射。staticvoid_initcreate_mapping(structmap_desc*md)
{
unsignedlongvirt,1ength;
intprot_sect,prot_l1,domain;
pgprot_tprot_pte;
1ongoff;
if(md->virtual!=vectors_base()&&md->virtua1<TASK_SIZE){
printk(KERN_WARNING“BUG:notcreatingrnappingfor”
"0x%08lxat0x%08lxinuserregion\n”,
md->physical,md->virtual);
return:}
if((md->type==MT_DEVICE||md->type==MT_ROM)&&
md->virtual>=PAGE_OFFSET&&md->virtua1<VMALLOC_END){
printk(KERN_WARNING"BUG:mappingfor0x%08lxat0x%08lx"
“overlapsvmal1ocspace\n",
md->physical,md->virtual);
}domain=mem_types[md->type].domain;
prot_pte=_pgprot(men_types[md->type].prot_pte);
prot_l1=men_types[md->type].prot_l1|PMD_DOMAIN(domain);
prot_sect=memtypes[md->type].prot_sect|PMD_DOMAIN(domain);
virt=nd->virtual;
off=md->physical-virt;
length=md->length;
if(mem_types[md->type].prot_l1==0&&
(virt&0xfffff||(virt+off)&0xfffff||(virt+1ength)&0xfffff)){
printk(KERN_WARNING"BUG:mapfor0x%08lxat0x%081xcannot”
"bemappedusingpages,ignoring.\n”,
md->physical,md->virtual);
return;
}while((virt&0xfffff||(virt+off)&0xfffff)&&length>=PAGE_SIZE){
alloc_init_page(virt,virt+off,prot_l1,prot_pte);
virt+=PAGE_SIZE;
length-=PAGE_SIZE;
}
if(cpu_architecture()>=CPU_ARCH_ARM6&&domain==0){
/*與supersection的邊界對齊*/
while((virt&~SUPERSECTION_MASK||(virt+off)&
~SUPERSECTION_MASK)&&length>=(PGDIR_SIZE/2)){
Alloc_init_section(virt,virt+off,prot_sect);
virt+=(PGDIR_SIZE/2);
length-=(PGDIR_SIZE/2);
}
whi1e(length>=SUPERSECTION_SIZE{
alloc_init_supersection(virt,virt+off,prot_sect);
virt+=SUPERSECTION_SIZE;
1ength-=SUPERSECTION_SIZE;
}
}
/*一個區(qū)間的映射覆蓋了“pgdir”表項的一半*/
while(length>=(PGDIR_SIZE/2)){
alloc_init_section(virt,virt+off,prot_sect);
virt+=(PGDIR_SIZE/2);
Length-=(PGDIR_SIZE/2);
}
while(length>=PAGE_SIZE){
alloc_init_page(virt,virt+off,prot_l1,prot_pte);
virt+=PAGE_SIZE;
length-=PACE_SIZE;
}
}從上面的代碼可以看出,該函數(shù)通過三個while循環(huán)來為給定的區(qū)間建立映射。如果區(qū)間的起點和1?MB邊界對齊,就先通過alloc_init_page()建立若干二層頁面的映射,直到和1?MB邊界對齊為止,然后以1?MB為單位通過alloc_init_section()逐段建立單層映射。另外,如果區(qū)間的終點不和1?MB邊界對齊,則再通過alloc_init_page()建立若干二層頁面映射。6.4.4ARM嵌入式Linux對進程虛擬空間的管理
進程的虛擬內(nèi)存存儲可執(zhí)行代碼和進程的資源。由于進程運行過程中并不會同時使用虛擬內(nèi)存空間中的所有代碼和數(shù)據(jù),而已有的代碼和數(shù)據(jù)使用的頻率非常低,如果進程運行中把所有的代碼和數(shù)據(jù)都裝入到物理內(nèi)存空間中,無疑是對物理內(nèi)存的浪費,因此,Linux使用頁面調(diào)度技術(shù)把那些進程需要訪問的虛擬內(nèi)存裝入物理內(nèi)存中,其他的都存放在進程的虛擬內(nèi)存中。當(dāng)進程訪問代碼或者數(shù)據(jù)的時候,如果要訪問的內(nèi)容不在物理內(nèi)存中,系統(tǒng)硬件會產(chǎn)生頁面錯誤,同時將控制權(quán)轉(zhuǎn)交給Linux內(nèi)核,以便處理因頁面錯誤而引起的一系列操作。所以,對于處理器地址空間的每個虛擬內(nèi)存空間,內(nèi)核都必須了解這些虛擬內(nèi)存的詳細信息,比如從何處而來,如何裝入物理內(nèi)存。這樣內(nèi)核才能正確處理出現(xiàn)的頁面錯誤。
當(dāng)然,Linux的虛擬內(nèi)存實現(xiàn)需要各種機制的支持,如地址映射機制、內(nèi)存分配回收機制、緩存和刷新機制、請求頁機制、頁面交換機制和頁面共享機制。內(nèi)存管理程序先通過映射機制把用戶程序的邏輯地址映射到物理地址,在用戶程序運行時如果發(fā)現(xiàn)程序中要用的虛擬地址沒有對應(yīng)的物理地址,也就是說頁面不在物理內(nèi)存中,會發(fā)出頁請求:如果有空閑的內(nèi)存可供分配,就請求分配內(nèi)存,并把正在使用的物理頁記錄在頁緩存中;如果沒有足夠的內(nèi)存分配,就調(diào)用交換機制,騰出一部分內(nèi)存。另外,在地址映射中會通過TLB來尋找物理頁,交換機制中要用到交換緩存,并且把物理頁內(nèi)容交換到交換文件中也要修改頁表來映射文件地址。為實現(xiàn)以上操作,Linux內(nèi)核需要管理所有的虛擬內(nèi)存地址,也需要許多數(shù)據(jù)結(jié)構(gòu)來對進程的虛擬內(nèi)存進行描述和管理。比如,vm_area_stuct結(jié)構(gòu)描述的是進程虛擬內(nèi)存中的內(nèi)容,mm_struct數(shù)據(jù)結(jié)構(gòu)包含了已加載的可執(zhí)行映射的信息和指向進程頁表的指針。這兩種數(shù)據(jù)結(jié)構(gòu)的具體描述如下:mm_struct數(shù)據(jù)結(jié)構(gòu):
structmm_struct{
intcount:
pgd_t*pgd;
unsignedlongcontext;
unsignedlongstart_code,end_code,start_data,end_data;
unsiqnedlongstart_brk,brk,start_stack,start_mmap;
unsignedlongarg_start,arg_end,env_start,env_end;
unsignedlongrss,tota1_vm,locked--vm;
unsignedlongdef_flags;structvm_area_struct*mmap;
structvm_area_struct*mmap_avl;
structsemaphoremmap_sem;
};
vm_area_struct數(shù)據(jù)結(jié)構(gòu):
structvm_area_struct
{
structmm_struct *vm_mm;/*用于指向運行的內(nèi)存管理結(jié)構(gòu)*/
unsignedlongvm_start;
/*線性區(qū)的起始地址*/unsignedlongvm_end; /*線性區(qū)的結(jié)束地址*/
structvm_area_struct*vm_next; /*指向鏈表中的下一個線
性區(qū)*/
pgprot_t vm_page_port; /*頁保護位*/
unsignedshortvm_flags;
shortvm_avl_height; /*在AVL中以該線性區(qū)為
根結(jié)點的子樹高度*/
structvm_area_struct*vm_avl_left;/*指向AVL樹中的左子
樹*/
structvm_area_struct*vm_avl_right;/*指向AVL樹中的右子
樹*/
structvm_area_struct*vm_next_share; /*指向區(qū)共享鏈表中的下一個線性區(qū)*/
structvm_area_struct**vm_pprev_share;
structvm_operations_struct*vm_ops;
unsignedlongvm_offset;
structfile*vm_file;
unsignedlongvm_pte;
};對于進程數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系,可以用圖6-2來描述。圖6-2中,可以看出Linux會為一個進程產(chǎn)生一組vin_area
_Struct結(jié)構(gòu)描述虛擬地址空間段。每個vm_area_struct結(jié)構(gòu)描述可執(zhí)行映像的一部分。同一進程的多個vm_areastruct結(jié)構(gòu)通過vm_next指針鏈接成一個單向鏈表。系統(tǒng)會以虛擬內(nèi)存地址的降序排列vm_area_struct結(jié)構(gòu),從而建立起文件的邏輯地址到虛擬線性地址的映射。當(dāng)進程請求分配虛擬內(nèi)存時,Linux并不直接分配物理內(nèi)存。只創(chuàng)建一個vm_area_struct結(jié)構(gòu)來描述該虛擬內(nèi)存,該結(jié)構(gòu)被鏈接到進程的虛擬內(nèi)存鏈表中。當(dāng)進程試圖對新分配的虛擬內(nèi)存進行寫操作時,因為內(nèi)容并不在物理內(nèi)存中,所以系統(tǒng)將產(chǎn)生頁錯誤。然后,處理器會嘗試解析該虛擬地址,如果找不到與該虛擬地址對應(yīng)的頁表入口,處理器將放棄解析并產(chǎn)生頁面錯誤,交給Linux內(nèi)核處理。Linux則查看此虛擬地址是否在當(dāng)前進程的虛擬地址空間中。如果是Linux,會為此進程分配物理頁面,包含在此頁面中的代碼或數(shù)據(jù)可能需要從文件系統(tǒng)或者交換磁盤上讀出。然后進程將從頁面錯誤處開始繼續(xù)執(zhí)行,由于物理內(nèi)存已經(jīng)存在,因此不會再次產(chǎn)生頁面錯誤。圖6-2一個進程的虛擬內(nèi)存及內(nèi)核數(shù)據(jù)結(jié)構(gòu)6.5.1task_struct數(shù)據(jù)結(jié)構(gòu)
為管理Linux系統(tǒng)中的進程,每個進程用一個task_struct數(shù)據(jù)結(jié)構(gòu)來表示(任務(wù)與進程在Linux中可以混用)。實際上,task_struct也就是通常所說的PCB,是對進程控制的唯一手段,也是最有效的手段。task_struct數(shù)據(jù)結(jié)構(gòu)的具體描述如下:6.5ARM嵌入式Linux的進程管理與調(diào)度structtask_struct{
volatilelongstate; /*進程的狀態(tài)*/
longcounter;
longpriority /*進程的優(yōu)先級*/
unslgnedlongsignal;
unslgnedlongblocked;
unsignedlongflags; /*進程的標志*/
interrno;
longdebugreg[8] /*硬件編譯寄存器*/
structexec_domain*exec_domain;
/*variousfields*/
structlinux_binfmt *binfmt;
structtask_struct *next_task,*prev_task;
structtask_struct *next_run,*prey_run;
unsignedlong saved_kernel_stack;
unsignedlong kernel_stack_page;
int exit_code,exit_signal;
unsignedlong personality;
int dumpable:1;
int did_exec:1;
in pid; /*進程標識符*/
int pgrp; /*進程組標號*/
int tty_old_pgrp;
int session;
int leader;
int groups[NGROUPS];
structtask_struct *p_opptr,*p_pptr,*p_cptr,
*P_ysptr,*p_osptr;
structwait_queue *wait_chldexit;
unsignedshort uid,euid,suid,fsuid;
unsignedshort gid,egid,sgid,fsgid:
unsignedlong timeout,policy,rt_priority;
unsignedlong it_real_value,it_prof_value,it_virt_va1ue;
unsignedlong it_real_incr,it_prof_incr,it_virt_incr;
struct timer_listrea1_timer;
1ong utime,stime,cutime,cstime,start--time;
unsignedlong min_f1t,maj_flt,nswap,cmin_f1t,cmaj_flt,cnswap;
intswappab1e:1;
unsigned1ong swap_address;
unsigned1ong
old_maj_flt;
/*用于保存maj_flt的舊值*/
unsignedlong dec_f1t;
unsignedlong swap_cnt; /*下次交換的頁數(shù)*/
structrlimit r1im[RLIM_NLIMITS];
unsignedshort used_math;
char comm[16];
int link_count;
structtty_struct *tty;
structsem_undo *semundo;
structsem_queue *semsleeping;
structdesc_struct *ldt;
structthread_structtss;structfs_struct *fs;
structfiles_struct *files;
structmm_struct *mm;
structsignal_struct*sig;
#ifdef_SMP_
intprocessor;
intlast_processor;
intlock_depth;
#endif
};從以上的代碼中可以看到task_struct數(shù)據(jù)結(jié)構(gòu)非常復(fù)雜。但是仍然可以根據(jù)變量在運行過程中所負責(zé)的不同任務(wù)將它們劃分為以下幾種類型。
1.與進程狀態(tài)相關(guān)的類型
進程在執(zhí)行過程中會根據(jù)環(huán)境來改變狀態(tài),一般來說,Linux進程有以下幾種狀態(tài):
運行態(tài)(Running):進程正在準備運行。進程被標志為運行態(tài),可能會被放入可運行進程隊列中??梢哉J為進程處于隨時可以運行的(準備)就緒狀態(tài)。
可喚醒阻塞態(tài)(Interruptible):進程處于等待隊列中,待資源有效時被激活,也可以由其他進程通過發(fā)送信號或者由定時器中斷喚醒后進入就緒隊列。
不可喚醒阻塞態(tài)(Uninterruptible):進程處于等待隊列中,待資源有效時被激活,不可由其他信號或定時器中斷喚醒。
僵死狀態(tài)(Zombie):進程結(jié)束運行并且已經(jīng)釋放大部分資源,但還沒有釋放進程控制塊。
停滯狀態(tài)(Stopped):進程運行停止,通常是由進程接收到的一個信號所致。當(dāng)某個進程處于調(diào)試狀態(tài)時也可能被暫停執(zhí)行。用戶進程一經(jīng)創(chuàng)建,就開始這五種進程狀態(tài)的轉(zhuǎn)移(參見圖6-3)。進程創(chuàng)建時的狀態(tài)為不可喚醒阻塞態(tài)。當(dāng)所有初始化工作完成之后,被其父進程激活,狀態(tài)被標示為運行態(tài),進入可運行進程隊列。依據(jù)一定的進程調(diào)度算法,某個處于可運行進程隊列的進程被選中,從而獲得處理器使用權(quán)。圖6-3Linux狀態(tài)轉(zhuǎn)移圖在處理器使用過程中,有如下四種情況發(fā)生:
第一種情況是當(dāng)分配給它的時間片結(jié)束之后,該進程會要求放棄其處理器使用權(quán)而后回到可運行進程隊列中。
第二種情況是進程在運行過程中,需要用到某個資源,但是該資源并非空閑,則進程轉(zhuǎn)為不可喚醒阻塞態(tài),當(dāng)資源申請得到滿足之后,進程會自動轉(zhuǎn)成運行態(tài)。
第三種情況就是進程因為受到某種系統(tǒng)信號或者通過系統(tǒng)調(diào)用轉(zhuǎn)入停滯狀態(tài),此時,進程同樣會因為某種信號激發(fā)而轉(zhuǎn)入運行態(tài)。
第四種情況是進程自行退出結(jié)束其任務(wù),進入僵死狀態(tài),等待系統(tǒng)收回它所占有的資源。
2.與調(diào)度信息相關(guān)的類型
調(diào)度器需要這些信息以便判定系統(tǒng)中哪個進程最迫切需要運行、由哪個函數(shù)調(diào)度、如何調(diào)度等。
3.與進程標志相關(guān)的類型
系統(tǒng)中每個進程都有進程標識符。進程標識符并不是task數(shù)組的索引,它僅僅是個數(shù)字。每個進程還有用戶組標識,用來控制進程對系統(tǒng)中文件和設(shè)備的存取權(quán)限。
4.與進程間通信相關(guān)的類型
系統(tǒng)中進程的運行并不是孤立的,進程之間需要互相通信以交換數(shù)據(jù)或代碼。Linux所支持的進程間通信機制包括信號、管道、信號量、共享內(nèi)存和消息隊列。
5.與進程間關(guān)聯(lián)信息相關(guān)的類型
Linux系統(tǒng)中所有進程都是相互聯(lián)系的。除了初始化進程外,所有進程都有一個父進程,新進程不是被創(chuàng)建,而是被復(fù)制或者從以前的進程克隆而來的。每個進程對應(yīng)的task_struct結(jié)構(gòu)中包含有指向其父進程和兄弟進程(具有相同父進程的進程)以及子進程的指針。
6.與時間相關(guān)的類型
內(nèi)核需要記錄進程的創(chuàng)建時間以及在其生命期中消耗的CPU時間。時鐘每跳動一次,內(nèi)核就更新保存在jiffies變量中的值——該變量記錄進程在系統(tǒng)和用戶模式下消耗的時間量。Linux支持與進程相關(guān)的interval定時器,進程可以通過系統(tǒng)調(diào)用來設(shè)定定時器,以便在定時器到時后向它發(fā)送信號。這些定時器可以是一次性或者周期性的。
7.與文件相關(guān)的類型
進程可以自由地打開或關(guān)閉文件。進程的tssk_truct結(jié)構(gòu)中包含一個指向每個打開文件描述符的指針以及指向兩個VFSinode的指針。每個VFSinode唯一地標識文件中的一個目錄或者文件,同時還對底層文件系統(tǒng)提供統(tǒng)一的接口。這兩個指針一個指向進程的根目錄,另一個指向其當(dāng)前或者pwd目錄。pwd從UNIX的pwd命令中派生出來,用來顯示當(dāng)前的工作目錄。這兩個VFSinode中包含一個count域,多個進程引用它時,它的值將增加,這就是為什么不能刪除進程的當(dāng)前目錄或者其子目錄的原因。
8.與虛擬內(nèi)存相關(guān)的類型
在6.4.4節(jié)中,已經(jīng)討論過虛擬內(nèi)存的相關(guān)內(nèi)容。Task_truct結(jié)構(gòu)中必須具有相關(guān)的變量來跟蹤虛擬內(nèi)存與系統(tǒng)物理內(nèi)存的映射關(guān)系。
9.與處理器上下文相關(guān)的類型
進程可以認為是系統(tǒng)當(dāng)前狀態(tài)的總和。進程運行時將使用處理器的寄存器以及堆棧等;進程被掛起時進程的上下文——所有的CPU相關(guān)狀態(tài)——必須保存在它的task_truct結(jié)構(gòu)中。當(dāng)調(diào)度器重新調(diào)度該進程時,所有上下文將被重新設(shè)定。6.5.2Linux進程的創(chuàng)建、執(zhí)行和終止
1.Linux進程的創(chuàng)建
系統(tǒng)啟動的時候,整個系統(tǒng)只有一個進程被創(chuàng)建,這就是初始化進程。初始化進程也擁有自己的堆棧、寄存器等基本的結(jié)構(gòu)和資源。初始化進程的所有信息也同樣保存在它的task_struct結(jié)構(gòu)中。在系統(tǒng)初始化的最后,初始化進程啟動一個內(nèi)核線程init,該線程主要負責(zé)完成系統(tǒng)的一些初始化設(shè)置任務(wù),比如安裝根文件系統(tǒng)等。隨后,init線程會調(diào)度并執(zhí)行系統(tǒng)的一些其他初始化程序,比如/etx/init、/bin/init等。此后,如果沒有其他的進程需要創(chuàng)建,調(diào)度管理器將運行idle進程,它是唯一不動態(tài)分配給task_struct的進程。idle進程的task_struct是在內(nèi)核載入時靜態(tài)定義的,名稱為init_task。
Linux可以通過調(diào)用fork()和clone來創(chuàng)建新進程。當(dāng)一個新的進程需要被創(chuàng)建時,系統(tǒng)從物理內(nèi)存中分配出來一個新的task_struct數(shù)據(jù)結(jié)構(gòu),以及一個或多個包含被復(fù)制進程堆棧(用戶與核心)的物理頁面,然后創(chuàng)建唯一地標記此新進程的進程標識符,并將創(chuàng)建的task_struct結(jié)構(gòu)放入到task數(shù)組中。同時,將父進程的task_truct中的內(nèi)容頁表拷入新的task_struct中。另外,Linux允許父進程和子進程共享資源,而不是完全使用自己的拷貝。這些資源包括文件、信號處理過程和虛擬內(nèi)存等。Linux對于復(fù)制進程虛擬空間所使用的技術(shù)非常巧妙。因為進程的虛擬內(nèi)存有的可能在物理內(nèi)存中,有的可能在當(dāng)前進程的可執(zhí)行映像中,而有的可能在交換文件中,所以拷貝將是一個困難且繁瑣的工作。為此,Linux對父進程的任何虛擬內(nèi)存都沒有被拷貝。Linux使用一種“copyonwrite”技術(shù),僅當(dāng)兩個進程之一對虛擬內(nèi)存進行寫操作時才拷貝此虛擬內(nèi)存塊。但是,任何虛擬內(nèi)存都可以在兩個進程間共享。一般來說,只讀屬性的內(nèi)存,如果執(zhí)行代碼,都是可以共享的。為了使“copyonwrite”策略工作,Linux必須將那些可寫區(qū)域的頁表入口標記為只讀的,同時,描述它們的vm_area_struct數(shù)據(jù)設(shè)置為“copyonwrite”。當(dāng)父進程或者子進程試圖對虛擬內(nèi)存進行寫操作時,將產(chǎn)生頁面錯誤。這時Linux才會拷貝這塊內(nèi)存并修改兩個進程的頁表以及虛擬內(nèi)存的數(shù)據(jù)結(jié)構(gòu)。2.Linux進程的執(zhí)行
通過fork創(chuàng)建子進程以后,只是給子進程分配了必需的資源,但是子進程并沒有進入運行狀態(tài)。如果要讓子進程執(zhí)行,則必須通過exec系統(tǒng)調(diào)用。exec其實是一個引用一系列函數(shù)的通用術(shù)語,它包含的函數(shù)基本上都由內(nèi)核中的系統(tǒng)調(diào)用服務(wù)函數(shù)sys_execve()加上不同的參數(shù)來實現(xiàn)。實際上,sys_execve()的執(zhí)行流程相對來說比較簡單,一是通過getname()子函數(shù)把可執(zhí)行文件從用戶空間調(diào)入到內(nèi)核空間,二是調(diào)用do_execve()函數(shù)來執(zhí)行具體的任務(wù)。下面對do_execve()函數(shù)的執(zhí)行流程進行分析。
do_execve()函數(shù)的執(zhí)行流程如下:
(1)執(zhí)行open操作,打開可執(zhí)行文件,并獲取該文件的file結(jié)構(gòu)。
(2)讀取參數(shù)區(qū)的長度,并調(diào)用memset()函數(shù)把存放參數(shù)的頁面清零。
(3)初始化linux_binprm結(jié)構(gòu)中的剩余項。結(jié)構(gòu)linux_binprm是用來讀取、存儲及運行可執(zhí)行文件的必要信息,該結(jié)構(gòu)定義如下:structlinux_binprm{
charbuf[BINPRM_BUF_SIZE]; /*文件頭緩沖區(qū),BINPRM_SIZE=128*/
unsignedlongpage[MAX_ARG_PAGES]; /*存放參數(shù)頁面的頁表*/
unsignedlongp; /*參數(shù)區(qū)長度*/
intsh_bang;
structfile*fi1e;
inte_uid,e_gid;
kerne1_cap_tcap_inheritab1e,cap_permitted,
cap_effective;
intargc,envc; /*參數(shù)個數(shù),環(huán)境個數(shù)*/
char*filename; /*可執(zhí)行文件路徑名*/
unsignedlongloader,exec;
};
(4)調(diào)用prepare_binprm()函數(shù),對數(shù)據(jù)結(jié)構(gòu)linux_binprm做進一步的準備,并完成訪問權(quán)限等內(nèi)容檢測。同時,把可執(zhí)行文件中的前128個字節(jié)讀入到linux_binprm的緩沖區(qū)中。
(5)把文件名、環(huán)境變量、文件參數(shù)等從用戶空間復(fù)制到內(nèi)核空間。
(6)調(diào)用search_binary_handler()函數(shù)。該函數(shù)的功能是搜尋目標文件的處理模塊(也就是二進制處理程序)并執(zhí)行,只有該函數(shù)被執(zhí)行,新進程所執(zhí)行的任務(wù)才與父進程的任務(wù)區(qū)別開來。二進制處理程序是Linux內(nèi)核統(tǒng)一處理各種二進制格式的機制,因為不是所有的文件都是以相同的文件格式存儲的,所以系統(tǒng)需要合適的處理程序來對文件進行處理。通過使用適當(dāng)?shù)亩M制處理程序,Linux可以把不同格式的文件當(dāng)作是自己特有的可執(zhí)行文件來進行處理。在搜索過程中,search_binary_handler()函數(shù)會通過一個大循環(huán)遍歷搜索linux_binfmt結(jié)構(gòu)鏈表,來找出合適的二進制處理程序。
linux_binfmt結(jié)構(gòu)定義如下:
structlinux_binfmt{
structlinux_binfmt *next;
longuse_count;/*使用計數(shù)*/
int(*load_binary)(structlinux_binfmt,structpt_regs regs); /*指向二進制代碼*/
int(*load_shlib)(intfd);/*指向庫函數(shù)*/
int(*core_dump)longsignr,structpt_regs*regs);
};
linux_binfmt的結(jié)構(gòu)中包含兩個指向函數(shù)的指針:laod_binary和load_shlib,使用這兩個指針是為了裝入可執(zhí)行代碼和要使用的庫。另外,linux_binfmt結(jié)構(gòu)中的next指針構(gòu)成一個鏈表,表頭由formats指示。Linux系統(tǒng)會為每種不同的文件格式定義一個相應(yīng)的對象,linux_binfmt的鏈表就是由這些不同的文件格式的linux_binfmt結(jié)構(gòu)構(gòu)成的一個鏈表。對于不同格式二進制文件的處理程序,會通過注冊在相應(yīng)的linux_binfmt中的函數(shù)執(zhí)行。linux_binfmt結(jié)構(gòu)鏈表如圖6-4所示。圖6-4linux_binfmt結(jié)構(gòu)鏈表
3.Linux進程的終止
在Linux中,進程的終止可以通過系統(tǒng)調(diào)用do_exit()實現(xiàn)。當(dāng)然,do_exit()函數(shù)終止的是當(dāng)前進程。執(zhí)行do_exit()時,首先會為當(dāng)前進程做上PF_EXITING標記,以釋放當(dāng)前進程的存儲管理信息、文件系統(tǒng)、文件信息、信號響應(yīng)函數(shù)、指針數(shù)組等,然后將進程狀態(tài)設(shè)置成TASK_ZOMBIE,并通知當(dāng)前進程的父進程。do_exit()帶有一個參數(shù)code,用于傳遞終止進程的原因。do_exit()的代碼如下:NORET_TYPEvoiddo_exit(longcode){
structtask_struct*tsk=current;
intgroup_dead;
profile_task_exit(tsk);
/*如進程中斷服務(wù)程序中調(diào)用do_exit(),則打印提示信息*/
if(un1ikely(in_interrupt()))
panic("Aiee,kil1inginterrupthandler !”);
if(unlikely(!Tsk->pid))
panic("Attemptedtokilltheid1etask!”):
if(unlikely(tsk->pid==1))
panic("Attemptedtokillinit!”);
if(tsk->io_context)
exit_io_context(); /*記錄進程的記賬相關(guān)信息*/
tsk->flags|=PF_EXITING; /*把進程標記為PF_EXITING*/
del_timer_sync(&tsk->real_timer); /*釋放定時器鏈表*/
if(unlikely(in_atomic()))
printk(KERN_INFO"note:%s[%d]exitedwithpreempt_count%d\n",
current->comm,current->pid,preempt_count());
if(unlikely(current-->ptrace&PT_TRACE_EXIT)){
current->ptrace_message=code;
ptrace_notify((PTRACE_EVENTEXIT<<8)|SIGTRAP):
}
Group_dead=atomic_dec_and_test(&tsk->signal->live);
if(group_dead)
acct_process(code);
_exit_mm(tsk); /*釋放進程的存儲管理信息*/
Exit_sem(tsk); /*處理與tsk相關(guān)的信號量*/
_exit_files(tsk); /*釋放進程已打開文件的信息*/
_exit_fs(tsk); /*釋放進程的文件系統(tǒng)*/
Exit_namespace(tsk); /*釋放tsk所屬的名字空間*/
Exit_thread(); /*清除任務(wù)的輸入/輸出*/
Exit_keys(tsk); /*對于CONFIG_KEYS無效的情況進行處理*/
if(group_dead&&tsk->signal->leader)
disassociate_ctty(1);
module_put(tsk->threadinfo->exec-domain->module);
if(tsk->binfmt)
module_put(tsk->binfmt->module);
tsk->exit_code=code;
exit_notify(tsk);/*向所有和當(dāng)前進程有關(guān)的進程發(fā)送相應(yīng)的消息*/
#ifdefCONFIG_NUMA
Mpol_free(tsk->mempolicy);
tsk->mempolicy=NULL;
#endif
BUG_ON(!(current->flags&PF_DEAD));
schedu1e(); /*調(diào)用schedule函數(shù)以讓出CPU的使用控制權(quán)*/
BUG();
for(;;);
}以上進程通過調(diào)用do_exit()函數(shù)終止,稱為進程的主動終止方式。還有另外一種方式也可以使其終止,就是其他的進程或者用戶通過向其發(fā)送信號量9,使其強行被終止,這種方式稱為被動終止方式。6.5.3ARM嵌入式Linux的進程調(diào)度
隨著Linux內(nèi)核的不斷更新,其調(diào)度策略也有所變化。Linux在以下情況下需要進程的調(diào)度:
(1)進程狀態(tài)轉(zhuǎn)換時,如進程終止睡眠等。
(2)可運行隊列中增加新的進程時。
(3)當(dāng)進程的時間片耗盡時。
(4)進程從系統(tǒng)調(diào)用返回到用戶態(tài)時。
(5)內(nèi)核處理完中斷后,進程返回到用戶態(tài)時。
盡管不同版本的進程調(diào)度策略有所不同,Linux的進程調(diào)度均由位于kernel/sched.c中的函數(shù)schedule()來實現(xiàn)。因為其源代碼較長,所以以下只是對該函數(shù)執(zhí)行流程的概括描述。
(1)當(dāng)前進程所指的內(nèi)存若為空,則出錯返回。
(2)令prev=current;this_cpu=prev->processor。
(3)若中斷服務(wù)程序調(diào)用schedule(),則跳轉(zhuǎn)至scheduling_in_interrupt。
(4)釋放全局內(nèi)核鎖。
(5)若有bottomhalf服務(wù)請求,則調(diào)用do_bottom_half()。
(6)保存當(dāng)前CPU調(diào)度進程的數(shù)據(jù)區(qū),對運行隊列加spinlock。
(7)若采用輪轉(zhuǎn)法進行調(diào)度,則對counter=0的情況進行處理。
(8)檢測進程狀態(tài):對need_resched置0。
(9)將next設(shè)置為當(dāng)前CPU的idletask,對當(dāng)前goodness變量c賦值。
(10)若當(dāng)前的進程是TASK_RUNNING狀態(tài),則變量c賦值為當(dāng)前進程goodness函數(shù)的返回值。
(11)搜索運行隊列計算出的每一個進程的goodness并與當(dāng)前的goodnesst值比較,goodness值最高的進程將獲取CPU。
(12)若運行隊列中所有進程的時間片都耗盡,則對系統(tǒng)中的所有進程重新分配時間片;否則,跳轉(zhuǎn)至步驟(9)。
(13)令sched_data->curt=next。
(14)若下一個進程與當(dāng)前進程不同,則執(zhí)行下一步驟;否則,跳轉(zhuǎn)至步驟(17)。
(15)準備進行進程轉(zhuǎn)換。
(16)切換至新的進程。
(17)重新獲取globalkernellock。
(18)若當(dāng)前進程的重調(diào)度標志為零,則返回并退出;否則,跳轉(zhuǎn)至步驟(2)。
ARM體系中通常用如下三種方式控制程序的執(zhí)行流程:
(1)在正常的程序執(zhí)行過程中,每執(zhí)行一條ARM指令,程序計數(shù)器(PC)的值加4個字節(jié);每執(zhí)行一條Thumb指令,程序計數(shù)器(PC)的值加2個字節(jié)。整個過程按序執(zhí)行。
(2)通過跳轉(zhuǎn)指令,程序可以跳到指定的地址標號處執(zhí)行,或者調(diào)用指定的子程序執(zhí)行。
6.6ARM嵌入式Linux的中斷響應(yīng)與處理6.6.1ARM的異常中斷種類
1.復(fù)位(Reset)
當(dāng)處理器的復(fù)位引腳有效時,系統(tǒng)產(chǎn)生復(fù)位異常中斷,程序跳轉(zhuǎn)到復(fù)位異常中斷處理程序處執(zhí)行。復(fù)位異常中斷通常用在:①系統(tǒng)加電時;②系統(tǒng)復(fù)位時;③跳轉(zhuǎn)到復(fù)位中斷向量處時,這稱為軟件復(fù)位。
2.未定義的指令(UndefinedInstruction)
當(dāng)ARM處理器或者是系統(tǒng)中的協(xié)處理器認為當(dāng)前指令未定義時,產(chǎn)生未定義指令異常中斷。
3.軟件中斷(SoftwareInterrupt,SWI)
軟件中斷是用戶定義的中斷指令,可用于用戶模式下的程序調(diào)用特權(quán)操作指令。
4.指令預(yù)取中止(PrefetchAbort)
如果處理器預(yù)取的指令地址不存在,或者該地址不允許當(dāng)前指令訪問,那么當(dāng)該被預(yù)取的指令執(zhí)行時,處理器將產(chǎn)生指令預(yù)取中止異常中斷。
5.?dāng)?shù)據(jù)訪問中止(DataAbort)
如果數(shù)據(jù)訪問指令的目標地址不存在,或者該地址不允許當(dāng)前指令訪問,那么處理器將產(chǎn)生數(shù)據(jù)訪問中止異常中斷。
6.外部中斷請求(IRQ)
當(dāng)處理器的外部中斷請求引腳有效,而且CPSR寄存器的I控制位被清除時,處理器將產(chǎn)生外部中斷請求異常中斷。系統(tǒng)中各個外設(shè)通常通過該異常中斷請求處理器的服務(wù)。
7.快速中斷請求(FIQ)
當(dāng)處理器的外部快速中斷請求引腳有效,而且CPSR寄存器的F控制位被清除時,處理器將產(chǎn)生快速中斷請求異常中斷。6.6.2ARM處理器對異常中斷的響應(yīng)及返回過程
ARM體系中,通常在存儲地址的低端固化了一個32字節(jié)的硬件中斷向量表,用來指定各異常中斷及其處理程序的對應(yīng)關(guān)系。當(dāng)異常出現(xiàn)后,ARM微處理器會執(zhí)行以下幾步
操作:
(1)保存處理器當(dāng)前狀態(tài)、中斷屏蔽位及各條件標志位。這通過將當(dāng)前程序狀態(tài)寄存器CPSR的內(nèi)容保存到將要執(zhí)行的異常中斷對應(yīng)的SPSR寄存器中實現(xiàn)。
(2)設(shè)置當(dāng)前程序狀態(tài)寄存器CPSR中相應(yīng)的位。這包括:設(shè)置CPSR中的位,使處理器進入相應(yīng)的執(zhí)行模式;設(shè)置CPSR中的位,禁止IRQ中斷。當(dāng)進入FIQ模式時,禁止FIQ中斷。
(3)將寄存器Ir_mode設(shè)置成返回地址。
(4)將程序計數(shù)器(PC)值設(shè)置成該異常中斷的中斷向量地址,從而跳轉(zhuǎn)到相應(yīng)的異常中斷處理程序處執(zhí)行。在接收到中斷請求以后,ARM處理器內(nèi)核會自動執(zhí)行以上四步,程序計數(shù)器PC總是跳轉(zhuǎn)到相應(yīng)的固定地址。從異常中斷處理程序中返回,包括下面兩個基本操作:
(1)恢復(fù)被屏蔽的程序的處理器狀態(tài),即將SPSR_mode中的內(nèi)容復(fù)制回SPSR。
(2)返回到發(fā)生異常中斷的指令的下一條指令處繼續(xù)執(zhí)行,即將lr_mode的內(nèi)容復(fù)制回程序計數(shù)器PC。當(dāng)異常中斷發(fā)生時,程序計數(shù)器PC所指的位置對于各種不同的異常中斷是不同的,同樣,返回地址對于各種不同的異常中斷也是不同的。例外的是,復(fù)位異常中斷處理程序不需要返回,因為整個應(yīng)用系統(tǒng)是從復(fù)位異常中斷處理程序開始執(zhí)行的。下面介紹支持中斷跳轉(zhuǎn)的解析程序。
1.解析程序的概念和作用
如前所述,ARM處理器響應(yīng)中斷時,總是從固定的地址開始,而在高級語言環(huán)境下開發(fā)中斷服務(wù)程序時,無法控制固定地址開始的跳轉(zhuǎn)流程。為了使上層應(yīng)用程序與硬件中斷跳轉(zhuǎn)聯(lián)系起來,需要編寫一段中間的服務(wù)程序進行連接。這樣的服務(wù)程序常被稱做中斷解析程序。每個異常中斷對應(yīng)一個4字節(jié)的空間,正好放置一條跳轉(zhuǎn)指令或向PC寄存器賦值的數(shù)據(jù)訪問指令。理論上可以通過這兩種指令直接使得程序跳轉(zhuǎn)到對應(yīng)的中斷處理程序中。但實際上由于函數(shù)地址值未知及其他一些問題,通常不這樣處理。常用的中斷跳轉(zhuǎn)流程如圖6-5所示。圖6-5中斷跳轉(zhuǎn)流程流程的關(guān)鍵部分是中斷向量表,為使解析程序能找到向量表,應(yīng)該將向量表的地址固化(編程者自定義)。這樣,整個跳轉(zhuǎn)流程的所有程序地址都是固定的,當(dāng)中斷觸發(fā)后可以自動運行。其中,只有向量表的內(nèi)容是可變的,編程者只要在向量表中填入正確的目標地址值即可。這使得上層中斷處理程序和底層硬件跳轉(zhuǎn)可以有機地聯(lián)系起來。
2.解析過程示例
這里以一次IRQ跳轉(zhuǎn)為例,假定中斷向量表定義在0x00400000開始的外部RAM空間,見圖6-6。
圖6-6中,實線表示的流程都用ARM匯編語言編寫,一般作為Bootloader代碼的一部分放在系統(tǒng)的底層模塊中。填寫向量表的操作可以在上層應(yīng)用程序中實現(xiàn),比如在C語言中,使用*(int*(0x00400018))=(init)ISR_IRQ;可將IRQ中斷的服務(wù)程序入口地址(0x00300260)填寫到中斷向量表中以固定地址0x00400018開始的4字節(jié)空間中。如此可避免在應(yīng)用程序中計算中斷的跳轉(zhuǎn)地址,并且可以很方便地選擇不同的函數(shù)作為指定中斷的服務(wù)程序。當(dāng)然,在程序開發(fā)時要合理配置向量表地址,避免對向量表地址空間不必要的寫操作。圖6-6中斷解析示例流程
3.解析程序的擴展
在ARM處理器中會包含很多中斷源,通常會在ARM內(nèi)核外面擴展中斷控制器管理各種原因產(chǎn)生的中斷。比如,三星公司的S3C4510B處理器中的IRQ/FIQ類型的中斷源可以有21個,S3C44BOX有26個。中斷處理的原理相同,無非是向量表更長,并且當(dāng)一個中斷觸發(fā)以后,需要在解析程序里查詢中斷控制器的狀態(tài)來確定具體的中斷源,再根據(jù)中斷源來讀取向量表中的對應(yīng)地址內(nèi)容。其處理流程可用圖6-7表示。圖6-7對中斷解析的擴展流程相比圖6-6,圖6-7中多了一級的跳轉(zhuǎn),也就是在第一次解析跳轉(zhuǎn)到IRQ/FIQ服務(wù)程序中后,再進行第二次的解析——中斷源的識別。
4.向量中斷的處理
一些處理器在設(shè)計外擴的中斷控制器時提供了一種叫做“向量中斷”的中斷跳轉(zhuǎn)機制。這與前文敘述的擴展解析跳轉(zhuǎn)流程有所不同,它不需要軟件來識別具體的中斷源,也就是不需要添加圖6-7中的IRQ/FIQ服務(wù)程序,而完全由硬件自動跳轉(zhuǎn)到對應(yīng)的中斷地址。其他跳轉(zhuǎn)流程的原理都是一樣的。這相當(dāng)于擴展了ARM內(nèi)核的硬件中斷向量表,減少了中斷響應(yīng)延時。這里以S3C44B0X處理器的外部中斷0為例,需要在其對應(yīng)的硬件固定跳轉(zhuǎn)地址0x00000020處添加指令:ldrpc,=HandlerEINT,使得程序跳轉(zhuǎn)到其服務(wù)程序HandlerEINT0處執(zhí)行,如圖6-8所示。圖6-8向量中斷解析流程示例6.7.1Linux的模塊化
Linux是一個單內(nèi)核操作系統(tǒng),其所有的內(nèi)核功能構(gòu)件均可訪問任一個內(nèi)部數(shù)據(jù)結(jié)構(gòu)和例程。Linux可針對用戶需要,動態(tài)地載入和卸載操作系統(tǒng)構(gòu)件。Linux模塊是一些代碼的集成,可以在啟動系統(tǒng)以后動態(tài)鏈接到內(nèi)核的任一部分,當(dāng)不再需要這些模塊時,又可隨時斷開鏈接并將其刪除。Linux內(nèi)核模塊通常是設(shè)備驅(qū)動程序、偽設(shè)備驅(qū)動程序(如網(wǎng)絡(luò)驅(qū)動程序)或文件系統(tǒng)。6.7嵌入式Linux的模塊化機制對于Linux的內(nèi)核模塊,可以用insmod或rmmod命令顯式地載入或卸載,或是在需要時調(diào)用內(nèi)核守護程序(kerneld)進行載入和卸載。進行動態(tài)載入工作的代碼非常有效,它將最小化內(nèi)核大小并增加內(nèi)核的靈活性。當(dāng)調(diào)試新內(nèi)核時,模塊也非常有用,通過對它的動態(tài)載入可省去每次重建和重啟內(nèi)核。當(dāng)然,使用模塊將降低系統(tǒng)性能并消耗一部分內(nèi)存空間,因為載入模塊會額外多出一些代碼和數(shù)據(jù)結(jié)構(gòu),并會間接地降低訪問內(nèi)核資源的效率。6.7.2模塊的載入
一般有兩種載入模塊方法:一種是用insmod命令手工載入;另一種方法更為靈活,是在需要時自動載入,這種方法稱為需求載入,當(dāng)內(nèi)核發(fā)現(xiàn)需要載入某個模塊時,會要求內(nèi)核守護程序載入相應(yīng)的模塊。
內(nèi)核守護程序是擁有超級用戶權(quán)限的一般用戶進程,啟動后(系統(tǒng)啟動時)會打開一個指向內(nèi)核的內(nèi)部進程間通信(Inter-ProcessCommunication,IPC)通道,內(nèi)核用該通道通知內(nèi)核守護程序進行各種操作。
內(nèi)核守護程序的主要工作是載入和卸載模塊,也有其他一些任務(wù),如打開和關(guān)閉使用電話線的PPP連接。內(nèi)核守護程序通過調(diào)用相應(yīng)的程序(如insmod)完成,內(nèi)核守護程序只是內(nèi)核代理,自動地安排調(diào)度各項工作。
insmod工具在加載之前,先要打開欲加載內(nèi)核模塊,需要時才將被加載的模塊保存在/lib/modules/kernel-version中,內(nèi)核模塊實際是一些鏈接的對象文件,與系統(tǒng)中其他的程序一樣,只不過是作為重分配的映像被鏈接的,也就是說,它們并非從一特定地址開始運行。內(nèi)核模塊可以是a.out或elfta式的對象文件,insmod要進行一次系統(tǒng)調(diào)用來查找內(nèi)核導(dǎo)出符號,這些符號保存在符號名值中。內(nèi)核維護了一張模塊表,模塊表中第一個模塊的數(shù)據(jù)結(jié)構(gòu)內(nèi)包括內(nèi)核導(dǎo)出符號表,module_list指針指向該符號表。只有特定的符號被加入到符號表中,并在編譯和鏈接內(nèi)核時創(chuàng)建,并非所有內(nèi)核中的符號都導(dǎo)出到其模塊上?!皉equest_irq”就是一個符號,當(dāng)一個驅(qū)動程序希望控制特定的系統(tǒng)中斷時,就要調(diào)用該內(nèi)核例程,通過查看/proc/ksyms文件或使用ksyms工具,可以看到導(dǎo)出內(nèi)核符號的名和值。ksyms工具能顯示出所有的導(dǎo)出內(nèi)核符號或僅僅被已載入模塊所導(dǎo)出的符號。
insmod將模塊讀入虛擬內(nèi)存中,然后利用內(nèi)核中的導(dǎo)出符號解決模塊對內(nèi)核進程的引用,解決方法是在內(nèi)存中對模塊映像進行修補:fnsmod把符號地址物理地寫入模塊的相應(yīng)位置中。
解決了模塊對導(dǎo)出內(nèi)核符號的引用問題,insmod通過系統(tǒng)調(diào)用為新的內(nèi)核申請足夠的空間,內(nèi)核為該模塊分配一個新的模塊數(shù)據(jù)結(jié)構(gòu)和足夠的核心內(nèi)存空間,并將其加到內(nèi)核模塊尾。可以用lsmod命令列出所有已載入的模塊及其相互的依賴關(guān)系。lsmod只是簡單地重新組織/proc/modules文件,該文件通過內(nèi)核模塊數(shù)據(jù)結(jié)構(gòu)表創(chuàng)建,在內(nèi)存中的地址被映射到insmod進程的地址空間中,便于該進程訪問。
insmod把模塊復(fù)制到為其分配的空間中,并重定位該模塊,這樣就可以從內(nèi)核地址上開始運行。若模塊在系統(tǒng)中需要載入兩次,為避免其兩次載入時擁有同一個地址,這種處理是必需的。對于這種重定位,則須用相應(yīng)的地址對模塊映像進行修補。
新模塊也要向內(nèi)核導(dǎo)出符號,insmod將會
溫馨提示
- 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 衛(wèi)生院信息報送工作制度
- 農(nóng)村衛(wèi)生所協(xié)管制度
- 萬達公共衛(wèi)生間管理制度
- 水果間衛(wèi)生監(jiān)管制度
- 某單位衛(wèi)生管理制度
- 衛(wèi)生健康宣傳制度
- 衛(wèi)生保健所規(guī)章制度
- 精神科食品衛(wèi)生管理制度
- 學(xué)校衛(wèi)生間消殺制度
- 選煤廠職業(yè)衛(wèi)生管理制度
- 加班工時管控改善方案
- 2025年江蘇省高考地理真題(含答案解析)
- 口腔科院感預(yù)防與控制考核試題附答案
- 心肌梗死護理教學(xué)課件
- 2025年市場監(jiān)督管理局招聘面試題及答案
- DB42T 1279-2017 機動車檢驗檢測機構(gòu)資質(zhì)認定評審?fù)?用指南
- 應(yīng)急測繪服務(wù)方案(3篇)
- 2025至2030年中國移動充電車行業(yè)市場全景評估及發(fā)展策略分析報告
- 2025年湖南省長沙市長郡教育集團中考三模道德與法治試題
- 南京市五校聯(lián)盟2024-2025學(xué)年高二上學(xué)期期末考試英語試卷(含答案詳解)
- 云南省昆明市五華區(qū)2024-2025學(xué)年高一上學(xué)期1月期末考試地理試題(解析版)
評論
0/150
提交評論