欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
十四、Linux驅動(dòng)程序開(kāi)發(fā)(10) - 中斷

十四、Linux驅動(dòng)程序開(kāi)發(fā)(10) -中斷

5.1中斷

中斷本質(zhì)上是一種特殊的電信號,由硬件設備發(fā)向處理器,處理器接收到中斷后,會(huì )馬上向操作系統反映此信號的到來(lái),然后就由OS復雜處理這些新到來(lái)的數據。硬件設備生成中斷的時(shí)候并不考慮與處理器的時(shí)鐘同步,也就是說(shuō)中斷可以隨時(shí)產(chǎn)生。

定義:

所謂中斷是指CPU在執行程序的過(guò)程中,出現了某些突發(fā)事件時(shí)CPU必須暫停執行當前的程序,轉去處理突發(fā)事件,處理完畢后CPU又返回原程序被中斷的位置并繼續執行。

分類(lèi):

根據中斷的來(lái)源:分為內部中斷和外部中斷。內部中斷的中斷源來(lái)自CPU內部(軟件中斷指令,溢出等),外部中斷的中斷源來(lái)自CPU外部,由外設提出請求。

根據是否屏蔽:分為可屏蔽中斷與不屏蔽中斷(NMI),可屏蔽中斷可通過(guò)屏蔽字屏蔽,屏蔽后該中斷不再得到響應。

根據中斷入口跳轉方式的不同:分為向量中斷和非向量中斷。采用向量中斷的CPU通常為不同的中斷分配不同的中斷號。非向量中斷的多個(gè)中斷共享一個(gè)入口地址。進(jìn)入該入口地址后再通過(guò)軟件判斷中斷標志來(lái)識別具體是哪個(gè)中斷。就是說(shuō),向量中斷是由硬件提供中斷服務(wù)程序入口地址,非向量中斷由軟件提供中斷服務(wù)程序入口地址。

5.2中斷處理程序

中斷處理程序與其他內和函數的真正區別在于:

中斷處理程序是被內核調用來(lái)響應中斷的,而它們運行于我們稱(chēng)之為中斷上下文的特殊上下文中。

 

前和后半部(上半部和下半部)

中斷處理的一個(gè)主要問(wèn)題是如何在處理中進(jìn)行長(cháng)時(shí)間的任務(wù). 常常大量的工作必須響應一個(gè)設備中斷來(lái)完成, 但是中斷處理需要很快完成并且不使中斷阻塞太長(cháng). 2 個(gè)需要(工作和速度)彼此沖突。

Linux (許多其他系統一起)解決這個(gè)問(wèn)題通過(guò)將中斷處理分為 2 . 所謂的前半部是實(shí)際響應中斷的函數 -- 你使用 request_irq 注冊的那個(gè). 后半部是由前半部調度來(lái)延后執行的函數, 在一個(gè)更安全的時(shí)間. 最大的不同在前半部處理和后半部之間是所有的中斷在后半部執行時(shí)都使能——這就是為什么它在一個(gè)更安全時(shí)間運行. 在典型的場(chǎng)景中, 前半部保存設備數據到一個(gè)設備特定的緩存,調度它的后半部, 并且退出: 這個(gè)操作非???/span>. 后半部接著(zhù)進(jìn)行任何其他需要的工作, 例如喚醒進(jìn)程, 啟動(dòng)另一個(gè) I/O 操作, 等等. 這種設置允許前半部來(lái)服務(wù)一個(gè)新中斷而同時(shí)后半部仍然在工作。

 

5.3注冊中斷處理程序

中斷處理程序是管理硬件的驅動(dòng)程序的組成部分。每個(gè)設備都有相關(guān)的驅動(dòng)程序,如果設備使用中斷,那么相應的驅動(dòng)程序就注冊一個(gè)中斷處理程序。驅動(dòng)程序可以通過(guò)下面的函數注冊并激活一個(gè)中斷處理程序,以便處理中斷:

int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long flags,
                const char *dev_name,
                void *dev_id);

1)第一個(gè)參數irq表示要分配的中斷號。對某些設備,這個(gè)值是先定的,對大多數設備來(lái)說(shuō),這個(gè)值是可以通過(guò)探測獲取,也可以動(dòng)態(tài)確定。

2)第二個(gè)參數handler是一個(gè)指針,指向處理這個(gè)中斷的實(shí)際中斷處理程序。只要操作系統一接收到中斷,該函數就被調用。

3)第三個(gè)參數irqflags可以為0,也可能是下列一個(gè)或多個(gè)標志的位掩碼:SA_INTERRUPT:此標志表明給定的中斷處理程序是一個(gè)快速中斷處理程序。

SA_SAMPLE_RANDOM:此標志表明這個(gè)設備產(chǎn)生的中斷對內核熵池有貢獻。

SA_SHIRQ:此標志表明可以在多個(gè)中斷處理程序之間共享中斷線(xiàn)。

4)第四個(gè)參數devname是與中斷相關(guān)的設備的ASCII文本表示法。這些名字會(huì )被/proc/irqproc/interrupt文件使用,以便與用戶(hù)通信。

