Linux中断编程
??中断:是指CPU在运行过程中,出现了某种异常事件,需要CPU先暂停当前工作,转而去处理新产生的异常事件,处理完后再返回暂停的事件继续往下执行。就例如我们正在使用手机进行微信视频聊天,这时突然有人打电话过来,这时手机的处理方式是手机来来电铃声响起,通知用户电话来了。 ??中断,就是来处理未来时间内可能会发生的事件, 中断事件也称为异常事件。有了中断处理,则可大大提高CPU处理效率。 ??在单片机中,我们也常用中断方式来处理一些紧急事件,帮我们实现快速响应一些实时性的事件。因此我们在编写中断服务函数时都是代码尽可能简洁、一定不能处理死循环、若需要处理的事情比较多则应在中断中设定标志位,然后将逻辑代码放到主函数中去实现。 ??在Linux内核中,我们一般会将中断分为顶半部分和底半部分。顶半部分主要是处理耗时短的代码(像单片机中设置标志位),启动底半部分代码;底半部分主要是处理耗时比较长的代码,完成中断响应后的事件处理。
1. Linux下外部中断
??要使用外部中断,则需要完成中断三要素的配置:中断号(irq)、中断服务函数、中断触发方式(电平触发、边沿触发)。
1.1 相关接口函数
??在Linux内核中提供了方便函数获取引脚中断号。
int gpio_to_irq(unsigned gpio) 函数功能: 获取中断号 返回值: 成功返回对应GPIO的中断号irq
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev) 函数功能: 注册中断 形 参: irq --中断号,gpio_to_irq函数返回值。 ?? ?handler --中断服务函数。 ?? ??服务函数原型:typedef irqreturn_t (*irq_handler_t)(int, void *); ?? ?flags --中断触发方式。 ?? ??#define IRQF_TRIGGER_RISING 0x00000001 //上升沿 ?? ??#define IRQF_TRIGGER_FALLING 0x00000002 //下升沿 ???? #define IRQF_TRIGGER_HIGH 0x00000004//高电平 ????#define IRQF_TRIGGER_LOW 0x00000008//低电平
????#define IRQF_SHARED 0x00000080 //共享中断 ???name --中断注册标志。 ???dev --传给中断服务函数的参数。 返回值: 成功返回0,失败返回其它值。
typedef irqreturn_t (*irq_handler_t)(int, void *); 函数功能: 中断服务函数 形 参: 第一个参数为中断号;第二个参数为注册函数传入的参数dev 返回值: ?? enum irqreturn { ????IRQ_NONE = (0 << 0), //如果不是本中断的则返回这个值,只在共享中断中使用 ????IRQ_HANDLED = (1 << 0), //正确执行中断程序返回这个值,常用 ????IRQ_WAKE_THREAD = (1 << 1), //表示去唤醒中断处理者的线程 ??};
??注意: irqreturn_t (*irq_handler_t)(int, void *);函数中不能出现带休眠的函数,如msleep函数;该函数必须要返回值。
free_irq(unsigned int irq, void *dev_id) 函数功能: 注销中断 形 参: irq --中断号,gpio_to_irq函数返回值。 ???dev --传给中断服务函数的参数。需和注册时保持一致
2. 工作队列
??中断处理函数分为中断顶半部分和中断底半部分。顶半部分代码实现即为中断服务函数,而底半部分代码则可由工作队列完成。
2.1 工作队列简介
??在操作系统中,如果我们需要进行一项工作处理,往往需要创建一个任务来加入内核的调度队列。一个任务对应一个处理函数,如果要进行不同的事务处理,则需要创建多个不同的任务。任务作为CPU调度的基本单元,任务数量越大,则调度成本越高。工作队列(workqueue)机制简化了基础的任务创建和处理机制,一个workqueue对应一个实体task任务处理,工作队列中可以挂载多个工作实体,每一个工作都能对应不同的工作处理函数。即用户只需要创建一个workqueue,则可以完成多个挂接不同处理函数的工作队列。 ?? 工作队列还具有将工作推后执行机制,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。 ??workqueue的处理依赖于task任务。一个workqueue队列会创建关联其对应的task任务,一个workqueue会挂载多个工作进行处理,每个工作都有工作处理函数。当workqueue得到调度,即其关联的task得到运行,在每次task的调度期间,都会从工作队列中按照先后顺序取出一个work来进行处理。workqueue模块在初始化时,会创建一个系统默认的工作队列,用户可根据需要将work添加到该队列中去执行。
2.2 工作相关函数接口
#include <linux/workqueue.h>
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
??在工作结构体体中,我们需要关心的成员是工作处理函数:work_func_t func,简单来说即一个工作会对应有一个处理函数。工作处理函数原型如下:
#include <linux/workqueue.h>
typedef void (*work_func_t)(struct work_struct *work);
#define INIT_WORK(_work, _func) 函数功能: 初始化工作,以宏的方式实现 形 参: _work --工作结构体体指针 ???_func --工作处理函数
int schedule_work(struct work_struct *work)
2.3工作队列使用步骤
- 定义工作结构体struct work_struct,初始化工作INIT_WORK;
- 编写工作处理函数void (*work_func_t)(struct work_struct *work);
- 在合适的地方调调度工作(一般在中断顶半部分);
2.4工作队列使用示例
??下面以按键为例,实现中断方式按键检测,通过工作队列处理底半部分代码,杂项设备框架实现设备注册。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
struct work_struct key_work;
struct _KEY
{
unsigned int gpio;
char name[20];
int irq;
int key_num;
};
static struct _KEY KEY_GPIO_PIN[]=
{
{EXYNOS4_GPX3(2),"key1",0,1},
{EXYNOS4_GPX3(3),"key2",0,2},
{EXYNOS4_GPX3(4),"key3",0,3},
{EXYNOS4_GPX3(5),"key4",0,4},
};
static struct _KEY *key_p;
static unsigned int key_val;
void key_work_func(struct work_struct *work)
{
msleep(30);
if(gpio_get_value(key_p->gpio)==0)
{
printk(" key%d 按下\n",key_p->key_num);
}
else
{
printk(" key%d 松开\n",key_p->key_num);
}
key_val=key_p->key_num;
}
static irqreturn_t key_irq_handler(int irq, void *dev)
{
key_p=(struct _KEY *)dev;
schedule_work(&key_work);
return IRQ_HANDLED;
}
static int key_open(struct inode *inode, struct file *file)
{
printk("设备打开成功\n");
return 0;
}
static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *seek)
{
int res=copy_to_user(buf,&key_val, 4);
key_val=0;
return 4-res;
}
static int key_release(struct inode *inode, struct file *file)
{
printk("设备关闭成功\n");
return 0;
}
static struct file_operations key_fops=
{
.owner= THIS_MODULE,
.open=key_open,
.read=key_read,
.release=key_release,
};
static struct miscdevice key_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "tiny4412_key",
.fops = &key_fops,
};
static int __init tiny4412_key_module_init(void)
{
int i=0;
int res;
printk("hello,驱动注册成功\n");
INIT_WORK(&key_work,key_work_func);
for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
{
KEY_GPIO_PIN[i].irq=gpio_to_irq(KEY_GPIO_PIN[i].gpio);
res=request_irq(KEY_GPIO_PIN[i].irq,key_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
KEY_GPIO_PIN[i].name,&KEY_GPIO_PIN[i]);
if(res)
{
printk("%d中断注册失败\n",i);
return -1;
}
}
misc_register(&key_miscdev);
return 0;
}
static void __exit tiny4412_key_module_cleanup(void)
{
int i=0;
for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
{
free_irq(KEY_GPIO_PIN[i].irq,&KEY_GPIO_PIN[i]);
}
misc_deregister(&key_miscdev);
printk("Good-bye, 驱动注销成功\n");
}
module_init(tiny4412_key_module_init);
module_exit(tiny4412_key_module_cleanup);
MODULE_LICENSE("GPL");
|