linux中斷編程、中斷編程詳解

Linux中斷處理驅動程序編寫

中斷處理是操作系統必須具備的上要功能之一,下面我們一起來探討一下Linux中的中斷處理。

1. 什麼是中斷

中斷就是CPU正常運行期間,由於內、外部事件引起的CPU暫時停止正在運行的程序,去執行該內部事件或外部事件的引起的服務中去,服務執行完畢後再返回斷點處繼續執行的情形。這樣的中斷機制極大的提高了CPU運行效率。

1.1. 中斷的分類:

1) 根據中斷的來源可分為內部中斷和外部中斷,內部中斷的中斷源來自於CPU內部(軟件中斷指令、溢出、除法錯誤等),例如操作系統從用戶態切換到內核態需要藉助CPU內部的軟件中斷,外部中斷的中斷源來自於CPU外部,由外設觸發。

2) 根據中斷是否可以被屏蔽,中斷可分為可屏蔽中斷和不可屏蔽中斷,可屏蔽中斷可以通過設置中斷控制器寄存器等方法被屏蔽,屏蔽後,該中斷不再得到響應,而不可屏蔽中斷不能被屏蔽。

3) 根據中斷入口跳轉方式的不同,中斷可分為向量中斷和非向量中斷。採用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當檢測到中斷的中斷號到來時,就自動跳轉到該中斷對應的地址處去執行程序。不同的中斷號對應不同的中斷入口地址。非向量中斷的多箇中斷共享一個入口程序處理入口地址,中斷程序跳轉到該入口地址執行時,再通過中斷程序來判斷中斷標誌來識別具體是哪一個中斷,也就是說向量中斷由硬件提供中斷服務程序入口地址,非向量中斷由軟件提供中斷服務程序入口地址。

4) 非向量中斷處理流程:

/*典型的非向量中斷首先會判斷中斷源,然後調用不同中斷源的中斷處理程序*/

irq_handler()

{

...

int int_src = read_int_status();/*讀硬件的中斷相關寄存器*/

switch(int_src)

{

//判斷中斷標誌

case DEV_A:

dev_a_handler();

break;

case DEV_B:

dev_b_handler();

break;

...

default:

break;

}

...

}

2. linux中斷頂部、底部概念

為保證系統實時性,中斷服務程序必須足夠簡短,但實際應用中某些時候發生中斷時必須處理大量的工作,這時候如果都在中斷服務程序中完成,則會嚴重降低中斷的實時性,基於這個原因,linux系統提出了一個概念:把中斷服務程序分為兩部分:頂半部、底半部。

2.1. 頂半部

完成儘可能少的比較急的功能,它往往只是簡單的讀取寄存器的中斷狀態,並清除中斷標誌後就進行“中斷標記”(也就是把底半部處理程序掛到設備的底半部執行隊列中)的工作。特點是響應速度快。

2.2. 底半部

中斷處理的大部分工作都在底半部,它幾乎做了中斷處理程序的所有事情。 特點:處理相對來說不是非常緊急的事件 ,底半部機制主要有:tasklet、工作隊列和軟中斷。

Linux中查看/proc/interrupts文件可以獲得系統中斷的統計信息:

linux中斷編程、中斷編程詳解

3. Linux中斷編程

3.1. 申請和釋放中斷

3.1.1. 申請中斷:

int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags,const char *devname,void *dev_id);

參數介紹:irq是要申請的硬件中斷號。

Handler:是向系統登記的中斷處理程序(頂半部),是一個回調函數,中斷髮生時,系統調用它,將dev_id參數傳遞給它。

irqflags:是中斷處理的屬性,可以指定中斷的觸發方式和處理方式:

觸發方式:IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW,處理方式:IRQF_DISABLE表明中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,IRQF_SHARED表示多個設備共享中斷,dev_id在中斷共享時會用到,一般設置為NULL。

返回值:為0表示成功,返回-EINVAL表示中斷號無效,返回-EBUSY表示中斷已經被佔用,且不能共享。

頂半部的handler的類型irq_handler_t定義為:

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

typedef int irqreturn_t;

3.1.2. 釋放IRQ

有請求當然就有釋放。中斷的釋放函數為:

void free_irq(unsigned int irq,void *dev_id);

參數定義與request_irq類似。

3.1.3. 中斷的使能和屏蔽

void disable_irq(int irq);//等待目前中斷處理完成(最好別在頂板部使用,你懂得)

void disable_irq_nosync(int irq);//立即返回

void enable_irq(int irq);//

3.1.4. 屏蔽本CPU內所有中斷:

#define local_irq_save(flags)...//禁止中斷並保存狀態。

void local_irq_disable(void); //禁止中斷,不保存狀態。

下面來分別介紹一下頂半部和底半部的實現機制