5)第五個(gè)參數dev_id主要用于共享中斷線(xiàn)。用于區分共享中斷線(xiàn)上的各個(gè)中斷處理程序。內核每次調用中斷處理程序時(shí),都會(huì )把這個(gè)指針傳遞給它。實(shí)踐中往往會(huì )通過(guò)它傳遞驅動(dòng)程序的設備結構:這個(gè)指針是唯一的,而且有可能在中斷處理程序內及設備模式中被用到。

 

request_irq()成功執行會(huì )返回0,如果返回非0值,就表示有錯誤發(fā)生,在這種情況下,指定的中斷處理程序不會(huì )被注冊,最常見(jiàn)的錯誤是-EBUSY,它表示給定的中斷線(xiàn)已經(jīng)在使用(或者當前用戶(hù)或者你沒(méi)有指定SA_SHIRQ())。

注意:request_irq()函數可能會(huì )睡眠,因此,不能在中斷上下文或其它不允許阻塞的代碼中調用該函數,在睡眠不安全的上下文中可以安全的調用request_irq()函數,是一種常見(jiàn)錯誤。

為什么request_irq()會(huì )引起睡眠?

在注冊的過(guò)程中,內核需要在/proc/irq文件創(chuàng )建一個(gè)與中斷對應的項,函數proc_mkdir()就是用來(lái)創(chuàng )建這個(gè)新的procfs項的,proc_makedir()通過(guò)調用函數proc_create()對這個(gè)新的profs項進(jìn)行設置,而proc_create()會(huì )調用函數kmalloc()來(lái)請求分配內存,而我們知道函數kmalloc()是可以睡眠的。

 

釋放中斷處理程序

釋放中斷線(xiàn),可以調用:

void free_irq(unsigned int irq, void *dev_id);
如果指定的中斷線(xiàn)不是共享的,那么,該函數刪除處理程序的同時(shí)將禁用這條中斷線(xiàn),如果中斷線(xiàn)是共享的,則僅刪除dec_id所對應的處理程序,而這條中斷線(xiàn)本身只有在刪除了最后一個(gè)處理程序才會(huì )被禁用,由此可以看出為什么唯一的dev_id如此重要,對于共享的中斷線(xiàn),需要一個(gè)唯一的信息來(lái)區分其上面的多個(gè)處理程序,并通過(guò)它來(lái)保證函數free_irq()能夠正確的刪除指定的處理程序,不管在哪種情況下(共享或不共享),如果dev_id非空,它都必須與需要的刪除的處理程序相匹配。
注意:必須從進(jìn)程上下文中調用free_irq()。
 

5.4編寫(xiě)中斷處理程序

一個(gè)典型的中斷處理程序聲明:                                                                                                  

staticirqreturn_t intr_handle(int irq, void *dev_id, struct pt_regs*regs                                          

注意,它的類(lèi)型與request_irq()參數中handler所要求的參數類(lèi)型相匹配(handler函數有一個(gè)類(lèi)型為irqreturn_t的返回值)。第一個(gè)參數irq就是這個(gè)處理程序要響應的中斷的中斷線(xiàn)號。dev_id是一個(gè)通用的指針,它與在中斷注冊時(shí)傳遞給request_irq()的參數dev_id必須一致。dev_id也有可能指向中斷處理程序使用的一個(gè)數據結構,對每個(gè)設備而言,設備結構都是唯一的,而且可能在中斷處理程序中也用的到,因此,也通常會(huì )把設備結構傳遞給dev_id。

參數regs是一個(gè)指向結構的指針,該結構包含處理中斷之前處理器的寄存器和狀態(tài),除了調試以外,很少用到。

中斷處理程序的返回值是一個(gè)特殊類(lèi)型:irqreturn_t。它可能返回兩個(gè)特殊的值:IRQ_NONEIRQ_HANDLED。irqreturn_t這個(gè)返回類(lèi)型實(shí)際上就是一個(gè)int型,之所以使用這些特殊值是為了與早期的內核保持兼容。

中斷處理程序通常會(huì )標記static,因為它從來(lái)不會(huì )被別的文件中的代碼直接調用。

 

5.4.1共享的中斷處理程序

共享的處理程序與非共享的處理程序在注冊和運行方式上比較相似,但差異主要有以下幾點(diǎn):

1request_irq()的參數flags必須設置SA_SHIRQ標志。

2)對每個(gè)注冊的中斷處理程序來(lái)說(shuō),dev_id參數必須唯一。而指向任一設備結構體的指針就可以滿(mǎn)足這一要求;通常會(huì )選擇設備結構,因為它是唯一的,而且中斷處理程序可能會(huì )用到它。

3)中斷處理程序必須能夠區分它的設備是否真的產(chǎn)生中斷。這既需要硬件支持,也需要處理程序中有相關(guān)的處理邏輯。如果硬件不支持這一功能,它就沒(méi)法知道到底是與它對應的設備發(fā)出了這個(gè)中斷,還是共享這條中斷線(xiàn)的其它設備發(fā)出了這個(gè)中斷。

指定SA_SHIRQ標志以調用request_irq()時(shí),只有在以下兩種情況下才可能成功,一是中斷線(xiàn)當前未被注冊,二是在該線(xiàn)上的所有已注冊處理程序都指定了SA_SHIRQ。

