国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
阿冰BLOG : Linux內(nèi)核分析方法談

Linux內(nèi)核分析方法談

posted on 2005年9月25日 21:26 由 devzhao

Linux內(nèi)核分析方法談
方法之一:從何入手
方法之二:以程序流程為線索,一線串珠
方法之二:I、系統(tǒng)的引導(dǎo)
方法之二:II、實(shí)模式下的初始化
方法之二:III、保護(hù)模式下的初始化
方法之三:以數(shù)據(jù)結(jié)構(gòu)為基點(diǎn),觸類旁通
方法之三:I、硬件提供的中斷機(jī)制和約定
方法之三:II、Linux的中斷處理
方法之四:以功能為中心,各個(gè)擊破
方法之四:I、系統(tǒng)調(diào)用的初始化設(shè)置和相關(guān)數(shù)據(jù)結(jié)構(gòu)
方法之四:II、系統(tǒng)調(diào)用過程
方法之四:III、系統(tǒng)調(diào)用總控程序(system_call)


Linux
內(nèi)核分析方法談


  Linux的最大的好處之一就是它的源碼公開。同時(shí),公開的核心源碼也吸引著無數(shù)的電腦愛好者和程序員;他們把解讀和分析Linux的核心源碼作為自己的最大興趣,把修改Linux源碼和改造Linux系統(tǒng)作為自己對計(jì)算機(jī)技術(shù)追求的最大目標(biāo)。

  Linux內(nèi)核源碼是很具吸引力的,特別是當(dāng)你弄懂了一個(gè)分析了好久都沒搞懂的問題;或者是被你修改過了的內(nèi)核,順利通過編譯,一切運(yùn)行正常的時(shí)候。那種成就感真是油然而生!而且,對內(nèi)核的分析,除了出自對技術(shù)的狂熱追求之外,這種令人生畏的勞動所帶來的回報(bào)也是非常令人著迷的,這也正是它擁有眾多追隨者的主要原因:

首先,你可以從中學(xué)到很多的計(jì)算機(jī)的底層知識,如后面將講到的系統(tǒng)的引導(dǎo)和硬件提供的中斷機(jī)制等;其它,象虛擬存儲的實(shí)現(xiàn)機(jī)制,多任務(wù)機(jī)制,系統(tǒng)保護(hù)機(jī)制等等,這些都是非都源碼不能體會的。
同時(shí),你還將從操作系統(tǒng)的整體結(jié)構(gòu)中,體會整體設(shè)計(jì)在軟件設(shè)計(jì)中的份量和作用,以及一些宏觀設(shè)計(jì)的方法和技巧:Linux的內(nèi)核為上層應(yīng)用提供一個(gè)與具體硬件不相關(guān)的平臺;同時(shí)在內(nèi)核內(nèi)部,它又把代碼分為與體系結(jié)構(gòu)和硬件相關(guān)的部分,和可移植的部分;再例如,Linux雖然不是微內(nèi)核的,但他把大部分的設(shè)備驅(qū)動處理成相對獨(dú)立的內(nèi)核模塊,這樣減小了內(nèi)核運(yùn)行的開銷,增強(qiáng)了內(nèi)核代碼的模塊獨(dú)立性。
而且你還能從對內(nèi)核源碼的分析中,體會到它在解決某個(gè)具體細(xì)節(jié)問題時(shí),方法的巧妙:如后面將分析到了的Linux通過Botoom_half機(jī)制來加快系統(tǒng)對中斷的處理。
最重要的是:在源碼的分析過程中,你將會被一點(diǎn)一點(diǎn)地、潛移默化地專業(yè)化。一個(gè)專業(yè)的程序員,總是把代碼的清晰性,兼容性,可移植性放在很重要的位置。他們總是通過定義大量的宏,來增強(qiáng)代碼的清晰度和可讀性,而又不增加編譯后的代碼長度和代碼的運(yùn)行效率;他們總是在編碼的同時(shí),就考慮到了以后的代碼維護(hù)和升級。 甚至,只要分析百分之一的代碼后,你就會深刻地體會到,什么樣的代碼才是一個(gè)專業(yè)的程序員寫的,什么樣的代碼是一個(gè)業(yè)余愛好者寫的。而這一點(diǎn)是任何沒有真正分析過標(biāo)準(zhǔn)代碼的人都無法體會到的。
  然而,由于內(nèi)核代碼的冗長,和內(nèi)核體系結(jié)構(gòu)的龐雜,所以分析內(nèi)核也是一個(gè)很艱難,很需要毅力的事;在缺乏指導(dǎo)和交流的情況下,尤其如此。只有方法正確,才能事半功倍。正是基于這種考慮,作者希望通過此文能給大家一些借鑒和啟迪。

  由于本人所進(jìn)行的分析都是基于2.2.5版本的內(nèi)核;所以,如果沒有特別說明,以下分析都是基于i386單處理器的2.2.5版本的Linux內(nèi)核。所有源文件均是相對于目錄/usr/src/linux的。


方法之一:從何入手



  要分析Linux內(nèi)核源碼,首先必須找到各個(gè)模塊的位置,也即要弄懂源碼的文件組織形式。雖然對于有經(jīng)驗(yàn)的高手而言,這個(gè)不是很難;但對于很多初級的Linux愛好者,和那些對源碼分析很有興趣但接觸不多的人來說,這還是很有必要的。

  1、Linux核心源程序通常都安裝在/usr/src/linux下,而且它有一個(gè)非常簡單的編號約定:任何偶數(shù)的核心(的二個(gè)數(shù)為偶數(shù),例如2.0.30)都是一個(gè)穩(wěn)定地發(fā)行的核心,而任何奇數(shù)的核心(例如2.1.42)都是一個(gè)開發(fā)中的核心。

  2、核心源程序的文件按樹形結(jié)構(gòu)進(jìn)行組織,在源程序樹的最上層,即目錄/usr/src/linux下有這樣一些目錄和文件:

COPYING: GPL版權(quán)申明。對具有GPL版權(quán)的源代碼改動而形成的程序,或使用GPL工具產(chǎn)生的程序,具有使用GPL發(fā)表的義務(wù),如公開源代碼;

CREDITS: 光榮榜。對Linux做出過很大貢獻(xiàn)的一些人的信息;

MAINTAINERS: 維護(hù)人員列表,對當(dāng)前版本的內(nèi)核各部分都有誰負(fù)責(zé);

Makefile: 第一個(gè)Makefile文件。用來組織內(nèi)核的各模塊,記錄了個(gè)模塊間的相互這間的聯(lián)系和依托關(guān)系,編譯時(shí)使用;仔細(xì)閱讀各子目錄下的Makefile文件對弄清各個(gè)文件這間的聯(lián)系和依托關(guān)系很有幫助;

ReadMe: 核心及其編譯配置方法簡單介紹;

Rules.make: 各種Makefilemake所使用的一些共同規(guī)則;

REPORTING-BUGS:有關(guān)報(bào)告Bug 的一些內(nèi)容;

Arch/ arch子目錄包括了所有和體系結(jié)構(gòu)相關(guān)的核心代碼。它的每一個(gè)子目錄都代表一種支持的體系結(jié)構(gòu),例如i386就是關(guān)于intel cpu及與之相兼容體系結(jié)構(gòu)的子目錄。PC機(jī)一般都基于此目錄;

Include/: include子目錄包括編譯核心所需要的大部分頭文件。與平臺無關(guān)的頭文件在 include/linux子目錄下,與 intel cpu相關(guān)的頭文件在include/asm-i386子目錄下,include/scsi目錄則是有關(guān)scsi設(shè)備的頭文件目錄;

Init/ 這個(gè)目錄包含核心的初始化代碼(注:不是系統(tǒng)的引導(dǎo)代碼),包含兩個(gè)文件main.cVersion.c,這是研究核心如何工作的好的起點(diǎn)之一。

Mm/:這個(gè)目錄包括所有獨(dú)立于 cpu 體系結(jié)構(gòu)的內(nèi)存管理代碼,如頁式存儲管理內(nèi)存的分配和釋放等;而和體系結(jié)構(gòu)相關(guān)的內(nèi)存管理代碼則位于arch/*/mm/,例如arch/i386/mm/Fault.c;

Kernel/:主要的核心代碼,此目錄下的文件實(shí)現(xiàn)了大多數(shù)linux系統(tǒng)的內(nèi)核函數(shù),其中最重要的文件當(dāng)屬sched.c;同樣,和體系結(jié)構(gòu)相關(guān)的代碼在arch/*/kernel中;

Drivers/ 放置系統(tǒng)所有的設(shè)備驅(qū)動程序;每種驅(qū)動程序又各占用一個(gè)子目錄:如,/block 下為塊設(shè)備驅(qū)動程序,比如ideide.c)。如果你希望查看所有可能包含文件系統(tǒng)的設(shè)備是如何初始化的,你可以看drivers/block/genhd.c中的device_setup()。它不僅初始化硬盤,也初始化網(wǎng)絡(luò),因?yàn)榘惭bnfs文件系統(tǒng)的時(shí)候需要網(wǎng)絡(luò);