3.1.5. 底半部機制:

簡介:底半部機制主要有tasklet、工作隊列和軟中斷

3.1.5.1. 底半部實現方法之一tasklet

(1) 我們需要定義tasklet機器處理器並將兩者關聯,例如:

void my_tasklet_func(unsigned long);/*定義一個處理函數*/

DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);

/*上述代碼定義了名為my_tasklet的tasklet並將其與my_tasklet_func()函數綁定,傳入的參數為data*/

(2)調度

tasklet_schedule(&my_tasklet);

//使用此函數就能在是當的時候進行調度運行

(3)tasklet使用模板:

/*定義tasklet和底半部函數並關聯*/

void xxx_do_tasklet(unsigned long);

DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet,0);

/*中斷處理底半部*/

void xxx_do_tasklet(unsigned long)

{

...

}

/*中斷處理頂半部*/

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_DISABLED,"xxx",NULL);

...

return IRQ_HANDLED;

}

/*設備驅動模塊卸載函數*/

void __exit xxx_exit(void)

{

...

/*釋放中斷*/

free_irq(xxx_irq,xxx_interrupt);

...

}

3.1.5.2. 底半部實現方法之二---工作隊列

使用方法和tasklet類似,相關操作:

struct work_struct my_wq;/*定義一個工作隊列*/

void my_wq_func(unsigned long);/*定義一個處理函數*/

通過INIT_WORK()可以初始化這個工作隊列並將工作隊列與處理函數綁定INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作隊列並將其與處理函數綁定*/

schedule_work(&my_wq);/*調度工作隊列執行*/

/*工作隊列使用模板*/

/*定義工作隊列和關聯函數*/

struct work_struct(unsigned long);

void xxx_do_work(unsigned long);

/*中斷處理底半部*/

void xxx_do_work(unsigned long)

{

...

}

/*中斷處理頂半部*/

irqreturn_t xxx_interrupt(int irq,void *dev_id)

{

...

schedule_work(&my_wq);//調度底半部

...

return IRQ_HANDLED;

}

/*設備驅動模塊加載函數*/

int xxx_init(void)

{

...

/*申請中斷*/

result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);

...

/*初始化工作隊列*/

INIT_WORK(&my_wq,(void (*)(void *))xxx_do_work,NULL);

}

/*設備驅動模塊卸載函數*/

void xxx_exit(void)

{

...

/*釋放中斷*/

free_irq(xxx_irq,xxx_interrupt);

...

}

4. 中斷共享

中斷共享是指多個設備共享一根中斷線的情況,中斷共享的使用方法:

(1).在申請中斷時,使用IRQF_SHARED標識;

(2).在中斷到來時,會遍歷共享此中斷的所有中斷處理程序,直到某一個函數返回IRQ_HANDLED,在中斷處理程序頂半部中,應迅速根據硬件寄存器中的信息參照dev_id參數判斷是否為本設備的中斷,若不是立即返回IR1_NONE

/*共享中斷編程模板*/

irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)

{

...

int status = read_int_status();/*獲知中斷源*/

if(!is_myint(dev_id,status))/*判斷是否為本設備中斷*/

return IRQ_NONE;/*不是本設備中斷,立即返回*/

/*是本設備中斷,進行處理*/

...

return IRQ_HANDLED;/*返回IRQ_HANDLER表明中斷已經被處理*/

}

/*設備模塊加載函數*/

int xxx_init(void)

{

...

/*申請共享中斷*/

result = request_irq(sh_irq,xxx_interrupt,IRQF_SHARE,"xxx",xxx_dev);

...

}

/*設備驅動模塊卸載函數*/

void xxx_exit()

{

...

/*釋放中斷*/

free_irq(xxx_irq,xxx_interrupt);

...

}

5. 內核定時器

內核定時器編程:

簡介:軟件意義上的定時器最終是依賴於硬件定時器實現的,內核在時鐘中斷髮生後檢測各定時器是否到期,到期後定時器處理函數作為軟中斷在底半部執行。

Linux內核定時器操作:

5.1. timer_list結構體

每一個timer_list對應一個定時器

struct timer_list{

struct list_head entry;/*定時器列表*/

unsigned long expires;/*定時器到期時間*/

void (*function)(unsigned long);/*定時器處理函數*/

unsigned long data;/*作為參數被傳遞給定時器處理函數*/

struct timer_base_s *base;

...

};

當定時器滿的時候,定時器處理函數將被執行

5.2. 初始化定時器

void init_timer(struct timer_list * timer);

//初始化timer_list的entry的next為NULL,並給base指針賦值。

TIMER_INITIALIZER(_function,_expires,_data);//此宏用來

//賦值定時器結構體的function、expires、data和base成員

#define TIMER_INITIALIZER(function,_expires,_data)