內核接收一個(gè)中斷后,它將依次調用在該中斷線(xiàn)上注冊的每一個(gè)處理程序,因此,一個(gè)處理程序必須知道它是否應該為這個(gè)中斷負責,如果與它相關(guān)的設備并沒(méi)有產(chǎn)生中斷,那么,處理程序應該立即退出。

 

5.4.3中斷上下文

當執行一個(gè)中斷處理程序或下半部時(shí),內核處于中斷上下文(interrput context)中。讓我們先回憶一下進(jìn)程上下文,進(jìn)程上下文是一種內核所處的操作模式,此時(shí)內核代表進(jìn)程執行——例如,執行系統調用或運行內核線(xiàn)程,在進(jìn)程上下文中,可以通過(guò)current宏去關(guān)聯(lián)當前進(jìn)程,此外,因為進(jìn)程是以進(jìn)程上下文的形式連接到內核中的,因此,在進(jìn)程上下文可以睡眠,也可以調用調度程序。

與之相反,中斷上下文和進(jìn)程并沒(méi)有什么瓜葛,與current宏也是不相干的(盡管它會(huì )指向被中斷的進(jìn)程),因為沒(méi)有進(jìn)程的背景,所以中斷上下文不可以睡眠——否則又怎能對它重新調度呢?因此,不能從中斷上下文中調用某些函數,如果一個(gè)函數睡眠,就不能在你的中斷處理程序中使用它——這是對什么樣的函數可以在中斷處理程序中的使用的限制。

有一點(diǎn)非常重要:中斷處理程序打斷了其它代碼(甚至可能是打斷了在其他中斷線(xiàn)上的另一個(gè)中斷處理程序)。正是因為這種異步執行的特性,所以所有的中斷處理程序必須盡可能的迅速,簡(jiǎn)潔,盡量把工作從中斷處理程序中分離出來(lái),放在下半部執行,因為下半部可以在更合適的時(shí)間運行。

最后,中斷處理程序并不具有自己的棧,相反,它共享被中斷進(jìn)程的內核棧,如果沒(méi)有正在運行的進(jìn)程,它將使用idle進(jìn)程的棧,因為中斷處理程序共享別人的堆棧所以它們在棧中獲取空間時(shí)必須非常節省,當然,內核棧本來(lái)就很有限(內核棧在32位體系結構上是8KB,在64位體系結構上是16KB,執行中的進(jìn)程上下文和產(chǎn)生的所有中斷都共享內核棧)。因此所有的內核代碼都應該謹慎利用它。

 

5.5中斷處理機制的實(shí)現

       中斷處理系統在Linux中的實(shí)現是非常依賴(lài)于體系結構的。實(shí)現依賴(lài)于處理器、所使用的中斷控制器的類(lèi)型、體系結構的設計及機器本身。

對于中斷從硬件到內核的路由,設備產(chǎn)生中斷,通過(guò)總線(xiàn)把電信號發(fā)送給中斷控制器。如果中斷是激活的,那么中斷控制器就會(huì )把中斷發(fā)往處理器。在大多數體系結構中,這個(gè)工作就是通過(guò)電信號給處理器的特定管腳發(fā)送一個(gè)信號,處理器會(huì )立即停止它正在做的事情,關(guān)閉中斷系統,然后跳轉到內存中預定義的位置開(kāi)始執行那里的代碼。這個(gè)預定以的位置是內核設置的,是中斷處理程序的入口點(diǎn)。

在內核中,中斷的旅程開(kāi)始于預定義入口點(diǎn),這類(lèi)似于系統調用通過(guò)預定義的異常句柄進(jìn)入內核。對于每一個(gè)中斷線(xiàn),處理器都會(huì )跳到對應的一個(gè)唯一的位置。至此,內核知道了所接受的中斷的IRQ號。初始入口點(diǎn)只是在棧中保存這個(gè)號,并存放當前寄存器的值(這些值屬于被中斷的任務(wù)),然后,內核調用函數do_IRQ()。

 

5.6中斷控制

Linux內核提供了一組接口用于操作機器上的中斷狀態(tài)。這些接口為我們提供了能夠禁止當前處理器的中斷系統,或屏蔽掉整個(gè)機器的一條中斷線(xiàn)的能力,這些例程都是與體系結構相關(guān)的,可以在<asm/system.h><asm/irq.h>中找到。 下面給出一些中斷控制方法:

local_irq_disable()          禁止本地中斷傳送

local_irq_enable()          激活本地中斷傳送

local_irq_save()      保存本地中斷傳遞的當前狀態(tài),然后禁止本地中斷傳遞

local_irq_restore()    恢復本地中斷傳遞到給定的狀態(tài)

disable_irq()              禁止給定中斷線(xiàn),并確保該函數返回之前在該中斷線(xiàn)上沒(méi)有處理程序在運行

disable_irq_nosynoc()       禁止給定中斷線(xiàn)

enable_irq()                       激活給定中斷線(xiàn)

irqs_disabled()               如果本地中斷傳遞被禁止,則返回非0,否則返回0

in_interrupt()                 如果在中斷上下文中,則返回非0,如果在進(jìn)程上下文中,則返回0

in_irq()                       如果當前正在執行中斷處理程序,則返回非0,否則,返回0

6.1下半部

下半部的任務(wù)就是執行與中斷處理密切相關(guān)但中斷處理程序本身不執行的工作。對于在上半部和下半部之間劃分工作,盡管不存在某種嚴格的規則,但還是有一些提示可供借鑒:(1)如果一個(gè)任務(wù)對時(shí)間非常敏感,將其放在中斷處理程序中執行。(2)如果一個(gè)任務(wù)和硬件相關(guān),將其放在中斷處理程序中執行。(3)如果一個(gè)任務(wù)要保證不被其它中斷打斷,將其放在中斷處理程序中執行。(4)其它所有任務(wù),考慮放在下半部執行。當我們開(kāi)始嘗試寫(xiě)自己的驅動(dòng)程序的時(shí)候,讀一下別人的中斷處理程序和相應的下半部會(huì )令你受益匪淺?,F在的問(wèn)題是:下半部具體放到以后的什么時(shí)候去做呢?下半部并不需要指明一個(gè)確切時(shí)間,只要把這些任務(wù)推遲一點(diǎn),讓他們在系統不太繁忙并且中斷恢復后執行就可以了。通常下半部在中斷處理程序一返回就會(huì )馬上執行。下半部執行的關(guān)鍵在于當它們運行的時(shí)候,允許響應所有中斷。

 

6.1.1為什么要用下半部?

因為在中斷處理程序運行的時(shí)候,當前的中斷線(xiàn)會(huì )被屏蔽,如果一個(gè)處理程序是SA_INTERRUPT類(lèi)型,它執行的時(shí)候會(huì )禁止所有本地中斷(而且把本地中斷線(xiàn)全局屏蔽掉),再加上中斷處理程序要與其它程序——甚至是其它的中斷處理程序——異步執行。

具體放到以后什么時(shí)候去做呢?

在這里,“以后”僅僅用來(lái)強調不是“馬上”而已,下半部并不需要指明一個(gè)確切時(shí)間,只是把這些任務(wù)推遲一點(diǎn),讓它們在系統不太繁忙并且中斷恢復后執行就可以了,通常下半部在中斷處理程序一返回就會(huì )馬上執行,下半部執行的關(guān)鍵在于當它們運行的時(shí)候,允許響應所有的中斷。

 

6.2軟中斷

軟中斷是用軟件方式模擬硬件中斷的概念,實(shí)現宏觀(guān)上的異步執行效果,tasklet也是基于軟中斷實(shí)現的。

異步通知所基于的信號也類(lèi)似于中斷。

硬中斷是外部設備對CPU的中斷

軟中斷通常是硬中斷服務(wù)程序對內核的中斷。

信號則是由內核(或其它進(jìn)程)對某個(gè)進(jìn)程的中斷。

 

6.2.1軟中斷的實(shí)現

       軟中斷是在編譯期間靜態(tài)分配的。不像tasklet那樣能被動(dòng)態(tài)的注冊或去除。軟中斷由softirq_action結構表示,它定義在<linux/interrupt.h>中:

structsoftirq_action {

            void( *action)(struct softirq_action *);         

/*待執行的函數*/

         Void*date;             /傳遞給函數的參數*/

                                 } ;

kernel/softirq.c中定義了一個(gè)包含有32個(gè)該結構體的數組。     

staticstrcut softirq_action softirq_vec[32]; 每個(gè)注冊的軟中斷都占據該數組中的一項。

(1)       軟中斷處理程序:

軟中斷處理程序action的函數原型如下:

voidsoftirq_handler(struct softirq_action *)

當內核運行一個(gè)軟中斷處理程序的時(shí)候,它就會(huì )執行這個(gè)action函數,其唯一的參數為指向相應的softirq_action結構體的指針。

一個(gè)軟中斷不會(huì )搶占另外一個(gè)軟中斷,實(shí)際上,唯一可以搶占軟中斷的是中斷處理程序,不過(guò),其它的軟中斷——甚至是相同類(lèi)型的軟中斷——可以在其它處理器上同時(shí)執行。

(2)       執行軟中斷:

一個(gè)注冊的軟中斷必須在被標記后才會(huì )執行。這被稱(chēng)作觸發(fā)軟中斷(raisingthe softirq)。通常,中斷處理程序會(huì )在返回前標記它的軟中斷,使其在稍后被執行。軟中斷被標記后,可以用softirq_pending()檢查到這個(gè)標記并按照索引號將softirq_pending()的返回值的相應位置1。

在合適的時(shí)刻,該軟中斷就會(huì )運行,在下列地方,待處理的軟中斷會(huì )被檢查和執行:

在處理完一個(gè)硬中斷以后

ksoftirqd內核線(xiàn)程中

在那些顯式檢查和執行待處理的軟中斷的代碼中,如網(wǎng)絡(luò )子系統中

不管是用什么辦法喚起,軟中斷都要在do_softirq()中執行,該函數很簡(jiǎn)單,如果有待處理的軟中斷,do_softirq()會(huì )遍歷每一個(gè),調用它們的處理程序。

