SoftIrq 流程分析

------------------------ kernel/softirq.c -----------------------

static __init int spawn_ksoftirqd(void);
static __init int spawn_ksoftirqd(void)
{
void *cpu = (void *)(long)smp_processor_id();
int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);

BUG_ON(err == NOTIFY_BAD);
cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
register_cpu_notifier(&cpu_nfb);
return 0;
}

early_initcall(spawn_ksoftirqd);

spawn_ksoftirqd 在系统启动的时候会被执行,这个函数调用了两次cpu_callback,根据两次传入的CPU_UP_PREPARECPU_ONLINE 这两个状态来判断,一般稍微有点觉悟的好同志都会立刻想到这两个状态是彼此相互联系的。下面具体看cpu_allback的实现

static int __cpuinit cpu_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
int hotcpu = (unsigned long)hcpu;
struct task_struct *p;

switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);
if (IS_ERR(p)) {
printk("ksoftirqd for %i failed\n", hotcpu);
return NOTIFY_BAD;
}
kthread_bind(p, hotcpu);
per_cpu(ksoftirqd, hotcpu) = p;
break;
case CPU_ONLINE:
case CPU_ONLINE_FROZEN:
wake_up_process(per_cpu(ksoftirqd, hotcpu));
break;

}
return NOTIFY_OK;
}

当前CPU的状态为CPU_UP_PREPARE, CPU还没有完全开始调度线程之前,我们利用kthread_create来创建一个内核线程 ksoftirqd ;接下来当CPU的状态为CPU_ONLINE后, wake_up_process 唤醒之前创建的ksoftirqd线程。那现在问题就是ksoftirqd 这个线程被唤醒之后都干了哪些勾当?! 

static int ksoftirqd(void * __bind_cpu)
{
set_current_state(TASK_INTERRUPTIBLE);

while (!kthread_should_stop()) {
preempt_disable();
if (!local_softirq_pending()) {
preempt_enable_no_resched();
schedule();
preempt_disable();
}

__set_current_state(TASK_RUNNING);

while (local_softirq_pending()) {  等待CPU状态从CPU_UP_PREPARE进入CPU_ONLINE后,被唤醒!
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();  处理各种 soft irq这个函数可以是kernel/softirq.c中的do_softirq,也可以在不同体系结构中自己实现;
preempt_enable_no_resched();
cond_resched();
preempt_disable();
rcu_qsctr_inc((long)__bind_cpu);
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;

wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}

------------------------- kernel/softirq.c -----------------------
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;

pending = local_softirq_pending();Q1返回pending值的含义 ?
account_system_vtime(current);

__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter();

cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);

local_irq_enable();

h = softirq_vec;   Q2:这个数组的含义?

do {
if (pending & 1) {
int prev_count = preempt_count();

trace_softirq_entry(h, softirq_vec);
h->action(h);     Q3:这里调用的具体是哪个函数?
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}

rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);

local_irq_disable();

pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;

if (pending)
wakeup_softirqd();

lockdep_softirq_exit();

account_system_vtime(current);
_local_bh_enable();
}


#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;

if (in_interrupt())
return;

local_irq_save(flags);

pending = local_softirq_pending();

if (pending)
__do_softirq();

local_irq_restore(flags);
}

#endif

Q1:
-----------------arch/arm/include/asm/hardirq.h-----------------------
typedef struct {
unsigned int __softirq_pending;
unsigned int local_timer_irqs;
} ____cacheline_aligned irq_cpustat_t;

-----------------incude/linux/irq_cpustat.h-----------------------
#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[];        /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member)    (irq_stat[cpu].member)  __IRQ_STAT用于存取第cpuCPU对应的irq_stat结构体中的member成员;确实有点绕口!
#endif

/* arch independent irq_stat fields */
#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)

local_softirq_pending宏用去存取当前CPU对应的irq_cpustat_t结构体中的__softirq_pending成员;

