Linux kernel的中断子系统之(九):tasklet

返回目录:《ARM-Linux中断系统》。

总结:

二介绍了tasklet存在的意义。

三介绍了通过tasklet_struct来抽想一个tasklet,每个CPU维护一个tasklet链表tasklet_vec/tasklet_hi_vec,然后介绍了如何定一个一个tasklet(静态/动态),以及如何调度一个tasklet,什么时候tasklet回调函数会被执行。

原文地址:《linux kernel的中断子系统之(九):tasklet

 

一、前言

对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),属于不那么紧急需要处理的事情被推迟执行,我们称之deferable task,或者叫做bottom half,。具体如何推迟执行分成下面几种情况:

1、推迟到top half执行完毕

2、推迟到某个指定的时间片(例如40ms)之后执行

3、推迟到某个内核线程被调度的时候执行

对于第一种情况,内核中的机制包括softirq机制和tasklet机制。第二种情况是属于softirq机制的一种应用场景(timer类型的softirq),在本站的时间子系统的系列文档中会描述。第三种情况主要包括threaded irq handler以及通用的workqueue机制,当然也包括自己创建该驱动专属kernel thread(不推荐使用)。本文主要描述tasklet这种机制,第二章描述一些背景知识和和tasklet的思考,第三章结合代码描述tasklet的原理。

注:本文中的linux kernel的版本是4.0

二、为什么需要tasklet?

1、基本的思考

我们的驱动程序或者内核模块真的需要tasklet吗?每个人都有自己的看法。我们先抛开linux kernel中的机制,首先进行一番逻辑思考。

将中断处理分成top half(cpu和外设之间的交互,获取状态,ack状态,收发数据等)和bottom half(后段的数据处理)已经深入人心,对于任何的OS都一样,将不那么紧急的事情推迟到bottom half中执行是OK的,具体如何推迟执行分成两种类型:有具体时间要求的(对应linux kernel中的低精度timer和高精度timer)和没有具体时间要求的。对于没有具体时间要求的又可以分成两种:

(1)越快越好型,这种实际上是有性能要求的,除了中断top half可以抢占其执行,其他的进程上下文(无论该进程的优先级多么的高)是不会影响其执行的,一言以蔽之,在不影响中断延迟的情况下,OS会尽快处理。

(2)随遇而安型。这种属于那种没有性能需求的,其调度执行依赖系统的调度器。

本质上讲,越快越好型的bottom half不应该太多,而且tasklet的callback函数不能执行时间过长,否则会产生进程调度延迟过大的现象,甚至是非常长而且不确定的延迟,对real time的系统会产生很坏的影响。

2、对linux中的bottom half机制的思考

在linux kernel中,“越快越好型”有两种,softirq和tasklet,“随遇而安型”也有两种,workqueue和threaded irq handler。“越快越好型”能否只留下一个softirq呢?对于崇尚简单就是美的程序员当然希望如此。为了回答这个问题,我们先看看tasklet对于softirq而言有哪些好处:

(1)tasklet可以动态分配,也可以静态分配,数量不限

(2)同一种tasklet在多个cpu上也不会并行执行,这使得程序员在撰写tasklet function的时候比较方便,减少了对并发的考虑(当然损失了性能)。