軟中斷在do_softirq()中執行。do_softirq()經(jīng)過(guò)簡(jiǎn)化后的核心部分:

u32 pending = sofeirq_pending(cpu);

if(pending) {

struct softirq_action *h = softirq_vec;

softirq_pending(cpu) = 0;

do {

       if(pending&1) h->action(h);    //調用action函數

       h++;

      pending>>=1;

       }while(pending);

}

 

6.2.2使用軟中斷

軟中斷保留給系統中對時(shí)間要求最嚴格以及最重要的下半部使用。內核定時(shí)器和tasklets都是建立在軟中斷上的,如果你想加入一個(gè)新的軟中斷,首先要想想為什么用tasklet實(shí)現不了,tasklet可以動(dòng)態(tài)生成,由于它們對加鎖的要求不高,所以使用起來(lái)也很方便,當然,對于時(shí)間要求養并能自己高效的完成加鎖工作的應用,軟中斷會(huì )是正確的選擇。

1、  分配索引:在編譯期間,可以通過(guò)<linux/interrupt.h>中定義的一個(gè)枚舉類(lèi)型來(lái)靜態(tài)的聲明軟中斷。

2、  注冊處理程序:接著(zhù),在運行時(shí)通過(guò)調用open_softirq()注冊軟件中斷處理程序,該函數有三個(gè)參數:索引號、處理函數和data域存放的數值。例如網(wǎng)絡(luò )子系統,通過(guò)以下方式注冊自己的軟中斷:

open_softirq(NET_TX_SOFTIRQ,net_tx_action,NULL);

open_softirq(NET_TX_SOFTIRQ,net_rx_action,NULL);

軟中斷處理程序的執行的時(shí)候,允許響應中斷,但自己不能睡眠。

3、  觸發(fā)你的軟中斷:

通過(guò)在枚舉類(lèi)型的列表中添加新項以及調用open_softirq()進(jìn)行注冊以后,新的軟中斷處理程序就能夠運行。raise_softirq()函數可以將一個(gè)軟中斷設置為掛起狀態(tài),讓他在下次調用do_softirq()函數時(shí)投入運行。一個(gè)例子:

raise_softirq(NET_TX_SOFTIRQ);

這會(huì )觸發(fā)NET_TX_SOFTIRQ軟中斷。它的處理程序net_tx_action()就會(huì )在內核下一次執行軟中斷時(shí)投入運行。該函數在觸發(fā)一個(gè)軟中斷前要禁止中斷,觸發(fā)后再恢復回原來(lái)的狀態(tài)。在中斷處理程序中觸發(fā)軟中斷是最常見(jiàn)的形式。這樣,內核在執行完中斷處理程序后,馬上就會(huì )調用do_softirq。于是軟中斷開(kāi)始執行中斷處理程序留給它去完成的剩余任務(wù)。

 

6.3 Tasklet

tasklet是利用軟中斷實(shí)現的一種下半部機制。它和進(jìn)程沒(méi)有任何關(guān)系。它和軟中斷本質(zhì)上很相似,行為表現也相近,但是,它的接口更簡(jiǎn)單,鎖保護也要求較低。

軟中斷和tasklet怎樣選擇呢?

通常你應該用tasklet,軟中斷一般用的很少,它只在那些執行頻率很高和連續性要求很高的情況下才需要,而tasklet卻有更廣泛的用途。

 

6.3.1 Tasklet的實(shí)現

因為tasklet是通過(guò)軟中斷實(shí)現的,所以它本身也是軟中斷。

1tasklet結構體tasklettasklet_struct結構表示。每個(gè)結構體單獨代表一個(gè)tasklet,它在<linux/interrupt.h>中定義:

struct tasklet_struct {

            struct task_struct   *next;      /*指向鏈表中的下一個(gè)tasklet*/

            unsigned   long   state;         /* tasklet的狀態(tài)*/

             atomic_t    count;                 /* 引用計數器*/

             void (*func) (unsigned long);     /* tasklet處理函數*/

            unsigned long data;                  /*tasklet處理函數的參數*/

};

結構體中的func成員是tasklet的處理程序,data是它唯一的參數。state成員只能在0、TASKLET_STATE_SCHEDTASKLET_STATE_RUN之間取值。TASKLET_STATE_SCHED表明tasklet已經(jīng)被調度,正準備投入運行,TASKLET_STATE_RUN表示該tasklet正在運行。只有count0時(shí),tasklet才被激活,否則被允許,不允許執行。

 

調度tasklet

已調度的tasklet存放在兩個(gè)單處理器數據結構:tasklet_vectask_hi_vec。它們都是由tasklet_struct結構體構成的鏈表。鏈表中的每個(gè)tasklet_struct代表一個(gè)不同的tasklet。

tasklet是由tasklet_schedule()tasklet_hi_schedule()函數進(jìn)行調度的,它們接受一個(gè)指向tasklet_struct結構的指針作為參數。

Tasklet的實(shí)現通過(guò)軟中斷來(lái)實(shí)現的,tasklet_schedule()調度函數執行一些初始工作,緊接著(zhù)喚起TASKLET_SOFTIRQHI_SOFTIRQ軟中斷,這樣在下一次調用do_softirq()時(shí)就會(huì )執行tasklet。

