浅析linux内核中断子系统—softirq和tasklet

2023-10-09 15:10

本文主要是介绍浅析linux内核中断子系统—softirq和tasklet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 概述

中断子系统中有一个重要的设计机制,那就是Top-half和Bottom-half,将紧急的工作放置在Top-half中来处理,而将耗时的工作放置在Bottom-half中来处理,这样确保Top-half能尽快完成处理,那么为什么需要这么设计呢?看一张图就明白了:

  • ARM处理器在进行中断处理时,处理器进行异常模式切换,此时会将中断进行关闭,处理完成后再将中断打开;
  • 如果中断不分上下半部处理,那么意味着只有等上一个中断完成处理后才会打开中断,下一个中断才能得到响应。当某个中断处理处理时间较长时,很有可能就会造成其他中断丢失而无法响应,这个显然是难以接受的,比如典型的时钟中断,作为系统的脉搏,它的响应就需要得到保障;
  • 中断分成上下半部处理可以提高中断的响应能力,在上半部处理完成后便将中断打开(通常上半部处理越快越好),这样就可以响应其他中断了,等到中断退出的时候再进行下半部的处理;
  • 中断的Bottom-half机制,包括了softirqtaskletworkqueue、以及前文中提到过的中断线程化处理等,其中tasklet又是基于softirq来实现的,这也是本文讨论的主题;

在中断处理过程中,离不开各种上下文的讨论,了解不同上下文的区分有助于中断处理的理解,所以,还是来一张老图吧:

  • task_struct结构体中的thread_info.preempt_count用于记录当前任务所处的context状态;
  • PREEMPT_BITS:用于记录禁止抢占的次数,禁止抢占一次该值就加1,使能抢占该值就减1;
  • SOFTIRQ_BITS:用于同步处理,关掉下半部的时候加1,打开下半部的时候减1;
  • HARDIRQ_BITS:用于表示处于硬件中断上下文中;

前戏结束了,直奔主题吧。

【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

  学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

2 softirq

2.1 初始化

softirq不支持动态分配,Linux kernel提供了静态分配,关键的结构体描述如下,可以类比硬件中断来理解:

/* 支持的软中断类型,可以认为是软中断号, 其中从上到下优先级递减 */
enum
{HI_SOFTIRQ=0,       /* 最高优先级软中断 */TIMER_SOFTIRQ,      /* Timer定时器软中断 */NET_TX_SOFTIRQ,     /* 发送网络数据包软中断 */NET_RX_SOFTIRQ,     /* 接收网络数据包软中断 */BLOCK_SOFTIRQ,      /* 块设备软中断 */IRQ_POLL_SOFTIRQ,   /* 块设备软中断 */TASKLET_SOFTIRQ,    /* tasklet软中断 */SCHED_SOFTIRQ,      /* 进程调度及负载均衡的软中断 */HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq, RCU相关的软中断 */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);

  • 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 软中断执行之一:中断处理后

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

  • 在接收到中断信号后,处理器进行异常模式切换,并跳转到异常向量表处进行执行,关键的流程为:
el0_irq
-->irq_handler-->handle_arch_irq(gic->handle_irq)-->handle_domain_irq-->__handle_domain_irq;
  • __handle_domain_irq函数中,irq_enterirq_exit分别用于来标识进入和离开硬件中断上下文处理,这个从preempt_count_add/preempt_count_sub来操作HARDIRQ_OFFSET可以看出来,这也对应到了上文中的Context描述图;
  • 在离开硬件中断上下文后,如果!in_interrupt() && local_softirq_pending为真,则进行软中断处理。这个条件有两个含义:1)!in_interrupt()表明不能处在中断上下文中,这个范围包括in_nmiin_irqin_softirq(Bottom-half disable)in_serving_softirq,凡是处于这几种状态下,软中断都不会被执行;2)local_softirq_pending不为0,表明有软中断处理请求;

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

  • invoke_softirq函数中,根据中断处理是否线程化进行分类处理,如果中断已经进行了强制线程化处理(中断强制线程化,需要在启动的时候传入参数 threadirqs),那么直接通过wakeup_softirqd 唤醒内核线程来执行,否则的话则调用 __do_softirq 函数来处理;
__read_mostly bool force_irqthreads;
static int __init setup_forced_irqthreads(char *arg){force_irqthreads = true;return 0;
}
early_param("threadirqs", setup_forced_irqthreads);
  • Linux内核会为每个CPU都创建一个内核线程ksoftirqd,通过smpboot_register_percpu_thread函数来完成,其中当内核线程运行时,在满足条件的情况下会执行run_ksoftirqd函数,如果此时有软中断处理请求,调用__do_softirq来进行处理;

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

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

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

软中断处理时,会打开本地中断,处理完后关闭本地中断,这个地方对应到上文中提到的Top-halfBottom-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,插张图吧:

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

  • 可以在中断处理过程中调用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使能的时候,通常用于并发处理,进程空间上下文中进行调用:

  • 在讨论并发专题的时候,我们谈到过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时,这时候就会检查是否有软中断处理请求了;

2.2.4 软中断堆栈调用实例

RT版本:硬件中断线程化
[    6.120196] st_gmac a5014000.ethernet: Link is Up - 1000/Full 
[    6.120244] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[    6.294399] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=0,current->comm=irq/43-eth-tx
[    6.294409] CPU: 3 PID: 961 Comm: irq/43-eth-tx Tainted: P                4.14.87 #12
[    6.294411] Hardware name: Hobot J3 SOC MP DVB (DT)
[    6.294413] Call trace:
[    6.294423] [<ffffff8008008590>] dump_backtrace+0x0/0x400
[    6.294428] [<ffffff80080089a4>] show_stack+0x14/0x20
[    6.294433] [<ffffff80087c3c8c>] dump_stack+0x8c/0xb0
[    6.294440] [<ffffff80084b537c>] xj3_poll+0xbf4/0xc08
[    6.294446] [<ffffff800864e4d4>] net_rx_action+0x34c/0x3c0
[    6.294450] [<ffffff800802582c>] do_current_softirqs+0x1dc/0x290
[    6.294454] [<ffffff8008025934>] __local_bh_enable+0x54/0x60
[    6.294459] [<ffffff800806bc7c>] irq_forced_thread_fn+0x6c/0xb8
[    6.294462] [<ffffff800806bfb0>] irq_thread+0x110/0x1a0
[    6.294467] [<ffffff800803f42c>] kthread+0xfc/0x128
[    6.294471] [<ffffff8008003b60>] ret_from_fork+0x10/0x18
[    6.399246] st_gmac a5014000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/tx
[    6.493225] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=1,current->comm=irq/42-eth-rx
[    6.493234] CPU: 3 PID: 962 Comm: irq/42-eth-rx Tainted: P                4.14.87 #12
[    6.493236] Hardware name: Hobot J3 SOC MP DVB (DT)
[    6.493237] Call trace:
[    6.493245] [<ffffff8008008590>] dump_backtrace+0x0/0x400
[    6.493249] [<ffffff80080089a4>] show_stack+0x14/0x20
[    6.493254] [<ffffff80087c3c8c>] dump_stack+0x8c/0xb0
[    6.493260] [<ffffff80084b537c>] xj3_poll+0xbf4/0xc08
[    6.493265] [<ffffff800864e4d4>] net_rx_action+0x34c/0x3c0
[    6.493269] [<ffffff800802582c>] do_current_softirqs+0x1dc/0x290
[    6.493272] [<ffffff8008025934>] __local_bh_enable+0x54/0x60
[    6.493277] [<ffffff800806bc7c>] irq_forced_thread_fn+0x6c/0xb8
[    6.493281] [<ffffff800806bfb0>] irq_thread+0x110/0x1a0
[    6.493285] [<ffffff800803f42c>] kthread+0xfc/0x128
[    6.493289] [<ffffff8008003b60>] ret_from_fork+0x10/0x18
[    7.139048] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=2,current->comm=irq/42-eth-rx
[    7.139057] CPU: 3 PID: 962 Comm: irq/42-eth-rx Tainted: P                4.14.87 #12
[    7.139059] Hardware name: Hobot J3 SOC MP DVB (DT)
[    7.139061] Call trace:
[    7.139072] [<ffffff8008008590>] dump_backtrace+0x0/0x400
[    7.139076] [<ffffff80080089a4>] show_stack+0x14/0x20
[    7.139081] [<ffffff80087c3c8c>] dump_stack+0x8c/0xb0
[    7.139088] [<ffffff80084b537c>] xj3_poll+0xbf4/0xc08
[    7.139094] [<ffffff800864e4d4>] net_rx_action+0x34c/0x3c0
[    7.139099] [<ffffff800802582c>] do_current_softirqs+0x1dc/0x290
[    7.139103] [<ffffff8008025934>] __local_bh_enable+0x54/0x60
[    7.139108] [<ffffff800806bc7c>] irq_forced_thread_fn+0x6c/0xb8
[    7.139111] [<ffffff800806bfb0>] irq_thread+0x110/0x1a0
[    7.139116] [<ffffff800803f42c>] kthread+0xfc/0x128
[    7.139120] [<ffffff8008003b60>] ret_from_fork+0x10/0x18
[    7.139151] arp: #### /home/users/quan01.wang/dev_xj3_1029/kernel/net/ipv4/arp.c arp_process 825,dev_type=1,ar_op=256,ar_pro=8
[    7.140715] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=3,current->comm=irq/42-eth-rx
[    7.140722] CPU: 3 PID: 962 Comm: irq/42-eth-rx Tainted: P                4.14.87 #12
[    7.140723] Hardware name: Hobot J3 SOC MP DVB (DT)
[    7.140725] Call trace:
[    7.140730] [<ffffff8008008590>] dump_backtrace+0x0/0x400
[    7.140734] [<ffffff80080089a4>] show_stack+0x14/0x20
[    7.140738] [<ffffff80087c3c8c>] dump_stack+0x8c/0xb0
[    7.140743] [<ffffff80084b537c>] xj3_poll+0xbf4/0xc08
[    7.140748] [<ffffff800864e4d4>] net_rx_action+0x34c/0x3c0
[    7.140751] [<ffffff800802582c>] do_current_softirqs+0x1dc/0x290
[    7.140755] [<ffffff8008025934>] __local_bh_enable+0x54/0x60
[    7.140759] [<ffffff800806bc7c>] irq_forced_thread_fn+0x6c/0xb8
[    7.140762] [<ffffff800806bfb0>] irq_thread+0x110/0x1a0
[    7.140766] [<ffffff800803f42c>] kthread+0xfc/0x128
[    7.140770] [<ffffff8008003b60>] ret_from_fork+0x10/0x18
[    7.229438] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=4,current->comm=irq/42-eth-rx
[    7.229447] CPU: 3 PID: 962 Comm: irq/42-eth-rx Tainted: P                4.14.87 #12
[    7.229449] Hardware name: Hobot J3 SOC MP DVB (DT)
[    7.229450] Call trace:
[    7.229456] [<ffffff8008008590>] dump_backtrace+0x0/0x400
[    7.229461] [<ffffff80080089a4>] show_stack+0x14/0x20
[    7.229465] [<ffffff80087c3c8c>] dump_stack+0x8c/0xb0
[    7.229470] [<ffffff80084b537c>] xj3_poll+0xbf4/0xc08
[    7.229475] [<ffffff800864e4d4>] net_rx_action+0x34c/0x3c0
[    7.229478] [<ffffff800802582c>] do_current_softirqs+0x1dc/0x290
[    7.229482] [<ffffff8008025934>] __local_bh_enable+0x54/0x60
[    7.229486] [<ffffff800806bc7c>] irq_forced_thread_fn+0x6c/0xb8
[    7.229490] [<ffffff800806bfb0>] irq_thread+0x110/0x1a0
[    7.229494] [<ffffff800803f42c>] kthread+0xfc/0x128
[    7.229497] [<ffffff8008003b60>] ret_from_fork+0x10/0x18
root@j3dvbj3-hynix2G-3200:~# 
root@j3dvbj3-hynix2G-3200:~# [    8.144798] arp: #### /home/users/quan01.wang/dev_xj3_1029/kernel/net/ipv4/arp.c arp_process 825,dev_type=1,ar_op=256,ar_pro=8root@j3dvbj3-hynix2G-3200:~# 
root@j3dvbj3-hynix2G-3200:~# ps  | grep eth960 root         0 SW   [irq/44-eth%d]961 root         0 SW   [irq/43-eth-tx]962 root         0 SW   [irq/42-eth-rx]1935 root      3284 S    grep eth非RT版本: 
[    4.389686] st_gmac a5014000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/tx
[    4.392887] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=0,current->comm=ksoftirqd/0
[    4.392893] CPU: 0 PID: 7 Comm: ksoftirqd/0 Tainted: P                4.14.87 #13
[    4.392895] Hardware name: Hobot J3 SOC MP DVB (DT)
[    4.392897] Call trace:
[    4.392907] [<ffffff8008008ae0>] dump_backtrace+0x0/0x3f0
[    4.392912] [<ffffff8008008ee4>] show_stack+0x14/0x20
[    4.392917] [<ffffff80087a8b18>] dump_stack+0x98/0xb8
[    4.392923] [<ffffff80084a442c>] xj3_poll+0xbec/0xc00
[    4.392929] [<ffffff800863ce60>] net_rx_action+0x358/0x3f0
[    4.392933] [<ffffff800800102c>] __do_softirq+0x134/0x254
[    4.392937] [<ffffff8008025a5c>] run_ksoftirqd+0x44/0x58
[    4.392941] [<ffffff80080423b8>] smpboot_thread_fn+0x198/0x1a0
[    4.392945] [<ffffff800803e64c>] kthread+0xfc/0x128
[    4.392949] [<ffffff80080041d0>] ret_from_fork+0x10/0x18
[    4.401263] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=1,current->comm=swapper/0
[    4.401267] CPU: 0 PID: 0 Comm: swapper/0 Tainted: P                4.14.87 #13
[    4.401268] Hardware name: Hobot J3 SOC MP DVB (DT)
[    4.401270] Call trace:
[    4.401275] [<ffffff8008008ae0>] dump_backtrace+0x0/0x3f0
[    4.401280] [<ffffff8008008ee4>] show_stack+0x14/0x20
[    4.401283] [<ffffff80087a8b18>] dump_stack+0x98/0xb8
[    4.401288] [<ffffff80084a442c>] xj3_poll+0xbec/0xc00
[    4.401292] [<ffffff800863ce60>] net_rx_action+0x358/0x3f0
[    4.401296] [<ffffff800800102c>] __do_softirq+0x134/0x254
[    4.401299] [<ffffff8008026070>] irq_exit+0xd8/0x108
[    4.401304] [<ffffff8008066c30>] __handle_domain_irq+0x60/0xb8
[    4.401307] [<ffffff8008000d58>] gic_handle_irq+0x58/0xb0
[    4.401310] Exception stack(0xffffff8008ab3de0 to 0xffffff8008ab3f20)
[    4.401314] 3de0: 0000000000000020 000000407648b000 0000000000000000 ffffff8008a9e810
[    4.401318] 3e00: ffffff8008a9e7d8 ffffff8008ab3f10 000000407648b000 ffffffc07c459600
[    4.401322] 3e20: ffffff8008ac2e90 ffffff8008ab3e90 00000000000008b0 0000000000000000
[    4.401326] 3e40: 0000000000000000 0000000000000000 00000000fffffff0 00000000fffffffd
[    4.401330] 3e60: 0000000059979b3b 00000000cf2798dc 0000000000000060 ffffff8008a9c000
[    4.401334] 3e80: ffffff8008ab8a2c ffffff8008ab89a0 ffffff8008ac2580 ffffff8008ac2580
[    4.401338] 3ea0: ffffff8008a6e028 0000000034d5d915 0000000004001e9c 0000000000000400
[    4.401342] 3ec0: 0000000000c20018 ffffff8008ab3f20 ffffff80080048ac ffffff8008ab3f20
[    4.401346] 3ee0: ffffff80080048b0 0000000060000145 0000000034d5d915 0000000004001e9c
[    4.401350] 3f00: ffffffffffffffff ffffff8008059004 ffffff8008ab3f20 ffffff80080048b0
[    4.401353] [<ffffff8008002830>] el1_irq+0xb0/0x140
[    4.401357] [<ffffff80080048b0>] arch_cpu_idle+0x10/0x18
[    4.401363] [<ffffff8008059038>] do_idle+0xd8/0x118
[    4.401367] [<ffffff8008059158>] cpu_startup_entry+0x20/0x28
[    4.401372] [<ffffff80087b94fc>] rest_init+0xac/0xb8
[    4.401376] [<ffffff8008a20b38>] start_kernel+0x340/0x354
[    4.410862] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=2,current->comm=swapper/0
[    4.410866] CPU: 0 PID: 0 Comm: swapper/0 Tainted: P                4.14.87 #13
[    4.410868] Hardware name: Hobot J3 SOC MP DVB (DT)
[    4.410869] Call trace:
[    4.410874] [<ffffff8008008ae0>] dump_backtrace+0x0/0x3f0
[    4.410878] [<ffffff8008008ee4>] show_stack+0x14/0x20
[    4.410882] [<ffffff80087a8b18>] dump_stack+0x98/0xb8
[    4.410886] [<ffffff80084a442c>] xj3_poll+0xbec/0xc00
[    4.410891] [<ffffff800863ce60>] net_rx_action+0x358/0x3f0
[    4.410894] [<ffffff800800102c>] __do_softirq+0x134/0x254
[    4.410898] [<ffffff8008026070>] irq_exit+0xd8/0x108
[    4.410902] [<ffffff8008066c30>] __handle_domain_irq+0x60/0xb8
[    4.410905] [<ffffff8008000d58>] gic_handle_irq+0x58/0xb0
[    4.410908] Exception stack(0xffffff8008ab3de0 to 0xffffff8008ab3f20)
[    4.410912] 3de0: 0000000000000020 000000407648b000 0000000000000000 ffffff8008a9e810
[    4.410916] 3e00: ffffff8008a9e7d8 ffffff8008ab3f10 000000407648b000 ffffffc07c459600
[    4.410920] 3e20: ffffff8008ac2e90 ffffff8008ab3e90 00000000000008b0 0000000000000000
[    4.410923] 3e40: 0000000000000000 0000000000000000 00000000fffffff0 00000000fffffffd
[    4.410927] 3e60: 0000000059979b3b 00000000cf2798dc 0000000000000060 ffffff8008a9c000
[    4.410931] 3e80: ffffff8008ab8a2c ffffff8008ab89a0 ffffff8008ac2580 ffffff8008ac2580
[    4.410935] 3ea0: ffffff8008a6e028 0000000034d5d915 0000000004001e9c 0000000000000400
[    4.410939] 3ec0: 0000000000c20018 ffffff8008ab3f20 ffffff80080048ac ffffff8008ab3f20
[    4.410943] 3ee0: ffffff80080048b0 0000000060000145 0000000034d5d915 0000000004001e9c
[    4.410947] 3f00: ffffffffffffffff ffffff8008059004 ffffff8008ab3f20 ffffff80080048b0
[    4.410950] [<ffffff8008002830>] el1_irq+0xb0/0x140
[    4.410954] [<ffffff80080048b0>] arch_cpu_idle+0x10/0x18
[    4.410959] [<ffffff8008059038>] do_idle+0xd8/0x118
[    4.410963] [<ffffff8008059158>] cpu_startup_entry+0x20/0x28
[    4.410967] [<ffffff80087b94fc>] rest_init+0xac/0xb8
[    4.410971] [<ffffff8008a20b38>] start_kernel+0x340/0x354
[    4.410985] arp: #### /home/users/quan01.wang/dev_xj3_1029/kernel/net/ipv4/arp.c arp_process 825,dev_type=1,ar_op=256,ar_pro=8
[    4.412492] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=3,current->comm=ksoftirqd/0
[    4.412496] CPU: 0 PID: 7 Comm: ksoftirqd/0 Tainted: P                4.14.87 #13
[    4.412497] Hardware name: Hobot J3 SOC MP DVB (DT)
[    4.412499] Call trace:
[    4.412504] [<ffffff8008008ae0>] dump_backtrace+0x0/0x3f0
[    4.412508] [<ffffff8008008ee4>] show_stack+0x14/0x20
[    4.412511] [<ffffff80087a8b18>] dump_stack+0x98/0xb8
[    4.412516] [<ffffff80084a442c>] xj3_poll+0xbec/0xc00
[    4.412520] [<ffffff800863ce60>] net_rx_action+0x358/0x3f0
[    4.412523] [<ffffff800800102c>] __do_softirq+0x134/0x254
[    4.412527] [<ffffff8008025a5c>] run_ksoftirqd+0x44/0x58
[    4.412530] [<ffffff80080423b8>] smpboot_thread_fn+0x198/0x1a0
[    4.412534] [<ffffff800803e64c>] kthread+0xfc/0x128
[    4.412538] [<ffffff80080041d0>] ret_from_fork+0x10/0x18
[    4.919518] #### /home/users/quan01.wang/dev_xj3_1029/kernel/drivers/net/ethernet/hobot/hobot_eth.c xj3_poll 4902,hw_eth_rx_idx_test=4,current->comm=swapper/0
[    4.919527] CPU: 0 PID: 0 Comm: swapper/0 Tainted: P                4.14.87 #13
[    4.919534] Hardware name: Hobot J3 SOC MP DVB (DT)
[    4.919535] Call trace:
[    4.919540] [<ffffff8008008ae0>] dump_backtrace+0x0/0x3f0
[    4.919545] [<ffffff8008008ee4>] show_stack+0x14/0x20
[    4.919548] [<ffffff80087a8b18>] dump_stack+0x98/0xb8
[    4.919552] [<ffffff80084a442c>] xj3_poll+0xbec/0xc00
[    4.919556] [<ffffff800863ce60>] net_rx_action+0x358/0x3f0
[    4.919560] [<ffffff800800102c>] __do_softirq+0x134/0x254
[    4.919563] [<ffffff8008026070>] irq_exit+0xd8/0x108
[    4.919573] [<ffffff8008066c30>] __handle_domain_irq+0x60/0xb8
[    4.919582] [<ffffff8008000d58>] gic_handle_irq+0x58/0xb0
[    4.919590] Exception stack(0xffffff8008ab3de0 to 0xffffff8008ab3f20)
[    4.919597] 3de0: 0000000000000020 000000407648b000 0000000000000000 ffffff8008a9e810
[    4.919601] 3e00: ffffff8008a9e7d8 ffffff8008ab3f10 000000407648b000 ffffffc07ef2bd80
[    4.919605] 3e20: 00000000000000ff ffffffc07ef2bd80 0000000000000001 0000000000000000
[    4.919609] 3e40: 0000000000000001 0000000000000000 00000000fffffff0 0000000000000000
[    4.919613] 3e60: 0000000059979b3b 00000000cf2798dc 0000000000000060 ffffff8008a9c000
[    4.919617] 3e80: ffffff8008ab8a2c ffffff8008ab89a0 ffffff8008ac2580 ffffff8008ac2580
[    4.919621] 3ea0: ffffff8008a6e028 0000000034d5d915 0000000004001e9c 0000000000000400
[    4.919624] 3ec0: 0000000000c20018 ffffff8008ab3f20 ffffff80080048ac ffffff8008ab3f20
[    4.919628] 3ee0: ffffff80080048b0 0000000060000145 0000000034d5d915 0000000004001e9c
[    4.919632] 3f00: ffffffffffffffff ffffff8008059004 ffffff8008ab3f20 ffffff80080048b0
[    4.919636] [<ffffff8008002830>] el1_irq+0xb0/0x140
[    4.919640] [<ffffff80080048b0>] arch_cpu_idle+0x10/0x18
[    4.919644] [<ffffff8008059038>] do_idle+0xd8/0x118
[    4.919648] [<ffffff800805915c>] cpu_startup_entry+0x24/0x28
[    4.919652] [<ffffff80087b94fc>] rest_init+0xac/0xb8
[    4.919656] [<ffffff8008a20b38>] start_kernel+0x340/0x354
[    5.413245] arp: #### /home/users/quan01.wang/dev_xj3_1029/kernel/net/ipv4/arp.c arp_process 825,dev_type=1,ar_op=256,ar_pro=8
[    6.415719] arp: #### /home/users/quan01.wang/dev_xj3_1029/kernel/net/ipv4/arp.c arp_process 825,dev_type=1,ar_op=256,ar_pro=8
root@j3dvbj3-hynix2G-3200:~# 

