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

下載本文檔

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

文檔簡(jiǎn)介

第四章中斷處理4.1中斷處理流程 4.2異常處理4.3外部中斷處理4.4系統(tǒng)調(diào)用在引導(dǎo)與初始化所建立的操作系統(tǒng)“大廈”中,其核心部分(內(nèi)核)是封閉的,進(jìn)出的“通道”僅有一條,Linux將其稱(chēng)之為中斷處理。

出于安全與可靠方面的考慮,操作系統(tǒng)內(nèi)核通常會(huì)限制應(yīng)用程序的能力,如禁止它直接操作外部設(shè)備等。當(dāng)需要完成被限制的工作時(shí),應(yīng)用程序只能請(qǐng)求內(nèi)核幫助,常用的方法是系統(tǒng)調(diào)用(又稱(chēng)為陷入式中斷)。用戶(hù)進(jìn)程通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核。

當(dāng)內(nèi)核需要了解外部設(shè)備的狀態(tài)時(shí),它可以向外設(shè)發(fā)出查詢(xún)命令,稱(chēng)為輪詢(xún)。輪詢(xún)極為耗時(shí),且掌握的信息也不夠及時(shí),較好的方法是外部中斷。當(dāng)有情況需要通知內(nèi)核時(shí),外設(shè)發(fā)出中斷請(qǐng)求。處理器收到中斷后,暫停當(dāng)前的工作,轉(zhuǎn)入內(nèi)核進(jìn)行相應(yīng)的處理。外部中斷是進(jìn)入內(nèi)核的一條重要途徑。

在處理器執(zhí)行程序的過(guò)程中,可能會(huì)遇到一些不尋常的事件,如被0除、頁(yè)故障等,Intel稱(chēng)之為異常(又稱(chēng)為內(nèi)部中斷)。當(dāng)異常發(fā)生時(shí),處理器無(wú)法再正常執(zhí)行,只能暫停當(dāng)前的工作,轉(zhuǎn)入內(nèi)核處理遇到的異常。異常處理是進(jìn)入內(nèi)核的另一重要途徑。

中斷處理有著大致相同的工作流程,如圖4.1所示。

4.1中斷處理流程圖4.1中斷處理流程

IDT表是所有中斷處理的入口。不管是異常、外部中斷還是系統(tǒng)調(diào)用,當(dāng)中斷發(fā)生時(shí),處理器會(huì)自動(dòng)切換堆棧、保存現(xiàn)場(chǎng),而后通過(guò)IDT表中的門(mén)進(jìn)入處理程序,完成預(yù)定的處理。IDT表已在系統(tǒng)初始化時(shí)設(shè)置完畢。

在所有的中斷處理程序中,異常處理程序是固定的,通過(guò)IDT中的門(mén)可直接進(jìn)入;所有的系統(tǒng)調(diào)用有著同一個(gè)入口程序,即system_call,該程序?qū)⒏鶕?jù)系統(tǒng)調(diào)用表確定各服務(wù)函數(shù)的位置;處理器間中斷(IPI)和局部中斷的處理程序也是固定的,通過(guò)IDT中的門(mén)也可直接進(jìn)入;每個(gè)設(shè)備中斷都有自己獨(dú)立的處理程序,但具有相似的入口程序,入口程序?qū)⒗迷O(shè)備中斷的向量號(hào)和irq_desc結(jié)構(gòu)確定實(shí)際處理程序的位置。在進(jìn)行中斷或異常處理時(shí),處理器的現(xiàn)場(chǎng)被保存在當(dāng)前進(jìn)程的系統(tǒng)堆棧中。為了便于訪問(wèn),Linux將系統(tǒng)堆棧的棧頂定義成了一個(gè)結(jié)構(gòu),稱(chēng)為pt_regs。當(dāng)中斷或異常發(fā)生時(shí),Linux總是設(shè)法將系統(tǒng)堆棧的棧頂做成pt_regs結(jié)構(gòu)。在IA-32處理器平臺(tái)上,結(jié)構(gòu)pt_regs的格式如圖4.2所示。圖4.2pt_regs結(jié)構(gòu)及意義中斷處理完后,一般情況下處理器都會(huì)恢復(fù)現(xiàn)場(chǎng),返回到中斷發(fā)生前的位置,繼續(xù)被中斷的工作。然而在退出內(nèi)核之前,還應(yīng)該做一些善后處理,如圖4.3所示。圖4.3中斷的善后處理流程圖4.3中的“中斷關(guān)閉否?”判斷的是進(jìn)入中斷處理程序之前處理器的中斷狀態(tài)。

中斷恢復(fù)程序restore_all會(huì)彈出進(jìn)程系統(tǒng)堆棧的棧頂,恢復(fù)處理器各通用寄存器的值,清除錯(cuò)誤代碼,而后執(zhí)行IRET指令,再?gòu)棾鯡IP、CS、EFLAGS、ESP、SS,返回異常或中斷前的執(zhí)行位置。特別地,由于EFLAGS的恢復(fù),處理器的中斷狀態(tài)也被恢復(fù)到之前的水準(zhǔn),使善后處理流程中的“關(guān)中斷”不再生效。

異常來(lái)自于程序執(zhí)行過(guò)程中的違規(guī)事件。Intel處理器定義了20個(gè)異常,Linux內(nèi)核為這些異常分別定義了處理程序。在系統(tǒng)初始化時(shí),已根據(jù)各異常的處理程序和特權(quán)級(jí)為它們構(gòu)造了中斷門(mén)(第8號(hào)異常除外),并已將它們填入到了IDT表中。表4.1是異常向量號(hào)與處理程序之間的對(duì)應(yīng)關(guān)系。4.2異常處理

表4.1異常類(lèi)型及異常處理程序在早期的版本中,Linux為每個(gè)異常處理程序定義了一個(gè)陷阱門(mén)。新版本的Linux為每個(gè)異常處理程序定義了一個(gè)中斷門(mén),但卻為第8號(hào)異常專(zhuān)門(mén)定義了一個(gè)任務(wù)門(mén)(用于處理雙故障異常),其中的任務(wù)狀態(tài)段由GDT中的第31個(gè)段描述符描述。4.2.1異常處理流程

除了異常的個(gè)數(shù)稍有增加以外,在Linux的演化過(guò)程中,異常處理的流程與處理的方法基本未變。當(dāng)處理器檢測(cè)到異常時(shí),它首先根據(jù)TR寄存器的內(nèi)容找到當(dāng)前進(jìn)程的系統(tǒng)堆棧。如果該系統(tǒng)堆棧不是當(dāng)前使用的堆棧,還要進(jìn)行堆棧切換,并將老堆棧的棧頂(即SS:ESP)壓入系統(tǒng)堆棧;而后在系統(tǒng)堆棧上壓入寄存器EFLAGS、CS、EIP和error-code(可能沒(méi)有);最后,根據(jù)異常向量號(hào)查IDT表,獲得系統(tǒng)注冊(cè)的處理程序,并跳轉(zhuǎn)到該程序進(jìn)行異常處理。各異常處理程序的執(zhí)行過(guò)程大致相同,如下:

(1)如果處理器未壓入錯(cuò)誤代碼(error-code),則在棧頂壓入一個(gè)0或-1,以保持棧頂

平衡。

(2)將真正的異常處理程序的入口地址壓入堆棧。真正的異常處理程序的名稱(chēng)通常是do_xxx,其中xxx是列在表4.1中的處理程序名,已預(yù)先指定。

(3)依次將寄存器FS、ES、DS、EAX、EBP、EDI、ESI、EDX、ECX、EBX壓入堆棧,形成一個(gè)pt_regs結(jié)構(gòu)。

(4)設(shè)置段寄存器。將DS、ES設(shè)為當(dāng)前處理器GDT表中的用戶(hù)數(shù)據(jù)段,將FS設(shè)為per-cpu數(shù)據(jù)段,以便訪問(wèn)PERCPU數(shù)據(jù)。

(5)準(zhǔn)備參數(shù)。將棧頂中的異常處理程序入口地址取到EDI中,將錯(cuò)誤代碼取到EDX中,將當(dāng)前的棧頂位置(指向pt_regs結(jié)構(gòu)的指針)存在EAX中。

(6)調(diào)用EDI中的異常處理程序,處理異常。此處通過(guò)寄存器向異常處理程序傳遞了兩個(gè)參數(shù),分別是EAX中的棧頂指針和EDX中的錯(cuò)誤代碼。

(7)當(dāng)異常處理程序返回時(shí),跳轉(zhuǎn)到ret_from_exception,進(jìn)行善后處理,其流程如圖4.3所示。

真正的異常處理程序都會(huì)接受兩個(gè)參數(shù),其原型定義如下:

voiddo_xxx(structpt_regs*regs,longerror_code)

不同異常的處理方法有很大區(qū)別。有些異常,如有效的頁(yè)故障異常,可以被Linux內(nèi)核糾正。當(dāng)這類(lèi)異常被處理完后,進(jìn)程會(huì)繼續(xù)向下執(zhí)行,整個(gè)異常處理的過(guò)程對(duì)進(jìn)程是透明的。8.4節(jié)分析了頁(yè)故障異常的處理程序do_page_fault。

然而大多數(shù)異常,如通用保護(hù)異常、除法錯(cuò)誤異常等,都沒(méi)有辦法糾正。如果異常發(fā)生時(shí)處理器運(yùn)行在用戶(hù)態(tài),Linux會(huì)向進(jìn)程發(fā)送信號(hào),通報(bào)異常的詳細(xì)信息。如果用戶(hù)進(jìn)程注冊(cè)了相應(yīng)信號(hào)的處理程序,該程序會(huì)被執(zhí)行;如果用戶(hù)進(jìn)程未注冊(cè)自己的信號(hào)處理程序,Linux將按缺省方式處理信號(hào),大部分情況下是直接終止進(jìn)程。4.2.2內(nèi)核異常捕捉

