2013年9月10日 星期二

Linux中斷處理

由於IO設備的速度遠慢於CPU,如果CPU要等待IO動作完成並處理的話,效率是非常差的。透過interrupt機制可解決這問題,當IO動作完成後會發出一個asynchronous interrupt,CPU偵測後會暫停手邊的工作,然後喚醒ISR(interrupt service routine)執行,並處理I/O的請求。
  • interrupt的精神:
    1. interrupt handlers不能進入sleep,所以要避免呼叫叫到一些functions,內部潛在包含了進入sleep的function calls。
    2. 當interrupt handlers部分code要進入critical section,請用spinlocks上鎖,而不是mutexs。
    3. interrupt handlers不能與user space進行資料交換。
    4. interrupt handlers必須要儘速執行完畢。為了確保這一點,最好將執行工作切成兩部分,分別是top half與bottom half。handler的top half部分會儘速完成工作,然後可以晚點執行的工作放在bottom half,可以透softirqs與tasklets達到此目的。
    5. interrupt handlers不能重複被呼叫。當一個handler已經在執行,它的對應IRQ必須要被disable,直到handler執行完畢。
    6. interrupt handlers能被更高權限的handlers中斷。如果要避免被高權限的handlers中斷,你可以將interrupt handler標示為fast handler。但如果太多被標示為fast handlers被導致系統performance下降,因為interrupt latency時間會變長。
  • synchronous interrupt(軟體產生):
    1. 當runtime錯誤發生,會發出此interrupt。
    2. 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.hreturns true when in interrupt handler
in_softirq()include/linux/hardirq.hreturns true when in bottom half (softirq,tasklet)
in_interrupt()include/linux/hardirq.hreturns 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影片:

沒有留言:

張貼留言