对于第一种好处,其实也就是为乱用tasklet打开了方便之门,很多撰写驱动的软件工程师不会仔细考量其driver是否有性能需求就直接使用了tasklet机制。对于第二种好处,本身考虑并发就是软件工程师的职责。因此,看起来tasklet并没有引入特别的好处,而且和softirq一样,都不能sleep,限制了handler撰写的方便性,看起来其实并没有存在的必要。在4.0 kernel的代码中,grep一下tasklet的使用,实际上是一个很长的列表,只要对这些使用进行简单的归类就可以删除对tasklet的使用。对于那些有性能需求的,可以考虑并入softirq,其他的可以考虑使用workqueue来取代。Steven Rostedt试图进行这方面的尝试(http://lwn.net/Articles/239484/),不过这个patch始终未能进入main line。

三、tasklet的基本原理

1、如何抽象一个tasklet

内核中用下面的数据结构来表示tasklet:

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

每个cpu都会维护一个链表,将本cpu需要处理的tasklet管理起来,next这个成员指向了该链表中的下一个tasklet。func和data成员描述了该tasklet的callback函数,func是调用函数,data是传递给func的参数。state成员表示该tasklet的状态,TASKLET_STATE_SCHED表示该tasklet以及被调度到某个CPU上执行TASKLET_STATE_RUN表示该tasklet正在某个cpu上执行。count成员是和enable或者disable该tasklet的状态相关,如果count等于0那么该tasklet是处于enable的,如果大于0,表示该tasklet是disable的。在softirq文档中,我们知道local_bh_disable/enable函数就是用来disable/enable bottom half的,这里就包括softirq和tasklet。但是,有的时候内核同步的场景不需disable所有的softirq和tasklet,而仅仅是disable该tasklet,这时候,tasklet_disable和tasklet_enable就派上用场了。

static inline void tasklet_disable(struct tasklet_struct *t)
{
    tasklet_disable_nosync(t);-------给tasklet的count加一
    tasklet_unlock_wait(t);-----如果该tasklet处于running状态,那么需要等到该tasklet执行完毕
    smp_mb();
}

static inline void tasklet_enable(struct tasklet_struct *t)
{
    smp_mb__before_atomic();
    atomic_dec(&t->count);-------给tasklet的count减一
}

tasklet_disable和tasklet_enable支持嵌套,但是需要成对使用。

2、系统如何管理tasklet?

系统中的每个cpu都会维护一个tasklet的链表,定义如下:

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

linux kernel中,和tasklet相关的softirq有两项,HI_SOFTIRQ用于高优先级的tasklet,TASKLET_SOFTIRQ用于普通的tasklet。对于softirq而言,优先级就是出现在softirq pending register(__softirq_pending)中的先后顺序,位于bit 0拥有最高的优先级,也就是说,如果有多个不同类型的softirq同时触发,那么执行的先后顺序依赖在softirq pending register的位置kernel总是从右向左依次判断是否置位,如果置位则执行。HI_SOFTIRQ占据了bit 0,其优先级甚至高过timer,需要慎用(实际上,我grep了内核代码,似乎没有发现对HI_SOFTIRQ的使用)。当然HI_SOFTIRQ和TASKLET_SOFTIRQ的机理是一样的,因此本文只讨论TASKLET_SOFTIRQ,大家可以举一反三。

3、如何定义一个tasklet?

你可以用下面的宏定义来静态定义tasklet:

#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 }

这两个宏都可以静态定义一个struct tasklet_struct的变量,只不过初始化后的tasklet一个是处于eable状态,一个处于disable状态的。当然,也可以动态分配tasklet,然后调用tasklet_init来初始化该tasklet

4、如何调度一个tasklet

为了调度一个tasklet执行,我们可以使用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执行(也可能来自多个cpu core),不过实际上该tasklet只会一次挂入首次调度到的那个cpu的tasklet链表,也就是说,即便是多次调用tasklet_schedule实际上tasklet只会挂入一个指定CPU的tasklet队列中(而且只会挂入一次),也就是说只会调度一次执行。这是通过TASKLET_STATE_SCHED这个flag来完成的,我们可以用下面的图片来描述:

tasklet

我们假设HW block A的驱动使用的tasklet机制并且在中断handler(top half)中将静态定义的tasklet(这个tasklet是各个cpu共享的,不是per cpu的)调度执行(也就是调用tasklet_schedule函数)。当HW block A检测到硬件的动作(例如接收FIFO中数据达到半满)就会触发IRQ line上的电平或者边缘信号GIC检测到该信号会将该中断分发给某个CPU执行其top half handler,我们假设这次是cpu0,因此该driver的tasklet被挂入CPU0对应的tasklet链表(tasklet_vec)并将state的状态设定为TASKLET_STATE_SCHED。HW block A的驱动中的tasklet虽已调度,但是没有执行,如果这时候,硬件又一次触发中断并在cpu1上执行,虽然tasklet_schedule函数被再次调用,但是由于TASKLET_STATE_SCHED已经设定,因此不会将HW block A的驱动中的这个tasklet挂入cpu1的tasklet链表中。

下面我们再仔细研究一下底层的__tasklet_schedule函数:

void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;

    local_irq_save(flags);-------------------(1)
    t->next = NULL;---------------------(2)
    *__this_cpu_read(tasklet_vec.tail) = t;
    __this_cpu_write(tasklet_vec.tail, &(t->next));
    raise_softirq_irqoff(TASKLET_SOFTIRQ);----------(3)
    local_irq_restore(flags);
}

