Linux内核tasklet机制
本文介绍了Linux内核中的tasklet机制及其在中断处理中的应用。tasklet是一种特殊的软中断,用于实现中断处理的底半部,具有在SMP系统上不会并发执行的特性。文章详细阐述了tasklet的数据结构tasklet_struct及其成员功能,包括状态管理、绑定函数和参数传递等。同时提供了tasklet的API使用方法,包括静态/动态初始化、调度控制等关键操作。
1.Linux内核微线程tasklet特征
- tasklet内核微线程是一种较为特殊的软中断。
- tasklet内核微线程是中断处理底半部分最常用的一种方法。
- tasklet绑定的函数在同一时刻只能在一个CPU上运行,在SMP系统(多处理器系统)上不会出现并发问题。
2.Linux内核tasklet应用于中断底半部

将中断处理程序拆分为两个部分,其中顶半部仅负责处理紧急事务,同时通过tasklet_schedule函数调度底半部代码。调用tasklet_schedule后,其关联的处理函数不会立即执行,而是会在中断处理完成后,经过一段不确定但通常较短的时间间隔才会被系统调用执行。
3.Linux内核tasklet数据结构:
内核微线使用tasklet_struct结构描述,结构定义在:include/interrupt.h文件中。
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
成员:
next:内核为了把所有tasklet使用链表统一管理而设置的一个成员。
count:用来表示tasklet是否处于激活状态。0时表示处于激活状态,非0时表示处于非激活状态
当处于激活状态的tasklet在调度后才会执行绑定函数。
state:表示当前tasklet状态,指示正在执行绑定函数,开发者不需要直接访问这个成员。
func:tasklet绑定函数
data:函数执行时传递的参数。
4.Linux内核tasklet内核API
4.1 静态初始化:DECLARE_TASKLET
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
#define DECLARE_TASKLET(name, func, data) struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } |
|
参数 |
func:tasklet绑定函数; |
|
data:函数执行时传递的参数。 |
|
|
功能 |
定义一个名字为name的tasklet,并且初始化为激活状态。 |
4.2 静态初始化:DECLARE_TASKLET_DISABLED
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
#define DECLARE_TASKLET_DISABLED(name, func, data) struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } |
|
参数 |
func:tasklet绑定函数; |
|
data:函数执行时传递的参数。 |
|
|
功能 |
定义一一个名字为name的tasklet,并且初始化为非激活状态。 |
说明:一开始非激活状态à调度à某时刻count修改为0à不确定后à绑定func就会执行不需要重新调度
4.3 动态初始化:tasklet_init
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); |
|
参数 |
t:任务核心结构体指针 |
|
func:tasklet绑定函数; |
|
|
data:函数执行时传递的参数。 |
|
|
功能 |
运行时动态初始化tasklet,一般写模块初始化函数中 |
|
返回值 |
无 |
4.4 tasklet禁止:tasklet_disable
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
static inline void tasklet_disable(struct tasklet_struct *t) |
|
参数 |
t:任务核心结构体指针 |
|
功能 |
禁止任务 |
|
返回值 |
无 |
4.5 tasklet使能:tasklet_enable
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
static inline void tasklet_enable(struct tasklet_struct *t) |
|
参数 |
t:任务核心结构体指针 |
|
功能 |
使能任务 |
|
返回值 |
无 |
4.6 tasklet调度:tasklet_schedule
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
void tasklet_schedule(struct tasklet_struct *t) |
|
参数 |
t:任务核心结构体指针 |
|
功能 |
调度tasklet,调度后,绑定的函数会在不确定时间后被调用(前提条件是tasklet处于激活状态)。 |
|
返回值 |
无 |
注意:如果tasklet已经调度,但是还没有被响应(绑定的函数已经开始执行,就算没有执行完成也算是响应了),重复调度是无效。
4.7 tasklet取消:tasklet_kill
|
头文件 |
#include <linux/interrupt.h> |
|
原型 |
void tasklet_kill(struct tasklet_struct *t) |
|
参数 |
t:任务核心结构体指针 |
|
功能 |
取消一个已经调试的tasklet。这个函数会等待已经绑定函数执行完成 |
|
返回值 |
无 |
注意:你不能取消一个处于非激活状态,但是已经被调度的tasklet,否则会出现死锁情况
5.Linux内核tasklet使用示例
- 使用步骤:
- 编写一个tasklet任务函数
- 定义struct tasklet_struct结构变量
- 初始化上一步定义的结构变量
- 调度tasklet
- 取消调度tasklet
- 示例代码
#include<linux/module.h>
#include<linux/init.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
//定义全局驱动数据结构体
struct my_driver_data {
struct tasklet_struct mytasklet; // 任务结构体
char *data; // 消息字符串
};
static struct my_driver_data mydrvdata; // 声明全局驱动数据实例
void mytask_func(unsigned long data)
{
struct my_driver_data *pdata = (struct my_driver_data *)data;
printk("data:%s\r\n",pdata->data);
//循环测试
//tasklet_schedule(&pdata->mytasklet);
//休眠测试
//msleep(1000); //不能调用休眠函数,否则内核会异常
//udelay(500); //这个函数不休眠,相当于while(t--);
}
static int __init mytasklet_init(void)
{
mydrvdata.data = "LIU";
tasklet_init(&mydrvdata.mytasklet, mytask_func, (unsigned long)&mydrvdata);
tasklet_disable(&mydrvdata.mytasklet);//禁止调度测试
printk("-----------begin------------\r\n");
//多次调度只算一次
tasklet_schedule(&mydrvdata.mytasklet);
tasklet_schedule(&mydrvdata.mytasklet);
tasklet_schedule(&mydrvdata.mytasklet);
printk("------------end-----------\r\n");
return 0;
}
static void __exit mytasklet_exit(void)
{
tasklet_enable(&mydrvdata.mytasklet);//禁止/重新使能调度测试
tasklet_kill(&mydrvdata.mytasklet);
}
module_init(mytasklet_init); // 指定模块初始化函数
module_exit(mytasklet_exit); // 指定模块清理函数
MODULE_LICENSE("GPL"); // 声明模块许可证(GPL v2)

