本篇定位: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 核心字段(分组)
struct task_struct {
// === 标识 ===
pid_t pid; // 进程 ID(线程组里是线程 ID)
pid_t tgid; // 线程组 ID(进程 ID,用户看到的)
struct task_struct *group_leader; // 线程组首领
char comm[TASK_COMM_LEN]; // 可执行文件名(16 字符)
// === 状态 ===
volatile long state; // 进程状态(见 §二)
int exit_state;
int prio, static_prio, normal_prio; // 优先级
unsigned int policy; // 调度策略(SCHED_NORMAL/FIFO/RR)
// === 调度 ===
const struct sched_class *sched_class; // 调度类
struct sched_entity se; // CFS 调度实体(vruntime 等)
struct sched_rt_entity rt; // RT 调度实体
unsigned int rt_priority;
cpumask_t cpus_allowed; // 允许跑的 CPU
// === 家族 ===
struct task_struct *parent; // 父进程
struct list_head children; // 子进程链表
struct list_head sibling;
// === 内存(★对照 FreeRTOS 最大差异)===
struct mm_struct *mm; // 进程地址空间(线程共享)
struct mm_struct *active_mm; // 内核线程用(借来的 mm)
// === 文件 ===
struct files_struct *files; // 打开的文件表
struct fs_struct *fs; // 文件系统根/root
// === 信号 ===
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
// === 栈 ===
void *stack; // 内核栈(union with thread_info)
// === 时间统计 ===
u64 utime, stime; // 用户态/内核态时间
u64 nvcsw, nivcsw; // 自愿/非自愿切换次数
// === namespace/cgroup(容器)===
struct nsproxy *nsproxy;
struct css_set __rcu *cgroups;
// … 还有几百个字段
};
1.3 对照 FreeRTOS TCB
| 栈指针 | 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 -eo pid,stat,comm
# STAT 列:
# R=running S=interruptible sleep D=uninterruptible sleep
# T=stopped Z=zombie X=dead
# + 前台 s 会话首 l 多线程 < 高优先级 N 低优先级
2.5 对照 FreeRTOS 状态
| Running | RUNNING(运行) | 同 |
| Ready | RUNNING(就绪) | Linux 不分就绪/运行(都 RUNNING) |
| Blocked | INTERRUPTIBLE/UNINTERRUPTIBLE | Linux 分两种睡眠 |
| Suspended | STOPPED | 类似 |
| Deleted | ZOMBIE | Linux 要父收尸 |
D 状态是 Linux 的"杀不掉" FreeRTOS 任务阻塞 = 等 signal/queue,能被删。Linux D 状态(等磁盘 IO)信号唤不醒,kill -9 也杀不掉——因为杀它要它响应信号,它在 D 不响应。这是 Linux 比 FreeRTOS 复杂的点:IO 等待有"不可打断"语义,防止 IO 中途被打断导致数据不一致。你调"进程杀不掉"先看是不是 D 状态。
三、进程 vs 线程 vs 内核线程
3.1 Linux 不区分进程线程(都是 task_struct)
| 进程 | 不共享(独立) | 不共享 | 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/exit
clone(CLONE_VM | CLONE_VFORK);
3.3 内核线程(kthread)
内核线程:只在内核态跑,没用户态地址空间。用途:后台活(kworker/ksoftirqd/kthreadd/migration)。
// 创建内核线程
struct task_struct *k = kthread_create(my_thread_fn, arg, "mythread");
wake_up_process(k); // 唤醒让它跑
// 或一步到位
struct task_struct *k = kthread_run(my_thread_fn, arg, "mythread");
内核线程的 mm 为 NULL,跑时 active_mm 借前一个进程的 mm(省切页表)。
嵌入式视角:内核线程 = FreeRTOS 任务 你 FreeRTOS 任务 = 函数死循环。Linux 内核线程最像 FreeRTOS 任务——一个函数在内核态循环跑。你写驱动要后台活(轮询/处理),用 kthread_run 起一个,和 FreeRTOS xTaskCreate 同构。区别:内核线程在内核态(全权),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):
static void context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next) {
struct mm_struct *mm = next->mm;
struct mm_struct *oldmm = prev->active_mm;
// 1. 切地址空间(若不同)
if (unlikely(!mm)) { // next 是内核线程
next->active_mm = oldmm; // 借 prev 的 mm
} else if (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 切栈
| 触发 | 中断/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 值与权重
| -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 优先级调度
| 选谁跑 | 最高优先级 | 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 -f 80 ./my_realtime_app # SCHED_FIFO 优先级 80
chrt -r 50 ./my_rr_app # SCHED_RR 优先级 50
chrt -o 0 ./my_normal_app # SCHED_OTHER
chrt -p $(pidof myapp) # 查看策略
7.4 SCHED_DEADLINE(硬实时)
struct sched_attr attr = {
.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_t lock;
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_t pid = fork();
if (pid == 0) {
// 子进程
} else if (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_t pid = wait(&status); // 等任一子进程
- 父 wait 收尸子进程
- 子 exit 后变 ZOMBIE,父 wait 才彻底释放
- 父不 wait → 子变僵尸,占 PID;父死了 → init 收养收尸
9.4 孤儿进程与僵尸
- 孤儿:父死了,子被 init(PID1)收养,init wait 收尸
- 僵尸:子死了父没 wait,占 PID 和退出码,父 wait 或父死后 init 收尸
嵌入式视角:fork 比 FreeRTOS 创建任务重 FreeRTOS xTaskCreate 立即分配栈和 TCB。Linux fork 用 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(可被信号唤醒) |
💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的 点赞 👍 和 收藏 ⭐。
如果你在调试过程中遇到了其他问题,欢迎在 评论区 💬 留言,我们一起探讨。也欢迎 关注 👀 我,一起交流底层开发的那些事儿。
网硕互联帮助中心




评论前必须登录!
注册