如果異常發(fā)生時(shí)處理器運(yùn)行在核心態(tài),說(shuō)明異常是由內(nèi)核程序引起的。除了一些特殊情況之外,內(nèi)核可以預(yù)估到異常發(fā)生的位置。事實(shí)上,這類(lèi)異常的大多數(shù)是內(nèi)核有意留下的,是由故意忽略的合法性檢查引起的。被忽略的合法性檢查通常代價(jià)較高且違法的概率較低,每次都進(jìn)行這類(lèi)檢查會(huì)降低系統(tǒng)性能。將檢查工作推遲到異常處理程序中完成,或者說(shuō)利用糾錯(cuò)機(jī)制代替檢查機(jī)制會(huì)極大地減少不必要的工作量,提升系統(tǒng)性能,是Linux“懶惰”哲學(xué)的一種體現(xiàn)。

為了有效捕捉到內(nèi)核故意留下的異常,Linux定義了兩個(gè)專(zhuān)門(mén)的節(jié):

(1)節(jié)“_

_ex_table”是結(jié)構(gòu)exception_table_entry的一個(gè)數(shù)組,記錄各預(yù)留異常可能發(fā)生的位置insn和為其定義的處理程序入口fixup,稱(chēng)為異常列表,定義如下:

structexception_table_entry{

unsignedlonginsn,fixup;

};

structexception_table_entry_

_start_

_ex_table[];

系統(tǒng)初始化程序已對(duì)數(shù)組_

_start_

_ex_table[]進(jìn)行了排序,小的insn在前,大的insn在后。

(2)節(jié)“.fixup”是各預(yù)留異常的處理程序。

當(dāng)異常發(fā)生時(shí),處理器會(huì)按正常的流程進(jìn)入異常處理程序。如果異常發(fā)生時(shí)處理器運(yùn)行在核心態(tài),那么當(dāng)處理程序無(wú)法按正常方式處理該異常時(shí),Linux會(huì)根據(jù)異常發(fā)生時(shí)的指令地址(系統(tǒng)堆棧中的ip)搜索數(shù)組_

_start_

_ex_table[]。如果找到了與之匹配(insn==ip)的exception_table_entry結(jié)構(gòu),表明捕捉到了一次內(nèi)核預(yù)留的異常,則將系統(tǒng)堆棧中的ip換成fixup。當(dāng)異常處理程序返回時(shí),處理器會(huì)從fixup處執(zhí)行特定的異常處理程序,糾正預(yù)留的異常。

圖4.4預(yù)留異常處理實(shí)例上述程序被放在3個(gè)節(jié)中,其中第5、6兩行程序是異常處理程序,被放在“.fixup”節(jié)中,第9、10兩行聲明一個(gè)exception_table_entry結(jié)構(gòu),放在“_

_ex_table”節(jié)中,其余代碼放在“.text”節(jié)中。

由于addr位于用戶(hù)空間,因而正常情況下需要檢查地址addr的有效性。這種檢查的代價(jià)較高,且大部分情況下都是有效的,所以Linux故意忽略了檢查,直接用第2行的指令復(fù)制數(shù)據(jù)。當(dāng)處理器發(fā)現(xiàn)地址無(wú)效且無(wú)法正常處理時(shí),根據(jù)異常列表,Linux會(huì)捕捉到該異常,并會(huì)執(zhí)行第4、5兩行程序處理異常,即返回一個(gè)錯(cuò)誤代碼。

與異常不同,外部中斷來(lái)自處理器之外的硬件。在早期的計(jì)算機(jī)系統(tǒng)中,外部中斷僅有一個(gè)來(lái)源,即外部設(shè)備。但在目前的計(jì)算機(jī)系統(tǒng)中,外部中斷已有多種來(lái)源。根據(jù)來(lái)源的不同,可以將外部中斷分為局部中斷(來(lái)自處理器自身的LocalAPIC)、處理器間中斷(來(lái)自其它處理器)和設(shè)備中斷(來(lái)自外部設(shè)備)。局部中斷直接遞交給處理器,處理器間中斷通過(guò)LocalAPIC遞交給處理器,設(shè)備中斷通過(guò)中斷控制器(8259A或I/OAPIC)遞交給LocalAPIC并進(jìn)而遞交給處理器。不同來(lái)源的中斷有著不同的處理方法。4.3外部中斷處理外部中斷可以屏蔽,且有多個(gè)不同的屏蔽層次。可以讓設(shè)備不產(chǎn)生中斷,可以讓中斷控制器不遞交中斷,也可以讓處理器不處理中斷。通常情況下,經(jīng)過(guò)中斷門(mén)自動(dòng)進(jìn)入外部中斷處理程序會(huì)導(dǎo)致處理器屏蔽中斷。為了縮短關(guān)中斷的時(shí)間又不至于造成中斷處理的混亂,需要將外部中斷的處理工作分成兩部分,一部分是由硬件自動(dòng)進(jìn)入、必須在關(guān)中斷狀態(tài)下處理的,稱(chēng)為硬處理,另一部分是由軟件進(jìn)入、可以在開(kāi)中斷狀態(tài)下處理的,稱(chēng)為軟處理。因而,在外部中斷處理中,既需要為硬處理提供靈活的注冊(cè)、注銷(xiāo)和處理機(jī)制,又需要為軟處理提供靈活的通知、喚醒和處理機(jī)制。4.3.1硬處理管理結(jié)構(gòu)

在所有的外部中斷中,局部中斷和處理器間中斷(簡(jiǎn)稱(chēng)IPI)是比較簡(jiǎn)單的,其意義是明確的。Linux內(nèi)核在初始化時(shí)已為每一種局部中斷和處理器間中斷都指定了處理程序,且已將其設(shè)置在IDT表中。局部中斷和處理器間中斷不再需要特別的管理結(jié)構(gòu)。

比較復(fù)雜的是設(shè)備中斷。由于設(shè)備中斷的分派不固定、其處理程序也不固定,不能預(yù)先將處理程序設(shè)置在IDT表中,而且由于設(shè)備中斷的處理程序變化頻繁、不太可靠,也不應(yīng)當(dāng)將其設(shè)置在IDT表中。Linux的解決辦法是:為每個(gè)設(shè)備中斷預(yù)先創(chuàng)建一個(gè)固定的入口程序,用這一入口程序創(chuàng)建中斷門(mén),并將其填入IDT中;在IDT表之外再為設(shè)備中斷準(zhǔn)備一個(gè)注冊(cè)表,用于動(dòng)態(tài)地注冊(cè)和注銷(xiāo)實(shí)際的中斷處理程序。表4.2列出了IDT表中的外部中斷設(shè)置,包括各外部中斷的向量號(hào)及其處理程序。

各設(shè)備中斷的入口程序大同小異。向量號(hào)為vector的入口程序如圖4.5所示。為各設(shè)備中斷預(yù)建的固定入口程序的地址記錄在數(shù)組interrupt中。

圖4.5設(shè)備中斷入口程序

表4.2外部中斷向量及其處理程序在圖4.5中,SAVE_ALL是一個(gè)宏,主要完成兩件工作,一是將寄存器GS、FS、ES、DS、EAX、EBP、EDI、ESI、EDX、ECX、EBX壓到系統(tǒng)堆棧的棧頂,構(gòu)造出pt_regs結(jié)構(gòu);二是將段寄存器DS、ES設(shè)成用戶(hù)數(shù)據(jù)段,將FS設(shè)成處理器的per-cpu段,將GS設(shè)成處理器的stack_canary-20段。注冊(cè)表是一個(gè)irq_desc結(jié)構(gòu)的指針數(shù)組,用于組織各設(shè)備中斷的處理程序。在使用設(shè)備之前(初始化或打開(kāi)時(shí)),需要為其申請(qǐng)中斷向量號(hào)、注冊(cè)中斷處理程序并設(shè)置中斷控制器(允許遞交該設(shè)備的中斷);在使用設(shè)備之時(shí),如設(shè)備產(chǎn)生中斷,通過(guò)中斷向量號(hào)和注冊(cè)表可找到它的處理程序并可對(duì)其進(jìn)行必要的檢查和處理;在使用設(shè)備之后,需注銷(xiāo)其處理程序、釋放中斷向量號(hào)并重置中斷控制器(屏蔽該設(shè)備的中斷)。

由于多個(gè)設(shè)備可能共用一個(gè)向量號(hào),因而應(yīng)將注冊(cè)表中的每一項(xiàng)都改為處理程序隊(duì)列,而且需要有某種標(biāo)志來(lái)區(qū)分處理程序所對(duì)應(yīng)的設(shè)備。毫無(wú)疑問(wèn),在設(shè)備中斷注冊(cè)、注銷(xiāo)、處理的過(guò)程中,需要經(jīng)常操作中斷控制器,而且設(shè)備中斷的處理方式也與中斷控制器有著密切的關(guān)系。由于中斷控制器有多種不同的型號(hào),其操作方法有著較大的差別,為了簡(jiǎn)化對(duì)中斷控制器的管理,應(yīng)該提供一個(gè)統(tǒng)一的中斷控制器操作接口。在早期的版本中,Linux用結(jié)構(gòu)hw_interrupt_type描述中斷控制器類(lèi)型,其中包含中斷控制器特有的一組操作,如enable(開(kāi)中斷)、disable(關(guān)中斷)、handle(處理中斷)等。當(dāng)中斷發(fā)生時(shí),Linux調(diào)用中斷控制器特有的handle操作,按控制器特有的方式來(lái)處理中斷,如執(zhí)行注冊(cè)表中的處理程序等。在隨后的發(fā)展中,人們發(fā)現(xiàn)中斷控制器在遞交中斷時(shí)可能采用不同的觸發(fā)方式,如邊緣觸發(fā)、水平觸發(fā)等,而不同的觸發(fā)方式有著不同的中斷處理流程,因而簡(jiǎn)單地用一個(gè)hw_interrupt_type結(jié)構(gòu)來(lái)描述中斷控制器會(huì)使中斷處理流程過(guò)于復(fù)雜(handle中必須包含各種不同的處理流程),而且會(huì)使中斷處理代碼重復(fù)(不同的控制器可能有相同的處理流程)。為此,新版本的Linux將控制器類(lèi)型結(jié)構(gòu)hw_interrupt_type一分為二,用結(jié)構(gòu)irq_flow_handler_t描述流程控制程序,用結(jié)構(gòu)irq_chip描述中斷控制器。結(jié)構(gòu)irq_chip中包含一組中斷控制器特有的底層操作,如enable、disable、mask、unmask、ack、eoi、end等,用于中斷控制器的管理,如開(kāi)中斷、關(guān)中斷、應(yīng)答中斷等。