Documentation/: 文檔目錄,沒有內(nèi)核代碼,只是一套有用的文檔,可惜都是English的,看看應(yīng)該有用的哦;

Fs/: 所有的文件系統(tǒng)代碼和各種類型的文件操作代碼,它的每一個(gè)子目錄支持一個(gè)文件系統(tǒng), 例如fatext2;

Ipc/: 這個(gè)目錄包含核心的進(jìn)程間通訊的代碼;

Lib/: 放置核心的庫代碼;

Net/: 核心與網(wǎng)絡(luò)相關(guān)的代碼;

Modules/: 模塊文件目錄,是個(gè)空目錄,用于存放編譯時(shí)產(chǎn)生的模塊目標(biāo)文件;

Scripts/: 描述文件,腳本,用于對核心的配置;

  一般,在每個(gè)子目錄下,都有一個(gè) Makefile 和一個(gè)Readme 文件,仔細(xì)閱讀這兩個(gè)文件,對內(nèi)核源碼的理解很有用。

  對Linux內(nèi)核源碼的分析,有幾個(gè)很好的入口點(diǎn):一個(gè)就是系統(tǒng)的引導(dǎo)和初始化,即從機(jī)器加電到系統(tǒng)核心的運(yùn)行;另外一個(gè)就是系統(tǒng)調(diào)用,系統(tǒng)調(diào)用是用戶程序或操作調(diào)用核心所提供的功能的接口。對于那些對硬件比較熟悉的愛好者,從系統(tǒng)的引導(dǎo)入手進(jìn)行分析,可能來的容易一些;而從系統(tǒng)調(diào)用下口,則可能更合適于那些在dosUinxLinux下有過C編程經(jīng)驗(yàn)的高手。這兩點(diǎn),在后面還將介紹到。


方法之二:以程序流程為線索,一線串珠



  從表面上看,Linux的源碼就象一團(tuán)扎亂無章的亂麻,其實(shí)它是一個(gè)組織得有條有理的蛛網(wǎng)。要把整個(gè)結(jié)構(gòu)分析清楚,除了找出線頭,還得理順各個(gè)部分之間的關(guān)系,有條不紊的一點(diǎn)一點(diǎn)的分析。


  所謂以程序流程為線索、一線串珠,就是指根據(jù)程序的執(zhí)行流程,把程序執(zhí)行過程所涉及到的代碼分析清楚。這種方法最典型的應(yīng)用有兩個(gè):一是系統(tǒng)的初始化過程;二是應(yīng)用程序的執(zhí)行流程:從程序的裝載,到運(yùn)行,一直到程序的退出。

  為了簡便起見,遵從循序漸進(jìn)的原理,現(xiàn)就系統(tǒng)的初始化過程來具體的介紹這種方法。系統(tǒng)的初始化流程包括:系統(tǒng)引導(dǎo),實(shí)模式下的初始化,保護(hù)模式下的初始化共三個(gè)部分。下面將一一介紹。


方法之二:I、系統(tǒng)的引導(dǎo)


  Linux系統(tǒng)的常見引導(dǎo)方式有兩種:Lilo引導(dǎo)和Loadin引導(dǎo);同時(shí)linux內(nèi)核也自帶了一個(gè)bootsect-loader。由于它只能實(shí)現(xiàn)linux的引導(dǎo),不像前兩個(gè)那樣具有很大的靈活性(lilo可實(shí)現(xiàn)多重引導(dǎo)、loadin可在dos下引導(dǎo)linux,所以在普通應(yīng)用場合實(shí)際上很少使用bootsect-loader。當(dāng)然,bootsect-loader也具有它自己的優(yōu)點(diǎn):短小沒有多余的代碼、附帶在內(nèi)核源碼中、是內(nèi)核源碼的有機(jī)組成部分,等等。

  bootsect-loader在內(nèi)和源碼中對應(yīng)的程序是 /Arch/i386/boot/bootsect.S 。下面將主要是針對此文件進(jìn)行的分析。


幾個(gè)相關(guān)文件:

<1.> /Arch/i386/boot/bootsect.S

<2.> /include/linux/config.h

<3.> /include/asm/boot.h

<4.> /include/linux/autoconf.h


引導(dǎo)過程分析:

對于Intel x86 PC , 開啟電源后, 機(jī)器就會開始執(zhí)行ROM BIOS的一系列系統(tǒng)測試動作,包括檢查RAM,keyboard,顯示器,軟硬磁盤等等。執(zhí)行完bios的系統(tǒng)測試之后,緊接著控制權(quán)會轉(zhuǎn)移給ROM中的啟動程序(ROM bootstrap routine);這個(gè)程序會將磁盤上的第0軌第0扇區(qū)(叫boot sectorMBR ,系統(tǒng)的引導(dǎo)程序就放在此處)讀入內(nèi)存中,并放到自0x07C0:0x0000開始的512個(gè)字節(jié)處;然后處理機(jī)將跳到此處開始執(zhí)行這一引導(dǎo)程序;也即裝入MBR中的引導(dǎo)程序后, CS:IP = 0x07C0:0x0000 。加電后處理機(jī)運(yùn)行在與8086相兼容的實(shí)模式下。

如果要用bootsect-loader進(jìn)行系統(tǒng)引導(dǎo),則必須把bootsect.S編譯連接后對應(yīng)的二進(jìn)制代碼置于MBR; 當(dāng)ROM BIOS bootsect.S編譯連接后對應(yīng)的二進(jìn)制代碼裝入內(nèi)存后,機(jī)器的控制權(quán)就完全轉(zhuǎn)交給bootsect; 也就是說,bootsect將是第一個(gè)被讀入內(nèi)存中并執(zhí)行的程序。

Bootsect
接管機(jī)器控制權(quán)后,將依次進(jìn)行以下一些動作:

1
.首先,bootsect將它"自己"(自位置0x07C0:0x0000開始的512個(gè)字節(jié))從被ROM BIOS載入的地址0x07C0:0x0000處搬到0x9000:0000; 這一任務(wù)由bootsect.S的前十條指令完成;第十一條指令“jmpi go,INITSEG”則把機(jī)器跳轉(zhuǎn)到bootsect“jmpi go,INITSEG”后的那條指令“go: mov di,#0x4000-12”;之后,繼續(xù)執(zhí)行bootsect的剩下的代碼;在bootsect.S中定義了幾個(gè)常量:

BOOTSEG = 0x07C0 bios
載入 MBR的約定位置的段址;

INITSEG = 0x9000 bootsect.S
的前十條指令將自己搬到此處(段址)

SETUPSEG =0x9020
裝入Setup.S的段址

SYSSEG =0x1000
系統(tǒng)區(qū)段址

對于這些常量可參見/include/asm/boot.h中的定義;這些常量在下面的分析中將會經(jīng)常用到;

2
.以0x9000:0x4000-12為棧底,建立自己的棧區(qū);其中0x9000:0x4000-120x9000:0x4000的一十二個(gè)字節(jié)預(yù)留作磁盤參數(shù)表區(qū);

3
.在0x9000:0x4000-120x9000:0x4000的一十二個(gè)預(yù)留字節(jié)中建立新的磁盤參數(shù)表,之所以叫的磁盤參數(shù)表,是相對于bios建立的磁盤參數(shù)表而言的。由于設(shè)計(jì)者考慮到有些老的bios不能準(zhǔn)確地識別磁盤每個(gè)磁道的扇區(qū)數(shù),從而導(dǎo)致bios建立的磁盤參數(shù)表妨礙磁盤的最高性能發(fā)揮,所以,設(shè)計(jì)者就在bios建立的磁盤參數(shù)表的基礎(chǔ)上通過枚舉法測試,試圖建立準(zhǔn)確的的磁盤參數(shù)表(這是在后繼步驟中完成的);并把參數(shù)表的位置由原來的0x0000:0x0078搬到0x9000:0x4000-12;且修改老的磁盤參數(shù)表區(qū)使之指向新的磁盤參數(shù)表;

4
.接下來就到了load_setup子過程;它調(diào)用0x13中斷的第2號服務(wù);把第0道第2扇區(qū)開始的連續(xù)的setup_sects (為常量4)個(gè)扇區(qū)讀到緊鄰bootsect的內(nèi)存區(qū);,即0x9000:0x0200開始的2048個(gè)字節(jié);而這四個(gè)扇區(qū)的內(nèi)容即是/arch/i386/boot/setup.S編譯連接后對應(yīng)的二進(jìn)制代碼; 也就是說,如果要用bootsect-loader進(jìn)行系統(tǒng)引導(dǎo),不僅必須把bootsect.S編譯連接后對應(yīng)的二進(jìn)制代碼置于MBR,而且還得把setup.S編譯連接后對應(yīng)的二進(jìn)制代碼置于緊跟MBR后的連續(xù)的四個(gè)扇區(qū)中;當(dāng)然,由于setup.S對應(yīng)的可執(zhí)行碼是由bootsect裝載的,所以,在我們的這個(gè)項(xiàng)目中可以通過修改bootsect來根據(jù)需要隨意地放置setup.S對應(yīng)的可執(zhí)行碼;

5
load_setup子過程的唯一出口是probe_loop子過程;該過程通過枚舉法測試磁盤每個(gè)磁道的扇區(qū)數(shù)”;

6.
接下來幾個(gè)子過程比較清晰易懂:打印我們熟悉的“Loading”;讀入系統(tǒng)到0x1000:0x0000; 關(guān)掉軟驅(qū)馬達(dá);根據(jù)的5步測出的每個(gè)磁道的扇區(qū)數(shù)確定磁盤類型;最后跳轉(zhuǎn)到0x9000:0x0200,setup.S對應(yīng)的可執(zhí)行碼的入口,將機(jī)器控制權(quán)轉(zhuǎn)交setup.S;整個(gè)bootsect代碼運(yùn)行完畢;

引導(dǎo)過程執(zhí)行完后的內(nèi)存印象圖:


  出于簡便考慮,在此分析中,我忽略了對大內(nèi)核的處理的分析,因?yàn)閷Υ髢?nèi)核的處理,只是此引導(dǎo)過程中的一個(gè)很小的部分,并不影響對整體的把握。完成了系統(tǒng)的引導(dǎo)后,系統(tǒng)將進(jìn)入到初始化處理階段。系統(tǒng)的初始化分為實(shí)模式和保護(hù)模式兩部分。


