中断的两种类型:
1、同步中断。比如运行时发生了除0错误,内核会产生一个中断,如采用信号机制(软中断)通知应用程序出现了异常,这样应用程序才有机会输出一些错误信息。又比如内核发生一个缺页中断(通常进程在运行时只会把当前需要的内容加载到物理内存,如果访问的虚拟内存没有实际的物理页帧相关联,就发生一个缺页中断,从磁盘中拷贝数据到物理内存)。
2、异步中断。由外部设备产生,不与任何进程相关联,可以发生在任何时间点。
中断上下部机制:一般中断上半部只是对中断进行登记,将工作推迟到下半部中去执行,中断上半部要求时间尽量快,不可引起阻塞和休眠,也不可以嵌套中断。在中断上半部发起对下半部的调度,下半部会在合适的时机执行(与内核的调度有关),下半部机制有软中断、tasklet和工作队列。为什么要分上下部?因为只在中断的上半部禁止其他中断,不那么重要的任务就推到下半部去执行,如果上下半部都禁止中断的话,那么就会影响系统的中断响应速度。
中断过程描述
一个中断到来后,会从用户态切换到内核态,并且从用户态栈切换到内核态栈(中断处理程序在内核态栈中)。在执行中断处理程序之前必须保存用户应用程序当前的寄存器状态,以便在中断结束后恢复。紧接着执行中断处理程序,中断返回时,是一个调度时机,调度器会判断是否选择一个新进程代替旧的进程。从中断返回之后,内核还原寄存器的状态、切换到用户态栈、切换到适用于用户应用程序的处理器状态。
下面就通过按键中断,编写两个实验程序,说明如何申请中断,中断上半部和中断下半部的关系。
一、按键中断与tasklet下半部机制
这个实验我们通过按键中断进入中断上半部,在上半部里调度tasklet,下半部tasklet的任务是打印一行printk(“my_tasklet_func,data is %ld\n”,data),将初始化时传递的参数data打印出来,注意下半部tasklet运行在中断上下文中,不能休眠和阻塞。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IRQNAME "key_irq"
#define GPIONUM 66 //按键的GPIO号,需要根据实际情况修改
struct module_dev{
struct tasklet_struct my_tasklet; //定义一个tasklet
};
struct module_dev mydevice;
//下半部tasklet执行的函数,打印初始化时传递的参数data
void my_tasklet_func(unsigned long data)
{
printk("my_tasklet_func,data is %ld\n",data);
}
//上半部服务函数,发起tasklet的调度。
static irqreturn_t irqhanler(int irq, void *dev_id)
{
printk("key irq catched !!\n");
tasklet_schedule(&mydevice.my_tasklet);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int __init moudule_test_init(void)
{
int ret;
int irqnum;
unsigned long data=1;
printk("moudule init!\n");
//tasklet初始化,指定tasklet执行的函数,以及传递给函数的参数为data
tasklet_init(&mydevice.my_tasklet, my_tasklet_func, data);
//按键的gpio资源申请,设置为输入
gpio_request(GPIONUM,"key");
gpio_direction_input(GPIONUM);
//按键的gpio转换为中断号
irqnum=gpio_to_irq(GPIONUM);
//中断申请,与中断服务函数关联,设置触发模式上升沿触发,传递设备结构体参数mydevice
ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
if(ret<0)
{
printk("irq request failed!\n");
}
return 0;
}
static void __exit moudule_test_exit(void)
{
printk("moudule exit!\n");
gpio_free(GPIONUM);
free_irq(gpio_to_irq(GPIONUM), &mydevice);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
二、按键中断和工作队列
本实验通过按键中断进入中断上半部,在中断上半部里调度延时工作队列,添加到工作队列的工作只是简单的打印printk(“my delay work func here!\n”)而已,由于工作队列运行休眠和阻塞,我们的延时工作队列在上半部调度queue_delayed_work的时候,指定了一个延时时间,所以可以观察到按键按下1s后才打印printk(“my delay work func here!\n”)。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IRQNAME "key_irq"
#define GPIONUM 66
struct module_dev{
struct workqueue_struct *task; //定义一个工作队列
struct delayed_work my_work; //定义一个延时工作
};
struct module_dev mydevice;
//下半部delay_work执行的函数
void my_work_func_t(struct work_struct *work)
{
printk("my delay work func here!\n");
}
//上半部服务函数,发起delay_work的调度,delay_work延时一秒执行
static irqreturn_t irqhanler(int irq, void *dev_id)
{
printk("key irq catched !!\n");
queue_delayed_work(mydevice.task, &mydevice.my_work, msecs_to_jiffies(1000));
return IRQ_RETVAL(IRQ_HANDLED);
}
static int __init moudule_test_init(void)
{
int ret;
int irqnum;
printk("moudule init\n");
//创建工作队列task,该函数会为cpu创建内核线程
mydevice.task = create_singlethread_workqueue("test");
if(IS_ERR(mydevice.task))
{
printk("Failed to create workqueue\n");
mydevice.task = NULL;
}
//delay_work初始化,指定delay_work要执行的函数
INIT_DELAYED_WORK(&mydevice.my_work, my_work_func_t);
gpio_request(GPIONUM,"key");
gpio_direction_input(GPIONUM);
irqnum=gpio_to_irq(GPIONUM);
//中断申请,与中断服务函数关联,设置触发模式上升沿触发
ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
if(ret<0)
{
printk("irq request failed!\n");
}
return 0;
}
static void __exit moudule_test_exit(void)
{
printk("moudule exit\n");
gpio_free(GPIONUM);
free_irq(gpio_to_irq(GPIONUM), &mydevice);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");