天天看點

linux核心設計與實作(2)-- 中斷和中斷處理1. 中斷2. 下半部和推後執行的工作

1. 中斷

       中斷就是一些外設硬體發送通知給處理器的通道。外設可以不在cpu的幹預下執行一些動作,在完成這些任務後通過中斷通知CPU。每個中斷都有一個系統唯一的中斷号。

1.1 中斷上半部和下半部

       為了解決中斷處理函數運作得快且完成盡量多的工作量的沖突,我們一般把中斷處理切為兩部分。

  • 上半部:會在接受到一個中斷後立刻開始執行,但是隻是做一些有嚴格時限的工作,例如中斷應答和複位硬體,這些工作都是在所有中斷被禁止的情況下完成的。
  • 下半部:可以推遲的工作。比如網卡中斷會在上半部分把網絡資料拷貝到記憶體中,提高資料吞吐量。而網路資料協定的解析就可以放到下半部分區執行。

1.2 中斷處理函數

       中斷處理函數利用request_irp()。

request_irp()函數可能會随眠,不能在中斷上下文或者不允許阻塞的代碼中調用改函數。

       上半部的中斷處理程式無須重入的,因為在執行的時候目前的所有中斷線都是被禁止的。

  • 共享中斷處理程式

    一個中斷号可以注冊多個中斷處理函數來實作所有的裝置共享這個中斷。共享中斷的處理函數會形成一個表,在中斷來的時候都會執行這些連結清單中的函數,而這些中斷處理函數需要能夠區分這些裝置是否真正産生了中斷。

1.3 中斷上下文

        程序在進入核心時即程序上下文可以睡眠也可以調用排程程式。因為沒有後備程序,中斷上下文不可以睡眠。

1.4 中斷控制和處理機制的實作

  • /proc/interrupts:檢視所注冊的中斷以及中斷裝置,控制器,中斷次數等資訊,可以通過cat這個檔案檢視核心中斷的資訊

2. 下半部和推後執行的工作

        下半部的任務就是執行與終端處理密切相關但中斷處理程式本身不執行的工作。

        中斷處理函數完成與硬體相關等的和時間密切相關的動作。而需要比較多的時間來處理的工作,比如網絡資料的協定解析就留給下半部來執行。

        上半部與下半部任務配置設定的經驗:

        如果一個任務對時間非常敏感,那就應該放到中斷處理函數中執行

        如果一個任務和硬體相關,那就應該放在中斷處理函數中執行

        如果一個任務要保證不被其他中斷(特别是相同的中斷)打斷,那就應該凡在中斷處理函數中執行

        其他所有的任務,考慮全部放到下半部去執行。

2.1 下半部實作的方法

一,工作隊列

        工作隊列是把工作推後,交由一個核心線程去執行—這個下半部分總是在程序上線文中執行(而不是中斷上下文)。這樣,他就有了程序上下文的優勢,可以在工作隊列中重新排程或者是休眠。

1,任務隊列相關的資料結構

(1)表示線程的資料結構:

struct workqueue_struct { 
    struct cpu_workqueue_struct *cpu_wq; 
    struct list_head list; 
    const char *name; 
    int singlethread; 
    int freezeable; /* Freeze threads during suspend */ 
    int rt;
};
           

       該結構是一個由cpu_workqueue_struct結構組成的數組,它定義在kernel/woekqueue.c,數組的沒一項對應系統中的一個處理器。每個處理器有一個工作者線程,是以每工作者線程對應一個這樣的cpu_workqueue_struct結構體。

struct cpu_workqueue_struct { 
    spinlock_t lock; //鎖保護這種結構 
    struct list_head worklist; //工作清單 
    wait_queue_head_t more_work; 
    struct work_struct *current_work; 
    struct workqueue_struct *wq; //關聯工作隊列結構 
    struct task_struct *thread; //關聯線程 
} ____cacheline_aligned;
           

       每種工作者線程在每個cpu中都有一個線程,該線程的資料結構如上。

(2)表示工作的資料結構:

Struct work_struct{
Atomic_long_t data;
Struct list_head entry;
Work_fun_t func;
}
           

       這些結構體會被作為隊列連接配接成連結清單,在每個處理器上的美中類型的隊列都對應這樣的一個連結清單。當工作者線程被喚醒時,它就執行連結清單中的工作。當執行完畢過後,它就會從連結清單中剔除出去。

(3)三種資料接頭的關系

       工作者線程位于最上層。系統允許有許多類型的工作者線程存在( workqueue_struct)。而一個類型的工作者線程又在每個cpu上存在一個線程( cpu_workqueue_struct)。而在每個線程者線程中又可以挂很多的工作(work_struct)。

2,建立下半部推後的工作。

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

來建立工作結構體,并形成連結清單

3,對工作進行排程

在中斷處理函數中調用工作排程,實作工作隊列下半部

schedule_work(Struct work_struct * work)

