萬盛學電腦網

 萬盛學電腦網 >> 服務器教程 >> linux中斷編程&.amp

linux中斷編程&.amp

   1.中斷可以隨時的打斷處理機對其他程序的執行,如果被打斷的代碼對系統很重要,那麼此時中斷處理程序的執行時間應該是越短越好。

  2.通過上文我們知道,中斷處理程序正在執行時,會屏蔽同條中斷線上的中斷請求;而更嚴重的是,如果設置了IRQF_DISABLED,那麼該中斷服務程序執行是會屏蔽所有其他的中斷請求。那麼此時應該讓中斷處理程序執行的越快越好。

  上面的幾個例子都要求中斷服務程序的執行時間越短越好。一般的,中斷處理程序會在上半部分執行。而事實上,幾乎所有的情況,上半部分就只執行中斷處理程序。因此,我們可以這樣認為:一個完整的中斷處理流程是由中斷處理程序和下半部分共同完成的。

  這樣劃分是有一定原因的,因為我們必須有一個快速、異步而且簡單的處理程序專門來負責對硬件的中斷請求做出快速響應,與此同時也要完成那些對時間要求很嚴格的操作。而那些對時間要求相對寬松,其他的剩余工作則會在稍候的任意時間執行,也就是在所謂的下半部分去執行。

  總之,這樣劃分一個中斷處理過程主要是希望減少中斷處理程序的工作量(當然了,理想情況是將全部工作都拋給下半段。但是中斷處理程序至少應該完成對中斷請求的相應。),因為在它運行期間至少會使得同級的中斷請求被屏蔽,這些都直接關系到整個系統的響應能力和性能。而在下半段執行期間,則會允許響應所有的中斷。

  和上半段只能通過中斷處理程序實現不同的是,下半部可以通過多種機制來完成:小任務(tasklet),工作隊列,軟中斷。在本博客後續的文章當中你會看到,不管是那種機制,它們均為下半部提供了一種執行機制,比上半部靈活多了。至於何時執行,則由內核負責。

  以上是上下部分劃分的基本概述,通過tasklet和工作隊列機制,你可以更深刻的理解下部分的執行。

  tasklet的實現

  tasklet(小任務)機制是中斷處理下半部分最常用的一種方法,其使用也是非常簡單的。正如在前文中你所知道的那樣,一個使用tasklet的中斷程序首先會通過執行中斷處理程序來快速完成上半部分的工作,接著通過調用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所調用,至於下半部分何時執行則屬於內核的工作。對應到我們此刻所說的tasklet就是,在中斷處理程序中,除了完成對中斷的響應等工作,還要調用tasklet,如下圖示。

