- 論壇徽章:
- 13
|
完全理解本節(jié)內(nèi)容,需要先理解80386 CPU保護(hù)模式下段式映射過(guò)程
中斷:CPU外部硬件導(dǎo)致,比如網(wǎng)卡收發(fā)到報(bào)文、硬盤讀寫完成等
異常:CPU內(nèi)部執(zhí)行指令異常導(dǎo)致,比如除0、缺頁(yè)等
陷阱:CPU內(nèi)部執(zhí)行到INT指令時(shí)導(dǎo)致,與異常的區(qū)別是陷阱是"主動(dòng)"的,往往是用戶程序通過(guò)系統(tǒng)調(diào)用進(jìn)入的
這種區(qū)分只是從產(chǎn)生原因上劃分的,可以不用太關(guān)心,它們(后面統(tǒng)稱"中斷")從產(chǎn)生,到CPU進(jìn)內(nèi)核執(zhí)行完相應(yīng)處理函數(shù)的過(guò)程基本一樣,主要區(qū)別有兩點(diǎn):
① 中斷與異常/陷阱區(qū)別:處理中斷的過(guò)程中關(guān)中斷,即將標(biāo)志寄存器的IF標(biāo)志位置0,而處理異常/陷阱時(shí)不改變IF標(biāo)志位
② 異常與陷阱區(qū)別:異常處理完成,再重新執(zhí)行一遍導(dǎo)致異常的指令(比如訪問(wèn)某個(gè)換出頁(yè)面時(shí)導(dǎo)致缺頁(yè)異常,內(nèi)核恢復(fù)好映射后,再重新執(zhí)行出錯(cuò)指令就沒問(wèn)題了,對(duì)于出錯(cuò)指令來(lái)看,好像什么都沒發(fā)生過(guò)一樣),陷阱處理完成,執(zhí)行INT的下一條指令(gdb的斷點(diǎn)功能就是用的INT 3指令)
作為一個(gè)學(xué)習(xí)者而不是設(shè)計(jì)者,可以先看一下硬件如何設(shè)計(jì)的,再回頭理解為什么要這樣設(shè)計(jì)。
在程序中,我們用一些變量表示程序當(dāng)前的狀態(tài),比如全局變量running、stop,進(jìn)入某個(gè)函數(shù)時(shí)的參數(shù),其實(shí)變量本質(zhì)上都要?dú)w根于硬件上,都是內(nèi)存或寄存器的帶電狀態(tài)。通過(guò)了解硬件的設(shè)計(jì),更能體會(huì)到這一點(diǎn),硬件在作執(zhí)行一些操作的過(guò)程中,"傳參"都是靠寄存器或某個(gè)約定位置的內(nèi)存。
interrupt_1.png (359.92 KB, 下載次數(shù): 26)
下載附件
2016-10-28 11:26 上傳
80386 CPU為了支持現(xiàn)代意義的中斷,添加了IDTR寄存器,指向一個(gè)中斷向量表。類似于為了支持保護(hù)模式的段式內(nèi)存管理,添加了GDTR/LDTR寄存器,指向段描述符表;為了支持頁(yè)式內(nèi)存管理,添加CR3寄存器,指向頁(yè)目錄表。
中斷向量表中的每個(gè)64位都代表一個(gè)"門",門的種類分為“任務(wù)門”(101,忙閑忙,忙完剛休息一會(huì)又來(lái)任務(wù)了)、“中斷門”(110,前面都是1,到最后時(shí)"斷了")、“陷阱門”(111,陷阱下面放了一排刺)、“調(diào)用門”(100,每次考試都滿分,diǎo爆了)。
以下是Linux內(nèi)核中設(shè)置門(即為每個(gè)64位數(shù)據(jù)賦值)的函數(shù)(后面詳細(xì)說(shuō)明):
interrupt_2.png (22.25 KB, 下載次數(shù): 18)
下載附件
2016-10-28 12:22 上傳
為什么要設(shè)計(jì)這么多種門呢?因?yàn)橛布こ處焸兒苜N心呀 ,希望軟件設(shè)計(jì)可以更簡(jiǎn)單,從而考慮了較多情況下的任務(wù)切換,并在硬件層就實(shí)現(xiàn)好了。
但是,如同GDTR/LDTR寄存器,Linux幾乎不用LDTR,Linux也幾乎不用任務(wù)門和調(diào)用門:
通過(guò)任務(wù)門切換進(jìn)程時(shí),硬件缺少充足的信息而做了很多冗余的操作,執(zhí)行效率不夠高,而進(jìn)程切換又是一個(gè)非常頻繁的操作,所以Linux內(nèi)核進(jìn)程切換沒有利用任務(wù)門(詳見《Linux內(nèi)核源代碼情況分析》第四章);
內(nèi)核為了避開硬件復(fù)雜的設(shè)計(jì),比如內(nèi)核維護(hù)人員原本必須了解硬件的4種門,現(xiàn)在只需要了解2種。
① 根據(jù)IDTR寄存器指向的中斷向量表,以及中斷原因?qū)?yīng)的中斷向量,找到對(duì)應(yīng)的門(比如內(nèi)核啟動(dòng)時(shí),將缺頁(yè)異常對(duì)應(yīng)的門,設(shè)置在了表中下標(biāo)為14的地方,缺頁(yè)異常發(fā)生時(shí),CPU自動(dòng)根據(jù)該門進(jìn)入異常處理函數(shù)do_page_fault());
② 對(duì)比CPU當(dāng)前執(zhí)行權(quán)限CPL與門所要求的權(quán)限D(zhuǎn)PL(如同段式內(nèi)存映射過(guò)程要對(duì)比段寄存器RPL與段描述符中的DPL,門的結(jié)構(gòu)里面也有DPL),CPL≤DPL則可以穿過(guò)此門(值越小權(quán)限越高);
③ 每種門里面都有段選擇碼(16位),就可以把它當(dāng)作真實(shí)段寄存器一樣理解,并經(jīng)過(guò)段式映射過(guò)程,找到一個(gè)段描述符(其中通過(guò)任務(wù)門找到的是TSS段),段描述符中的DPL也要拿來(lái)和CPL對(duì)比,DPL≤CPL則可以訪問(wèn)到對(duì)應(yīng)的段(與段式映射過(guò)程中,段寄存器RPL≤段描述符DPL才能通過(guò)權(quán)限檢查相反,因?yàn)楝F(xiàn)在是中斷,想想通過(guò)系統(tǒng)調(diào)用可以從低權(quán)限的用戶態(tài)進(jìn)入高權(quán)限的內(nèi)核態(tài)就明白了);
④ 描述一個(gè)任務(wù),需要很多信息,比如該任務(wù)當(dāng)前各個(gè)寄存器的值、當(dāng)前的權(quán)限等,為此設(shè)計(jì)了TSS結(jié)構(gòu),穿過(guò)四種門,只有任務(wù)門段選擇碼找到的是TSS段,表示要切換到的新任務(wù),而當(dāng)前任務(wù)的TSS結(jié)構(gòu)由新增的TR寄存器指向。而其它門只是用于從用戶態(tài)切換到內(nèi)核態(tài),并不涉及任務(wù)的切換,其中的“段選擇碼+位移”指向一塊代碼,并且所要求的執(zhí)行權(quán)限一般會(huì)更高。
注意:進(jìn)程切換和用戶態(tài)/內(nèi)核態(tài)切換是兩回事,一個(gè)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài),只是一個(gè)TSS中部分信息發(fā)生了變化(比如穿過(guò)陷阱門的過(guò)程中CPU自動(dòng)修改CPL等),而進(jìn)程切換是從一個(gè)TSS“跳到”另一個(gè)TSS,不光要根據(jù)另一個(gè)TSS的內(nèi)容修改CPU的各種狀態(tài),TR寄存器也要指向新的TSS結(jié)構(gòu)。
⑤ 步驟③中對(duì)比段描述符DPL與CPL,如果不一致,還要進(jìn)行堆棧的切換,每個(gè)Linux進(jìn)程在用戶空間的棧和在內(nèi)核空間的棧是不同的,只要切換前后不同的,都必須在切換前記錄下來(lái),并且是記錄到切換后所能訪問(wèn)的空間,這樣切換回之前的狀態(tài)時(shí),才能找到恢復(fù)的依據(jù)。
上述只說(shuō)明了如何利用IDT,那么IDT是如何生成的呢?
interrupt_3.png (49.47 KB, 下載次數(shù): 23)
下載附件
2016-10-28 13:54 上傳
set_intr_gate()、set_trap_gate()、set_system_gate()、set_call_gate()調(diào)用的都是_set_gate()函數(shù),結(jié)合門的結(jié)構(gòu),就可以理解該函數(shù)了,并且體會(huì)一下內(nèi)核函數(shù)的精湛:
interrupt_4.png (46.55 KB, 下載次數(shù): 26)
下載附件
2016-10-28 14:42 上傳
通過(guò)_set_gate()函數(shù)的參數(shù)也可以看出,各種門的區(qū)別就是類型碼、dpl、偏移地址,比如set_system_gate(0x80, addr)→_set_gate(idt_table+0x80, 15/*0,D:1,type:111*/, 3/*dpl*/, addr)。
早期的計(jì)算機(jī),將中斷向量表大小設(shè)計(jì)為256個(gè)向量,經(jīng)過(guò)幾十年的發(fā)展,向量號(hào)有了“約定俗成”的含義,比如0x80用于系統(tǒng)調(diào)用,如今中斷向量表仍然包含256個(gè)項(xiàng),一個(gè)項(xiàng)即一個(gè)門,設(shè)置所有門,即是對(duì)每個(gè)門調(diào)用_set_gate()的過(guò)程:
interrupt_5.png (25.03 KB, 下載次數(shù): 32)
下載附件
2016-10-28 15:27 上傳
0~0x20、0x80比較特殊,通過(guò)上述trap_init()函數(shù)可以看到它們的初始化,其余的都設(shè)置為中斷門,并且處理函數(shù)地址都來(lái)自于interruput[]:
interrupt_6.png (25.81 KB, 下載次數(shù): 19)
下載附件
2016-10-28 15:40 上傳
interrupt[]數(shù)組存的實(shí)際是IRQ_0x0?_interrupt()這些函數(shù),它們的定義如下:
interrupt_7.png (54.27 KB, 下載次數(shù): 20)
下載附件
2016-10-28 16:18 上傳
本篇文章只介紹了中斷微量表的設(shè)置,以及根據(jù)"門"中的代碼段+偏移跳轉(zhuǎn)到IRQ_0x0?_interrupt(),或trap_init()設(shè)置的函數(shù)執(zhí)行的過(guò)程。至于common_interrupt處的代碼,到后面分析完整進(jìn)入中斷處理函數(shù)的過(guò)程時(shí),再分析,比如從page_fault()函數(shù)到do_page_fault()函數(shù),還要經(jīng)歷很長(zhǎng)的過(guò)程呢。
|
|