linux内核中断

中断的两种类型
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");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值