{

.entry = {.prev = TIMER_ENTRY_STATIC},\

.function= (_function), \

.expires = (_expire), \

.data = (_data), \

.base = &boot_tvec_bases,\

}

DEFINE_TIMER(_name,_function,_expires,_data)//定義一個定時器結構體變量//併為此變量取名_name

//還有一個setup_timer()函數也可以用於定時器結構體的初始化。

5.3. 增加定時器

void add_timer(struct timer_list * timer);//註冊內核定時器,也就是將定時器加入到內核動態定時器鏈表當中。

5.4. 刪除定時器

del_timer(struct timer_list *timer);

del_timer_sync()//在刪除一個定時器時等待刪除操作被處理完(不能用於中斷上下文中)

5.5. 修改定時器expires

int mod_timer(struct timer_list * timer,unsigned long expires);//修改定時器的到期時間

/*內核定時器使用模板*/

/*xxx設備結構體*/

struct xxx_dev

{

struct cdev cdev;

...

timer_list xxx_timer;/*設備要使用的定時器*/

};

/*xxx驅動中的某函數*/

xxx_funcl(...)

{

struct xxx_dev *dev = filp->private_data;

...

/*初始化定時器*/

init_timer(&dev->xxx_timer);

dev->xxx_timer.function = &xxx_do_timer;

dev->xxx_timer.data = (unsigned long)dev;

/*設備結構體指針作為定時器處理函數參數*/

dev->xxx_timer.expires = jiffes + delays;

/*添加(註冊)定時器*/

add_timer(&dev->xxx_timer);

...

}

/*xxx驅動中的某函數*/

xxx_func2(...)

{

...

/*刪除定時器*/

del_timer(&dev->xxx_timer);

...

}

/*定時器處理函數*/

static void xxx_do_timer(unsigned long arg)

{

struct xxx_device *dev = (struct xxx_device *)(arg);

...

/*調度定時器再執行*/

dev->xxx_timer.expires = jiffes + delay;

add_timer(&dev -> xxx_timer);

...

}

//定時器到期時間往往是在jiffies的基礎上添加一個時延,若為HZ則表示延遲一秒

5.6. 內核中的延遲工作:

簡介:對於這種週期性的工作,Linux提供了一套封裝好的快捷機制,本質上利用工作隊列和定時器實現,這其中用到兩個結構體:

(1)struct delayed_work

{

struct work_struct work;

struct timer_list timer;

};

(2) struct work_struct

{

atomic_long_t data;

...

}

相關操作:

int schedule_delay_work(struct delayed_work *work,unsigned long delay);//當指定的delay到來時delay_work中的work成員的work_func_t類型成員func()會被執行work_func_t類型定義如下:

typedef void (*work_func_t)(struct work_struct *work);//delay參數的單位是jiffes

mescs_to_jiffies(unsigned long mesc);//將毫秒轉化成jiffes單位

int cancel_delayed_work(struct delayed_work *work);

int cancel_delayed_work_sync(struct delayed_work *work);//等待直到刪除(不能用於中斷上下文)

內核延遲的相關函數:

短延遲:

Linux內核提供瞭如下三個函數分別進行納秒、微妙和毫秒延遲:

void ndelay(unsigned long nsecs);

void udelay(unsigned long usecs);

void mdelay(unsigned long msecs);

機制:根據CPU頻率進行一定次數的循環(忙等待)

注意:在Linux內核中最好不要使用毫秒級的延時,因為這樣會無謂消耗CPU的資源。

對於毫秒以上的延時,Linux提供如下函數:

void msleep(unsigned int millisecs);

unsigned long msleep_interruptible(unsigned int millisecs);//可以被打斷

void ssleep(unsigned int seconds);

//上述函數使得調用它的進程睡眠指定的時間

長延遲:

機制:設置當前jiffies加上時間間隔的jiffies,直到未來的jiffies達到目標jiffires

/*實例:先延遲100個jiffies再延遲2s*/

unsigned long delay = jiffies + 100;

while(time_before(jiffies,delay));

/*再延遲2s*/

unsigned long delay = jiffies + 2*Hz;

while(time_before(jiffies,delay));//循環直到到達指定的時間與timer_before()相對應的還有一個time_after

睡著延遲:

睡著延遲是比忙等待更好的一種方法

機制:在等待的時間到來之前進程處於睡眠狀態,CPU資源被其他進程使用,實現函數有:

schedule_timeout()

schedule_timeout_uninterruptible()

其實在短延遲中的msleep() msleep_interruptible()

本質上都是依賴於此函數實現的,下面兩個函數可以讓當前進程加入到等待隊列中,從而在等待隊列上睡眠,當超時發生時,進程被喚醒

sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);

interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);


分享到:


相關文章: