- interrupt的精神:
- interrupt handlers不能進入sleep,所以要避免呼叫叫到一些functions,內部潛在包含了進入sleep的function calls。
- 當interrupt handlers部分code要進入critical section,請用spinlocks上鎖,而不是mutexs。
- interrupt handlers不能與user space進行資料交換。
- interrupt handlers必須要儘速執行完畢。為了確保這一點,最好將執行工作切成兩部分,分別是top half與bottom half。handler的top half部分會儘速完成工作,然後可以晚點執行的工作放在bottom half,可以透softirqs與tasklets達到此目的。
- interrupt handlers不能重複被呼叫。當一個handler已經在執行,它的對應IRQ必須要被disable,直到handler執行完畢。
- interrupt handlers能被更高權限的handlers中斷。如果要避免被高權限的handlers中斷,你可以將interrupt handler標示為fast handler。但如果太多被標示為fast handlers被導致系統performance下降,因為interrupt latency時間會變長。
- synchronous interrupt(軟體產生):
- 當runtime錯誤發生,會發出此interrupt。
- software interrupts,例如system calls。
當我們為可中斷devices撰寫interrupt handlers,首先我們需要為每個device配置一個IRQ(interrupt request number),然後透過request_irq()將一個IRQ與一個interrupt handler建立關連,如果IRQ不再使用,透過free_irq()將它釋放掉。下面linux指令可印出系統上active IRQs:
$ cat /proc/interrupts底下範例是在mini2440開發版實現按鍵控制LED跑馬燈範例,此範例會透過tasklets將interrupt handler切成top half與bottom half,以確保top half部分會儘速完成工作,更符合interrupt精神:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <linux/gpio.h> #include <linux/random.h> #define LED_LEFT_TO_RIGHT 1 #define LED_RIGHT_TO_LEFT 2 #define LED_DOUBLE 3 #define LED_RANDOM 4 #define LED_FLASH 5 #define LED_TURNOFF 6 struct button_irq_desc { int irq; int pin; int pin_setting; int number; char *name; }; static struct button_irq_desc button_irqs [] = { {IRQ_EINT8 , S3C2410_GPG(0) , S3C2410_GPG0_EINT8 , 0, "KEY0"}, {IRQ_EINT11, S3C2410_GPG(3) , S3C2410_GPG3_EINT11 , 1, "KEY1"}, {IRQ_EINT13, S3C2410_GPG(5) , S3C2410_GPG5_EINT13 , 2, "KEY2"}, {IRQ_EINT14, S3C2410_GPG(6) , S3C2410_GPG6_EINT14 , 3, "KEY3"}, {IRQ_EINT15, S3C2410_GPG(7) , S3C2410_GPG7_EINT15 , 4, "KEY4"}, {IRQ_EINT19, S3C2410_GPG(11), S3C2410_GPG11_EINT19, 5, "KEY5"}, }; struct led_desc { int pin; int pin_setting; }; static struct led_desc leds[] = { {S3C2410_GPB(5), S3C2410_GPIO_OUTPUT}, {S3C2410_GPB(6), S3C2410_GPIO_OUTPUT}, {S3C2410_GPB(7), S3C2410_GPIO_OUTPUT}, {S3C2410_GPB(8), S3C2410_GPIO_OUTPUT}, }; struct timer_list timer; unsigned long ticks; unsigned int led = 0; int current_irq = -1, current_mode = LED_TURNOFF; struct tasklet_struct *tsklt; /* timer時間到所啟動的function */ void leds_timer_func(unsigned long data) { int i; for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) { s3c2410_gpio_setpin(leds[i].pin, 1); } switch(current_mode) { case LED_LEFT_TO_RIGHT: led++; led %= 4; s3c2410_gpio_setpin(leds[led].pin, 0); break; case LED_RIGHT_TO_LEFT: led--; led %= 4; s3c2410_gpio_setpin(leds[led].pin, 0); break; case LED_DOUBLE: led++; led %= 4; s3c2410_gpio_setpin(leds[led].pin, 0); s3c2410_gpio_setpin(leds[(led+1)%4].pin, 0); break; case LED_RANDOM: get_random_bytes(&led, sizeof(led)); led %= 4; s3c2410_gpio_setpin(leds[led].pin, 0); break; case LED_FLASH: led++; for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) { s3c2410_gpio_setpin(leds[i].pin, led%2); } break; case LED_TURNOFF: for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) { s3c2410_gpio_setpin(leds[i].pin, 1); } break; } mod_timer(&timer, jiffies + 30); } /* 改變LEDs燈閃爍模式 */ void leds_running_mode(unsigned long data) { if(current_irq > 0) { switch(current_irq) { case IRQ_EINT8: current_mode = LED_DOUBLE; printk("LEDs controller is in LED_DOUBLE mode\n"); break; case IRQ_EINT11: current_mode = LED_RIGHT_TO_LEFT; printk("LEDs controller is in LED_RIGHT_TO_LEFT mode\n"); break; case IRQ_EINT13: current_mode = LED_RANDOM; printk("LEDs controller is in LED_RANDOM mode\n"); break; case IRQ_EINT14: current_mode = LED_FLASH; printk("LEDs controller is in LED_FLASH mode\n"); break; case IRQ_EINT15: current_mode = LED_TURNOFF; printk("Turn off LEDs\n"); break; case IRQ_EINT19: current_mode = LED_LEFT_TO_RIGHT; printk("LEDs controller is in LED_LEFT_TO_RIGHT mode\n"); break; } } } /* interrupt handler */ static irqreturn_t buttons_interrupt(int irq, void *dev_id) { printk("IRQ number is %d\n", irq); current_irq = irq; tasklet_schedule(tsklt); return IRQ_HANDLED; } static int __init LEDs_controller_module_init(void) { int i; int err = 0; /* 初始化LEDs燈 */ for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) { s3c2410_gpio_cfgpin(leds[i].pin, leds[i].pin_setting); s3c2410_gpio_setpin(leds[i].pin, 1); } /*初始化interrupt的IRQs*/ for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) continue; s3c2410_gpio_cfgpin(button_irqs[i].pin, button_irqs[i].pin_setting); err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQF_DISABLED | IRQF_TRIGGER_RISING, button_irqs[i].name, NULL); if (err) break; } /* 當errors發生,釋放已註冊的IRQs */ if (err) { i--; for (; i >= 0; i--) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, NULL); } return -EBUSY; } /* 初始化tasklet */ tsklt = kmalloc(sizeof(struct tasklet_struct),GFP_KERNEL); tasklet_init(tsklt, leds_running_mode, 0); /* 初始化timer */ init_timer(&timer); timer.data = 10; timer.expires = jiffies + 30; timer.function = leds_timer_func; add_timer(&timer); ticks = jiffies; printk("LEDs controller module loaded\n"); return 0; } static void __exit LEDs_controller_module_exit(void) { int i; del_timer(&timer); /* 移除timer */ /* 將所有LEDs熄滅 */ for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) { s3c2410_gpio_setpin(leds[i].pin, 1); } /* 釋放所以IRQs */ for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, NULL); } /* 釋放tasklet */ tasklet_kill(tsklt); printk("LEDs controller module unloaded\n"); } module_init(LEDs_controller_module_init); module_exit(LEDs_controller_module_exit); MODULE_DESCRIPTION("Use buttons to control LEDs in mini2440 board"); MODULE_LICENSE("GPL"); MODULE_ALIAS("LEDs controller module"); MODULE_AUTHOR("Renee's Blog");Makefile內容:
ifneq ($(KERNELRELEASE),) obj-m := leds_controller.o else KDIR := ~/myfs/rootfs/lib/modules/2.6.32.2-FriendlyARM/build all: make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif上面的範例採用tasklet實現bottom half,經過修改並移除tasklet相關的code,讓底下範例採用softirq實現:
... ... ... /* 改變LEDs燈閃爍模式 */ void leds_running_mode(struct softirq_action *a) { ... ... ... } /* interrupt handler */ static irqreturn_t buttons_interrupt(int irq, void *dev_id) { printk("IRQ number is %d\n", irq); current_irq = irq; raise_softirq(LED_SOFTIRQ); return IRQ_HANDLED; } static int __init LEDs_controller_module_init(void) { ... ... ... /* 當errors發生,釋放已註冊的IRQs */ ... ... ... /* 初始化softirq */ open_softirq(LED_SOFTIRQ, leds_running_mode); /* 初始化timer */ ... ... ... } static void __exit LEDs_controller_module_exit(void) { ... ... ... /* 釋放所以IRQs */ for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, NULL); } printk("LEDs controller module unloaded\n"); } ... ... ...在編譯kernel會出現錯誤,還需要修改底下兩個檔案:
kernel/softirq.c
... ... ... char *softirq_to_name[NR_SOFTIRQS] = { "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL", "TASKLET", "SCHED", "HRTIMER", "RCU", "LED" }; ... ... ... void raise_softirq(unsigned int nr) { unsigned long flags; local_irq_save(flags); raise_softirq_irqoff(nr); local_irq_restore(flags); } EXPORT_SYMBOL(raise_softirq); void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action; } EXPORT_SYMBOL(open_softirq); ... ... ...linux/interrupt.h
enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ LED_SOFTIRQ, NR_SOFTIRQS };Intrrupt相關函式:
函式 | 位置 | 說明 |
---|---|---|
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id) | kernel/irq/manage.c | 註冊一個IRQ,參數如下: irq:IRQ號碼。 handler:interrupt handler,return的資料型態是irq_handler_t,如果它return值是IRQ_HANDLED,表示成功處理完成,但如果return值是IRQ_NONE,表示處理失敗。 flags:僅列出一些,如下 IRQF_DISABLED IRQF_TRIGGER_RISING IRQF_TRIGGER_FALLING IRQF_TRIGGER_HIGH IRQF_TRIGGER_LOW IRQF_SHARED name:用於識別使用此IRQ的裝置名稱,例如cat /proc/interrupts會列出IRQ號碼與裝置名稱。 dev_id:如果IRQ被很多devices共享,表示共用同一個handler,此資料結構會餵給handler,用於識別IRQ正被哪一個device使用,並作對應處理。 |
free_irq(unsigned int irq, void *dev_id) | kernel/irq/manage.c | 釋放一個由request_irq()註冊的IRQ,參數如下: irq:IRQ號碼。 dev_id:既是request_irq的最後一個參數。 |
enable_irq(unsigned int irq) | kernel/irq/manage.c | 重新enable被disable_irq或disable_irq_nosync禁止的interrupt。 |
disable_irq(unsigned int irq) | kernel/irq/manage.c | 禁止某個IRQ發出interrupt。 |
disable_irq_nosync(unsigned int irq) | kernel/irq/manage.c | 禁止某個IRQ發出interrupt,但不等是否有正在執行的interrupt handler完成與否。 |
in_irq() | include/linux/hardirq.h | returns true when in interrupt handler |
in_softirq() | include/linux/hardirq.h | returns true when in bottom half (softirq,tasklet) |
in_interrupt() | include/linux/hardirq.h | returns true when in interrupt handler or bottom half |
open_softirq(int nr, void (*action)(struct softirq_action *)) | kernel/softirq.c | 註冊一個software interrupt handler。 |
raise_softirq(unsigned int nr) | kernel/softirq.c | 觸發software interrupt handler |
tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) | ||
tasklet_schedule(struct tasklet_struct *t) | ||
tasklet_enable(struct tasklet_struct *t) | ||
tasklet_disable(struct tasklet_struct *t) | ||
tasklet_disable_nosync(struct tasklet_struct *t) | ||
tasklet_kill(struct tasklet_struct *t) |
Demo影片:
沒有留言:
張貼留言