歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

Linux 2.4.x內核軟中斷機制

  作者:楊沙洲     本文從Linux內核幾種軟中斷機制相互關系和發展沿革入手,分析了這些機制的實現方法,給出了它們的基本用法。  一. 軟中斷概況    軟中斷是利用硬件中斷的概念,用軟件方式進行模擬,實現宏觀上的異步執行效果。很多情況下,軟中斷和"信號"有些類似,同時,軟中斷又是和硬中斷相對應的,"硬中斷是外部設備對CPU的中斷","軟中斷通常是硬中斷服務程序對內核的中斷","信號則是由內核(或其他進程)對某個進程的中斷"(《Linux內核源代碼情景分析》第三章)。軟中斷的一種典型應用就是所謂的"下半部"(bottom half),它的得名來自於將硬件中斷處理分離成"上半部"和"下半部"兩個階段的機制:上半部在屏蔽中斷的上下文中運行,用於完成關鍵性的處理動作;而下半部則相對來說並不是非常緊急的,通常還是比較耗時的,因此由系統自行安排運行時機,不在中斷服務上下文中執行。bottom half的應用也是激勵內核發展出目前的軟中斷機制的原因,因此,我們先從bottom half的實現開始。    二. bottom half    在Linux內核中,bottom half通常用"bh"表示,最初用於在特權級較低的上下文中完成中斷服務的非關鍵耗時動作,現在也用於一切可在低優先級的上下文中執行的異步動作。最早的bottom half實現是借用中斷向量表的方式,在目前的2.4.x內核中仍然可以看到:    static void (*bh_base[32])(void); /* kernel/softirq.c */  系統如此定義了一個函數指針數組,共有32個函數指針,采用數組索引來訪問,與此相對應的是一套函數:    void init_bh(int nr,void (*routine)(void));  為第nr個函數指針賦值為routine。    void remove_bh(int nr);  動作與init_bh()相反,卸下nr函數指針。    void mark_bh(int nr);  標志第nr個bottom half可執行了。    由於歷史的原因,bh_base各個函數指針位置大多有了預定義的意義,在v2.4.2內核裡有這樣一個枚舉:    enum {   TIMER_BH = 0,   TQUEUE_BH,   DIGI_BH,   SERIAL_BH,   RISCOM8_BH,   SPECIALIX_BH,   AURORA_BH,   ESP_BH,   SCSI_BH,   IMMEDIATE_BH,   CYCLADES_BH,   CM206_BH,   JS_BH,   MACSERIAL_BH,   ISICOM_BH   };     並約定某個驅動使用某個bottom half位置,比如串口中斷就約定使用SERIAL_BH,現在我們用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但語義已經很不一樣了,因為整個bottom half的使用方式已經很不一樣了,這三個函數僅僅是在接口上保持了向下兼容,在實現上一直都在隨著內核的軟中斷機制在變。現在,在2.4.x內核裡,它用的是tasklet機制。    三. task queue    在介紹tasklet之前,有必要先看看出現得更早一些的task queue機制。顯而易見,原始的bottom half機制有幾個很大的局限,最重要的一個就是個數限制在32個以內,隨著系統硬件越來越多,軟中斷的應用范圍越來越大,這個數目顯然是不夠用的,而且,每個bottom half上只能掛接一個函數,也是不夠用的。因此,在2.0.x內核裡,已經在用task queue(任務隊列)的辦法對其進行了擴充,這裡使用的是2.4.2中的實現。    task queue是在系統隊列數據結構的基礎上建成的,以下即為task queue的數據結構,定義在include/linux/tqueue.h中:    strUCt tq_struct {   struct list_head list; /* 鏈表結構 */   unsigned long sync; /* 初識為0,入隊時原子的置1,以避免重復入隊 */   void (*routine)(void *); /* 激活時調用的函數 */   void *data; /* routine(data) */  };    typedef struct list_head task_queue;    在使用時,按照下列步驟進行:    DECLARE_TASK_QUEUE(my_tqueue); /* 定義一個my_tqueue,實際上就是一個以tq_struct為元素的list_head隊列 */   說明並定義一個tq_struct變量my_task;   queue_task(&my_task,&my_tqueue); /* 將my_task注冊到my_tqueue中 */   run_task_queue(&my_tqueue); /* 在適當的時候手工啟動my_tqueue */   大多數情況下,都沒有必要調用DECLARE_TASK_QUEUE()定義自己的task queue,因為系統已經預定義了三個task queue:    tq_timer,由時鐘中斷服務程序啟動;   tq_immediate,在中斷返回前以及schedule()函數中啟動;   tq_disk,內存管理模塊內部使用。   一般使用tq_immediate就可以完成大多數異步任務了。    run_task_queue(task_queue *list)函數可用於啟動list中掛接的所有task,可以手動調用,也可以掛接在上面提到的bottom half向量表中啟動。以run_task_queue()作為bh_base[nr]的函數指針,實際上就是擴充了每個bottom half的函數句柄數,而對於系統預定義的tq_timer和tq_immediate的確是分別掛接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH沒有如此使用,但TQUEUE_BH也是在do_timer()中啟動的),從而可以用於擴充bottom half的個數。此時,不需要手工調用run_task_queue()(這原本就不合適),而只需調用mark_bh(IMMEDIATE_BH),讓bottom half機制在合適的時候調度它。    四. tasklet    由上看出,task queue以bottom half為基礎;而bottom half在v2.4.x中則以新引入的tasklet為實現基礎。    之所以引入tasklet,最主要的考慮是為了更好的支持SMP,提高SMP多個CPU的利用率:不同的tasklet可以同時運行於不同的CPU上。在它的源碼注釋中還說明了幾點特性,歸結為一點,就是:同一個tasklet只會在一個CPU上運行。    struct tasklet_struct  {   struct tasklet_struct *next; /* 隊列指針 */   unsigned long state; /* tasklet的狀態,按位操作,目前定義了兩個位的含義:   TASKLET_STATE_SCHED(第0位)或TASKLET_STATE_RUN(第1位) */   atomic_t count; /* 引用計數,通常用1表示disabled */   void (*func)(unsigned long); /* 函數指針 */   unsigned long data; /* func(data) */  };    把上面的結構與tq_struct比較,可以看出,tasklet擴充了一點功能,主要是state屬性,用於CPU間的同步。    tasklet的使用相當簡單:    定義一個處理函數void my_tasklet_func(unsigned long);   DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /*  定義一個tasklet結構my_tasklet,與my_tasklet_func(data)函數相關聯,相當於DECLARE_TASK_QUEUE() */   tasklet_schedule(&my_tasklet); /*  登記my_tasklet,允許系統在適當的時候進行調度運行,相當於queue_task(&my_task,&tq_immediate)和mark_bh(IMMEDIATE_BH) */   可見tasklet的使用比task queue更簡單,而且,tasklet還能更好的支持SMP結構,因此,在新的2.4.x內核中,tasklet是建議的異步任務執行機制。除了以上提到的使用步驟外,tasklet機制還提供了另外一些調用接口:    DECLARE_TASKLET_DISABLED(name,function,data); /*  和DECLARE_TASKLET()類似,不過即使被調度到也不會馬上運行,必須等到enable */  tasklet_enable(struct tasklet_struct *); /* tasklet使能 */  tasklet_disble(struct tasklet_struct *); /* 禁用tasklet,只要tasklet還沒運行,則會推遲到它被enable */  tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); /* 類似DECLARE_TASKLET() */  tasklet_kill(struct tasklet_struct *); /* 清除指定tasklet的可調度位,即不允許調度該tasklet,但不做tasklet本身的清除 */    前面提到過,在2.4.x內核中,bottom half是利用tasklet機制實現的,它表現在所有的bottom half動作都以一類tasklet的形式運行,這類tasklet與我們一般使用的tasklet不同。    在2.4.x中,系統定義了兩個tasklet隊列的向量表,每個向量對應一個CPU(向量表大小為系統能支持的CPU最大個數,SMP方式下目前2.4.2為32)組織成一個tasklet鏈表:    struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;  struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;    另外,對於32個bottom half,系統也定義了對應的32個tasklet結構:    struct tasklet_struct bh_task_vec[32];  在軟中斷子系統初始化時,這組tasklet的動作被初始化為bh_action(nr),而bh_action(nr)就會去調用bh_base[nr]的函數指針,從而與bottom half的語義掛鉤。mark_bh(nr)被實現為調用tasklet_hi_schedule(bh_tasklet_vec+nr),在這個函數中,bh_tasklet_vec[nr]將被掛接在tasklet_hi_vec[cpu]鏈上(其中cpu為當前cpu編號,也就是說哪個cpu提出了bottom half的請求,則在哪個cpu上執行該請求),然後激發HI_SOFTIRQ軟中斷信號,從而在HI_SOFTIRQ的中斷響應中啟動運行。    tasklet_schedule(&my_tasklet)將把my_tasklet掛接到tasklet_vec[cpu]上,激發TASKLET_SOFTIRQ,在TASKLET_SOFTIRQ的中斷響應中執行。HI_SOFTIRQ和TASKLET_SOFTIRQ是softirq子系統中的術語,下一




Copyright © Linux教程網 All Rights Reserved