建设项目环境影响网站,长沙招聘网站有哪些,非洲跨境电商平台有哪些,展厅设计要考虑哪些方面在介绍软中断之前#xff0c;先来介绍一个概念#xff1a;中断下半部#xff1a; 为了避免处理复杂的中断嵌套#xff0c;中断处理程序是在关闭中断的情况下执行的。可是#xff0c;如果关闭中断的时间太长#xff0c;可能导致中断请求丢失。例如周期时钟每隔 10 毫秒发送…
在介绍软中断之前先来介绍一个概念中断下半部 为了避免处理复杂的中断嵌套中断处理程序是在关闭中断的情况下执行的。可是如果关闭中断的时间太长可能导致中断请求丢失。例如周期时钟每隔 10 毫秒发送一个中断请求如果执行某个中断处理程序花费的时间超过 10 毫秒在这段时间里时钟发送了两个中断请求但是处理器只认为收到一个时钟中断请求。 最激进的解决办法是中断线程化但是常用的解决办法是把中断处理程序分为两部分上半部top half th在关闭中断的情况下执行只做对时间非常敏感、与硬件相关或者不能被其他中断打断的工作下半部bottom halfbh在开启中断的情况下执行可以被其他中断打断。 上半部称为硬中断hardirq下半部有 3 种软中断softirq、小任务tasklet和工作队列workqueue。 3 种下半部的区别如下。 1软中断和小任务是工作在中断上下文不允许睡眠工作队列是使用内核线程实现的是工作在进程上下文处理函数可以睡眠。 2软中断的种类是编译时静态定义的在运行时不能添加或删除小任务可以在运行时添加或删除。 3同一种软中断的处理函数可以在多个处理器上同时执行处理函数必须是可以重入的需要使用锁保护临界区一个小任务同一时刻只能在一个处理器上执行不要求处理函数是可以重入的。
软中断softirq是中断处理程序在开启中断的情况下执行的部分可以被硬中断抢占。 内核定义了一张软中断向量表每种软中断有一个唯一的编号对应一个 softirq_action实例 softirq_action 实例的成员 action 是处理函数。 kernel/softirq.c static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
include/linux/interrupt.h struct softirq_action { void (*action)(struct softirq_action *); };
2.1 软中断的种类 目前内核定义了 10 种软中断各种软中断的编号如下 include/linux/interrupt.h enum { HI_SOFTIRQ0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, /* 没有使用但是保留因为有些工具依赖这个编号 */ RCU_SOFTIRQ, /* RCU软中断应该总是最后一个软中断 */ NR_SOFTIRQS }; 1 HI_SOFTIRQ高优先级的小任务。 2 TIMER_SOFTIRQ定时器软中断。 3 NET_TX_SOFTIRQ网络栈发送报文的软中断。 4 NET_RX_SOFTIRQ网络栈接收报文的软中断。 5 BLOCK_SOFTIRQ块设备软中断。 6 IRQ_POLL_SOFTIRQ支持 I/O 轮询的块设备软中断。 7 TASKLET_SOFTIRQ低优先级的小任务。 8 SCHED_SOFTIRQ调度软中断用于在处理器之间负载均衡。 9 HRTIMER_SOFTIRQ高精度定时器这种软中断已经被废弃目前在中断处理程序的上半部处理高精度定时器。 10 RCU_SOFTIRQ RCU 软中断。 软中断的编号形成了优先级顺序编号小的软中断优先级高。 注册软中断的处理函数 函数 open_softirq()用来注册软中断的处理函数在软中断向量表中为指定的软中断编号设置处理函数。 kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action action; }
同一种软中断的处理函数可以在多个处理器上同时执行处理函数必须是可以重入的需要使用锁保护临界区。 触发软中断 函数 raise_softirq 用来触发软中断参数是软中断编号。 void raise_softirq(unsigned int nr);
在已经禁止中断的情况下可以调用函数 raise_softirq_irqoff 来触发软中断。 void raise_softirq_irqoff(unsigned int nr);
函数 raise_softirq 在当前处理器的待处理软中断位图中为指定的软中断编号设置对应的位如下所示 raise_softirq() - raise_softirq_irqoff() - __raise_softirq_irqoff() kernel/softirq.c void __raise_softirq_irqoff(unsigned int nr) { or_softirq_pending(1UL nr); }
把宏 or_softirq_pending 展开以后是 irq_stat[smp_processor_id()].__softirq_pending | (1UL nr); 执行软中断 内核执行软中断的地方如下。 1在中断处理程序的后半部分执行软中断对执行时间有限制不能超过 2 毫秒并且最多执行 10 次。 2每个处理器有一个软中断线程调度策略是 SCHED_NORMAL优先级是 120。 3开启软中断的函数 local_bh_enable()。
如果开启了强制中断线程化的配置宏 CONFIG_IRQ_FORCED_THREADING并且在引导内核的时候指定内核参数“ threadirqs”那么所有软中断由软中断线程执行。
1中断处理程序执行软中断。 在中断处理程序的后半部分调用函数 irq_exit()以退出中断上下文处理软中断其代码如下 kernel/softirq.c void irq_exit(void) { … preempt_count_sub(HARDIRQ_OFFSET); if (!in_interrupt() local_softirq_pending()) invoke_softirq(); … } 如果 in_interrupt()为真表示在不可屏蔽中断、硬中断或软中断上下文或者禁止软中断。 这里我们提一下本地中断的开启与关闭当中断上来时为了防止中断的嵌套硬件会自动关闭本地中断那本地中断什么时候打开呢分两种情况 1. 退出中断上下文时若有待处理的软中断在执行软中断前__do_softirq-local_irq_enable会打开本地中断即软中可以被硬件中断的打断。 2. 没有要处理的软中断那么在中断完全退出时会恢复被中断进程的寄存器上下文系统状态寄存器一但被恢复本地中断自然也就开了。 如果正在处理的硬中断没有抢占正在执行的软中断没有禁止软中断并且当前处理器的待处理软中断位图不是空的那么调用函数 invoke_softirq()来处理软中断。 函数 invoke_softirq 的代码如下 kernel/softirq.c 1 static inline void invoke_softirq(void) 2 { 3 if (ksoftirqd_running()) 4 return; 5 6 if (!force_irqthreads) { 7 __do_softirq(); 8 } else { 9 wakeup_softirqd(); 10 } 11 } 第 3 行代码如果软中断线程处于就绪状态或运行状态那么让软中断线程执行软中断。 第 6 行和第 7 行代码如果没有强制中断线程化那么调用函数__do_softirq()执行软中断。 第 8 行和第 9 行代码如果强制中断线程化那么唤醒软中断线程执行软中断。
函数__do_softirq 是执行软中断的核心函数其主要代码如下 kernel/softirq.c 1 #define MAX_SOFTIRQ_TIME msecs_to_jiffies(2) 2 #define MAX_SOFTIRQ_RESTART 10 3 asmlinkage __visible void __softirq_entry __do_softirq(void) 4 { 5 unsigned long end jiffies MAX_SOFTIRQ_TIME; 6 unsigned long old_flags current-flags; 7 int max_restart MAX_SOFTIRQ_RESTART; 8 struct softirq_action *h; 9 bool in_hardirq; 10 __u32 pending; 11 int softirq_bit; 12 13 … 14 pending local_softirq_pending(); 15 … 16 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); 17 … 18 19 restart: 20 set_softirq_pending(0); 21 22 local_irq_enable(); 23 24 h softirq_vec; 25 26 while ((softirq_bit ffs(pending))) { 27 … 28 h softirq_bit - 1; 29 … 30 h-action(h); 31 … 32 h; 33 pending softirq_bit; 34 } 35 36 … 37 local_irq_disable(); 38 39 pending local_softirq_pending(); 40 if (pending) { 41 if (time_before(jiffies, end) !need_resched() 42 --max_restart) 43 goto restart; 44 45 wakeup_softirqd(); 46 } 47 48 … 49 __local_bh_enable(SOFTIRQ_OFFSET); 50 … 51}
第 14 行代码把局部变量 pending 设置为当前处理器的待处理软中断位图。 第 16 行代码把抢占计数器的软中断计数加 1。 第 20 行代码把当前处理器的待处理软中断位图重新设置为 0。 第 22 行代码开启硬中断。 第 2634 行代码从低位向高位扫描待处理软中断位图针对每个设置了对应位的软中断编号执行软中断的处理函数。 第 37 行代码禁止硬中断。 第 40 行代码如果软中断的处理函数又触发软中断处理如下。 a) 第 4143 行代码如果软中断的执行时间小于 2 毫秒不需要重新调度进程并且软中断的执行次数没超过 10那么跳转到第 19 行代码继续执行软中断。 b) 第 45 行代码唤醒软中断线程执行软中断。 第 49 行代码把抢占计数器的软中断计数减 1。
2软中断线程 每个处理器有一个软中断线程名称是“ ksoftirqd/”后面跟着处理器编号调度策略是 SCHED_NORMAL优先级是 120。 软中断线程的核心函数是 run_ksoftirqd()其代码如下 kernel/softirq.c static void run_ksoftirqd(unsigned int cpu) { local_irq_disable(); if (local_softirq_pending()) { __do_softirq(); local_irq_enable(); … return; } local_irq_enable(); }
3开启软中断时执行软中断。 当进程调用函数 local_bh_enable()开启软中断的时候如果是开启最外层的软中断并且当前处理器的待处理软中断位图不是空的那么执行软中断。 local_bh_enable() - __local_bh_enable_ip() kernel/softirq.c void __local_bh_enable_ip(unsigned long ip, unsigned int cnt) { … preempt_count_sub(cnt - 1); if (unlikely(!in_interrupt() local_softirq_pending())) { do_softirq(); } preempt_count_dec(); … } 抢占计数器 在介绍“禁止/开启软中断”之前首先了解一下抢占计数器这个背景知识。 每个进程的 thread_info 结构体有一个抢占计数器 int preempt_count它用来表示当前进程能不能被抢占。 抢占是指当进程在内核模式下运行的时候可以被其他进程抢占如果优先级更高的进程处于就绪状态强行剥夺当前进程的处理器使用权。 但是有时候进程可能在执行一些关键操作不能被抢占所以内核设计了抢占计数器。如果抢占计数器为 0表示可以被抢占如果抢占计数器不为 0表示不能被抢占。 当中断处理程序返回的时候如果进程在被打断的时候正在内核模式下执行就会检查抢占计数器是否为 0。如果抢占计数器是 0可以让优先级更高的进程抢占当前进程。 虽然抢占计数器不为 0 意味着禁止抢占但是内核进一步按照各种场景对抢占计数器的位进行了划分如下图所示。 其中第 07 位是抢占计数第 815 位是软中断计数第 1619 位是硬中断计数第 20 位是不可屏蔽中断 Non Maskable Interrupt NMI计数。 include/linux/preempt.h /* * PREEMPT_MASK: 0x000000ff * SOFTIRQ_MASK: 0x0000ff00 * HARDIRQ_MASK: 0x000f0000 * NMI_MASK: 0x00100000 */ #define PREEMPT_BITS 8 #define SOFTIRQ_BITS 8 #define HARDIRQ_BITS 4 #define NMI_BITS 1 各种场景分别利用各自的位禁止或开启抢占。 1普通场景 PREEMPT_MASK对应函数 preempt_disable()和 preempt_enable()。 2软中断场景 SOFTIRQ_MASK对应函数 local_bh_disable()和 local_bh_enable()。 3硬中断场景 HARDIRQ_MASK对应函数 __irq_enter()和__irq_exit()。 4不可屏蔽中断场景 NMI_MASK对应函数 nmi_enter()和 nmi_exit()。 反过来我们可以通过抢占计数器的值判断当前处在什么场景 include/linux/preempt.h #define in_irq() (hardirq_count()) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count()) #define in_serving_softirq() (softirq_count() SOFTIRQ_OFFSET) #define in_nmi() (preempt_count() NMI_MASK) #define in_task() (!(preempt_count() \ (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET))) #define hardirq_count() (preempt_count() HARDIRQ_MASK) #define softirq_count() (preempt_count() SOFTIRQ_MASK) #define irq_count() (preempt_count() (HARDIRQ_MASK | SOFTIRQ_MASK \ | NMI_MASK)) in_irq()表示硬中断场景也就是正在执行硬中断。 in_softirq()表示软中断场景包括禁止软中断和正在执行软中断。 in_interrupt()表示正在执行不可屏蔽中断、硬中断或软中断或者禁止软中断。 in_serving_softirq()表示正在执行软中断。 in_nmi()表示不可屏蔽中断场景。 in_task()表示普通场景也就是进程上下文。 禁止/开启软中断 如果进程和软中断可能访问同一个对象 那么进程和软中断需要互斥 进程需要禁止软中断。 禁止软中断的函数是 local_bh_disable()注意这个函数只能禁止本处理器的软中断不能禁止其他处理器的软中断。该函数把抢占计数器的软中断计数加 2其代码如下 include/linux/bottom_half.h static inline void local_bh_disable(void) { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); } static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt) { preempt_count_add(cnt); barrier(); } include/linux/preempt.h #define SOFTIRQ_DISABLE_OFFSET (2 * SOFTIRQ_OFFSET) 开启软中断的函数是 local_bh_enable()该函数把抢占计数器的软中断计数减 2。 为什么禁止软中断的函数 local_bh_disable()把抢占计数器的软中断计数加 2 而不是加1 呢目的是区分禁止软中断和正在执行软中断这两种情况。执行软中断的函数__do_softirq()把抢占计数器的软中断计数加 1。如果软中断计数是奇数可以确定正在执行软中断。 注意local_bh_enable() 在硬中断或者关闭硬中断时使用有可能出现问题会有警告提醒。 挂起的软中断 另一个跟软中断相关的字段是每个CPU都有一个32位掩码的字段 typedef struct { unsigned int __softirq_pending; unsigned int ipi_irqs[NR_IPI]; } ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned; EXPORT_SYMBOL(irq_stat);
他描述挂起的软中断。每一位对应相应的软中断。比如0位代表HI_SOFTIRQ.一个注册软中断必须在被标记后才会执行。 宏local_softirq_pending();来获取该字段的值。 宏set_softirq_pending(0);来设置该字段的值。 使用函数raise_softirq()来激活软中断。即把响应的软中断号对应的__softirq_pending中的位置1。表示该软中断被挂起。如果当前CPU不在中断上下文中唤醒内核线程ksoftirqd来检查被挂起的软中断然后执行相应软中断处理函数。 内核在如下几个点上检查被挂起的软中断 1、当do_IRQ() 完成硬中断处理时调用irq_exit()时调用do_softirq()来处理软中断。 2、当一个特殊内核线程ksoftirqd/n被唤醒时处理软中断。 3、当调用local_bh_enable()函数激活本地CPU的软中断时。条件满足就调用do_softirq() 来处理软中断。