#ifndef __ARCH_SET_SOFTIRQ_PENDING
#define set_softirq_pending(x) (local_softirq_pending() = (x))
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))
#endif

#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);

/*
* If we're in an interrupt or softirq, we're 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();
}

void raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

也就是说raise_softirq--->raise_softirq_irqoff--->__raise_softirq_irqoff--->or_softirq_pending 会将当前CPU对应的irq_cpustat_t结构体中的__softirq_pending成员的第nrBIT置位。nr的具体的含义在Q2中解释。
提前先科普下,对于TIMER_SOFTIRQ来说,每次system clock产生中断时,即一个tick 到来时,在system clock的中断处理函数中会调用run_local_timers来设置TIMER_SOFTIRQ触发条件;也就是当前CPU对应的irq_cpustat_t结构体中的__softirq_pending成员的第TIMER_SOFTIRQBIT被置为1。 而当这个条件满足时,
ksoftirqd线程会被唤醒,然后按照下面的流程调用TIMER_SOFTIRQ数组softirq_vec中注册的actionrun_timer_softirq

ksoftirqd--->do_softirq--->__do_softirq--->softirq_vec[TIMER_SOFTIRQ].action

启示:
1TIMER_SOFTIRQ的最小粒度为一个tick的时间间隔
2之所以成为softirq,是因为这些中断是由硬件中断来间接触发的
3__do_softirq会优先处理__softirq_pending中的BIT_0,HI_SOFTIRQ,然后依次检查__softirq_pendingBIT_1 BIT_7是否被置位,如果置位,执行对应的action



/*
* Called by the local, per-CPU timer interrupt on SMP.
*/
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);
softlockup_tick();
}

Q2:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};

struct softirq_action
{
void    (*action)(struct softirq_action *);
};

static struct softirq_action softirq_vec[NR_SOFTIRQS] ;
内核中定义了用于不同场合中的各种软中断,例如网络收发、tasklet、高精度定时器和软中断等等应用场合;没一种应用场合都对应一个总的处理函数,这个函数就是struct softirq_action中的action成员。这写action都是在提前需要提前注册好的。我们以TIMER_SOFTIRQ为例:

Q3
start_kernel--> init_timers
void __init init_timers(void)
{
int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
(void *)(long)smp_processor_id());

init_timer_stats();

BUG_ON(err == NOTIFY_BAD);
register_cpu_notifier(&timers_nb);
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
也就是说,当TIMER_SOFTIRQ这个softirq发生时,run_timer_softirq函数会被调用!
问题是谁回去触发这个TIMER_SOFTIRQ信号呢?


总结:
ksoftirqd 是内核启动时创建的一个内核线程;
该线程会一次检查。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。
。。。。。。当时钟中断发生时。。。。。