方法之二:II、實(shí)模式下的初始化



  實(shí)模式下的初始化,主要是指從內(nèi)核引導(dǎo)成功后,到進(jìn)入保護(hù)模式之前系統(tǒng)所做的一些處理。在內(nèi)核源碼中對應(yīng)的程序是 /Arch/i386/boot/setup.S;以下部分主要是針對此文件進(jìn)行的分析。這部分的分析主要是要弄懂它的處理流程和INITSEG(9000:0000)段參數(shù)表的建立,此參數(shù)表包含了很多硬件參數(shù),這些都是以后進(jìn)行保護(hù)模式下初始化,以及核心建立的基礎(chǔ)。


幾個(gè)其它相關(guān)文件:

<1.> /Arch/i386/boot/bootsect.S

<2.> /include/linux/config.h

<3.> /include/asm/boot.h

<4.> /include/ asm/segment.h

<5.> /include/linux/version.h

<6.> /include/linux/compile.h

實(shí)模式下的初始化過程分析:

INITSEG(9000:0000)段參數(shù)表:(參見Include/linux/tty.h)

 

 

INITSEG(9000:0000)段參數(shù)表:(參見Include/linux/tty.h)

 

參數(shù)名

偏移量(段址均為0x9000)

長度Byte

參考文件

PARAM_CURSOR_POS

0x0000

2

Arch/i386/boot/video.S

extended mem Size

0x0002

2

Arch/i386/boot/setup.S

PARAM_VIDEO_PAGE

0x0004

2

Arch/i386/boot/video.S

PARAM_VIDEO_MODE

0x0006

1

Arch/i386/boot/video.S

PARAM_VIDEO_COLS

0x0007

1

Arch/i386/boot/video.S

沒用

0x0008

2

Include/linux/tty.h

PARAM_VIDEO_EGA_BX

0x000a

2

Arch/i386/boot/video.S

沒用

0x000c

2

Include/linux/tty.h

PARAM_VIDEO_LINES

0x000e

1

Arch/i386/boot/video.S

PARAM_HAVE_VGA

0x000f

1

Arch/i386/boot/video.S

PARAM_FONT_POINTS

0x0010

2

Arch/i386/boot/video.S

PARAM_LFB_WIDTH

0x0012

2

Arch/i386/boot/video.S

PARAM_LFB_HEIGHT

0x0014

2

Arch/i386/boot/video.S

PARAM_LFB_DEPTH

0x0016

2

Arch/i386/boot/video.S

PARAM_LFB_BASE

0x0018

4

Arch/i386/boot/video.S

PARAM_LFB_SIZE

0x001c

4

Arch/i386/boot/video.S

暫未用

0x0020

4

Include/linux/tty.h

PARAM_LFB_LINELENGTH

0x0024

2

Arch/i386/boot/video.S

PARAM_LFB_COLORS

0x0026

6

Arch/i386/boot/video.S

暫未用

0x002c

2

Arch/i386/boot/video.S

PARAM_VESAPM_SEG

0x002e

2

Arch/i386/boot/video.S

PARAM_VESAPM_OFF

0x0030

2

Arch/i386/boot/video.S

PARAM_LFB_PAGES

0x0032

2

Arch/i386/boot/video.S

保留

0x0034--0x003f

 

Include/linux/tty.h

APM BIOS Version③

0x0040

2

Arch/i386/boot/setup.S

BIOS code segment

0x0042

2

Arch/i386/boot/setup.S

BIOS entry offset

0x0044

4

Arch/i386/boot/setup.S

BIOS 16 bit code seg

0x0048

2

Arch/i386/boot/setup.S

BIOS data segment

0x004a

2

Arch/i386/boot/setup.S

支持32位標(biāo)志④

0x004c

2

Arch/i386/boot/setup.S

BIOS code seg length

0x004e

4

Arch/i386/boot/setup.S

BIOS data seg length

0x0052

2

Arch/i386/boot/setup.S

hd0 參數(shù)

0x0080

16

Arch/i386/boot/setup.S

hd0 參數(shù)

0x0090

16

Arch/i386/boot/setup.S

PS/2 device 標(biāo)志⑤

0x01ff

1

Arch/i386/boot/setup.S


*
注: ① Include/linux/tty.h CL_MAGIC and CL_OFFSET here


Include/linux/tty.h


unsigned char rsvd_size; /* 0x2c */

unsigned char rsvd_pos; /* 0x2d */

0表示沒有APM BIOS
0x0002置位表示支持32位模式
0表示沒有,0x0aa表示有鼠標(biāo)器


方法之二:III、保護(hù)模式下的初始化

  保護(hù)模式下的初始化,是指處理機(jī)進(jìn)入保護(hù)模式后到運(yùn)行系統(tǒng)第一個(gè)內(nèi)核程序過程中系統(tǒng)所做的一些處理。保護(hù)模式下的初始化在內(nèi)核源碼中對應(yīng)的程序是 /Arch/i386/boot/compressed/head.S /Arch/i386/KERNEL/head.S ;以下部分主要是針對這兩個(gè)文件進(jìn)行的分析。

幾個(gè)相關(guān)文件:
<1.> /Arch/i386/boot/compressed/head.S
<2.> /Arch/i386/KERNEL/head.S
<3.> //Arch/i386/boot/compressed/MISC.c
<4.> /Arch/i386/boot/setup.S
<5.> /include/ asm/segment.h
<6.> /arch/i386/kernel/traps.c
<7.> /include/i386/desc.h
<8.> /include/asm-i386/processor.h

保護(hù)模式下的初始化過程分析:

一、/Arch/i386/KERNEL/head.S流程:


二、/Arch/i386/boot/compressed/head.S流程:


從流程圖中可以看到,保護(hù)模式下的初始化主要干了這樣幾件事:
解壓內(nèi)核到0x100000處、
建立頁目錄和pg0頁表并啟動分頁功能(即虛存管理功能)
保存實(shí)模式下測到的硬件信息到empty_zero_page、初始化命令緩存區(qū)、
檢測cpu類型、檢查協(xié)處理器、
重新建立gdt全局描述符表、和中斷描述附表idt;
從頁目錄和pg0頁表可以看出,0�4M物理內(nèi)存被用作系統(tǒng)區(qū),它被映射到系統(tǒng)段線性空間的0�4M3G�;3G+4M;即系統(tǒng)可以通過訪問這兩個(gè)段來訪問實(shí)際的0�;4M物理內(nèi)存,也就是系統(tǒng)所在的區(qū)域;
本來在實(shí)模式下初始化時(shí)已經(jīng)建立了全局描述符表gdt,而此處重新建立全局描述符表gdt則主要是出于兩個(gè)原因:一個(gè)就是若內(nèi)核是大內(nèi)核bzimag,則以前建立的gdt,可能已經(jīng)在解壓時(shí)被覆蓋掉了所以,在這個(gè)源碼文件中均只采用相對轉(zhuǎn)移指令jxx nfjxx nb;二是以前建立的gdt是建立在實(shí)地址方式下的,而現(xiàn)在則是在啟用保護(hù)虛擬地址方式之后建立的,也即現(xiàn)在的gdt是建立在邏輯地址(即線性地址)上的;
每次建立新的gdt后和啟用保護(hù)虛擬地址方式后都必須重新裝載系統(tǒng)棧和重新初始化各段寄存器:cs,ds,es,fs,gs
從實(shí)模式下的初始化和保護(hù)模式下的初始化過程可以看出,linux系統(tǒng)由實(shí)模式進(jìn)入到保護(hù)模式的過程大致如下:

初始后主要系統(tǒng)數(shù)據(jù)分布表

位置

系統(tǒng)數(shù)據(jù)

大小

0x101000

頁目錄swapper_pg_dir

4K

0x102000

頁表pg0

4K

0x103000

empty_bad_page

4K

0x104000

empty_bad_page_table

4K

0x105000

empty_zero_page

4K

0x105000

系統(tǒng)硬件參數(shù)

2K

0x105800

命令緩沖區(qū)

2K

0x106000

全局描述附表gdt_table

4192B


  從上面對Linux系統(tǒng)的初始化過程的分析可以看出,以程序執(zhí)行流程為線索、一線串珠,就是按照程序的執(zhí)行先后順序,弄懂程序執(zhí)行的各個(gè)階段所進(jìn)行的處理,及其各階段之間的相互聯(lián)系。而流程圖應(yīng)該是這種分析方法最合適的表達(dá)工具。

  事實(shí)上,以程序執(zhí)行流程為線索,是分析任何源代碼都首選的方法。由于操作系統(tǒng)的特殊性,光用這種方法是遠(yuǎn)遠(yuǎn)不夠的。當(dāng)然用這種方法來分析系統(tǒng)的初始化過程或用戶進(jìn)程的執(zhí)行流程應(yīng)該說是很有效的。

 

方法之三:以數(shù)據(jù)結(jié)構(gòu)為基點(diǎn),觸類旁通

  結(jié)構(gòu)化程序設(shè)計(jì)思想認(rèn)為:程序 數(shù)據(jù)結(jié)構(gòu) 算法。數(shù)據(jù)結(jié)構(gòu)體現(xiàn)了整個(gè)系統(tǒng)的構(gòu)架,所以數(shù)據(jù)結(jié)構(gòu)通常都是代碼分析的很好的著手點(diǎn),對Linux內(nèi)核分析尤其如此。比如,把進(jìn)程控制塊結(jié)構(gòu)分析清楚了,就對進(jìn)程有了基本的把握;再比如,把頁目錄結(jié)構(gòu)和頁表結(jié)構(gòu)弄懂了,兩級虛存映射和內(nèi)存管理也就掌握得差不多了。為了體現(xiàn)循序漸進(jìn)的思想,在這我就以Linux對中斷機(jī)制的處理來介紹這種方法。

  首先,必須指出的是:在此處,中斷指廣義的中斷概義,它指所有通過idt進(jìn)行的控制轉(zhuǎn)移的機(jī)制和處理;它覆蓋以下幾個(gè)常用的概義:中斷、異常、可屏蔽中斷、不可屏蔽中斷、硬中斷、軟中斷 … …


方法之三:I、硬件提供的中斷機(jī)制和約定



.中斷向量尋址:

  硬件提供可供256個(gè)服務(wù)程序中斷進(jìn)入的入口,即中斷向量;

  中斷向量在保護(hù)模式下的實(shí)現(xiàn)機(jī)制是中斷描述符表idt,idt的位置由idtr確定,idtr是個(gè)48位的寄存器,高32位是idt的基址,低16位為idt的界限(通常為2k=256*8);

  idt中包含256個(gè)中斷描述符,對應(yīng)256個(gè)中斷向量;每個(gè)中斷描述符8位,其結(jié)構(gòu)如圖一:



中斷進(jìn)入過程如圖二所示。

  當(dāng)中斷是由低特權(quán)級轉(zhuǎn)到高特權(quán)級(即當(dāng)前特權(quán)級CPL>DPL)時(shí),將進(jìn)行堆棧的轉(zhuǎn)移;內(nèi)層堆棧的選擇由當(dāng)前tss的相應(yīng)字段確定,而且內(nèi)層堆棧將依次被壓入如下數(shù)據(jù):外層SS,外層ESP,EFLAGS,外層CS,外層EIP 中斷返回過程為一逆過程;



表一、中斷向量和異常事件對應(yīng)表

中斷向量號

異常事件

Linux的處理程序

0

除法錯(cuò)誤

Divide_error

1

調(diào)試異常

Debug

2

NMI中斷

Nmi

3

單字節(jié),int 3

Int3

4

溢出

Overflow

5

邊界監(jiān)測中斷

Bounds

6

無效操作碼

Invalid_op

7

設(shè)備不可用

Device_not_available

8

雙重故障

Double_fault

9

協(xié)處理器段溢出

Coprocessor_segment_overrun

10

無效TSS

Incalid_tss

11

缺段中斷

Segment_not_present

12

堆棧異常

Stack_segment

13

一般保護(hù)異常

General_protection

14

頁異常

Page_fault

15

 

Spurious_interrupt_bug

16

協(xié)處理器出錯(cuò)

Coprocessor_error

17

對齊檢查中斷

Alignment_check

.可編程中斷控制器8259A

  為更好的處理外部設(shè)備,x86微機(jī)提供了兩片可編程中斷控制器,用來輔助cpu接受外部的中斷信號;對于中斷,cpu只提供兩個(gè)外接引線:NMIINTR;

  NMI只能通過端口操作來屏蔽,它通常用于:電源掉電和物理存儲器奇偶驗(yàn)錯(cuò);

  INTR可通過直接設(shè)置中斷屏蔽位來屏蔽,它可用來接受外部中斷信號,但只有一個(gè)引線,不夠用;所以它通過外接兩片級鏈了的8259A,以接受更多的外部中斷信號。8259A主要完成這樣一些任務(wù):


中斷優(yōu)先級排隊(duì)管理,


接受外部中斷請求


cpu提供中斷類型號


  外部設(shè)備產(chǎn)生的中斷信號在IRQ(中斷請求)管腳上首先由中斷控制器處理。中斷控制器可以響應(yīng)多個(gè)中斷輸入,它的輸出連接到 CPU INT 管腳,信號可通過INT 管腳,通知處理器產(chǎn)生了中斷。如果 CPU 這時(shí)可以處理中斷,CPU 會通過 INTA(中斷確認(rèn))管腳上的信號通知中斷控制器已接受中斷,這時(shí),中斷控制器可將一個(gè) 8 位數(shù)據(jù)放置在數(shù)據(jù)總線上,這一 8 位數(shù)據(jù)也稱為中斷向量號,CPU 依據(jù)中斷向量號和中斷描述符表(IDT)中的信息自動調(diào)用相應(yīng)的中斷服務(wù)程序。圖三中,兩個(gè)中斷控制器級聯(lián)了起來,從屬中斷控制器的輸出連接到了主中斷控制器的第 3 個(gè)中斷信號輸入,這樣,該系統(tǒng)可處理的外部中斷數(shù)量最多可達(dá) 15 個(gè),圖的右邊是 i386 PC 中各中斷輸入管腳的一般分配??赏ㄟ^對8259A的初始化,使這15個(gè)外接引腳對應(yīng)256個(gè)中斷向量的任何15個(gè)連續(xù)的向量;由于intel公司保留0-31號中斷向量用來處理異常事件(而默認(rèn)情況下,IBM bios把硬中斷設(shè)在0x08-0x0f),所以,硬中斷必須設(shè)在31以后,linux則在實(shí)模式下初始化時(shí)把其設(shè)在0x20-0x2F,對此下面還將具體說明。

圖三、i386 PC 可編程中斷控制器8259A級鏈?zhǔn)疽鈭D



方法之三:II、Linux的中斷處理


  硬件中斷機(jī)制提供了256個(gè)入口,即idt中包含的256個(gè)中斷描述符(對應(yīng)256個(gè)中斷向量)。

  而0-31號中斷向量被intel公司保留用來處理異常事件,不能另作它用。對這0-31號中斷向量,操作系統(tǒng)只需提供異常的處理程序,當(dāng)產(chǎn)生一個(gè)異常時(shí),處理機(jī)就會自動把控制轉(zhuǎn)移到相應(yīng)的處理程序的入口,運(yùn)行相應(yīng)的處理程序;而事實(shí)上,對于這32個(gè)處理異常的中斷向量,此版本(2.2.5)的Linux只提供了0-17號中斷向量的處理程序,其對應(yīng)處理程序參見表一、中斷向量和異常事件對應(yīng)表;也就是說,17-31號中斷向量是空著未用的。

  既然0-31號中斷向量已被保留,那么,就是剩下32-255224個(gè)中斷向量可用。這224個(gè)中斷向量又是怎么分配的呢?在此版本(2.2.5)的Linux中,除了0x80 (SYSCALL_VECTOR)用作系統(tǒng)調(diào)用總?cè)肟谥?,其他都用在外部硬件中斷源上,其中包括可編程中斷控制?/span>8259A15個(gè)irq;事實(shí)上,當(dāng)沒有定義CONFIG_X86_IO_APIC時(shí),其他223(0x80)個(gè)中斷向量,只利用了從32號開始的15個(gè),其它208個(gè)空著未用。

  這些中斷服務(wù)程序入口的設(shè)置將在下面有詳細(xì)說明。

