中斷與異常簡(jiǎn)介與分析
掃描二維碼
隨時(shí)隨地手機(jī)看文章
本人才疏學(xué)淺,若有說(shuō)得不對(duì)的地方,愿各位大佬指教!
微信公眾號(hào):morixinguan
關(guān)注可了解更多的教程。問(wèn)題或建議,請(qǐng)公眾號(hào)留言;
[如果你覺(jué)得本文對(duì)你有幫助,歡迎贊賞]
▲長(zhǎng)按圖片保存可分享至朋友圈
一、用戶(hù)態(tài)、內(nèi)核態(tài)
開(kāi)篇,我們先來(lái)了解下什么是用戶(hù)態(tài)、內(nèi)核態(tài)。
一般現(xiàn)代CPU都有幾種不同的指令執(zhí)行級(jí)別。
在高執(zhí)行級(jí)別下,代碼可以執(zhí)行特權(quán)指令,訪問(wèn)任意的物理地址,這種CPU執(zhí)行級(jí)別就對(duì)應(yīng)著內(nèi)核態(tài)。
而在相應(yīng)的低級(jí)別執(zhí)行狀態(tài)下,代碼的掌控范圍會(huì)受到限制。只能在對(duì)應(yīng)級(jí)別允許的范圍內(nèi)活動(dòng)。
舉例:
intel x86 CPU有四種不同的執(zhí)行級(jí)別0-3,linux只使用了其中的0級(jí)和3級(jí)分別來(lái)表示內(nèi)核態(tài)和用戶(hù)態(tài)。
二、如何區(qū)分用戶(hù)態(tài)和內(nèi)核態(tài)?
一般來(lái)說(shuō)在linux中,地址空間是一個(gè)顯著的標(biāo)志:0xc0000000以上的地址空間只能在內(nèi)核態(tài)下訪問(wèn),0x00000000-0xbfffffff的地址空間在兩種狀態(tài)下都可以訪問(wèn)。
注意:這里所說(shuō)的地址空間是邏輯地址而不是物理地址。
孟寧老師在講解內(nèi)核知識(shí)點(diǎn)已經(jīng)把這個(gè)知識(shí)點(diǎn)最精華的部分提取出來(lái)了,那么到底內(nèi)核中有什么樣的接口是跟老師說(shuō)的相關(guān)的呢?
其實(shí)寫(xiě)過(guò)linux內(nèi)核驅(qū)動(dòng)程序的同學(xué)應(yīng)該就知道,實(shí)現(xiàn)一個(gè)字符設(shè)備驅(qū)動(dòng),在write方法和read方法中,內(nèi)核態(tài)和用戶(hù)態(tài)之間的橋梁就是copy_to_user和copy_form_user這兩個(gè)接口,因?yàn)樵诟邎?zhí)行級(jí)別下,代碼可以執(zhí)行特權(quán)指令,訪問(wèn)任意的物理地址,這種CPU執(zhí)行級(jí)別就對(duì)應(yīng)著內(nèi)核態(tài),而在相應(yīng)的低級(jí)別執(zhí)行狀態(tài)下,代碼的掌控范圍會(huì)受到限制。只能在對(duì)應(yīng)級(jí)別允許的范圍內(nèi)活動(dòng)。其實(shí)正好說(shuō)明了這個(gè)問(wèn)題,程序員或者軟件應(yīng)用工程師在編寫(xiě)應(yīng)用程序去控制設(shè)備驅(qū)動(dòng)的時(shí)候,首先肯定是會(huì)打開(kāi)相應(yīng)的文件描述符,然后對(duì)相應(yīng)的文件描述符進(jìn)行讀寫(xiě),ioctl,lseek之類(lèi)的操作。當(dāng)在應(yīng)用層編寫(xiě)程序即是屬于用戶(hù)態(tài),在應(yīng)用程序不能訪問(wèn)任意的硬件物理地址,所以當(dāng)用戶(hù)需要讀取文件描述符的內(nèi)的內(nèi)容時(shí),就需要調(diào)用read,當(dāng)用戶(hù)需要寫(xiě)數(shù)據(jù)進(jìn)文件描述符時(shí),就需要用write,在用戶(hù)態(tài)調(diào)用這兩個(gè)接口,進(jìn)而就會(huì)進(jìn)行系統(tǒng)調(diào)用,產(chǎn)生相應(yīng)的系統(tǒng)調(diào)用號(hào),然后內(nèi)核會(huì)根據(jù)系統(tǒng)調(diào)用號(hào)找到相應(yīng)的驅(qū)動(dòng)程序,此時(shí)系統(tǒng)就處在內(nèi)核態(tài)中,在驅(qū)動(dòng)程序中,首先進(jìn)行驅(qū)動(dòng)程序初始化,然后注冊(cè),產(chǎn)生驅(qū)動(dòng)程序最重要主設(shè)備號(hào)和次設(shè)備號(hào)。初始化的過(guò)程中由于對(duì)相應(yīng)的read方法和wirte方法進(jìn)行初始化,故用戶(hù)態(tài)執(zhí)行read操作,就會(huì)進(jìn)而使CPU產(chǎn)生異常,然后進(jìn)入內(nèi)核態(tài)找到相應(yīng)的read,write當(dāng)然也是一樣的。
當(dāng)然,我的分析依然還是處于非常片面的,只能說(shuō)個(gè)大概,因?yàn)椴僮飨到y(tǒng)在執(zhí)行系統(tǒng)調(diào)用的過(guò)程依然是非常復(fù)雜的,但是復(fù)雜歸復(fù)雜,對(duì)于這樣的一個(gè)流程我們還是應(yīng)當(dāng)要去了解清楚。
還有一個(gè)例子就是,假設(shè)我需要實(shí)現(xiàn)一個(gè)led驅(qū)動(dòng)或者其它的驅(qū)動(dòng),在內(nèi)核驅(qū)動(dòng)中,我需要將相應(yīng)的物理地址ioremap成為一個(gè)虛擬地址,當(dāng)驅(qū)動(dòng)調(diào)用結(jié)束后,還應(yīng)當(dāng)取消相應(yīng)的地址映射,這其實(shí)就是在內(nèi)核態(tài)進(jìn)行的操作,因?yàn)樵趦?nèi)核中,訪問(wèn)這些地址以虛擬地址的形式進(jìn)行相應(yīng)的內(nèi)存分配。為了使軟件訪問(wèn)I/O內(nèi)存,必須為設(shè)備分配虛擬地址.這就是ioremap的工作。
以上是Linux內(nèi)核中斷以及異常需要了解的基礎(chǔ)。
三、中斷基礎(chǔ)
中斷,通常被定義為一個(gè)事件。打個(gè)比方,你燒熱水,水沸騰了,這時(shí)候你要去關(guān)掉燒熱水的電磁爐,然后再去辦之前手中停不下來(lái)的事情。那么熱水沸騰就是打斷你正常工作的一個(gè)信號(hào)機(jī)制。當(dāng)然,還有其它的情況,我們以后再做分析。
中斷也就是這樣產(chǎn)生的,中斷分為同步中斷還有異步中斷。
同步中斷在Intel的手冊(cè)中被稱(chēng)為異常,而異步中斷被稱(chēng)作中斷。打個(gè)比方在ARM處理器的異常種類(lèi)就有不少,有未定義指令異常,軟中斷異常,快中斷異常等等。異常是由程序錯(cuò)誤產(chǎn)生的,或者是內(nèi)核必須處理的異常條件產(chǎn)生的。如果你曾經(jīng)學(xué)過(guò)單片機(jī),那么你一定會(huì)清楚,51單片機(jī)的P32,P33是外部中斷0和1,假設(shè)當(dāng)你在程序中開(kāi)啟了外部中斷0,然后在中斷中執(zhí)行了相應(yīng)的程序,這時(shí)你在外部中斷0的一腳連接一個(gè)按鍵,這時(shí)候你按下去P30這個(gè)引腳就會(huì)產(chǎn)生一個(gè)中斷。那么中斷服務(wù)程序就會(huì)響應(yīng)你的操作,比如點(diǎn)亮一個(gè)LED燈,或者說(shuō)讓蜂鳴器叫一下。
那么在linux內(nèi)核中的中斷其實(shí)也是和單片機(jī)類(lèi)似的,只不過(guò)linux內(nèi)核的中斷定義的比較豐富,但是基本思想還是一樣的。linux內(nèi)核處理中斷有一種叫做中斷信號(hào)的機(jī)制。它的作用就是當(dāng)一個(gè)中斷信號(hào)到來(lái)時(shí),CPU必須停止它當(dāng)然正在做的事情,然后切換到一個(gè)新的活動(dòng),為了做到這一點(diǎn),內(nèi)核態(tài)堆棧保存的程序計(jì)數(shù)器的當(dāng)前值,其實(shí)就是eip和cs寄存器的存儲(chǔ)數(shù)據(jù),然后把中斷相關(guān)類(lèi)型的一個(gè)地址放到一個(gè)程序計(jì)數(shù)器當(dāng)中去。
其實(shí)在內(nèi)核中,中斷這樣的切換機(jī)制很像進(jìn)程的調(diào)度,上下文切換這樣的機(jī)制,但是依然存在著一個(gè)非常明顯的差異,那就是中斷或者異常在處理的代碼并不是一個(gè)進(jìn)程。
中斷信號(hào)的來(lái)臨必將會(huì)引起中斷的處理,那么中斷處理必須要滿(mǎn)足以下的約束:
1、linux內(nèi)核在響應(yīng)中斷以后必須要進(jìn)行的操作分為兩部分:我們把非常重要的,非常緊急的處理程序讓內(nèi)核立即去運(yùn)行。剩下的有延時(shí)的部分就讓它后面再去執(zhí)行。這樣也就驗(yàn)證了水沸騰,而人停下手中的事去關(guān)電磁爐,再回去做他的事一樣的道理。
2、中斷編寫(xiě)的程序必須編寫(xiě)成可以使內(nèi)核控制的路徑能以嵌套的方式來(lái)執(zhí)行,或者說(shuō),當(dāng)最后一個(gè)內(nèi)核控制路徑終止的時(shí)候,內(nèi)核必須能恢復(fù)被中斷進(jìn)程的執(zhí)行,或者說(shuō),中斷信號(hào)已經(jīng)導(dǎo)致了進(jìn)程重新調(diào)度,內(nèi)核能切換到另外一個(gè)進(jìn)程。這是我們分析的另外一種情況,水開(kāi)了,人去關(guān)電磁爐,然后人接著做事,這是第一種情況。水開(kāi)了,人去關(guān)電磁爐,接下來(lái)門(mén)鈴響了,客人來(lái)了,你必須去迎接客人,然后就打斷了你之前在做的事情,也就是客人來(lái)了打斷了你正在做的這件事進(jìn)入到與陪客的階段。
3、在臨界區(qū)中,中斷必須要被禁止。臨界區(qū)其實(shí)就是加鎖和去鎖的實(shí)現(xiàn)。程序員將非常關(guān)鍵的步驟放進(jìn)臨界區(qū),就是為了防止中斷或者其它的信號(hào)去影響到它,其實(shí)在內(nèi)核中這樣的步驟是有必要的。臨界區(qū)的代碼必須在短時(shí)間內(nèi)被執(zhí)行,而不應(yīng)該出現(xiàn)延時(shí)的操作,且必須盡可能的去限制這樣的臨界區(qū),因?yàn)?,?nèi)核在處理中斷程序的時(shí)候,應(yīng)該是在大部分時(shí)間以開(kāi)中斷的方式去運(yùn)行。
在intel的文檔中,中斷和異常通常分為幾類(lèi):中斷有可屏蔽的,不可屏蔽的。異常有處理器探測(cè)異常,這就包括故障的產(chǎn)生,陷阱,異常的中止,還有編程異常的狀況。每個(gè)中斷和異常都是由0-255之間的一個(gè)數(shù)來(lái)標(biāo)識(shí)。intel管這東西叫向量。
其實(shí)在ARM中就有那么一張表叫異常向量表,那就是我剛剛文章里說(shuō)過(guò)的那幾個(gè)。
在linux中也有這么一張表:
在linux內(nèi)核中,每一個(gè)能夠發(fā)出中斷請(qǐng)求的硬件設(shè)備控制器都有一條名為IRQ的輸出線。所有現(xiàn)在存在的IRQ線都與一個(gè)名為可編程中斷控制器的硬件電路的輸入引腳相連,上次講到單片機(jī)的時(shí)候,我就講到了單片機(jī)中斷的一些概念。我們現(xiàn)在來(lái)看一幅圖,更好說(shuō)明一個(gè)問(wèn)題:
下面的這幅圖是51單片機(jī)的一個(gè)關(guān)于矩陣鍵盤(pán)的學(xué)習(xí)的一個(gè)proteus的仿真電路圖。
其中P3.2和P3.3為外部中斷引腳,當(dāng)可編程控制器(51MCU)收到外部中斷響應(yīng)的時(shí)候,會(huì)執(zhí)行一些特定的操作,當(dāng)然這需要開(kāi)發(fā)者去編寫(xiě)一個(gè)中斷初始化程序和一個(gè)中斷服務(wù)程序。
那么,可編程中斷控制器會(huì)做以下的操作:
1、監(jiān)視IRQ線,我們可以理解就是監(jiān)視單片機(jī)外部中斷的IO口,檢查產(chǎn)生的信號(hào)。如果有條或者兩條以上的IRQ上產(chǎn)生信號(hào),就選擇引腳編號(hào)較小的IRQ線。
2、如果一個(gè)引發(fā)信號(hào)出現(xiàn)在IRQ線上:
a.把接收到的引發(fā)信號(hào)轉(zhuǎn)換成對(duì)應(yīng)的向量。
b.把這個(gè)向量存放在中斷控制器的一個(gè)I/O端口,從而允許CPU通過(guò)數(shù)據(jù)總線讀這個(gè)向量。
c.把引發(fā)信號(hào)發(fā)送到處理器的INTR引腳,即會(huì)產(chǎn)生一個(gè)中斷。
d.等待,直到CPU通過(guò)把這個(gè)中斷信號(hào)寫(xiě)進(jìn)可編程中斷控制器的一個(gè)I/O端口來(lái)確認(rèn)它,當(dāng)這種情況發(fā)送時(shí),清INTR線。
3、最后一步返回到第一步繼續(xù)監(jiān)視,然后依次執(zhí)行。
當(dāng)然,也存在著一些更加高級(jí)的可編程中斷控制器,其中ARM算一種,Intel也是,等等。。。單片機(jī)算是最簡(jiǎn)單的一種。像多APIC系統(tǒng)的結(jié)構(gòu),會(huì)存在以下的一個(gè)圖的關(guān)系:
中斷信號(hào)通過(guò)IO引腳,然后通過(guò)中斷控制器I2C總線與相應(yīng)的CPU進(jìn)行通信。
一般情況下,有兩種分發(fā)的方式:
1、靜態(tài)分發(fā)模式:IRQ信號(hào)傳遞給重定向表相應(yīng)的項(xiàng)中所列出的本地APIC,然后中斷立即傳遞一個(gè)特定的CPU,或者是一組CPU,或者是所有的CPU。其實(shí)這是廣播模式的一種模型,接觸過(guò)UNIX網(wǎng)絡(luò)編程應(yīng)該會(huì)知道。
2、動(dòng)態(tài)分發(fā)模式:如果處理器正在執(zhí)行最低優(yōu)先級(jí)的進(jìn)程,IRQ信號(hào)線就會(huì)傳遞給這種處理器的本地APIC。也就是說(shuō),在CPU內(nèi)部有一個(gè)控制優(yōu)先級(jí)的寄存器,用來(lái)計(jì)算當(dāng)前運(yùn)行進(jìn)程的優(yōu)先級(jí)。如果兩個(gè)或者多個(gè)CPU共享最低優(yōu)先級(jí),那么就利用仲裁的技術(shù)在這些CPU之間分配負(fù)荷等等的形式。
異常有很多種,在8086處理器可以找到多達(dá)20種不同的異常,內(nèi)核必須為每種異常提供一個(gè)專(zhuān)門(mén)的異常處理程序。對(duì)于某些異常,CPU控制單元會(huì)在開(kāi)始執(zhí)行異常處理程序前產(chǎn)生一個(gè)硬件出錯(cuò)碼,并且壓入內(nèi)核態(tài)的堆棧中去。
關(guān)于這個(gè)異常處理信息,我們有必要來(lái)了解以下perror這個(gè)函數(shù)。
perror( ) 用來(lái)將上一個(gè)函數(shù)發(fā)生錯(cuò)誤的原因輸出到標(biāo)準(zhǔn)設(shè)備(stderr)。參數(shù) s 所指的字符串會(huì)先打印出,后面再加上錯(cuò)誤原因字符串。此錯(cuò)誤原因依照全局變量errno(這里的說(shuō)法不準(zhǔn)確,errno是一個(gè)宏,該宏返回左值) 的值來(lái)決定要輸出的字符串。在庫(kù)函數(shù)中有個(gè)errno變量,每個(gè)errno值對(duì)應(yīng)著以字符串表示的錯(cuò)誤類(lèi)型。當(dāng)你調(diào)用"某些"函數(shù)出錯(cuò)時(shí),該函數(shù)已經(jīng)重新設(shè)置了errno的值。perror函數(shù)只是將你輸入的一些信息和現(xiàn)在的errno所對(duì)應(yīng)的錯(cuò)誤一起輸出。
用法:void perror(const char *s); perror ("open_port");
我們寫(xiě)段代碼來(lái)看看就知道了:
1#include 2#include 3 4int main(void) 5{ 6 FILE?*filp?= NULL; 7 filp?=?fopen("txt","r"); 8 if(NULL ==?filp) 9 { 10 perror("沒(méi)有相應(yīng)的文件"); 11 } 12 fclose(filp); 13 return 0 ; 14}
運(yùn)行結(jié)果:
在當(dāng)前目錄下,找不到txt這個(gè)文件,所以perror會(huì)根據(jù)相應(yīng)的出錯(cuò)信息打印No such file or directory。
看了這個(gè)函數(shù)的應(yīng)用,相信更會(huì)理解上面的異常的相關(guān)知識(shí)。當(dāng)然還有更多日常寫(xiě)程序發(fā)現(xiàn)的BUG,比如段錯(cuò)誤,段錯(cuò)誤是最常見(jiàn)的,一些初學(xué)者在使用指針的時(shí)候,沒(méi)有分配相應(yīng)的空間,這時(shí)候給指針賦值,雖然沒(méi)有語(yǔ)法錯(cuò)誤,但可能會(huì)有警告。當(dāng)程序運(yùn)行的時(shí)候,就會(huì)自動(dòng)退出并提示段錯(cuò)誤(Segment fault),這一般是在linux上會(huì)出現(xiàn)這兩個(gè)英語(yǔ)單詞,在window的Devcpp上是這樣,:
段錯(cuò)誤的產(chǎn)生原因有很多種,程序在進(jìn)行遞歸的時(shí)候,如果沒(méi)有相應(yīng)的條件退出的話(huà),程序一旦進(jìn)行死循環(huán)遞歸之后就會(huì)產(chǎn)生爆棧錯(cuò)誤,也就是棧被擠爆了,棧這個(gè)概念其實(shí)并不陌生。我們?cè)趯?xiě)C語(yǔ)言程序的時(shí)候,一旦寫(xiě)了一個(gè)子函數(shù),那就相當(dāng)于建立了一個(gè)堆棧,一般情況下函數(shù)在執(zhí)行完退出后堆棧是自動(dòng)分配,自動(dòng)銷(xiāo)毀的,不用程序員去手動(dòng)malloc申請(qǐng)內(nèi)存再free釋放內(nèi)存。因?yàn)槭謩?dòng)分配的內(nèi)存是用了堆區(qū)的內(nèi)存,而自動(dòng)分配是在棧區(qū)進(jìn)行分配的。在32位操作系統(tǒng)上,棧的大小就只有12M,所以寫(xiě)代碼的時(shí)候,一定要記得防止爆棧錯(cuò)誤的產(chǎn)生,特別是遞歸!在main函數(shù)中多寫(xiě)些子函數(shù)是有好處的,要養(yǎng)成良好的編程習(xí)慣。
四、Linux中斷應(yīng)用
接下來(lái)我們結(jié)合一個(gè)真實(shí)的驅(qū)動(dòng)案例來(lái)描述linux內(nèi)核中驅(qū)動(dòng)的中斷機(jī)制,首先我們先了解一下linux內(nèi)核中提供的中斷接口。
這個(gè)接口我們需要包含一個(gè)頭文件:#include
1、這個(gè)是請(qǐng)求中斷函數(shù)
1int request_irq(unsigned?int?irq,?irq_handler_t?handler, 2 unsigned?long?irqflags,?const?char?*devname,?void?*dev_id) 3 irq: 4 中斷號(hào)?arch/arm/plat-s3c64xx/include/plat/irqs.h 5 handler: 6 中斷處理函數(shù)?irqreturn_t?handler(int irq,?void?*dev_id); 7 irqreturn_t: 8 See?include/linux/irqreturn.h 9 10 irqflags: 11 See?line 21-59 in?include/linux/interrupt.h 12 使用IRQF_SHARED共享irq時(shí),?irqflags必須相同 13 如:??????request_irq(IRQ_EINT(0),?handler1, 14 IRQF_TRIGGER_FALLING?|?IRQF_SHARED, "dev1",?&dev1); 15 16 request_irq(IRQ_EINT(0),?handler2, 17 IRQF_TRIGGER_FALLING?|?IRQF_SHARED, "dev2",?&dev2); 18 19 devname: 20 設(shè)備名,?cat?/proc/interrupts 21 22 dev_id: 23 發(fā)生中斷時(shí)將dev_id傳遞給handler函數(shù), 24 irqflags含有IRQF_SHARED時(shí)dev_id不能為NULL,?并且要保證唯一 25 dev_id一般采用當(dāng)前設(shè)備的結(jié)構(gòu)體指針
2、釋放中斷
1void free_irq ( unsigned int irq, void *?dev_id); 2 釋放匹配irq和dev_id的中斷,?如果irq有多個(gè)相同的dev_id,?將釋放第一個(gè) 3 So,?共享中斷的dev_id不是唯一時(shí),?可能會(huì)釋放到其它設(shè)備的中斷
3、開(kāi)啟中斷
1void?enable_irq(unsigned?int?irq); 2 開(kāi)啟irq號(hào)中斷
4、關(guān)閉中斷
1void?disable_irq(unsigned?int?irq); 2 關(guān)閉irq號(hào)中斷
5、關(guān)閉當(dāng)前CPU中斷并保存在flag中去
1void local_irq_save(unsigned long flags);
6、恢復(fù)flag到CPU中去
1void local_irq_restore(unsigned long flags); 2 恢復(fù)flags到當(dāng)前CPU
7、關(guān)閉當(dāng)前的CPU中斷
1void local_irq_disable(void);
8、開(kāi)始當(dāng)前的CPU中斷
1void local_irq_enable(void);
接下來(lái)我們來(lái)看一個(gè)按鍵中斷的例子,這個(gè)例子是基于Tiny4412按鍵驅(qū)動(dòng)的源碼:
使用的Linux3.5的內(nèi)核,雖然版本有點(diǎn)老,但作為開(kāi)發(fā)者來(lái)說(shuō),其實(shí)并沒(méi)有什么差別。
1#include 2#include 3#include 4#include 5#include 6#include 7#include 8#include 9#include 10#include 11#include 12#include 13#include 14#include 15#include 16#include 17#include 18 19#include 20#include 21#include 22#include 23 24//設(shè)備名稱(chēng) 25#define DEVICE_NAME "buttons" 26 27struct button_desc { 28 int gpio; 29 int number; 30 char *name; 31 struct timer_list timer; 32}; 33 34//定義按鍵相關(guān)的寄存器 35static struct button_desc buttons[]?=?{ 36 {?EXYNOS4_GPX3(2), 0, "KEY0" }, 37 {?EXYNOS4_GPX3(3), 1, "KEY1" }, 38 {?EXYNOS4_GPX3(4), 2, "KEY2" }, 39 {?EXYNOS4_GPX3(5), 3, "KEY3" }, 40}; 41 42//存儲(chǔ)按鍵的鍵值 43static volatile char key_values[]?=?{ 44 '0', '0', '0', '0', '0', '0', '0', '0' 45}; 46 47//創(chuàng)建一個(gè)等待隊(duì)列頭并初始化 48static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 49 50static volatile int ev_press?= 0; 51 52//按鍵定時(shí)器 53static void tiny4412_buttons_timer(unsigned long _data) 54{ 55 struct button_desc *bdata =?(struct button_desc *)_data; 56 int down; 57 int number; 58 unsigned tmp; 59 //獲取按鍵的值 60 tmp?=?gpio_get_value(bdata->gpio); 61 //判斷是否為低電平 62 down?=?!tmp; 63 printk(KERN_DEBUG "KEY?%d:?%08x\n",?bdata->number,?down); 64 65 number?=?bdata->number; 66 //如果此時(shí)不為低電平,中斷處理進(jìn)入休眠狀態(tài),一般有事件產(chǎn)生就會(huì)立即被喚醒 67 if (down?!=?(key_values[number]?& 1))?{ 68 key_values[number]?= '0' +?down; 69 70 ev_press?= 1; 71 //中斷休眠 72 wake_up_interruptible(&button_waitq); 73 } 74} 75//按鍵中斷處理函數(shù) 76//irq:中斷號(hào) 77//dev_id:設(shè)備ID號(hào) 78static irqreturn_t button_interrupt(int irq, void *dev_id) 79{ 80 struct button_desc *bdata =?(struct button_desc *)dev_id; 81 //注冊(cè)一個(gè)定時(shí)器 82 mod_timer(&bdata->timer,?jiffies?+?msecs_to_jiffies(40)); 83 //返回一個(gè)中斷句柄 84 return IRQ_HANDLED; 85} 86//按鍵打開(kāi)函數(shù) 87//inode?:?節(jié)點(diǎn) 88//file?:?打開(kāi)文件的形式 89static int tiny4412_buttons_open(struct?inode?*inode,?struct?file?*file) 90{ 91 int irq; 92 int i; 93 int err?= 0; 94 //循環(huán)遍歷四個(gè)IO口,看看有哪個(gè)按鍵被按下了 95 for (i?= 0;?i?< ARRAY_SIZE(buttons); i++) { 96 if (!buttons[i].gpio) 97 continue; 98 //初始化定時(shí)器 99 setup_timer(&buttons[i].timer,?tiny4412_buttons_timer, 100 (unsigned long)&buttons[i]); 101 //設(shè)置GPIO為中斷引腳,也就是對(duì)應(yīng)那四個(gè)按鍵 102 irq?=?gpio_to_irq(buttons[i].gpio); 103 err?=?request_irq(irq,?button_interrupt,?IRQ_TYPE_EDGE_BOTH, //請(qǐng)求中斷處理函數(shù) 104 buttons[i].name,?(void *)&buttons[i]); 105 if (err) 106 break; 107 } 108 109 if (err)?{ 110 i--; 111 for (;?i?>= 0;?i--)?{ 112 if (!buttons[i].gpio) 113 continue; 114 115 irq?=?gpio_to_irq(buttons[i].gpio); 116 disable_irq(irq); //關(guān)中斷 117 free_irq(irq,?(void *)&buttons[i]);//釋放中斷 118 119 del_timer_sync(&buttons[i].timer);//刪除一個(gè)定時(shí)器 120 } 121 122 return -EBUSY; 123 } 124 125 ev_press?= 1; 126 return 0; 127} 128//按鍵關(guān)閉處理函數(shù) 129static int tiny4412_buttons_close(struct?inode?*inode,?struct?file?*file) 130{ 131 int irq,?i; 132 133 for (i?= 0;?i?< ARRAY_SIZE(buttons); i++) { 134 if (!buttons[i].gpio) 135 continue; 136 //同樣的,這里也是釋放 137 irq?=?gpio_to_irq(buttons[i].gpio); 138 free_irq(irq,?(void *)&buttons[i]); 139"white-space:pre"> //刪除一個(gè)定時(shí)器 140 del_timer_sync(&buttons[i].timer); 141 } 142 143 return 0; 144} 145//讀取按鍵的鍵值函數(shù) 146static int tiny4412_buttons_read(struct?file?*filp, char __user?*buff, 147 size_t count, loff_t *offp) 148{ 149 unsigned long err; 150 151 if (!ev_press)?{ 152 if (filp->f_flags?&?O_NONBLOCK) 153 return -EAGAIN; 154 else //等待中斷的事件產(chǎn)生 155 wait_event_interruptible(button_waitq,?ev_press); 156 } 157 158 ev_press?= 0; 159 //將獲取到的鍵值返回到用戶(hù)空間 160 err?=?copy_to_user((void *)buff,?(const void *)(&key_values), 161 min(sizeof(key_values),?count)); 162 163 return err???-EFAULT?:?min(sizeof(key_values),?count); 164} 165 166//按鍵非阻塞型接口設(shè)計(jì) 167static unsigned int tiny4412_buttons_poll(?struct?file?*file, 168 struct?poll_table_struct?*wait) 169{ 170 unsigned int mask?= 0; 171"white-space:pre"> //非阻塞型等待 172 poll_wait(file,?&button_waitq,?wait); 173 if (ev_press) 174 mask?|=?POLLIN?|?POLLRDNORM; 175 176 return mask; 177} 178 179//驅(qū)動(dòng)文件操作結(jié)構(gòu)體成員初始化 180static struct file_operations dev_fops =?{ 181 .owner??????=?THIS_MODULE, 182 .open???????=?tiny4412_buttons_open, 183 .release????=?tiny4412_buttons_close, 184 .read???????=?tiny4412_buttons_read, 185 .poll???????=?tiny4412_buttons_poll, 186}; 187//注冊(cè)雜類(lèi)設(shè)備的結(jié)構(gòu)體成員初始化 188static struct miscdevice misc =?{ 189 .minor??????=?MISC_DYNAMIC_MINOR, 190 .name???????=?DEVICE_NAME, 191 .fops???????=?&dev_fops, //這里就是把上面那個(gè)文件操作結(jié)構(gòu)體的成員注冊(cè)到雜類(lèi)操作這里 192}; 193//按鍵驅(qū)動(dòng)初始化 194static int __init button_dev_init(void) 195{ 196 int ret; 197 //先注冊(cè)一個(gè)雜類(lèi)設(shè)備 198 //這相當(dāng)于讓misc去管理open?,read,write,close這些接口 199 ret?=?misc_register(&misc); 200 // 201 printk(DEVICE_NAME"\tinitialized\n"); 202 203 return ret; 204} 205//按鍵驅(qū)動(dòng)注銷(xiāo) 206static void __exit button_dev_exit(void) 207{ 208 //注銷(xiāo)一個(gè)雜類(lèi)設(shè)備驅(qū)動(dòng) 209 misc_deregister(&misc); 210} 211 212module_init(button_dev_init); 213module_exit(button_dev_exit); 214 215MODULE_LICENSE("GPL"); 216MODULE_AUTHOR("Yang.yuanxin");
運(yùn)行結(jié)果:
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!