1 中断编程
2 驱动中断处理模板
中断的基本知识在第一节已经有文档记录,这里我们给出中断处理在驱动中的使用模板
2.1 tasklet与底半部中断
/* 定义tasklet和中断底半部函数并将它们关联 */void xxx_do_tasklet(unsigned long);DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);/* 中断处理底半部函数 */void xxx_do_tasklet(unsigned long){// ...}/* 中断处理顶半部 */irqreturn_t xxx_interrupt(int irq, void *dev_id){// ...tasklet_schedule(&xxx_tasklet);//调度底半部处理// ...}/* 设备驱动模块加载函数 */int __init xxx_init(void){// .../* 申请中断 */result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);// ...return IRQ_HANDLED;}/* 设备驱动模块卸载函数 */void __exit xxx_exit(void){// .../* 释放中断 */free_irq(xxx_irq, xxx_interrupt);// ...}
2.2 工作队列与底半部中断
/* 定义工作队列和关联函数 */struct work_struct xxx_wq;void xxx_do_work(struct work_struct *work);/* 中断处理底半部 */void xxx_do_work(struct work_struct *work){//...}/* 中断处理顶半部 */irqreturn_t xxx_interrupt(int irq, void *dev_id){//...schedule_work(&xxx_wq); //调度底半部处理//...return IRQ_HANDLED;}/* 设备驱动模块加载函数 */int xxx_init(void){//.../* 申请中断 */result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);//.../* 初始化工作队列 */INIT_WORK(&xxx_wq, xxx_do_work);//...}/* 设备驱动模块卸载函数 */void xxx_exit(void){//.../* 释放中断 */free_irq(xxx_irq, xxx_interrupt);//...}
2.3 多设备共享中断
/* 中断处理顶半部 */irqreturn_t xxx_interrupt(int irq, void *dev_id){//...int status = read_int_status(); /* 获知中断源 */if (!is_myint(dev_id, status)) /* 判断是否为本设备中断 */return IRQ_NONE; /* 不是本设备中断, 立即返回 *//* 是本设备中断, 进行处理 *///...return IRQ_HANDLED; /* 返回 IRQ_HANDLED 表明中断已被处理 */}/* 设备驱动模块加载函数 */int xxx_init(void){//.../* 申请共享中断 */result = request_irq(sh_irq, xxx_interrupt,IRQF_SHARED, "xxx", xxx_dev);//...}/* 设备驱动模块卸载函数 */void xxx_exit(void){//.../* 释放中断 */free_irq(xxx_irq, xxx_interrupt);//...}
3 内核定时器编程
4 驱动中定时器处理模板
/* xxx 设备结构体 */struct xxx_dev{struct cdev cdev;//...timer_list xxx_timer; /* 设备要使用的定时器 */};/* xxx 驱动中的某函数 */xxx_func1( … ){struct xxx_dev *dev = filp->private_data;//.../* 初始化定时器 */init_timer(&dev->xxx_timer);dev->xxx_timer.function = &xxx_do_timer;dev->xxx_timer.data = (unsigned long)dev;/* 设备结构体指针作为定时器处理函数参数 */dev->xxx_timer.expires = jiffies + delay;/* 添加(注册) 定时器 */add_timer(&dev->xxx_timer);//...}/* xxx 驱动中的某函数 */xxx_func2( … ){//.../* 删除定时器 */del_timer(&dev->xxx_timer);//...}/* 定时器处理函数 */static void xxx_do_timer(unsigned long arg){struct xxx_device *dev = (struct xxx_device *)(arg);//.../* 调度定时器再执行 */dev->xxx_timer.expires = jiffies + delay;add_timer(&dev->xxx_timer);//...}
5 驱动定时器处理示例
//秒字符设备//在打开时初始化定时器并添加到内核的定时器链表中,每秒输出一次当前jiffies#include <linux/module.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/init.h>#include <linux/cdev.h>#include <linux/slab.h>#include <linux/uaccess.h>#define SECOND_MAJOR 232static int second_major = SECOND_MAJOR;module_param(second_major, int, S_IRUGO);struct second_dev{struct cdev cdev;atomic_t counter; //原子类型计数struct timer_list s_timer; //内核定时器};static struct second_dev *second_devp;static void second_timer_handler(struct timer_list* arg){mod_timer(&(second_devp->s_timer), jiffies + HZ); //触发下一次定时atomic_inc(&(second_devp->counter));printk(KERN_INFO "current jiffies is %ld\n", jiffies);}static int second_open(struct inode *inode, struct file *filp){//打开设备时,初始化定时器,设置处理函数second_devp->s_timer.function = &second_timer_handler;second_devp->s_timer.expires = jiffies + HZ;add_timer(&(second_devp->s_timer)); //添加timer到内核atomic_set(&(second_devp->counter), 0);return 0;}static int second_release(struct inode *inode, struct file *filp){del_timer(&(second_devp->s_timer)); //释放设备时,从内核删除定时器return 0;}static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos){//读操作int counter = atomic_read(&(second_devp->counter));if (put_user(counter, (int *)buf)) //复制counter到用户空间return -EFAULT;elsereturn sizeof(unsigned int);}static const struct file_operations second_fops ={.owner = THIS_MODULE,.open = second_open,.release = second_release,.read = second_read};//驱动模块加载函数static void second_setup_cdev(struct second_dev* dev, int index){int err, devno = MKDEV(second_major, index); //获得dev_t对象cdev_init(&(dev->cdev), &second_fops);//初始化设备dev->cdev.owner = THIS_MODULE;//注册设备err = cdev_add(&dev->cdev, devno, 1);if (err){printk(KERN_NOTICE"Error %d adding second %d", err, index);}}static int __init second_init(void){int ret;dev_t devno = MKDEV(second_major, 0);//申请设备号if (second_major){ret = register_chrdev_region(devno, 1, "second");}else{ret = alloc_chrdev_region(&devno, 0, 1, "second");second_major = MAJOR(devno);}if (ret < 0)return ret;second_devp = kzalloc(sizeof(struct second_dev), GFP_KERNEL);if (!second_devp){//空间申请失败ret = -ENOMEM;goto fail_malloc;}second_setup_cdev(second_devp, 0);//初始化互斥量return 0;fail_malloc:unregister_chrdev_region(devno, 1);return ret;}module_init(second_init);//驱动模块卸载函数static void __exit second_exit(void){cdev_del(&second_devp->cdev);//注销设备kfree(second_devp);unregister_chrdev_region(MKDEV(second_major, 0), 1);//释放设备号}module_exit(second_exit);//模块声明MODULE_AUTHOR("BARRET REN <barret.ren@outlook.com>");MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("A driver for virtual second charactor device");MODULE_ALIAS("second device driver");
测试程序很简单,只有触发驱动的read就可以:
//second cdev的用户空间测试程序#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(){int fd;int counter = 0;int old_counter = 0;/* 打开 /dev/second 设备文件 */fd = open("/dev/second", O_RDONLY);if (fd != -1){while (1){read(fd, &counter, sizeof(unsigned int)); /* 读目前经历的秒数 */if (counter != old_counter){printf("seconds after open /dev/second :%d\n", counter);old_counter = counter;}}}else{printf("Device open failure\n");}}
之后创建设备号为232的/dev/second设备节点,运行测试程序就会看待控制台打印,同时模块在dmesg也会打印jiffies的值。