schedule_delayed_work(Struct work_struct * work)

queue_work(struct workqueue_struct *wq,Struct work_struct * work)

queue_delayed_work(struct workqueue_struct *wq,Struct work_struct * work)

使用以上四個函數可以讓工作進行排程,前兩個是預設的work,後面兩個是指定events隊列。

可以在核心源碼中檢視工作隊列的使用,加深對其的了解

二,軟中斷

核心定義了的軟中斷的中斷号

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,  /*Preferable RCU should always be the last softirq */
    NR_SOFTIRQS
};
           

       從中可以看出,網絡發送接收下半部用到了軟中斷,以及中斷下半部的tasklet也是采用軟中斷實作的。軟中斷的具體實作在kernel/softirq.c中。

1,軟中斷實作

(1)軟中斷處理函數

Void softirq_handler(struct softirq_action *)

(2)執行軟中斷

一個注冊的軟中斷必須被标記後才會執行,這個标記就是觸發軟中斷。在合适的時機,标記過後的軟中斷就會被執行。在以下情況,待處理的軟中斷會被檢查和執行:

①,從一個硬體中斷傳回時

②,在ksoftirqd核心線程中

③,在顯示檢查和執行待處理的軟中斷的代碼中,如網絡子系統。

不管是用什麼辦法,軟中斷都是通過do_softirq()中去檢查然後執行

2,使用軟中斷

       目前,隻要網絡和排程子系統直接使用了軟中斷,同時,核心定時器和tasklet也是基于軟中斷實作的。如果想加入一個軟中斷,可以先問一下tasklet為什麼不能實作。

(1)配置設定索引

       前面的枚舉值定義了現在已經使用的軟中斷,如果要使用軟中斷。則需要在其中添加索引,一般是在BLOCK_SOFTIRQ與 TASKLET_SOFTIRQ,之間添加自己的軟中斷

(2)注冊軟中斷處理函數

Open_softirq(NET_TX_SOFTIRQ, net_tx_axtion);

(3)觸發軟中斷

Raise_softirq(NET_TX_SOFTIRQ)

       在中斷處理程式中觸發軟中端是最常見的形式。在這種情況下,中斷處理函數執行硬體裝置中斷後觸發軟中斷,當退出中斷後就執行軟中斷的處理。

具體可以參考核心源碼中的網絡軟中斷實作

三,Tasklet

結構體資料

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};
           

1,tasklet的實作與使用

(1)聲明自己的tasklet

DECLARE_TASKLET(name,func,data) //靜态的建立

Tasklet_init(t, tasklet_hander, dev) //動态的建立

(2)編寫自己的tasklet處理函數

Void tasklet_handler(unsigned long data)

(3)排程自己的tasklet

tasklet_schedule(struct tasklet_struct *t);

2,tasklet的執行—ksoftirqd

前面說到tasklet的實作也是通過軟中斷來實作的,并且在ksoftirqd中會執行軟中斷。

每個處理器都有一個輔助處理軟中斷的核心線程—ksoftirqd/n n就是處理器的編号。

線程的實作在 kernel/softirq.c中,被設計為以下的死循環

For(;;)
{
    If(!softirq_pending(CPU))
    Schedule();
    Set_current_state(TASK_RUNNING);
    While(softirq_pending(cpu)
    {
	    Do_doftirq();
	    If(need_resched())
	    {
	   	 	Schedule();
	    }
    }
    Set_current_state()TASK_INTERRUPTIBLE);
}
           

       隻要有待處理的軟中低,ksoftirq就會調用do_softirq()去處理他們。同時tasklet是軟中斷實作的,也就會同時被調用。

3.在下半部加鎖

       在不通的程序,或者程序和中斷之間共享資料的時候都需要鎖來保證資料的同步。

       因為軟中斷和tasklet是中斷上下文,是以在有這些環境相關的情況下,在擷取鎖之後需要禁止中斷下半部或者中斷,保證不被死鎖。如果下半部擷取了鎖之後被其它中斷打斷,并且中斷也需要這個鎖,那麼就會出現死鎖的情況。

4. 三種實作方法的差別

1,tasklet與軟中斷不能睡眠,是中斷上下文環境

2,工作隊列可以睡眠,可以産出排程,是程序上下文環境

當選擇方法的時候,如果需要睡眠,則選擇任務隊列;當不需要睡眠的時候,選擇tasklet,當tasklet無法實作時才考慮使用軟中斷。

3,軟中斷處理程式執行的時候,允許響應中斷,但自己不能休眠。

4,如果軟中斷在執行的時候再次觸發,則别的處理器可以同時執行,是以加鎖很關鍵。

5,tasklet負責執行的序列化保障,兩個類型的tasklet不允許同時執行,即使在不同的處理器上也不行。是以相同tasklet之間不需要操心同步的問題,隻需要考慮不同tasklet之間的同步

繼續閱讀