流程控制程序用于控制中斷處理的流程,也就是中斷處理程序執(zhí)行的方法和執(zhí)行前后的動(dòng)作。當(dāng)設(shè)備中斷被注冊(cè)之后,它的處理流程也隨之確定。每個(gè)設(shè)備中斷都可以有自己的處理流程,由同一個(gè)控制器遞交的中斷也可以有不同的處理流程,因而這一分割帶來(lái)了極大的靈活性。Linux將中斷控制流程大致分成如下幾類(lèi):

(1)

LEVEL類(lèi)用于處理水平觸發(fā)的中斷。

(2)

EDGE類(lèi)用于處理邊緣觸發(fā)的中斷。

(3)

SIMPLE類(lèi)用于處理簡(jiǎn)單的、不需要控制器操作的中斷。

(4)

PERCPU類(lèi)用于處理與處理器綁定的中斷。

(5)

FASTEOI類(lèi)用于處理需快速應(yīng)答的中斷。

設(shè)備中斷的注冊(cè)、注銷(xiāo)、處理都是以中斷號(hào)為依據(jù)的,一個(gè)設(shè)備使用一個(gè)中斷號(hào)。在早期的計(jì)算機(jī)系統(tǒng)中,中斷號(hào)就是IRQ號(hào),但在目前的計(jì)算機(jī)系統(tǒng)中,與一個(gè)設(shè)備中斷關(guān)聯(lián)的有三個(gè)編號(hào),分別是IRQ號(hào)(或GSI號(hào))、中斷向量號(hào)(vector)和管腳號(hào)(pin)。外部設(shè)備使用的是IRQ號(hào),中斷控制器遞交給處理器的是中斷向量號(hào),對(duì)中斷控制器的設(shè)置使用的是管腳號(hào)。由于系統(tǒng)中可能配置有多個(gè)中斷控制器,而且中斷控制器的每個(gè)管腳都可以單獨(dú)編程,使得IRQ號(hào)、中斷向量號(hào)、管腳號(hào)之間不再是一一對(duì)應(yīng)的關(guān)系。一般情況下,一個(gè)IRQ號(hào)等價(jià)于一個(gè)全局統(tǒng)一的管腳號(hào)(GSI),對(duì)應(yīng)一個(gè)中斷控制器管腳(pin),有一個(gè)綁定的中斷向量號(hào),但多個(gè)管腳可以遞交同一個(gè)中斷向量號(hào)。為了描述三者之間的映射關(guān)系,Linux為每個(gè)設(shè)備中斷準(zhǔn)備了一個(gè)irq_cfg結(jié)構(gòu),用于記錄IRQ號(hào)與中斷向量號(hào)的對(duì)應(yīng)關(guān)系;在結(jié)構(gòu)irq_cfg中包含一個(gè)隊(duì)列記錄遞交同一個(gè)中斷向量號(hào)的所有I/OAPIC管腳;在PERCPU數(shù)據(jù)區(qū)中為每個(gè)處理器定義了一個(gè)數(shù)組vector_irq,用于記錄向量號(hào)vector與irq之間的對(duì)應(yīng)關(guān)系。

結(jié)構(gòu)irq_desc是上述各元素的集合,是為設(shè)備中斷準(zhǔn)備的管理結(jié)構(gòu),每個(gè)設(shè)備中斷一個(gè),用于描述與該中斷相關(guān)的所有管理信息,如設(shè)備中斷的IRQ號(hào)、向量號(hào)、中斷控制器操作接口、中斷處理流程、處理程序隊(duì)列、中斷狀態(tài)等。結(jié)構(gòu)irq_desc可以動(dòng)態(tài)創(chuàng)建,向量表(或注冊(cè)表)irq_desc_ptrs中記錄著系統(tǒng)中所有的irq_desc結(jié)構(gòu),以設(shè)備中斷的IRQ號(hào)為索引。結(jié)構(gòu)irq_desc中包含如下主要內(nèi)容:

structirq_desc{

unsignedint irq; //irq號(hào)

unsignedint *kstat_irqs; //統(tǒng)計(jì)信息

irq_flow_handler_t handle_irq; //中斷處理流程

structirq_chip *chip; //控制器操作接口

structmsi_desc *msi_desc; //MSI相關(guān)信息

void *handler_data; //處理程序私有數(shù)據(jù)

void *chip_data; //控制器私有數(shù)據(jù),即irq_cfg

structirqaction *action; //處理程序隊(duì)列

unsignedint status; //中斷狀態(tài)

unsignedint depth; //中斷去能計(jì)數(shù)

unsignedint wake_depth; //電源喚醒計(jì)數(shù)

unsignedint irq_count; //中斷產(chǎn)生次數(shù)

spinlock_t lock; //自旋鎖,用于保護(hù)action隊(duì)列

cpumask_var_t affinity; //處理器集合

atomic_t threads_active; //活動(dòng)線程數(shù)

wait_queue_head_t wait_for_threads; //等待隊(duì)列

constchar *name;

}____cacheline_internodealigned_in_smp;

在結(jié)構(gòu)irq_desc中,action是中斷處理程序隊(duì)列。所有的設(shè)備中斷處理程序都被包裝成irqaction結(jié)構(gòu),并在使用之前掛在自己的irq_desc中。使用同一個(gè)IRQ號(hào)的所有處理程序被掛在同一個(gè)irq_desc中,形成一個(gè)隊(duì)列。結(jié)構(gòu)irqaction的定義如下:

structirqaction{

irq_handler_t handler; //真正的中斷處理程序

unsignedlong flags; //特殊要求

constchar *name; //設(shè)備名

void *dev_id; //設(shè)備ID

structirqaction *next; //隊(duì)列指針

int irq; //IRQ號(hào)

structproc_dir_entry *dir; //在proc文件系統(tǒng)中的目錄項(xiàng)

irq_handler_t thread_fn; //中斷處理線程所執(zhí)行的函數(shù)

structtask_struct *thread; //線程管理結(jié)構(gòu)

unsignedlong thread_flags; //與線程相關(guān)的標(biāo)志

};

如果設(shè)備中斷經(jīng)由I/OAPIC遞交,那么在該中斷的irq_desc結(jié)構(gòu)中,指針chip_data指向的就是irq_cfg結(jié)構(gòu),其中的主要成員如下:

structirq_pin_list{

int apic,pin; //遞交中斷的I/OAPIC號(hào)及管腳

structirq_pin_list *next;

};

structirq_cfg{

structirq_pin_list *irq_2_pin; //隊(duì)列

u8 vector; //中斷向量

};由此可見(jiàn),IRQ是結(jié)構(gòu)irq_desc在指針數(shù)組irq_desc_ptrs中的索引,而vector則是I/OAPIC遞交的中斷向量號(hào),是中斷門(mén)在IDT中的索引。IRQ與vector是一一對(duì)應(yīng)的,IRQ從0開(kāi)始編碼,vector從0x30開(kāi)始編碼。各結(jié)構(gòu)之間的關(guān)系如圖4.6所示。

圖4.6設(shè)備中斷硬處理管理結(jié)構(gòu)設(shè)備中斷的入口程序得到的是向量號(hào)vector。以vector為索引查數(shù)組vector_irq可得到與之對(duì)應(yīng)的IRQ號(hào),查數(shù)組irq_desc_ptrs可得到描述結(jié)構(gòu)irq_desc。在結(jié)構(gòu)irq_desc中,action中列出了該中斷的所有處理程序,irq_2_pin中列出了遞交該中斷的所有管腳,chip是中斷控制器操作接口,handle_irq是中斷的流程控制程序。4.3.2設(shè)備中斷硬處理管理接口

顯然,設(shè)備中斷的管理結(jié)構(gòu)是比較復(fù)雜的,如讓驅(qū)動(dòng)程序直接操作這么復(fù)雜的結(jié)構(gòu)必然會(huì)帶來(lái)混亂。因此,Linux另外定義了一個(gè)管理接口,用于操作上述結(jié)構(gòu),如在其中插入、刪除irqaction結(jié)構(gòu)(注冊(cè)、注銷(xiāo)處理程序),插入、刪除irq_pin_list結(jié)構(gòu),更換irq_chip結(jié)構(gòu)等。不管外部中斷來(lái)自哪種設(shè)備、被哪種中斷控制器轉(zhuǎn)發(fā),利用這些接口函數(shù)都可以完成中斷的注冊(cè)、注銷(xiāo)、打開(kāi)、關(guān)閉等操作。中斷管理接口屏蔽了底層的操作細(xì)節(jié),也將設(shè)備中斷處理分成了多個(gè)層次,從而增加了設(shè)備中斷處理的靈活性和可靠性。設(shè)備中斷管理層的組織結(jié)構(gòu)如圖4.7所示,其中設(shè)備中斷的硬處理程序通常由設(shè)備的驅(qū)動(dòng)程序提供。設(shè)備驅(qū)動(dòng)程序了解設(shè)備的實(shí)現(xiàn)細(xì)節(jié),并能通過(guò)設(shè)備接口操作設(shè)備,完成相應(yīng)的處理。

圖4.7設(shè)備中斷管理中的層次關(guān)系

1.控制器操作接口

I/OAPIC類(lèi)中斷控制器提供的操作接口包括startup、mask、unmask、ack、eoi、set_affinity、retrigger等。

啟動(dòng)操作startup用于設(shè)置I/OAPIC的重定向表,打開(kāi)指定的中斷。如果要啟動(dòng)中斷的IRQ號(hào)小于16,還要設(shè)置8259A類(lèi)控制器,使其停止轉(zhuǎn)發(fā)該中斷。

屏蔽操作mask用于設(shè)置I/OAPIC的重定向表,屏蔽指定的中斷。

解除操作unmask用于設(shè)置I/OAPIC的重定向表,解除對(duì)指定中斷的屏蔽。應(yīng)答操作ack用于向LocalAPIC的EOI寄存器中寫(xiě)0,表示中斷已經(jīng)處理完畢。如果需要,應(yīng)答操作還會(huì)試圖遷移中斷。

