title: 进程调度与中断管理 tags: [Linux内核, 进程调度, 中断管理, 嵌入式, CFS, tasklet] created: 2026-09-16
关联知识: [[01-Cortex-A7架构详解|Cortex-A7架构详解]] · [[freertos-task-scheduling|FreeRTOS任务调度]] · [[freertos-interrupt-time|FreeRTOS中断管理]]
Linux 中进程和线程本质上都由 task_struct 描述,区别在于共享资源的程度:
| 特性 | 进程 (Process) | 线程 (Thread) |
|---|---|---|
| 地址空间 | 独立的 mm_struct | 共享所属进程的地址空间 |
| 资源开销 | fork 时需复制页表,代价较高 | clone 时仅分配栈和 TLS,轻量 |
| 通信方式 | 管道、共享内存、信号等 | 直接读写共享变量 |
| 内核视角 | 都是 task_struct,通过 CLONE 标志区分 | 同左,线程本质是轻量级进程 (LWP) |
嵌入式场景中,多线程比多进程更常见——内存受限、切换开销敏感。
task_struct 是 Linux 内核中最大的数据结构之一(约 6KB,视配置而定),定义在 include/linux/sched.h。核心字段分组:
struct task_struct {
/* --- 标识 --- */
pid_t pid; // 进程 ID (全局唯一)
pid_t tgid; // 线程组 ID (= 主线程 PID)
char comm[TASK_COMM_LEN]; // 进程名称 (16 字节)
/* --- 状态 --- */
volatile long state; // 进程状态 (TASK_RUNNING 等)
int exit_state; // 退出状态 (EXIT_ZOMBIE / EXIT_DEAD)
unsigned int flags; // 进程标志 (PF_KTHREAD, PF_USED_MATH 等)
/* --- 调度 --- */
int prio; // 动态优先级 (0~139)
int static_prio; // 静态优先级 (由 nice 值映射)
int normal_prio; // 普通优先级
unsigned int rt_priority; // 实时优先级 (0~99)
unsigned int policy; // 调度策略 (SCHED_NORMAL / SCHED_FIFO 等)
u64 sum_exec_runtime; // 总执行时间 (纳秒)
u64 vruntime; // 虚拟运行时间 (CFS 核心)
/* --- 内存 --- */
struct mm_struct *mm; // 用户空间内存描述符
struct mm_struct *active_mm; // 当前活跃的内存描述符
/* --- 栈 --- */
void *stack; // 内核栈指针 (通常 8KB / 16KB)
/* --- 家族 --- */
struct task_struct *parent; // 父进程
struct list_head children; // 子进程链表
struct task_struct *group_leader; // 线程组领头进程
/* --- 文件与信号 --- */
struct fs_struct *fs; // 当前工作目录
struct files_struct *files;// 打开的文件表
struct signal_struct *sig; // 信号信息
};
fork()
┌──────────────────────────┐
│ TASK_RUNNING │◄─────── 被调度器选中
│ (就绪 / 正在运行) │
└────┬──────────────┬──────┘
│ │
schedule() 被唤醒 / 时间片到
│ │
▼ │
┌─────────────┐ │
│ 可中断睡眠 │───────┘
│ TASK_INTERRUPTIBLE │ (等待事件,可被信号唤醒)
└──────┬──────┘
│ 不可被信号唤醒
▼
┌──────────────────┐
│ 不可中断睡眠 │ (等待 I/O,不可被任何信号唤醒)
│ TASK_UNINTERRUPTIBLE│
└──────┬───────────┘
│
▼
┌─────────────┐ wait() ┌─────────────┐
│ EXIT_ZOMBIE │ ──────────► │ EXIT_DEAD │
│ (僵尸进程) │ 释放资源 │ (彻底消失) │
└─────────────┘ └─────────────┘
TASK_STOPPED — 被 SIGSTOP 暂停 (调试 / Ctrl+Z)
TASK_TRACED — 被 ptrace 跟踪
嵌入式注意: 进程陷入
TASK_UNINTERRUPTIBLE不响应任何信号,若出现在驱动 I/O 路径中可能导致系统"假死"。排查时检查D状态进程。
| 策略 | 常量 | 适用场景 | 调度类 |
|---|---|---|---|
| 普通分时 | SCHED_NORMAL |
一般用户进程 | CFS |
| 批处理 | SCHED_BATCH |
吞吐量敏感的后台任务 | CFS |
| 空闲 | SCHED_IDLE |
极低优先级,仅系统空闲时运行 | CFS |
| 先来先服务 | SCHED_FIFO |
硬实时任务(无时间片,主动让出) | RT |
| 时间片轮转 | SCHED_RR |
硬实时任务(同优先级间轮转) | RT |
调度类继承链:
stop_sched_class→dl_sched_class→rt_sched_class→fair_sched_class→idle_sched_class。高优先级类的进程始终优先于低优先级类。
CFS 是 Linux 默认的普通进程调度器(SCHED_NORMAL / SCHED_BATCH),核心思想:用虚拟运行时间 (vruntime) 衡量每个进程的"公平份额",始终调度 vruntime 最小的进程。
实际运行时间 × (NICE_0_WEIGHT / 该进程权重) = vruntime 增量
sched_prio_to_weight[] 数组映射,共 40 级(对应 nice -20 ~ 19)权重表(部分):
| nice 值 | 权重 | nice 值 | 权重 |
|---|---|---|---|
| -20 | 88761 | 0 | 1024 |
| -10 | 33554 | 10 | 256 |
| -5 | 52429 | 19 | 15 |
CFS 使用红黑树按 vruntime 排序所有可运行进程:
[vruntime=500]
/ \
[vruntime=300] [vruntime=700]
/ \ / \
[200] [400] [600] [800]
vruntime 使用 u64 存储,溢出时通过 calc_delta_mine() 重新计算调度周期 (sysctl_sched_latency) = 6ms (默认)
最小粒度 (sysctl_sched_min_granularity) = 0.75ms
每个进程分配的时间片 = max(调度周期 / 可运行进程数, 最小粒度)
当进程数超过 8 时,6ms / 8 = 0.75ms 触发最小粒度限制,此时 CFS 退化为近似轮转。
进程睡眠醒来后,其 vruntime 可能远小于当前树中其他进程的 vruntime,导致它独占 CPU。CFS 的处理:
/* kernel/sched/fair.c: place_entity() */
if (sleeping) {
/* 将新唤醒进程的 vruntime 设为树中最小值减去偏移 */
se->vruntime = avg_vruntime() - sysctl_sched_latency;
}
这确保睡眠进程获得合理的时间份额,但不会"抢夺"过多 CPU。
Linux 优先级分三层,数值越小优先级越高:
实时优先级 (0~99) ← SCHED_FIFO / SCHED_RR 使用
↓
普通优先级 (100~139) ← SCHED_NORMAL / SCHED_BATCH 使用
↓
nice 值 -20 ~ +19 对应 100~139
优先级换算关系:
prio (动态优先级) = max(普通优先级, 实时优先级)
rt_priority (用户态) = 99 - prio (实时进程时)
nice = prio - 120 (普通进程时)
nice 值是用户态接口,范围 -20(最高)到 +19(最低),每级差 1 个权重等级。
| 参数 | 默认值 | 说明 |
|---|---|---|
sysctl_sched_latency |
6ms | 所有进程至少运行一遍的目标周期 |
sysctl_sched_min_granularity |
0.75ms | 每个进程的最小运行时间片 |
sysctl_sched_wakeup_granularity |
1ms | 唤醒抢占粒度 |
sched_nr_latency |
8 | 调度周期内可轮转的最大进程数 |
实时进程使用固定时间片(FIFO 不用时间片),优先级 0~99,通过
SCHED_FIFO/SCHED_RR设置。实时进程始终优先于 CFS 进程。
Linux 内核通过调度类实现可插拔调度策略:
struct sched_class {
const struct sched_class *next; // 下一级调度类
void (*enqueue_task)(...); // 入队
void (*dequeue_task)(...); // 出队
void (*pick_next_task)(...); // 选择下一个进程
void (*put_prev_task)(...); // 放回上一个进程
int (*check_preempt_curr)(...); // 检查是否可抢占
/* ... */
};
五级调度类从高到低:
SCHED_DEADLINESCHED_FIFO / SCHED_RRSCHED_NORMAL / SCHED_BATCH
pick_next_task()从最高级调度类开始遍历,直到找到可运行的进程。这就是为什么实时进程总是抢占普通进程。
| 系统调用 | 共享资源 | 典型用途 |
|---|---|---|
fork() |
无(写时复制 COW) | 创建独立子进程 |
vfork() |
地址空间 + 栈 + 文件描述符 | fork 后立即 exec,避免复制 |
clone() |
通过 flags 精确控制 | 创建线程、容器、子进程 |
clone() 的 flags 控制共享哪些资源:
#define CLONE_VM 0x00000100 // 共享内存空间(线程)
#define CLONE_FS 0x00000200 // 共享文件系统信息
#define CLONE_FILES 0x00000400 // 共享文件描述符表
#define CLONE_SIGHAND 0x00000800 // 共享信号处理
#define CLONE_THREAD 0x00010000 // 线程组(同一线程组内共享)
glibc 的
pthread_create()底层调用clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, ...)。
内核统一入口是 kernel_clone()(旧版为 do_fork()),流程:
用户态 fork()/clone()
│
▼
kernel_clone()
│
├── copy_process() // 核心:复制进程描述符
│ ├── dup_task_struct() // 复制 task_struct + 内核栈
│ ├── copy_creds() // 复制权限信息
│ ├── copy_mm() // 复制内存映射 (COW)
│ ├── copy_files() // 复制文件描述符
│ ├── copy_fs() // 复制文件系统
│ ├── copy_sighand() // 复制信号处理
│ ├── copy_signal() // 复制信号信息
│ └── sched_fork() // 初始化调度相关字段
│
└── wake_up_new_task() // 将子进程加入调度器
/* fork 时不复制物理页面,仅标记页表为只读 */
/* 子进程或父进程写入时触发缺页中断 */
/* 缺页处理函数为该页分配新的物理帧并复制内容 */
static int copy_page_range(...) {
/* 仅复制页表项,设置 COW 标志 */
pte_t *src_pte = pte_offset_map(...);
if (pte_present(*src_pte)) {
pte_set_wrprotect(src_pte); // 父进程页表改为只读
copy_pte(dst, src_pte); // 子进程映射同一页,只读
}
}
嵌入式优化: COW 避免了 fork 的物理内存复制开销。对于内存 < 256MB 的嵌入式系统,频繁 fork 仍需谨慎——页表复制本身也有开销。
进程退出分两个阶段:
exit() / _exit()
│
▼
do_exit()
├── 释放当前线程持有的资源
├── 设置 PF_EXITING 标志
├── exit_mm() // 释放内存映射
├── exit_files() // 关闭文件描述符
├── exit_fs() // 释放文件系统
├── exit_signal() // 发送 SIGCHLD 给父进程
├── exit_state = EXIT_ZOMBIE // 变为僵尸
└── schedule() // 最后一次调度
│
▼
父进程 wait() / waitpid()
│
▼
release_task()
├── 释放 task_struct
├── 释放内核栈
└── 从进程表中移除
僵尸进程: 进程已退出但父进程尚未
wait()回收,其task_struct仍占用内核内存。嵌入式系统若不wait()子进程,会导致进程表泄漏。
ARM Cortex-A7 使用异常向量表(固定地址 0xFFFF0000 或 0x00000000,由 VBAR 寄存器配置):
/* arch/arm/kernel/entry-armv.S */
.section .vectors, "ax", %progbits
.Lvectors:
W(b) vector_swi /* 0x00: SVC 异常 (系统调用) */
W(b) vector_und /* 0x04: 未定义指令 */
W(nop) /* 0x08: 预留 */
W(b) vector_pabt /* 0x0C: 指令预取中止 */
W(b) vector_dabt /* 0x10: 数据访问中止 */
nop /* 0x14: 预留 */
W(b) vector_irq /* 0x18: IRQ 中断 */
W(b) vector_fiq /* 0x1C: FIQ 快速中断 */
Cortex-A7 有 8 个异常入口,IRQ 占一个。中断号通过 GIC (Generic Interrupt Controller) 确定,而非向量表偏移。
GIC (Generic Interrupt Controller) 是 ARM 的标准中断控制器,GIC-400 支持最多 1020 个 SPI 中断:
┌─────────────────────────────────────────────┐
│ GIC-400 │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Distributor │ │ CPU Interface │ │
│ │ (分发器) │──│ (CPU 接口) │ │
│ │ │ │ │ │
│ │ GICD_ISENABLER │ ICC_IAR (中断确认) │ │
│ │ GICD_ICENABLER │ ICC_EOIR (中断结束) │ │
│ │ GICD_IPRIORITYR │ ICC_PMR (优先级) │ │
│ │ GICD_ITARGETSR │ ICC_CTLR (控制) │ │
│ │ GICD_ICFGR │ │ │
│ └──────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────┘
│ │
IRQ 线连接 CPU 接口连接
(SPI / PPI / SGI) (CPU0 / CPU1 / ...)
中断类型:
| 类型 | 编号范围 | 说明 | 典型 |
|---|---|---|---|
| SGI | 0~15 | 软件触发中断 (核间通信) | IPI |
| PPI | 16~31 | 私有外设中断 (每核独有) | 定时器 |
| SPI | 32~1020 | 共享外设中断 | UART、GPIO、DMA |
硬件中断触发 (IRQ)
│
▼
CPU 保存现场 (R0-R15, CPSR → SPSR_irq)
│
▼
切换到 IRQ 模式,跳转 vector_irq
│
▼
gic_handle_irq()
├── 读取 ICC_IAR 获取中断号
├── 查找注册的 irq_desc
│
▼
handle_irq_event() ← 上半部 (Hardirq)
│ ├── 执行注册的 irq_handler
│ ├── 处理紧急事务 (读/清中断标志)
│ └── 调度下半部 (如需)
│
▼
写 ICC_EOIR 通知 GIC 中断结束
│
▼
检查 softirq_pending 标志
│
▼ (中断返回前)
__do_softirq() ← 下半部 (Softirq)
│ ├── 执行已注册的 softirq
│ ├── 执行 tasklet
│ └── 时间片用完则在 softirqd 线程中执行
│
▼
恢复现场 (SPSR_irq → CPSR, R0-R15 恢复)
│
▼
返回被中断的进程/线程
/* 申请中断 — 成功返回 0 */
int request_irq(unsigned int irq,
irq_handler_t handler, // 中断处理函数
unsigned long flags, // 标志
const char *name, // /proc/interrupts 中显示的名字
void *dev); // 传给 handler 的 cookie
/* 释放中断 — 返回注册时的 handler */
void *free_irq(unsigned int irq, void *dev);
/* 中断标志 */
#define IRQF_SHARED 0x00000800 // 共享中断线
#define IRQF_ONESHOT 0x00002000 // 中断线在下半部完成前保持屏蔽
#define IRQF_NO_THREAD 0x00008000 // 不创建线程化的中断处理
#define IRQF_NO_SUSPEND 0x00004000 // 系统挂起时不屏蔽此中断
/* 禁止/使能中断 (本地 CPU) */
local_irq_disable(); // 关中断 (CPSR.I=1)
local_irq_enable(); // 开中断 (CPSR.I=0)
/* 保存并关中断 */
unsigned long flags;
local_irq_save(flags); // 保存当前 CPSR 并关中断
local_irq_restore(flags); // 恢复 CPSR
/* 禁止/使能指定中断线 (其他 CPU 不受影响) */
disable_irq(irq); // 同步等待正在执行的 handler 完成
disable_irq_nosync(irq); // 不等待,立即返回
enable_irq(irq);
disable_irqvslocal_irq_disable: 前者仅屏蔽一条中断线,其他中断不受影响;后者屏蔽本 CPU 所有中断,应尽量少用。
| 特性 | 上半部 (Hardirq) | 下半部 (Softirq / tasklet / workqueue) |
|---|---|---|
| 执行环境 | 中断上下文,不可睡眠 | Softirq/tasklet: 不可睡眠;workqueue: 可睡眠 |
| 延迟要求 | 极短 (< 100μs) | 可较长 |
| 并发性 | 仅在触发的 CPU 上运行 | Softirq: 所有 CPU 可同时运行;tasklet: 同一 tasklet 不并发 |
| 典型操作 | 读硬件寄存器、清中断、唤醒下半部 | 数据拷贝、协议处理、延迟工作 |
软中断是 Linux 内核的延迟处理机制,编译时静态定义,运行在中断返回路径中:
/* 定义的软中断类型 (include/linux/interrupt.h) */
enum {
HI_SOFTIRQ = 0, // 高优先级 tasklet
TIMER_SOFTIRQ, // 定时器
NET_TX_SOFTIRQ, // 网络发送
NET_RX_SOFTIRQ, // 网络接收
BLOCK_SOFTIRQ, // 块设备
IRQ_POLL_SOFTIRQ, // 中断轮询
TASKLET_SOFTIRQ, // tasklet
SCHED_SOFTIRQ, // 调度器负载均衡
HRTIMER_SOFTIRQ, // 高精度定时器 (未使用)
RCU_SOFTIRQ, // RCU 回调
NR_SOFTIRQS
};
/* 注册软中断处理函数 */
open_softirq(TIMER_SOFTIRQ, timer_softirq);
/* 触发软中断 */
raise_softirq(NET_TX_SOFTIRQ);
/* 软中断处理函数原型 */
void (*action)(struct softirq_action *);
软中断执行流程:
中断返回 / ksoftirqd 被唤醒
│
▼
__do_softirq()
├── 检查 softirq_pending 位图
├── 循环执行所有 pending 的 softirq
│ ├── while ((h = softirq_vec[i]) && pending)
│ │ h->action(h) // 执行处理函数
│ └── 清除 pending 位
├── 若执行时间超过 2ms 或仍有 pending → 唤醒 ksoftirqd
└── 重新检查 pending(防止丢失)
ksoftirqd: 每个 CPU 有一个
ksoftirqd内核线程 (优先级 nice 19)。当软中断处理耗时过长或持续有新 softirq,内核切换到 ksoftirqd 中执行,避免长时间关中断。
tasklet 基于软中断实现,是驱动开发中最常用的下半部机制:
/* tasklet 结构 */
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long state; // TASKLET_STATE_SCHED / TASKLET_STATE_RUN
atomic_t count; // 引用计数 (0=使能, >0=禁用)
void (*func)(unsigned long); // 处理函数
unsigned long data; // 参数
};
/* 静态声明 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, (unsigned long)&my_data);
/* 动态声明 */
struct tasklet_struct my_tasklet;
tasklet_init(&my_tasklet, my_tasklet_func, (unsigned long)&my_data);
/* 处理函数原型 */
void my_tasklet_func(unsigned long data) {
/* 此处不可睡眠 */
}
/* 调度 tasklet (可在中断上下文调用) */
tasklet_schedule(&my_tasklet);
/* tasklet 使用示例 — 网卡驱动 */
static irqreturn_t nic_irq_handler(int irq, void *dev_id) {
struct nic_device *ndev = dev_id;
/* 上半部:仅读取中断状态并清中断 */
u32 isr = readl(ndev->regs + ISR);
writel(isr, ndev->regs + ISR);
/* 将 ISR 传给下半部 */
ndev->irq_status = isr;
/* 调度 tasklet 处理后续工作 */
tasklet_schedule(&ndev->tasklet);
return IRQ_HANDLED;
}
static void nic_tasklet_handler(unsigned long data) {
struct nic_device *ndev = (struct nic_device *)data;
/* 下半部:处理接收数据包、更新统计等耗时操作 */
if (ndev->irq_status & RX_INT)
nic_rx_poll(ndev);
if (ndev->irq_status & TX_INT)
nic_tx_complete(ndev);
}
tasklet 特性: 同一 tasklet 实例不会在多个 CPU 上并发执行,但不同 tasklet 可以并行。
count > 0时 tasklet 被禁用。
工作队列运行在内核线程上下文(worker 线程),可以睡眠,适合需要阻塞操作的延迟工作:
/* 静态声明工作项 */
static void my_work_func(struct work_struct *work);
static DECLARE_WORK(my_work, my_work_func);
/* 动态声明 */
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
/* 工作处理函数 — 可以睡眠 */
static void my_work_func(struct work_struct *work) {
/* 可以调用 msleep()、mutex_lock()、kmalloc(GFP_KERNEL) 等 */
struct my_data *data = container_of(work, struct my_data, work);
msleep(100); // 完全合法
}
/* 调度工作 */
schedule_work(&my_work);
/* 取消工作 (等待执行完毕) */
cancel_work_sync(&my_work);
/* 延迟工作 — N jiffies 后执行 */
struct delayed_work my_dwork;
INIT_DELAYED_WORK(&my_dwork, my_work_func);
schedule_delayed_work(&my_dwork, HZ); // 1 秒后执行
cancel_delayed_work_sync(&my_dwork);
| 机制 | 执行上下文 | 可睡眠 | 并发性 | 延迟 | 适用场景 |
|---|---|---|---|---|---|
| softirq | 中断上下文 | 否 | 所有 CPU 可并行 | 极短 | 网络、定时器等内核核心 |
| tasklet | 中断上下文 | 否 | 同一 tasklet 不并发 | 短 | 驱动中断下半部 |
| workqueue | 进程上下文 (worker 线程) | 是 | 工作项串行 | 可长 | 需要阻塞的延迟工作 |
选型建议: 驱动开发首选 tasklet(简单安全);需要睡眠操作时用 workqueue;softirq 一般不直接使用(内核子系统专用)。
| 特性 | IMX6ULL (Cortex-A7) | STM32F4 (Cortex-M4) | RK3568 (Cortex-A55) |
|---|---|---|---|
| 调度器 | Linux CFS | FreeRTOS / 裸机 | Linux CFS (支持 EAS) |
| 核心数 | 单核 | 单核 | 四核 |
| 中断控制器 | GIC-400 | NVIC | GIC-500 |
| 中断嵌套 | 支持 (IRQ) | 支持 (NVIC) | 支持 (IRQ) |
| 实时性 | 有限 (可 PREEMPT_RT 补丁) | 强 (确定性) | 有限 (可 PREEMPT_RT) |
| 调度延迟 | 数毫秒 | 微秒级 | < 1ms (EAS 优化) |
| 负载均衡 | 多核需配置 | 无 | EAS 能效感知自动调度 |
| 能效管理 | 基本 DVFS | 无 | 大小核异构 + OPP |
RK3568 EAS: 能效感知调度 (Energy Aware Scheduling) 根据 CPU 负载动态在大核 (A55@2.0GHz) 和小核间迁移任务,平衡性能与功耗。
NVIC (STM32) GIC (ARM Cortex-A)
├── 嵌套向量中断控制器 ├── 通用中断控制器
├── 最多 240 个中断 ├── 最多 1020+ 个中断
├── 每个中断独立优先级 ├── 分组优先级 + 丢弃优先级
├── 硬件自动压栈 ├── 软件保存/恢复
├── 无核间中断 ├── SGI 支持核间通信
└── 中断延迟 12 周期 └── 中断延迟约 30~50 周期
考察点: 调度算法数据结构
参考答案:
CFS 通过虚拟运行时间 (vruntime) 量化每个进程的 CPU 使用份额。进程每次运行时,实际 CPU 时间按其权重换算为 vruntime 增量——权重高的进程 vruntime 增长慢,获得的 CPU 时间多。调度器始终选择 vruntime 最小的进程运行,确保所有可运行进程的 vruntime 趋于一致。
数据结构选择红黑树而非链表的原因:
rb_leftmost 指针),插入/删除 O(log n)考察点: 中断处理架构
参考答案:
上半部运行在中断上下文,硬件中断触发后立即执行,此时:
下半部运行在软中断或进程上下文,中断已开放:
若全部放在上半部:中断关闭时间过长 → 丢失中断 → 系统不稳定。
考察点: COW 机制
参考答案:
Linux 的 fork 使用写时复制 (Copy-On-Write):
这使 fork 的开销从"复制全部内存"降为"复制页表"。对于嵌入式系统(256MB RAM),页表复制约几十 KB,远优于完整复制。
disable_irq(irq) 和 local_irq_disable() 有什么区别?各自有什么风险?考察点: 中断屏蔽策略
参考答案:
disable_irq(irq) |
local_irq_disable() |
|
|---|---|---|
| 作用范围 | 仅屏蔽指定中断线 | 屏蔽本 CPU 所有中断 |
| 其他 CPU | 不受影响 | 不影响其他 CPU |
| 嵌套调用 | 可嵌套,引用计数 | 不可嵌套(会丢失之前的 flags) |
| 典型场景 | 驱动防止中断重入 | 临界区保护 |
风险:
disable_irq: 若长时间不 enable_irq,其他共享该中断线的设备中断丢失local_irq_disable: 关闭所有中断,系统无法响应时钟、网络等关键中断;嵌入式实时系统中必须限制关中断时间restore/enableTASK_UNINTERRUPTIBLE 进程不响应信号?嵌入式系统中如何避免"假死"?考察点: 进程状态与系统稳定性
参考答案:
TASK_UNINTERRUPTIBLE 的设计目的是等待不可中断的硬件操作完成(如磁盘 I/O)。内核在此状态下不检查信号,直到操作完成被 wake_up_process() 唤醒。这保证了硬件操作的原子性——若允许信号中断,可能导致数据不一致。
嵌入式系统中"假死"场景:
TASK_UNINTERRUPTIBLE 状态下等待硬件响应,但硬件异常未响应避免措施:
wait_event_timeout() 或 wait_for_completion_timeout() 替代无超时等待D 状态进程:ps -eo state,pid,cmd | grep DCONFIG_DETECT_HUNG_TASK 检测 D 状态超时TASK_KILLABLE(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE),既不可中断又可被 SIGKILL 杀死最后更新: 2026-09-17