0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

以x86 CPU架構(gòu)理解Linux中斷機(jī)制

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 作者:程磊 ? 2022-08-06 16:19 ? 次閱讀

一、中斷基本原理

中斷是計(jì)算機(jī)中非常重要的功能,其重要性不亞于人的神經(jīng)系統(tǒng)加脈搏。雖然圖靈機(jī)和馮諾依曼結(jié)構(gòu)中沒(méi)有中斷,但是計(jì)算機(jī)如果真的沒(méi)有中斷的話(huà),那么計(jì)算機(jī)就相當(dāng)于是半個(gè)殘疾人。今天我們就來(lái)全面詳細(xì)地講一講中斷。

1.1 中斷的定義

我們先來(lái)看一下中斷的定義:

中斷機(jī)制:CPU在執(zhí)行指令時(shí),收到某個(gè)中斷信號(hào)轉(zhuǎn)而去執(zhí)行預(yù)先設(shè)定好的代碼,然后再返回到原指令流中繼續(xù)執(zhí)行,這就是中斷機(jī)制。

可以發(fā)現(xiàn)中斷的定義非常簡(jiǎn)單。我們根據(jù)中斷的定義來(lái)畫(huà)一張圖:

在圖靈機(jī)模型中,計(jì)算機(jī)是一直線(xiàn)性運(yùn)行的。加入了中斷之后,計(jì)算機(jī)就可以透明地在進(jìn)程執(zhí)行流中插入一段代碼來(lái)執(zhí)行。那么這么做的目的是什么呢?

1.2 中斷的作用

設(shè)計(jì)中斷機(jī)制的目的在于中斷機(jī)制有以下4個(gè)作用,這些作用可以幫助操作系統(tǒng)實(shí)現(xiàn)自己的功能。這四個(gè)作用分別是:

1.外設(shè)異步通知CPU:外設(shè)發(fā)生了什么事情或者完成了什么任務(wù)或者有什么消息要告訴CPU,都可以異步給CPU發(fā)通知。例如,網(wǎng)卡收到了網(wǎng)絡(luò)包,磁盤(pán)完成了IO任務(wù),定時(shí)器的間隔時(shí)間到了,都可以給CPU發(fā)中斷信號(hào)。

2.CPU之間發(fā)送消息:在SMP系統(tǒng)中,一個(gè)CPU想要給另一個(gè)CPU發(fā)送消息,可以給其發(fā)送IPI(處理器間中斷)。

3.處理CPU異常:CPU在執(zhí)行指令的過(guò)程中遇到了異常會(huì)給自己發(fā)送中斷信號(hào)來(lái)處理異常。例如,做整數(shù)除法運(yùn)算的時(shí)候發(fā)現(xiàn)被除數(shù)是0,訪(fǎng)問(wèn)虛擬內(nèi)存的時(shí)候發(fā)現(xiàn)虛擬內(nèi)存沒(méi)有映射到物理內(nèi)存上。

4.實(shí)現(xiàn)系統(tǒng)調(diào)用:早期的系統(tǒng)調(diào)用就是靠中斷指令來(lái)實(shí)現(xiàn)的,后期雖然開(kāi)發(fā)了專(zhuān)用的系統(tǒng)調(diào)用指令,但是其基本原理還是相似的。

1.3 中斷的產(chǎn)生

那么中斷信號(hào)又是如何產(chǎn)生的呢?中斷信號(hào)的產(chǎn)生有以下4個(gè)來(lái)源:

1.外設(shè),外設(shè)產(chǎn)生的中斷信號(hào)是異步的,一般也叫做硬件中斷(注意硬中斷是另外一個(gè)概念)。硬件中斷按照是否可以屏蔽分為可屏蔽中斷和不可屏蔽中斷。例如,網(wǎng)卡、磁盤(pán)、定時(shí)器都可以產(chǎn)生硬件中斷。

2.CPU,這里指的是一個(gè)CPU向另一個(gè)CPU發(fā)送中斷,這種中斷叫做IPI(處理器間中斷)。IPI也可以看出是一種特殊的硬件中斷,因?yàn)樗陀布袛嗟哪J讲畈欢啵际钱惒降摹?/p>

3.CPU異常,CPU在執(zhí)行指令的過(guò)程中發(fā)現(xiàn)異常會(huì)向自己發(fā)送中斷信號(hào),這種中斷是同步的,一般也叫做軟件中斷(注意軟中斷是另外一個(gè)概念)。CPU異常按照是否需要修復(fù)以及是否能修復(fù)分為3類(lèi):1.陷阱(trap),不需要修復(fù),中斷處理完成后繼續(xù)執(zhí)行下一條指令,2.故障(fault),需要修復(fù)也有可能修復(fù),中斷處理完成后重新執(zhí)行之前的指令,3.中止(abort),需要修復(fù)但是無(wú)法修復(fù),中斷處理完成后,進(jìn)程或者內(nèi)核將會(huì)崩潰。例如,缺頁(yè)異常是一種故障,所以也叫缺頁(yè)故障,缺頁(yè)異常處理完成后會(huì)重新執(zhí)行剛才的指令。

4.中斷指令,直接用CPU指令來(lái)產(chǎn)生中斷信號(hào),這種中斷和CPU異常一樣是同步的,也可以叫做軟件中斷。例如,中斷指令int 0x80可以用來(lái)實(shí)現(xiàn)系統(tǒng)調(diào)用。

中斷信號(hào)的4個(gè)來(lái)源正好對(duì)應(yīng)著中斷的4個(gè)作用。前兩種中斷都可以叫做硬件中斷,都是異步的;后兩種中斷都可以叫做軟件中斷,都是同步的。很多書(shū)上也把硬件中斷叫做中斷,把軟件中斷叫做異常。

1.4 中斷的處理

那么中斷信號(hào)又是如何處理的呢?也許你會(huì)覺(jué)得這不是很簡(jiǎn)單嗎,前面的圖里面不是畫(huà)的很清楚嗎,中斷信號(hào)就是在正常的執(zhí)行流中插入一段中斷執(zhí)行流啊。雖然這種中斷處理方式簡(jiǎn)單又直接,但是它還存在著問(wèn)題。

執(zhí)行場(chǎng)景(execute context)

在繼續(xù)講解之前,我們先引入一個(gè)概念,執(zhí)行場(chǎng)景(execute context)。在中斷產(chǎn)生之前是沒(méi)有這個(gè)概念的,有了中斷之后,CPU就分為兩個(gè)執(zhí)行場(chǎng)景了,進(jìn)程執(zhí)行場(chǎng)景(process context)和中斷執(zhí)行場(chǎng)景(interrupt context)。那么哪些是進(jìn)程執(zhí)行場(chǎng)景哪些是中斷執(zhí)行場(chǎng)景呢?進(jìn)程的執(zhí)行是進(jìn)程執(zhí)行場(chǎng)景,同步中斷的處理也是進(jìn)程執(zhí)行場(chǎng)景,異步中斷的處理是中斷執(zhí)行場(chǎng)景。可能有的人會(huì)對(duì)同步中斷的處理是進(jìn)程執(zhí)行場(chǎng)景感到疑惑,但是這也很好理解,因?yàn)橥街袛嗵幚硎呛彤?dāng)前指令相關(guān)的,可以看做是進(jìn)程執(zhí)行的一部分。而異步中斷的處理和當(dāng)前指令沒(méi)有關(guān)系,所以不是進(jìn)程執(zhí)行場(chǎng)景。

進(jìn)程執(zhí)行場(chǎng)景和中斷執(zhí)行場(chǎng)景有兩個(gè)區(qū)別:一是進(jìn)程執(zhí)行場(chǎng)景是可以調(diào)度、可以休眠的,而中斷執(zhí)行場(chǎng)景是不可以調(diào)度不可用休眠的;二是在進(jìn)程執(zhí)行場(chǎng)景中是可以接受中斷信號(hào)的,而在中斷執(zhí)行場(chǎng)景中是屏蔽中斷信號(hào)的。所以如果中斷執(zhí)行場(chǎng)景的執(zhí)行時(shí)間太長(zhǎng)的話(huà),就會(huì)影響我們對(duì)新的中斷信號(hào)的響應(yīng)性,所以我們需要盡量縮短中斷執(zhí)行場(chǎng)景的時(shí)間。為此我們對(duì)異步中斷的處理有下面兩類(lèi)辦法:

1.立即完全處理:

對(duì)于簡(jiǎn)單好處理的異步中斷可以立即進(jìn)行完全處理。

2.立即預(yù)處理 + 稍后完全處理:

對(duì)于處理起來(lái)比較耗時(shí)的中斷可以采取立即預(yù)處理加稍后完全處理的方式來(lái)處理。

為了方便表述,我們把立即完全處理和立即預(yù)處理都叫做中斷預(yù)處理,把稍后完全處理叫做中斷后處理。中斷預(yù)處理只有一種實(shí)現(xiàn)方式,就是直接處理。但是中斷后處理卻有很多種方法,其處理方法可以運(yùn)行在中斷執(zhí)行場(chǎng)景,也可以運(yùn)行在進(jìn)程執(zhí)行場(chǎng)景,前者叫做直接中斷后處理,后者叫做線(xiàn)程化中斷后處理。

Linux中,中斷預(yù)處理叫做上半部,中斷后處理叫做下半部。由于“上半部、下半部”詞義不明晰,我們?cè)诒疚闹卸加弥袛囝A(yù)處理、中斷后處理來(lái)稱(chēng)呼。中斷預(yù)處理只有一種方法,叫做hardirq(硬中斷)。中斷后處理有很多種方法,分為兩類(lèi),直接中斷后處理有softirq(軟中斷)、tasklet(微任務(wù)),線(xiàn)程化中斷后處理有workqueue(工作隊(duì)列)、threaded_irq(中斷線(xiàn)程)。

硬中斷、軟中斷是什么意思呢?本來(lái)的異步中斷處理是直接把中斷處理完的,整個(gè)過(guò)程是屏蔽中斷的,現(xiàn)在,把整個(gè)過(guò)程分成了兩部分,前半部分還是屏蔽中斷的,叫做硬中斷,處理與硬件相關(guān)的緊急事物,后半部分不再屏蔽中斷,叫做軟中斷,處理剩余的事物。由于軟中斷中不再屏蔽中斷信號(hào),所以提高了系統(tǒng)對(duì)中斷的響應(yīng)性。

注意硬件中斷、軟件中斷,硬中斷、軟中斷是不同的概念,分別指的是中斷的來(lái)源和中斷的處理方式。

1.5 中斷向量號(hào)

不同的中斷信號(hào)需要有不同的處理方式,那么系統(tǒng)是怎么區(qū)分不同的中斷信號(hào)呢?是靠中斷向量號(hào)。每一個(gè)中斷信號(hào)都有一個(gè)中斷向量號(hào),中斷向量號(hào)是一個(gè)整數(shù)。CPU收到一個(gè)中斷信號(hào)會(huì)根據(jù)這個(gè)信號(hào)的中斷的向量號(hào)去查詢(xún)中斷向量表,根據(jù)向量表里面的指示去調(diào)用相應(yīng)的處理函數(shù)。

中斷信號(hào)和中斷向量號(hào)是如何對(duì)應(yīng)的呢?對(duì)于CPU異常來(lái)說(shuō),其向量號(hào)是由CPU架構(gòu)標(biāo)準(zhǔn)規(guī)定的。對(duì)于外設(shè)來(lái)說(shuō),其向量號(hào)是由設(shè)備驅(qū)動(dòng)動(dòng)態(tài)申請(qǐng)的。對(duì)于IPI中斷和指令中斷來(lái)說(shuō),其向量號(hào)是由內(nèi)核規(guī)定的。

那么中斷向量表是什么格式,應(yīng)該如何設(shè)置呢,這個(gè)我們后面會(huì)講。

1.6 中斷框架結(jié)構(gòu)

有了前面這么多基礎(chǔ)知識(shí),下面我們對(duì)中斷機(jī)制做個(gè)概覽。

