ISR的上下半段
中斷處理程序最麻煩的地方就是在中斷期間不能進行需要長時間的工作,因為中斷所造成的停頓時間必須縮到最短。但是處理中斷的工作有時相關可觀,沒辦法一下子處理完畢,因此解決的方法便是便是將ISR的處理分成前半段(Top Half)與後半段(Bottom Half)。前半段是指我們註冊的函式,只處理發生中斷時必須立刻處理的例行工作,後半段則是利用前半段交給排程器的一個函式,交由排程器另外找時間才開始執行。一般我們稱前半為前導函式,後半段為後續函式。 在後半段處理的過程,經常會使用tasklet以及workqueue的方式讓排程器處理,其詳細的介紹已在時序的章節有說明這邊以範例來說明使用時機
Tasklet
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
MODULE_LICENSE("Dual BSD/GPL");
const int IRQ_NUM = 17;
void *irq_dev_id = (void *)&IRQ_NUM;
struct tasklet_struct tasklet;
/*中斷發生後要排入工作排程的函式(bottom half)*/
void sample_tasklet(unsigned long data) {
printk("%s called (%ld, %ld, %ld)\n", __func__, in_irq(), in_softirq(), in_interrupt());
}
/*註冊在ISR的函式(top half),此函式者在確認是否觸發中斷條件*/
irqreturn_t sample_isr(int irq, void *dev_instance) {
if (printk_ratelimit()) {
printk("%s: irq %d (%ld, %ld, %ld)\n", __func__, irq, in_irq(), in_softirq(), in_interrupt());
/*如果中斷觸發條件成立的話,利用task_schedule()將工作排程*/
task_schedule(&tasklet);
return IRQ_HANDLED;
}
/*條件不成立返回IRQ_NONE,甚麼都不做*/
return IRQ_NONE;
}
static int sample_init(void) {
int ret = 0;
printk(KERN_ALERT "sample driver installed.\n");
/*將tasklet變數與函式做連結*/
tasklet_init(&tasklet, sample_tasklet, 0);
/*註冊ISR*/
ret = request_irq(IRQ_NUM, sample_isr, IRQF_SHARED, "sample", irq_dev_id);
if (ret) {
printk("request_irq() failed (%d)\n", ret);
return (ret);
}
return 0;
}
static void sample_exit(void) {
printk(KERN_ALERT "sample driver removed\n");
tasklet_kill(&tasklet);
free_irq(IRQ_NUM, irq_dev_id);
}
module_init(sample_init);
module_exit(sample_exit);
Workqueue
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
MODULE_LICENSE("Dual BSD/GPL");
const int IRQ_NUM = 17;
void *irq_dev_id = (void *)&IRQ_NUM;
struct work_struct workq;
/*中斷發生後執行的函式(bottom half)*/
void sample_workqueue(struct work_struct *work) {
printk("%s called (%ld, %ld, %ld) pid %d\n", __func__, in_irq(), in_softirq(), in_interrupt(), current->pid);
/*workqueue允許休眠*/
msleep(3000);
}
/*中斷發生時所呼叫的函式(top half)*/
irqreturn_t sample_isr(int irq, void *dev_instance) {
if (printk_ratelimit()) {
printk("%s: irq %d (%ld, %ld, %ld)\n", __func__, irq, in_irq(), in_softirq(), in_interrupt());
/*中斷成立將工作排入工作排程*/
schedule_work(&workq);
return IRQ_HANDLED;
}
/*中斷不成立返回,甚麼都不做*/
return IRQ_NONE;
}
static int sample_init(void) {
int ret = 0;
printk(KERN_ALERT "sample driver installed.\n");
/*將workqueue變數與函式作結合*/
INIT_WORK(&workq, sample_workqueue);
ret = request_irq(IRQ_NUM, sample_isr, IRQF_SHARED, "sample", irq_dev_id);
if (ret) {
printk("request_irq() failed (%d)\n", ret);
return (ret);
}
return 0;
}
static void sample_exit(void) {
flush_scheduled_work();
free_irq(IRQ_NUM, irq_dev_id);
printk(KERN_ALERT "sample driver removed\n");
}
module_init(sample_init);
module_exit(sample_exit);