linux tasklet函数,Linux中断子系统(三)-softirq和tasklet

NR_SOFTIRQS};

/* 软件中断描述符,只包含一个handler函数指针 */struct softirq_action {void (*action)(struct softirq_action *);};/* 软中断描述符表,实际上就是一个全局的数组 */static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

/* CPU软中断状态描述,当某个软中断触发时,__softirq_pending会置位对应的bit */typedef struct {unsigned int __softirq_pending;unsigned int ipi_irqs[NR_IPI];} ____cacheline_aligned irq_cpustat_t;/* 每个CPU都会维护一个状态信息结构 */irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

/* 内核为每个CPU都创建了一个软中断处理内核线程 */DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

来一张图吧:

20962fc56509202ed95c766009a3dffd.png

softirq_vec[] 数组,类比硬件中断描述符表 irq_desc[] ,通过软中断号可以找到对应的 handler 进行处理,比如图中的 tasklet_action 就是一个实际的 handler 函数;

软中断可以在不同的CPU上并行运行,在同一个CPU上只能串行执行;

每个CPU维护 irq_cpustat_t 状态结构,当某个软中断需要进行处理时,会将该结构体中的 __softirq_pending 字段或上 1UL << XXX_SOFTIRQ ;2.2 流程分析 2.2.1 软中断注册

中断处理流程中设备驱动通过 request_irq/request_threaded_irq 接口来注册中断处理函数,而在软中断处理流程中,通过 open_softirq 接口来注册,由于它实在是太简单了,我忍不住想把代码贴上来:

void open_softirq(int nr, void (*action)(struct softirq_action *)){softirq_vec[nr].action = action;}

也就是将软中断描述符表中对应描述符的 handler 函数指针指向对应的函数即可,以便软中断到来时进行回调。

那么,问题来了,什么时候进行软中断函数回调呢?

2.2.2 软中断执行之一:中断处理后

先看第一种情况,用图片来回答问题:

2ffc9da4d7f7b2fa5bb0249f7ff71e40.png

《Linux中断子系统(二)-通用框架处理》 文章中讲述了整个中断处理流程,在接收到中断信号后,处理器进行异常模式切换,并跳转到异常向量表处进行执行,关键的流程为: el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq ;

在 __handle_domain_irq 函数中, irq_enter 和 irq_exit 分别用于来标识进入和离开硬件中断上下文处理,这个从 preempt_count_add/preempt_count_sub 来操作 HARDIRQ_OFFSET 可以看出来,这也对应到了上文中的Context描述图;

在离开硬件中断上下文后,如果 !in_interrupt && local_softirq_pending 为真,则进行软中断处理。这个条件有两个含义:1) !in_interrupt 表明不能处在中断上下文中,这个范围包括 in_nmi 、 in_irq 、 in_softirq(Bottom-half disable) 、 in_serving_softirq ,凡是处于这几种状态下,软中断都不会被执行;2) local_softirq_pending 不为0,表明有软中断处理请求;

软中断执行的入口就是 invoke_softirq ,继续分析一波:

e871c7ceb822921aa81e13c9555790c7.png

invoke_softirq 函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数 threadirqs ),那么直接通过 wakeup_softirqd 唤醒内核线程来执行,否则的话则调用 __do_softirq 函数来处理;

Linux内核会为每个CPU都创建一个内核线程 ksoftirqd ,通过 smpboot_register_percpu_thread 函数来完成,其中当内核线程运行时,在满足条件的情况下会执行 run_ksoftirqd 函数,如果此时有软中断处理请求,调用 __do_softirq 来进行处理;

上图中的逻辑可以看出,最终的核心处理都放置在 __do_softirq 函数中完成:

0998779a03cae6c97182b0406f6d5aed.png

local_softirq_pending 函数用于读取 __softirq_pending 字段,可以类比于设备驱动中的状态寄存器,用于判断是否有软中断处理请求;

软中断处理时会关闭 Bottom-half ,处理完后再打开;

软中断处理时,会打开本地中断,处理完后关闭本地中断 ,这个地方对应到上文中提到的 Top-half 和 Bottom-half 机制,在 Bottom-half 处理的时候,是会将中断打开的,因此也就能继续响应其他中断,这个也就意味着其他中断也能来打断当前的 Bottom-half 处理;

while(softirq_bit = ffs(pending)) ,循环读取状态位,直到处理完每一个软中断请求;

跳出 while 循环之后,再一次判断是否又有新的软中断请求到来(由于它可能被中断打断,也就意味着可能有新的请求到来),有新的请求到来,则有三个条件判断,满足的话跳转到 restart 处执行,否则调用 wakeup_sotfirqd 来唤醒内核线程来处理:time_before(jiffies, MAX_SOFTIRQ_TIME) ,软中断处理时间小于两毫秒;

!need_resched ,当前没有进程调度的请求;

max_restart = MAX_SOFTIRQ_RESTART ,跳转到 restart 循环的次数不大于10次;这三个条件的判断,是基于延迟和公平的考虑,既要保证软中断尽快处理,又不能让软中断处理一直占据系统,正所谓 trade-off 的艺术;

__do_softirq 既然可以在中断处理过程中调用,也可以在 ksoftirqd 中调用,那么 softirq 的执行可能有两种context,插张图吧:

d17a64976dc12f6ada868139110e2fad.png

让我们来思考最后一个问题:硬件中断触发的时候是通过硬件设备的电信号,那么软中断的触发是通过什么呢?答案是通过 raise_softirq 接口:

53c13c8a3ba0ee6507172794f686df49.png

可以在中断处理过程中调用 raise_softirq 来进行软中断处理请求,处理的实际也就是上文中提到过的 irq_exit 退出硬件中断上下文之后再处理;

raise_softirq_irqoff 函数中,最终会调用到 or_softirq_pending ,该函数会去读取本地CPU的 irq_stat 中 __softirq_pending 字段,然后将对应的软中断号给置位,表明有该软中断的处理请求;

raise_softirq_irqoff 函数中,会判断当前的请求的上下文环境,如果不在中断上下文中,就可以通过唤醒内核线程来处理,如果在中断上下文中处理,那就不执行;

多说一句,在软中断整个处理流程中,会经常看到 in_interrupt 的条件判断,这个可以确保软中断在CPU上的串行执行,避免嵌套;2.2.3 软中断执行之二:Bottom-half Enable后

第二种软中断执行的时间点,在 Bottom-half 使能的时候,通常用于并发处理,进程空间上下文中进行调用:

623100b942ddad2494152c82fa0c04cd.png

在讨论并发专题的时候,我们谈到过 Bottom-half 与进程之间能产生资源争夺的情况,如果在软中断和进程之间有临界资源(软中断上下文优先级高于进程上下文),那么可以在进程上下文中调用 local_bh_disable/local_bh_enable 来对临界资源保护;

图中左侧的函数,都是用于打开 Bottom-half 的接口,可以看出是 spin_lock_bh/read_lock_bh/write_lock_bh 等并发处理接口的变种形式调用;

__local_bh_enable_ip 函数中,首先判断调用该本接口时中断是否是关闭的,如果已经关闭了再操作BH接口就会告警;

preempt_count_sub 需要与 preempt_count_add 配套使用,用于操作 thread_info->preempt_count 字段,加与减的值是一致的,而在 __local_bh_enable_ip 接口中,将 cnt 值的减操作分成了两步: preempt_count_sub(cnt-1) 和 preempt_count_dec ,这么做的原因是执行完 preempt_count_sub(cnt-1) 后, thread_info->preempt_count 字段的值保留了1,把抢占给关闭了,当 do_softirq 执行完毕后,再调用 preempt_count_dec 再减去剩下的1,进而打开抢占;

为什么在使能 Bottom-half 时要进行软中断处理呢?在并发处理时,可能已经把 Bottom-half 进行关闭了,如果此时中断来了后,软中断不会被处理,在进程上下文中打开 Bottom-half 时,这时候就会检查是否有软中断处理请求了;3. tasklet

从上文中分析可以看出, tasklet 是软中断的一种类型,那么两者有啥区别呢?先说结论吧:

软中断类型内核中都是静态分配,不支持动态分配,而 tasklet 支持动态和静态分配,也就是驱动程序中能比较方便的进行扩展;

软中断可以在多个CPU上并行运行,因此需要考虑可重入问题,而 tasklet 会绑定在某个CPU上运行,运行完后再解绑,不要求重入问题,当然它的性能也就会下降一些;3.1 数据结构

ea39b9da8262a742da45bfe762f1172e.png

DEFINE_PER_CPU(struct tasklet_head, tasklet_vec) 为每个CPU都分配了 tasklet_head 结构,该结构用来维护 struct tasklet_struct 链表,需要放到该CPU上运行的 tasklet 将会添加到该结构的链表中,内核中为每个CPU维护了两个链表 tasklet_vec 和 tasklet_vec_hi ,对应两个不同的优先级,本文以 tasklet_vec 为例;

struct tasklet_struct 为 tasklet 的抽象,几个关键字段如图所示,通过 next 来链接成链表,通过 state 字段来标识不同的状态以确保能在CPU上串行执行, func 函数指针在调用 task_init 接口时进行初始化,并在最终触发软中断时执行;3.2 流程分析

088ba9e49c1a206796f1fdda51999381.png

tasklet 本质上是一种软中断,所以它的调用流程与上文中讨论的软中断流程是一致的;

调度 tasklet 运行的接口是 tasklet_schedule ,如果 tasklet 没有被调度则进行调度处理,将该 tasklet 添加到CPU对应的链表中,然后调用 raise_softirq_irqoff 来触发软中断执行;

软中断执行的处理函数是 tasklet_action ,这个在 softirq_init 函数中通过 open_softirq 函数进行注册的;

tasklet_action 函数,首先将该CPU上 tasklet_vec 中的链表挪到临时链表 list 中,然后再对这个 list 进行遍历处理,如果满足执行条件则调用 t->func 执行,并 continue 跳转遍历下一个节点。如果不满足执行条件,则继续将该 tasklet 添加回原来的 tasklet_vec 中,并再次触发软中断;3.3 接口

简单贴一下接口吧:

/* 静态分配tasklet */DECLARE_TASKLET(name, func, data)/* 动态分配tasklet */void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

/* 禁止tasklet被执行,本质上是增加tasklet_struct->count值,以便在调度时不满足执行条件 */void tasklet_disable(struct tasklet_struct *t);

/* 使能tasklet,与tasklet_diable对应 */void tasklet_enable(struct tasklet_struct *t);

/* 调度tasklet,通常在设备驱动的中断函数里调用 */void tasklet_schedule(struct tasklet_struct *t);

/* 杀死tasklet,确保不被调度和执行, 主要是设置state状态位 */void tasklet_kill(struct tasklet_struct *t);

收工!

如果觉得文章对您有帮助,那就点个 在看吧,谢谢。 返回搜狐,查看更多

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值