中斷信號(hào)的產(chǎn)生有兩類(lèi),分別是異步中斷和同步中斷,異步中斷包括外設(shè)中斷和IPI中斷,同步中斷包括CPU異常和指令中斷。無(wú)論是同步中斷還是異步中斷,都要經(jīng)過(guò)中斷向量表進(jìn)行處理。對(duì)于同步中斷的處理是異常處理或者系統(tǒng)調(diào)用,它們都是進(jìn)程執(zhí)行場(chǎng)景,所以沒(méi)有過(guò)多的處理方法,就是直接執(zhí)行。對(duì)于異步中斷的處理,由于直接調(diào)用處理是屬于中斷執(zhí)行場(chǎng)景,默認(rèn)的中斷執(zhí)行場(chǎng)景是會(huì)屏蔽中斷的,這會(huì)降低系統(tǒng)對(duì)中斷的響應(yīng)性,所以?xún)?nèi)核開(kāi)發(fā)出了很多的方法來(lái)解決這個(gè)問(wèn)題。

下面的章節(jié)是對(duì)這個(gè)圖的詳細(xì)解釋?zhuān)覀兿戎v中斷向量表,再講中斷的產(chǎn)生,最后講中斷的處理。

本文后面都是以x86 CPU架構(gòu)進(jìn)行講解的。

二、中斷流程

CPU收到中斷信號(hào)后會(huì)首先保存被中斷程序的狀態(tài),然后再去執(zhí)行中斷處理程序,最后再返回到原程序中被中斷的點(diǎn)去執(zhí)行。具體是怎么做呢?我們以x86為例講解一下。

2.1 保存現(xiàn)場(chǎng)

CPU收到中斷信號(hào)后會(huì)首先把一些數(shù)據(jù)push到內(nèi)核棧上,保存的數(shù)據(jù)是和當(dāng)前執(zhí)行點(diǎn)相關(guān)的,這樣中斷完成后就可以返回到原執(zhí)行點(diǎn)。如果CPU當(dāng)前處于用戶(hù)態(tài),則會(huì)先切換到內(nèi)核態(tài),把用戶(hù)棧切換為內(nèi)核棧再去保存數(shù)據(jù)(內(nèi)核棧的位置是在當(dāng)前線(xiàn)程的TSS中獲取的)。下面我們畫(huà)個(gè)圖看一下:

CPU都push了哪些數(shù)據(jù)呢?分為兩種情況。當(dāng)CPU處于內(nèi)核態(tài)時(shí),會(huì)push寄存器EFLAGS、CS、EIP的值到棧上,對(duì)于有些CPU異常還會(huì)push Error Code。Push CS、EIP是為了中斷完成后返回到原執(zhí)行點(diǎn),push EFLAGS是為了恢復(fù)之前的CPU狀態(tài)。當(dāng)CPU處于用戶(hù)態(tài)時(shí),會(huì)先切換到內(nèi)核態(tài),把棧切換到內(nèi)核棧,然后push寄存器SS(old)、ESP(old)、EFLAGS、CS、EIP的值到新的內(nèi)核棧,對(duì)于有些CPU異常還會(huì)push Error Code。Push SS(old)、ESP(old),是為了中斷返回的時(shí)候可以切換回原來(lái)的棧。有些CPU異常會(huì)push Error Code,這樣可以方便中斷處理程序知道更具體的異常信息。不是所有的CPU異常都會(huì)push Error Code,具體哪些會(huì)哪些不會(huì)在3.1節(jié)中會(huì)講。

上圖是32位的情況,64位的時(shí)候會(huì)push 64位下的寄存器。

2.2 查找向量表

保存完被中斷程序的信息之后,就要去執(zhí)行中斷處理程序了。CPU會(huì)根據(jù)當(dāng)前中斷信號(hào)的向量號(hào)去查詢(xún)中斷向量表找到中斷處理程序。CPU是如何獲得當(dāng)前中斷信號(hào)的向量號(hào)的呢,如果是CPU異??梢栽贑PU內(nèi)部獲取,如果是指令中斷,在指令中就有向量號(hào),如果是硬件中斷,則可以從中斷控制器中獲取中斷向量號(hào)。那CPU又是怎么找到中斷向量表呢,是通過(guò)IDTR寄存器。IDTR寄存器的格式如下圖所示:

IDTR寄存器由兩部分組成:一部分是IDT基地址,在32位上是32位,在64位上是64位,是虛擬內(nèi)存上的地址;一部分是IDT限長(zhǎng),是16位,單位是字節(jié),代表中斷向量表的長(zhǎng)度。雖然x86支持256個(gè)中斷向量,但是系統(tǒng)不一定要用滿(mǎn)256個(gè),IDT限長(zhǎng)用來(lái)指定中斷向量表的大小。系統(tǒng)在啟動(dòng)時(shí)分配一定大小的內(nèi)存用來(lái)做中斷向量表,然后通過(guò)LIDT指令設(shè)置IDTR寄存器的值,這樣CPU就知道中斷向量表的位置和大小了。

IDTR寄存器設(shè)置好之后,中斷向量表的內(nèi)容還是可以再修改的。該如何修改呢,這就需要我們知道中斷向量表的數(shù)據(jù)結(jié)構(gòu)了。中斷向量表是一個(gè)數(shù)組結(jié)構(gòu),數(shù)組的每一項(xiàng)叫做中斷向量表?xiàng)l目,每個(gè)條目都是一個(gè)門(mén)描述符(gate descriptor)。門(mén)描述符一共有三種類(lèi)型,不同類(lèi)型的具體結(jié)構(gòu)不同,三類(lèi)門(mén)描述符分別是任務(wù)門(mén)描述符、中斷門(mén)描述符、陷阱門(mén)描述符。任務(wù)門(mén)不太常用,后面我們都默認(rèn)忽略任務(wù)門(mén)。中斷門(mén)一般用于硬件中斷,陷阱門(mén)一般用于軟件中斷。32位下的門(mén)描述符是8字節(jié),下面是它們的具體結(jié)構(gòu):

Segment Selector是段選擇符,Offset是段偏移,兩個(gè)段偏移共同構(gòu)成一個(gè)32的段偏移。p代表段是否加載到了內(nèi)存。dpl是段描述符特權(quán)級(jí)。d為0代表是16位描述符,d為1代表是32位描述符。Type 是8 9 10三位,代表描述符的類(lèi)型。

下面看一下64位門(mén)描述符的格式:

可以看到64位和32位最主要的變化是把段偏移變成了64位。

關(guān)于x86的分段機(jī)制,這里就不展開(kāi)討論了,簡(jiǎn)介地介紹一下其在Linux內(nèi)核中的應(yīng)用。Linux內(nèi)核并不使用x86的分段機(jī)制,但是x86上特權(quán)級(jí)的切換還是需要用到分段。所以L(fǎng)inux采取的方法是,定義了四個(gè)段__KERNEL_CS、__KERNEL_DS、__USER_CS、__USER_DS,這四個(gè)段的段基址都是0,段限長(zhǎng)都是整個(gè)內(nèi)存大小,所以在邏輯上相當(dāng)于不分段。但是這四個(gè)段的特權(quán)級(jí)不一樣,__KERNEL_CS、__KERNEL_DS是內(nèi)核特權(quán)級(jí),用在內(nèi)核執(zhí)行時(shí),__USER_CS、__USER_DS是用戶(hù)特權(quán)級(jí),用在進(jìn)程執(zhí)行時(shí)。由于中斷都運(yùn)行在內(nèi)核,所以所有中斷的門(mén)描述符的段選擇符都是__KERNEL_CS,而段偏移實(shí)際上就是終端處理函數(shù)的虛擬地址。

CPU現(xiàn)在已經(jīng)把被中斷的程序現(xiàn)場(chǎng)保存到內(nèi)核棧上了,又得到了中斷向量號(hào),然后就根據(jù)中斷向量號(hào)從中斷向量表中找到對(duì)應(yīng)的門(mén)描述符,對(duì)描述符做一番安全檢查之后,CPU就開(kāi)始執(zhí)行中斷處理函數(shù)(就是門(mén)描述符中的段偏移)。中斷處理函數(shù)的最末尾執(zhí)行IRET指令,這個(gè)指令會(huì)根據(jù)前面保存在棧上的數(shù)據(jù)跳回到原來(lái)的指令繼續(xù)執(zhí)行。

三、軟件中斷

對(duì)中斷的基本概念和整個(gè)處理流程有了大概的認(rèn)識(shí)之后,我們來(lái)看一下軟件中斷的產(chǎn)生。軟件中斷有兩類(lèi),CPU異常和指令中斷。我們先來(lái)看CPU異常:

3.1 CPU異常

CPU在執(zhí)行指令的過(guò)程中遇到了異常就會(huì)給自己發(fā)送中斷信號(hào)。注意異常不一定是錯(cuò)誤,只要是異于平常就都是異常。有些異常不但不是錯(cuò)誤,它還是實(shí)現(xiàn)內(nèi)核重要功能的方法。CPU異常分為3類(lèi):1.陷阱(trap),陷阱并不是錯(cuò)誤,而是想要陷入內(nèi)核來(lái)執(zhí)行一些操作,中斷處理完成后繼續(xù)執(zhí)行之前的下一條指令,2.故障(fault),故障是程序遇到了問(wèn)題需要修復(fù),問(wèn)題不一定是錯(cuò)誤,如果問(wèn)題能夠修復(fù),那么中斷處理完成后會(huì)重新執(zhí)行之前的指令,如果問(wèn)題無(wú)法修復(fù)那就是錯(cuò)誤,當(dāng)前進(jìn)程將會(huì)被殺死。3.中止(abort),系統(tǒng)遇到了很?chē)?yán)重的錯(cuò)誤,無(wú)法修改,一般系統(tǒng)會(huì)崩潰。

CPU異常的含義和其向量號(hào)都是架構(gòu)標(biāo)準(zhǔn)提前定義好的,下面我們來(lái)看一下。

x86一共有256個(gè)中斷向量號(hào),前32個(gè)(0-31)是Intel預(yù)留的,其中0-21(除了15)都已分配給特定的CPU異常。32-255是給硬件中斷和指令中斷保留的向量號(hào)。

3.2 指令中斷

指令中斷和CPU異常有很大的相似性,都屬于同步中斷,都是屬于因?yàn)閳?zhí)行指令而產(chǎn)生了中斷。不同的是CPU異常不是在執(zhí)行特定的指令時(shí)發(fā)生的,也不是必然發(fā)生。而指令中斷是執(zhí)行特定的指令而發(fā)生的中斷,設(shè)計(jì)這些指令的目的就是為了產(chǎn)生中斷的,而且一定會(huì)產(chǎn)生中斷或者有些條件成立的情況下一定會(huì)產(chǎn)生中斷。其中指令I(lǐng)NT n可以產(chǎn)生任意中斷,n可以取任意值。Linux用int 0x80來(lái)作為系統(tǒng)調(diào)用的指令。

四、硬件中斷

硬件中斷分為外設(shè)中斷和處理器間中斷(IPI),下面我們先來(lái)看一下外設(shè)中斷。

4.1 外設(shè)中斷

外設(shè)中斷和軟件中斷有一個(gè)很大的不同,軟件中斷是CPU自己給自己發(fā)送中斷,而外設(shè)中斷是需要外設(shè)發(fā)送中斷給CPU。外設(shè)想要給CPU發(fā)送中斷,那就必須要連接到CPU,不可能隔空發(fā)送。那么怎么連接呢,如果所有外設(shè)都直接連到CPU,顯然是不可能的。因?yàn)橐粋€(gè)計(jì)算機(jī)系統(tǒng)中的外設(shè)是非常多的,而且多種多樣,CPU無(wú)法提前為所有外設(shè)設(shè)計(jì)和預(yù)留接口。所以需要一個(gè)中間設(shè)備,就像秘書(shū)一樣替CPU連接到所有的外設(shè)并接收中斷信號(hào),再轉(zhuǎn)發(fā)給CPU,這個(gè)設(shè)備就叫做中斷控制器(Interrupt Controller )。

在x86上,在UP時(shí)代的時(shí)候,有一個(gè)中斷控制器叫做PIC(Programmable Interrupt Controller )。所有的外設(shè)都連接到PIC上,PIC再連接到CPU的中斷引腳上。外設(shè)給PIC發(fā)中斷,PIC再把中斷轉(zhuǎn)發(fā)給CPU。由于PIC的設(shè)計(jì)問(wèn)題,一個(gè)PIC只能連接8個(gè)外設(shè),所以后來(lái)把兩個(gè)PIC級(jí)聯(lián)起來(lái),第二個(gè)PIC連接到第一個(gè)PIC的一個(gè)引腳上,這樣一共能連接15個(gè)外設(shè)。

