ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

linux软中断

linux软中断 在 linux 中任务执行的载体有很多包括线程中断软中断tasklet定时器等。但是从本质上来划分的话任务执行的载体只有两个线程和中断。软中断和 tasklet 的执行可能在中断中也可能在线程中定时器的执行可能在中断、软中断或者线程中。在讨论中断和软中断的时候经常以网卡收包为例子来理解。如下是网卡收包的主要环节分别说明每个环节① 网卡从链路上收包网卡的作用是成帧网卡从链路上收到的是字节流根据前导码帧开始界定符帧间隙来界定一个帧这就是成帧。② 网卡界定一帧报文之后将这一帧报文通过 dma 保存到内存中。③ 网卡触发中断收包中断有对应的处理程序对于现在的大多网卡来说在中断处理程序中做的事情是触发收包软中断。④ 收包的主要工作是在软中断中处理而不是在硬中断中处理。中断是硬件和软件进行通信的一种方式。中断具有异步响应快的特点。在 linux 中中断可以打断当前正在运行的程序并且在处理一个中断的时候往往需要关闭本地中断。在网络收包的过程中从网卡驱动到链路层代码再到 ip 层tcp 层报文的处理过程比较长所需要花费的时间往往是比较长的。如果在中断处理程序中把收包的工作都做了那么很可能会导致本地关中断时间太长进而影响系统的响应。所以在网卡收包时往往采用中断和软中断结合的方式在中断处理程序中做的工作比较简单只是触发一个软中断后边的处理过程在软中断中进行。中断处理程序中触发软中断之后立即返回此时就可以打开本地中断了。软中断处理函数是 __do_softirq从这个函数中也可以看出来在处理软中断之前调用函数 local_irq_enable也就说在处理软中断的时候是开中断的。asmlinkage __visible void __softirq_entry __do_softirq(void) { ... local_irq_enable(); h softirq_vec; while ((softirq_bit ffs(pending))) { unsigned int vec_nr; int prev_count; h softirq_bit - 1; vec_nr h - softirq_vec; prev_count preempt_count(); kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); h-action(h); trace_softirq_exit(vec_nr); if (unlikely(prev_count ! preempt_count())) { pr_err(huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n, vec_nr, softirq_to_name[vec_nr], h-action, prev_count, preempt_count()); preempt_count_set(prev_count); } h; pending softirq_bit; } if (__this_cpu_read(ksoftirqd) current) rcu_softirq_qs(); local_irq_disable(); ... }处理软中断的时候有没有禁止中断没有禁止中断禁止中断的话那引入软中断还有什么意义。 引入软中断的意义是减少中断对cpu的占用将工作延后执行能按照线程优先级进行统一调度。中断返回的时候也可能处理软中断判断如果软中断线程正在运行那么返回时就不会处理软中断如果编译宏强制使用线程来处理那么也不会处理软中断否则的话会处理一定数量的软中断这个时候如果没处理完则会激活软中断处理线程。 中断退出过程中如果处理软中断也是处理一定数量就退出了不可能一直处理软中断。1 软中断种类如下是软中断的种类当前定义了 10 种软中断其中 NET_TX_SOFTIRQ 和 NET_RX_SOFTIRQ 用于网卡发包和收包。从定义可以看出来软中断的种类是静态的确定的不能动态改变。从注释可以看出来不到万不得已不要新增软中断大部分情况下使用 tasklet 已经足够了。/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high frequency threaded job scheduling. For almost all the purposes tasklets are more than enough. F.e. all serial device BHs et al. should be converted to tasklets, not to softirqs. */ enum { HI_SOFTIRQ0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS };2 开启软中断和触发软中断以网卡软中断为例来说明。通过函数 open_softirq来打开软中断也叫注册这个软中断在内核初始化的时候会调用函数 net_dev_init完成网络相关的初始化在该函数中打开了网络收发包软中断。static int __init net_dev_init(void) { ... open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); ... }软中断使用一个数组来维护数组是 softirq_vec数组的下标是软中断的中断号数组的元素struct softirq_action 类型这个结构体中只有一个成员就是软中断的处理函数。网卡发包和收包软中断的处理函数分别是 net_tx_action 和 net_rx_action。struct softirq_action { void (*action)(struct softirq_action *); }; static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;在网卡中断处理程序中一般会调用函数 __raise_softirq_irqoff触发软中断。触发软中断就是在一个全局的变量中设置一个标志标志有软中断了。在 linux 中这个全局变量是 per cpu 的。void __raise_softirq_irqoff(unsigned int nr) { lockdep_assert_irqs_disabled(); trace_softirq_raise(nr); or_softirq_pending(1UL nr); }3 处理软中断3.1 中断返回时中断返回时最终会调用到函数 __irq_exit_rcu在这个函数中会进行判断如果当前不是处于中断上下文并且有软中断需要处理那么就会调用 invoke_softirq来处理软中断。在中断返回时已经preempt_count_sub(HARDIRQ_OFFSET)如果当前处于中断上下文中那么就是出现了中断抢占当前要退出的中断抢占了一个低优先级的中断。static inline void __irq_exit_rcu(void) { #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED local_irq_disable(); #else lockdep_assert_irqs_disabled(); #endif account_irq_exit_time(current); preempt_count_sub(HARDIRQ_OFFSET); if (!in_interrupt() local_softirq_pending()) invoke_softirq(); tick_irq_exit(); }在函数 invoke_softirq中进行判断如果软中断处理线程当前正在运行那么直接返回。如果 force_irqthreads 是 false 的话那么就调用 __do_softirq或者 do_softirq_onwn_stack处理软中断如果强制让软中断处理线程来处理软中断那么意思是在中断返回的时候不处理软中断会将线程唤醒。static inline void invoke_softirq(void) { if (ksoftirqd_running(local_softirq_pending())) return; if (!force_irqthreads) { #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK /* * We can safely execute softirq on the current stack if * it is the irq stack, because it should be near empty * at this stage. */ __do_softirq(); #else /* * Otherwise, irq_exit() is called on the task stack that can * be potentially deep already. So call softirq in its own stack * to prevent from any overrun. */ do_softirq_own_stack(); #endif } else { wakeup_softirqd(); } }3.2 ksoftirqd除了在中断返回的时候处理软中断还有专门的线程来处理。在内核中每个 cpu 核上都会创建一个 ksoftirqd 线程用来处理这个核上的软中断。ksoftirqd 的信息维护在 softirq_threads 中。static struct smp_hotplug_thread softirq_threads { .store ksoftirqd, .thread_should_run ksoftirqd_should_run, .thread_fn run_ksoftirqd, .thread_comm ksoftirqd/%u, };在 ksoftirqd 中最终也是调用函数 __do_softirq来处理软中断。static void run_ksoftirqd(unsigned int cpu) { local_irq_disable(); if (local_softirq_pending()) { /* * We can safely run softirq on inline stack, as we are not deep * in the task stack here. */ __do_softirq(); local_irq_enable(); cond_resched(); return; } local_irq_enable(); }什么时候在中断返回时处理软中断 ?中断返回时会进行判断如果当前不是处于中断上下文并且有软中断需要处理并且这个时候 ksofrirqd 没有在运行那么这个时候就会处理软中断。什么时候在 ksoftirqd 中处理软中断 ① 中断返回的时候 判断 force_irqthreads 为 true那么只能在 ksoftirqd 中处理软中断这个时候会通过 wakeup_softirqd() 来唤醒 ksoftirqd。② 使用 raise_softirq_irqoff() 触发软中断时如果当前不是处于中断上下文也会唤醒 ksoftirqdinline void raise_softirq_irqoff(unsigned int nr) { __raise_softirq_irqoff(nr); /* * If were in an interrupt or softirq, were done * (this also catches softirq-disabled code). We will * actually run the softirq once we return from * the irq or softirq. * * Otherwise we wake up ksoftirqd to make sure we * schedule the softirq soon. */ if (!in_interrupt()) wakeup_softirqd(); }4 软中断处理时为什么不能睡眠软中断的处理可能在中断返回的时候也可能在 ksoftirq 线程中。在中断返回的时候虽然通过 in_interrupt判断当前不是处于中断上下文但是这个时候中断处理程序还没有真正返回还不是进程上下文所以此时也是不能睡眠。
返回列表