結(jié)束操作eoi也用于應(yīng)答中斷(向EOI寄存器寫(xiě)0),并在必要時(shí)遷移中斷。結(jié)束操作還會(huì)修改I/OAPIC的重定向表,將所應(yīng)答中斷的觸發(fā)模式改為水平觸發(fā)。

設(shè)置處理器集操作set_affinity用于修改結(jié)構(gòu)irq_desc中的affinity域。

重發(fā)操作retrigger用于向自己再次發(fā)送指定的中斷。

2.設(shè)備中斷管理接口

設(shè)備中斷管理接口中包含多個(gè)接口函數(shù),其中最主要的是request_irq()、free_irq()、disable_irq()、enable_irq()、synchronize_irq()等。

函數(shù)request_irq()用于注冊(cè)中斷處理程序。在使用某個(gè)外部中斷之前必須為它注冊(cè)中斷處理程序。在注冊(cè)完成之后,新到來(lái)的中斷即會(huì)交由該處理程序處理。

注冊(cè)操作的主要工作是創(chuàng)建一個(gè)irqaction結(jié)構(gòu)并將其掛在irq_desc結(jié)構(gòu)的action隊(duì)列中。action隊(duì)列上可能沒(méi)有任何處理程序,可能僅有一個(gè)處理程序,也可能有多個(gè)處理程序。如果要在一個(gè)隊(duì)列上注冊(cè)多個(gè)處理程序,那么所有的處理程序都應(yīng)該允許共享(irqaction.flags上有IRQF_SHARED標(biāo)志),而且它們的觸發(fā)方式也應(yīng)該一致。

注冊(cè)操作可能伴隨著中斷使能,即打開(kāi)中斷控制器對(duì)新申請(qǐng)中斷的屏蔽。

函數(shù)free_irq()用于注銷(xiāo)已注冊(cè)的中斷處理程序,其主要工作是將包含中斷處理程序的irqaction結(jié)構(gòu)從隊(duì)列中摘下并釋放。注銷(xiāo)操作可能伴隨著中斷去能,即讓中斷控制器屏蔽已注銷(xiāo)的中斷。函數(shù)disable_irq()用于去能一個(gè)外部中斷,enable_irq()用于使能一個(gè)外部中斷。去能的結(jié)果是在中斷對(duì)應(yīng)的irq_desc.status上加入IRQ_DISABLED標(biāo)志并執(zhí)行irq_chip上的disable操作。使能的結(jié)果是清除irq_desc.status上的IRQ_DISABLED標(biāo)志并執(zhí)行irq_chip上的enable操作。去能、使能操作可以嵌套使用,為了保持中斷狀態(tài)的一致性,去能操作會(huì)增加irq_desc.depth,使能操作會(huì)減少irq_desc.depth。只有當(dāng)該計(jì)數(shù)為0時(shí)才真正執(zhí)行irq_chip上的去能、使能操作。函數(shù)synchronize_irq()用于同步中斷,即等待一個(gè)設(shè)備中斷的所有處理程序都處理完畢,不管它們?cè)谀膫€(gè)處理器上執(zhí)行。中斷去能和注銷(xiāo)操作都需要同步。

另外幾個(gè)接口函數(shù)用于設(shè)置irq_desc結(jié)構(gòu)中的關(guān)鍵參數(shù),如觸發(fā)方式、chip_data、chip、handler_data、wake_depth等。4.3.3外部中斷硬處理

不同的外部中斷有不同的硬處理方式。局部和處理器間(Inter-processorinterrupt,IPI)中斷的硬處理比較簡(jiǎn)單,設(shè)備中斷的硬處理比較復(fù)雜。若硬中斷處理程序未屏蔽或又打開(kāi)了正在處理的中斷,可能還會(huì)出現(xiàn)中斷嵌套的現(xiàn)象。

1.設(shè)備中斷硬處理

處理器收到設(shè)備中斷后,首先完成堆棧切換和狀態(tài)保存(在棧頂),而后查IDT表獲得缺省的中斷門(mén),最后經(jīng)過(guò)中斷門(mén)進(jìn)入設(shè)備中斷入口程序。各設(shè)備中斷的入口程序完成大致相同的三項(xiàng)工作(如圖4.5所示),一是在棧頂構(gòu)造出pt_regs結(jié)構(gòu),二是調(diào)用函數(shù)do_IRQ完成真正的中斷處理工作,三是跳轉(zhuǎn)到ret_from_intr完成善后處理工作。

函數(shù)do_IRQ是設(shè)備中斷處理的總控程序或者說(shuō)調(diào)度程序,它根據(jù)設(shè)備中斷的描述結(jié)構(gòu)irq_desc,決定采用何種處理流程、調(diào)用哪些處理函數(shù)以完成設(shè)備中斷的實(shí)際處理工作。do_IRQ的工作過(guò)程大致如下:

(1)從棧頂取出中斷向量號(hào)。根據(jù)向量號(hào)查處理器自己的vector_irq數(shù)組獲得中斷的IRQ號(hào)。根據(jù)IRQ號(hào)查向量表irq_desc_ptrs,找到中斷的描述結(jié)構(gòu)irq_desc。

(2)累加當(dāng)前進(jìn)程的硬中斷計(jì)數(shù)(在thread_info.preempt_count中),表示當(dāng)前進(jìn)程被中斷,系統(tǒng)即將進(jìn)入中斷處理流程。

(3)如果需要,可再次切換堆棧,以便在專(zhuān)門(mén)的上下文中處理中斷。如果未切換堆棧,系統(tǒng)將在當(dāng)前進(jìn)程的上下文中處理中斷。

(4)執(zhí)行函數(shù)irq_desc.handle_irq,按照預(yù)定的流程處理中斷。

LEVEL類(lèi)的處理流程。屏蔽正在處理的中斷;應(yīng)答中斷;累計(jì)與該中斷相關(guān)的統(tǒng)計(jì)信息;如果該中斷未被去能,則順序執(zhí)行irq_desc.action中的各個(gè)處理程序;解除被屏蔽的中斷。如果在處理過(guò)程中又收到了同一個(gè)硬中斷(嵌套中斷),僅屏蔽、應(yīng)答(相當(dāng)于丟棄)但不處理它們。②

EDGE類(lèi)的處理流程。累計(jì)與該中斷相關(guān)的統(tǒng)計(jì)信息;應(yīng)答中斷;如果該中斷未被去能,則順序執(zhí)行irq_desc.action中的各個(gè)處理程序。由于未屏蔽中斷,因而在中斷處理過(guò)程中其它處理器可能會(huì)再次收到正在處理的中斷。為了避免重入,新到達(dá)的中斷不會(huì)立刻被處理,僅會(huì)留下一個(gè)標(biāo)記。在當(dāng)前處理器執(zhí)行完irq_desc.action中的處理程序之后,如果發(fā)現(xiàn)留下的標(biāo)記,應(yīng)再次執(zhí)行irq_desc.action中的處理程序。為了保證不丟失中斷,新到的中斷應(yīng)屏蔽自己,以使待處理的中斷數(shù)不超過(guò)1個(gè)。但一旦開(kāi)始處理被標(biāo)記的中斷,就要再次解除屏蔽。③

SIMPLE類(lèi)的處理流程。累計(jì)與該中斷相關(guān)的統(tǒng)計(jì)信息;如果該中斷未被去能,則順序執(zhí)行irq_desc.action中的各個(gè)處理程序。不應(yīng)答中斷,不屏蔽正在處理的中斷,也不特別處理嵌套的中斷。

PERCPU類(lèi)的處理流程。累計(jì)與該中斷相關(guān)的統(tǒng)計(jì)信息;應(yīng)答中斷;如果該中斷未被去能,則順序執(zhí)行irq_desc.action中的各個(gè)處理程序;結(jié)束中斷(eoi操作)。由于未屏蔽正在處理的中斷,因而新到達(dá)的中斷會(huì)被遞交給其它處理器并立刻處理。PERCPU類(lèi)的中斷處理允許并行。⑤

FASTEOI類(lèi)的處理流程。累計(jì)與該中斷相關(guān)的統(tǒng)計(jì)信息;如果該中斷被去能,則屏蔽該中斷,否則順序執(zhí)行irq_desc.action中的各個(gè)處理程序;結(jié)束中斷(eoi操作)。如果在處理過(guò)程中有新到達(dá)的嵌套中斷,則應(yīng)答但不處理它們(丟棄),如果需要,此時(shí)也可遷移中斷。

(5)遞減當(dāng)前進(jìn)程的硬中斷計(jì)數(shù)(在thread_info.preempt_count中),表示已退出中斷處理程序。

(6)如果有激活的軟中斷,則處理軟中斷。

屏蔽和解除正在處理中斷的方法是設(shè)置I/OAPIC的重定向表,應(yīng)答中斷的方法是向LocalAPIC的EOI寄存器寫(xiě)0。程序ret_from_intr是外部中斷處理的總出口,完成外部中斷處理的善后工作,如進(jìn)程調(diào)度、信號(hào)處理、中斷返回等。善后處理的流程如圖4.3所示。

2.局部中斷硬處理

局部中斷是外部中斷,它來(lái)自自身的LocalAPIC而不是處理器內(nèi)部。局部中斷是異步的。處理器收到局部中斷后,首先完成堆棧切換和狀態(tài)保存(在棧頂),而后查IDT表獲得局部中斷的中斷門(mén),并經(jīng)過(guò)該中斷門(mén)進(jìn)入入口程序。局部中斷的入口程序完成大致相同的三件工作:一是在棧頂壓入中斷向量號(hào)、段寄存器與通用寄存器的值,構(gòu)造出一個(gè)pt_regs結(jié)構(gòu);二是調(diào)用真正的局部中斷處理程序,完成局部中斷處理;三是跳轉(zhuǎn)到ret_from_intr完成中斷的善后處理工作。與設(shè)備中斷不同,局部中斷的意義是明確的,其處理程序是固定的,且是由操作系統(tǒng)內(nèi)核提供的,在系統(tǒng)運(yùn)行過(guò)程中無(wú)法改變局部中斷的處理程序。Intel處理器已為各局部中斷規(guī)定了明確的意義。除偽中斷之外,其它局部中斷都需要統(tǒng)計(jì)與應(yīng)答。

