中断下半部处理之tasklet
1.tasklet概述
下半部和退后执行的工作,软中断的使用只在那些执行频率很高和连续性要求很高的情况下才需要。在大多数情况下,为了控制一个寻常的硬件设备,tasklet机制都是实现自己下半部的最佳选择。其实tasklet是利用软中断实现的一种下半部机制。tasklet和软中断在本质上很相似,行为表现也相近。tasklet有两类中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者之间唯一的区别在于HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。
2.tasklet的定义
tasklet由tasklet_struct结构表示,每个结构体单独代表一个tasklet,在<linux/interrupt.h>中定义为:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
1.next:链表中的下一个tasklet;
2.state:tasklet的状态。有三个取值:0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间的取值。TASKLET_STATE_SCHED表明tasklet已经被调度,正准备投入运行,TASKLET_STATE_RUN表示该tasklet正在运行。
3.count:tasklet的引用计数,如果它不为0,则tasklet被禁止,不允许执行;只有当它为0时,tasklet才被激活,并且在被设置为挂起(TASKLET_STATE_SCHED)状态时,该tasklet才能够被执行。
4.结构体中的func成员是tasklet的处理程序,data是它唯一的参数。
3.tasklet的调度
内核使用tasklet_schedule()函数来执行tasklet的调度,已调度的tasklet存放在两个单处理器数据结构:tasklet_vec(普通的tasklet)和tasklet_hi_vec(高优先级的tasklet)。这两个数据结构都是由tasklet_struct结构体构成的链表。链表中的每个tasklet_struct 代表一个不同的tasklet。
tasklet是由tasklet_schedule()和tasklet_hi_shedule()函数进行调度的,它们接受一个指向tasklet_struct结构的指针作为参数。两个函数非常相似(区别在于一个使用TASKLET_SOFTIRQ,一个使用HI_SOFTIRQ()),接下来我们来看下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_STATE_SCHED,则表示已经被调度过了,直接返回。如果没有调度的话就会去执行__tasklet_schedule(t);
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags); //保存IF标志的状态,并禁用本地中断
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t; //下面的这两行代码就是为该tasklet分配per_cpu变量
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ); //触发软中断,让其在下一次do_softirq()的时候,有机会被执行
local_irq_restore(flags); //恢复前面保存的标志
}
由上面的代码可以看出,每次中断只会向其中的一个cpu注册,而不是所有的cpu。完成注册后的tasklet由tasklet_action()函数来执行。
4.执行tasklet
前面说过tasklet被放在一个全局的tasklet_vec的链表中,链表中的元素是tasklet_struct结构体。内核中有个ksoftirqd()的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了(pending),就执行相应的处理函数。tasklet对应的处理函数就是tasklet_action,这个函数在系统启动初始化软中断时,就在软中断向量表中注册。tasklet_action()遍历全局的tasklet_vec链表。链表中的元素为tasklet_struct结构体
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).head; //得到当前处理器上的tasklet链表tasklet_vec或者tasklet_hi_vec.
__get_cpu_var(tasklet_vec).head = NULL; //将当前处理器上的该链表设置为NULL, 达到清空的效果。
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
local_irq_enable(); //允许响应中断
//循环遍历获得链表上的每一个待处理的tasklet
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
在遍历执行时,在tasklet_trylock()和tasklet_unlock()这一段函数中,完成的功能是:首先会去检查count值时否为0,前面已经分析过,当值不为0的时候,说明该tasklet被禁止,如果没有被禁止,则执行其注册的函数,首先会检查tasklet_state的标志位是否是TASKLET_STATE_RUN状态,如果是,则表示该任务已经在别的处理器上运行,如果没有运行,则将其状态标志设置为TASKLET_STATE_RUN,这样别的处理器就不会再去执行它了,这就保证了在同一时间里,相同类型的tasklet只能有一个在运行。
5.Tsklet提供的API
5.1.声明一个Tasklet
静态创建
声明一个tasklet,可以使用下面<linux/interrupt.h>中定义的两个宏中的一个:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
这两个宏都能根据给定的名字静态的创建一个tasklet_struct结构。当该tasklet被调度后,给定的函数func会被执行,data为其参数。这两个宏的区别在于前者前面一个宏把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态,另一个把引用计数器设置为1,所以该tasklet处于禁止状态。
动态创建
也可以使用一个间接的引用(一个指针)赋给一个动态创建的tasklet_struct结构的方式来初始化一个tasklet_init()函数:
tasklet_init(t, tasklet_handler, dev) 动态创建
5.2.编写tasklet处理函数
因为是软中断实现的,这就意味着不能在tasklet处理函数中使用信号量或者一些阻塞的函数。两个相同的tasklet绝不会同时执行,所以,如果你的tasklet和其他的tasklet或者是软中断共享了数据,你必须进行相应的锁保护。
5.3.调度
只有通过调度才能使我们的tasklet有机会被执行,这就使用上面提到的tasklet_shedule()函数。
tasklet_schedule(&my_tasklet);
5.4.禁止或者激活一个tasklet
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
可以用来禁止指定的tasklet,不过它无须再返回前等待tasklet执行完毕,这么做往往不太安全,因为你无法估计该tasklet是否仍在执行。
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_disable()函数来禁止某个指定的tasklet,如果该tasklet当前正在执行,这个函数会等到它执行完毕再返回。
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
tasklet_enable()函数可以激活一个tasklet。
5.5.删除一个tasklet
通过调用tasklet_kill()函数从挂起的队列中去掉一个tasklet。该函数的参数是一个指向某个tasklet的tasklet_struct的指针。这个函数会等待tasklet执行完毕,然后再将它移除。该函数可能会引起休眠,所以要禁止在中断上下文中使用。
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);
}
6.编写自己的tasklet任务
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
char my_tasklet_data[]="my_tasklet_function was called";
/* Bottom Half Function */
void my_tasklet_function( unsigned long data )
{
printk( "%s\n", (char *)data );
}
//声明tasklet
DECLARE_TASKLET( my_tasklet, my_tasklet_function,(unsigned long) &my_tasklet_data );
int init_module( void )
{
/* Schedule the Bottom Half */
printk("diable my_tasklet\n");
tasklet_disable(&my_tasklet);
// printk("enable my_tasklet\n");
// tasklet_enable(&my_tasklet);
tasklet_schedule( &my_tasklet );
return 0;
}
void cleanup_module( void )
{
/* Stop the tasklet before we exit */
tasklet_kill( &my_tasklet );
return;
}
MODULE_AUTHOR("support@ingben.com");
MODULE_DESCRIPTION ("tasklet of buttom half test2");
MODULE_LICENSE ("GPL v2");
中断下半部处理之tasklet的更多相关文章
- Linux 中断下半部
为什么使用中断下半部? 中断执行的原则是要以最快的速度执行完,而且期间不能延时和休眠! 可是现实中,中断中可能没办法很快的处理完需要做的事,或者必须用到延时和休眠,因此引入了中断下半部. 中断中处理紧 ...
- Linux中断分层--软中断和tasklet
1. Linux中断分层 (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断.通常由中断处理程序充当上半部.(一般情况下,上半部不可被打断) (2)下半部:在系统空闲的时候,对上半部“ ...
- 《Linux内核设计与实现》读书笔记(八)- 中断下半部的处理
在前一章也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件. 主要内容: 中断下半部处理 实现中断下半部的机制 总结中断下半部的实现 中断实现示例 1. 中断下半部 ...
- linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)【转】
转自:https://blog.csdn.net/weixin_42471952/article/details/81609141 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协 ...
- 中断下半部-tasklet
http://edsionte.com/techblog/ tasklet的实现 tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的.正如在前文中你所知道的那样,一个 ...
- 中断下半部tasklet【转】
本文转载自:http://edsionte.com/techblog/archives/1547 tasklet的实现 tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简 ...
- 【linux kernel】 中断处理-中断下半部
欢迎转载,转载时需保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http:// ...
- 【linux kernel】 中断处理-中断下半部【转】
转自:http://www.cnblogs.com/embedded-tzp/p/4453987.html 欢迎转载,转载时需保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地 ...
- Linux设备驱动程序 之 中断下半部
中断处理程序的局限 1. 中断处理程序以异步的方式执行,并且它有可能会打断其他重要代码的执行,因此,为了避免被打段的代码停止时间过长,中断处理程序应该执行的越快越好: 2. 如果当前有一个中断处理程序 ...
随机推荐
- 2018年3月份的PTA(一)
写程序证明p++等价于(p)++还是等价于(p++)? 由程序说明p++等价于(p)++,因为(p++)在程序中是没有地址的,而输出中p++和(p)++的地址不同是由于在线C语言开发环境地址是动态的 ...
- 小草手把手教你LabVIEW串口仪器控制—安装使用仪器现有驱动
声明:很多仪器是没有驱动的.所以,具体问题具体分析.另外声明:所谓的驱动,也就是封装好的底层的串口通信程序,也是程序而已,只不过别人帮你做成了子 VI,让自己容易用.所以:不要弄混淆了概念.国外的很多 ...
- 服务器磁盘阵列数据恢复,raid5两块硬盘掉线数据恢复方法
[用户单位信息] 农业科学研究院某研究所 [磁盘阵列故障发生过程描述]客户的DELL MD1000服务器内置15块1TB硬盘搭建为RAID5磁盘阵列阵列,服务器在正常工作中有一块硬盘离线,管理员对磁盘 ...
- Mysql-5.7.21安装配置
搞开发多年,其实MySql前前后后安装配置了无数次,但是每次都需要到网上搜教程,折腾半天才搞定,这次索性把整个过程全部记录下来,以便以后查阅. 下载 到MySql官网,导航找到DOWNLOADS> ...
- django报错Manager isn't accessible via UserInfo instances
出现这种错误是因为调用模型对象时使用了变量名,而不是对象名(模型类),例如: user = UserInfo()user_li = user.objects.filter(uname=username ...
- Xdebug的优点!php代码开发
可以跟踪函数,知道那个函数出错,之前只是输出一点调试信息,只是哪一行错了,并且是白色 如果是死循环,debug会对死循环判断一百次的循环,并且会给出每一次循环的时间,内存,和函数名,位置.根据时间可以 ...
- C++ 异常小记
catch必定使用拷贝构造函数 如下代码编译不通过,因为拷贝构造被标记delete #include <stdexcept> #include <cstdlib> #inclu ...
- Spring Security入门(3-5)Spring Security 的鉴权 - 决策管理器和投票器
1.决策管理器的运行原理: 2.Spring Security提供的决策管理器实现 3.用户自定义的决策管理器
- Oracle update 执行更新操作后的数据恢复
操作数据库,经常会出现误操作,昨天执行的更新操作之后发现更新错了,只能想办法数据恢复了,现在整理一下 第一步:查询执行更新操作的时间 select r.FIRST_LOAD_TIME,r.* from ...
- mysql的账户管理
mysql中账户管理:1 查看所有用户: 所有用户及权限信息都存储在mysql数据库中的user表中 查看user表的结构 desc user\G; 主要字段: host: 表示允许访问的主机 use ...