到了SMP時(shí)代的時(shí)候,PIC顯然不能勝任工作了,于是Intel開(kāi)發(fā)了APIC(Advanced PIC)。APIC分為兩個(gè)部分:一部分是Local APIC,有NR_CPU個(gè),每個(gè)CPU都連接一個(gè)Local APIC;一部分是IO APIC,只有一個(gè),所有的外設(shè)都連接到這個(gè)IO APIC上。IO APIC連接到所有的Local APIC上,當(dāng)外設(shè)向IO APIC發(fā)送中斷時(shí),IO APIC會(huì)把中斷信號(hào)轉(zhuǎn)發(fā)給某個(gè)Local APIC。有些per CPU的設(shè)備是直接連接到Local APIC的,可以通過(guò)Local APIC直接給自己的CPU發(fā)送中斷。

外設(shè)中斷并不是直接分配中斷向量號(hào),而是直接分配IRQ號(hào),然后IRQ+32就是其中斷向量號(hào)。有些外設(shè)的IRQ是內(nèi)核預(yù)先設(shè)定好的,有些是行業(yè)默認(rèn)的IRQ號(hào)。

關(guān)于APIC的細(xì)節(jié)這里就不再闡述了,推薦大家去看《Interrupt in Linux (硬件篇)》,對(duì)APIC講的比較詳細(xì)。

4.2 處理器間中斷

在SMP系統(tǒng)中,多個(gè)CPU之間有時(shí)候也需要發(fā)送消息,于是就產(chǎn)生了處理器間中斷(IPI)。IPI既像軟件中斷又像硬件中斷,它的產(chǎn)生像軟件中斷,是在程序中用代碼發(fā)送的,而它的處理像硬件中斷,是異步的。我們這里把IPI看作是硬件中斷,因?yàn)橐粋€(gè)CPU可以把另外一個(gè)CPU看做外設(shè),就相當(dāng)于是外設(shè)發(fā)來(lái)的中斷。

五、中斷處理

終于講到中斷處理了,我們?cè)侔阎暗闹虚g機(jī)制圖搬過(guò)來(lái),再回顧一下:

無(wú)論是硬件中斷還是軟件中斷,都是通過(guò)中斷向量表進(jìn)行處理的。但是不同的是,軟件中斷的處理程序是屬于進(jìn)程執(zhí)行場(chǎng)景,所以直接把中斷處理程序設(shè)置好就行了,中斷處理程序怎么寫(xiě)也沒(méi)有什么要顧慮的。而硬件中斷的處理程序就不同了,它是屬于中斷執(zhí)行場(chǎng)景。不僅其中斷處理函數(shù)中不能調(diào)用會(huì)阻塞、休眠的函數(shù),而且處理程序本身要盡量的短,越短越好。所以為了使硬件中斷處理函數(shù)盡可能的短,Linux內(nèi)核開(kāi)發(fā)了一大堆方法。這些方法包括硬中斷(hardirq)、軟中斷(softirq)、微任務(wù)(tasklet)、中斷線(xiàn)程(threaded irq)、工作隊(duì)列(workqueue)。其實(shí)硬中斷嚴(yán)格來(lái)說(shuō)不算是一種方法,因?yàn)樗侵袛嗵幚淼谋亟?jīng)之路,它就是中斷向量表里面設(shè)置的處理函數(shù)。為了和軟中斷進(jìn)行區(qū)分,才把硬中斷叫做硬中斷。硬中斷和軟中斷都是屬于中斷執(zhí)行場(chǎng)景,而中斷線(xiàn)程和工作隊(duì)列是屬于進(jìn)程執(zhí)行場(chǎng)景。把硬件中斷的處理任務(wù)放到進(jìn)程場(chǎng)景里面來(lái)做,大大提高了中斷處理的靈活性。

由于軟件中斷的處理都是直接處理,都是內(nèi)核本身直接寫(xiě)好了的,一般都接觸不到,而硬件中斷的處理和硬件驅(qū)動(dòng)密切相關(guān),所以很多書(shū)上所講的中斷處理都是指的硬件中斷的處理。

5.1 異常處理

x86上的異常處理是怎么設(shè)置的呢?我們把前面的圖搬過(guò)來(lái)看一下:

我們對(duì)照著這個(gè)圖去捋代碼。首先我們需要分配一片內(nèi)存來(lái)存放中斷向量表,這個(gè)是在如下代碼中分配的。

linux-src/arch/x86/kernel/idt.c

/* Must be page-aligned because the real IDT is used in the cpu entry area */static gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

linux-src/arch/x86/include/asm/desc_defs.h

struct idt_bits { u16 ist : 3, zero : 5, type : 5, dpl : 2, p : 1;} __attribute__((packed));

struct gate_struct { u16 offset_low; u16

segment; struct idt_bits bits; u16 offset_middle;#ifdef CONFIG_X86_64 u32

offset_high;

u32

reserved;#endif} __attribute__((packed));

typedef struct gate_struct gate_desc;

linux-src/arch/x86/include/asm/segment.h

#define IDT_ENTRIES 256

可以看到我們的中斷向量表idt_table是門(mén)描述符gate_desc的數(shù)組,數(shù)組大小是IDT_ENTRIES 256。門(mén)描述符gate_desc的定義和前面畫(huà)的圖是一致的,注意x86是小端序。

寄存器IDTR內(nèi)容包括IDT的基址和限長(zhǎng),為此我們專(zhuān)門(mén)定義一個(gè)數(shù)據(jù)結(jié)構(gòu)包含IDT的基址和限長(zhǎng),然后就可以用這個(gè)變量通過(guò)LIDT指令來(lái)設(shè)置IDTR寄存器了。

linux-src/arch/x86/kernel/idt.c

static struct desc_ptr idt_descr __ro_after_init = { .size = IDT_TABLE_SIZE - 1, .address = (unsigned long) idt_table,};

linux-src/arch/x86/include/asm/desc.h

#define load_idt(dtr) native_load_idt(dtr)static __always_inline void native_load_idt(const struct desc_ptr *dtr){ asm volatile(“l(fā)idt %0”::“m” (*dtr));}

有一點(diǎn)需要注意的,我們并不是需要把idt_table完全初始化好了再去load_idt,我們可以先初始化一部分的idt_table,然后再去load_idt,之后可以不停地去完善idt_table。

我們先來(lái)看一下內(nèi)核是什么時(shí)候load_idt的,其實(shí)內(nèi)核有多次load_idt,不過(guò)實(shí)際上只需要一次就夠了。

調(diào)用棧如下:

start_kernel

setup_arch

idt_setup_early_traps

代碼如下:

linux-src/arch/x86/kernel/idt.c

void __init idt_setup_early_traps(void){ idt_setup_from_table(idt_table, early_idts, ARRAY_SIZE(early_idts), true); load_idt(&idt_descr);}

這是內(nèi)核在start_kernel里第一次設(shè)置IDTR,雖然之前的代碼里也有設(shè)置過(guò)IDTR,我們就不考慮了。load_idt之后,IDT就生效了,只不過(guò)這里IDT還沒(méi)有設(shè)置全,只設(shè)置了少數(shù)幾個(gè)CPU異常的處理函數(shù),我們來(lái)看一下是怎么設(shè)置的。

linux-src/arch/x86/kernel/idt.c

static __init voididt_setup_from_table(gate_desc *idt, const struct idt_data *t, int size, bool sys){ gate_desc desc;

for (; size 》 0; t++, size--) { idt_init_desc(&desc, t); write_idt_entry(idt, t-》vector, &desc);

if (sys)

set_bit(t-》vector, system_vectors); }}

static inline void idt_init_desc(gate_desc *gate, const struct idt_data *d){ unsigned long addr = (unsigned long) d-》addr;

gate-》offset_low = (u16) addr; gate-》segment

= (u16) d-》segment; gate-》bits

= d-》bits; gate-》offset_middle = (u16) (addr 》》 16);#ifdef CONFIG_X86_64 gate-》offset_high = (u32) (addr 》》 32); gate-》reserved = 0;#endif}

#define write_idt_entry(dt, entry, g)

native_write_idt_entry(dt, entry, g)static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate){ memcpy(&idt[entry], gate, sizeof(*gate));}

在函數(shù)idt_setup_from_table里會(huì)定義一個(gè)gate_desc的臨時(shí)變量,然后用idt_data來(lái)初始化這個(gè)gate_desc,最后會(huì)把gate_desc復(fù)制到idt_table中對(duì)應(yīng)的位置中去。這樣中斷向量表中的這一項(xiàng)就生效了。

下面我們?cè)賮?lái)看看idt_data數(shù)據(jù)是怎么來(lái)的:

linux-src/arch/x86/kernel/idt.c

static const __initconst struct idt_data early_idts[] = { INTG(X86_TRAP_DB,

asm_exc_debug), SYSG(X86_TRAP_BP, asm_exc_int3),};

#define G(_vector, _addr, _ist, _type, _dpl, _segment) {

.vector = _vector,

.bits.ist = _ist,

.bits.type = _type,

.bits.dpl = _dpl,

.bits.p = 1,

.addr = _addr, 。

segment = _segment, }

/* Interrupt gate */#define INTG(_vector, _addr)

G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL0, __KERNEL_CS)

/* System interrupt gate */#define SYSG(_vector, _addr)

G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL3, __KERNEL_CS)

linux-src/arch/x86/kernel/traps.c

DEFINE_IDTENTRY_DEBUG(exc_debug){ exc_debug_kernel(regs, debug_read_clear_dr6());}

EFINE_IDTENTRY_RAW(exc_int3){ /* * poke_int3_handler() is completely self contained code; it does (and * must) *NOT* call out to anything, lest it hits upon yet another

* INT3. */ if (poke_int3_handler(regs)) return;

/* * irqentry_enter_from_user_mode() uses static_branch_{,un}likely() * and therefore can trigger INT3, hence poke_int3_handler() must

* be done before. If the entry came from kernel mode, then use * nmi_enter() because the INT3 could have been hit in any context * including NMI.

*/ if (user_mode(regs)) { irqentry_enter_from_user_mode(regs);

instrumentation_begin();

do_int3_user(regs);

instrumentation_end();

irqentry_exit_to_user_mode(regs); } else {

irqentry_state_t irq_state = irqentry_nmi_enter(regs);

instrumentation_begin();

if (!do_int3(regs))

die(“int3”, regs, 0);

instrumentation_end(); irqentry_nmi_exit(regs, irq_state); }}

early_idts是idt_data的數(shù)組,在這里定義了兩個(gè)中斷向量表的條目,分別是X86_TRAP_DB和X86_TRAP_BP,它們的中斷處理函數(shù)分別是asm_exc_debug和asm_exc_int3。這里只是設(shè)置了兩個(gè)中斷向量表?xiàng)l目,并且把IDTR寄存器設(shè)置好了,后來(lái)就不需要再設(shè)置IDTR寄存器了。

下面我們看一下所有CPU異常的處理函數(shù)是怎么設(shè)置的。

先看調(diào)用棧:

start_kernel

trap_init

idt_setup_traps

代碼如下:

linux-src/arch/x86/kernel/idt.c

void __init idt_setup_traps(void){ idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);}

static const __initconst struct idt_data def_idts[] = { INTG(X86_TRAP_DE, asm_exc_divide_error), ISTG(X86_TRAP_NMI,

asm_exc_nmi, IST_INDEX_NMI), INTG(X86_TRAP_BR,

asm_exc_bounds), INTG(X86_TRAP_UD,

asm_exc_invalid_op), INTG(X86_TRAP_NM, asm_exc_device_not_available),

INTG(X86_TRAP_OLD_MF,

asm_exc_coproc_segment_overrun), INTG(X86_TRAP_TS,

asm_exc_invalid_tss), INTG(X86_TRAP_NP,

asm_exc_segment_not_present), INTG(X86_TRAP_SS,

asm_exc_stack_segment), INTG(X86_TRAP_GP,

asm_exc_general_protection), INTG(X86_TRAP_SPURIOUS,

asm_exc_spurious_interrupt_bug), INTG(X86_TRAP_MF,

asm_exc_coprocessor_error), INTG(X86_TRAP_AC,

asm_exc_alignment_check), INTG(X86_TRAP_XF,

asm_exc_simd_coprocessor_error),

#ifdef CONFIG_X86_32 TSKG(X86_TRAP_DF,

GDT_ENTRY_DOUBLEFAULT_TSS),#else ISTG(X86_TRAP_DF,