linux中斷編程@amp 三聯

  tasklet由tasklet_struct結構體來表示,每一個這樣的結構體就表示一個tasklet。在中可以看到如下的定義:

  1tasklet_struct

  2{

  3structtasklet_struct *next;

  4unsigned long state;

  5atomic_t count;

  6void(*func)(unsignedlong);

  7unsigned long data;

  8};

  在這個結構體中,第一個成員代表鏈表中的下一個tasklet。第二個變量代表此刻tasklet的狀態,一般為TASKLET_STATE_SCHED,表示此tasklet已被調度且正准備運行;此變量還可取TASKLET_STATE_RUN,表示正在運行,但只用在多處理器的情況下。count成員是一個引用計數器,只有當其值為0時候,tasklet才會被激活;否則被禁止,不能被執行。而接下來的func變量很明顯是一個函數指針,它指向tasklet處理函數,這個處理函數的唯一參數為data。

  使用tasklet

  在使用tasklet前,必須首先創建一個tasklet_struct類型的變量。通常有兩種方法:靜態創建和動態創建。這樣官方的說法仍然使我們不能理解這兩種創建到底是怎麼一回事。不夠透過源碼來分析倒是可以搞明白。

  在中的兩個宏:

  1464#define DECLARE_TASKLET(name, func, data)

  2465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

  3466

  4467#define DECLARE_TASKLET_DISABLED(name, func, data)

  5468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

  就是我們進行靜態創建tasklet的兩種方法。通過第一個宏創建的tasklet處於激活狀態,再通過調度函數被掛起盡而被內核執行;而通過第二個宏創建的tasklet處於禁止狀態。從兩個宏的定義可以看到,所謂的靜態創建就是直接定義個一個名為name的tasklet_struct類型的變量,並將宏中各個參數相應的賦值給這個name變量的各個成員。注意,兩個宏在功能上差異就在於對name變量count成員的賦值上,具體原因在第一部分已經說明。也許你對ATOMIC_INIT這樣的初始化方式感到疑惑,那麼看完定義後,你就會一目了然:

  1//在arch/x86/include/asm/atomic.h中

  215#define ATOMIC_INIT(i) { (i) }

  3//在linux/types.h中

  4190typedef struct{

  5191 intcounter;

  6192} atomic_t;

  與靜態創建相對的是動態創建,通過給tasklet_init函數傳遞一個事先定義的指針,來動態創建一個tasklet。這個函數源碼如下。

  1470void tasklet_init(structtasklet_struct *t,

  2471 void(*func)(unsignedlong), unsignedlongdata)

  3472{

  4473 t->next = NULL;

  5474 t->state = 0;

  6475 atomic_set(&t->count, 0);

  7476 t->func = func;

  8477 t->data = data;

  9478}

  相信你在閱讀上面的代碼是基本上沒有什麼難以理解的地方,不過這裡還是要特別說明一下atomic_set函數:

  1//在arch/x86/include/asm/atomic.h中

  235static inlinevoidatomic_set(atomic_t *v,inti)

  336{

  437 v->counter = i;

  538}

  首先tasklet_init當中,將&t->count傳遞給了此函數。也就是說將atomic_t類型的成員count的地址傳遞給了atomic_set函數。而我們在此函數中卻要為count變量中的成員counter賦值。如果說我們當前要使用i,那麼應該是如下的引用方法:t-》count.i。明白了嗎?

  ok,通過上述兩種方法就可以創建一個tasklet了。同時,你應該注意到不管是上述那種創建方式都有func參數。透過上述分析的源碼,我們可以看到func參數是一個函數指針,它指向的是這樣的一個函數:

  1void tasklet_handler(unsignedlongdata);

  如同上半部分的中斷處理程序一樣,這個函數需要我們自己來實現。

  創建好之後,我們還要通過如下的方法對tasklet進行調度:

  1tasklet_schedule(&my_tasklet)

  通過此函數的調用,我們的tasklet就會被掛起,等待機會被執行

  一個舉例

  在此只分析上下兩部分的調用關系,完整代碼在這裡查看。

  01//define a argument of tasklet struct

  02static struct tasklet_struct mytasklet;

  03

  04static void mytasklet_handler(unsigned longdata)

  05{

  06printk("This is tasklet handler..n");

  07}

  08

  09static irqreturn_t myirq_handler(int irq,void* dev)

  10{

  11staticintcount=0;

  12if(count<10)

  13{

  14printk("-----------%d start--------------------------n",count+1);

  15printk("The interrupt handeler is working..n");

  16printk("The most of interrupt work will be done by following tasklet..n");

  17tasklet_init(&mytasklet,mytasklet_handler,0);

  18tasklet_schedule(&mytasklet);

  19printk("The top half has been done and bottom half will be processed..n");

  20}

  21count++;

  22returnIRQ_HANDLED;

  23}

  從代碼中可以看到,在上半部中通過調用tasklet,使得對時間要求寬松的那部分中斷程序推後執行。

  為什麼還需要工作隊列?

  工作隊列(work queue)是另外一種將中斷的部分工作推後的一種方式,它可以實現一些tasklet不能實現的工作,比如工作隊列機制可以睡眠。這種差異的本質原因是,在工作隊列機制中,將推後的工作交給一個稱之為工作者線程(worker thread)的內核線程去完成(單核下一般會交給默認的線程events/0)。因此,在該機制中,當內核在執行中斷的剩余工作時就處在進程上下文(process context)中。也就是說由工作隊列所執行的中斷代碼會表現出進程的一些特性,最典型的就是可以重新調度甚至睡眠。

  對於tasklet機制(中斷處理程序也是如此),內核在執行時處於中斷上下文(interrupt context)中。而中斷上下文與進程毫無瓜葛,所以在中斷上下文中就不能睡眠。

  因此,選擇tasklet還是工作隊列來完成下半部分應該不難選擇。當推後的那部分中斷程序需要睡眠時,工作隊列毫無疑問是你的最佳選擇;否則,還是用tasklet吧。

  中斷上下文

  在了解中斷上下文時,先來回顧另一個熟悉概念:進程上下文(這個中文翻譯真的不是很好理解,用“環境”比它好很多)。一般的進程運行在用戶態,如果這個進程進行了系統調用,那麼此時用戶空間中的程序就進入了內核空間,並且稱內核代表該進程運行於內核空間中。由於用戶空間和內核空間具有不同的地址映射,

copyright © 萬盛學電腦網 all rights reserved