1. 程式人生 > >Linux 中斷學習總結

Linux 中斷學習總結

中斷這個概念並不陌生,在微控制器程式設計裡就用的很多,最常見的如定時器中斷、按鍵中斷等。
在微控制器裡面使用中斷,大致的步驟如下:

  • 1.初始化配置(中斷暫存器等)
  • 2.編寫中斷處理函式

中斷處理過程,大致如下:

  • 1.觸發中斷
  • 2.跳轉至相應中斷處理函式
  • 3.中斷處理完成後恢復至進入中斷前的執行位置繼續執行

中斷與中斷處理函式的繫結關係是通過中斷向量表確定的。

在linux 系統中,中斷的使用相對複雜些,不過大同小異。

1.中斷註冊與登出

函式 說明
request_irq() 在給定的中斷線(由引數irq指定)上註冊一個給定的中斷處理函式
free_irq() 登出給定中斷線的中斷處理函式
  • 1.1 request_irq 函式

request_irq 函式宣告如下(include/linux/interrupt.h):

request_irq(unsigned int irq,
	 irq_handler_t handler, 
	 unsigned long flags,
	 const char *name, void *dev)

第一個引數:irq 是要申請的硬體中斷號。對於某些裝置,如系統時鐘、鍵盤,這個值是預先制定的,而對於大多數裝置來說,這個值是通過探測獲取,或者程式設計動態確定。
第二個引數:handler 是一個指標,指向這個中斷的實際處理函式,中斷髮生時,系統呼叫這個函式。
第三個引數:flags 可以為0,或者是一個或多個標誌的位掩碼,如:IRQF_DISABLED、IRQF_TIMER、IRQF_SHARED等。
第四個引數:name 與中斷相關的裝置的ASCII文字表示,如"keyboard", 這些名字會被/proc/irq ,/proc/interrupts 檔案使用。
第五個引數:dev 一般用它來傳遞程式的驅動結構

  • 1.2 中斷釋放

free_irq

void free_irq(unsigned int irq, void *dev_id)

第一個引數:irq 是要申請的硬體中斷號
第二個引數:dev_id 一般與 註冊時傳的dev相同

2.中斷處理函式

宣告:
irq_handler_t 定義如下(include/linux/interrupt.h):

typedef irqreturn_t (*irq_handler_t)(int, void *);

一個簡單的中斷程式模板:

/*中斷處理函式*/
irqreturn_t xxx_interrupt
(int irq, void *dev_id) { //... do_something; //... } /*裝置驅動模組載入函式*/ int __init xxx_init(void) { //... /*申請中斷*/ result = request_irq(xxx_irq, xxx_interrupt,IRQF_SHARED, "xxx", dev_id); //... } /*裝置驅動模組解除安裝函式*/ void __exit xxx_exit(void) { //... /*釋放中斷*/ free_irq(xxx_irq, dev_id); }

3.中斷控制

函式 說明
local_irq_disable() 禁用本地(僅僅是當前處理器)中斷
local_irq_enable() 啟用本地中斷
local_irq_save() 儲存本地中斷的當前狀態,然後禁用本地中斷
local_irq_restore() 恢復本地中斷到指定狀態
disable_irq() 禁用給定中斷線,並確保函式返回前沒有在該中斷線上沒有處理程式在執行
disable_irq_nosync() 禁用給定中斷線,不等待處理程式執行完
enable_irq() 啟用給定中斷
irqs_disabled() 如果本地中斷被禁止,返回非0,否則返回0
in_interrupt() 如果在中斷上下文中,返回非0,否則返回0
in_irq() 如果當前正在執行中斷處理程式,返回非0,否則返回0

4.tasklet 與 workque

  • 4.1 中斷處理的上半部與下半部

       裝置的中斷會打斷核心中程序的正常排程和執行,系統對更高吞吐率的追求勢必要求中斷服務程式儘可能地短小精悍。但是,這個良好的願望往往與現實並不吻合。在大多數真實的系統中,當中斷到來時,要完成的工作往往並不會是短小的,它可能要進行較大量的耗時處理。
       為了在中斷執行時間儘可能短和中斷處理需完成大量工作之間找到一個平衡點,Linux 將中斷處理程式分解為兩個半部 。上半部(Top half)與下半部(Bottom half)。
       所謂的上半部是實際響應中斷的函式 – 你使用 request_irq 註冊的那個. 下半部是由上半部排程來延後執行的函式。

    在這裡插入圖片描述

    就是當一箇中斷處理程式比較耗時間時(畢竟你的幾毫秒對於CPU來說都是幾個世紀),使用上半部與下半部機制對其進行優化,上半部只處理必要的硬體互動,耗時的工作放到下半部去處理。這樣來提高效能。

    底半部的實現方式,現在主要有兩種,分別是tasklet 與 workqueue.

  • 4.2 tasklet 的使用
    • 建立一個tasklet
    DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, data)
    //或者
    tasklet_init(&xxx_tasklet,xxx_do_tasklet,data)
    
    • 編寫tasklet處理程式
    void xxx_do_tasklet(unsigned long)
    {
    	//...
    }
    
    • 排程tasklet
    tasklet_schedule(&xxx_tasklet)
    
    • 一個使用tasklet的中斷處理程式模板
    /*定義 tasklet 和底半部函式並關聯*/
    void xxx_do_tasklet(unsigned long);
    DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
    
    
    /*中斷處理上半部*/
    void xxx_do_tasklet(unsigned long data)
    {
    	//...
    }
    
    /*中斷處理下半部*/
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
    	//...
     	tasklet_schedule(&xxx_tasklet);
     	//...
    }
    
    /*裝置驅動模組載入函式*/
    int __init xxx_init(void)
    {
    	//...
    	/*申請中斷*/
    	result = request_irq(xxx_irq, xxx_interrupt,IRQF_SHARED, "xxx", dev_id);
    	//...
    }
    
    /*裝置驅動模組解除安裝函式*/
    void __exit xxx_exit(void)
    {
    	//...
    	/*釋放中斷*/
    	free_irq(xxx_irq, dev_id);
    }
    
  • 4.3 workqueue的使用
    • 建立一個work
     DECLARE_WORK(xxx_wq, xxx_do_work)
     //或者
     INIT_WORK(&xxx_wq,xxx_do_work)
    
    • workque 處理函式
    void xxx_do_work(unsigned long data)
    {
    	//...
    }
    
    • work 排程
     schedule_work(&xxx_wq)
    
    • 一個使用workqueue 的中斷處理程式模板
    tasklet_schedule(&xxx_tasklet)
    
    • 一個使用tasklet的中斷處理程式模板
    /*定義 work 和底半部函式並關聯*/
    void xxx_do_work(unsigned long);
    DECLARE_WORK(xxx_wq, xxx_do_work);
    
    
    /*中斷處理上半部*/
    void xxx_do_work(unsigned long data)
    {
    	//...
    }
    
    /*中斷處理下半部*/
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
    	//...
     	schedule_work(&xxx_wq);
     	//...
    }
    
    /*裝置驅動模組載入函式*/
    int __init xxx_init(void)
    {
    	//...
    	/*申請中斷*/
    	result = request_irq(xxx_irq, xxx_interrupt,IRQF_SHARED, "xxx", dev_id);
    	//...
    }
    
    /*裝置驅動模組解除安裝函式*/
    void __exit xxx_exit(void)
    {
    	//...
    	/*釋放中斷*/
    	free_irq(xxx_irq, dev_id);
    }
    

    程式碼上看其實大同小異,不過實際上有很大tasklet 與workqueue有很大差別

  • 5.4 tasklet 與 workqueue 的區別
    • tasklet 基於軟中斷實現,workqueue 基於核心執行緒實現
    • tasklet 不可以睡眠,workqueue 可以睡眠
    • tasklet 運行於中斷上下文,worqueue 運行於程序上下文
      如何選擇
      1.如果有休眠的需要,worqueue 是唯一的選擇 ,如果不需要休眠,那麼用 tasklet

最後:不論是裸機程式設計還是Linux程式設計,中斷的處理函式都要求儘可能快的退出,不佔用太長的CPU時間,以保證效能。Linux中斷的上半部底半部思想其實也可以應用的到微控制器程式設計裡面。

參考書籍:
《Linux 核心設計與實現》
《Linux 裝置驅動開發詳解(宋寶華)》
《Linux 裝置驅動程式》
《深入理解Linux 核心》
以上書籍各有側重點,我覺得《Linux 核心設計與實現》最為親民。而《Linux 裝置驅動程式》則是由於翻譯的問題,讀起來十分費勁,有時需要對照英文原版進行理解。當然這幾本書結合起來看最好。我現在也還沒把這些書讀完,嗯,繼續前行!