asm_exc_double_fault, IST_INDEX_DF),#endif ISTG(X86_TRAP_DB, asm_exc_debug, IST_INDEX_DB),

#ifdef CONFIG_X86_MCE ISTG(X86_TRAP_MC,

asm_exc_machine_check, IST_INDEX_MCE),#endif

#ifdef CONFIG_AMD_MEM_ENCRYPT ISTG(X86_TRAP_VC,

asm_exc_vmm_communication, IST_INDEX_VC),#endif

SYSG(X86_TRAP_OF,

asm_exc_overflow),#if defined(CONFIG_IA32_EMULATION) SYSG(IA32_SYSCALL_VECTOR,

entry_INT80_compat),#elif defined(CONFIG_X86_32) SYSG(IA32_SYSCALL_VECTOR,

entry_INT80_32),#endif};

可以看到這次設(shè)置非常簡(jiǎn)單,就是調(diào)用了一下idt_setup_from_table,并沒(méi)有調(diào)用load_idt。主要是數(shù)組def_idts里面包含了大部分的CPU異常處理。但是沒(méi)缺頁(yè)異常,缺頁(yè)異常是單獨(dú)設(shè)置。設(shè)置路徑如下:

調(diào)用棧:

start_kernel

setup_arch

idt_setup_early_pf

代碼如下:

linux-src/arch/x86/kernel/idt.c

void __init idt_setup_early_pf(void){ idt_setup_from_table(idt_table, early_pf_idts,

ARRAY_SIZE(early_pf_idts), true);}

static const __initconst struct idt_data early_pf_idts[] = { INTG(X86_TRAP_PF,

asm_exc_page_fault),};

現(xiàn)在CPU異常的中斷處理函數(shù)就全部設(shè)置完成了,想要研究具體哪個(gè)異常是怎么處理的同學(xué),可以去跟蹤研究一下相應(yīng)的函數(shù)。

5.2 硬中斷(hardirq)

硬件中斷的中斷處理和軟件中斷有一部分是相同的,有一部分卻有很大的不同。對(duì)于IPI中斷和per CPU中斷,其設(shè)置是和軟件中斷相同的,都是一步到位設(shè)置到具體的處理函數(shù)。但是對(duì)于余下的外設(shè)中斷,只是設(shè)置了入口函數(shù),并沒(méi)有設(shè)置具體的處理函數(shù),而且是所有的外設(shè)中斷的處理函數(shù)都統(tǒng)一到了同一個(gè)入口函數(shù)。然后在這個(gè)入口函數(shù)處會(huì)調(diào)用相應(yīng)的irq描述符的handler函數(shù),這個(gè)handler函數(shù)是中斷控制器設(shè)置的。中斷控制器設(shè)置的這個(gè)handler函數(shù)會(huì)處理與這個(gè)中斷控制器相關(guān)的一些事物,然后再調(diào)用具體設(shè)備注冊(cè)的irqaction的handler函數(shù)進(jìn)行具體的中斷處理。

我們先來(lái)看一下對(duì)中斷向量表?xiàng)l目的設(shè)置代碼。

調(diào)用棧如下:

start_kernel

init_IRQ

native_init_IRQ

idt_setup_apic_and_irq_gates

代碼如下:

linux-src/arch/x86/kernel/idt.c

/** * idt_setup_apic_and_irq_gates - Setup APIC/SMP and normal interrupt gates */void __init idt_setup_apic_and_irq_gates(void){ int i = FIRST_EXTERNAL_VECTOR; void *entry;

idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);

for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {

entry = irq_entries_start + 8 * (i - FIRST_EXTERNAL_VECTOR);

set_intr_gate(i, entry); }

#ifdef CONFIG_X86_LOCAL_APIC for_each_clear_bit_from(i, system_vectors, NR_VECTORS) {

/* * Don‘t set the non assigned system vectors in the * system_vectors bitmap. Otherwise they show up in

* /proc/interrupts. */ entry = spurious_entries_start + 8 * (i - FIRST_SYSTEM_VECTOR);

set_intr_gate(i, entry); }#endif /* Map IDT into CPU entry area and reload it. */ idt_map_in_cea(); load_idt(&idt_descr);

/* Make the IDT table read only */ set_memory_ro((unsigned long)&idt_table, 1);

idt_setup_done = true;}

static const __initconst struct idt_data apic_idts[] = {#ifdef CONFIG_SMP INTG(RESCHEDULE_VECTOR,

asm_sysvec_reschedule_ipi), INTG(CALL_FUNCTION_VECTOR,

asm_sysvec_call_function),

INTG(CALL_FUNCTION_SINGLE_VECTOR, asm_sysvec_call_function_single), INTG(IRQ_MOVE_CLEANUP_VECTOR,

asm_sysvec_irq_move_cleanup), INTG(REBOOT_VECTOR,

asm_sysvec_reboot),#endif

#ifdef CONFIG_X86_THERMAL_VECTOR INTG(THERMAL_APIC_VECTOR,

asm_sysvec_thermal),#endif

#ifdef CONFIG_X86_MCE_THRESHOLD INTG(THRESHOLD_APIC_VECTOR,

asm_sysvec_threshold),#endif

#ifdef CONFIG_X86_MCE_AMD INTG(DEFERRED_ERROR_VECTOR,

asm_sysvec_deferred_error),#endif

#ifdef CONFIG_X86_LOCAL_APIC INTG(LOCAL_TIMER_VECTOR,

asm_sysvec_apic_timer_interrupt), INTG(X86_PLATFORM_IPI_VECTOR,

asm_sysvec_x86_platform_ipi),# ifdef CONFIG_HAVE_KVM INTG(POSTED_INTR_VECTOR,

asm_sysvec_kvm_posted_intr_ipi), INTG(POSTED_INTR_WAKEUP_VECTOR, asm_sysvec_kvm_posted_intr_wakeup_ipi), INTG(POSTED_INTR_NESTED_VECTOR,

asm_sysvec_kvm_posted_intr_nested_ipi),# endif# ifdef CONFIG_IRQ_WORK INTG(IRQ_WORK_VECTOR,

asm_sysvec_irq_work),# endif INTG(SPURIOUS_APIC_VECTOR,

asm_sysvec_spurious_apic_interrupt), INTG(ERROR_APIC_VECTOR,

asm_sysvec_error_interrupt),#endif};

static __init void set_intr_gate(unsigned int n, const void *addr){ struct idt_data data;

init_idt_data(&data, n, addr);

idt_setup_from_table(idt_table, &data, 1, false);}

linux-src/arch/x86/include/asm/desc.h

static inline void init_idt_data(struct idt_data *data, unsigned int n,

const void *addr){ BUG_ON(n 》 0xFF);

memset(data, 0, sizeof(*data)); data-》vector = n;

data-》addr = addr;

data-》segment = __KERNEL_CS; data-》bits.type = GATE_INTERRUPT; data-》bits.p = 1;}

linux-src/arch/x86/include/asm/idtentry.h

SYM_CODE_START(irq_entries_start)

vector=FIRST_EXTERNAL_VECTOR

.rept NR_EXTERNAL_VECTORS UNWIND_HINT_IRET_REGS0 :

.byte 0x6a, vector jmp asm_common_interrupt nop

/* Ensure that the above is 8 bytes max */

。 = 0b + 8 vector = vector+1

.endrSYM_CODE_END(irq_entries_start)

linux-src/arch/x86/kernel/irq.c

DEFINE_IDTENTRY_IRQ(common_interrupt){ struct pt_regs *old_regs = set_irq_regs(regs); struct irq_desc *desc;

/* entry code tells RCU that we’re not quiescent. Check it. */ RCU_LOCKDEP_WARN(!rcu_is_watching(), “IRQ failed to wake up RCU”);

desc = __this_cpu_read(vector_irq[vector]); if (likely(!IS_ERR_OR_NULL(desc))) {

handle_irq(desc, regs); } else { ack_APIC_irq();

if (desc == VECTOR_UNUSED) {

pr_emerg_ratelimited(“%s: %d.%u No irq handler for vector

”, __func__, smp_processor_id(),

vector); } else { __this_cpu_write(vector_irq[vector], VECTOR_UNUSED); } }

set_irq_regs(old_regs);}

static __always_inline void handle_irq(struct irq_desc *desc,

struct pt_regs *regs){ if (IS_ENABLED(CONFIG_X86_64))

generic_handle_irq_desc(desc);

else __handle_irq(desc, regs);}

linux-src/arch/x86/kernel/irqinit.c

DEFINE_PER_CPU(vector_irq_t, vector_irq) = { [0 。.. NR_VECTORS - 1] = VECTOR_UNUSED,};

linux-src/arch/x86/include/asm/hw_irq.h

typedef struct irq_desc* vector_irq_t[NR_VECTORS];

linux-src/include/linux/irqdesc.h

static inline void generic_handle_irq_desc(struct irq_desc *desc){ desc-》handle_irq(desc);}

從上面的代碼可以看出,對(duì)硬件中斷的設(shè)置分為兩個(gè)部分,一部分就像前面的軟件中斷的方式一樣,是從apic_idts數(shù)組設(shè)置的,設(shè)置的都是一些IPI和per CPU的中斷。另一部分是把所有剩余的硬件中斷的處理函數(shù)都設(shè)置為irq_entries_start,irq_entries_start會(huì)調(diào)用common_interrupt函數(shù)。在common_interrupt函數(shù)中會(huì)根據(jù)中斷向量號(hào)去讀取per CPU的數(shù)組變量vector_irq,得到一個(gè)irq_desc。最終會(huì)調(diào)用irq_desc中的handle_irq來(lái)處理這個(gè)中斷。

對(duì)于外設(shè)中斷為什么要采取這樣的處理方式呢?有兩個(gè)原因,1是因?yàn)橥庠O(shè)中斷和中斷控制器相關(guān)聯(lián),這樣可以統(tǒng)一處理與中斷控制器相關(guān)的事物,2是因?yàn)橥庠O(shè)中斷的驅(qū)動(dòng)執(zhí)行比較晚,有些設(shè)備還是可以熱插拔的,直接把它們放到中斷向量表上比較麻煩。有個(gè)irq_desc這個(gè)中間層,設(shè)備驅(qū)動(dòng)后面只需要調(diào)用函數(shù)request_irq來(lái)注冊(cè)ISR,只處理與設(shè)備相關(guān)的業(yè)務(wù)就可以了,而不用考慮和中斷控制器硬件相關(guān)的處理。

我們先來(lái)看一下vector_irq數(shù)組是怎么初始化的。

linux-src/arch/x86/kernel/apic/vector.c

void lapic_online(void){ unsigned int vector;

lockdep_assert_held(&vector_lock);

/* Online the vector matrix array for this CPU */ irq_matrix_online(vector_matrix);

/* * The interrupt affinity logic never targets interrupts to offline

* CPUs. The exception are the legacy PIC interrupts. In general

* they are only targeted to CPU0, but depending on the platform

* they can be distributed to any online CPU in hardware. The

* kernel has no influence on that. So all active legacy vectors * must be installed on all CPUs. All non legacy interrupts can be

* cleared. */ for (vector = 0; vector 《 NR_VECTORS; vector++)

this_cpu_write(vector_irq[vector], __setup_vector_irq(vector));}

static struct irq_desc *__setup_vector_irq(int vector){ int isairq = vector - ISA_IRQ_VECTOR(0);

/* Check whether the irq is in the legacy space */ if (isairq 《 0 || isairq 》= nr_legacy_irqs())

return VECTOR_UNUSED; /* Check whether the irq is handled by the IOAPIC */ if (test_bit(isairq, &io_apic_irqs))

return VECTOR_UNUSED; return irq_to_desc(isairq);}

linux-src/kernel/irq/irqdesc.c

struct irq_desc *irq_to_desc(unsigned int irq){return radix_tree_lookup(&irq_desc_tree, irq);}

可以看出vector_irq數(shù)組的初始化數(shù)據(jù)是從irq_desc_tree來(lái)的,我們?cè)賮?lái)看一下irq_desc_tree是怎么初始化的。

linux-src/kernel/irq/irqdesc.c