从上述的堆栈可以发现软中断 do_current_softirqs/__do_softirq 执行的场景包括:进程上下文,硬件中断退出,软中断线程。

3 tasklet

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

  • 软中断类型内核中都是静态分配,不支持动态分配,而tasklet支持动态和静态分配,也就是驱动程序中能比较方便的进行扩展;
  • 软中断可以在多个CPU上并行运行,因此需要考虑可重入问题,而tasklet会绑定在某个CPU上运行,运行完后再解绑,不要求重入问题,当然它的性能也就会下降一些;

3.1 数据结构

  • DEFINE_PER_CPU(struct tasklet_head, tasklet_vec)为每个CPU都分配了tasklet_head结构,该结构用来维护struct tasklet_struct链表,需要放到该CPU上运行的tasklet将会添加到该结构的链表中,内核中为每个CPU维护了两个链表tasklet_vectasklet_vec_hi,对应两个不同的优先级,本文以tasklet_vec为例;
  • struct tasklet_structtasklet的抽象,几个关键字段如图所示,通过next来链接成链表,通过state字段来标识不同的状态以确保能在CPU上串行执行,func函数指针在调用task_init()接口时进行初始化,并在最终触发软中断时执行;

3.2 流程分析

  • 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);

3.4 编程实例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/init_task.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/percpu-defs.h>
#include <linux/interrupt.h>
#include <linux/delay.h>void xxx_do_tasklet(unsigned long val);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
static int tasklet_test_idx = 0;
/*  中断处理底半部 */
void xxx_do_tasklet(unsigned long val){printk("#### %s,tasklet_test_idx=%d...\n",__func__,tasklet_test_idx);tasklet_test_idx++;dump_stack();return ;
}static int __init softirq_tasklet_test_init(void){printk("#### %s...\n",__func__);tasklet_schedule(&xxx_tasklet);//mdelay(10);tasklet_schedule(&xxx_tasklet); return 0;
}static void __exit softirq_tasklet_test_exit(void){return;
}module_init(softirq_tasklet_test_init);
module_exit(softirq_tasklet_test_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

3.4.1 tasklet调度丢失根因

如上代码所示,如果连续调度两次tasklet下半部可能会有丢失,即没有mdelay,堆栈如下:

虽然调度了两次但是tasklet仅执行一次,根因如下:

函数中关键部分是 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) 这一行代码。设置tasklet类型的结构体对象t的state状态属性的TASKLET_STATE_SCHED位为1,同时返回tasklet类型的结构体对象t的state状态属性的TASKLET_STATE_SCHED位的原值。如果这个原值是1,就说明这个tasklet类型的结构体对象已经被调度到另一个CPU上去等待执行了。考虑到一个tasklet结构体对象同一时刻只能由一个CPU来执行,因此tasklet_schedule()不做任何操作,就直接返回了。反之,如果原值是0,那么就会执行__tasklet_schedule()调度函数。

