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_SOFTIRQTASKLET_SOFTIRQ安装上函数执行体,分别为tasklet_hi_actiontasklet_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_SOFTIRQTASKLET_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任务被执行完之前,它的的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_disabletasklet_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机制的更多相关文章

  1. linux内核--软中断与tasklet

    硬件中断通常都需要在最短的时间内执行完毕,如果将所有硬件中断相关的处理都放在硬件中断处理程序中,那么就达不到这个目的. 通过linux提供的软中断和tasklet,可以将硬件中断处理程序中可以延迟处理 ...

  2. [Linux内核]软中断、tasklet、工作队列

    转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...

  3. Linux内核实践之tasklet机制【转】

    转自:http://blog.csdn.net/bullbat/article/details/7423321 版权声明:本文为博主原创文章,未经博主允许不得转载. 作者:bullbat 源代码分析与 ...

  4. 嵌入式Linux内核tasklet机制(附实测代码)

    Linux 中断编程分为中断顶半部,中断底半部 中断顶半部: 做紧急,耗时短的事情,同时还启动中断底半部. 中断底半部: 做耗时的事件,这个事件在执行过程可以被中断. 中断底半部实现方法: taskl ...

  5. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  6. Linux中断管理 (2)软中断和tasklet

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  7. Linux软中断、tasklet和工作队列

    Linux内核中的软中断.tasklet和工作队列详解 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来 ...

  8. linux中的tasklet机制【转】

    转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...

  9. Linux中断分层--软中断和tasklet

    1. Linux中断分层 (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断.通常由中断处理程序充当上半部.(一般情况下,上半部不可被打断) (2)下半部:在系统空闲的时候,对上半部“ ...

随机推荐

  1. Netty 源码分析系列(一)Netty 概述

    前言 关于Netty的学习,最近看了不少有关视频和书籍,也收获不少,希望把我知道的分享给你们,一起加油,一起成长.前面我们对 Java IO.BIO.NIO. AIO进行了分析,相关文章链接如下: 深 ...

  2. 腾讯云分布式数据库TDSQL在银行传统核心系统中的应用实践

    本文是腾讯云TDSQL首席架构师张文在腾讯云Techo开发者大会现场的演讲实录,演讲主题是<TDSQL在银行传统核心系统中的应用实践>. 我是TDSQL架构师张文,同时也是TDSQL的开发 ...

  3. XCTF-Web进阶-upload1

    显然是让我们上传文件,思路当然是上传一个木马文件,然后通过蚁剑连接查看目录获取flag. 但是当我们想要上传php文件的时候会出现弹窗,并且连"上传"按钮都被禁用了. ext = ...

  4. fiddler各种颜色锁说明

  5. 【NLP学习其五】模型保存与载入的注意事项(记问题No module named 'model')

    这是一次由于路径问题(找不到模型)引出模型保存问题的记录 最近,我试着把使用GPU训练完成的模型部署至预发布环境时出现了一个错误,以下是log节选 unpickler.load() ModuleNot ...

  6. Create Shortcut to Get Jar File Meta Information

    You have to get meta information of cobertura.jar with command "unzip -q -c cobertura.jar META- ...

  7. Vue-cli4 唤醒摄像头扫描二维码

    <template> <div class="scan"> <div id="bcid"> <div id=" ...

  8. 1002 A+B for Polynomials (25分) 格式错误

    算法笔记上能踩的坑都踩了. #include<iostream> using namespace std; float a[1001];//至少1000个位置 int main(){ in ...

  9. html 去除重复边框

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...

  10. iNeuOS工业互网平台,在纸业领域的成功应用案例

    目       录 1.      项目背景... 2 2.      项目基本情况... 3 3.      概念解释... 5 1.   项目背景 最终用户是全国第5大纸业集团之一,年浆纸产能40 ...