(1)下面的链表操作是per-cpu的,因此这里禁止本地中断就可以拦截所有的并发。

(2)这里的三行代码就是将一个tasklet挂入链表的尾部

(3)raise TASKLET_SOFTIRQ类型的softirq。

5、在什么时机会执行tasklet?

上面描述了tasklet的调度,当然调度tasklet不等于执行tasklet系统会在适合的时间点执行tasklet callback function。由于tasklet是基于softirq的,因此,我们首先总结一下softirq的执行场景:

(1)在中断返回用户空间(进程上下文)的时候,如果有pending的softirq,那么将执行该softirq的处理函数。这里限定了中断返回用户空间也就是意味着限制了下面两个场景的softirq被触发执行:

    (a)中断返回hard interrupt context,也就是中断嵌套的场景

    (b)中断返回software interrupt context,也就是中断抢占软中断上下文的场景

(2)上面的描述缺少了一种场景:中断返回内核态的进程上下文的场景,这里我们需要详细说明。进程上下文中调用local_bh_enable的时候,如果有pending的softirq,那么将执行该softirq的处理函数。由于内核同步的要求,进程上下文中有可能会调用local_bh_enable/disable来保护临界区。在临界区代码执行过程中,中断随时会到来,抢占该进程(内核态)的执行(注意:这里只是disable了bottom half,没有禁止中断)。在这种情况下,中断返回的时候是否会执行softirq handler呢?当然不会,我们disable了bottom half的执行,也就是意味着不能执行softirq handler,但是本质上bottom half应该比进程上下文有更高的优先级,一旦条件允许,要立刻抢占进程上下文的执行,因此,当立刻离开临界区,调用local_bh_enable的时候,会检查softirq pending,如果bottom half处于enable的状态,pending的softirq handler会被执行

(3)系统太繁忙了,不过的产生中断,raise softirq,由于bottom half的优先级高,从而导致进程无法调度执行。这种情况下,softirq会推迟到softirqd这个内核线程中去执行

对于TASKLET_SOFTIRQ类型的softirq,其handler是tasklet_action,我们来看看各个tasklet是如何执行的:

static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    local_irq_disable();--------------------------(1)
    list = __this_cpu_read(tasklet_vec.head);
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
    local_irq_enable();

    while (list) {---------遍历tasklet链表
        struct tasklet_struct *t = list;

        list = list->next;

        if (tasklet_trylock(t)) {-----------------------(2)
            if (!atomic_read(&t->count)) {------------------(3)
                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                    BUG();
                t->func(t->data);
                tasklet_unlock(t);
                continue;-----处理下一个tasklet
            }
            tasklet_unlock(t);----清除TASKLET_STATE_RUN标记
        }

        local_irq_disable();-----------------------(4)
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        __raise_softirq_irqoff(TASKLET_SOFTIRQ); ------再次触发softirq,等待下一个执行时机
        local_irq_enable();
    }
}

(1)从本cpu的tasklet链表中取出全部的tasklet,保存在list这个临时变量中,同时重新初始化本cpu的tasklet链表,使该链表为空。由于bottom half是开中断执行的,因此在操作tasklet链表的时候需要使用关中断保护

(2)tasklet_trylock主要是用来设定该tasklet的state为TASKLET_STATE_RUN,同时判断该tasklet是否已经处于执行状态,这个状态很重要,它决定了后续的代码逻辑。

static inline int tasklet_trylock(struct tasklet_struct *t)
{
    return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

你也许会奇怪:为何这里从tasklet的链表中摘下一个本cpu要处理的tasklet list,而这个list中的tasklet已经处于running状态了,会有这种情况吗?会的,我们再次回到上面的那个软硬件结构图。同样的,HW block A的驱动使用的tasklet机制并且在中断handler(top half)中将静态定义的tasklet 调度执行。HW block A的硬件中断首先送达cpu0处理,因此该driver的tasklet被挂入CPU0对应的tasklet链表并在适当的时间点上开始执行该tasklet。这时候,cpu0的硬件中断又来了,该driver的tasklet callback function被抢占,虽然tasklet仍然处于running状态。与此同时,HW block A硬件又一次触发中断并在cpu1上执行,这时候,该driver的tasklet处于running状态,并且TASKLET_STATE_SCHED已经被清除,因此,调用tasklet_schedule函数将会使得该driver的tasklet挂入cpu1的tasklet链表中。由于cpu0在处理其他硬件中断,因此,cpu1的tasklet后发先至,进入tasklet_action函数调用,这时候,当从cpu1的tasklet摘取所有需要处理的tasklet链表中,HW block A对应的tasklet实际上已经是在cpu0上处于执行状态了。

我们在设计tasklet的时候就规定,同一种类型的tasklet只能在一个cpu上执行,因此tasklet_trylock就是起这个作用的。

(3)检查该tasklet是否处于enable状态,如果是,说明该tasklet可以真正进入执行状态了。主要的动作就是清除TASKLET_STATE_SCHED状态,执行tasklet callback function。

(4)如果该tasklet已经在别的cpu上执行了,那么我们将其挂入该cpu的tasklet链表的尾部,这样,在下一个tasklet执行时机到来的时候,kernel会再次尝试执行该tasklet,在这个时间点,也许其他cpu上的该tasklet已经执行完毕了。通过这样代码逻辑,保证了特定的tasklet只会在一个cpu上执行,不会在多个cpu上并发。

原创文章,转发请注明出处。蜗窝科技

转载于:https://www.cnblogs.com/arnoldlu/p/7599602.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/253295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能音箱 之 功放介绍

基本分类 功率放大器分甲类功放(A 类),乙类(B 类),甲乙类(AB 类)和丁类(D 类); A 类 指在信号的整个周期内,放大器的任何功率输出…

create_workqueue和create_singlethread_workqueue【转】

本文转载自:http://bgutech.blog.163.com/blog/static/18261124320116181119889/ 1. 什么是workqueueLinux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线…

vue学习之路.02

2019独角兽企业重金招聘Python工程师标准>>> 第一个vue项目 1.创建 vue init webpack app01 2.安装依赖 cd app01 npm install 3.构建 npm run dev 启动本机的8080端口 或 …

解析电子墨水屏技术(工作原理与LCD的区别)

阅读电子书早已成为大家生活中一部分,方便轻巧的电子版书籍更便于携带,而电子阅读器也不仅仅局限于电脑、手机等传统设备,新兴的电子书阅读器渐渐为我们所接受。E-ink电子墨水技术就是现在最著名的产品之一,他的出现让电子书阅读器…

入门视频采集与处理(BT656简介) 转

凡是做模拟信号采集的,很少不涉及BT.656标准的,因为常见的模拟视频信号采集芯片都支持输出BT.656的数字信号,那么,BT.656到底是何种格式呢?本文将主要介绍 标准的 8bit BT656(4:2:2)YCbCr SDTV&…

眼图(Eye Diagram)与数字信号测试

问题: 什么是眼图?它用在什么场合?反映了波形的什么信息?NI相应的解决方案是怎样的? 解答: 眼图(Eye Diagram)可以显示出数字信号的传输质量,经常用于需要对电子设备、芯片中串行数字信号或者…

2018年智能音箱对比

众所周知,2014年底,电商巨头亚马逊推出智能音箱产品Echo之后,引起市场的强烈反响。随后、谷歌、微软、苹果均开始布局智能音箱市场,国内公司以玲珑科技打头阵。2017年国内公司纷纷发布智能音箱,被称为智能音箱元年。经…

LVDS通信接口详细介绍

1. 概述 LVDS Low-Voltage Differential Signaling 低电压差分信号,属于平衡传输信号。 这种技术的核心是采用极低的电压摆幅高速差动传输数据,从而有以下特点: 低功耗---低误码率---低串扰---低抖动---低辐射 良好的信号完整性。 推…

ThinkPHP简单的验证码实现

ThinkPHP简单的验证码实现 写一个最简单的TP验证码。 写Controller 首先在Controller/IndexController.class.php&#xff08;简称Index&#xff09;文件中编辑&#xff1a; 1 <?php 2 namespace Home\Controller; 3 use Think\Controller; 4 use Think\Verify;//这个类…

Celery框架简单实例

Python 中可以使用Celery框架 Celery框架是提供异步任务处理的框架&#xff0c;有两种用法&#xff0c;一种&#xff1a;应用程式发布任务消息&#xff0c;后台Worker监听执行&#xff0c;好处在于不影响应用程序继续执行。第二种&#xff0c;设置定时执行&#xff08;这边没测…

沸腾新十年 | 中国语音产业江湖和科大讯飞的前半生

沸腾新十年 | 中国语音产业江湖和科大讯飞的前半生 2019-01-09 来源:左林右狸 写在前面&#xff1a; 这是《沸腾新十年》的第十一篇剧透文&#xff0c;也是2019年的第一篇剧透文&#xff0c;从确认选题到采编到反复修改&#xff0c;这篇稿子操作时间前后历经近半年。究其原…

[oracle]分区表学习

&#xff08;一&#xff09;什么是分区 所谓分区&#xff0c;就是将一张巨型表或巨型索引分成若干个独立的组成部分进行存储和管理&#xff0c;每一个相对小的&#xff0c;可独立管理的部分&#xff0c;称为分区。 &#xff08;二&#xff09;分区的优势 提高数据可管理性。对表…

Matcher类的简单使用

今天工作时遇到一个问题&#xff0c; 用正则处理html标签时不知该如何下手。还好有Matcher帮助解决了问题。需求如下&#xff1a;例如有如下html文章内容&#xff1a;<p><a href"www.baidu.com">百度的链接</a>; 这是一个百度的链接。 <a href&…

USB 摄像头成熟方案介绍

UVC&#xff0c;全称为&#xff1a;USB video class 或USB video device class。是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义 的协议标准&#xff0c;目前已成为USB org标准之一。 如今的主流操作系统(如Windows XP SP2 and later, Linux 2.4.6 and later…

JS练习:商品的左右选择

代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>商品的左右选择</title><!--步骤分析1. 确定事件: 点击事件 :onclick事件2. 事件要触发函数 selectOne3. selectOne要做一些操作(将左边选中的元…

Python 购物车

购物车需求&#xff1a; 可购买的商品信息显示 显示购物车内的商品信息、数量 购物车内的商品数量进行增加、减少 用户余额的充值 用户购买完成进行结账&#xff0c;将最终余额回写到用户文件中   流程图&#xff1a; 代码&#xff1a; 1、主文件 def login():# 验证用户帐号…

认对画对MOS管

MOS管我们在设计电路中经常用的一种无源器件。 首先介绍下&#xff0c;在原理图和PCB以及实物PCBA中如何辨别各种MOS管&#xff0c;作为应用好的先决条件&#xff0c;必须认对画对管子。 1. MOS管的GSD三极在原理图和PCB上怎么判定&#xff1a; G极(gate)—栅极&#xff0c;原…

Jmeter之BeanShell

在Jmeter中各种分类组件中都有相应的BeanShell组件&#xff0c;这里简单的说明一下Beanshell的使用。 一、概念 BeanShell是一种符合Java语法的脚本语言&#xff0c;也有自己的一些特定语法 二、内置变量 Jmeter在它的Beanshell中内置了变量&#xff0c;用户可以通过这些变量与…