顺藤摸瓜,我们很容易找到tasklet执行完毕后,清除TASKLET_STATE_SCHED位值的地方即__tasklet_action函数,代码如下

static void __tasklet_action(struct softirq_action *a,struct tasklet_struct *list)
{if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))WARN_ON(1);....if (test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))goto again;....
}

核心思想:不在使用静态的tasklet,而是在中断顶半部动态申请tasklet并传递给底半部执行。

3.4.2 调度延时测试

在两次调度中加上mdelay,延时10ms再次调度,堆栈如下:

这篇关于浅析linux内核中断子系统—softirq和tasklet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/173711

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面

LinuxMint怎么安装? Linux Mint22下载安装图文教程

《LinuxMint怎么安装?LinuxMint22下载安装图文教程》LinuxMint22发布以后,有很多新功能,很多朋友想要下载并安装,该怎么操作呢?下面我们就来看看详细安装指南... linux Mint 是一款基于 Ubuntu 的流行发行版,凭借其现代、精致、易于使用的特性,深受小伙伴们所喜爱。对

什么是 Linux Mint? 适合初学者体验的桌面操作系统

《什么是LinuxMint?适合初学者体验的桌面操作系统》今天带你全面了解LinuxMint,包括它的历史、功能、版本以及独特亮点,话不多说,马上开始吧... linux Mint 是一款基于 Ubuntu 和 Debian 的知名发行版,它的用户体验非常友好,深受广大 Linux 爱好者和日常用户的青睐,

Linux(Centos7)安装Mysql/Redis/MinIO方式

《Linux(Centos7)安装Mysql/Redis/MinIO方式》文章总结:介绍了如何安装MySQL和Redis,以及如何配置它们为开机自启,还详细讲解了如何安装MinIO,包括配置Syste... 目录安装mysql安装Redis安装MinIO总结安装Mysql安装Redis搜索Red

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch