本篇定位:Linux 内核的"宇宙中心"是
task_struct——所有子系统都围绕它。对于有过轻量级实时操作系统,如FreeRTOS TCB 到 port 层经验的工程师,本篇把"Linux 进程"对你 FreeRTOS 任务的增量讲清:task_struct 比 TCB 胖十倍(含 mm/fs/files/signals)、调度从"优先级抢占"跳到"CFS 公平 + EEVDF"、上下文切换从"切栈"跳到"切地址空间 + 切栈 + 切 fs/files"。读完能写内核线程、能调调度问题、能讲清 CFS vs FreeRTOS 优先级调度的本质差异。
目录
- 一、task_struct:宇宙中心的数据结构
- 1.1 task_struct 有多大
- 1.2 task_struct 核心字段(分组)
- 1.3 对照 FreeRTOS TCB
- 二、进程状态
- 2.1 Linux 进程状态
- 2.2 状态机
- 2.3 关键状态解读
- 2.4 ps 看状态
- 2.5 对照 FreeRTOS 状态
- 三、进程 vs 线程 vs 内核线程
- 3.1 Linux 不区分进程线程(都是 task_struct)
- 3.2 fork / clone / vfork
- 3.3 内核线程(kthread)
- 3.4 PID vs TGID vs TID
- 四、上下文切换(对照 RISC-V trap)
- 4.1 两种"切换"
- 4.2 进程上下文切换做什么
- 4.3 switch_to:切寄存器(架构相关)
- 4.4 对照你 RISC-V trap 切栈
- 4.5 为什么只存 callee-saved
- 五、CFS 调度器(Completely Fair Scheduler)⭐ 核心
- 5.1 CFS 的核心思想
- 5.2 vruntime 怎么算
- 5.3 nice 值与权重
- 5.4 CFS 数据结构:红黑树
- 5.5 调度时序
- 5.6 sched_latency 与 min_granularity
- 5.7 对照 FreeRTOS 优先级调度
- 六、EEVDF(新调度器,Linux 6.6+)
- 6.1 为什么 CFS 升级到 EEVDF
- 6.2 EEVDF 核心思想
- 6.3 对你影响
- 七、调度策略(Scheduling Policy)
- 7.1 三类调度策略
- 7.2 RT 优先级
- 7.3 chrt 改策略
- 7.4 SCHED_DEADLINE(硬实时)
- 八、抢占(Preemption)
- 8.1 抢占点
- 8.2 抢占等级(CONFIG)
- 8.3 关抢占 vs 关中断
- 九、fork / exec / wait(进程生命周期)
- 9.1 fork()
- 9.2 exec()
- 9.3 wait()
- 9.4 孤儿进程与僵尸
- 十、进程关系与会话
- 10.1 进程组 / 会话
- 10.2 守护进程(daemon)
- 十一、常用进程调试
- 11.1 工具
- 11.2 关键 /proc 字段
- 十二、本篇小结
- 速查表
一、task_struct:宇宙中心的数据结构
1.1 task_struct 有多大
- FreeRTOS TCB:几百字节(栈指针/优先级/状态/链表节点)
- Linux
task_struct:约 8000 字节(ARM64/RISC-V 64)
为什么这么大?因为 Linux 进程要独立地址空间 + 文件上下文 + 信号 + cgroup + namespace + 审计…全挂这里。
1.2 task_struct 核心字段(分组)
structtask_struct{// === 标识 ===pid_tpid;// 进程 ID(线程组里是线程 ID)pid_ttgid;// 线程组 ID(进程 ID,用户看到的)structtask_struct*group_leader;// 线程组首领charcomm[TASK_COMM_LEN];// 可执行文件名(16 字符)// === 状态 ===volatilelongstate;// 进程状态(见 §二)intexit_state;intprio,static_prio,normal_prio;// 优先级unsignedintpolicy;// 调度策略(SCHED_NORMAL/FIFO/RR)// === 调度 ===conststructsched_class*sched_class;// 调度类structsched_entityse;// CFS 调度实体(vruntime 等)structsched_rt_entityrt;// RT 调度实体unsignedintrt_priority;cpumask_tcpus_allowed;// 允许跑的 CPU// === 家族 ===structtask_struct*parent;// 父进程structlist_headchildren;// 子进程链表structlist_headsibling;// === 内存(★对照 FreeRTOS 最大差异)===structmm_struct*mm;// 进程地址空间(线程共享)structmm_struct*active_mm;// 内核线程用(借来的 mm)// === 文件 ===structfiles_struct*files;// 打开的文件表structfs_struct*fs;// 文件系统根/root// === 信号 ===structsignal_struct*signal;structsighand_struct*sighand;sigset_tblocked,real_blocked;// === 栈 ===void*stack;// 内核栈(union with thread_info)// === 时间统计 ===u64 utime,stime;// 用户态/内核态时间u64 nvcsw,nivcsw;// 自愿/非自愿切换次数// === namespace/cgroup(容器)===structnsproxy*nsproxy;structcss_set__rcu*cgroups;// ... 还有几百个字段};1.3 对照 FreeRTOS TCB
| 字段 | FreeRTOS TCB | Linux task_struct | 差异 |
|---|---|---|---|
| 栈指针 | pxTopOfStack | sp(在 stack 里) | 同 |
| 优先级 | uxPriority | prio/static_prio | Linux 更复杂(多套) |
| 状态 | eTaskState | state | Linux 状态更多 |
| 链表 | xStateListItem 等 | list_head 多个 | 同思路 |
| 地址空间 | ❌ 无 | mm_struct ★ | Linux 最大增量 |
| 文件表 | ❌ 无 | files_struct ★ | Linux 最大增量 |
| 信号 | ❌ 无 | signal/sighand ★ | Linux 有 |
| 时间统计 | ❌ 无 | utime/stime | Linux 有 |
嵌入式视角:
Linux task_struct 是"胖版 TCB"——多了 mm(地址空间)/files(文件)/signals(信号)/namespace(容器)。因为 Linux 进程要独立地址空间和文件上下文,FreeRTOS 任务全在一个地址空间共享一切。字段虽多,核心思路同(标识/状态/调度/栈),增量在 mm 和 files。
二、进程状态
2.1 Linux 进程状态
#define TASK_RUNNING 0 // 可运行(就绪或正在跑) #define TASK_INTERRUPTIBLE 1 // 可中断睡眠(信号能唤醒) #define TASK_UNINTERRUPTIBLE 2 // 不可中断睡眠(信号不唤醒,等 IO) #define __TASK_STOPPED 4 // 停止(信号 SIGSTOP) #define __TASK_TRACED 8 // 被跟踪(gdb) #define EXIT_ZOMBIE 16 // 僵尸(死了等父收尸) #define EXIT_DEAD 32 // 最终死亡2.2 状态机
fork() │ ▼ ┌─────────┐ 调度选中 ┌─────────┐ │ RUNNING │ ─────────> │ RUNNING │ │ (就绪) │ <───────── │ (运行) │ └────┬────┘ 时间片到 └────┬────┘ │ │ │ 等 IO/信号量 │ 信号/IO 完成 ▼ ▼ ┌──────────────┐ ┌─────────┐ │INTERRUPTIBLE │ ───────>│ RUNNING │ │ (可中断睡) │ 唤醒 │ (就绪) │ └──────────────┘ └─────────┘ │ │ 等 磁盘 IO(D 状态) ▼ ┌────────────────┐ │UNINTERRUPTIBLE │ 信号唤不醒,只能 IO 完成 │ (不可中断睡) │ └────────────────┘2.3 关键状态解读
- TASK_RUNNING:就绪(在运行队列)或正在跑(单 CPU 同时只有一个"跑")
- TASK_INTERRUPTIBLE:可中断睡眠,等事件(信号量/IO/信号),信号能打断
- TASK_UNINTERRUPTIBLE:
D状态,等磁盘 IO 等,信号唤不醒——这是"杀不掉"的进程(kill -9 也没用) - TASK_ZOMBIE:死了但父没
wait(),占 PID 和退出码,等收尸
2.4 ps 看状态
ps-eopid,stat,comm# STAT 列:# R=running S=interruptible sleep D=uninterruptible sleep# T=stopped Z=zombie X=dead# + 前台 s 会话首 l 多线程 < 高优先级 N 低优先级2.5 对照 FreeRTOS 状态
| FreeRTOS | Linux | 差异 |
|---|---|---|
| Running | RUNNING(运行) | 同 |
| Ready | RUNNING(就绪) | Linux 不分就绪/运行(都 RUNNING) |
| Blocked | INTERRUPTIBLE/UNINTERRUPTIBLE | Linux 分两种睡眠 |
| Suspended | STOPPED | 类似 |
| Deleted | ZOMBIE | Linux 要父收尸 |
D 状态是 Linux 的"杀不掉"
FreeRTOS 任务阻塞 = 等 signal/queue,能被删。LinuxD状态(等磁盘 IO)信号唤不醒,kill -9 也杀不掉——因为杀它要它响应信号,它在 D 不响应。这是 Linux 比 FreeRTOS 复杂的点:IO 等待有"不可打断"语义,防止 IO 中途被打断导致数据不一致。你调"进程杀不掉"先看是不是 D 状态。
三、进程 vs 线程 vs 内核线程
3.1 Linux 不区分进程线程(都是 task_struct)
| 概念 | mm 是否共享 | files 是否共享 | 创建方式 |
|---|---|---|---|
| 进程 | 不共享(独立) | 不共享 | fork() |
| 线程 | 共享 | 共享 | clone(CLONE_VM|CLONE_FILES|…) |
| 内核线程 | 无 mm(借 active_mm) | 共享 init_files | kthread_create() |
3.2 fork / clone / vfork
Linux 进程创建底层都是clone系统调用,靠 flag 控制共享什么:
// fork:不共享(子独立)clone(SIGCHLD);// pthread_create:共享 mm/files/信号等clone(CLONE_VM|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|...);// vfork:共享 mm,父阻塞到子 exec/exitclone(CLONE_VM|CLONE_VFORK);3.3 内核线程(kthread)
内核线程:只在内核态跑,没用户态地址空间。用途:后台活(kworker/ksoftirqd/kthreadd/migration)。
// 创建内核线程structtask_struct*k=kthread_create(my_thread_fn,arg,"mythread");wake_up_process(k);// 唤醒让它跑// 或一步到位structtask_struct*k=kthread_run(my_thread_fn,arg,"mythread");内核线程的mm为 NULL,跑时active_mm借前一个进程的 mm(省切页表)。
嵌入式视角:内核线程 = FreeRTOS 任务
你 FreeRTOS 任务 = 函数死循环。Linux内核线程最像 FreeRTOS 任务——一个函数在内核态循环跑。你写驱动要后台活(轮询/处理),用kthread_run起一个,和 FreeRTOSxTaskCreate同构。区别:内核线程在内核态(全权),FreeRTOS 任务在"内核态"(FreeRTOS 无分层)。用户态线程( pthread)是另一回事,在 U 态跑。
3.4 PID vs TGID vs TID
- PID(Process ID):用户看到的进程号,实际是TGID(线程组 ID)
- TID(Thread ID):线程 ID,内核
task_struct->pid(每个 task_struct 唯一) - 单线程进程:PID == TID
- 多线程进程:同组 task_struct 共享 TGID,各 TID 不同
ps-eLf# 看 PID(=TGID)/ TID# 多线程进程:N 行同 PID,不同 TID(LWP)四、上下文切换(对照 RISC-V trap)
4.1 两种"切换"
| 切换 | 是什么 | 频率 |
|---|---|---|
| 用户态↔内核态 | syscall/中断,同进程内 | 高(每次 syscall) |
| 进程上下文切换 | 从进程 A 切到进程 B | 中(调度时) |
4.2 进程上下文切换做什么
context_switch()(kernel/sched/core.c):
staticvoidcontext_switch(structrq*rq,structtask_struct*prev,structtask_struct*next){structmm_struct*mm=next->mm;structmm_struct*oldmm=prev->active_mm;// 1. 切地址空间(若不同)if(unlikely(!mm)){// next 是内核线程next->active_mm=oldmm;// 借 prev 的 mm}elseif(prev->mm!=mm){// 不同进程switch_mm(oldmm,mm,next);// 切页表(写 satp + flush TLB)}// 2. 切寄存器(栈指针/ra/...)-> switch_to()switch_to(prev,next,prev);}4.3 switch_to:切寄存器(架构相关)
switch_to最终调__switch_to(arch/riscv/kernel/entry.S 或类似):
// 简化的 RISC-V 版 __switch_to: # 1. 保存 prev 的 callee-saved 寄存器到 prev 栈 addi sp, sp, -FRAME sd ra, 0(sp) sd s0, 8(sp) sd s1, 16(sp) ... # s2-s11 sd sp, TASK_THREAD_SP(a0) # 存 prev->thread.sp # 2. 切栈:加载 next 的 sp ld sp, TASK_THREAD_SP(a1) # next->thread.sp # 3. 切 TP(percpu 指针) ld tp, TASK_THREAD_TP(a1) # 4. 切 CSR(thread.fcsr 浮点 CSR 等) ... # 5. 恢复 next 的 callee-saved ld s1, 16(sp) ld s0, 8(sp) ld ra, 0(sp) addi sp, sp, FRAME ret # 跳到 next 上次被切走的地方(ra)4.4 对照你 RISC-V trap 切栈
| 你 mscratch 切栈(中断栈) | Linux switch_to(进程切换) | |
|---|---|---|
| 触发 | 中断/trap | 调度器决定 |
| 切什么 | sp(中断栈↔任务栈) | sp + 页表 + percpu + CSR |
| 保存啥 | 全部通用寄存器(中断可能破坏) | 只存 callee-saved(s0-s11/ra) |
| 返回 | mret | ret |
| 频率 | 每次中断 | 每次调度 |
你的 mscratch 切栈是 Linux switch_to 的简化版
你 [[04-trap 机制详解]] 的 mscratch 切栈:trap 时 swap sp 和 mscratch。Linux switch_to 同构——切 prev/next 的 sp。增量是:① 还切页表(mm);② 只存 callee-saved(调度点已知 caller-saved 不重要);③ 还切 percpu(tp)。你已经懂"切栈",Linux 是放大版,加了切地址空间。
4.5 为什么只存 callee-saved
调度点是自愿的(调度器在明确位置调用 schedule),不是中断打断。所以调度点之后,编译器只保证 callee-saved 寄存器(s0-s11/ra/sp)跨函数调用保留,caller-saved(t0-t6/a0-a7)编译器已处理。只存 callee-saved 足够——比中断保存全部寄存器省。
五、CFS 调度器(Completely Fair Scheduler)⭐ 核心
5.1 CFS 的核心思想
不是按优先级抢,是按"谁跑得少"轮。
- 每个进程有
vruntime(虚拟运行时间):表示它"已经跑了多少" - 调度器选
vruntime最小的跑(跑得最少的优先) - 跑一会儿,vruntime 增加,变成不是最小,换别人跑
- 结果:每个进程公平分享 CPU
5.2 vruntime 怎么算
vruntime += 实际运行时间 × (NICE_0_LOAD / 进程权重)- 权重(weight):由 nice 值算出,nice 0 权重 1024,nice -1 权重 1171(高 10%),nice +1 权重 920(低 10%)
- 高权重进程(nice 低):vruntime 增长慢 → 更久保持"跑得少" → 更多 CPU
- 低权重进程(nice 高):vruntime 增长快 → 很快被换下 → 少 CPU
5.3 nice 值与权重
| nice | 权重 | CPU 占比(nice 0 为基准) |
|---|---|---|
| -20 | 88761 | 极高 |
| -10 | 9548 | ~10x |
| 0 | 1024 | 1x(基准) |
| +10 | 110 | ~0.1x |
| +19 | 15 | 极低 |
- nice 范围 -20 ~ +19,默认 0
- nice 不是优先级,是权重——影响 vruntime 增长率,进而影响 CPU 份额
- 普通用户只能调高 nice(降权),root 能调低(升权)
5.4 CFS 数据结构:红黑树
- 每个 CPU 一个运行队列
struct rq - CFS 部分用红黑树(rbtree),按 vruntime 排序
- 最左节点 = vruntime 最小 = 下一个跑
- 进程入队:插入红黑树(O(log n))
- 进程出队:取最左(O(log n))
CPU0 rq.cfs │ 红黑树(按 vruntime) │ [vruntime=100] ← 最左,下一个跑 / \ [200] [150] / \ / [180][210] [130]5.5 调度时序
1. tick 中断 → scheduler_tick() 2. 当前进程 vruntime 更新 3. 检查:当前 vruntime 是否 > 红黑树最左 + sched_latency? - 否:继续跑 - 是:设 need_resched,稍后 schedule() 4. schedule(): - 当前进程入队(回红黑树) - 取最左 = next - context_switch(prev, next)5.6 sched_latency 与 min_granularity
- sched_latency(目标延迟,~6ms):在这个窗口内,所有进程都应跑一遍
- min_granularity(最小粒度,~0.75ms):每个进程至少跑这么久,避免频繁切换
- 时间片 = sched_latency / 进程数,但不小于 min_granularity
4 个进程,sched_latency=6ms: 每个时间片 = 6ms / 4 = 1.5ms(> min_granularity 0.75ms,OK) 每 6ms 每进程跑 1.5ms,公平5.7 对照 FreeRTOS 优先级调度
| FreeRTOS 优先级抢占 | Linux CFS | |
|---|---|---|
| 选谁跑 | 最高优先级 | vruntime 最小 |
| 同优先级 | 轮转(time slicing) | 自然公平(vruntime 趋同) |
| 高优先级 | 抢占低优先级(可能饿死低) | 权重高 CPU 多,但不饿死 |
| 饥饿 | 低优先级可能饿死 | 不会(都跑) |
| 实时性 | 高(优先级绝对) | 低(公平,延迟不定) |
| 适合 | 实时控制 | 交互/吞吐 |
CFS 不会饿死进程,但实时性弱
FreeRTOS 高优先级任务永远先跑,低优先级可能饿死——对实时控制是优点(关键任务必须先跑)。CFS 不让任何进程饿死——对交互/吞吐是优点(都响应),但实时性弱。Linux 实时需求用 SCHED_FIFO/RR(见 §六),或 RT-Linux 补丁。这是你 MCU 转 Linux 要适应的:Linux 默认不硬实时。
六、EEVDF(新调度器,Linux 6.6+)
6.1 为什么 CFS 升级到 EEVDF
CFS 用了十几年,问题:
- 公平性在某些场景不完美(特别是延迟敏感任务)
- 启发式多,难调
EEVDF(Earliest Eligible Virtual Deadline First)是 6.6 引入的新算法,理论更扎实。
6.2 EEVDF 核心思想
- 每个进程有lag(滞后量):应得 CPU - 实得 CPU
- Eligible(合格):lag ≤ 0(没多占)的进程才能跑
- 在合格进程里,选虚拟截止时间(vdeadline)最早的跑
- 结果:比 CFS 更精确的公平 + 更好的延迟控制
6.3 对你影响
- 接口不变(还是 nice/policy)
- 行为更公平,延迟敏感任务响应更好
- 你看 6.6+ 内核代码,
kernel/sched/fair.c是 EEVDF 实现了
嵌入式视角:EEVDF 是 CFS 的演进,不是革命
CFS → EEVDF ——核心思路(公平/vruntime)保留,机制优化(加 lag/vdeadline)。
七、调度策略(Scheduling Policy)
7.1 三类调度策略
| 策略 | 调度类 | 适合 | 行为 |
|---|---|---|---|
| SCHED_NORMAL(SCHED_OTHER) | CFS/EEVDF | 普通进程 | 公平调度,nice 加权 |
| SCHED_BATCH | CFS/EEVDF | 批处理 | CPU 密集,少交互,降优先级 |
| SCHED_IDLE | CFS/EEVDF | 极低优先级 | 只在空闲跑(nice 比所有都低) |
| SCHED_FIFO | RT | 实时 | 优先级抢占,无时间片,跑到让/阻塞 |
| SCHED_RR | RT | 实时 | 优先级抢占 + 同优先级轮转 |
| SCHED_DEADLINE | DL | 硬实时 | EDF(最早截止期优先),指定周期/运行时间/截止期 |
7.2 RT 优先级
- RT 优先级范围 1-99(99 最高)
- RT 优先级 > 普通进程(任何 RT 都抢普通)
- SCHED_FIFO/RR 的优先级 = rt_priority
- RT 进程能饿死普通进程(全占 CPU)→ 内核有
rt_throttling默认让 RT 最多占 95%,留 5% 给普通
7.3 chrt 改策略
chrt-f80./my_realtime_app# SCHED_FIFO 优先级 80chrt-r50./my_rr_app# SCHED_RR 优先级 50chrt-o0./my_normal_app# SCHED_OTHERchrt-p$(pidof myapp)# 查看策略7.4 SCHED_DEADLINE(硬实时)
structsched_attrattr={.size=sizeof(attr),.sched_policy=SCHED_DEADLINE,.sched_runtime=10000000,// 每周期需 10ms.sched_period=100000000,// 周期 100ms.sched_deadline=20000000,// 截止期 20ms};sched_setattr(0,&attr,0);- 内核保证每 period 内给 runtime,在 deadline 前完成
- 用 EDF 算法调度
- 比 FIFO/RR 更精确的实时保证
嵌入式视角:Linux 实时性
FreeRTOS 是硬实时(优先级抢占,us 级响应)。Linux 默认(SCHED_NORMAL)不是实时,ms 级延迟。要用 Linux 做实时:
- SCHED_FIFO/RR:内核抢占,软实时,但可能被中断/驱动阻塞
- SCHED_DEADLINE:更精确,但仍非硬实时
- PREEMPT_RT 补丁(主线化中):全内核抢占,接近硬实时
- Xenomai:双核(Linux + 实时核),硬实时
边缘 AI/控制若要硬实时,选 PREEMPT_RT 或 Xenomai。
八、抢占(Preemption)
8.1 抢占点
Linux 内核可抢占(CONFIG_PREEMPT),在安全点检查need_resched,调schedule():
| 抢占点 | 何时 |
|---|---|
| 中断返回内核态 | IRQ 返回,检查 need_resched |
| 系统调用返回 | syscall 返回用户态前 |
| 显式 schedule() | 主动睡眠/阻塞 |
| preempt_enable() 后 | 临界区退出 |
| 互斥锁释放 | mutex_unlock 后 |
8.2 抢占等级(CONFIG)
| 配置 | 抢占性 | 用途 |
|---|---|---|
| PREEMPT_NONE | 不抢占(除非阻塞) | 服务器(吞吐) |
| PREEMPT_VOLUNTARY | 自愿抢占(显式点) | 桌面(平衡) |
| PREEMPT | 全内核抢占 | 低延迟(嵌入式/桌面) |
| PREEMPT_RT | 实时(几乎全可抢占) | 硬实时 |
8.3 关抢占 vs 关中断
preempt_disable();// 关抢占(本 CPU 不切走)// 临界区(不能睡!)preempt_enable();// 开抢占(检查 need_resched)local_irq_save(flags);// 关中断(本 CPU 中断不来)// 临界区local_irq_restore(flags);spinlock_tlock;spin_lock(&lock);// 自旋锁隐含关抢占(SMP)+ 可能关中断spin_unlock(&lock);- 关抢占:本核不切走,但中断还来(中断里可能 schedule)
- 关中断:中断不来,绝对安静
- 自旋锁:隐含关抢占(07 篇详讲)
关抢占/持锁时不能睡眠
FreeRTOS 临界区里能调阻塞 API(会切走)。Linux 关抢占/持自旋锁时绝对不能睡眠——睡眠会 schedule(),但你关了抢占,schedule 切不出去,死锁/panic。这是 Linux 比 FreeRTOS 严格的点。能睡的临界区用 mutex(07 篇详讲)。
九、fork / exec / wait(进程生命周期)
9.1 fork()
pid_tpid=fork();if(pid==0){// 子进程}elseif(pid>0){// 父进程}fork()创建子进程,复制父的 task_struct- 用COW(Copy-On-Write):页表复制,物理页共享,写时才复制(惰性,01 篇哲学)
- 子继承父的 mm/files/signals 副本
9.2 exec()
execl("/bin/ls","ls","-l",NULL);- 替换当前进程的代码/数据/堆/栈(丢弃旧 mm,建新 mm)
- PID 不变,但变成新程序
- file_operations 保持(打开的 fd 默认保留,除 FD_CLOEXEC 标记的)
9.3 wait()
pid_tpid=wait(&status);// 等任一子进程- 父 wait 收尸子进程
- 子 exit 后变 ZOMBIE,父 wait 才彻底释放
- 父不 wait → 子变僵尸,占 PID;父死了 → init 收养收尸
9.4 孤儿进程与僵尸
- 孤儿:父死了,子被 init(PID1)收养,init wait 收尸
- 僵尸:子死了父没 wait,占 PID 和退出码,父 wait 或父死后 init 收尸
嵌入式视角:fork 比 FreeRTOS 创建任务重
FreeRTOSxTaskCreate立即分配栈和 TCB。Linuxfork用 COW,看似复制 mm,实际只复制页表(快),物理页写时才复制。但 fork 仍比 xTaskCreate 重——要复制 files/signals/页表。所以 Linux 服务器用进程池/线程,不频繁 fork。你写服务别无脑 fork。
十、进程关系与会话
10.1 进程组 / 会话
- 进程组:相关进程集合(如 pipeline),组 ID = PGID
- 会话:进程组集合,会话首控制终端,会话 ID = SID
- 用途:信号广播(kill -PGID)、作业控制
10.2 守护进程(daemon)
// daemon 化典型步骤pid=fork();if(pid>0)exit(0);// 父退出setsid();// 子建新会话,脱离控制终端fork();// 再 fork,防再获控制终端if(pid>0)exit(0);chdir("/");// 改根umask(0);// 关标准 fd,重定向到 /dev/null- 守护进程:后台运行,无控制终端
- BSP 启动后台服务常 daemon 化
十一、常用进程调试
11.1 工具
| 工具 | 看什么 |
|---|---|
ps aux/ps -ef | 进程列表 |
top/htop | 实时进程状态/CPU |
pstree | 进程树 |
cat /proc/<pid>/status | 进程详情(状态/内存/信号) |
cat /proc/<pid>/sched | 调度信息(vruntime/policy) |
cat /proc/<pid>/maps | 地址空间布局 |
cat /proc/<pid>/stack | 内核栈(WARNING) |
strace -p <pid> | 跟踪系统调用 |
perf top | 热点函数 |
chrt/nice/renice | 改调度策略/优先级 |
11.2 关键 /proc 字段
# /proc/<pid>/status State: R (running) Pid: 1234 PPid: 1 Uid: 0 voluntary_ctxt_switches: 1500 # 主动睡眠切换 nonvoluntary_ctxt_switches: 30 # 被抢占切换- voluntary 多 = 频繁等 IO/睡眠(正常)
- nonvoluntary 多 = 频繁被抢占(可能 CPU 紧张)
嵌入式视角:
Linux 的perf统计任意函数/CPU 周期。/proc/<pid>/sched看 vruntime 判断进程是否被公平调度。16 篇详讲 perf/ftrace。
十二、本篇小结
task_struct是宇宙中心,比 FreeRTOS TCB 胖十倍(mm/files/signals/namespace)- 进程状态:RUNNING/INTERRUPTIBLE/UNINTERRUPTIBLE(D,杀不掉)/ZOMBIE 等
- Linux 不区分进程线程(都 task_struct,clone flag 控制共享);内核线程无 mm
- 上下文切换
context_switch:切页表(switch_mm)+ 切寄存器(switch_to,只存 callee-saved),对照你 mscratch 切栈是放大版 - CFS:vruntime 最小优先跑,红黑树,权重由 nice 决定;不会饿死进程但实时性弱
- EEVDF(6.6+):CFS 演进,加 lag/vdeadline,更公平
- 调度策略:SCHED_NORMAL(CFS)/SCHED_FIFO/RR(RT)/SCHED_DEADLINE(硬实时)
- 抢占:关抢占/持自旋锁时不能睡眠(FreeRTOS 临界区能睡,Linux 不能,严格)
- fork 用 COW(惰性),exec 换程序,wait 收尸;孤儿归 init,僵尸等收尸
- PREEMPT_RT 补丁让 Linux 接近硬实时
速查表
| 想干啥 | 怎么做 |
|---|---|
| 看进程列表 | ps aux / top / htop |
| 看进程详情 | cat /proc//status |
| 看地址空间 | cat /proc//maps |
| 看调度信息 | cat /proc//sched |
| 改优先级 | nice -n 10 ./app / renice -n -5 -p PID |
| 改调度策略 | chrt -f 80 ./app |
| 创建内核线程 | kthread_run(fn, arg, “name”) |
| 跟踪 syscall | strace -p PID |
| 看进程树 | pstree -p |
| 杀进程 | kill -9 PID(D 状态杀不掉) |
| 看僵尸 | ps aux | grep Z |
| 实时性 | SCHED_FIFO/RR/DEADLINE 或 PREEMPT_RT |
| 关抢占 | preempt_disable(不能睡) |
| 睡眠等待 | TASK_INTERRUPTIBLE(可被信号唤醒) |
💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的点赞 👍和收藏 ⭐。
如果你在调试过程中遇到了其他问题,欢迎在评论区 💬留言,我们一起探讨。也欢迎关注 👀我,一起交流底层开发的那些事儿。