<think>好的,用户现在问的是“软中断怎么分析”,我需要详细回答这个问题。首先,回忆一下之前用户问的是关于内核日志中的中断线程和看门狗任务的信息,现在用户可能是在进一步深入中断处理的问题,特别是软中断部分。 软中断(SoftIRQ)是Linux内核中处理延迟敏感任务的一种机制,通常在硬中断处理之后执行。分析软中断通常涉及性能问题和系统延迟的排查。用户可能是遇到了系统延迟高、网络性能不佳或者某些内核任务占用过高的问题,想要了解如何分析软中断的情况。 首先,我需要解释软中断的基本概念,包括它与硬中断的区别,以及常见的软中断类型,比如网络接收(NET_RX)、定时器(TIMER)、块设备(BLOCK)等。然后,介绍分析工具,比如/proc/softirqs、top命令、perf、ftrace等。接着,需要分步骤说明如何查看软中断的统计信息,如何监控软中断的频率和时间,以及如何诊断高负载的软中断。 用户可能想知道具体怎么操作,所以需要给出具体的命令示例,比如使用watch命令监控/proc/softirqs的变化,使用perf记录软中断的处理函数,或者使用ftrace跟踪软中断的执行情况。同时,还需要解释这些命令输出的含义,帮助用户理解数据。 另外,用户可能遇到性能瓶颈,比如网络软中断处理导致CPU使用率过高,这时候需要指导如何定位问题,比如检查网络驱动、调整中断亲和性(IRQ affinity)或者进行内核参数优化。这部分需要给出实际的排查步骤和优化建议。 还要注意用户的技术水平,可能不是内核开发者,但有一定的系统管理经验。因此,解释需要清晰,避免过于深入的代码细节,而是侧重于工具使用和实际案例分析。同时,提醒用户在高负载情况下如何正确诊断,避免误解数据。 最后,总结软中断分析的步骤,强调系统性排查,从监控到深入分析,再到优化调整。可能还需要提到常见问题及解决方法,比如软中断不平衡、单个CPU核心过载等,并给出相应的解决策略。</think>### **软中断(SoftIRQ分析指南** 软中断(SoftIRQ)是 Linux 内核中处理 **延迟敏感任务** 的核心机制,常用于网络数据包处理(如 TCP/IP 协议栈)、块设备 I/O 完成、定时器等场景。其设计目标是在 **硬中断(HardIRQ)** 处理程序之外,以更灵活的调度方式完成耗时操作。以下是软中断的 **分析方法和工具**: --- #### **一、软中断基础概念** 1. **与硬中断的区别** - **硬中断**:由硬件触发(如网卡收到数据包),需要立即响应,执行时间极短(微秒级),且运行在 **中断上下文**(不可休眠)。 - **软中断**:由内核触发(如 `NET_RX` 网络接收),允许稍后处理,运行在 **内核线程上下文**(可休眠),通常由 `ksoftirqd/<CPU>` 线程执行。 2. **常见软中断类型** | 软中断名称 | 用途 | |----------------------|--------------------------------------| | `NET_RX` | 网络数据包接收处理(如 TCP/IP 协议栈)| | `NET_TX` | 网络数据包发送完成 | | `TIMER` | 定时器回调(如 `jiffies` 更新) | | `BLOCK` | 块设备 I/O 完成(如磁盘读写) | | `TASKLET` | 小任务调度(如 USB 驱动) | | `SCHED` | 进程调度相关(如负载均衡) | --- #### **二、软中断分析工具** ##### **1. 查看软中断统计** - **实时统计**: ```bash watch -n1 "cat /proc/softirqs" # 每秒刷新各 CPU 软中断计数 ``` **输出示例**: ``` CPU0 CPU1 CPU2 HI: 0 0 0 TIMER: 12345678 9876543 10456789 NET_TX: 5678 4321 6789 NET_RX: 98765432 87654321 76543210 BLOCK: 0 0 0 IRQ_POLL: 0 0 0 TASKLET: 123 456 789 SCHED: 456789 345678 234567 ``` - **关键字段**:`NET_RX`/`NET_TX` 高数值可能指示网络负载过大。 - **历史统计**: ```bash sar -w -I SUM 1 # 使用 sysstat 监控软中断频率(需安装) ``` ##### **2. 定位软中断 CPU 开销** - **按 CPU 核心排序**: ```bash mpstat -P ALL 1 # 查看各 CPU 使用率,结合软中断分布 ``` - 若某个 CPU 核心的 `%soft`(软中断占比)过高(如 >30%),需进一步分析。 - **跟踪软中断处理函数**: ```bash perf top -e irq:softirq_entry # 动态显示软中断热点函数 perf record -e irq:softirq_entry -ag -- sleep 5 # 记录 5 秒软中断调用栈 ``` ##### **3. 深入分析软中断延迟** - **使用 `trace-cmd` 或 `ftrace`**: ```bash echo 'softirq:*' > /sys/kernel/debug/tracing/set_event echo 1 > /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace_pipe # 实时跟踪软中断事件 ``` **输出关键字段**: - `softirq_raise`:软中断被触发的时间点。 - `softirq_entry`/`softirq_exit`:软中断处理的开始和结束时间。 - **计算延迟**:`exit_time - entry_time` 即为处理耗时。 --- #### **三、典型问题与排查方法** ##### **场景 1:网络性能瓶颈(高 `NET_RX` 计数)** - **现象**: - 网卡吞吐量下降,CPU `%soft` 占比高,`/proc/softirqs` 中 `NET_RX` 计数飙升。 - **排查步骤**: 1. **确认网卡中断亲和性**: ```bash cat /proc/interrupts | grep eth0 # 查看网卡中断绑定的 CPU ``` - 若多队列网卡的中断集中到少数 CPU,会导致软中断负载不均。 - **优化方法**:调整中断亲和性(IRQ Affinity): ```bash echo 0-3 > /proc/irq/<IRQ_NUM>/smp_affinity_list # 将中断分散到 CPU0~3 ``` 2. **检查网络协议栈处理**: - **跟踪 `NET_RX` 处理函数**: ```bash perf record -e irq:softirq_entry -C <CPU> -g -- sleep 10 # 指定 CPU 抓取调用栈 perf report # 分析热点函数(如 `napi_gro_receive`、`__netif_receive_skb`) ``` - **优化建议**: - 启用 RSS(Receive Side Scaling)分散数据包到多队列。 - 调整内核参数(如 `net.core.netdev_budget` 增加单次软中断处理的数据包数)。 ##### **场景 2:软中断处理延迟过高** - **现象**: - 系统响应延迟增加,`ftrace` 显示软中断处理时间过长(如 >1ms)。 - **排查步骤**: 1. **确认软中断类型**: - 若 `TIMER` 软中断延迟高,检查定时器回调函数是否阻塞。 - 若 `TASKLET` 延迟高,检查任务是否因锁竞争或 I/O 阻塞。 2. **分析内核锁竞争**: ```bash echo 1 > /proc/sys/kernel/lock_stat # 开启锁统计 cat /proc/lock_stat | grep spin_lock # 查看自旋锁争用情况 ``` 3. **优化方向**: - 减少软中断触发频率(如合并网络数据包)。 - 将任务迁移到线程化中断(Threaded IRQ)或工作队列(Workqueue)。 --- #### **四、高级工具与内核调试** 1. **动态追踪(BPF/BCC)**: - **统计软中断耗时分布**: ```bash sudo trace -K 'r::softirq_entry(unsigned int vec) { @[vec] = nsecs; } r::softirq_exit(unsigned int vec) { @[vec] = hist(nsecs - @[vec]); delete(@[vec]); }' ``` - **输出示例**: ``` @[NET_RX]: [0, 1) 12 |@@@@@ [1, 2) 145 |@@@@@@@@@@@@@@@@@@ [2, 4) 89 |@@@@@@@@@@@@ ``` 显示 `NET_RX` 软中断的处理时间分布。 2. **内核源码调试**: - **关键代码路径**: - 软中断触发:`raise_softirq()` → `__raise_softirq_irqoff()` - 软中断处理:`do_softirq()` → `__do_softirq()` - **调试方法**: ```bash echo 'p:softirq_raise __raise_softirq_irqoff vec=%di' > /sys/kernel/debug/tracing/kprobe_events echo 1 > /sys/kernel/debug/tracing/events/kprobes/softirq_raise/enable ``` --- ### **总结与建议** 1. **分析流程总结**: - **Step 1**:通过 `/proc/softirqs` 和 `mpstat` 定位高负载软中断类型和 CPU。 - **Step 2**:使用 `perf` 或 `ftrace` 追踪热点函数和延迟。 - **Step 3**:结合硬件中断分布(`/proc/interrupts`)优化负载均衡。 2. **常见优化手段**: - 调整中断亲和性(IRQ Affinity)。 - 启用多队列网卡(RSS/RPS)。 - 优化内核参数(如 `net.core.netdev_budget`)。 - 避免在软中断上下文中执行耗时操作(改用工作队列)。 如需深入分析特定场景,请提供 `/proc/softirqs` 输出和 `perf` 热点函数列表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值