那么do_softirq()函數什么時(shí)候執行呢?

do_softirq()會(huì )盡可能早的在下一個(gè)合適的時(shí)機執行,由于大部分tasklet和軟中斷都是在中斷處理程序中被設置成待處理狀態(tài),所以最近一個(gè)中斷返回的時(shí)候看起來(lái)就是執行do_softirq()的最佳時(shí)機。因為TASKLET_SOFTIRQHI_SOFTIRQ已經(jīng)被觸發(fā)了,所以do_softirq會(huì )執行相應的軟中斷處理程序。

Tasklet_action()Tasklet_hi_action()兩個(gè)處理程序就是tasklet處理的核心。

總結:所有的Tasklets都通過(guò)重復運用TASKLET_SOFTIRQHI_SOFTIRQ這兩個(gè)軟中斷實(shí)現,當一個(gè)tasklet被調度時(shí),內核就會(huì )喚起這兩個(gè)軟中斷中的一個(gè),隨后,該軟中斷會(huì )被特定的函數處理,執行所有已調度的tasklet,這個(gè)函數保證同一時(shí)間里只有一個(gè)給定類(lèi)別的tasklet會(huì )被執行(但其它不同類(lèi)型的tasklet可以同時(shí)執行),所有這些復雜性都被一個(gè)簡(jiǎn)潔的接口隱藏起來(lái)了。

 

6.3.2使用tasklet

聲明你自己的tasklet

可以靜態(tài)創(chuàng )建,也可以動(dòng)態(tài)創(chuàng )建,分別對應直接引用和間接引用。選擇哪種方式取決于你到底是有(或者是想要)一個(gè)對tasklet的直接引用還是間接引用,靜態(tài)創(chuàng )建一個(gè)tasklet(也就是有一個(gè)直接引用),使用下面<linux/interrupt.h>中定義的兩個(gè)宏中的一個(gè):

 

DECLARE_TASKLET(name,func, data)

實(shí)現了定義名稱(chēng)為nametasklet并將其與func這個(gè)函數綁定,而傳入這個(gè)函數的參數為data。

DECLARE_TASKLET_DISABLED(name, func, data);

DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);運行代碼實(shí)際上等價(jià)于:

structtasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),my_tasklet_handler, dev };    這樣就創(chuàng )建了一個(gè)名為my_tasklet,處理程序為tasklet_handler并且已經(jīng)被激活的tasklet。

還可以通過(guò)一個(gè)間接引用(一個(gè)指針)賦給一個(gè)動(dòng)態(tài)創(chuàng )建的tasklet_struct結構的方式來(lái)初始化一個(gè)tasklet

Tasklet_init(t,tasklet_handler,dev);/*動(dòng)態(tài)而不是靜態(tài)創(chuàng )建*/

 

編寫(xiě)你自己的tasklet處理程序

必須符合規定的函數類(lèi)型:                                              

voidtasklet_handler(unsigned long data)

因為是靠軟件中斷實(shí)現,所以tasklet不能睡眠,這意味著(zhù)你不能在tasklet中使用信號量或者其它什么阻塞式的函數。

 

調度你自己的tasklet

通過(guò)調用task_schedule()函數并傳遞給它相應的tasklet_struct的指針,該tasklet就會(huì )被調度以便執行。

tasklet_schedule(&my_tasklet);  /*my_tasklet標記為掛起*/

 

下面我們看一下軟中斷和tasklet的異同:

在前期準備工作上,首先要給軟中斷分配索引,而tasklet則要用宏對處理程序聲明。在給軟中斷分配索引后,還要通過(guò)open_softirq()函數來(lái)注冊處理程序。這樣看來(lái),tasklet是一步到位,直接到了處理函數,而軟中斷需要做更多工作。接下來(lái)軟中斷要等待觸發(fā)(raise_softirq()raise_softirq_irqoff,tasklet則是等待tasklet_schedule()tasklet_hi_schedule()對其進(jìn)行調度。兩者雖然在命名上不同,但殊途同歸,最終的結果都是等待do_softirq()去執行處理函數,即將下半部設置為待執行狀態(tài)以便稍后執行。另外,在tasklettasklet_schedule()中,需要完成的動(dòng)作之一便是喚起(觸發(fā))TASKLET_SOFTIRQHI_SOFTIRQ軟中斷,說(shuō)明tasklet仍然是基于軟中斷的。在進(jìn)入do_softirq()之后,所做的工作仍然有所不同,不再論述。

軟中斷和工作隊列都是異步發(fā)生的(就是說(shuō),在中斷處理返回的時(shí)候)

 

6.4工作隊列

 

工作隊列work queue)是另外一種將工作推后執行的形式,他和我們前面討論過(guò)的其他形式完全不同。工作隊列可以把工作推后,交由一個(gè)內核線(xiàn)程去執行——這個(gè)下半部總是會(huì )在進(jìn)程上下文執行。這樣,通過(guò)工作隊列執行的代碼能占盡進(jìn)程上下文的所有優(yōu)勢,最重要的是工作隊列允許重新調度甚至是睡眠。