一.相關(guān)數(shù)據(jù)結(jié)構(gòu)

中斷描述符表idt 也就是中斷向量表,相當(dāng)如一個(gè)數(shù)組,保存著各中斷服務(wù)例程的入口。(詳細(xì)描述參見圖一、中斷描述符格式)
與硬中斷相關(guān)數(shù)據(jù)結(jié)構(gòu):
與硬中斷相關(guān)數(shù)據(jù)結(jié)構(gòu)主要有三個(gè):

一:定義在/arch/i386/kernel/irq.h中的

struct hw_interrupt_type {

const char * typename
;

void (*startup)(unsigned int irq)
;

void (*shutdown)(unsigned int irq)
;

void (*handle)(unsigned int irq, struct pt_regs * regs)
;

void (*enable)(unsigned int irq)


void (*disable)(unsigned int irq)
;

}



二:定義在/arch/i386/kernel/irq.h中的

typedef struct {

unsigned int status
; /* IRQ status - IRQ_INPROGRESS, IRQ_DISABLED */

struct hw_interrupt_type *handler
/* handle/enable/disable functions */

struct irqaction *action
; /* IRQ action list */

unsigned int depth
/* Disable depth for nested irq disables */

} irq_desc_t
;


三:定義在include/linux/ interrupt.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
;

}
;


三者關(guān)系如下:



圖四、與硬中斷相關(guān)的幾個(gè)數(shù)據(jù)結(jié)構(gòu)各關(guān)系

各結(jié)構(gòu)成員詳述如下:


struct irqaction
結(jié)構(gòu),它包含了內(nèi)核接收到特定IRQ之后應(yīng)該采取的操作,其成員如下:


handler
:是一指向某個(gè)函數(shù)的指針。該函數(shù)就是所在結(jié)構(gòu)對相應(yīng)中斷的處理函數(shù)。


flags
:取值只有SA_INTERRUPT(中斷可嵌套),SA_SAMPLE_RANDOM(這個(gè)中斷是源于物理隨機(jī)性的),和SA_SHIRQ(這個(gè)IRQ和其它struct irqaction共享)。


mask
:在x86或者體系結(jié)構(gòu)無關(guān)的代碼中不會使用(除非將其設(shè)置為0);只有在SPARC64的移植版本中要跟蹤有關(guān)軟盤的信息時(shí)才會使用它。


name
:產(chǎn)生中斷的硬件設(shè)備的名字。因?yàn)椴恢挂粋€(gè)硬件可以共享一個(gè)IRQ


dev_id
:標(biāo)識硬件類型的一個(gè)唯一的ID。Linux支持的所有硬件設(shè)備的每一種類型,都有一個(gè)由制造廠商定義的在此成員中記錄的設(shè)備ID。


next
:如果IRQ是共享的,那么這就是指向隊(duì)列中下一個(gè)struct irqaction結(jié)構(gòu)的指針。通常情況下,IRQ不是共享的,因此這個(gè)成員就為空。



struct hw_interrupt_type
結(jié)構(gòu),它是一個(gè)抽象的中斷控制器。這包含一系列的指向函數(shù)的指針,這些函數(shù)處理控制器特有的操作:


typename
:控制器的名字。


startup
:允許從給定的控制器的IRQ所產(chǎn)生的事件。


shutdown
:禁止從給定的控制器的IRQ所產(chǎn)生的事件。


handle
:根據(jù)提供給該函數(shù)的IRQ,處理唯一的中斷。


enable
disable:這兩個(gè)函數(shù)基本上和startupshutdown相同;



另外一個(gè)數(shù)據(jù)結(jié)構(gòu)是irq_desc_t,它具有如下成員:


status
:一個(gè)整數(shù)。代表IRQ的狀態(tài):IRQ是否被禁止了,有關(guān)IRQ的設(shè)備當(dāng)前是否正被自動檢測,等等。


handler
:指向hw_interrupt_type的指針。


action
:指向irqaction結(jié)構(gòu)組成的隊(duì)列的頭。正常情況下每個(gè)IRQ只有一個(gè)操作,因此鏈接列表的正常長度是1(或者0)。但是,如果IRQ被兩個(gè)或者多個(gè)設(shè)備所共享,那么這個(gè)隊(duì)列中就有多個(gè)操作。


depth
irq_desc_t的當(dāng)前用戶的個(gè)數(shù)。主要是用來保證在中斷處理過程中IRQ不會被禁止。


irq_desc
irq_desc_t 類型的數(shù)組。對于每一個(gè)IRQ都有一個(gè)數(shù)組入口,即數(shù)組把每一個(gè)IRQ映射到和它相關(guān)的處理程序和irq_desc_t中的其它信息。


Bottom_half相關(guān)的數(shù)據(jù)結(jié)構(gòu):


圖五、底半處理數(shù)據(jù)結(jié)構(gòu)示意圖



bh_mask_count
:計(jì)數(shù)器。對每個(gè)enable/disable請求嵌套對進(jìn)行計(jì)數(shù)。這些請求通過調(diào)用enable_bhdisable_bh實(shí)現(xiàn)。每個(gè)禁止請求都增加計(jì)數(shù)器;每個(gè)使能請求都減小計(jì)數(shù)器。當(dāng)計(jì)數(shù)器達(dá)到0時(shí),所有未完成的禁止語句都已經(jīng)被使能語句所匹配了,因此下半部分最終被重新使能。(定義在kernel/softirq.c中)


bh_mask
bh_active:它們共同決定下半部分是否運(yùn)行。它們兩個(gè)都有32位,而每一個(gè)下半部分都占用一位。當(dāng)一個(gè)上半部分(或者一些其它代碼)決定其下半部分需要運(yùn)行時(shí),就通過設(shè)置bh_active中的一位來標(biāo)記下半部分。不管是否做這樣的標(biāo)記,下半部分都可以通過清空bh_mask中的相關(guān)位來使之失效。因此,對bh_maskbh_active進(jìn)行位AND運(yùn)算就能夠表明應(yīng)該運(yùn)行哪一個(gè)下半部分。特別是如果位與運(yùn)算的結(jié)果是0,就沒有下半部分需要運(yùn)行。


bh_base
:是一組簡單的指向下半部分處理函數(shù)的指針。


  bh_base代表的指針數(shù)組中可包含 32 個(gè)不同的底半處理程序。bh_mask bh_active 的數(shù)據(jù)位分別代表對應(yīng)的底半處理過程是否安裝和激活。如果 bh_mask 的第 N 位為 1,則說明 bh_base 數(shù)組的第 N 個(gè)元素包含某個(gè)底半處理過程的地址;如果 bh_active 的第 N 位為 1,則說明必須由調(diào)度程序在適當(dāng)?shù)臅r(shí)候調(diào)用第 N 個(gè)底半處理過程?!?/span>

. 向量的設(shè)置和相關(guān)數(shù)據(jù)的初始化:

在實(shí)模式下的初始化過程中,通過對中斷控制器8259A-1,9259A-2重新編程,把硬中斷設(shè)到0x20-0x2F。即把IRQ0�;IRQ15分別與0x20-0x2F號中斷向量對應(yīng)起來;當(dāng)對應(yīng)的IRQ發(fā)生了時(shí),處理機(jī)就會通過相應(yīng)的中斷向量,把控制轉(zhuǎn)到對應(yīng)的中斷服務(wù)例程。(源碼在Arch/i386/boot/setup.S文件中;相關(guān)內(nèi)容可參見 實(shí)模式下的初始化 部分)


在保護(hù)模式下的初始化過程中,設(shè)置并初始化idt,256個(gè)入口,服務(wù)程序均為ignore_int, 該服務(wù)程序僅打印“Unknown interruptn”。(源碼參見Arch/i386/KERNEL/head.S文件;相關(guān)內(nèi)容可參見 保護(hù)模式下的初始化 部分)


在系統(tǒng)初始化完成后運(yùn)行的第一個(gè)內(nèi)核程序asmlinkage void __init start_kernel(void) (源碼在文件init/main.c) 中,通過調(diào)用void __init trap_init(void)函數(shù),把各自陷和中斷服務(wù)程序的入口地址設(shè)置到 idt 表中,即將表一中對應(yīng)的處理程序入口設(shè)置到相應(yīng)的中斷向量表項(xiàng)中;在此版本(2.2.5)的Linux只設(shè)置0-17號中斷向量。(trap_init(void)函數(shù)定義在arch/i386/kernel/traps.c 中; 相關(guān)內(nèi)容可參見 詳解系統(tǒng)調(diào)用 部分)


