From: http://blog.csdn.net/tankai19880619/article/details/13507601
一、Linux内核中断机制
1.同步中断:当一条指令执行完毕后,由CPU控制单元产生、而不是发生在代码指令执行期间的中断。也叫异常,例如系统调用。(注意:它运行在进程上下文!!!)
2.异步中断:由其他硬件设备依照CPU时钟信号随机产生、能够发生在指令执行过程中的中断。也叫硬件中断,例如键盘中断,这是狭义上的中断概念。(注意:异步中断内核都运行在中断上下文中。)
3.软中断:Linux2.6内核中软中断还是由函数do_softirq调用,调用时机如下:
硬件处理完一个中断之后(irq_exit函数):一定在中断上下文中。
内核线程ksoftirqd:一般在进程上下文中。
显式调用do_softirq的地方(比如net子系统中):一般在进程上下文中。
因此,软中断属于同步中断,但其可能运行在中断上下文、也可能运行在进程上下文。
广义上的中断应该包含上述两个部分。
二、中断上下文
在Linux系统中,异步中断发生,内核在进入中断之前都会保存当前的进程上下文、然后CPU的寄存器值变为当前发出中断信息的硬件设备的值,内核运行与中断上下文中;由于此时已经没有进程上下文,因此,需要避免使用会引起调度的函数(如msleep、信号量获取等)、因为这些函数都与具体进程相关。
三、中断底半部的实现方式
中断处理的矛盾:它需要完成大量的设备数据处理,同时、又不得不快速的退出。
解决方法:中断处理分成2部分,顶半部和底半部;顶半部是异步的,底半部是同步的。
下半部和上半部最大的不同:下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!
实现中断处理底半部的方式如下:
1.softirq和tasklet:这两种方式中断底半部可能运行与中断上下文,因此、不允许有会引起调度的函数使用(如msleep、信号量获取等);这时的中断底半部可以被其他irq打断。
2.workqueue:这种方式中断底半部运行与events/0内核线程的进程上下文,因此、允许有引起调度的函数使用(如msleep、信号量获取等);这时的中断底半部并没有在中断上下文中运行。
四、中断底半部的实现例子
1.tasklet方式
interrupt.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h> //for current全局变量
#include <asm/current.h>
//add by tankai
#include <linux/delay.h>
//end tankai
static int irq;
static char *devname;
static struct tasklet_struct mytasklet;
static struct work_struct mywork;
module_param(irq, int, 0644);
module_param(devname, charp, 0644);
struct myirq
{
int devid;
};
struct myirq mydev={1119};
static void mytasklet_handler(unsigned long data)
{
printk("current1111111->pid is %d\n", current->pid);
printk("current->pid is %d\n", current->pid);
printk("in_interrupt() is %d\n",in_interrupt()); //in_interrupt()判断是否处于中断上下文中
printk("in_irq() is %d\n",in_irq()); // 判断是否处理硬中断处理中
printk("in_softirq() is %d\n",in_softirq()); //判断是否处于软中断
printk("tasklet is working..\n");
//情况一:使用不带进程调度的睡眠函数
mdelay(10000);
/*
//情况二:可以使用带进程调度的睡眠函数
msleep(10000);
*/
}
static irqreturn_t myirq_handler(int irq, void *dev)
{
struct myirq mydev;
static int count =0;
mydev=*(struct myirq*)dev;
printk("##################\n");
//msleep(10000); //中断顶半部不能调用可能会导致睡眠的函数
printk("key:%d..\n",count+1);
//printk("devid:%d ISR is working..\n",mydev.devid);
//printk("Bottom half will be working..\n");
printk("current->pid is %d\n",current->pid);
printk("in_interrupt() is %d\n",in_interrupt());
printk("in_irq() is %d\n",in_irq());
printk("in_softirq() is %d\n",in_softirq());
//底半部实现一:tasklet(由内核线程——软中断(ksoftirqd/0)调度执行),不能睡眠
tasklet_init(&mytasklet, mytasklet_handler,0); //中断底半部,tasklet实现方式中、底半部也不能睡眠
tasklet_schedule(&mytasklet); //调度底半部
//此时,底半部在合适时机运行与软中断上下文
/*
//底半部实现二:workqueue(由内核线程——events/0调度执行),可以睡眠
INIT_WORK(&mywork, mytasklet_handler);
schedule_work(&mywork);
//此时,底半部在合适时机运行与events/0的进程上下文
*/
printk("ISR is leaving\n");
printk("##################\n");
count++;
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
printk("Module is working..\n");
if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0)
{
printk("%s request IRQ:%d failed..\n",devname,irq);
return -1;
}
printk("%s rquest IRQ:%d success..\n",devname,irq);
return 0;
}
static void __exit myirq_exit(void)
{
printk("Module is leaving");
/*
//移除底半部一:
tasklet_kill(&mytasklet);
*/
/*
//移除底半部二:
destroy_workqueue(&mywork);
*/
free_irq(irq, &mydev);
printk("%s request IRQ :%d success...\n",devname ,irq);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
Makefile:
ifeq ($(KERNELRELEASE),)
PWD :=$(shell pwd)
KERSRC := /lib/modules/$(shell uname -r)/build/
modules:
$(MAKE) -C $(KERSRC) M=$(PWD) modules
moules_install:
$(MAKE) -C $(KERSRC) M=$(PWD) modules_install
.PHONY: modules modules_install clean
clean:
-rm -rf *.o *.cmd.* *.ko
else
modules-objs :=interrupt.o
obj-m := interrupt.o
endif
查看系统当前irq中断:
cat /proc/interrupts- CPU0
- 1: 1 goldfish goldfish_pdev_bus
- 3: 8965 goldfish Goldfish Timer Tick
- 4: 0 goldfish goldfish_tty
- 10: 0 goldfish goldfish_rtc
- 11: 149 goldfish goldfish_tty
- 12: 364 goldfish eth0
- 13: 326 goldfish goldfish_fb
- 14: 1 goldfish goldfish_audio
- 15: 1241 goldfish goldfish_mmc
- 16: 0 goldfish goldfish-battery
- 17: 116 goldfish goldfish-events-keypad
- 18: 1 goldfish goldfish_switch
- 19: 0 goldfish goldfish_switch
- Err: 0
运行,与tty共享中断向量:
insmod interrupt.ko irq="11"
结果:- current1111111->pid is 0
- current->pid is 0
- in_interrupt() is 256 //在中断上下文
- in_irq() is 0
- in_softirq() is 256 //而且是软中断、同步中断
- tasklet is working..
2.workqueue方式
interrupt.c- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/interrupt.h>
- //add by tankai
- #include <linux/delay.h>
- //end tankai
- static int irq;
- static char *devname;
- static struct tasklet_struct mytasklet;
- static struct work_struct mywork;
- module_param(irq, int, 0644);
- module_param(devname, charp, 0644);
- struct myirq
- {
- int devid;
- };
- struct myirq mydev={1119};
- static void mytasklet_handler(unsigned long data)
- {
- printk("current1111111->pid is %d\n",current->pid);
- printk("current->pid is %d\n",current->pid);
- printk("in_interrupt() is %d\n",in_interrupt());
- printk("in_irq() is %d\n",in_irq());
- printk("in_softirq() is %d\n",in_softirq());
- printk("tasklet is working..\n");
- /*
- //情况一:使用不带进程调度的睡眠函数
- mdelay(10000);
- */
- //情况二:可以使用带进程调度的睡眠函数
- msleep(10000);
- }
- static irqreturn_t myirq_handler(int irq, void *dev)
- {
- struct myirq mydev;
- static int count =0;
- mydev=*(struct myirq*)dev;
- printk("##################\n");
- //msleep(10000); //中断顶半部不能调用可能会导致睡眠的函数
- printk("key:%d..\n",count+1);
- //printk("devid:%d ISR is working..\n",mydev.devid);
- //printk("Bottom half will be working..\n");
- printk("current->pid is %d\n",current->pid);
- printk("in_interrupt() is %d\n",in_interrupt());
- printk("in_irq() is %d\n",in_irq());
- printk("in_softirq() is %d\n",in_softirq());
- /*
- //底半部实现一:tasklet(由内核线程——软中断(ksoftirqd/0)调度执行),不能睡眠
- tasklet_init(&mytasklet, mytasklet_handler,0); //中断底半部,tasklet实现方式中、底半部也不能睡眠
- tasklet_schedule(&mytasklet); //调度底半部
- //此时,底半部在合适时机运行与软中断上下文
- */
- //底半部实现二:workqueue(由内核线程——events/0调度执行),可以睡眠
- INIT_WORK(&mywork, mytasklet_handler);
- schedule_work(&mywork);
- //此时,底半部在合适时机运行与events/0的进程上下文
- printk("ISR is leaving\n");
- printk("##################\n");
- count++;
- return IRQ_HANDLED;
- }
- static int __init myirq_init(void)
- {
- printk("Module is working..\n");
- if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0)
- {
- printk("%s request IRQ:%d failed..\n",devname,irq);
- return -1;
- }
- printk("%s rquest IRQ:%d success..\n",devname,irq);
- return 0;
- }
- static void __exit myirq_exit(void)
- {
- printk("Module is leaving");
- /*
- //移除底半部一:
- tasklet_kill(&mytasklet);
- */
- /*
- //移除底半部二:
- destroy_workqueue(&mywork);
- */
- free_irq(irq, &mydev);
- printk("%s request IRQ :%d success...\n",devname ,irq);
- }
- module_init(myirq_init);
- module_exit(myirq_exit);
- MODULE_LICENSE("GPL");
Makefile等同上,
运行,与tty共享中断向量:
insmod interrupt.ko irq="11"
结果:
- current1111111->pid is 4
- current->pid is 4
- in_interrupt() is 0 //不在中断上下文
- in_irq() is 0
- in_softirq() is 0
- tasklet is working..