如果你需要用一個(gè)可以重新調度的實(shí)體來(lái)執行你的下半部處理,你應該使用工作隊列,它是唯一能在進(jìn)程上下文運行的下半部實(shí)現的機制,也只有它才可以睡眠,這意味著(zhù)你在你需要獲得大量的內存時(shí),在你需要獲取信號量時(shí),在你需要執行阻塞式的IO操作時(shí),它都會(huì )非常有用,如果你不需要用一個(gè)內核線(xiàn)程來(lái)推后執行工作,那么就考慮使用tasklet吧!

 

6.4.1工作隊列的實(shí)現

工作隊列子系統是一個(gè)用于創(chuàng )建內核線(xiàn)程的接口,通過(guò)它創(chuàng )建的進(jìn)程負責執行由內核其他部分排到隊列里的其他任務(wù)。它創(chuàng )建的這些內核線(xiàn)程被稱(chēng)作工作者線(xiàn)程。工作隊列可以讓驅動(dòng)程序創(chuàng )建一個(gè)專(zhuān)門(mén)的工作者線(xiàn)程來(lái)處理需要推后的工作。不過(guò),工作隊列子系統提供了一個(gè)默認的工作者線(xiàn)程來(lái)處理這些工作。因此,工作隊列最基本的表現形式就轉變成了一個(gè)把需要推后執行的任務(wù)交給特定的通用線(xiàn)程這樣一個(gè)接口。

 

表示線(xiàn)程的數據結構

工作者線(xiàn)程用workqueue_struct結構表示:

struct workqueue_struct {

            struct cpu_workqueue_struct cpu_wq[NR_CPUS];

            const char *name;

           struct   list_head   list;

          };

該結構內是一個(gè)由cpu_workqueue_struct結構組成的數組,定義在kernel/workqueue.c中,數組的每一項對應一個(gè)系統中的處理器。每個(gè)工作者線(xiàn)程都對應這樣的cpu_workqueue_struct結構體。cpu_workqueue_structkernel/workqueue.c中的核心數據結構:   

struct cpu_workqueue_struct     {

                 spinlock_t    lock;             /* 鎖定以便保護該結構體*/

                 long    romove_sequeue; /* 最近一個(gè)被加上的(下一個(gè)要運行的)*/

                 long   insert_sequeue;     /*下一個(gè)要加上的  */

                wait_queue_head_t    more_work;

                wait_queue_head_t     work_done;

                struct   workqueue_struct   *wq;          /* 有關(guān)聯(lián)的workqueue_struct結構*/

                task_t   *thread;                                 /* 有關(guān)聯(lián)的線(xiàn)程*/

               int  run_depth;                                     /* run_workqueue()循環(huán)深度  */

               };

由此可以看出,每個(gè)工作者線(xiàn)程類(lèi)型關(guān)聯(lián)一個(gè)自己的workqueue_struct。在該結構體里面,給每個(gè)線(xiàn)程分配一個(gè)cpu_workqueue_struct,因而也就是給每個(gè)處理器分配一個(gè),因為每個(gè)處理器都有一個(gè)該類(lèi)型的線(xiàn)程。

表示工作的數據結構

        所有工作者線(xiàn)程都是用普通的內核線(xiàn)程實(shí)現的,它們都要執行worker_thread()函數。在它初始化完以后,這個(gè)函數(worker_thread)開(kāi)始休眠。當有操作被插入到隊列的時(shí)候,線(xiàn)程就會(huì )被喚醒,以便執行這些操作。當沒(méi)有剩余的操作時(shí),它又會(huì )繼續睡眠。

工作用<linux/workqueue.h>中定義的work_struct結構體表示:  

struct    work_struct {

                      unsigned   long   pending;        /* 這個(gè)工作是否正在等待處理*/

                     struct   list_head entry;            /* l連接所有工作的鏈表*/

                    void   (* func) (void *);              /* 處理函數*/

                   void   *wq_data;                        /* 內部使用*/

                    struct timer_list timer;              /* 延遲的工作隊列所用到的定時(shí)器 */

};

這些結構體被連接成鏈表,在每個(gè)處理器的每種類(lèi)型的隊列都對應這樣一個(gè)鏈表。當一個(gè)工作者線(xiàn)程被喚醒時(shí),它會(huì )執行它的鏈表上的所有工作。當工作完畢時(shí),他會(huì )將相應的work_struct對象從鏈表中移去,當鏈表上不再有對象的時(shí)候,它就會(huì )繼續睡眠。

 

6.4.2使用工作隊列

1)創(chuàng )建推后的工作

首先要做的是實(shí)際創(chuàng )建一些需要推后執行的工作??梢酝ㄟ^(guò)DECLARE_WORK在編譯時(shí)靜態(tài)的創(chuàng )建該結構體:

DECLARE_WORK(name, void (*func) (void *), void *data);

這樣就會(huì )靜態(tài)的創(chuàng )建一個(gè)名為name,處理函數為func,參數為datawork_struct結構體。也可以在運行時(shí)通過(guò)指針創(chuàng )建一個(gè)工作:

INIT_WORK(struct work_struct *work, void (*func)(void *),void   *data);

這樣就動(dòng)態(tài)的初始化了一個(gè)由work指向的工作。