(1)偽中斷(spurious_interrupt)。偽中斷表示LocalAPIC之前遞交的中斷被中途屏蔽或撤銷(xiāo),無(wú)法完成正常的遞交手續(xù)。偽中斷不需要特別處理,僅需要累計(jì)一下統(tǒng)計(jì)信息即可。但如果某個(gè)外部中斷錯(cuò)誤地遞交了偽中斷向量號(hào),則需要應(yīng)答一下,以便LocalAPIC將其清除。

(2)錯(cuò)誤中斷(error_interrupt)。錯(cuò)誤中斷表示LocalAPIC檢測(cè)到了某種錯(cuò)誤條件,如非法向量號(hào)、非法寄存器地址、檢驗(yàn)和錯(cuò)、發(fā)送錯(cuò)、接收錯(cuò)等。LocalAPIC將錯(cuò)誤原因記錄在它的錯(cuò)誤狀態(tài)寄存器(ESR)中。錯(cuò)誤中斷不需要特別處理。

(3)溫度傳感器中斷(thermal_interrupt)。溫度傳感器中斷表示處理器核心的溫度超過(guò)了預(yù)設(shè)的門(mén)檻。處理器硬件會(huì)自動(dòng)處理溫度異常,如降低處理器的主頻等。操作系統(tǒng)不需要對(duì)該中斷做過(guò)多的處理,僅需要做一些必要的統(tǒng)計(jì)即可。

(4)校正的機(jī)器檢查錯(cuò)誤中斷(threshold_interrupt)。在處理器運(yùn)行過(guò)程中,可能會(huì)出現(xiàn)一些錯(cuò)誤,其中有些錯(cuò)誤是可以自動(dòng)校正的。當(dāng)被校正的錯(cuò)誤數(shù)達(dá)到預(yù)設(shè)的門(mén)檻后,LocalAPIC會(huì)產(chǎn)生該中斷。操作系統(tǒng)可以做一些統(tǒng)計(jì),也可以做一些特別的處理,如清除錯(cuò)誤狀態(tài)等。

(5)局部定時(shí)器中斷(apic_timer_interrupt)。LocalAPIC中的局部定時(shí)器是一種常用的時(shí)鐘設(shè)備,用于代替?zhèn)鹘y(tǒng)的PIT,完成常規(guī)的時(shí)鐘中斷處理,見(jiàn)5.2節(jié)。

(6)性能監(jiān)控器中斷(perf_pending_interrupt)。Intel處理器提供了若干組計(jì)數(shù)器用于監(jiān)測(cè)系統(tǒng)的性能,如過(guò)去的時(shí)鐘周期數(shù)、執(zhí)行的指令數(shù)、訪問(wèn)Cache的次數(shù)等。當(dāng)這些計(jì)數(shù)器溢出時(shí),LocalAPIC會(huì)產(chǎn)生性能監(jiān)控器中斷。收到該中斷時(shí),操作系統(tǒng)可以完成一些預(yù)定但未完成的工作。

3.處理器間中斷硬處理

處理器間中斷(IPI)是一個(gè)處理器通過(guò)自己的LocalAPIC向其它處理器(包括自己)發(fā)出的中斷。在Intel處理器上,發(fā)送IPI的方法是向LocalAPIC的中斷命令寄存器(ICR)寫(xiě)入一個(gè)64位的IPI消息,消息中包括中斷向量號(hào)、遞交模式、觸發(fā)模式、目的處理器等。一個(gè)處理器可以向系統(tǒng)中另一個(gè)處理器(包括自己)發(fā)送IPI、可以向多個(gè)處理器同時(shí)發(fā)送IPI、也可以向所有的處理器廣播IPI。顯然,IPI僅是一種通知機(jī)制,難以傳遞更多的信息,IPI的意義是收發(fā)雙方自己約定的。

Linux預(yù)定了幾個(gè)處理器間中斷,為它們指派了中斷向量號(hào)、設(shè)定了處理程序,且已在IDT表中創(chuàng)建了相應(yīng)的中斷門(mén)。處理器間中斷的處理流程與局部中斷相似。所有的處理器間中斷都需要統(tǒng)計(jì)、應(yīng)答。

(1)重調(diào)度IPI(reschedule_interrupt)。當(dāng)處理器收到重調(diào)度IPI時(shí),它應(yīng)該立刻進(jìn)行調(diào)度。重調(diào)度工作會(huì)在中斷的善后處理程序ret_from_intr中自動(dòng)完成,因而在中斷處理程序中僅需要應(yīng)答、統(tǒng)計(jì)、返回即可。

(2)單函數(shù)調(diào)用IPI(call_function_single_interrupt)。當(dāng)一個(gè)處理器需要另一個(gè)處理器執(zhí)行某個(gè)函數(shù)時(shí),它可以向該處理器發(fā)送一個(gè)單函數(shù)調(diào)用IPI。當(dāng)然,在此之前,需要通過(guò)另外的機(jī)制告訴對(duì)方函數(shù)名及參數(shù)。在SMP系統(tǒng)中,由于內(nèi)存是共享的,所以可以將欲調(diào)用的函數(shù)名及參數(shù)做成一個(gè)結(jié)構(gòu),預(yù)先掛在接收者的特定隊(duì)列中。事實(shí)上,Linux在PERCPU數(shù)據(jù)區(qū)中為每個(gè)處理器準(zhǔn)備了一個(gè)名為cfd_data的結(jié)構(gòu)和一個(gè)名為call_single_queue的隊(duì)列。請(qǐng)求者填寫(xiě)自己的cfd_data結(jié)構(gòu)并將其掛在接收者的call_single_queue的隊(duì)列中。目的處理器收到單函數(shù)調(diào)用IPI后,順序執(zhí)行自己call_single_queue隊(duì)列中的各函數(shù)即可。

(3)函數(shù)調(diào)用IPI(call_function_interrupt)。當(dāng)一個(gè)處理器需要多個(gè)處理器執(zhí)行某個(gè)函數(shù)時(shí),它可以向這些處理器發(fā)送函數(shù)調(diào)用IPI。當(dāng)然,在此之前,需要通過(guò)另外的機(jī)制告訴接收者函數(shù)名及參數(shù)。在SMP系統(tǒng)中,Linux在PERCPU數(shù)據(jù)區(qū)中為每個(gè)處理器準(zhǔn)備了一個(gè)cfd_data結(jié)構(gòu),請(qǐng)求者根據(jù)欲調(diào)用的函數(shù)名、參數(shù)及應(yīng)執(zhí)行該函數(shù)的處理器位圖信息設(shè)置好自己的cfd_data結(jié)構(gòu),并將其掛在全局隊(duì)列call_function中。收到函數(shù)調(diào)用IPI的處理器順序搜索call_function隊(duì)列,若發(fā)現(xiàn)自己在某個(gè)cfd_data結(jié)構(gòu)的處理器位圖中,則執(zhí)行一次其中的函數(shù)。最后一個(gè)執(zhí)行的處理器將cfd_data結(jié)構(gòu)從隊(duì)列中摘下。

(4)停止IPI(reboot_interrupt)。在關(guān)閉整個(gè)計(jì)算機(jī)系統(tǒng)之前,應(yīng)該向所有在線的處理器發(fā)送停止IPI。收到停止IPI的處理器先把自己設(shè)為離線狀態(tài),再關(guān)閉自己的LocalAPIC(清除LVT表、屏蔽所有局部中斷并將其去能),最后執(zhí)行指令HLT進(jìn)入停止?fàn)顟B(tài)。發(fā)送停止IPI的處理器也要關(guān)閉自己的LocalAPIC。

(5)

TLB刷新IPI(invalidate_interrupt)。在單處理器系統(tǒng)中,如處理器改變了頁(yè)表或頁(yè)目錄項(xiàng),它需要刷新自己的TLB,使已改變的頁(yè)表或頁(yè)目錄項(xiàng)在TLB中失效。在多處理器系統(tǒng)中,由于每個(gè)處理器都有自己的TLB,一個(gè)頁(yè)表或頁(yè)目錄項(xiàng)可能被緩存在多個(gè)TLB中,因而當(dāng)一個(gè)處理器改變了頁(yè)表或頁(yè)目錄項(xiàng)后,除了要刷新自己的TLB外,還要設(shè)法通知其它處理器,讓它們也刷新自己的TLB,這一過(guò)程稱(chēng)為T(mén)LB擊落。通知的方法是TLB刷新IPI。當(dāng)然,不是每一次改變頁(yè)表或頁(yè)目錄項(xiàng)都需要所有的處理器刷新TLB,事實(shí)上,只有正在使用同一個(gè)頁(yè)目錄的處理器才需要刷新TLB。在初始化時(shí),Linux定義了8個(gè)TLB刷新IPI(用于區(qū)分不同的請(qǐng)求者),并在IDT中設(shè)置了它們的處理程序。當(dāng)一個(gè)處理器需要其它處理器刷新TLB時(shí),它在預(yù)定位置設(shè)置刷新要求,如需要刷新的內(nèi)存環(huán)境(即結(jié)構(gòu)mm_struct)及刷新方式(全部刷新還是僅刷新一項(xiàng))等,而后向可能受影響的處理器發(fā)送TLB刷新IPI。收到TLB刷新IPI的處理器根據(jù)請(qǐng)求者的要求決定是否刷新和如何刷新自己的TLB。

(6)平臺(tái)特定IPI(generic_interrupt)。平臺(tái)特定IPI一般不需要特別的處理。事實(shí)上目前只有SGI的RTC需要在平臺(tái)特定IPI中進(jìn)行一些特定的處理。

(7)機(jī)器檢查自中斷(mce_self_interrupt)。在處理機(jī)器檢查異常時(shí),如果發(fā)現(xiàn)異常的后果比較嚴(yán)重,機(jī)器檢查異常處理程序會(huì)向自己發(fā)送一個(gè)機(jī)器檢查自中斷,請(qǐng)求系統(tǒng)在合適的時(shí)候(至少是在開(kāi)中斷狀態(tài)下)完成某些通知性工作。4.3.4外部中斷軟處理

如前所述,為了縮短關(guān)中斷的時(shí)間,外部中斷的硬處理部分通常被設(shè)計(jì)得盡可能短小,但常常無(wú)法完成所有的中斷處理工作,因而必須提供另外一種機(jī)制來(lái)完成外部中斷的剩余處理工作。在硬處理中未完成的工作必須是與硬件無(wú)關(guān)、對(duì)時(shí)間不敏感而且可以在開(kāi)中斷狀態(tài)下處理的工作,對(duì)這部分工作的處理稱(chēng)為外部中斷的軟處理。

將外部中斷處理分成兩部分還帶來(lái)了另外的好處。由于軟處理部分與硬件無(wú)關(guān),因而多個(gè)硬處理程序可以共用一個(gè)軟處理程序,從而可以極大地壓縮軟處理程序的數(shù)量,甚至可以由操作系統(tǒng)內(nèi)核統(tǒng)一提供所有的軟處理程序,提高了外部中斷處理的可靠性。事實(shí)上,目前的Linux系統(tǒng)僅提供了有限幾個(gè)軟處理程序。另外,軟處理程序可以將硬處理程序轉(zhuǎn)交過(guò)來(lái)的工作進(jìn)行分類(lèi)、合并,在更合適的時(shí)候(不是中斷時(shí))一次性完成處理工作,從而可減少處理次數(shù),加快處理速度(一種典型的Lazy策略)。

在早期的版本中,Linux將軟處理稱(chēng)為底半處理(bottomhalf)。底半處理的管理結(jié)構(gòu)比較簡(jiǎn)單,包括一個(gè)向量表bh_base和兩個(gè)位圖bh_mask和bh_active,其中bh_base用于注冊(cè)底半處理程序,bh_mask用于記錄已注冊(cè)的底半處理,bh_active用于記錄已激活的底半處理。bh_mask&bh_active中的非0位就是目前待處理的底半操作。

Linux定義的底半處理程序不超過(guò)32個(gè)。為了簡(jiǎn)化設(shè)計(jì),Linux按嚴(yán)格串行的方式執(zhí)行底半處理程序,也就是說(shuō)底半處理程序不許重入。不管系統(tǒng)中有多少個(gè)處理器,在同一時(shí)間內(nèi)最多只允許一個(gè)處理器執(zhí)行底半處理程序。這一約定簡(jiǎn)化了底半處理程序的設(shè)計(jì),但也帶來(lái)了嚴(yán)重的性能損失。事實(shí)上,在不同的處理器上同時(shí)執(zhí)行不同的底半處理程序并不會(huì)帶來(lái)干擾,因而應(yīng)該是允許的。

Linux2.4放寬了對(duì)底半處理的限制,引入了軟中斷(Softirq)。軟中斷類(lèi)似于底半處理,但不同的軟中斷處理程序可以同時(shí)在不同的處理器上運(yùn)行,同一個(gè)軟中斷處理程序也可以在不同的處理器上同時(shí)運(yùn)行。在同一時(shí)刻,一個(gè)處理器上只能運(yùn)行一個(gè)軟中斷處理程序(不能嵌套)。軟中斷處理程序運(yùn)行在中斷上下文中,不能睡眠,不能調(diào)度,而且一個(gè)軟中斷不能搶占另一個(gè)軟中斷。

由于軟中斷處理程序可能并行運(yùn)行,因而其設(shè)計(jì)比較困難,其內(nèi)部要么全部使用PERCPU變量,要么使用自旋鎖以保護(hù)關(guān)鍵性資源。為了保證系統(tǒng)的可靠性,Linux不允許用戶(hù)自行設(shè)計(jì)軟中斷處理程序。在目前的Linux中,所有的軟中斷處理程序都是由內(nèi)核提供的,在系統(tǒng)初始化時(shí)注冊(cè),且不允許更改。因而軟中斷穩(wěn)重有余、靈活不足。如果某種軟處理并不需要在多個(gè)處理器上并行執(zhí)行,那么軟中斷的限制其實(shí)有些過(guò)分。為了彌補(bǔ)軟中斷的缺陷,需要提供更靈活的軟處理手段,為此Linux在軟中斷的基礎(chǔ)上又引入了任務(wù)片(tasklet)機(jī)制。毫無(wú)疑問(wèn),任務(wù)片是延遲的軟處理工作,但又與軟中斷有區(qū)別。一種特定類(lèi)型的任務(wù)片只能運(yùn)行在一個(gè)處理器上,不能并行,只能串行執(zhí)行;不同類(lèi)型的任務(wù)片可以在多個(gè)處理器上并行運(yùn)行;任務(wù)片允許動(dòng)態(tài)地注冊(cè)、注銷(xiāo)。傳統(tǒng)的底半處理是特殊的任務(wù)片,任務(wù)片又是特殊的軟中斷。在Linux實(shí)現(xiàn)的軟中斷中,HI_SOFTIRQ和TASKLET_SOFTIRQ是專(zhuān)門(mén)用來(lái)處理任務(wù)片的。

1.軟中斷處理

軟處理的核心是軟中斷,其數(shù)量已從最初的4個(gè)發(fā)展到了目前的10個(gè)。各軟中斷的向量號(hào)、標(biāo)識(shí)、意義及處理程序的設(shè)置如表4.3所示。

表4.3軟中斷及其處理程序軟中斷的管理結(jié)構(gòu)類(lèi)似于底半處理,需要為其定義一個(gè)數(shù)組以記錄各軟中斷的處理程序,并需要定義一個(gè)位圖以記錄各軟中斷的請(qǐng)求或激活情況。由于軟中斷不允許注銷(xiāo),也不允許屏蔽,因而不再需要軟中斷屏蔽位圖。全局向量表softirq_vec[]用于記錄各軟中斷的處理程序,其定義如下:

structsoftirq_action{

void (*action)(structsoftirq_action*);

};

structsoftirq_actionsoftirq_vec[NR_SOFTIRQS]_

_cacheline_aligned_in_smp;系統(tǒng)初始化時(shí),已為各軟中斷注冊(cè)了處理程序。表4.3列出了各軟中斷的向量號(hào)、標(biāo)識(shí)及處理程序。

在底半處理中,激活位圖bh_active是全局共用的。由于軟中斷可以在多個(gè)處理器上并行運(yùn)行,一個(gè)全局共用的激活位圖已無(wú)法表示軟中斷在不同處理器上的激活情況,因而需要為每個(gè)處理器定義一個(gè)軟中斷激活位圖。事實(shí)上,Linux在PERCPU數(shù)據(jù)區(qū)中為每個(gè)處理器定義了一個(gè)irq_cpustat_t類(lèi)型的結(jié)構(gòu)變量irq_stat,用于統(tǒng)計(jì)在處理器上發(fā)生的各種中斷的次數(shù),其中的_

_softirq_pending就是軟中斷激活位圖。

typedefstruct{

unsignedint_

_softirq_pending; //軟中斷激活位圖

unsignedint_

_nmi_count; //不可屏蔽中斷發(fā)生次數(shù)

unsignedintirq0_irqs; //時(shí)鐘中斷發(fā)生次數(shù)

unsignedintapic_timer_irqs; //APIC局部定時(shí)器中斷發(fā)生次數(shù)

unsignedintirq_spurious_count; //APIC偽中斷發(fā)生次數(shù)

unsignedintgeneric_irqs; //平臺(tái)特定IPI發(fā)生次數(shù)

unsignedintapic_perf_irqs; //NMI中斷發(fā)生次數(shù)

unsignedintapic_pending_irqs; //APIC性能監(jiān)控器中斷發(fā)生次數(shù)

unsignedintirq_resched_count; //重調(diào)度IPI發(fā)生次數(shù)

unsignedintirq_call_count; //函數(shù)調(diào)用IPI發(fā)生次數(shù)

unsignedintirq_tlb_count; //TLB刷新IPI發(fā)生次數(shù)

unsignedintirq_thermal_count; //溫度傳感器中斷發(fā)生次數(shù)

unsignedintirq_threshold_count; //校正的機(jī)器檢查錯(cuò)誤中斷發(fā)生次數(shù)

}_cacheline_alignedirq_cpustat_t;

每個(gè)軟中斷都有一個(gè)編號(hào),該編號(hào)對(duì)應(yīng)_

_softirq_pending中的1位。激活某個(gè)處理器的某個(gè)軟中斷就是在該處理器的_

_softirq_pending位圖的相應(yīng)位上置1。有意思的是硬處理程序通常并不直接激活軟中斷,硬處理延遲的工作一般由任務(wù)片處理,注冊(cè)任務(wù)片時(shí)會(huì)激活軟中斷HI_SOFTIRQ或TASKLET_SOFTIRQ。

一個(gè)處理器上的軟中斷必須由該處理器自己處理。軟中斷可能被掛起,因而不像異常和外部中斷那樣及時(shí)。在下列情況下處理器會(huì)試圖處理已激活的軟中斷:

(1)設(shè)備硬處理部分執(zhí)行完畢之后。

(2)局部中斷處理完畢之后。

(3)處理器間中斷(包含TLB刷新和重調(diào)度IPI)處理完畢之后。

(4)中斷遷移清理工作完成之后。

(5)打開(kāi)軟中斷(local_bh_enable)之時(shí)。

(6)守護(hù)進(jìn)程ksoftirqd運(yùn)行時(shí)。

處理器進(jìn)行軟中斷處理的唯一限制條件是它當(dāng)前不在中斷處理程序之中,包括硬處理和軟處理。這一限制保證了軟中斷處理程序不會(huì)在同一處理器上重入,同時(shí)也使軟中斷可能被推遲處理。為了標(biāo)識(shí)處理器當(dāng)前是否在中斷處理程序之中,Linux在所有進(jìn)程的系統(tǒng)堆棧的棧頂都預(yù)留了一個(gè)thread_info結(jié)構(gòu),其中的域preempt_count是一個(gè)32位的整型變量,格式如圖4.8所示。圖4.8preempt_count域的格式在進(jìn)入外部中斷的硬處理程序(包括設(shè)備中斷、局部中斷、處理器間中斷的硬處理程序)之前,處理器增加當(dāng)前進(jìn)程的preempt_count中的硬處理計(jì)數(shù);在退出外部中斷的硬處理程序之前,處理器減少當(dāng)前進(jìn)程的preempt_count中的硬處理計(jì)數(shù)。

在進(jìn)入NMI處理程序之前,處理器增加當(dāng)前進(jìn)程的preempt_count中的硬處理計(jì)數(shù)并設(shè)置NMI_MASK標(biāo)志;在退出NMI處理程序之前,處理器減少當(dāng)前進(jìn)程的preempt_count中的硬處理計(jì)數(shù)并清除NMI_MASK標(biāo)志。硬件可以保證NMI不會(huì)嵌套。在進(jìn)入外部中斷的軟處理程序之前,處理器增加當(dāng)前進(jìn)程的preempt_count中的軟處理計(jì)數(shù);在退出外部中斷的軟處理程序之前,處理器減少當(dāng)前進(jìn)程的preempt_count中的軟處理計(jì)數(shù)。

域preempt_count的另一個(gè)作用是控制進(jìn)程調(diào)度的時(shí)機(jī)。不為0的preempt_count表示處理器正處于一個(gè)不安全的環(huán)境中,不應(yīng)該強(qiáng)行進(jìn)行搶占調(diào)度。因而,若preempt_count的當(dāng)前值不是0,在返回核心態(tài)之前,外部中斷的善后處理程序ret_from_intr就不會(huì)調(diào)度進(jìn)程(如圖4.3所示)。

標(biāo)志PREEMPT_ACTIVE表示進(jìn)程正在被搶占。由此可見(jiàn),當(dāng)前進(jìn)程的preempt_count中的硬處理計(jì)數(shù)、軟處理計(jì)數(shù)和NMI_MASK同時(shí)為0就表示處理器當(dāng)前不在中斷處理程序之中。

軟中斷處理程序與硬處理程序一樣,可能被隨機(jī)地插入在任何程序的執(zhí)行過(guò)程中。如果某段程序不能被硬中斷,則可用函數(shù)local_irq_disable(指令CLI)和local_irq_enable(指令STI)將其括起來(lái);如果某段程序可以被硬中斷但不能被軟中斷(該段程序的執(zhí)行過(guò)程中不允許插入軟中斷處理程序),則可用軟中斷關(guān)閉函數(shù)local_bh_disable(增加preempt_count中的軟處理計(jì)數(shù))和軟中斷打開(kāi)函數(shù)local_bh_enable(減少preempt_count中的軟處理計(jì)數(shù))將其括起來(lái)。特別地,如函數(shù)local_bh_enable發(fā)現(xiàn)處理器已不在中斷處理程序之中且處理器上已有軟中斷被激活,也應(yīng)該嘗試處理軟中斷。

軟中斷處理的大致流程如下:

(1)增加preempt_count中的軟處理計(jì)數(shù),表示即將進(jìn)入軟中斷處理流程。

(2)取出處理器的軟中斷激活位圖_softirq_pending,并將_softirq_pending清空。

(3)打開(kāi)中斷(STI)。允許軟中斷處理過(guò)程被再次中斷。

(4)檢查取出的軟中斷激活位圖,如果其中的第i位為1,說(shuō)明第i個(gè)軟中斷被激活,則應(yīng)執(zhí)行一次softirq_vec[i]中的處理程序action()。所有被激活的軟中斷處理程序都應(yīng)被執(zhí)行一次。

(5)軟中斷處理完畢,關(guān)閉中斷(CLI)。

(6)如果位圖_

_softirq_pending不空,說(shuō)明軟中斷處理過(guò)程被中斷過(guò),新的硬處理程序再次激活了當(dāng)前處理器的軟中斷,且新激活的軟中斷還未被處理,應(yīng)轉(zhuǎn)(2)再次處理軟中斷。

這種循環(huán)可能反復(fù)進(jìn)行,其間會(huì)禁止進(jìn)程調(diào)度,有可能導(dǎo)致其它進(jìn)程進(jìn)入饑餓狀態(tài)。為了解決這一問(wèn)題,Linux為每個(gè)處理器預(yù)建了一個(gè)軟中斷守護(hù)線程ksoftirqd。當(dāng)發(fā)現(xiàn)此處的循環(huán)過(guò)多時(shí),喚醒ksoftirqd,而后退出。

(7)減少preempt_count中的軟處理計(jì)數(shù),表示已退出軟中斷處理流程。

守護(hù)線程ksoftirqd專(zhuān)門(mén)為處理器處理軟中斷,其處理流程與上述相同,不同的是ksoftirqd會(huì)不斷地嘗試調(diào)度,從而給其它進(jìn)程以運(yùn)行的機(jī)會(huì)。

在激活軟中斷時(shí),如果當(dāng)前處理器正在中斷處理程序之中,ksoftirqd也會(huì)被喚醒。由軟中斷的處理流程可以看出,軟中斷處理可能被推遲,但最終都會(huì)被處理。

2.任務(wù)片處理

軟中斷是軟處理的基礎(chǔ),但不夠靈活。軟處理的靈活性是由任務(wù)片提供的。事實(shí)上,大部分硬處理程序都通過(guò)任務(wù)片來(lái)延遲處理自己未完成的工作。一個(gè)任務(wù)片由一個(gè)tasklet_struct結(jié)構(gòu)描述,其定義如下:

structtasklet_struct{

structtasklet_struct *next;

unsignedlong state; //狀態(tài)(正等待執(zhí)行、正在執(zhí)行)

atomic_t count; //引用計(jì)數(shù)

void(*func)(unsignedlong); //軟處理函數(shù)

unsignedlong data; //給軟處理函數(shù)的參數(shù)

};

Linux在PERCPU數(shù)據(jù)區(qū)中為每個(gè)處理器提供了兩個(gè)任務(wù)片隊(duì)列,tasklet_hi_vec中任務(wù)片的優(yōu)先級(jí)高于tasklet_vec中的任務(wù)片。在系統(tǒng)初始化時(shí),已將tasklet_hi_vec和tasklet_vec設(shè)成了空隊(duì)列。

如果硬處理程序認(rèn)為自己的某項(xiàng)工作應(yīng)該推遲到軟處理中,它可以將要推遲的工作包裝成任務(wù)片,而后調(diào)用函數(shù)tasklet_hi_schedule或tasklet_schedule將其調(diào)度到上述某個(gè)隊(duì)列中。函數(shù)tasklet_hi_schedule將任務(wù)片調(diào)度到隊(duì)列tasklet_hi_vec中并激活軟中斷HI_SOFTIRQ,函數(shù)tasklet_schedule將任務(wù)片調(diào)度到隊(duì)列tasklet_vec中并激活軟中斷TASKLET_SOFTIRQ。一個(gè)任務(wù)片只能被掛在一個(gè)隊(duì)列中。當(dāng)系統(tǒng)處理軟中斷時(shí),HI_SOFTIRQ的處理程序會(huì)將隊(duì)列tasklet_hi_vec中的任務(wù)片摘下,將隊(duì)列清空并順序執(zhí)行其中各任務(wù)片中的處理函數(shù)func;TASKLET_SOFTIRQ的處理程序會(huì)將隊(duì)列tasklet_vec中的各任務(wù)片摘下,將隊(duì)列清空并順序執(zhí)行其中各任務(wù)片中的處理函數(shù)func。由于一個(gè)任務(wù)片結(jié)構(gòu)僅能掛在一個(gè)隊(duì)列中,因而它不可能在多個(gè)處理器上并行運(yùn)行。雖然任務(wù)片處理過(guò)程可能被中斷,任務(wù)片結(jié)構(gòu)可能被再次調(diào)度到某個(gè)隊(duì)列中,但軟中斷機(jī)制可以保證新調(diào)度的任務(wù)片只能在下一輪軟中斷處理中執(zhí)行,任務(wù)片中的處理程序不會(huì)重入。

出于保護(hù)的目的,Linux將進(jìn)程的地址空間分成了兩部分,用戶(hù)空間和內(nèi)核空間。操作系統(tǒng)內(nèi)核運(yùn)行在內(nèi)核空間,用戶(hù)程序運(yùn)行在用戶(hù)空間。當(dāng)進(jìn)程運(yùn)行在用戶(hù)空間時(shí),其權(quán)限和能力被限制,很多工作,如創(chuàng)建子進(jìn)程、讀寫(xiě)文件等,無(wú)法自己完成,只能請(qǐng)求內(nèi)核的幫助。4.4系統(tǒng)調(diào)用然而,由于保護(hù)級(jí)別的不同,運(yùn)行在用戶(hù)空間的程序不能直接訪問(wèn)內(nèi)核中的數(shù)據(jù),也不能直接調(diào)用內(nèi)核中的函數(shù)。因此,操作系統(tǒng)內(nèi)核需要提供一種機(jī)制,既能保護(hù)內(nèi)核信息,又能為用戶(hù)進(jìn)程提供必須的服務(wù)。這種機(jī)制就是系統(tǒng)調(diào)用。

系統(tǒng)調(diào)用是內(nèi)核空間與用戶(hù)空間之間的一道門(mén)戶(hù),通過(guò)這道門(mén)戶(hù)用戶(hù)進(jìn)程可有限度地進(jìn)出內(nèi)核空間,如圖4.9所示。

圖4.9用戶(hù)空間、內(nèi)核空間與系統(tǒng)調(diào)用實(shí)現(xiàn)系統(tǒng)調(diào)用的方法有多種,最常用的是一個(gè)特殊的中斷,即陷入(int指令),其中斷向量號(hào)是$0x80。因此,系統(tǒng)調(diào)用的處理過(guò)程也就是int$0x80的處理過(guò)程。

與老版本相比,系統(tǒng)調(diào)用的處理流程并沒(méi)有太大改變。4.4.1系統(tǒng)調(diào)用表

Linux的系統(tǒng)調(diào)用是預(yù)先定義好的,每一個(gè)系統(tǒng)調(diào)用都有一個(gè)編號(hào),在內(nèi)核中有一個(gè)對(duì)應(yīng)的服務(wù)函數(shù)。在Linux的發(fā)展過(guò)程中,它提供的系統(tǒng)調(diào)用也在不斷變化。從Linux1.0的135個(gè)系統(tǒng)調(diào)用發(fā)展到Linux3.0的347個(gè)系統(tǒng)調(diào)用。

