當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 嵌入式云IOT技術(shù)圈
[導(dǎo)讀]開(kāi)篇,我們先來(lái)了解下什么是用戶(hù)態(tài)、內(nèi)核態(tài)。

本人才疏學(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 ,在Linux中斷編程中,最重要的是要了解以下的接口函數(shù):

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)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