在同一個(gè)函數(shù)void __init trap_init(void)中,通過調(diào)用函數(shù)set_system_gate(SYSCALL_VECTOR,&system_call) 把系統(tǒng)調(diào)用總控程序的入口掛在中斷0x80上。其中SYSCALL_VECTOR是定義在 linux/arch/i386/kernel/irq.h中的一個(gè)常量0x80; system_call 即為中斷總控程序的入口地址;中斷總控程序用匯編語言定義在arch/i386/kernel/entry.S中。(相關(guān)內(nèi)容可參見 詳解系統(tǒng)調(diào)用 部分)


在系統(tǒng)初始化完成后運(yùn)行的第一個(gè)內(nèi)核程序asmlinkage void __init start_kernel(void) (源碼在文件init/main.c) 中,通過調(diào)用void init_IRQ(void)函數(shù),把地址標(biāo)號interrupt[i]i1-223)設(shè)置到 idt 表中的的32-255號中斷向量(0x80除外),外部硬件IRQ的觸發(fā),將通過這些地址標(biāo)號最終進(jìn)入到各自相應(yīng)的處理程序。(init_IRQ(void)函數(shù)定義在arch/i386/kernel/IRQ.c 中;)


interrupt[i](i
1-223),是在arch/i386/kernel/IRQ.c文件中,通過一系列嵌套的類似如BUILD_16_IRQS(0x0)的宏,定義的一系列地址標(biāo)號;(這些定義interrupt[i]的宏,全部定義在文件arch/i386/kernel/IRQ.carch/i386/kernel/IRQ.H中。這些嵌套的宏的使用,原理很簡單,但很煩,限于篇幅,在此省略)


各以interrupt[i]為入口的代碼,在進(jìn)行一些簡單的處理后,最后都會調(diào)用函數(shù)asmlinkage void do_IRQ(struct pt_regs regs),do_IRQ函數(shù)調(diào)用static void do_8259A_IRQ(unsigned int irq, struct pt_regs * regs) do_8259A_IRQ在進(jìn)行必要的處理后,將調(diào)用已與此IRQ建立聯(lián)系irqaction中的處理函數(shù),以進(jìn)行相應(yīng)的中斷處理。最后處理機(jī)將跳轉(zhuǎn)到ret_from_intr進(jìn)行必要處理后,整個(gè)中斷處理結(jié)束返回。(相關(guān)源碼都在文件arch/i386/kernel/IRQ.carch/i386/kernel/IRQ.H中。Irqaction結(jié)構(gòu)參見上面的數(shù)據(jù)結(jié)構(gòu)說明)
. Bottom_half處理機(jī)制

  在此版本(2.2.5)Linux中,中斷處理程序從概念上被分為上半部分(top half)和下半部分(bottom half);在中斷發(fā)生時(shí)上半部分的處理過程立即執(zhí)行,但是下半部分(如果有的話)卻推遲執(zhí)行。內(nèi)核把上半部分和下半部分作為獨(dú)立的函數(shù)來處理,上半部分決定其相關(guān)的下半部分是否需要執(zhí)行。必須立即執(zhí)行的部分必須位于上半部分,而可以推遲的部分可能屬于下半部分。

  那么為什么這樣劃分成兩個(gè)部分呢?


一個(gè)原因是要把中斷的總延遲時(shí)間最小化。Linux內(nèi)核定義了兩種類型的中斷,快速的和慢速的,這兩者之間的一個(gè)區(qū)別是慢速中斷自身還可以被中斷,而快速中斷則不能。因此,當(dāng)處理快速中斷時(shí),如果有其它中斷到達(dá);不管是快速中斷還是慢速中斷,它們都必須等待。為了盡可能快地處理這些其它的中斷,內(nèi)核就需要盡可能地將處理延遲到下半部分執(zhí)行。


另外一個(gè)原因是,當(dāng)內(nèi)核執(zhí)行上半部分時(shí),正在服務(wù)的這個(gè)特殊IRQ將會被可編程中斷控制器禁止,于是,連接在同一個(gè)IRQ上的其它設(shè)備就只有等到該該中斷處理被處理完畢后果才能發(fā)出IRQ請求。而采用Bottom_half機(jī)制后,不需要立即處理的部分就可以放在下半部分處理,從而,加快了處理機(jī)對外部設(shè)備的中斷請求的響應(yīng)速度。


還有一個(gè)原因就是,處理程序的下半部分還可以包含一些并非每次中斷都必須處理的操作;對這些操作,內(nèi)核可以在一系列設(shè)備中斷之后集中處理一次就可以了。即在這種情況下,每次都執(zhí)行并非必要的操作完全是一種浪費(fèi),而采用Bottom_half機(jī)制后,可以稍稍延遲并在后來只執(zhí)行一次就行了。

  由此可見,沒有必要每次中斷都調(diào)用下半部分;只有bh_mask bh_active的對應(yīng)位的與為1時(shí),才必須執(zhí)行下半部分(do_botoom_half)。所以,如果在上半部分中(也可能在其他地方)決定必須執(zhí)行對應(yīng)的半部分,那么可以通過設(shè)置bh_active的對應(yīng)位,來指明下半部分必須執(zhí)行。當(dāng)然,如果bh_active的對應(yīng)位被置位,也不一定會馬上執(zhí)行下半部分,因?yàn)檫€必須具備另外兩個(gè)條件:首先是bh_mask的相應(yīng)位也必須被置位,另外,就是處理的時(shí)機(jī),如果下半部分已經(jīng)標(biāo)記過需要執(zhí)行了,現(xiàn)在又再次標(biāo)記,那么內(nèi)核就簡單地保持這個(gè)標(biāo)記;當(dāng)情況允許的時(shí)候,內(nèi)核就對它進(jìn)行處理。如果在內(nèi)核有機(jī)會運(yùn)行其下半部分之前給定的設(shè)備就已經(jīng)發(fā)生了100次中斷,那么內(nèi)核的上半部分就運(yùn)行100次,下半部分運(yùn)行1次。

  bh_base數(shù)組的索引是靜態(tài)定義的,定時(shí)器底半處理過程的地址保存在第 0 個(gè)元素中,控制臺底半處理過程的地址保存在第 1 個(gè)元素中,等等。當(dāng) bh_mask bh_active 表明第 N 個(gè)底半處理過程已被安裝且處于活動狀態(tài),則調(diào)度程序會調(diào)用第 N 個(gè)底半處理過程,該底半處理過程最終會處理與之相關(guān)的任務(wù)隊(duì)列中的各個(gè)任務(wù)。因?yàn)檎{(diào)度程序從第 0 個(gè)元素開始依次檢查每個(gè)底半處理過程,因此,第 0 個(gè)底半處理過程具有最高的優(yōu)先級,第 31 個(gè)底半處理過程的優(yōu)先級最低。

  內(nèi)核中的某些底半處理過程是和特定設(shè)備相關(guān)的,而其他一些則更一般一些。表二列出了內(nèi)核中通用的底半處理過程。

表二、Linux 中通用的底半處理過程

TIMER_BH(定時(shí)器)

在每次系統(tǒng)的周期性定時(shí)器中斷中,該底半處理過程被標(biāo)記為活動狀態(tài),并用來驅(qū)動內(nèi)核的定時(shí)器隊(duì)列機(jī)制。

CONSOLE_BH(控制臺)

該處理過程用來處理控制臺消息。

TQUEUE_BH(TTY 消息隊(duì)列)

該處理過程用來處理 tty 消息。

NET_BH(網(wǎng)絡(luò))

用于一般網(wǎng)絡(luò)處理,作為網(wǎng)絡(luò)層的一部分

IMMEDIATE_BH(立即)

這是一個(gè)一般性處理過程,許多設(shè)備驅(qū)動程序利用該過程對自己要在隨后處理的任務(wù)進(jìn)行排隊(duì)。


  當(dāng)某個(gè)設(shè)備驅(qū)動程序,或內(nèi)核的其他部分需要將任務(wù)排隊(duì)進(jìn)行處理時(shí),它將任務(wù)添加到適當(dāng)?shù)南到y(tǒng)隊(duì)列中(例如,添加到系統(tǒng)的定時(shí)器隊(duì)列中),然后通知內(nèi)核,表明需要進(jìn)行底半處理。為了通知內(nèi)核,只需將 bh_active 的相應(yīng)數(shù)據(jù)位置為 1。例如,如果驅(qū)動程序在 immediate 隊(duì)列中將某任務(wù)排隊(duì),并希望運(yùn)行 IMMEDIATE 底半處理過程來處理排隊(duì)任務(wù),則只需將 bh_active 的第 8 位置為 1。在每個(gè)系統(tǒng)調(diào)用結(jié)束并返回調(diào)用進(jìn)程之前,調(diào)度程序要檢驗(yàn) bh_active 中的每個(gè)位,如果有任何一位為 1,則相應(yīng)的底半處理過程被調(diào)用。每個(gè)底半處理過程被調(diào)用時(shí),bh_active 中的相應(yīng)為被清除。bh_active 中的置位只是暫時(shí)的,在兩次調(diào)用調(diào)度程序之間 bh_active 的值才有意義,如果 bh_active 中沒有置位,則不需要調(diào)用任何底半處理過程。