int __init early_irq_init(void){ int i, initcnt, node = first_online_node; struct irq_desc *desc;

init_irq_default_affinity();

/* Let arch update nr_irqs and return the nr of preallocated irqs */ initcnt = arch_probe_nr_irqs();

printk(KERN_INFO “NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d

”, NR_IRQS, nr_irqs, initcnt);

if (WARN_ON(nr_irqs 》 IRQ_BITMAP_BITS)) nr_irqs = IRQ_BITMAP_BITS;

if (WARN_ON(initcnt 》 IRQ_BITMAP_BITS)) initcnt = IRQ_BITMAP_BITS;

if (initcnt 》 nr_irqs) nr_irqs = initcnt;

for (i = 0; i 《 initcnt; i++) {

desc = alloc_desc(i, node, 0, NULL, NULL);

set_bit(i, allocated_irqs);

irq_insert_desc(i, desc); } return arch_early_irq_init();}

可以看到vector_irq數(shù)組的內(nèi)容是在系統(tǒng)初始化的時(shí)候通過(guò)alloc_desc函數(shù)為每個(gè)irq進(jìn)行分配的。在alloc_desc中對(duì)irq_desc的初始化會(huì)把handle_irq函數(shù)指針默認(rèn)初始化為handle_bad_irq,這個(gè)函數(shù)代表還沒(méi)有中斷控制器注冊(cè)這個(gè)函數(shù),handle_bad_irq只是簡(jiǎn)單地確認(rèn)一下中斷,然后做個(gè)錯(cuò)誤記錄。

中斷控制器注冊(cè)handle_irq函數(shù)的代碼如下:

linux-src/kernel/irq/chip.c

void__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,

const char *name){ unsigned long flags; struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

if (!desc) return;

__irq_do_set_handler(desc, handle, is_chained, name); irq_put_desc_busunlock(desc, flags);}

static void__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,

int is_chained, const char *name){ if (!handle) {

handle = handle_bad_irq; } else { struct irq_data *irq_data = &desc-》irq_data;#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY

/* * With hierarchical domains we might run into a * situation where the outermost chip is not yet set

* up, but the inner chips are there. Instead of * bailing we install the handler, but obviously we

* cannot enable/startup the interrupt at this point.

*/ while (irq_data) {

if (irq_data-》chip != &no_irq_chip) break;

/* * Bail out if the outer chip is not set up

* and the interrupt supposed to be started * right away.

*/ if (WARN_ON(is_chained)) return; /* Try the parent */

irq_data = irq_data-》parent_data; }#endif if (WARN_ON(!irq_data || irq_data-》chip == &no_irq_chip))

return; }

/* Uninstall? */ if (handle == handle_bad_irq) {

if (desc-》irq_data.chip != &no_irq_chip) mask_ack_irq(desc);

irq_state_set_disabled(desc); if (is_chained) desc-》action = NULL;

desc-》depth = 1; } desc-》handle_irq = handle; desc-》name = name;

if (handle != handle_bad_irq && is_chained) { unsigned int type = irqd_get_trigger_type(&desc-》irq_data);

/* * We‘re about to start this interrupt immediately,

* hence the need to set the trigger configuration.

* But the .set_type callback may have overridden the

* flow handler, ignoring that we’re dealing with a

* chained interrupt. Reset it immediately because we * do know better.

*/ if (type != IRQ_TYPE_NONE) {

__irq_set_trigger(desc, type);

desc-》handle_irq = handle; }

irq_settings_set_noprobe(desc);

irq_settings_set_norequest(desc);

irq_settings_set_nothread(desc);

desc-》action = &chained_action;

irq_activate_and_startup(desc, IRQ_RESEND); }}

不同的系統(tǒng)有不同的中斷控制器,其在啟動(dòng)初始化的時(shí)候都會(huì)去注冊(cè)irq_desc的handle_irq函數(shù)。

下面我們?cè)賮?lái)看一下具體的硬件驅(qū)動(dòng)應(yīng)該如何注冊(cè)自己設(shè)備的ISR:

linux-src/include/linux/interrupt.h

static inline int __must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

const char *name, void *dev){ return request_threaded_irq(irq, handler, NULL, flags, name, dev);}

linux-src/kernel/irq/manage.c

int request_threaded_irq(unsigned int irq, irq_handler_t handler,

irq_handler_t thread_fn, unsigned long irqflags,

const char *devname, void *dev_id);

驅(qū)動(dòng)程序使用request_irq接口來(lái)注冊(cè)自己的ISR,ISR就是運(yùn)行在硬中斷的,參數(shù)handler代表的就是ISR。request_irq又調(diào)用request_threaded_irq來(lái)實(shí)現(xiàn)自己。request_threaded_irq是用來(lái)創(chuàng)建中斷線(xiàn)程的函數(shù)接口,其中有兩個(gè)參數(shù)handler、thread_fn,都是函數(shù)指針,handler代表的是ISR,是進(jìn)行中斷預(yù)處理的,thread_fn代表的是要?jiǎng)?chuàng)建的中斷線(xiàn)程的入口函數(shù),是進(jìn)行中斷后處理的。中斷線(xiàn)程的細(xì)節(jié)我們?cè)?.5中斷線(xiàn)程中再細(xì)講。

我們?cè)賮?lái)總結(jié)一下外設(shè)中斷的處理方式。外設(shè)中斷的向量表?xiàng)l目都被統(tǒng)一設(shè)置到同一個(gè)函數(shù)common_interrupt。在函數(shù)common_interrupt中又會(huì)根據(jù)irq參數(shù)去一個(gè)類(lèi)型為irq_desc的vector_irq數(shù)組中尋找其對(duì)應(yīng)的irq_desc,并用irq_desc的handle_irq來(lái)處理這個(gè)中斷。vector_irq數(shù)組是在系統(tǒng)啟動(dòng)時(shí)初始化的,每個(gè)irq_desc的handle_irq都是中斷控制器初始化時(shí)設(shè)置的,handle_irq的處理是和中斷控制器密切相關(guān)的。具體的硬件驅(qū)動(dòng)會(huì)通過(guò)request_irq接口來(lái)注冊(cè)ISR,每個(gè)ISR都會(huì)生成一個(gè)irqaction,這個(gè)irqaction會(huì)掛在irq_desc的鏈表上。這樣中斷發(fā)生時(shí)handle_irq就可以去執(zhí)行與irq相對(duì)應(yīng)的每個(gè)ISR了。

5.3 軟中斷(softirq)

軟中斷是把中斷處理程序分成了兩段:前一段叫做硬中斷,執(zhí)行驅(qū)動(dòng)的ISR,處理與硬件密切相關(guān)的事,在此期間是禁止中斷的;后一段叫做軟中斷,軟中斷中處理和硬件不太密切的事物,在此期間是開(kāi)中斷的,可以繼續(xù)接受硬件中斷。軟中斷的設(shè)計(jì)提高了系統(tǒng)對(duì)中斷的響應(yīng)性。下面我們先說(shuō)軟中斷的執(zhí)行時(shí)機(jī),然后再說(shuō)軟中斷的使用接口。

軟中斷也是中斷處理程序的一部分,是在ISR執(zhí)行完成之后運(yùn)行的,在ISR中可以向軟中斷中添加任務(wù),然后軟中斷有事要做就會(huì)運(yùn)行了。有些時(shí)候當(dāng)軟中斷過(guò)多,處理不過(guò)來(lái)的時(shí)候,也會(huì)喚醒ksoftirqd/x線(xiàn)程來(lái)執(zhí)行軟中斷。

linux-src/kernel/irq/irqdesc.c

int handle_domain_irq(struct irq_domain *domain,

unsigned int hwirq, struct pt_regs *regs){ struct pt_regs *old_regs = set_irq_regs(regs);

struct irq_desc *desc; int ret = 0;

irq_enter();

/* The irqdomain code provides boundary checks */

desc = irq_resolve_mapping(domain, hwirq); if (likely(desc))

handle_irq_desc(desc); else ret = -EINVAL;

irq_exit(); set_irq_regs(old_regs); return ret;}

linux-src/kernel/softirq.c

void irq_exit(void){ __irq_exit_rcu(); rcu_irq_exit(); /* must be last! */ lockdep_hardirq_exit();}

static inline void __irq_exit_rcu(void){#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED local_irq_disable();#else

lockdep_assert_irqs_disabled();#endif account_hardirq_exit(current); preempt_count_sub(HARDIRQ_OFFSET);

if (!in_interrupt() && local_softirq_pending()) invoke_softirq();

tick_irq_exit();}

static inline void invoke_softirq(void){ if (ksoftirqd_running(local_softirq_pending())) return;

if (!force_irqthreads() || !__this_cpu_read(ksoftirqd)) {#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK

/* * We can safely execute softirq on the current stack if * it is the irq stack, because it should be near empty

* at this stage. */ __do_softirq();#else /* * Otherwise, irq_exit() is called on the task stack that can

* be potentially deep already. So call softirq in its own stack

* to prevent from any overrun.

*/ do_softirq_own_stack();#endif } else { wakeup_softirqd(); }}

asmlinkage __visible void __softirq_entry __do_softirq(void){ unsigned long end = jiffies + MAX_SOFTIRQ_TIME; unsigned long old_flags = current-》flags; int max_restart = MAX_SOFTIRQ_RESTART;

struct softirq_action *h;

bool in_hardirq; __u32 pending; int softirq_bit;

static int i = 0; if(++i == 50) dump_stack();

/* * Mask out PF_MEMALLOC as the current task context is borrowed for the * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC * again if the socket is related to swapping.

*/ current-》flags &= ~PF_MEMALLOC;

pending = local_softirq_pending();

softirq_handle_begin(); in_hardirq = lockdep_softirq_start(); account_softirq_enter(current);

restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0);

local_irq_enable();

h = softirq_vec;

while ((softirq_bit = ffs(pending))) { unsigned int vec_nr; int prev_count;

h += softirq_bit - 1;

vec_nr = h - softirq_vec; prev_count = preempt_count();

kstat_incr_softirqs_this_cpu(vec_nr);

trace_softirq_entry(vec_nr); h-》action(h); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { pr_err(“huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?

”, vec_nr, softirq_to_name[vec_nr], h-》action, prev_count, preempt_count()); preempt_count_set(prev_count); } h++; pending 》》= softirq_bit; }

if (!IS_ENABLED(CONFIG_PREEMPT_RT) && __this_cpu_read(ksoftirqd) == current) rcu_softirq_qs();

local_irq_disable();

pending = local_softirq_pending(); if (pending) { if (time_before(jiffies, end) && !need_resched() && --max_restart) goto restart;

wakeup_softirqd(); }

account_softirq_exit(current); lockdep_softirq_end(in_hardirq); softirq_handle_end(); current_restore_flags(old_flags, PF_MEMALLOC);}

可以看到__do_softirq在執(zhí)行軟中斷前會(huì)打開(kāi)中斷l(xiāng)ocal_irq_enable(),在執(zhí)行完軟中斷之后又會(huì)關(guān)閉中斷l(xiāng)ocal_irq_disable()。所以軟中斷執(zhí)行期間CPU是可以接收硬件中斷的。

下面我們?cè)賮?lái)看一下軟中斷的使用接口。軟中斷定義了一個(gè)softirq_action類(lèi)型的數(shù)組,數(shù)組大小是NR_SOFTIRQS,代表軟中斷的類(lèi)型,目前只有10種軟中斷類(lèi)型。softirq_action結(jié)構(gòu)體里面僅僅只有一個(gè)函數(shù)指針。當(dāng)我們要設(shè)置某一類(lèi)軟中斷的處理函數(shù)時(shí)使用接口open_softirq。當(dāng)我們想要觸發(fā)某一類(lèi)軟中斷的執(zhí)行時(shí)使用接口raise_softirq。

下面我們來(lái)看一下代碼:

linux-src/include/linux/interrupt.h

enum{ HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ,

IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ,

RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS};

struct softirq_action{ void (*action)(struct softirq_action *);};

linux-src/kernel/softirq.c

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

void open_softirq(int nr, void (*action)(struct softirq_action *)){ softirq_vec[nr].action = action;}

void raise_softirq(unsigned int nr){ unsigned long flags;

local_irq_save(flags); raise_softirq_irqoff(nr); local_irq_restore(flags);}

inline void raise_softirq_irqoff(unsigned int nr){ __raise_softirq_irqoff(nr);

/* * If we‘re in an interrupt or softirq, we’re done * (this also catches softirq-disabled code)。 We will

* actually run the softirq once we return from * the irq or softirq. * * Otherwise we wake up ksoftirqd to make sure we

* schedule the softirq soon. */ if (!in_interrupt() && should_wake_ksoftirqd()) wakeup_softirqd();}

void __raise_softirq_irqoff(unsigned int nr){ lockdep_assert_irqs_disabled(); trace_softirq_raise(nr);

or_softirq_pending(1UL 《《 nr);}

所有軟中斷的處理函數(shù)都是在系統(tǒng)啟動(dòng)的初始化函數(shù)里面用open_softirq接口設(shè)置的。raise_softirq一般是在硬中斷或者軟中斷中用來(lái)往軟中斷上push work使得軟中斷可以被觸發(fā)執(zhí)行或者繼續(xù)執(zhí)行。

5.4 微任務(wù)(tasklet)

新代碼要想使用softirq就必須修改內(nèi)核的核心代碼,添加新的softirq類(lèi)型,這對(duì)于很多驅(qū)動(dòng)程序來(lái)說(shuō)是做不到的,于是內(nèi)核在softirq的基礎(chǔ)上開(kāi)發(fā)了tasklet。使用tasklet不需要修改內(nèi)核的核心代碼,驅(qū)動(dòng)程序直接使用tasklet的接口就可以了。

Tasklet其實(shí)是一種特殊的softirq,它是在softirq的基礎(chǔ)上進(jìn)行了擴(kuò)展。它利用的就是softirq中的HI_SOFTIRQ和TASKLET_SOFTIRQ。softirq在初始化的時(shí)候會(huì)設(shè)置這兩個(gè)softirq類(lèi)型。然后其處理函數(shù)會(huì)去處理tasklet的鏈表。我們?cè)谑褂胻asklet的時(shí)候只需要定義一個(gè)tasklet_struct,并用我們想要執(zhí)行的函數(shù)初始化它,然后再用tasklet_schedule把它放入到隊(duì)列中,它就會(huì)被執(zhí)行了。下面我們來(lái)看一下代碼:

linux-src/kernel/softirq.c

void __init softirq_init(void){ int cpu;

for_each_possible_cpu(cpu) { per_cpu(tasklet_vec, cpu).tail =

&per_cpu(tasklet_vec, cpu).head; per_cpu(tasklet_hi_vec, cpu).tail =

&per_cpu(tasklet_hi_vec, cpu).head; }

open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action);}

static __latent_entropy void tasklet_action(struct softirq_action *a){

tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);}

static __latent_entropy void tasklet_hi_action(struct softirq_action *a){

tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);}

static void tasklet_action_common(struct softirq_action *a,

struct tasklet_head *tl_head,

unsigned int softirq_nr){ struct tasklet_struct *list;

local_irq_disable(); list = tl_head-》head;

tl_head-》head = NULL; tl_head-》tail = &tl_head-》head; local_irq_enable();

while (list) { struct tasklet_struct *t = list;

list = list-》next;

if (tasklet_trylock(t)) {

if (!atomic_read(&t-》count)) {

if (tasklet_clear_sched(t)) {

if (t-》use_callback)

t-》callback(t);

else t-》func(t-》data);

} tasklet_unlock(t);

continue; }

tasklet_unlock(t); }

local_irq_disable();

t-》next = NULL; *tl_head-》tail = t;

tl_head-》tail = &t-》next;

__raise_softirq_irqoff(softirq_nr);

local_irq_enable(); }}

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

static void __tasklet_schedule_common(struct tasklet_struct *t,

struct tasklet_head __percpu *headp,

unsigned int softirq_nr){ struct tasklet_head *head; unsigned long flags;

local_irq_save(flags);

head = this_cpu_ptr(headp); t-》next = NULL; *head-》tail = t;

head-》tail = &(t-》next);

raise_softirq_irqoff(softirq_nr); local_irq_restore(flags);}

void __tasklet_schedule(struct tasklet_struct *t){ __tasklet_schedule_common(t, &tasklet_vec,

TASKLET_SOFTIRQ);}EXPORT_SYMBOL(__tasklet_schedule);

void __tasklet_hi_schedule(struct tasklet_struct *t){ __tasklet_schedule_common(t, &tasklet_hi_vec,

HI_SOFTIRQ);}EXPORT_SYMBOL(__tasklet_hi_schedule);

linux-src/include/linux/interrupt.h

static inline void tasklet_schedule(struct tasklet_struct *t){

if (!test_and_set_bit(TASKLET_STATE_SCHED, &t-》state))

__tasklet_schedule(t);}

static inline void tasklet_hi_schedule(struct tasklet_struct *t){

if (!test_and_set_bit(TASKLET_STATE_SCHED, &t-》state))

__tasklet_hi_schedule(t);}

Tasklet和softirq有一個(gè)很大的區(qū)別就是,同一個(gè)softirq可以在不同的CPU上并發(fā)執(zhí)行,而同一個(gè)tasklet不會(huì)在多個(gè)CPU上并發(fā)執(zhí)行。所以我們?cè)?a target="_blank">編程的時(shí)候,如果使用的是tasklet就不用考慮多CPU之間的同步問(wèn)題。

還有很重要的一點(diǎn),tasklet不是獨(dú)立的,它是softirq的一部分,禁用軟中斷的同時(shí)也禁用了tasklet。

5.5 中斷線(xiàn)程(threaded_irq)

前面講的硬中斷,它是外設(shè)中斷處理中必不可少的一部分。Softirq和tasklet雖然不會(huì)禁用中斷,提高了系統(tǒng)對(duì)中斷的響應(yīng)性,但是softirq的執(zhí)行優(yōu)先級(jí)還是比進(jìn)程的優(yōu)先級(jí)高,有些確實(shí)不那么重要的任務(wù)其實(shí)可以放到進(jìn)程里執(zhí)行,和普通進(jìn)程共同競(jìng)爭(zhēng)CPU。而且軟中斷里不能調(diào)用會(huì)阻塞、休眠的函數(shù),這對(duì)軟中斷函數(shù)的編程是很不利的,所以綜合各種因素,我們需要把中斷處理任務(wù)中的與硬件無(wú)關(guān)有不太緊急的部分放到進(jìn)程里面來(lái)做。為此內(nèi)核開(kāi)發(fā)了兩種方法,中斷線(xiàn)程和工作隊(duì)列。

我們這節(jié)先講中斷線(xiàn)程,其接口如下:

linux-src/include/linux/interrupt.h

extern int __must_checkrequest_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev);

如果我們要為某個(gè)外設(shè)注冊(cè)中斷處理程序,可以使用這個(gè)接口。其中handler是硬中斷,是處理與硬件密切相關(guān)的事物。其處理完成后,可以把接收到的數(shù)據(jù)、要繼續(xù)處理的事情放到某個(gè)位置,然后返回是否需要喚醒對(duì)應(yīng)的中斷線(xiàn)程。如果需要的話(huà),系統(tǒng)會(huì)喚醒其對(duì)應(yīng)的中斷線(xiàn)程來(lái)繼續(xù)處理任務(wù),這個(gè)線(xiàn)程的主函數(shù)就是第三個(gè)參數(shù)thread_fn。下面我們來(lái)看一下這個(gè)接口的實(shí)現(xiàn)。

linux-src/kernel/irq/manage.c

int request_threaded_irq(unsigned int irq, irq_handler_t handler,

irq_handler_t thread_fn, unsigned long irqflags,

const char *devname, void *dev_id){ struct irqaction *action; struct irq_desc *desc; int retval;

if (irq == IRQ_NOTCONNECTED)

return -ENOTCONN;

/* * Sanity-check: shared interrupts must pass in a real dev-ID,

* otherwise we‘ll have trouble later trying to figure out

* which interrupt is which (messes up the interrupt freeing

* logic etc)。

* * Also shared interrupts do not go well with disabling auto enable.

* The sharing interrupt might request it while it’s still disabled

* and then wait for interrupts forever.

* * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and

* it cannot be set along with IRQF_NO_SUSPEND.

*/ if (((irqflags & IRQF_SHARED) && !dev_id) ||

((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||

(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||

((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))

return -EINVAL;

desc = irq_to_desc(irq); if (!desc)

return -EINVAL;

if (!irq_settings_can_request(desc) ||

WARN_ON(irq_settings_is_per_cpu_devid(desc)))

return -EINVAL;

if (!handler) {

if (!thread_fn)

return -EINVAL;

handler = irq_default_primary_handler;

}

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

if (!action)

return -ENOMEM;

action-》handler = handler; action-》thread_fn = thread_fn;

action-》flags = irqflags; action-》name = devname;

action-》dev_id = dev_id;

retval = irq_chip_pm_get(&desc-》irq_data);

if (retval 《 0) {

kfree(action);

return retval; }

retval = __setup_irq(irq, desc, action);

if (retval) {

irq_chip_pm_put(&desc-》irq_data);

kfree(action-》secondary);

kfree(action); }

#ifdef CONFIG_DEBUG_SHIRQ_FIXME if (!retval && (irqflags & IRQF_SHARED)) {

/* * It‘s a shared IRQ -- the driver ought to be prepared for it

* to happen immediately, so let’s make sure.。..

* We disable the irq to make sure that a ‘real’ IRQ doesn‘t

* run in parallel with our fake.

*/ unsigned long flags;

disable_irq(irq);

local_irq_save(flags);

handler(irq, dev_id);

local_irq_restore(flags);

enable_irq(irq);

}#endif return retval;}

static int__setup_irq(unsigned int irq, struct irq_desc *desc,

struct irqaction *new){ struct irqaction *old, **old_ptr;

unsigned long flags, thread_mask = 0; int ret, nested, shared = 0;

if (!desc)

return -EINVAL;

if (desc-》irq_data.chip == &no_irq_chip)

return -ENOSYS; if (!try_module_get(desc-》owner))

return -ENODEV;

new-》irq = irq;

/* * If the trigger type is not specified by the caller,

* then use the default for this interrupt.

*/ if (?。╪ew-》flags & IRQF_TRIGGER_MASK))

new-》flags |= irqd_get_trigger_type(&desc-》irq_data);

/* * Check whether the interrupt nests into another interrupt

* thread. */ nested = irq_settings_is_nested_thread(desc); if (nested) {

if (!new-》thread_fn) {

ret = -EINVAL;

goto out_mput; }

/* * Replace the primary handler which was provided from

* the driver for non nested interrupt handling by the

* dummy function which warns when called. */

new-》handler = irq_nested_primary_handler; } else {

if (irq_settings_can_thread(desc)) {

ret = irq_setup_forced_threading(new);

if (ret)

goto out_mput; } }

/* * Create a handler thread when a thread function is supplied

* and the interrupt does not nest into another interrupt

* thread.

*/ if (new-》thread_fn && !nested) {

ret = setup_irq_thread(new, irq, false);

if (ret)

goto out_mput;

if (new-》secondary) {

ret = setup_irq_thread(new-》secondary, irq, true);

if (ret)

goto out_thread;

} }

/* * Drivers are often written to work w/o knowledge about the

* underlying irq chip implementation, so a request for a

* threaded irq without a primary hard irq context handler

* requires the ONESHOT flag to be set. Some irq chips like

* MSI based interrupts are per se one shot safe. Check the

* chip flags, so we can avoid the unmask dance at the end of

* the threaded handler for those.

*/ if (desc-》irq_data.chip-》flags & IRQCHIP_ONESHOT_SAFE)

new-》flags &= ~IRQF_ONESHOT;

/* * Protects against a concurrent __free_irq() call which might wait

* for synchronize_hardirq() to complete without holding the optional

* chip bus lock and desc-》lock. Also protects against handing out

* a recycled oneshot thread_mask bit while it’s still in use by

* its previous owner.

*/ mutex_lock(&desc-》request_mutex);

/* * Acquire bus lock as the irq_request_resources() callback below

* might rely on the serialization or the magic power management

* functions which are abusing the irq_bus_lock() callback,

*/ chip_bus_lock(desc);

/* First installed action requests resources.

*/ if (!desc-》action) {

ret = irq_request_resources(desc);

if (ret) {

pr_err(“Failed to request resources for %s (irq %d) on irqchip %s

”, new-》name, irq, desc-》irq_data.chip-》name);

goto out_bus_unlock;

} }

/* * The following block of code has to be executed atomically

* protected against a concurrent interrupt and any of the other

* management calls which are not serialized via

* desc-》request_mutex or the optional bus lock.

*/ raw_spin_lock_irqsave(&desc-》lock, flags);

old_ptr = &desc-》action; old = *old_ptr; if (old) {

/* * Can‘t share interrupts unless both agree to and are

* the same type (level, edge, polarity)。 So both flag

* fields must have IRQF_SHARED set and the bits which

* set the trigger type must match. Also all must

* agree on ONESHOT.

* Interrupt lines used for NMIs cannot be shared.

*/ unsigned int oldtype;

if (desc-》istate & IRQS_NMI) {

pr_err(“Invalid attempt to share NMI for %s (irq %d) on irqchip %s.

”, new-》name, irq, desc-》irq_data.chip-》name);

ret = -EINVAL;

goto out_unlock; }

/* * If nobody did set the configuration before, inherit

* the one provided by the requester.

*/ if (irqd_trigger_type_was_set(&desc-》irq_data)) {

oldtype = irqd_get_trigger_type(&desc-》irq_data); } else {

oldtype = new-》flags & IRQF_TRIGGER_MASK;

irqd_set_trigger_type(&desc-》irq_data, oldtype);

}

if (!((old-》flags & new-》flags) & IRQF_SHARED) ||

(oldtype != (new-》flags & IRQF_TRIGGER_MASK)) ||

((old-》flags ^ new-》flags) & IRQF_ONESHOT))

goto mismatch;

/* All handlers must agree on per-cpuness */

if ((old-》flags & IRQF_PERCPU) !=

(new-》flags & IRQF_PERCPU))

goto mismatch;

/* add new interrupt at end of irq queue */

do {

/*

* Or all existing action-》thread_mask bits,

* so we can find the next zero bit for this

* new action. */

thread_mask |= old-》thread_mask;

old_ptr = &old-》next;

old = *old_ptr;

} while (old);

shared = 1; }

/* * Setup the thread mask for this irqaction for ONESHOT. For

* !ONESHOT irqs the thread mask is 0 so we can avoid a

* conditional in irq_wake_thread()。

*/ if (new-》flags & IRQF_ONESHOT) {

/* * Unlikely to have 32 resp 64 irqs sharing one line,

* but who knows. */ if (thread_mask == ~0UL) {

ret = -EBUSY; goto out_unlock; }

/* * The thread_mask for the action is or’ed to

* desc-》thread_active to indicate that the

* IRQF_ONESHOT thread handler has been woken, but not

* yet finished. The bit is cleared when a thread

* completes. When all threads of a shared interrupt

* line have completed desc-》threads_active becomes

* zero and the interrupt line is unmasked. See

* handle.c:irq_wake_thread() for further information. *

* If no thread is woken by primary (hard irq context)

* interrupt handlers, then desc-》threads_active is

* also checked for zero to unmask the irq line in the

* affected hard irq flow handlers

* (handle_[fasteoi|level]_irq)。

* * The new action gets the first zero bit of

* thread_mask assigned. See the loop above which or‘s

* all existing action-》thread_mask bits.

*/ new-》thread_mask = 1UL 《《 ffz(thread_mask);

} else if (new-》handler == irq_default_primary_handler &&

?。╠esc-》irq_data.chip-》flags & IRQCHIP_ONESHOT_SAFE)) {

/*

* The interrupt was requested with handler = NULL, so

* we use the default primary handler for it. But it

* does not have the oneshot flag set. In combination

* with level interrupts this is deadly, because the

* default primary handler just wakes the thread, then

* the irq lines is reenabled, but the device still

* has the level irq asserted. Rinse and repeat.。.. *

* While this works for edge type interrupts, we play

* it safe and reject unconditionally because we can’t

* say for sure which type this interrupt really

* has. The type flags are unreliable as the

* underlying chip implementation can override them.

*/ pr_err(“Threaded irq requested with handler=NULL and !ONESHOT for %s (irq %d)

”, new-》name, irq);

ret = -EINVAL;

goto out_unlock; }

if (!shared) {

init_waitqueue_head(&desc-》wait_for_threads);

/* Setup the type (level, edge polarity) if configured: */

if (new-》flags & IRQF_TRIGGER_MASK) {

ret = __irq_set_trigger(desc,

new-》flags & IRQF_TRIGGER_MASK);

if (ret)

goto out_unlock; }

/* * Activate the interrupt. That activation must happen

* independently of IRQ_NOAUTOEN. request_irq() can fail

* and the callers are supposed to handle

* that. enable_irq() of an interrupt requested with

* IRQ_NOAUTOEN is not supposed to fail. The activation

* keeps it in shutdown mode, it merily associates

* resources if necessary and if that‘s not possible it

* fails. Interrupts which are in managed shutdown mode

* will simply ignore that activation request.

*/ ret = irq_activate(desc);

if (ret)

goto out_unlock;

desc-》istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED |

IRQS_ONESHOT | IRQS_WAITING);

irqd_clear(&desc-》irq_data, IRQD_IRQ_INPROGRESS);

if (new-》flags & IRQF_PERCPU) {

irqd_set(&desc-》irq_data, IRQD_PER_CPU);

irq_settings_set_per_cpu(desc);

if (new-》flags & IRQF_NO_DEBUG)

irq_settings_set_no_debug(desc); }

if (noirqdebug)

irq_settings_set_no_debug(desc);

if (new-》flags & IRQF_ONESHOT)

desc-》istate |= IRQS_ONESHOT;

/* Exclude IRQ from balancing if requested */

if (new-》flags & IRQF_NOBALANCING) {

irq_settings_set_no_balancing(desc);

irqd_set(&desc-》irq_data, IRQD_NO_BALANCING); }

if (!(new-》flags & IRQF_NO_AUTOEN) &&

irq_settings_can_autoenable(desc)) {

irq_startup(desc, IRQ_RESEND, IRQ_START_COND);

} else {

/*

* Shared interrupts do not go well with disabling

* auto enable. The sharing interrupt might request

* it while it’s still disabled and then wait for

* interrupts forever. */

WARN_ON_ONCE(new-》flags & IRQF_SHARED);

/* Undo nested disables: */

desc-》depth = 1; }

} else if (new-》flags & IRQF_TRIGGER_MASK) {

unsigned int nmsk = new-》flags & IRQF_TRIGGER_MASK;

unsigned int omsk = irqd_get_trigger_type(&desc-》irq_data);

if (nmsk != omsk)

/* hope the handler works with current trigger mode */

pr_warn(“irq %d uses trigger mode %u; requested %u

”, irq, omsk, nmsk); }

*old_ptr = new;

irq_pm_install_action(desc, new);

/* Reset broken irq detection when installing new handler */

desc-》irq_count = 0;

desc-》irqs_unhandled = 0;

/* * Check whether we disabled the irq via the spurious handler

* before. Reenable it and give it another chance.

*/ if (shared && (desc-》istate & IRQS_SPURIOUS_DISABLED)) {

desc-》istate &= ~IRQS_SPURIOUS_DISABLED;

__enable_irq(desc); }

raw_spin_unlock_irqrestore(&desc-》lock, flags);

chip_bus_sync_unlock(desc); mutex_unlock(&desc-》request_mutex);

irq_setup_timings(desc, new);

/* * Strictly no need to wake it up, but hung_task complains

* when no hard interrupt wakes the thread up.

*/ if (new-》thread)

wake_up_process(new-》thread);

if (new-》secondary)

wake_up_process(new-》secondary-》thread);

register_irq_proc(irq, desc); new-》dir = NULL; register_handler_proc(irq, new); return 0;

mismatch: if (?。╪ew-》flags & IRQF_PROBE_SHARED)) {

pr_err(“Flags mismatch irq %d. %08x (%s) vs. %08x (%s)

”, irq, new-》flags, new-》name, old-》flags, old-》name);#ifdef CONFIG_DEBUG_SHIRQ

dump_stack();#endif } ret = -EBUSY;

out_unlock: raw_spin_unlock_irqrestore(&desc-》lock, flags);

if (!desc-》action) irq_release_resources(desc);out_bus_unlock:

chip_bus_sync_unlock(desc); mutex_unlock(&desc-》request_mutex);

out_thread: if (new-》thread) { struct task_struct *t = new-》thread;

new-》thread = NULL; kthread_stop(t); put_task_struct(t);

} if (new-》secondary && new-》secondary-》thread) {

struct task_struct *t = new-》secondary-》thread;

new-》secondary-》thread = NULL; kthread_stop(t);

put_task_struct(t); }out_mput: module_put(desc-》owner); return ret;}

static intsetup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary){ struct task_struct *t;

if (!secondary) { t = kthread_create(irq_thread, new, “irq/%d-%s”, irq,

new-》name); } else {

t = kthread_create(irq_thread, new, “irq/%d-s-%s”, irq,

new-》name); }

if (IS_ERR(t)) return PTR_ERR(t);

sched_set_fifo(t);

/* * We keep the reference to the task struct even if

* the thread dies to avoid that the interrupt code

* references an already freed task_struct.

*/ new-》thread = get_task_struct(t);

/* * Tell the thread to set its affinity. This is

* important for shared interrupt handlers as we do

* not invoke setup_affinity() for the secondary

* handlers as everything is already set up. Even for

* interrupts marked with IRQF_NO_BALANCE this is

* correct as we want the thread to move to the cpu(s)

* on which the requesting code placed the interrupt.

*/ set_bit(IRQTF_AFFINITY, &new-》thread_flags); return 0;}

中斷線(xiàn)程雖然實(shí)現(xiàn)很復(fù)雜,但是其使用接口還是很簡(jiǎn)單的。

5.6 工作隊(duì)列(workqueue)

工作隊(duì)列是內(nèi)核中使用最廣泛的線(xiàn)程化中斷處理機(jī)制。系統(tǒng)中有一些默認(rèn)的工作隊(duì)列,你也可以創(chuàng)建自己的工作隊(duì)列,工作隊(duì)列背后對(duì)應(yīng)的是內(nèi)核線(xiàn)程。你可以創(chuàng)建一個(gè)work,然后push到某個(gè)工作隊(duì)列,然后這個(gè)工作隊(duì)列背后的內(nèi)核線(xiàn)程就會(huì)去執(zhí)行這些work。下面我們來(lái)看一下工作隊(duì)列的接口。

linux-src/include/linux/workqueue.h

struct work_struct { atomic_long_t data;

struct list_head entry;

work_func_t func;#ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map;#endif};

#define DECLARE_WORK(n, f)

struct work_struct n = __WORK_INITIALIZER(n, f)

#define __WORK_INITIALIZER(n, f) {

.data = WORK_DATA_STATIC_INIT(),

.entry = { &(n).entry, &(n).entry },

.func = (f),

__WORK_INIT_LOCKDEP_MAP(#n, &(n))

}

static inline bool schedule_work(struct work_struct *work){ return queue_work(system_wq, work);}

static inline bool schedule_work_on(int cpu, struct work_struct *work){ return queue_work_on(cpu, system_wq, work);}

這是創(chuàng)建work,把work push到系統(tǒng)默認(rèn)的工作隊(duì)列上的接口,下面我們?cè)賮?lái)看一下創(chuàng)建自己的工作隊(duì)列的接口:

linux-src/include/linux/workqueue.h

struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, 。..);

#define create_workqueue(name)

alloc_workqueue(“%s”, __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))

工作隊(duì)列還有很多很豐富的接口,這里就不一一介紹了。

關(guān)于工作隊(duì)列的實(shí)現(xiàn)原理,推薦閱讀:

http://www.wowotech.net/irq_subsystem/workqueue.html

http://www.wowotech.net/irq_subsystem/cmwq-intro.html

http://www.wowotech.net/irq_subsystem/alloc_workqueue.html

http://www.wowotech.net/irq_subsystem/queue_and_handle_work.html

六、中斷與同步

在只有線(xiàn)程的情況下,線(xiàn)程之間的同步邏輯還是很好理解的,但是有了中斷之后,硬中斷、軟中斷、線(xiàn)程相互之間的同步就變得復(fù)雜起來(lái)。下面我們就來(lái)看一下它們?cè)谶\(yùn)行的時(shí)候相互之間的搶占關(guān)系。

6.1 CPU運(yùn)行模型

首先我們來(lái)看一下CPU最原始的運(yùn)行模型,圖靈機(jī)模型,非常簡(jiǎn)單,就是一條直線(xiàn)一直運(yùn)行下去。

在圖靈機(jī)上加入中斷之后,CPU的運(yùn)行模型也是比較簡(jiǎn)單的。但是當(dāng)我們考慮軟件中斷、硬件中斷的區(qū)別時(shí),CPU運(yùn)行模型就開(kāi)始變得復(fù)雜起來(lái)了。

