歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> 《Linux內核設計與實現》學習筆記——中斷、中斷處理程序

《Linux內核設計與實現》學習筆記——中斷、中斷處理程序

日期:2017/3/1 11:44:56   编辑:Linux內核

中斷和中斷處理程序

中斷隨時可能產生,打斷CPU的執行,CPU轉而處理中斷。 不同的設備對應的中斷不同,每個中斷都通過一個唯一的數字標志。
這些中斷值稱為中斷請求(IRQ)線,每個irq線關聯一個數值。 中斷處理程序
響應中斷時,內核會執行一個函數,中斷處理程序/中斷服務例程ISR, 一個設備的中斷處理程序是他的設備驅動的一部分。 IO資源包括 : 中斷,I/O端口,共享RAM,DMA。驅動程序需要管理注冊釋放這些資源。

上半部:接收到中斷就立即執行,只做有嚴格時限的工作,如對中斷應答或復位硬件。
下半部 : 能夠被允許稍後完成的工作推遲到下半部執行。

注冊中斷處理程序
Request_irq(uint irq, irq_handlet_t handler, ulong flasgs,void* dev)注冊中斷處理程序,激活給定的中斷線;這個函數可能睡眠,不能在中斷上下文/其他不允許阻塞的代碼中執行。 卸載驅動程序時
注銷相應中斷處理程序,釋放中斷線。 void free_irq(uint irq, void* dev); Linux的中斷處理程序是無需重入的,給定的中斷處理程序在執行時,相應中斷線在所有處理器上都會被屏蔽

中斷上下文:沒有後備進程,不可睡眠。中斷處理程序打斷了其他的代碼。

中斷處理機制的實現

這裡寫圖片描述

設備產生中斷,通過總線把電信號發送給中斷控制器 如果中斷線是激活的,中斷控制器發送中斷到處理器(處理器特定管腳) 如果處理器沒有禁止該中斷,處理器會停止正在做的事關閉中斷系統,調到預定義的中斷處理程序入口。 每條中斷線調到一個唯一位置。初始入口點保存這個中斷線號,存放寄存器的值調用do_irq()
計算出中斷號,對中斷應答,禁止這條線上的中斷傳遞 確保這條中斷線上有一個有效的處理程序,已經啟動,但沒有執行。 調用handle_IRQ_event()調用中斷線安裝的中斷處理程序。 將中斷禁止,返回到do_IRQ。 做清理工作,返回初始入口點,跳到ret_from_intr() 檢查是否調度掛起;恢復寄存器;內核恢復到中斷的點。

禁止當前處理器中斷和激活中斷,

Local_irq_disable();local_irq_enable();
Unsigned long flags; local_irq_save(flags);local_irq_restore(flags);

禁止指定中斷

Disable_irq(int);禁止中斷向所有處理器的中斷。

中斷處理程序上、下半步處理邏輯分配原則:

上半部:
任務對時間非常敏感 任務和硬件相關 任務保證不被其他中斷打斷,不並發,不阻塞 下半部:
對時間不敏感 和硬件無關 可以被其他中斷打斷,可以睡眠,可以並發

Linux的上半部就是中斷處理程序,下半部有多種機制:

軟中斷

軟中斷是一組靜態定義的下半部接口,有32個,可以在所有處理器上同時執行,類型相同也可以;在編譯時靜態注冊。

實現:

struct softirq_action{ // 表示軟中斷
    void (*action)(struct softirq_action*);
}

32個目前用了6個。

static struct softirq_action soft_irq_vec[NR_SOFTIRQS];//kernel/softirq.c軟中斷數組
中斷處理程序:內核運行軟中斷處理程序的時候,執行action函數。
一個軟中斷不會搶占另外一個軟中斷。唯一可以搶占軟中斷的是中斷處理程序。其他的軟中斷甚至同類型的可以在其他處理器上同時執行 執行軟件中斷:一個注冊的軟件中斷在標記後才會執行,這稱作觸發中斷。
中斷處理程序在返回前標記軟中斷。
在:硬件中斷代碼返回時;在ksoftirq內核線程中;顯示檢查執行軟中斷 處,待處理的軟中斷會被檢查和執行

軟中斷在do_softirq中執行

u32 pending;
pending = local_sofqirq_pending();
if(pending){
    struct softirq_action* h;
    set_softirq_pending(0);
    h = softirq_vec;
    do{
        if(pending & 1){
            h->action();
        }
        h++;
        pending >>=1;
    }while(pending);
}

使用軟中斷

軟中斷留給對時間要求最嚴格及最重要的下半部使用。目前只有網絡,scsi使用內核定時器和tasklet都建立在軟中斷上。

通過枚舉類型靜態聲明軟中斷,並分配索引 注冊處理程序
open_softirq(NET_TX_SOFTIRQ,net_tx_action);軟中斷處裡程序執行時,允許響應中斷,但不能睡眠。由於只禁止當前處理器上的運行,其他處理器可以同時運行處理程序,需要加鎖保護。 rase_softirq(NET_TX_SOFTIRQ)將軟中斷設置為掛起狀態,下次再調用do_softirq時執行。