四.中斷處理全過程

  由前面的分析可知,對于0-31號中斷向量,被保留用來處理異常事件;0x80中斷向量用來作為系統(tǒng)調(diào)用的總?cè)肟邳c(diǎn);而其他中斷向量,則用來處理外部設(shè)備中斷;這三者的處理過程都是不一樣的。

異常的處理全過程
對這0-31號中斷向量,保留用來處理異常事件;操作系統(tǒng)提供相應(yīng)的異常的處理程序,并在初始化時(shí)把處理程序的入口等級在對應(yīng)的中斷向量表項(xiàng)中。當(dāng)產(chǎn)生一個(gè)異常時(shí),處理機(jī)就會自動把控制轉(zhuǎn)移到相應(yīng)的處理程序的入口,運(yùn)行相應(yīng)的處理程序,進(jìn)行相應(yīng)的處理后,返回原中斷處。當(dāng)然,在前面已經(jīng)提到,此版本(2.2.5)的Linux只提供了0-17號中斷向量的處理程序。

中斷的處理全過程
  對于0-31號和0x80之外的中斷向量,主要用來處理外部設(shè)備中斷;在系統(tǒng)完成初始化后,其中斷處理過程如下:

  當(dāng)外部設(shè)備需要處理機(jī)進(jìn)行中斷服務(wù)時(shí),它就會通過中斷控制器要求處理機(jī)進(jìn)行中斷服務(wù)。如果 CPU 這時(shí)可以處理中斷,CPU將根據(jù)中斷控制器提供的中斷向量號和中斷描述符表(IDT)中的登記的地址信息,自動跳轉(zhuǎn)到相應(yīng)的interrupt[i]地址;在進(jìn)行一些簡單的但必要的處理后,最后都會調(diào)用函數(shù)do_IRQ , do_IRQ函數(shù)調(diào)用 do_8259A_IRQ do_8259A_IRQ在進(jìn)行必要的處理后,將調(diào)用已與此IRQ建立聯(lián)系irqaction中的處理函數(shù),以進(jìn)行相應(yīng)的中斷處理。最后處理機(jī)將跳轉(zhuǎn)到ret_from_intr進(jìn)行必要處理后,整個(gè)中斷處理結(jié)束返回。

  從數(shù)據(jù)結(jié)構(gòu)入手,應(yīng)該說是分析操作系統(tǒng)源碼最常用的和最主要的方法。因?yàn)椴僮飨到y(tǒng)的幾大功能部件,如進(jìn)程管理,設(shè)備管理,內(nèi)存管理等等,都可以通過對其相應(yīng)的數(shù)據(jù)結(jié)構(gòu)的分析來弄懂其實(shí)現(xiàn)機(jī)制。很好的掌握這種方法,對分析Linux內(nèi)核大有裨益。


方法之四:以功能為中心,各個(gè)擊破

  從功能上看,整個(gè)Linux系統(tǒng)可看作有一下幾個(gè)部分組成:
進(jìn)程管理機(jī)制部分;
內(nèi)存管理機(jī)制部分;
文件系統(tǒng)部分;
硬件驅(qū)動部分;
系統(tǒng)調(diào)用部分等;
  以功能為中心、各個(gè)擊破,就是指從這五個(gè)功能入手,通過源碼分析,找出Linux是怎樣實(shí)現(xiàn)這些功能的。

  在這五個(gè)功能部件中,系統(tǒng)調(diào)用是用戶程序或操作調(diào)用核心所提供的功能的接口;也是分析Linux內(nèi)核源碼幾個(gè)很好的入口點(diǎn)之一。對于那些在dosUinxLinux下有過C編程經(jīng)驗(yàn)的高手尤其如此。又由于系統(tǒng)調(diào)用相對其它功能而言,較為簡單,所以,我就以它為例,希望通過對系統(tǒng)調(diào)用的分析,能使讀者體會到這一方法。

方法之四:I、系統(tǒng)調(diào)用的初始化設(shè)置和相關(guān)數(shù)據(jù)結(jié)構(gòu)

  與系統(tǒng)調(diào)用相關(guān)的內(nèi)容主要有:系統(tǒng)調(diào)用總控程序,系統(tǒng)調(diào)用向量表sys_call_table,以及各系統(tǒng)調(diào)用服務(wù)程序。下面將對此一一介紹:

保護(hù)模式下的初始化過程中,設(shè)置并初始化idt,256個(gè)入口,服務(wù)程序均為ignore_int, 該服務(wù)程序僅打印“Unknown interruptn”。(源碼參見/Arch/i386/KERNEL/head.S文件;相關(guān)內(nèi)容可參見 保護(hù)模式下的初始化 部分)
在系統(tǒng)初始化完成后運(yùn)行的第一個(gè)內(nèi)核程序start_kernel中,通過調(diào)用 trap_init函數(shù),把各自陷和中斷服務(wù)程序的入口地址設(shè)置到 idt 表中;同時(shí),此函數(shù)還通過調(diào)用函數(shù)set_system_gate 把系統(tǒng)調(diào)用總控程序的入口地址掛在中斷0x80上。其中:
start_kernel
的原型為void __init start_kernel(void) ,其源碼在文件 init/main.c中;
trap_init
函數(shù)的原型為void __init trap_init(void),定義在arch/i386/kernel/traps.c
函數(shù)set_system_gate同樣定義在arch/i386/kernel/traps.c 中,調(diào)用原型為set_system_gate(SYSCALL_VECTOR,&system_call);
其中,SYSCALL_VECTOR是定義在 linux/arch/i386/kernel/irq.h中的一個(gè)常量0x80;
system_call 即為系統(tǒng)調(diào)用總控程序的入口地址;中斷總控程序用匯編語言定義在arch/i386/kernel/entry.S中。
(其它相關(guān)內(nèi)容可參見 中斷和中斷處理 部分)

系統(tǒng)調(diào)用向量表sys_call_table, 是一個(gè)含有NR_syscalls=256個(gè)單元的數(shù)組。它的每個(gè)單元存放著一個(gè)系統(tǒng)調(diào)用服務(wù)程序的入口地址。該數(shù)組定義在/arch/i386/kernel/entry.S中;而NR_syscalls則是一個(gè)等于256的宏,定義在include/linux/sys.h中。
各系統(tǒng)調(diào)用服務(wù)程序則分別定義在各個(gè)模塊的相應(yīng)文件中;例如asmlinkage int sys_time(int * tloc)就定義在kerneltime.c中;另外,在kernelsys.c中也有不少服務(wù)程序;


方法之四:II、系統(tǒng)調(diào)用過程

  我們知道,系統(tǒng)調(diào)用是用戶程序或操作調(diào)用核心所提供的功能的接口;所以系統(tǒng)掉用的過程就是從用戶程序到系統(tǒng)內(nèi)核,然后又回到用戶程序的過程;在Linux中,此過程大體過程可描述如下:

系統(tǒng)調(diào)用過程示意圖:


  整個(gè)系統(tǒng)調(diào)用進(jìn)入過程客表示如下:


用戶程序 系統(tǒng)調(diào)用總控程序(system_call) 各個(gè)服務(wù)程序


  可見,系統(tǒng)調(diào)用的進(jìn)入課分為“用戶程序 系統(tǒng)調(diào)用總控程序”和“系統(tǒng)調(diào)用總控程序 各個(gè)服務(wù)程序兩部分;下邊將分別對這兩個(gè)部分進(jìn)行詳細(xì)說明:



“用戶程序 系統(tǒng)調(diào)用總控程序”的實(shí)現(xiàn):在前面已經(jīng)說過,Linux的系統(tǒng)調(diào)用使用第0x80號中斷向量項(xiàng)作為總的入口,也即,系統(tǒng)調(diào)用總控程序的入口地址system_call就掛在中斷0x80上。也就是說,只要用戶程序執(zhí)行0x80中斷 ( int 0x80 ),就可實(shí)現(xiàn)用戶程序 系統(tǒng)調(diào)用總控程序的進(jìn)入;事實(shí)上,在Linux中,也是這么做的。只是0x80中斷的執(zhí)行語句int 0x80 被封裝在標(biāo)準(zhǔn)C庫中,用戶程序只需用標(biāo)準(zhǔn)系統(tǒng)調(diào)用函數(shù)就可以了,而不需要在用戶程序中直接寫0x80中斷的執(zhí)行語句int 0x80。至于中斷的進(jìn)入的詳細(xì)過程可參見前面的中斷和中斷處理部分。