6.Linux内核tasklet实现按键中断底半部
在原来按键驱动基础上修改,把按键中断程序分为顶半部和底半部。
- 设计思路
- 在按键结构中增加一个tasklet_struct结构成员
- 在注册中断的循环初始化tasklet_struct成员
- 把原来读取按键状态的代码从中断程序转移到tasklet任务函数中
- 在中断程序中调度tasklet,这一步就称为启动中断底部分
- 在模块卸载函数取消tasklet
drv_btn.c
//增加内核定时器功能
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/gpio.h>
#include<linux/slab.h>
#define LEDS_MINOR 255
#define DEVICE_NAME "my-buttons" // 设备节点名称,将在/dev目录下创建
#ifndef ARRAYSIZE
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
//按键数量,在模块初始化函数中进行计算
static int key_size;
//按键缓冲区,一个元素存放一个按键值,'1'表示按下,'0'表示松开
//在模块的初始化函数中分配缓冲区空间
static char *keys_buf;
//使用面向对象思想设计按键,把一个按键信息进行封装
struct key_info{
int id; //按键编号
int gpio; //统一的GPIO编号
unsigned long flags; //触发方式
char *name; //按键名
int irq; //中断编号
struct tasklet_struct tasklet; // 任务结构体
};
//实例化对象
static struct key_info keys[]={
[0]={
.id = 0,
.gpio = 5,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
.name = "key-0",
},
[1]={
.id = 0,
.gpio = 54,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
.name = "key-1",
},
};
static int key_open (struct inode *pinode, struct file *pfile)
{
printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
return 0;
}
loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
{
return 0;
}
static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)
{
int ret;
if(count > key_size) count = key_size;
if(count == 0) return 0;
//准备数据,但是按键数据在中断中实时更新,不需要在这里读取
//复制数据到用户空间
ret = copy_to_user(buf, keys_buf, count);
if(ret){
printk("error:copy_to_user\r\n");
ret = -EFAULT;
goto errot_copy_to_user;
}
return count; // 返回成功读取的字节数
errot_copy_to_user:
return ret;
}
int key_release (struct inode *pinode, struct file *pfile)
{
return 0;
}
// 文件操作结构体(关联实现的操作函数)
static const struct file_operations dev_fops = {
.open = key_open, // 打开设备
.read = key_read, // 读取设备状态
.owner = THIS_MODULE,
};
// 杂项设备定义
static struct miscdevice key_device = {
.name = DEVICE_NAME, // 设备名称(出现在/dev目录下)
.minor = LEDS_MINOR, // 次设备号(建议使用动态分配)
.fops = &dev_fops, // 关联文件操作函数集
};
//tasklet处理函数
void btn_tasklet(unsigned long data)
{
int s;
struct key_info *pdata = (struct key_info *)data;
//检测当前的电平状态
s = !gpio_get_value(pdata->gpio);
keys_buf[pdata->id]='0'+s; //保存状态
}
//按键中断函数
//设置了双边触发,按下和松开都会进入这个函数
irqreturn_t btns_irq_handler(int irq,void *devid)
{
struct key_info *pdata = (struct key_info *)devid;
tasklet_schedule(&pdata->tasklet);//调度tasklet
return IRQ_HANDLED;
}
static int __init my_btn_init(void)
{
int ret,i;
key_size = ARRAY_SIZE(keys); //计算按键数量
//分配按键缓冲区
keys_buf = kzalloc(key_size,GFP_KERNEL);
if(keys_buf == NULL){
return -EFAULT;
}
//循环注册中断
for(i = 0;i < key_size;i++){
keys[i].irq = gpio_to_irq(keys[i].gpio);
if(keys[i].irq < 0){
printk("error:gpio_to_irq\r\n");
goto error_gpio_to_irq;
}
printk("irq:%d\r\n",keys[i].irq);
//传递每个按键结构变量地址,发生中断时可以通过参数取得
ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
if(ret < 0){
printk("error:request_irq\r\n");
goto error_request_irq;
}
//初始化tasklet
tasklet_init(&keys[i].tasklet, btn_tasklet, (unsigned long)&keys[i]);
}
ret = misc_register(&key_device);
if(ret < 0){
printk("error:misc_register\r\n");
goto error_misc_register;
}
return 0;
error_misc_register:
error_request_irq:
while(--i >= 0){
free_irq(keys[i].irq, (void *)&keys[i]); //注销中断
}
error_gpio_to_irq:
kfree(keys_buf); //释放按键缓冲区空间
return ret;
}
static void __exit my_btn_exit(void)
{
int i = key_size;
while(--i >= 0){
tasklet_kill(&keys[i].tasklet); //取消tasklet
}
misc_deregister(&key_device); //注销杂项设备
kfree(keys_buf); //释放按键缓冲区空间
}
module_init(my_btn_init); // 指定模块初始化函数
module_exit(my_btn_exit); // 指定模块清理函数
MODULE_LICENSE("GPL"); // 声明模块许可证(GPL v2)
app.c
#include<stdio.h> // 标准输入输出库(printf, perror等)
#include<stdlib.h>
#include<string.h>
#include<sys/types.h> // 系统数据类型定义(如dev_t)
#include<sys/stat.h> // 文件状态信息(文件模式等)
#include<fcntl.h> // 文件控制选项(open等)
#include<unistd.h> // 系统调用封装(lseek, read, write, sleep等)
#include<sys/ioctl.h> // I/O控制操作(ioctl)
#include<errno.h>
#define BTN_SIZE 1 // 按键数量
#define DEV_NAME "/dev/my-buttons" // 默认设备名
int main(int argc, char **argv)
{
int fd,ret, i;
const char *devname; // 设备路径指针(初始化为默认路径)
unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];
memset(pre_buf,'0',BTN_SIZE);
memset(recv_buf,'0',BTN_SIZE);
if(argc == 1)
devname = DEV_NAME;
else if(argc == 2)
devname= argv[1];
else {
printf("Usage:%s [/dev/devname]\r\n", argv[0]);
return 0;
}
fd = open(devname, O_RDWR); // O_RDWR:以读写模式打开
if(fd < 0) {
perror("open"); // 打印系统错误信息
printf("fd=%d\r\n", fd);
return -1; // 打开失败退出程序
}
printf("fd=%d\r\n", fd); // 成功打开后输出fd值
while(1) {
ret = read(fd,recv_buf,BTN_SIZE); //读取按键数据
if(ret < 0){
if(errno != EAGAIN){
perror("read");
exit(-1);
}else continue;
}
//只在状态发生变化时候才输出
for(i = 0;i < BTN_SIZE;i++){
//分别判断每一个按键状态是否发生变化
if(recv_buf[i] != pre_buf[i]){
//更新当前状态为上一次状态
pre_buf[i] = recv_buf[i];
//判断这次变化是按下还是松开
if(pre_buf[i] == '1')
printf("KEY%d is press!\r\n",i+1);
else
printf("KEY%d is up!\r\n",i+1);
}
}
}
return 0;
}
现象