Linux的系統(tǒng)調(diào)用表稱(chēng)為sys_call_table,其中包含各個(gè)系統(tǒng)調(diào)用的編號(hào)及其服務(wù)函數(shù)。表4.4列出了其中前16個(gè)系統(tǒng)調(diào)用。

表4.4Linux的系統(tǒng)調(diào)用表每一個(gè)系統(tǒng)調(diào)用都有一個(gè)唯一的系統(tǒng)調(diào)用號(hào),或者說(shuō)一個(gè)系統(tǒng)調(diào)用號(hào)唯一地標(biāo)識(shí)了一個(gè)系統(tǒng)調(diào)用。服務(wù)函數(shù)sys_xxx對(duì)應(yīng)的系統(tǒng)調(diào)用號(hào)由宏_

_NR_xxx定義,如sys_exit對(duì)應(yīng)的系統(tǒng)調(diào)用號(hào)是1,其宏為_(kāi)

_NR_exit。

Linux系統(tǒng)調(diào)用的服務(wù)函數(shù)有著統(tǒng)一的命名方法,即sys_xxx。格式為ptregs_xxx的函數(shù)實(shí)際是對(duì)格式為sys_xxx的函數(shù)的包裝,它先將系統(tǒng)棧頂?shù)膒t_regs結(jié)構(gòu)的位置(指向pt_regs的指針)保存在EAX寄存器中,作為傳遞給函數(shù)sys_xxx的唯一參數(shù),而后再跳轉(zhuǎn)到sys_xxx的入口。為了保持兼容性,老的系統(tǒng)調(diào)用號(hào)總是被保留,即使與之對(duì)應(yīng)的系統(tǒng)調(diào)用已經(jīng)被廢棄。新增的系統(tǒng)調(diào)用總是使用新的系統(tǒng)調(diào)用號(hào)和新的名稱(chēng)。如果一個(gè)系統(tǒng)調(diào)用被做了較大修改,改進(jìn)后的系統(tǒng)調(diào)用會(huì)使用一個(gè)新的系統(tǒng)調(diào)用號(hào),其名稱(chēng)要么使用新的,要么使用老的。如果改進(jìn)后的系統(tǒng)調(diào)用使用老的名稱(chēng),如sys_xxx,那么原來(lái)的系統(tǒng)調(diào)用名會(huì)被改為sys_oldxxx、sys_old_xxx或old_xxx。如有關(guān)uname的系統(tǒng)調(diào)用有三個(gè),其服務(wù)函數(shù)分別名為sys_olduname、sys_uname和sys_newuname。4.4.2標(biāo)準(zhǔn)函數(shù)庫(kù)

系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核提供給應(yīng)用程序的服務(wù)接口。應(yīng)用程序可直接將要請(qǐng)求的系統(tǒng)調(diào)用號(hào)放在EAX中,而后執(zhí)行int$0x80進(jìn)入內(nèi)核空間請(qǐng)求內(nèi)核服務(wù)。但這種方式比較麻煩,也不可靠,不大適合應(yīng)用程序直接使用。事實(shí)上,Linux已將系統(tǒng)調(diào)用封裝在標(biāo)準(zhǔn)的C函數(shù)庫(kù)中,如Libc或Glibc中,應(yīng)用程序只需通過(guò)這些庫(kù)使用系統(tǒng)調(diào)用即可。標(biāo)準(zhǔn)C庫(kù)是對(duì)系統(tǒng)調(diào)用的加強(qiáng),它為進(jìn)入內(nèi)核空間做必要的前期準(zhǔn)備和善后處理,如檢查參數(shù)的合法性、轉(zhuǎn)換參數(shù)的格式、將參數(shù)按序放入通用寄存器中、執(zhí)行int$0x80進(jìn)入內(nèi)核、檢查返回值等。下面是標(biāo)準(zhǔn)C庫(kù)中的一個(gè)宏定義。

#defineINTERNAL_SYSCALL(name,err,nr,args...)({ \

registerunsignedintresultvar; \

asmvolatile( \

"movl%1,%%eax\n\t" \

"int$0x80\n\t" \

:"=a"(resultvar) \

:"i"(__NR_##name)ASMFMT_##nr(args) \

:"memory","cc");

\

(int)resultvar;})

上述宏定義中的name是系統(tǒng)服務(wù)的名稱(chēng),如fork、open等,nr是從用戶(hù)空間傳遞到內(nèi)核空間的參數(shù)個(gè)數(shù),args是要傳遞的參數(shù)。 由于指令int$0x80會(huì)引起堆棧切換,無(wú)法通過(guò)堆棧傳遞參數(shù),因而應(yīng)用程序只能將參數(shù)放在寄存器中以便將其傳遞到內(nèi)核。按照Linux的約定,一次最多能向內(nèi)核傳遞6個(gè)參數(shù),它們被分別放在EBX、ECX、EDX、ESI、EDI、EBP中,構(gòu)成了結(jié)構(gòu)pt_regs的前6個(gè)域。宏ASMFMT_##nr(args)用于為系統(tǒng)調(diào)用準(zhǔn)備參數(shù),其定義如下:

#defineASMFMT_1(arg1)\

,"b"(arg1)

#defineASMFMT_2(arg1,arg2)\

,"b"(arg1),"c"(arg2)

#defineASMFMT_3(arg1,arg2,arg3)\

,"b"(arg1),"c"(arg2),"d"(arg3)

#defineASMFMT_4(arg1,arg2,arg3,arg4)\

,"b"(arg1),"c"(arg2),"d"(arg3),"S"(arg4)

#defineASMFMT_5(arg1,arg2,arg3,arg4,arg5)\

,"b"(arg1),"c"(arg2),"d"(arg3),"S"(arg4),"D"(arg5)

值得注意的是,上述傳遞參數(shù)的方法與C語(yǔ)言的約定不同,它是Linux自己的約定,并由Linux自己實(shí)現(xiàn),僅適用于系統(tǒng)調(diào)用(由匯編程序到匯編程序)。在缺省情況下,標(biāo)準(zhǔn)C語(yǔ)言也用寄存器傳遞參數(shù),但傳遞的方法由編譯器自己掌握,如GCC用EAX、EDX、ECX分別傳遞第一、二、三個(gè)參數(shù)(其余的參數(shù)用堆棧傳遞)。如果一個(gè)C語(yǔ)言函數(shù)想用堆棧接收參數(shù),它必須特別聲明,如加上asmlinkage標(biāo)志。大多數(shù)的系統(tǒng)調(diào)用服務(wù)程序都通過(guò)堆棧接收參數(shù)。宏INTERNAL_SYSCALL是標(biāo)準(zhǔn)函數(shù)庫(kù)的核心,需要進(jìn)入內(nèi)核的庫(kù)函數(shù)都會(huì)使用該宏。宏INTERNAL_SYSCALL的主體部分是一段嵌入式匯編,該段匯編程序?qū)⑾到y(tǒng)調(diào)用號(hào)放在EAX寄存器中,將其它的參數(shù)分別放在EBX、ECX、EDX、ESI、EDI中,而后執(zhí)行指令int$0x80進(jìn)入內(nèi)核。當(dāng)指令int$0x80返回時(shí),說(shuō)明請(qǐng)求的系統(tǒng)調(diào)用已經(jīng)完成,系統(tǒng)已從內(nèi)核空間返回到了用戶(hù)空間,此時(shí)EAX寄存器中保存的是系統(tǒng)調(diào)用的返回值,可將其輸出到變量resultvar中作為整個(gè)宏定義的返回值。許多系統(tǒng)調(diào)用都需要在用戶(hù)空間和內(nèi)核之間傳遞數(shù)據(jù),包括參數(shù)和返回值。Linux傳遞數(shù)據(jù)的方法如下:

(1)如需要傳遞的參數(shù)少于5個(gè),則直接利用寄存器傳遞。

(2)如需要傳遞的參數(shù)多于5個(gè),調(diào)用者需將它們組織成結(jié)構(gòu)或緩沖區(qū),并通過(guò)寄存器傳遞開(kāi)始地址和大小。服務(wù)函數(shù)根據(jù)寄存器中傳來(lái)的信息,將參數(shù)拷貝到內(nèi)核。

(3)如服務(wù)函數(shù)只產(chǎn)生一個(gè)整型的處理結(jié)果,則通過(guò)EAX將其返回給調(diào)用者。

(4)如服務(wù)函數(shù)產(chǎn)生的結(jié)果較多,則由服務(wù)函數(shù)將結(jié)果拷貝到用戶(hù)空間,拷貝的位置由調(diào)用者在參數(shù)中指定。

由此可見(jiàn),在兩個(gè)空間之間來(lái)回拷貝數(shù)據(jù)是不可避免的。由于用戶(hù)程序不能訪問(wèn)內(nèi)核空間,因而數(shù)據(jù)拷貝工作只能由內(nèi)核完成。事實(shí)上,操作系統(tǒng)內(nèi)核駐留在所有進(jìn)程的地址空間中(如圖4.9),使用的是當(dāng)前進(jìn)程的頁(yè)目錄/頁(yè)表,能夠直接訪問(wèn)當(dāng)前進(jìn)程的整個(gè)地址空間,因而可輕松地在兩個(gè)空間之間拷貝數(shù)據(jù)(mov、movsb、movsw、movsl等)。當(dāng)然,在拷貝數(shù)據(jù)時(shí)需要檢查用戶(hù)空間地址的合法性(是否越界,是否允許寫(xiě)等)。

Linux提供了一組函數(shù)用于在兩個(gè)空間之間拷貝數(shù)據(jù),如get_user用于將用戶(hù)空間的單個(gè)數(shù)據(jù)拷貝到內(nèi)核空間,put_user用于將內(nèi)核空間的單個(gè)數(shù)據(jù)拷貝到用戶(hù)空間,copy_from_user用于將用戶(hù)空間中的一塊數(shù)據(jù)拷貝到內(nèi)核空間,copy_to_user用于將

溫馨提示

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

評(píng)論

0/150

提交評(píng)論