轉自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609
在linux里,中斷處理分為頂半(top half),底半(bottom half),在頂半里處理優先級比較高的事情,要求占用中斷時間盡量的短,在處理完成后,就激活底半,有底半處理其余任務。底半的處理方式主要有soft_irq, tasklet, workqueue三種,他們在使用方式和適用情況上各有不同。soft_irq用在對底半執行時間要求比較緊急或者非常重要的場合,主要為一些subsystem用,一般driver基本上用不上。 tasklet和work queue在普通的driver里用的相對較多,主要區別是tasklet是在中斷上下文執行,而work queue是在process上下文,因此可以執行可能sleep的操作。
request_threaded_irq()是Linux kernel 2.6.30 之后新加的irq handler API
如何確定可以用到 request_threaded_irq() ?
Linux kernel config 需要定義CONFIG_GENERIC_HARDIQS
kernel config 才有支援threaded irq
Moving interrupts to threads 介紹request_threaded_irq() 的由來
http://lwn.net/Articles/302043/
從realtime tree 移植而來,為了減少kernel 因為要等待每一個硬件中斷處理的時間
,就另外交給kernel thread 處理中斷后續工作。
優點:
1 減少 kernel 延遲時間
2 避免處理中斷時要分辨是在硬體中斷或軟體中斷?
3 更容易為kernel 中斷處理除錯,可能可完全取代tasklet
原本的中斷處理分上半部(硬體中斷處理,必須關閉中斷無法處理新的中斷)跟下半部(
軟體中斷處理),因此上半部的硬體中斷處理必須盡可能簡短,讓系統反應速度更快。
request_threaded_irq 是在將上半部的硬件中斷處理縮短為只確定硬體中斷來
自我們要處理的裝置,喚醒kernel thread 執行后續中斷任務。
缺點:
對於非irq 中斷的kernel threads ,需要在原本task_struct 新增struct
irqaction 多占 4/8 bytes 記憶體空間
linux kernel 2.6.29 之后(2.6.30)加入request_threaded_irq
跟傳統top/bottom havles 的差異是threaded_irq 受Linux kernel system
的 process scheduling 控制,不會因為寫錯的bottom half 代碼造成整個系統
延遲的問題。
也可以透過RT/non RT 跟nice 等工具調整各個thread 優先權,丟給使用率較低的
cpu 以及受惠於kernel 原本可以對threads 做的各種控制,包括但不限於sleep,
lock, allocate 新的記憶體區塊。
受惠最大的是shared irq line 的多個中斷處理。除了可以加速共享中斷造成的延遲
,threaded_irq 也可以降低在同一段程式碼處理多個裝置中斷的復雜度。
threaded irq 在使用性上也比tasklet(接着top half 直接執行,無法sleep)
/workqueue(kernel context?) 等需要在top half 增加跟bottom half 連結與溝通
的麻煩。
int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)
IRQF_SHARED 共享中斷時,dev_id不能為空,因為釋放irq時要區分哪個共享中斷
irq:中斷號
handler:發生中斷時首先要執行的硬中斷處理函數,這個函數可以通過返回 IRQ_WAKE_THREADED喚醒中斷線程,也可
返回IRQ_HANDLE不執行中斷線程
thread_fn : 中斷線程,類似於中斷下半部
后三個參數與request_irq中的一致
關於IRQF_ONESHOT, 直到線程函數執行完畢才會開啟該中斷
IRQF_ONESHOT:Interrupt is not reenabled after the hardirq handler finished. Used by threaded interrupts which need to keep the irq line disabled until the threaded handler has been run. 這里linus在郵件列表里指明IRQF_ONESHOT 的原因 Making the IRQF_ONESHOT explicit does two things: - it makes people who read the code *aware* of things - if/when you have irq conflicts and two drivers want to attach to the same interrupt, at least you can see directly from the source what flags they used (and again, not have to even *think* about it). IRQF_ONESHOT 與 IRQF_SHARED 不能同時使用 當多個設備共享中斷時,由於IRQF_ONESHOT會關閉中斷線程的中斷,而線程一般執行時間會比較長,所以是不允許的 當hardirq函數為NULL時,必須聲明IRQF_ONESHOT, 表示threadirq線程中關閉該中斷,在某些情況下,這個標志會非常有用 例如:設備是低電平產生中斷,而硬中斷函數為NULL,如果不使用IRQF_ONESHOT,就會一直產生中斷執行NULL函數,中斷線程 得不到執行,聲明IRQF_ONESHOT后,會執行完線程才使能該中斷
點擊(此處)折疊或打開
- /*
- * gpio_irqTest.c
- * PB27 receive this signal as IRQ and make the LED linking on PB17 turn on or turn off
- *
- */
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/platform_device.h>
- #include <linux/cdev.h>
- #include <linux/ioctl.h>
- #include <linux/fs.h>
- #include <linux/gpio.h>
- #include <linux/delay.h>
- #include <linux/cdev.h>
- #include <linux/interrupt.h>
- #include <asm/io.h>
- #include <asm/io.h>
- #include <mach/gpio.h>
- #include <mach/hardware.h>
- #include <mach/board.h>
- #include <mach/gpio.h>
- #include <mach/at91_pio.h>
- #include <mach/at91_aic.h>
- #include <mach/at91_pmc.h>
- void led_on()
- {
- // at91_set_gpio_output(AT91_PIN_PB17,1);
- printk("led on\n");
- }
- void led_off()
- {
- // at91_set_gpio_output(AT91_PIN_PB17 ,0);
- printk("led off.\n");
- }
- struct light_dev *light_devp;
- int light_major = 200;
- struct light_dev
- {
- struct cdev cdev;
- unsigned char value;
- };
- static void io_init(void)
- {
- at91_set_GPIO_periph(AT91_PIN_PB27, 0);
- at91_set_gpio_input(AT91_PIN_PB27, 1);
- at91_set_deglitch(AT91_PIN_PB27, 1);
- }
- struct gpio_irq_desc
- {
- int pin;
- int irq;
- unsigned long flags;
- char *name;
- };
- static struct gpio_irq_desc gpio_irq={AT91_PIN_PB27, AT91_PIN_PB27,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING|IRQF_ONESHOT,"PB27"};
- static irqreturn_t gpio_irqhandler(int irq, void *dev_id)
- {
- printk(KERN_INFO "In hard irq handler.\n");
- return IRQ_WAKE_THREAD;
- }
- static irqreturn_t gpio_threadhandler(int irq, void *dev_id)
- {
- int rst;
- rst = at91_get_gpio_value(gpio_irq.pin);
- printk(KERN_INFO "gpio stat: %d\n", rst);
- if(rst == 0){
- led_on();
- }else{
- led_off();
- }
- printk(KERN_INFO "sleep 3000ms\n");
- msleep(3000);
- printk(KERN_INFO "awake after sleep\n");
- return IRQ_HANDLED;
- }
- int light_open(struct inode *inode,struct file *filp)
- {
- int err;
- struct light_dev *dev;
- dev = container_of(inode->i_cdev,struct light_dev,cdev);
- filp->private_data = dev;
- printk(KERN_DEBUG "%s", __FUNCTION__);
- io_init();
- // err = request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
- err = request_threaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
- if(err)
- {
- // free_irq(gpio_irq.irq,(void*)0);
- printk(KERN_DEBUG "request irq failed.\n");
- return -EBUSY;
- }
- return 0;
- }
- int light_release(struct inode *inode,struct file *filp)
- {
- free_irq(gpio_irq.irq,(void*)0);
- return 0;
- }
- int light_ioctl(struct inode *inode,struct file *filp,unsigned int cmd, unsigned long arg)
- {
- struct light_dev *dev = filp->private_data;
- switch(cmd)
- {
- case 0:
- at91_set_gpio_output(AT91_PIN_PB19,0);
- break;
- case 1:
- at91_set_gpio_output(AT91_PIN_PB19,1);
- led_off();
- break;
- default:
- return -ENOTTY;
- // break;
- }
- return 0;
- }
- struct file_operations light_fops =
- {
- .owner = THIS_MODULE,
- .open = light_open,
- .release = light_release,
- .unlocked_ioctl = light_ioctl,
- };
- static void light_setup_cdev(struct light_dev *dev,int index)
- {
- int err,devno = MKDEV(light_major,index);
- cdev_init(&dev->cdev,&light_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &light_fops;
- err = cdev_add(&dev->cdev,devno,1);
- if(err)
- {
- printk(KERN_NOTICE "Error %d adding LED%d",err,index);
- }
- }
- int __init light_init(void)
- {
- int result;
- dev_t dev = MKDEV(light_major,0);
- if(light_major)
- {
- result = register_chrdev_region(dev,1,"gpio");
- }
- if(result < 0)
- {
- printk(KERN_DEBUG "%s: register char dev failed.\n", __FUNCTION__);
- return result;
- }
- light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL);
- if(!light_devp)
- {
- result = - ENOMEM;
- goto fail_malloc;
- }
- memset(light_devp,0,sizeof(struct light_dev));
- light_setup_cdev(light_devp,0);
- printk(KERN_DEBUG "%s done\n", __FUNCTION__);
- return 0;
- fail_malloc:unregister_chrdev_region(dev,light_devp);
- return result;
- }
- void __exit light_cleanup(void)
- {
- cdev_del(&light_devp->cdev);
- kfree(light_devp);
- unregister_chrdev_region(MKDEV(light_major,0),1);
- }
- module_init(light_init);
- module_exit(light_cleanup);
- MODULE_AUTHOR("Enzo Fang");
- MODULE_LICENSE("Dual BSD/GPL");
結論:
使用
request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
hardirq和thread_fn同時出現時,處理thread_fn時該中斷是打開的
err = request_threaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
但hardirq和thread_fn只有一個存在時,處理thread_fn時,中斷是關閉的
