前言
前面讲了linux中断编程:顶半部、底半部
中断顶半部:做紧急的,耗时短的事情,同时还启动中断底半部(如果有)。
中断底半部:做耗时的事情,这个事件可以被中断
实现方法:tasklet、工作队列、软中断等机制。实际上是把耗时的事件推后执行,不在中断程序中执行。
内核tasklet机制
tasklet
机制是一种比较特殊的软中断,tasklet
一词原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。这个tasklet
绑定的函数在一个时刻只能在一个CPU
上运行,在SMP
系统上不会出现并发问题。
tasklet
(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet
的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着会通过调用tasklet
使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核工作。对应到我们此刻所说的tasklet
就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet
,如下图所示:
tasklet核心数据结构
路径:interrupt.h linux-3.5\include\linux
struct tasklet_struct
{
struct tasklet_struct *next; //用来实现多个 tasklet_struct 结构链表
unsigned long state; //当前这个tasklet是否已经被调度
atomic_t count; //值为0时候用户才可以调度
void (*func)(unsigned long); //指向tasklet绑定的函数的指针
unsigned long data; //传递给tasklet绑定函数的参数
};
使用tasklet
机制就是定义一个这个结构体变量,然后填充必须的元素:count
、func
、data
。
tasklet机制内核API
tasklet定义及初始化函数
头文件interrupt.h linux-3.5\include\linux
名称 | 宏、函数 | 作用 |
---|---|---|
静态初始化 | DECLARE_TASKLET(name, func, data) | 初始化相关成员默认被调用 |
静态初始化 | DECLARE_TASKLET_DISABLED(name, func, data) | 初始化相关成员默认不能被调用 |
动态初始化 | extern void tasklet_init(struct tasklet_struct *t, | 初始化相关成员,处于激活状态 |
void (*func)(unsigned long), unsigned long data) ; | 默认可以被调用 |
补充tasklet_init函数源码:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0); //默认设置为0 可以被调用
t->func = func;
t->data = data;
}
tasklet机制使能、失能函数
前面提到结构中atomic_t count
成员值为0
时用户才可以调度,如果用户想去调度一个没有激活的tasklet
或者禁止调度一个已经激活的tasklet
可以使用以下两个函数
名称 | 函数 |
---|---|
取消激活函数 | tasklet_disable(struct tasklet_struct *t) |
激活函数 | void tasklet_enable(struct tasklet_struct *t) |
说明:
如果对一个tasklet_struct
结构变量调用了两次tasklet_disable
函数,则count
会增加为2
,这时你在调用一次tasklet_enable
,那实际count
值还是1
,还是处于非激活状态。enable
与disable
只是相对而言。
tasklet_disable源码补充:
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count); //单纯的+1
smp_mb__after_atomic_inc();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_enable源码补充:
void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count); //单纯-1
}
tasklet调度函数 tasklet_schedule(struct tasklet_struct*t)
tasklet_schedule(struct tasklet_struct*t)
tasklet_struct
结构体中绑定了一个函数,当用户需要执行这个函数的时候,需要自己调用tasklet_schedule
函数去通知内核帮我们调度所绑定的函数
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
tasklet取消调度函数tasklet_kill(struct tasklet_struct*t)
void tasklet_kill(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
tasklet机制编程步骤
- 第一步:写tasklet结构中的绑定函数
void tasklet_func(unsigned long data)
{
······
}
- 第二步:定义tasklet结构
第一种使用静态定义:(定义出的
tasklet
能被调度)
DECLARE_TASKLET(mttasklet,tasklet_func,123);
第二种使用静态定义:(定义出的tasklet
不能被调度)
DECLARE_TASKLET_DISABLED(mttasklet,tasklet_func,123);
第三种动态定义:一般在模块初始化时候
1)先定义一个变量
struct tasklet_struct mytasklet;
2)对这个变量进行初始化,如果是静态定义结构体,略过这一步
tasklet_init(&mytasklet,tasklet_func,123)
- 第三步:初始化tasklet结构
根据你想实现的功能是什么,在什么情况要调用到你绑定的函数,就在那里调用tasklet_schedule
函数进行调度。
tasklet_schedule(&mytasklet)
- 第四步:在适当的地方进行调度
如果中途不想使用tasklet,则可以删除它。
tasklet_kill(&mytasklet)
一般情况在模块卸载函数中编写。 - 第五步:如果想取消调度,则在适当的地方取消
tasklet机制编程示例
简单的示例代码
#include<linux/module.h>
#include<linux/init.h>
//添加头文件
#include<linux/interrupt.h>
//实现一个tasklet工作函数
void tasklet_func(unsigned long data)
{
printk("%s is call!! data:%d\r\n",__FUNCTION__,data);
}
//定义一个tasklet_struct结构变量,并且进行初始化
DECLARE_TASKLET(mytasklet, tasklet_func, 123);
static int __init tasklet_init(void)
{
//一安装模块就进行调度
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
static void __exit tasklet_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
MODULE_INIT(tasklet_init);
MODULE_EXIT(tasklet_exit);
MODULE_LICENSE("GPL");
开发板运行效果
[root@ZC/zhangchao]#insmod tasklet.ko
[ 34.940000] tasklett_init is call!![ 34.940000] tasklet_func is call!! data:123
[root@ZC/zhangchao]#
循环调度绑定函数
在tasklet工作函数中再调度即可#include<linux/module.h>
#include<linux/init.h>
//添加头文件
#include<linux/interrupt.h>
void tasklet_func(unsigned long data);
//定义一个tasklet_struct结构变量,并且进行初始化
DECLARE_TASKLET(mytasklet, tasklet_func, 123);
//实现一个tasklet工作函数
void tasklet_func(unsigned long data)
{
static unsigned long cnt=0;
printk("cnt:%d %s is call!! data:%d\r\n",cnt++,__FUNCTION__,data);
tasklet_schedule(&mytasklet);
}
static int __init tasklett_init(void)
{
//一安装模块就进行调度
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
static void __exit tasklett_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
module_init(tasklett_init);
module_exit(tasklett_exit);
MODULE_LICENSE("GPL");
开发板运行效果
[ 4010.785000] cnt:1573 tasklet_func is call!! data:123
[ 4010.790000] cnt:1574 tasklet_func is call!! data:123
[ 4010.795000] cnt:1575 tasklet_func is call!! data:123
[ 4010.800000] cnt:1576 tasklet_func is call!! data:123
[ 4010.805000] cnt:1577 tasklet_func is call!! data:123
[ 4010.810000] cnt:1578 tasklet_func is call!! data:123
[ 4010.815000] cnt:1579 tasklet_func is call!! data:123
[ 4010.820000] cnt:1580 tasklet_func is call!! data:123
[ 4010.825000] cnt:1581 tasklet_func is call!! data:123
[ 4010.830000] cnt:1582 tasklet_func is call!! data:123
[ 4010.835000] cnt:1583 tasklet_func is call!! data:123
[ 4010.840000] cnt:1584 tasklet_func is call!! data:123
[ 4010.845000] cnt:1585 tasklet_func is call!! data:123
[ 4010.850000] cnt:1586 tasklet_func is call!! data:123
[ 4010.855000] cnt:1587 tasklet_func is call!! data:123
[ 4010.860000] cnt:1588 tasklet_func is call!! data:123
失能函数
在初始化函数内先失能tasklet结构,再调度,将不会执行工作函数,但是`tasklet_kill` 一个没有使能但是调度了的`tasklet`内核会异常,造成模块卸载时内核会卡住。static int __init tasklet_init(void)
{
//故意测试 失能tasklet再次调用看是否还能执行
tasklet_disable(&mytasklet);
//一安装模块就进行调度
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
//tasklet_kill 一个没有使能但是调度了的tasklet内核会异常
static void __exit tasklet_exit(void)
{
//删除tasklet
tasklet_kill(&mytasklet);
printk("tasklet is kill!\r\n");
}
先失能后使能功能正常,卸载效果也正常
static int __init tasklet_init(void)
{
//故意测试 失能tasklet再次调用看是否还能执行
tasklet_disable(&mytasklet);
tasklet_enable(&mytasklet);
//一安装模块就进行调度
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
重复调度会出现什么状况呢?
**注意:先把工作函数中调度函数注释掉否则会重复调度,否则不能达成测试目的**static int __init tasklet_init(void)
{
//一安装模块就进行调度
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
tasklet_schedule(&mytasklet);
printk("%s is call!!",__FUNCTION__);
return 0;
}
**运行效果表明:虽重复调度,但只执行一次工作函数**
linux使用tasklet机制实现中断底半部
思路:把以前写的按键驱动程序中的服务函数分为中断顶半部和中断底半部,下面是按键中断服务函数中所做的事
irqreturn_t key_handler(int irq, void * dev_id)
{
int dn; //存放按键状态
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)dev_id;
//判断按下还是松开按键;可通过读取按键IO电平状态来判断
//硬件上,按下按键返回电平0 松开返回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+1);
else
printk("key%d up!!\r\n",pdev_data->key_num+1);
//存储按键状态到按键缓冲区
//这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
软件思路
把上面的代码分为两部分
顶半部:
只是单纯写调度底半部代码,因为当前代码没有什么非常紧急的事情需要完成。
底半部:
把原来中断程序中的代码原样复制过去,写在tasklet服务函数中去
修改部分
- 添加头文件
#include<linux/interrupt.h>
- 修改按键结构体
因为有四个按键,每个按键对应一个tasklet
struct key_info{
int key_gpio; //按键IO
int key_num; //按键编号
const char* name; //按键名称
struct tasklet_struct keytasklet; //按键对应tasklet结构体
};
- 构造tasklet服务函数
//实现 tasklet函数
void key_tasklet_func(unsigned long data)
{
int dn; //存放按键状态
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)data;
//判断按下还是松开按键;可通过读取按键IO电平状态来判断
//硬件上,按下按键返回电平0 松开返回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+1);
else
printk("key%d up!!\r\n",pdev_data->key_num+1);
//存储按键状态到按键缓冲区
//这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=1;
wake_up_interruptible(&wq);
}
- 修改中断函数
irqreturn_t key_handler(int irq, void * dev_id)
{
//注册的时候传递进来的是每个按键元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)dev_id;
//启动中断底半部
tasklet_schedule(&pdev_data->keytasklet);
return IRQ_HANDLED;
}
- 初始化中注册tasklet这里不使用静态初始化,使用动态初始化,在模块初始化部分动态初始化
for(i=0;i<4;i++)
{
//初始化每个按键 tasklet结构
key_info[i].keytasklet;
tasklet_init(&key_info[i].keytasklet,key_tasklet_func,&key[i]);
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
//如果有注册失败的 跳出循环
if(err<0)
{
break;
}
}
- 在模块卸载中 删除tasklet
for(i=0;i<4;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
tasklet_kill(&key_info[i].keytasklet); //删除tasklet
}
驱动源码
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自动创建设备头文件
#include<linux/uaccess.h>
#include<linux/interrupt.h> //中断注册注销头文件
#include<linux/gpio.h> //gpio相关的头文件
#include<linux/delay.h>
#include<linux/sched.h>
//定义等待队列 头 wq,要是一个全局变量
DECLARE_WAIT_QUEUE_HEAD(wq);
//按键数量
#define KEY_SIZE (4)
//按键缓冲区
unsigned char kbuf[KEY_SIZE]={"0000"}; //储存按键状态 初始化为字符 0,0表示没有按下,1表示按下
//定义全局变量是否有按键标识
static int press=0;
struct key_info{
int key_gpio; //按键IO
int key_num; //按键编号
const char* name; //按键名称
struct tasklet_struct keytasklet; //按键对应tasklet结构体
};
struct key_info keys[]={
{EXYNOS4_GPX3(2),0,"key1"},
{EXYNOS4_GPX3(3),1,"key2"},
{EXYNOS4_GPX3(4),2,"key3"},
{EXYNOS4_GPX3(5),3,"key4"}
};
//定义字符设备结构体
static struct cdev *xxxdriver_cdev;
//定义设备号(包含主次)
static dev_t xxxdriver_num=0;
//定义设备类
static struct class *xxxdriver_class;
//定义设备结构体
static struct device *xxxdriver_device;
//定义设备名称
#define XXXDRIVER_NAME "mydevice"
ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
if(size>KEY_SIZE)
{
size=KEY_SIZE;
}
//如果按键没有动作
if(press==0)
{
//如果是非阻塞方式打开
if(file->f_flags & O_NONBLOCK)
{
//返回错误码
return -EAGAIN;
}else
{
//休眠
wait_event(wq,press);
}
}
//清除按键处理标志,执行到这里表示发生了中断有了按键动作
//一定清0,否则下次调用会出问题
press=0;
if(copy_to_user(usr,kbuf,size))
{
printk("copy to user faild\r\n");
return -EFAULT;
}
return size;
}
int XXX_open (struct inode *node, struct file *pfile)
{
printk("files open success!!\r\n");
return 0;
}
int XXX_release (struct inode *node, struct file *file)
{
printk("file close success!!\r\n");
return 0;
}
/*key中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来
*/
irqreturn_t key_handler(int irq, void * dev_id)
{
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)dev_id;
//启动中断底半部
tasklet_schedule(&pdev_data->keytasklet);
return IRQ_HANDLED;
}
//实现 tasklet函数
void key_tasklet_func(unsigned long data)
{
int dn; //存放按键状态
//注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
//这里进行还原,还原后可以取得这个结构的所有元素信息
struct key_info *pdev_data=(struct key_info*)data;
//判断按下还是松开按键;可通过读取按键IO电平状态来判断
//硬件上,按下按键返回电平0 松开返回1
dn=gpio_get_value(pdev_data->key_gpio);
if(!dn)
printk("key%d down!!\r\n",pdev_data->key_num+1);
else
printk("key%d up!!\r\n",pdev_data->key_num+1);
//存储按键状态到按键缓冲区
//这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
kbuf[pdev_data->key_num]= '0'+!dn;
press=1;
wake_up(&wq);
}
//文件操作函数结构体
static struct file_operations xxxdriver_fops={
.owner=THIS_MODULE,
.open=XXX_open,
.release=XXX_release,
.read=XXX_read,
};
static __init int xxxdriver_init(void)
{
//定义错误返回类型
int err;
//定义中断编号
int irq,i;
//设置中断属性 非共享中断 上升下降沿触发
unsigned long flags=IRQF_DISABLED |
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING;
for(i=0;i<4;i++)
{
//初始化每个按键 tasklet结构
tasklet_init(&keys[i].keytasklet,key_tasklet_func,&keys[i]);
irq=gpio_to_irq(keys[i].key_gpio);
err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
//如果有注册失败的 跳出循环
if(err<0)
{
break;
}
}
//有注册失败的 将前面注册成功的释放掉
if(err<0){
for(--i;i>0;i--)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq, &keys[i]);
}
}
//分配字符设备结构体,前面只是定义没有分配空间
xxxdriver_cdev=cdev_alloc();
//判断分配成功与否
if(xxxdriver_cdev==NULL)
{
err=-ENOMEM;
printk("xxxdriver alloc is err\r\n");
goto err_xxxdriver_alloc;
}
//动态分配设备号
err=alloc_chrdev_region(&xxxdriver_num, 0, 1, XXXDRIVER_NAME);
//错误判断
if(err<0)
{
printk("alloc xxxdriver num is err\r\n");
goto err_alloc_chrdev_region;
}
//初始化结构体
cdev_init(xxxdriver_cdev,&xxxdriver_fops);
//驱动注册
err=cdev_add(xxxdriver_cdev,xxxdriver_num,1);
if(err<0)
{
printk("cdev add is err\r\n");
goto err_cdev_add;
}
//创建设备类
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
err=PTR_ERR(xxxdriver_class);
if(IS_ERR(xxxdriver_class))
{
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
}
//创建设备
xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
err=PTR_ERR(xxxdriver_device);
if(IS_ERR(xxxdriver_device))
{
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
}
printk("xxxdriver init is success\r\n");
return 0;
//硬件初始化部分
err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, 1);
err_alloc_chrdev_region:
kfree(xxxdriver_cdev);
err_xxxdriver_alloc:
return err;
}
static __exit void xxxdriver_exit(void)
{
int irq,i;
for(i=0;i<4;i++)
{
irq=gpio_to_irq(keys[i].key_gpio);
free_irq(irq,&keys[i]);
tasklet_kill(&keys[i].keytasklet); //删除tasklet
}
device_destroy(xxxdriver_class,xxxdriver_num);
class_destroy(xxxdriver_class);
cdev_del(xxxdriver_cdev);
unregister_chrdev_region(xxxdriver_num, 1);
printk("xxxdriver is exit\r\n");
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
app函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i,file_fp;
unsigned char btn[4]={"0000"},cur[4]={"0000"};
file_fp = open(argv[1],O_RDWR);
while(1)
{
read(file_fp,cur,4);
for(i=0;i<4;i++)
{
if(cur[i]!=btn[i])
{
btn[i]=cur[i];
if(cur[i]=='1')
printf("按键%d 按下\r\n",i+1);
else
printf("按键%d 弹起\r\n",i+1);
}
}
}
close(file_fp);
}
Makefile
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
prog:
arm-linux-gcc btn_app.c -o app
obj-m += btn.o
开发板运行效果
[root@ZC/zhangchao]#insmod btn.ko
[ 1943.005000] xxxdriver init is success
[root@ZC/zhangchao]#./app /dev/xxxdevice
[ 1954.680000] files open success!!
[ 1958.160000] key1 down!!
按键1 按下
[ 1958.345000] key1 up!!
按键1 弹起
[ 1959.135000] key2 down!!
按键2 按下
[ 1959.305000] key2 up!!
按键2 弹起
开发板运行效果与单纯运行按键中断代码效果相同,当然这里只是一个简单的例子没有实际意义。