Tasklet

基於軟件中斷實現的,靈活性強,動態創建的下半部實現機制。兩個不同類型的tasklet可以在不同處理器上運行,但相同的不可以。可以通過代碼動態注冊。

實現:基於軟中斷

struct tasklet_struct {
  struct tasklet_struct *next;
  unsigned long sate;//(0/TASKLET_STATE_SCHED/TASKLET_STATE_RUN)
  atomic_t count;/*引用計數器,0允許執行,否則禁止*/
  void (*func)(unsigned long);/*執行函數*/
  unsigned long data;//func的參數
};

調度:每個處理器有tasklet_vec和tasklet_hi_vec結構,分別為低、高優先級tasklet_strucu鏈表

由tasklet_schedule()和tasklet_hi_schdule()進行調度

檢查tasklet是否為TASKLET_STATE_SCHED.如果是返回 調用_tasklet_schedule 保存中斷狀態,禁止本地中斷 把需要調度的處理器加到tasklet_vec 或tasklet_hi_vec鏈表的頭上 喚起TASKLET_SOFTIRQ或TASKLET_HI_SOFTIRQ軟中斷 恢復中斷狀態並返回

軟中斷處理程序:

tasklet_action(),tasklet_hi_action()的操作

禁止中斷 將當前處理器置為null,清空鏈表 允許中斷 循環遍歷鏈表每一個待處理的tasklet
如果是多處理器系統,判斷是否TASKLET_STATE_SCHED,如果在運行,跳過。 如果未在執行,設置TASKLET_STATE_RUN。 檢查count==0,否則tasklet被禁止,跳過。 執行tasklet,清空TASKLET_STATE_RUN標志 執行下一個tasklet

使用tasklet

聲明tasklet:
DECLARE_TASKLET(name,func,data)DECLARE_TASKLET_DISABLED(.)
tasklet_init(t, tasklet_handler, dev); 編寫處理程序:因為是依靠軟中斷實現的,處理程序不能睡眠。Tasklet允許響應中斷。 調用tasklet_schedule(&my_tasklet);調度tasklet,實際上是標記/掛起,只要有機會,my_tasklet就會盡快執行。

tasklet_disable(&my_tasklet); tasklet_enable(&my_tasklet);禁止和激活

折衷

頻繁中斷或tasklet頻繁發生的時候:盡快處理,用戶進程得不到響應;滯後執行,中斷處理也不快。
==》》使用低優先級核心進程專門處理軟中斷ksoftirqd/n

for(;;){
    if(!softirq_pending(cpu)){
        schedule();
    }
    set_current_state(TASK_RUNNING);
    while(softirq_pending(cpu)){
        do_softirq();
        if(need_schdule())schedule();
    }
}

工作隊列:

將下半部功能交由內核線程執行,有著線程上下文環境,可以睡眠。
提供創建worker threads的接口,提供接口把需要推後執行的任務排到隊列裡,提供默認的工作者線程處理排到隊列裡的下半部工作。

實現:

數據結構

每種工作者線程有一個workqueue_struct結構 裡面有NR_CPUS個cpu_work_queue_strcut對應於每個處理器的一個工作者線程 工作用work_struct表示,含有一個執行函數fuc,每個cpu的工作線程都對應一個work_struct鏈表。

worker_thread()的核心,是一個死循環

線程將自己放置為休眠狀態 若鏈表為空,休眠 不為空,調用run_workqueue函數執行工作。
鏈表不為空時,循環執行
選取下一個節點對象,獲取執行函數和參數 待處理標志清0 調用函數 重復執行

使用:

創建推後的工作
DECLARE_WORK(name, void(func)(void), void*data);靜態
INIT_WORK(strut work_struct* task,…);動態 工作隊列處理函數
運行於進程上下文,允許響應中斷,不持有鎖,可以睡眠。不能訪問用戶空間 對工作進行調度
schedule_work(&work);提交給工作者進程
schedule_delay_work(&work,delay) 刷新操作
flush_scheduled_work(),函數等待隊列中所有對象都被執行以後返回 創建新的工作隊列
如果缺省的隊列不能滿足你的需要,你應該創建新的工作隊列和與之相對應的工作線程。

各種機制的比較

下半部 上下文 順序執行保障 軟中斷 中斷 沒有 Tasklet 中斷 同類型不能同時執行 工作隊列 進程 沒有(和進程上下文一樣,被調度)

如果任務需要推後到進程上下文完成,有休眠的需要 工作隊列
任務隊列接口簡單,同種類型不能同時執行 tasklet
軟中斷提供的執行序列化的保障最少,必須格外小心采取一些步驟確保共享數據

Copyright © Linux教程網 All Rights Reserved