内核软中断之tasklet机制
1. 软中断IRQ简介
软中断(SoftIRQ)是内核提供的一种基于中断的延时机制, Linux内核定义的软中断有以下几种:
enum
{
HI_SOFTIRQ=0, /*高优先级的tasklet*/
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ, /*普通tasklet*/
SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
HRTIMER_SOFTIRQ,
#endif
};
此外内核维持一个全局的IRQ数组,用来记录不同的软中断的详细信息:
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
该全局变量的类型为struct softirq_action, 它所有IRQ中断处理操作的函数原型. 也就是说系统维持的全局变量记录的是每一种IRQ的中断处理函数信息, 但它的结构非常简单:
struct softirq_action
{
void (*action)(struct softirq_action *); /*中断处理函数指针*/
void *data; /*传递的参数*/
};
由于内核开发者们不建议我们添加自定义软中断, 为此专门提供了tasklet的机制,我们可以通过tasklet来实现我们自己的中断处理任务。tasklet是内核提供开发者的、基于软中断的任务延时机制。tasklet只是内核定义的几种softirq中的一种,设备驱动程序往往通过tasklet来实现某些延时操作。在软中断中tasklet分为两类:一类为高优先级的HI_SOFTIRQ; 另一类为TASKLET_SOFTIRQ。下面详细介绍tasklet的相关信息:
2. tasklet结构体之tasklet_struct
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的状态: TASKLET_STATE_SCHED表示tasklet已经被提交。TASKLET_STATE_RUN表示tasklet正在执行(只用于SMP) |
3 | count | 0:enable, 可以被调度执行。 1: disable, 不可执行 ; |
4 | func | tasklet执行体,由开发者实现 |
5 | data | 给func传递的参数 |
3. tasklet机制初始化
Linux系统在初始化的过程中,通过调用softirq_init函数来为HI_SOFTIRQ和TASKLET_SOFTIRQ安装上函数执行体,分别为tasklet_hi_action和tasklet_action:
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
所谓安装执行函数,就是在IRQ全局变量上,将这两个中断对应的成员参数(一个回调函数指针、一个传递的参数)进行初始化,而这部分是通过open_softirq()来实现的。open_softirq()函数实现十分简单:
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
通过上述两个函数的执行,便完成了tasklet机制的初始化任务,即:
softirq_vec[TASKLET_SOFTIRQ].action = tasklet_hi_action;
softirq_vec[HI_SOFTIRQ].action = tasklet_action;
4. tasklet的初始化
tasklet对象的初始化方法有两种,一种为静态方式,一种为动态方式:
4.1 tasklet对象静态初始化
tasklet对象的声明由两个函数,声明对象时需要将tasklet的对象名name, 延时操作函数func, 以及需要传递的参数进行实现。
#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 }
使用DECLARE_TASKLET声明的对象处于enable状态,使用DECLARE_TASKLET_DISABLED声明的对象处于disable状态。
4.2 tasklet对象动态方式初始化
动态方式初始化则是直接定义一个tasklet_struct 变量,然后使用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);
t->func = func;
t->data = data;
}
5. tasklet对象提交
所谓tasklet对象提交,实际上就是将声明的tasket加入到tasklet_vec管理的链表中, tasklet_vec是一个per-CPU类型的变量,用来将系统中所有通过tasklet_schedule函数提交的tasklet对象构成链表。(linux-2.6.22)
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = __get_cpu_var(tasklet_vec).list; /*使用头插法插入一个新节点 */
__get_cpu_var(tasklet_vec).list = t;
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))/*调度位为0,再提交;如果为1,则证明已经提交,无需再提交*/
__tasklet_schedule(t);
}
- test_and_set_bit(nr,vaddr) 函数:将vaddr的值修改为nr, 并返回vaddr未修改时的值。
- local_irq_save: 关闭本地中断响应。
- local_irq_restore: 打开本地中断。
- 注意: 调度时的条件是state未被设置位TASKLET_STATE_SCHED; 而tasklet在执行权限是通过count来判断的,count=0时该tasklet才可以被执行。
解析如下:
- 调度tasklet对象时,如果state位不为TASKLET_STATE_SCHED,表示该tasklet对象尚未被调度,则将state值置为TASKLET_STATE_SCHED(如果state的值已经为TASKLET_STATE_SCHED,表示已经被调度,则无需再重新调度);
- 关闭本地中断,然后将tasklet对象插入到tasklet_vec链表上。Linux2.6.22内核采用的为头插法,有的Linux版本采用的为尾插法 ,同时struct tasklet_head 的成员有所不同,
- 发送软件中断请求,raise_softirq_irqoff(TASKLET_SOFTIRQ) 来通知内核当前的处理器上有个==“TASKLET_SOFTIRQ”== 正等待处理,它是使用一个整型变量是上的位来表示该位上是由有待处理的IRQ操作,0表示没有,1表示有。
- 重新使能本地中断。
5. tasklet内部执行体
tasklet任务被tasklet_schedule提交到系统相应的链表上后,系统会在遍历该链表然后依次执行tasklet任务。由于HI_SOFTIRQ和TASKLET_SOFTIRQ两个中断只是优先级的不同,其他在处理上基本一致。这里只是介绍TASKLET_SOFTIRQ的执行体tasklet_action。
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).list; /*从tasklet_vec上取下链表,实际是链表的头指针*/
__get_cpu_var(tasklet_vec).list = NULL; /*清空tasklet_vec链表,实际只是清list指针*/
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {/*count=0 使能*/
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))/*清空调度位,返回原来值*/
BUG();/*如果原来为0,属异常情况,应结束退出*/
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = __get_cpu_var(tasklet_vec).list;/*重新插入到tasklet_var中*/
__get_cpu_var(tasklet_vec).list = t;
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
- 在关闭本地中断的前提下,将当前cpu上待处理的tasklet_vec 链表头指针移到临时变量list记录下来,然后将tasklet链表清空,之后重新使能本地中断。重新本地中断的目的是为了可以使系统重新调度新的tasklet任务到tasklet_vec 链表中;
- 遍历该链表
- 使用tasklet_trylock来判断当前的tasklet任务是否在其他cpu上进行处理;
- 获取当前tasklet任务的count值, 如果count=0,表示当前任务(enable)是可以被调度执行的。如果该值为1,则表示当前的tasklet任务(disable)不可调度;
- 检测state的TASKLET_STATE_SCHED位,如果为1,使正常提交的;但如果为0,则表示当前的处理函数正在调度一个没有提交的tasklet,这是种异常情况,直接退出返回;
- 执行tasklet注册的执行体
- 解锁,并重新执行下一个tasklet任务。
- 如果tasklet_trylock的返回值为0,表示该任务可能在其他cpu上进行处理,那么需要重新将该tasklet任务重新提交到tasklet_vec链表上,然后触发软中断等待下次软中断重新执行;
- 使用tasklet_trylock来判断当前的tasklet任务是否在其他cpu上进行处理;
- 从上述代码中可以知道,一个tasklet任务被执行完之前,它的的state上的TASKLET_STATE_SCHED被清空,这就是说除非这个tasklet被重新提交,否则下次软中断是不会再次执行该tasklet任务的。这是一种one-shot特性:提交一次,调度一次,运行完后,从CPU的tasklet_var链表上删除,除非该tasklet任务再次被提交。
- 在执行tasklet时,中断是被重新使能的,这是软中断设计时的初衷。如果执行期间,有其他软中段到来,且新的tasklet也是在该CPU上,那么新的tasklet会被调度到tasklet_vec , 此时并不会影响正在执行的list链表,已经执行的tasklet任务的TASKLET_STATE_SCHED位被清空,而新的tasklet任务的TASKLET_STATE_SCHED被使能,新的tasklet会在下一次IRQ调度中被执行
6. tasklet其他操作
6.1 tasklet_disable
tasklet_disable 和tasklet_disable_nosync 可以使tasklet任务无法被调度后运行,实际上最关键的操作为atomic_inc(&t->count); 因为count=0时(enable),tasklet才可以被调用运行。
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
6.2 tasklet_enable
tasklet_enable 是通过将tasklet对象中的count-1来实现tasklet可以被调度运行的。
注:一个处于disable状态的tasklet可以被提交到tasklet_vec中,但不会被调度执行
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
6.3 tasklet_kill
该函数用来清除一个tasklet对象上的TASKLET_STATE_SCHED的状态位,使IRQ不再能够调度运行此任务。如果当前的tasklet任务正在被运行,那么tasklet_kill将忙等直到该任务执行完毕;如果一个对象已经被提交到系统但尚未调度运行,那么tasklet_kill将会睡眠直到tasklet从tasklet_vec链表中删除。因此该函数是一个可能被阻塞的函数。
- 由此可以看出,一个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);
}
内核软中断之tasklet机制的更多相关文章
- linux内核--软中断与tasklet
硬件中断通常都需要在最短的时间内执行完毕,如果将所有硬件中断相关的处理都放在硬件中断处理程序中,那么就达不到这个目的. 通过linux提供的软中断和tasklet,可以将硬件中断处理程序中可以延迟处理 ...
- [Linux内核]软中断、tasklet、工作队列
转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...
- Linux内核实践之tasklet机制【转】
转自:http://blog.csdn.net/bullbat/article/details/7423321 版权声明:本文为博主原创文章,未经博主允许不得转载. 作者:bullbat 源代码分析与 ...
- 嵌入式Linux内核tasklet机制(附实测代码)
Linux 中断编程分为中断顶半部,中断底半部 中断顶半部: 做紧急,耗时短的事情,同时还启动中断底半部. 中断底半部: 做耗时的事件,这个事件在执行过程可以被中断. 中断底半部实现方法: taskl ...
- Linux内核中的软中断、tasklet和工作队列具体解释
[TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...
- Linux中断管理 (2)软中断和tasklet
目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...
- Linux软中断、tasklet和工作队列
Linux内核中的软中断.tasklet和工作队列详解 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来 ...
- linux中的tasklet机制【转】
转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...
- Linux中断分层--软中断和tasklet
1. Linux中断分层 (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断.通常由中断处理程序充当上半部.(一般情况下,上半部不可被打断) (2)下半部:在系统空闲的时候,对上半部“ ...
随机推荐
- Flutter开发进阶学习指南Flutter开发进阶学习指南
Flutter 的起源 Flutter 的诞生其实比较有意思,Flutter 诞生于 Chrome 团队的一场内部实验, 谷歌的前端团队在把前端一些"乱七八糟"的规范去掉后,发现在 ...
- Docker小白到实战之开篇概述
前言 "不对啊,在我这运行很正常啊",这句话小伙伴们在前几年应该听得很多:每次一到安装.部署时总有一堆问题,毕竟操作系统版本.软件环境.硬件资源.网络等因素在作怪,此时难免会导致开 ...
- 熬夜肝了一份 C++/Linux 开发学习路线
大家好,我是帅地. 之前写过几篇学习路线的文章 前端开发学习路线 Java 后端开发学习路线 一般开发岗主流的就是 Java 后台开发,前端开发以及 C++ 后台开发,现在 Go 开发也是越来越多了, ...
- vue 快速入门 系列 —— vue-cli 下
其他章节请看: vue 快速入门 系列 Vue CLI 4.x 下 在 vue loader 一文中我们已经学会从零搭建一个简单的,用于单文件组件开发的脚手架:本篇,我们将全面学习 vue-cli 这 ...
- Java EE-下载安装eclipse并设置环境变量的步骤
1.下载eclipse: 官网:https://www.eclipse.org/downloads/ (1)点击链接后显示如图 (2)点击Download Packages 下载安装包,不要点击&qu ...
- 玩转Java8日期工具类-基础
内容基于的是 Java8官方文档,以及Java时间类总结 的总结.BTW:其实具体方法的使用直接在IDEA中看源码更方便直接. 1.老一辈:Java.util.Date Java.sql.Date J ...
- CVE-2021-1732 Windows 本地权限提升漏洞 EXP 下载
漏洞简介 2021年2月10日,微软修复了一个Windows本地权限提升漏洞,漏洞编号为 CVE-2021-1732 ,本地攻击者可以利用该漏洞将权限提升为 System ,目前EXP已公开. 影响范 ...
- Beescms V4.0_R_20160525代码审计笔记
写在前面 什么是报错注入?正常用户访问服务器发送id信息返回正确的id数据.报错注入是想办法构造语句,让错误信息中可以显示数据库的内容:如果能让错误信息中返回数据库中的内容,即实现SQL注入. 复现过 ...
- SQL 练习26
查询 1990 年出生的学生名单 --方式1 SELECT * FROM Student WHERE Sage BETWEEN '1990-01-01' AND '1990-12-31' --方式2 ...
- NOIP 模拟 $32\; \rm Smooth$
题解 \(by\;zj\varphi\) 很简单的贪心题. 开 \(B\) 个队列,每个队列存最后一次乘上的数为当前队列编号的数. 每次去所有队列中队首的最小值,不用开堆,因为开堆用于将所有数排序,但 ...