软中断与硬中断

明白了,你希望得到一个系统、完整、连贯的图示或说明,同时要明确软中断与硬中断的关系,并且细化整个流程和内存关联。我们可以分几个层次来梳理:


1️⃣ 中断概念与关系

硬中断(Hardware Interrupt)

  • 触发源:外设(键盘、鼠标、网络卡、定时器等)

  • 特点:异步触发,CPU外部信号,必须立即响应

  • 处理流程:由CPU硬件捕获 → 切换到内核 → 保存上下文 → ISR处理 → 恢复上下文

软中断(Software Interrupt)

  • 触发源:程序执行特权指令或系统调用(如 int 0x80syscall

  • 特点:同步触发,由程序主动请求,常用于从用户态切换到内核态

  • 处理流程:CPU捕获中断指令 → 切换到内核 → 保存上下文 → 系统调用处理 → 恢复上下文

软中断 vs 硬中断关系

  • 区别

    • 硬中断来源外部设备,异步

    • 软中断来源CPU指令,程序主动触发,同步

  • 联系

    • 都需要切换到内核态

    • 都要保存上下文和使用内核栈

    • 都可以触发调度/上下文切换

    • 从内核角度看,本质都是中断机制(只是触发源不同)


2️⃣ 流程图(详细、连贯)

我将硬中断、软中断和上下文切换放在同一大流程中,标注触发条件和上下文操作。

        ┌───────────────────────┐
        │      用户程序执行      │
        └───────────────────────┘
                    │
                    │ 1. 程序执行软中断/syscall?
                    │
           ┌────────┴────────┐
           │                 │
           ▼                 ▼
   [软中断触发]           [硬中断触发]
    (CPU指令)           (外设信号)
           │                 │
           └───────┬─────────┘
                   ▼
          CPU切换到内核态
                   │
           保存当前上下文
      ┌───────────┴─────────────┐
      │                           │
      ▼                           ▼
 保存用户栈和寄存器           保存内核栈/寄存器(如中断嵌套)
      │                           │
      ▼                           ▼
 执行软中断处理/系统调用        执行硬中断ISR
      │                           │
      ▼                           ▼
  是否触发调度? <──────────────────┘
      │
      ▼
  调度器选择下一进程/线程
      │
      ▼
  保存/切换上下文
      │
      ▼
  切换地址空间(如果是跨进程切换)
      │
      ▼
  恢复新进程/线程上下文
      │
      ▼
  CPU返回用户态程序

3️⃣ 内存布局(流程对应)

用户进程内存

高地址
+-------------------+
| 线程栈(独立)     |
+-------------------+
| 堆(共享进程内存) |
+-------------------+
| BSS段(未初始化全局变量)|
+-------------------+
| 数据段(已初始化全局变量)|
+-------------------+
| 代码段(Text Segment)     |
低地址

线程/进程关系

  • 共享:堆、代码段、数据段

  • 独立:线程栈、寄存器上下文

内核内存

高地址
+---------------------------+
| 内核栈(每进程/线程独立)|
+---------------------------+
| 内核数据区(内核堆/全局数据)|
+---------------------------+
| 内核代码段                  |
低地址

中断和上下文切换对内存的影响

  • 硬中断、软中断 → 使用内核栈、访问内核数据

  • 上下文切换 → 保存用户栈 + 内核栈 + 寄存器

  • 进程切换 → 切换地址空间


4️⃣ 总结逻辑

  1. 用户程序 → 可触发软中断

  2. 外设 → 可触发硬中断

  3. 两者 → 都进入内核态,使用内核栈,保存上下文

  4. 中断处理 → 可触发调度,上下文切换

  5. 调度器 → 保存旧进程上下文 → 加载新进程上下文 → 切换地址空间(如果跨进程)

  6. 恢复执行 → 返回用户态


我可以基于这个结构画出一张完整的图像版流程图,把硬中断、软中断、上下文切换用户/内核内存布局都整合到一张图里,箭头清晰表示触发和流程,非常适合学习或汇报使用。

我现在直接帮你画吗?

image

 

image

 

  • idle:CPU 处于空闲状态,没有任务需要处理。
  • user:CPU 在执行用户空间的应用程序代码。
  • system:CPU 在执行内核空间的系统代码。
  • nice:CPU 在执行低优先级(nice)的用户空间进程。
  • iowait:CPU 空闲,但因为等待 I/O 操作(如磁盘或网络)完成而无法执行其他任务。
  • irq:CPU 在处理硬件中断(Hardware Interrupts)。
  • softirq:CPU 在处理软件中断(Software Interrupts)。
  • steal:在虚拟化环境中,CPU 被其他虚拟机(如宿主机或其他虚拟机)占用的时间。

注意:softirq和system这种只能扩容cpu(想排查如下难度非常高)或者驱逐部分服务到其他机器上均衡负载。具体参考如下:核心计算器+中断+总线

负载类型定位重点工具溯源关键点
%soft 软中断类型及软中断执行代码 /proc/softirqstop -H ksoftirqdperf record/report 网卡驱动软中断处理路径
%si 触发硬中断的设备 /proc/interruptsperf record/report 设备中断号及驱动中断处理路径
%iowait 磁盘性能或内存换页压力 iostatvmstatiotoptrace-cmdperf 磁盘 I/O 延迟或内存 swap 触发的阻塞 syscall
%sys 频繁系统调用、内核执行热点 straceperf topperf report 内核系统调用路径和耗时函数
%user 用户态 CPU 消耗 top -Hperf record 用户态热点函数

 

 

#!/bin/bash
# ==========================================================
# Linux 软中断(softirq)统计与诊断脚本
# 完全动态检测 CPU 数量和软中断类型
# ==========================================================

# 检查 /proc/softirqs 是否存在
if [[ ! -f /proc/softirqs ]]; then
    echo "错误: /proc/softirqs 文件不存在或无法读取"
    exit 1
fi

# 使用 awk 处理软中断统计
awk '
BEGIN {
    # 初始化数组
    delete softirq_counts
    delete softirq_names
    num_types = 0
}

NR == 1 {
    # 第一行 CPU header,动态获取 CPU 数量
    ncpus = NF - 1
    next
}

{
    # 获取当前行软中断类型
    type = $1
    gsub(/:/, "", type)  # 移除冒号

    # 新类型加入列表
    if (!(type in softirq_counts)) {
        num_types++
        softirq_names[num_types] = type
    }

    # 累加每个 CPU 的计数
    sum = 0
    for (i = 2; i <= NF; i++) sum += $i
    softirq_counts[type] += sum
}

END {
    # 计算总数
    total = 0
    for (type in softirq_counts) total += softirq_counts[type]

    if (total == 0) {
        print "错误: 未读取到任何软中断数据"
        print "可能原因:"
        print "  1. /proc/softirqs 文件格式不符合预期"
        print "  2. 没有足够的权限读取文件"
        print "  3. 系统未产生任何软中断"
        exit 1
    }

    # 打印报告头
    printf "%-12s %15s %10s\n", "中断类型", "总计", "占比"
    printf "==========================================\n"

    # 按计数排序输出
    n = asorti(softirq_counts, sorted_types, "@val_num_desc")
    for (i = 1; i <= n; i++) {
        t = sorted_types[i]
        c = softirq_counts[t]
        ratio = (c / total) * 100
        printf "%-12s %15d %9.2f%%\n", t, c, ratio
    }

    printf "==========================================\n"
    printf "%-12s %15d %10s\n\n", "总计", total, "100.00%"

    # 添加诊断建议
    print "诊断建议:"
    for (i = 1; i <= n; i++) {
        t = sorted_types[i]
        if (softirq_counts[t] / total > 0.3) {
            printf "高负载类型: %s (%.2f%%)\n", t, (softirq_counts[t]/total)*100
            print_suggestions(t)
        }
    }
}

# 诊断建议函数
function print_suggestions(t) {
    if (t ~ /NET_/) {
        print "  - 网络优化建议:"
        print "    * 检查网络负载: ethtool -S eth0"
        print "    * 增加队列数量: ethtool -L eth0 combined <N>"
        print "    * 启用RSS: ethtool -X eth0 equal <N>"
        print "    * 检查网络中断均衡: cat /proc/interrupts | grep eth0"
    }
    else if (t ~ /TIMER/) {
        print "  - 定时器优化建议:"
        print "    * 检查时钟源: cat /sys/devices/system/clocksource/clocksource0/current_clocksource"
        print "    * 考虑使用TSC时钟源: echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource"
        print "    * 调整tickless模式: 在内核启动参数添加 nohz_full=cpulist"
    }
    else if (t ~ /RCU/) {
        print "  - RCU优化建议:"
        print "    * 调整RCU参数: sysctl -w kernel.rcupdate.rcu_cpu_stall_timeout=30"
        print "    * 检查RCU状态: cat /proc/rcu/rcu*/gpstats"
        print "    * 考虑调整RCU回调批处理大小: 修改内核参数 rcutree.rcu_min_cb_interval"
    }
    else if (t ~ /SCHED/) {
        print "  - 调度优化建议:"
        print "    * 调整调度粒度: sysctl -w kernel.sched_min_granularity_ns=1000000"
        print "    * 检查调度统计: cat /proc/schedstat"
        print "    * 考虑使用性能调控器: cpupower frequency-set -g performance"
    }
    else if (t ~ /TASKLET/) {
        print "  - Tasklet优化建议:"
        print "    * 检查驱动负载: cat /proc/interrupts"
        print "    * 考虑升级相关硬件驱动"
        print "    * 检查ksoftirqd进程CPU使用: top -p $(pgrep ksoftirqd | tr \"\\n\" \",\" | sed \"s/,$//\")"
    }
    else if (t ~ /BLOCK/ || t ~ /IO_/) {
        print "  - 块设备/IO优化建议:"
        print "    * 检查存储设备队列深度: cat /sys/block/sd*/queue/nr_requests"
        print "    * 优化IO调度器: echo deadline > /sys/block/sd*/queue/scheduler"
        print "    * 检查磁盘负载: iostat -x 1"
    }
    else {
        print "  - 通用优化建议:"
        print "    * 检查系统日志: dmesg | grep -i error"
        print "    * 考虑升级内核版本"
        print "    * 检查硬件健康状况: sensors, smartctl -a /dev/sdX"
    }
    print ""
}
' /proc/softirqs

image

 

 

 

#!/bin/bash
# 硬件中断统计终极版(100%动态适配所有Linux系统)
 
awk '
BEGIN {
    print "中断统计报告(按中断控制器和设备分类)"
    print "================================================================"
    printf "%-20s %-30s %12s %10s\n", "控制器类型", "设备/描述", "中断计数", "占比(%)"
    print "----------------------------------------------------------------"
}
NR == 1 {
    ncpus = NF - 1
    next
}
/^[ \t]*$/ { next }
/:/ && $1 ~ /^[0-9]+:/ {
    irq_num = $1
    irq_num = substr(irq_num, 1, length(irq_num) - 1)
 
    # 统计中断数
    sum = 0
    for (i = 2; i <= 1 + ncpus; i++) {
        sum += $i
    }
    if (sum == 0) next
 
    # 获取控制器类型和设备名(倒数第2列和最后一列)
    controller = $(NF - 1)
    device = $NF
 
    # 合并部分字段,比如 MSI控制器带空格的情况
    if (controller ~ /^PCI|MSI|IO-APIC|Reschedule|Function/) {
        controller = ""
        for (j = NF - 2; j >= 2 + ncpus && $(j) !~ /^[0-9]+$/; j--) {
            controller = $(j) " " controller
        }
        gsub(/[ \t]+$/, "", controller)
    }
 
    total_irqs += sum
    key = controller "|" device
    counts[key] += sum
    ctrl_totals[controller] += sum
    raw_lines[key] = $0
}
END {
    if (total_irqs == 0) {
        print "错误:未能读取有效中断数据"
        print "可能原因:"
        print "1. /proc/interrupts 文件格式不符合预期"
        print "2. 当前系统没有产生硬件中断"
        print "3. 需要 root 权限访问该数据"
        print "原始文件样例:"
        system("head -n 5 /proc/interrupts")
        exit 1
    }
 
    PROCINFO["sorted_in"] = "@val_num_desc"
    for (key in counts) {
        split(key, parts, "|")
        ratio = (counts[key] * 100.0) / total_irqs
        printf "%-20s %-30s %12d %9.2f%%\n",
               parts[1], parts[2], counts[key], ratio
    }
 
    print "\n中断控制器汇总:"
    print "-----------------------------------------------"
    PROCINFO["sorted_in"] = "@val_num_desc"
    for (ctrl in ctrl_totals) {
        ratio = (ctrl_totals[ctrl] * 100.0) / total_irqs
        printf "%-20s %12d %9.2f%%\n", ctrl, ctrl_totals[ctrl], ratio
    }
 
    print "==============================================="
    printf "%-20s %12d %10s\n", "总中断数", total_irqs, "100.00%"
 
    print "\n调试信息:"
    print "1. 检测到", ncpus, "个CPU核心"
    print "2. 共处理了", length(counts), "个有效中断源"
}
' /proc/interrupts

image

 

类型触发方式是否可睡眠场景常见表现
硬件中断 外设触发 驱动响应设备事件 /proc/interrupts
SoftIRQ 内核调度触发 网络栈 / 调度器 / 定时器等高频事件 /proc/softirqs
Tasklet 内核调度触发 简化版软中断,串行 不常见,内核内部使用
Workqueue 内核线程调度 需要睡眠、长时间任务 ps / stack 可观测
#!/bin/bash
# node_analysis_report.sh
# Node CPU/Load/Disk分析报告(完整、整齐对齐、表格化)
# Disk I/O 每隔3秒采样3次,排除 avg-cpu 干扰

echo "============================================"
echo "Node CPU Load Analysis Report"
echo "============================================"

# ------------------- SYSTEM LOAD -------------------
echo "==== SYSTEM LOAD ===="
now=$(date +"%H:%M:%S")
uptime_info=$(uptime -p)
load1=$(uptime | awk -F'load average: ' '{print $2}' | awk -F',' '{print $1}')
cores=$(nproc)

printf "%s up %s, load average: %s\n" "$now" "$uptime_info" "$load1"
printf "CPU cores: %d\n" "$cores"
printf "1-min Load: %s\n\n" "$load1"


# ------------------- 1. TOP CPU PROCESS GROUPS -------------------
echo "============================================"
echo "1. TOP CPU PROCESS GROUPS (by COMMAND)"
printf "%-20s %-8s %-12s %-12s %-12s\n" "COMMAND" "CPU%" "CORES_USED" "PROC_COUNT" "VOL_CTX/NONVOL_CTX"
echo "--------------------------------------------------------------------------------"

ps -eo pid,comm,%cpu --no-headers | \
awk '
{
    pid=$1
    cmd=$2
    cpu=$3
    if (!(pid in pid_seen)) {
        pid_seen[pid]=1
        cpu_sum[cmd]+=cpu
        proc_count[cmd]++

        vol=0
        nonvol=0
        status_file="/proc/"pid"/status"
        while((getline line < status_file) > 0){
            if(line ~ /^voluntary_ctxt_switches:/) {split(line,a," "); vol=a[2]}
            if(line ~ /^nonvoluntary_ctxt_switches:/) {split(line,a," "); nonvol=a[2]}
        }
        close(status_file)

        vol_ctx[cmd]+=vol
        nonvol_ctx[cmd]+=nonvol
    }
}
END {
    for(c in cpu_sum){
        cores_used=cpu_sum[c]/100
        printf "%-20s %-8.2f %-12d %-12s %-12s\n", c, cpu_sum[c], cores_used, proc_count[c], vol_ctx[c]"/"nonvol_ctx[c]
    }
}' | sort -k2 -nr | head -20


# ------------------- 2. PROCESS GROUP COUNTS WITH THREADS -------------------
echo
echo "============================================"
echo "2. PROCESS GROUP COUNTS WITH THREADS"
printf "%-20s %-12s %-12s\n" "COMMAND" "PROC_COUNT" "THREAD_COUNT"
echo "------------------------------------------------"

declare -A PROC_COUNT
declare -A THREAD_COUNT

while read -r pid cmd; do
    PROC_COUNT["$cmd"]=$(( ${PROC_COUNT["$cmd"]:-0} + 1 ))
    if [[ -d "/proc/$pid/task" ]]; then
        threads=$(ls -1 /proc/$pid/task | wc -l)
        THREAD_COUNT["$cmd"]=$(( ${THREAD_COUNT["$cmd"]:-0} + threads ))
    fi
done < <(ps -eo pid,comm --no-headers)

for cmd in "${!PROC_COUNT[@]}"; do
    printf "%-20s %-12d %-12d\n" "$cmd" "${PROC_COUNT[$cmd]}" "${THREAD_COUNT[$cmd]}"
done | sort -k2 -nr | head -30


# ------------------- 3. PROCESS NAME/STATE SUMMARY  ←(新增模块)
echo
echo "============================================"
echo "3. PROCESS NAME / STATE STATISTICS"
echo "COUNT  NAME                     STATE"
echo "------------------------------------------------"

for pid in /proc/[0-9]*; do
    status="$pid/status"
    [[ -r "$status" ]] || continue

    name=$(grep "^Name:" "$status" | awk '{print $2}')
    state=$(grep "^State:" "$status" | awk '{print $2}')

    [[ -n "$name" && -n "$state" ]] && echo "$name $state"
done \
| sort \
| uniq -c \
| sort -nk1 \
| awk '{printf "%-6s %-25s %-10s\n", $1, $2, $3}'


# ------------------- 4. DISK I/O STATISTICS (原 3,编号后移)
echo
echo "============================================"
echo "4. DISK I/O STATISTICS (2s interval, 2 samples)"
echo "-------------------------------------------------------------------------------------"

iostat -xz 2 2


# ------------------- 5. ANALYSIS HINTS -------------------
echo
echo "============================================"
echo "5. ANALYSIS HINTS"
echo "--------------------------------------------"
echo "- CPU% 高的进程组可能是性能瓶颈,关注 CORES_USED 和 PROC_COUNT"
echo "- Load 高但 CPU% 不高,可能是 I/O 等待或阻塞"
echo "- 如果 load > CPU cores,总体系统可能 CPU 饱和"
echo "- 检查 DISK I/O %util、await,分析是否存在瓶颈"
echo "- 多线程进程已去重 PID,避免重复累加 CPU%"
echo "- 可结合 top/htop/perf 等工具进一步分析热点函数和系统瓶颈"
字段含义
COMMAND 进程名(按前缀聚合,比如 styx-cloudrule-* 会合并)
CPU% 聚合的 CPU 使用率(多个同名进程求和)
CORES_USED CPU%/100,表示大概占用了多少核
PROC_COUNT 参与聚合的进程数量
VOL_CTX 自愿上下文切换数(voluntary context switch)
NONVOL_CTX 非自愿上下文切换(被调度抢占)

posted on 2026-01-16 12:02  吃草的青蛙  阅读(5)  评论(0)    收藏  举报

导航