7.按键消抖
把定时器和tasklet结合实现按键驱动实现消抖和底半部。
驱动代码
//增加内核定时器功能
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/gpio.h>
#include<linux/slab.h>
#define LEDS_MINOR 255
#define DEVICE_NAME "my-buttons" // 设备节点名称,将在/dev目录下创建
#ifndef ARRAYSIZE
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
//按键数量,在模块初始化函数中进行计算
static int key_size;
//按键缓冲区,一个元素存放一个按键值,'1'表示按下,'0'表示松开
//在模块的初始化函数中分配缓冲区空间
static char *keys_buf;
//使用面向对象思想设计按键,把一个按键信息进行封装
struct key_info{
int id; //按键编号
int gpio; //统一的GPIO编号
unsigned long flags; //触发方式
char *name; //按键名
int irq; //中断编号
struct timer_list timer; //增加一个定时器作消抖
struct tasklet_struct tasklet; // 任务结构体
};
//实例化对象
static struct key_info keys[]={
[0]={
.id = 0,
.gpio = 5,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
.name = "key-0",
},
[1]={
.id = 1,
.gpio = 54,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
.name = "key-1",
},
};
static int key_open (struct inode *pinode, struct file *pfile)
{
printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
return 0;
}
loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
{
return 0;
}
int key_release (struct inode *pinode, struct file *pfile)
{
return 0;
}
static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)
{
int ret;
if(count > key_size) count = key_size;
if(count == 0) return 0;
//准备数据,但是按键数据在中断中实时更新,不需要在这里读取
//复制数据到用户空间
ret = copy_to_user(buf, keys_buf, count);
if(ret){
printk("error:copy_to_user\r\n");
ret = -EFAULT;
goto errot_copy_to_user;
}
return count; // 返回成功读取的字节数
errot_copy_to_user:
return ret;
}
// 文件操作结构体(关联实现的操作函数)
static const struct file_operations dev_fops = {
.open = key_open, // 打开设备
.read = key_read, // 读取设备状态
.owner = THIS_MODULE,
};
// 杂项设备定义
static struct miscdevice key_device = {
.name = DEVICE_NAME, // 设备名称(出现在/dev目录下)
.minor = LEDS_MINOR, // 次设备号(建议使用动态分配)
.fops = &dev_fops, // 关联文件操作函数集
};
//tasklet处理函数
void btn_tasklet(unsigned long data)
{
int state;
struct key_info *pdata = (struct key_info *)data;
//检测当前的电平状态
state = !gpio_get_value(pdata->gpio);
// 确保ID在有效范围内
if (pdata->id >= 0 && pdata->id < key_size) {
keys_buf[pdata->id] = '0' + state;
printk("Key %d state: %d\n", pdata->id+1, state);
} else {
printk("Invalid key ID: %d\n", pdata->id+1);
}
}
//超时处理函数
void btn_timer(unsigned long data)
{
struct key_info *pdata = (struct key_info *)data;
tasklet_schedule(&pdata->tasklet); //调度tasklet
}
//按键中断函数
//设置了双边触发,按下和松开都会进入这个函数
irqreturn_t btns_irq_handler(int irq,void *devid)
{
struct key_info *pdata = (struct key_info *)devid;
//启动新一次定时
mod_timer(&pdata->timer,jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
static int __init my_btn_init(void)
{
int ret,i;
key_size = ARRAY_SIZE(keys); //计算按键数量
//分配按键缓冲区
keys_buf = kzalloc(key_size,GFP_KERNEL);
if(keys_buf == NULL) return -EFAULT;
//循环注册中断
for(i = 0;i < key_size;i++){
keys[i].irq = gpio_to_irq(keys[i].gpio);
if(keys[i].irq < 0){
printk("error:gpio_to_irq\r\n");
goto error_gpio_to_irq;
}
printk("irq:%d\r\n",keys[i].irq);
//传递每个按键结构变量地址,发生中断时可以通过参数取得
ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
if(ret < 0){
printk("error:request_irq\r\n");
goto error_request_irq;
}
//初始化tasklet
tasklet_init(&keys[i].tasklet, btn_tasklet, (unsigned long)&keys[i]);
//初始化按键的定时器,参数是按键自身的结构内存地址
setup_timer(&keys[i].timer,btn_timer, (unsigned long)&keys[i]);
}
ret = misc_register(&key_device);
if(ret < 0){
printk("error:misc_register\r\n");
goto error_misc_register;
}
return 0; // 初始化成功
error_misc_register:
error_request_irq:
while(--i >= 0){
free_irq(keys[i].irq, (void *)&keys[i]); //注销中断
}
error_gpio_to_irq:
kfree(keys_buf); //释放按键缓冲区空间
return ret;
}
static void __exit my_btn_exit(void)
{
int i = key_size;
while(--i >= 0){
tasklet_kill(&keys[i].tasklet); //取消tasklet
free_irq(keys[i].irq, (void *)&keys[i]); //注销中断
del_timer_sync(&keys[i].timer);
}
misc_deregister(&key_device); //注销杂项设备
kfree(keys_buf); //释放按键缓冲区空间
}
module_init(my_btn_init); // 指定模块初始化函数
module_exit(my_btn_exit); // 指定模块清理函数
MODULE_LICENSE("GPL"); // 声明模块许可证(GPL v2)
应用程序
#include<stdio.h> // 标准输入输出库(printf, perror等)
#include<stdlib.h>
#include<string.h>
#include<sys/types.h> // 系统数据类型定义(如dev_t)
#include<sys/stat.h> // 文件状态信息(文件模式等)
#include<fcntl.h> // 文件控制选项(open等)
#include<unistd.h> // 系统调用封装(lseek, read, write, sleep等)
#include<sys/ioctl.h> // I/O控制操作(ioctl)
#include<errno.h>
#define BTN_SIZE 2 // 按键数量
#define DEV_NAME "/dev/my-buttons" // 默认设备名
int main(int argc, char **argv)
{
int fd,ret, i;
const char *devname; // 设备路径指针(初始化为默认路径)
unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];
memset(pre_buf,'0',BTN_SIZE);
memset(recv_buf,'0',BTN_SIZE);
if(argc == 1)
devname = DEV_NAME;
else if(argc == 2)
devname= argv[1];
else {
printf("Usage:%s [/dev/devname]\r\n", argv[0]);
return 0;
}
/* 打开设备文件(字符设备)*/
fd = open(devname, O_RDWR); // O_RDWR:以读写模式打开
if(fd < 0) {
perror("open"); // 打印系统错误信息
printf("fd=%d\r\n", fd);
return -1; // 打开失败退出程序
}
printf("fd=%d\r\n", fd); // 成功打开后输出fd值
while(1) {
ret = read(fd,recv_buf,BTN_SIZE); //读取按键数据
if(ret < 0){
if(errno != EAGAIN){
perror("read");
exit(-1);
}else continue;
}
//只在状态发生变化时候才输出
for(i = 0;i < BTN_SIZE;i++){
//分别判断每一个按键状态是否发生变化
if(recv_buf[i] != pre_buf[i]){
//更新当前状态为上一次状态
pre_buf[i] = recv_buf[i];
//判断这次变化是按下还是松开
if(pre_buf[i] == '1')
printf("KEY%d is press!\r\n",i+1);
else
printf("KEY%d is up!\r\n",i+1);
}
}
}
return 0;
}
现象

更多推荐


所有评论(0)