2)工作隊列的處理函數

原型是:void  work_handler(void   *data)

這個(gè)函數會(huì )由一個(gè)工作者線(xiàn)程執行,因此,函數會(huì )運行在進(jìn)程上下文中,默認情況下,允許響應中斷,并且不持有任何鎖,如果需要,函數可以睡眠,注意的是,盡管操作處理函數運行在進(jìn)程上下文中,但它不能訪(fǎng)問(wèn)用戶(hù)空間,因為內核線(xiàn)程在用戶(hù)空間沒(méi)有相關(guān)的內存映射,通常在系統調用發(fā)生時(shí),內核會(huì )代表用戶(hù)空間的進(jìn)程運行,此時(shí)它才能訪(fǎng)問(wèn)用戶(hù)空間,也只有在此時(shí)它才會(huì )映射用戶(hù)空間的內存。

3)對工作進(jìn)行調度

現在工作已經(jīng)創(chuàng )建,我們可以調度它了,要把給定工作的處理函數提交給默認的events工作線(xiàn)程,只需調用:schedule_work(&work); work馬上就會(huì )被調度,一旦其所在的處理器上的工作者線(xiàn)程被喚醒,它就會(huì )被執行。

4)刷新操作

刷新工作隊列的函數就是確保在卸載模塊之前,要確保一些操作已經(jīng)執行完畢了,該函數如下:

Voidflush_scheduled_work(void);

該函數會(huì )一直等待,直到隊列中所有對象都被執行以后才返回,在等待所以待處理的工作執行的時(shí)候,該函數會(huì )進(jìn)入休眠狀態(tài),所以只能在進(jìn)程上下文中使用它。

 

5)創(chuàng )建新的工作隊列

當缺省的隊列不能滿(mǎn)足你的需要時(shí),你應該創(chuàng )建一個(gè)新的工作隊列和與之對應的工作者線(xiàn)程。

 

 

 

6.5下半部之間的選擇

1 從設計的角度考慮

軟中斷提供的執行序列化的保障最少,這就要求軟中斷必須采取一些步驟確保共享數據的安全。如果被考察的代碼本身多線(xiàn)索化的工作就做得非常好,軟中斷就很好,對于時(shí)間要求嚴格和執行頻率很高的話(huà),它執行的也快。如果代碼本身多線(xiàn)索化的工作就做得不充分,就選擇tasklet比較好,由于兩個(gè)同種類(lèi)型的tasklet不能同時(shí)執行,實(shí)現起來(lái)也很簡(jiǎn)單一些。

2 如果你需要把任務(wù)推到進(jìn)程上下文中完成,只能選擇工作隊列。

如果不需要睡眠,那么軟中斷和工作隊列就更合適。工作隊列造成的開(kāi)銷(xiāo)最大,因為他要牽扯到內核線(xiàn)程甚至是上下文切換。

3 說(shuō)到易用性,工作隊列最好,使用缺省的events隊列簡(jiǎn)直不費吹灰之力。接下來(lái)就tasklet。他的的接口很簡(jiǎn)單,最后才是軟中斷,它必須靜態(tài)創(chuàng )建。

 

6.6在下半部之間加鎖

使用tasklet的一個(gè)好處是在于它自己負責執行的序列化保障,兩個(gè)相同類(lèi)型的tasklet不允許同時(shí)執行,即使在不同的處理器上也不行,意味著(zhù)你無(wú)須考慮相同類(lèi)型的tasklet內部的同步問(wèn)題。當然,tasklet之間的同步(兩個(gè)不同類(lèi)型的tasklet共享同一數據時(shí))需要正確使用鎖機制。

因為軟中斷根本不保障執行序列化,(即使相同類(lèi)型的軟中斷也有可能有兩個(gè)實(shí)例在同時(shí)執行)所以所有的共享數據都需要合適的鎖。

如果進(jìn)程上下文和一個(gè)下半部共享數據,在訪(fǎng)問(wèn)這些數據之前,你需要禁止下半部的處理并得到鎖的使用權,所做的這些是為了本地和SMP的保護并且防止死鎖的出現。

如果中斷上下文和一個(gè)下半部共享數據,在訪(fǎng)問(wèn)數據之前,你需要禁止中斷并得到鎖的使用權,所做的這些是為了本地和SMP的保護并且防止死鎖的出現。

任何在工作隊列中被共享的數據也需要使用鎖機制,其中有關(guān)鎖的要點(diǎn)和在一般內核代碼中沒(méi)什么區別,因為工作隊列本來(lái)就是在進(jìn)程上下文中執行的。

 

禁止下半部

一般單純禁止下半部的處理是不夠的,為了保證共享數據的安全,更常見(jiàn)的做法是先得到一個(gè)鎖然后在禁止下半部的處理,驅動(dòng)程序中通常使用的都是這種方法。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
linux 中斷機制淺析
Linux內核開(kāi)發(fā)之中斷與時(shí)鐘(一)
Linux 內核 tasklet 機制和工作隊列
Linux內核中斷頂半部和底半部的理解
Linux中斷處理淺析
linux設備驅動(dòng)歸納總結(六):3.中斷的上半部和下半部——tasklet
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久