“系統(tǒng)調(diào)用總控程序 各個(gè)服務(wù)程序的實(shí)現(xiàn):在系統(tǒng)調(diào)用總控程序中通過語句“call * SYMBOL_NAME(sys_call_table)(,%eax,4)”來調(diào)用各個(gè)服務(wù)程序(SYMBOL_NAME是定義在/include/linux/linkage.h中的宏:#define SYMBOL_NAME_LABEL(X) X),可以忽略)。當(dāng)系統(tǒng)調(diào)用總控程序執(zhí)行到此語句時(shí),eax中的內(nèi)容即是相應(yīng)系統(tǒng)調(diào)用的編號,此編號即為相應(yīng)服務(wù)程序在系統(tǒng)調(diào)用向量表sys_call_table中的編號(關(guān)于系統(tǒng)調(diào)用的編號說明在/linux/include/asm/unistd.h中)。又因?yàn)橄到y(tǒng)調(diào)用向量表sys_call_table每項(xiàng)占4個(gè)字節(jié),所以由%eax 乘上4形成偏移地址,而sys_call_table則為基址;基址加上偏移所指向的內(nèi)容就是相應(yīng)系統(tǒng)調(diào)用服務(wù)程序的入口地址。所以此call語句就相當(dāng)于直接調(diào)用對應(yīng)的系統(tǒng)調(diào)用服務(wù)程序。


參數(shù)傳遞的實(shí)現(xiàn):在Linux中所有系統(tǒng)調(diào)用服務(wù)例程都使用了asmlinkage標(biāo)志。此標(biāo)志是一個(gè)定義在/include/linux/linkage.h 中的一個(gè)宏:

#if defined __i386__ && (__GNUC__ > 2 || __GNUC_MINOR__ > 7)

#define asmlinkage CPP_ASMLINKAGE__attribute__((regparm(0)))

#else

#define asmlinkage CPP_ASMLINKAGE

#endif

其中涉及到了gcc的一些約定,總之,這個(gè)標(biāo)志它可以告訴編譯器該函數(shù)不需要從寄存器中獲得任何參數(shù),而是從堆棧中取得參數(shù);即參數(shù)在堆棧中傳遞,而不是直接通過寄存器;

堆棧參數(shù)如下:

EBX = 0x00

ECX = 0x04

EDX = 0x08

ESI = 0x0C

EDI = 0x10

EBP = 0x14

EAX = 0x18

DS = 0x1C

ES = 0x20

ORIG_EAX = 0x24

EIP = 0x28

CS = 0x2C

EFLAGS = 0x30

  在進(jìn)入系統(tǒng)調(diào)用總控程序前,用戶按照以上的對應(yīng)順序?qū)?shù)放到對應(yīng)寄存器中,在系統(tǒng)調(diào)用總控程序一開始就將這些寄存器壓入堆棧;在退出總控程序前又按如上順序堆棧;用戶程序則可以直接從寄存器中復(fù)得被服務(wù)程序加工過了的參數(shù)。而對于系統(tǒng)調(diào)用服務(wù)程序而言,參數(shù)就可以直接從總控程序壓入的堆棧中復(fù)得;對參數(shù)的修改一可以直接在堆棧中進(jìn)行;其實(shí),這就是asmlinkage標(biāo)志的作用。所以在進(jìn)入和退出系統(tǒng)調(diào)用總控程序時(shí),保護(hù)現(xiàn)場恢復(fù)現(xiàn)場的內(nèi)容并不一定會相同。

特殊的服務(wù)程序:在此版本(2.2.5)linux內(nèi)核中,有好幾個(gè)系統(tǒng)調(diào)用的服務(wù)程序都是定義在/usr/src/linux/kernel/sys.c 中的同一個(gè)函數(shù):
asmlinkage int sys_ni_syscall(void)

{

return -ENOSYS;

}

此函數(shù)除了返回錯(cuò)誤號之外,什么都沒干。那他有什么作用呢?歸結(jié)起來有如下三種可能:

1
.處理邊界錯(cuò)誤,0號系統(tǒng)調(diào)用就是用的此特殊的服務(wù)程序;

2
.用來替換舊的已淘汰了的系統(tǒng)調(diào)用,如: Nr 17, Nr 31, Nr 32, Nr 35, Nr 44, Nr 53, Nr 56, Nr58, Nr 98;

3.
用于將要擴(kuò)展的系統(tǒng)調(diào)用,如: Nr 137, Nr 188, Nr 189;

方法之四:III、系統(tǒng)調(diào)用總控程序(system_call)

  系統(tǒng)調(diào)用總控程序(system_call)可參見arch/i386/kernel/entry.S其執(zhí)行流程如下圖:


IV
、實(shí)例:增加一個(gè)系統(tǒng)調(diào)用

由以上的分析可知,增加系統(tǒng)調(diào)用由于下兩種方法:

i
.編一個(gè)新的服務(wù)例程,將它的入口地址加入到sys_call_table的某一項(xiàng),只要該項(xiàng)的原服務(wù)例程是sys_ni_syscall,并且是sys_ni_syscall的作用屬于第三種的項(xiàng),也即Nr 137, Nr 188, Nr 189。


ii
.直接增加:


編一個(gè)新的服務(wù)例程;


sys_call_table中添加一個(gè)新項(xiàng), 并把的新增加的服務(wù)例程的入口地址加到sys_call_table表中的新項(xiàng)中;


把增加的 sys_call_table 表項(xiàng)所對應(yīng)的向量, include/asm-386/unistd.h 中進(jìn)行必要申明,以供用戶進(jìn)程和其他系統(tǒng)進(jìn)程查詢或調(diào)用。


由于在標(biāo)準(zhǔn)的c語言庫中沒有新系統(tǒng)調(diào)用的承接段,所以,在測試程序中,除了要#include ,還要申明如下 _syscall1(int,additionSysCall,int, num)。


下面將對第ii種情況列舉一個(gè)我曾經(jīng)實(shí)現(xiàn)過了的一個(gè)增加系統(tǒng)調(diào)用的實(shí)例:

1.
)在kernel/sys.c中增加新的系統(tǒng)服務(wù)例程如下:

asmlinkage int sys_addtotal(int numdata)

{

int i=0,enddata=0;

while(i<=numdata)

enddata+=i++;

return enddata;

}

  該函數(shù)有一個(gè) int 型入口參數(shù) numdata , 并返回從 0 numdata 的累加值; 當(dāng)然也可以把系統(tǒng)服務(wù)例程放在一個(gè)自己定義的文件或其他文件中,只是要在相應(yīng)文件中作必要的說明;

2.
)把 asmlinkage int sys_addtotal( int) 的入口地址加到sys_call_table表中:

arch/i386/kernel/entry.S
中的最后幾行源代碼修改前為:

... ...

.long SYMBOL_NAME(sys_sendfile)

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */

.long SYMBOL_NAME(sys_vfork) /* 190 */

.rept NR_syscalls-190

.long SYMBOL_NAME(sys_ni_syscall)

.endr

修改后為: ... ...

.long SYMBOL_NAME(sys_sendfile)

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */

.long SYMBOL_NAME(sys_vfork) /* 190 */

/* add by I */

.long SYMBOL_NAME(sys_addtotal)

.rept NR_syscalls-191

.long SYMBOL_NAME(sys_ni_syscall)

.endr

3.
把增加的 sys_call_table 表項(xiàng)所對應(yīng)的向量,include/asm-386/unistd.h 中進(jìn)行必要申明,以供用戶進(jìn)程和其他系統(tǒng)進(jìn)程查詢或調(diào)用:

增加后的部分 /usr/src/linux/include/asm-386/unistd.h 文件如下:

... ...

#define __NR_sendfile 187

#define __NR_getpmsg 188

#define __NR_putpmsg 189

#define __NR_vfork 190

/* add by I */

#define __NR_addtotal 191

 

4.
測試程序(test.c)如下:

#include

#include

 

_syscall1(int,addtotal,int, num)

 

main()

{

int i,j;


do

printf("Please input a numbern");

while(scanf("%d",&i)==EOF);

if((j=addtotal(i))==-1)

printf("Error occurred in syscall-addtotal();n");

printf("Total from 0 to %d is %d n",i,j);

}

  對修改后的新的內(nèi)核進(jìn)行編譯,并引導(dǎo)它作為新的操作系統(tǒng),運(yùn)行幾個(gè)程序后可以發(fā)現(xiàn)一切正常;在新的系統(tǒng)下對測試程序進(jìn)行編譯(*注:由于原內(nèi)核并未提供此系統(tǒng)調(diào)用,所以只有在編譯后的新內(nèi)核下,此測試程序才能可能被編譯通過),運(yùn)行情況如下:

$gcc �o test test.c

$./test

Please input a number

36

Total from 0 to 36 is 666

  可見,修改成功。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux中PowerPC的中斷原理分析
linux 中斷機(jī)制淺析
中斷相關(guān)
Linux內(nèi)核開發(fā)之中斷與時(shí)鐘(一)
linux的中斷和異常(一)
start_kernel
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服