不同的中斷類(lèi)型使得中斷執(zhí)行流有了不同的類(lèi)型,這里一共分為三種類(lèi)型,系統(tǒng)調(diào)用、CPU異常、硬件中斷?,F(xiàn)在這個(gè)還不算復(fù)雜,下面我們看一下它們之間的搶占情形。

在系統(tǒng)調(diào)用時(shí)會(huì)發(fā)生CPU異常,也可能會(huì)發(fā)生硬件中斷,在CPU異常的時(shí)候也可能發(fā)生硬件中斷。其實(shí)這三者也可以嵌套起來(lái),請(qǐng)看下圖:

系統(tǒng)調(diào)用時(shí)發(fā)生了CPU異常,CPU異常時(shí)發(fā)生了硬件中斷。下面我們把硬件中斷的處理過(guò)程分為硬中斷和軟中斷兩部分,看看它們之間的關(guān)系。

硬件中斷的前半部分是硬中斷,后半部分是軟中斷,硬中斷中不能再嵌套硬中斷了,但是軟中斷中可以嵌套硬中斷。不過(guò)嵌套的硬中斷在返回時(shí)發(fā)現(xiàn)正在執(zhí)行軟中斷,就不會(huì)再重新還行軟中斷了,而是會(huì)回到原來(lái)的軟中斷執(zhí)行流中。軟中斷的執(zhí)行還有一種情況,如下圖所示:

這是因?yàn)榫€(xiàn)程在其臨界區(qū)中禁用了軟中斷,如果臨界區(qū)中發(fā)生了硬中斷還是會(huì)執(zhí)行的,但是硬中斷返回時(shí)不會(huì)去執(zhí)行軟中斷,因?yàn)檐浿袛啾唤昧恕.?dāng)線(xiàn)程的臨界區(qū)結(jié)束是會(huì)再打開(kāi)軟中斷,此時(shí)發(fā)現(xiàn)有pending的軟中斷沒(méi)有處理,就會(huì)去執(zhí)行軟中斷。

還有一種比較特殊的情況,就是線(xiàn)程里套軟中斷,軟中斷里套硬中斷,硬中斷里套NMI中斷,如下圖所示:

首先軟中斷是不能獨(dú)立觸發(fā)的,必須是硬中斷觸發(fā)軟中斷。在圖中,第一個(gè)硬中斷是執(zhí)行完成了的,然后在軟中斷的執(zhí)行過(guò)程中又發(fā)生了硬中斷,第二個(gè)硬中斷還沒(méi)執(zhí)行完的時(shí)候在執(zhí)行過(guò)程中的時(shí)候又發(fā)生了NMI中斷。這樣就發(fā)生了四個(gè)不同等級(jí)的執(zhí)行流一一嵌套的情況,這也是隊(duì)列自旋鎖的鎖節(jié)點(diǎn)為啥要乘以4的原因。

6.2 中斷相關(guān)同步方法

軟中斷可以搶占線(xiàn)程,硬中斷可以搶占軟中斷也可以搶占線(xiàn)程,而返回來(lái)則不能搶占,所以如果我們的低等級(jí)執(zhí)行流代碼和高等級(jí)執(zhí)行流代碼有同步問(wèn)題的話(huà),就要考慮禁用高等級(jí)執(zhí)行流。下面我們來(lái)看一下它們的接口,首先看禁用硬中斷:

linux-src/include/linux/irqflags.h

#define local_irq_enable()

do { raw_local_irq_enable(); } while (0)#define local_irq_disable()

do { raw_local_irq_disable(); } while (0)#define local_irq_save(flags)

do { raw_local_irq_save(flags);

} while (0)#define local_irq_restore(flags)

do { raw_local_irq_restore(flags);

} while (0)

linux-src/include/linux/interrupt.h

extern void disable_irq_nosync(unsigned int irq);extern bool disable_hardirq(unsigned int irq);

extern void disable_irq(unsigned int irq);extern void disable_percpu_irq(unsigned int irq);

extern void enable_irq(unsigned int irq);extern void enable_percpu_irq(unsigned

int

irq,

unsigned int type);

你可以在一個(gè)CPU上禁用所有中斷,也可以在所有CPU上禁用某個(gè)硬件中斷,但是你不能在所有CPU上同時(shí)禁用所有硬件中斷。

再來(lái)看一下禁用軟中斷的接口:

linux-src/include/linux/bottom_half.h

static inline void local_bh_disable(void){ __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);}

static inline void local_bh_enable(void){ __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);}

我們只能禁用本地CPU的軟中斷,而且是整體禁用,不能只禁用某一類(lèi)型的軟中斷。雖然在Linux中,下半部bh包括所有的下半部,但是此處的bh僅僅指軟中斷(包括tasklet),不包括中斷線(xiàn)程和工作隊(duì)列。

七、總結(jié)回顧

本文我們從中斷的概念開(kāi)始講起,一路上分析了中斷的作用、中斷的產(chǎn)生、中斷的處理。其中內(nèi)容最多的是硬件中斷的處理,方法很多很繁雜。從6.1節(jié)CPU運(yùn)行模型中,我們可以看到中斷對(duì)于推動(dòng)整個(gè)系統(tǒng)運(yùn)行的重要性。所以說(shuō)中斷機(jī)制是計(jì)算機(jī)系統(tǒng)的神經(jīng)和脈搏,一點(diǎn)都不為過(guò)。想要學(xué)會(huì)Linux內(nèi)核,弄明白中斷機(jī)制是其中必不可少的一環(huán)。最后我們?cè)賮?lái)看一下中斷機(jī)制的圖:

參考文獻(xiàn):

《Linux Kernel Development》

《Understanding the Linux Kernel》

《Professional Linux Kernel Architecture》

《Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3》

《Interrupt in Linux (硬件篇)》

編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10770

    瀏覽量

    210428
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11161

    瀏覽量

    208468

原文標(biāo)題:深入理解Linux中斷機(jī)制

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    多方位對(duì)比ARM和x86 CPU兩大架構(gòu)現(xiàn)在發(fā)展如何?

    隨便逮住一個(gè)人問(wèn)他知不知道CPU,我想他的答案一定會(huì)是肯定的,但是如果你再問(wèn)他知道ARM和X86架構(gòu)么?這兩者的區(qū)別又是什么?絕大多數(shù)的人肯定是一臉懵逼。今天小編就帶你深入了解CPU
    發(fā)表于 05-30 14:14 ?2541次閱讀

    深入了解CPU兩大架構(gòu)ARM與X86

    ARM和X86現(xiàn)在發(fā)展如何?關(guān)于X86架構(gòu)和ARM架構(gòu)這兩者誰(shuí)將統(tǒng)一市場(chǎng)的爭(zhēng)執(zhí)一直都有,但是也有人說(shuō)這兩者根本不具備可比性,X86無(wú)法做到A
    發(fā)表于 05-30 15:20 ?1.8w次閱讀

    x86自主新架構(gòu)呼之欲出!國(guó)產(chǎn)CPU性能或大幅提升

    的保障。 ? 圖源:兆芯 作為國(guó)內(nèi)x86?CPU的代表,兆芯也是全球除了英特爾、AMD外,唯一能夠制造x86架構(gòu)CPU企業(yè)。如果說(shuō)龍芯是國(guó)
    的頭像 發(fā)表于 01-10 07:18 ?7118次閱讀

    Powerpc架構(gòu)X86架構(gòu)的區(qū)別

    目錄1、ARM1.1 ARM歷史1.2 ARM內(nèi)核系列2、MIPS應(yīng)用范圍發(fā)展歷史3、PowerPC三巨頭4、X86架構(gòu)X86歷史5、PowerPC架構(gòu)相比于ARM的優(yōu)勢(shì)6、Power
    發(fā)表于 07-26 06:16

    為什么x86和arm的架構(gòu)不同,但是都能裝linux呢?

    為什么x86和arm的架構(gòu)不同,但是都能裝linux呢?他們的編譯時(shí)如何實(shí)現(xiàn)的?
    發(fā)表于 05-16 10:21

    探秘X86架構(gòu)CPU流水線(xiàn)

    探秘X86架構(gòu)CPU流水線(xiàn)
    發(fā)表于 01-14 12:19 ?25次下載

    x86 cpu詳細(xì)介紹 x86 cpu遵循原則與生產(chǎn)廠家

    雖然隨著CPU技術(shù)的不斷發(fā)展,Intel陸續(xù)研制出更新型的i80386、i80486直到今天的Pentium Ⅲ(以下簡(jiǎn)為PⅢ)系列,但為了保證電腦能繼續(xù)運(yùn)行以往開(kāi)發(fā)的各類(lèi)應(yīng)用程序保護(hù)和繼承豐富的軟件資源,所以Intel公司所生產(chǎn)的所有
    發(fā)表于 01-31 13:59 ?3479次閱讀

    X86架構(gòu)無(wú)可取代 業(yè)界地位無(wú)法撼動(dòng)

    X86架構(gòu)已經(jīng)問(wèn)世41年了,當(dāng)年它還只是眾多CPU架構(gòu)中的一種,但是被IBM選擇為兼容PC的處理器之后,X86這么多年來(lái)已經(jīng)確定了它在業(yè)界的
    發(fā)表于 12-13 10:19 ?1891次閱讀

    CPU架構(gòu)大戰(zhàn)未曾停歇,x86、Arm、RISC-V開(kāi)始互占地盤(pán)

    x86進(jìn)入Arm專(zhuān)長(zhǎng)領(lǐng)域,則以2014年華碩ASUS推出ZenPhone手機(jī)為指標(biāo)。ZenPhone使用x86架構(gòu)的Atom Z系列CPU
    發(fā)表于 09-28 10:43 ?2017次閱讀

    CPU架構(gòu)x86、RISC-V、ARM的區(qū)別和特點(diǎn)

    x86架構(gòu)的指令集相對(duì)于RISC(精簡(jiǎn)指令集計(jì)算機(jī))架構(gòu)而言更為復(fù)雜。這意味著x86架構(gòu)CPU
    發(fā)表于 04-03 10:21 ?1.3w次閱讀

    X86架構(gòu)與Arm架構(gòu)的區(qū)別

    X86架構(gòu)和ARM架構(gòu)是主流的兩種CPU架構(gòu),X86架構(gòu)
    的頭像 發(fā)表于 06-16 12:50 ?2.2w次閱讀
    <b class='flag-5'>X86</b><b class='flag-5'>架構(gòu)</b>與Arm<b class='flag-5'>架構(gòu)</b>的區(qū)別

    CPU架構(gòu)X86和ARM的區(qū)別

    隨著科技的快速發(fā)展,計(jì)算機(jī)技術(shù)已經(jīng)深入到我們生活的方方面面。作為計(jì)算機(jī)的核心部件,CPU(中央處理器)的性能和架構(gòu)對(duì)于整個(gè)系統(tǒng)的運(yùn)行起著至關(guān)重要的作用。目前,市場(chǎng)上主流的 CPU 架構(gòu)
    發(fā)表于 09-18 10:02 ?2420次閱讀

    X86架構(gòu)與ARM架構(gòu)的主要區(qū)別

    X86和ARM是兩種主要的CPU架構(gòu)X86架構(gòu)CPU是PC服務(wù)器行業(yè)的老大,而ARM
    的頭像 發(fā)表于 09-22 08:23 ?8156次閱讀
    <b class='flag-5'>X86</b><b class='flag-5'>架構(gòu)</b>與ARM<b class='flag-5'>架構(gòu)</b>的主要區(qū)別

    x86與arm架構(gòu)區(qū)別主板還是cpu

    x86和ARM架構(gòu)是計(jì)算機(jī)處理器的兩種不同體系結(jié)構(gòu),涉及到CPU和主板兩方面的區(qū)別。下面將詳細(xì)介紹它們的特點(diǎn)和區(qū)別。 首先,我們需要先了解x86和ARM是什么。
    的頭像 發(fā)表于 12-21 17:08 ?1922次閱讀

    arm架構(gòu)x86架構(gòu)區(qū)別 linuxx86還是arm

    ARM架構(gòu)x86架構(gòu)是兩種不同的計(jì)算機(jī)處理器架構(gòu),它們?cè)隗w系結(jié)構(gòu)、指令集、應(yīng)用領(lǐng)域等方面有著明顯的區(qū)別。Linux操作系統(tǒng)則具有廣泛的適配
    的頭像 發(fā)表于 01-30 13:46 ?1.6w次閱讀