云计算百科
云计算领域专业知识百科平台

【linux专栏 03】进程管理与调度

本篇定位: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

字段FreeRTOS TCBLinux 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 -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 状态

FreeRTOSLinux差异
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)

概念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/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 切栈

你 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 -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(可被信号唤醒)

💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的 点赞 👍 和 收藏 ⭐。

如果你在调试过程中遇到了其他问题,欢迎在 评论区 💬 留言,我们一起探讨。也欢迎 关注 👀 我,一起交流底层开发的那些事儿。


赞(0)
未经允许不得转载:网硕互联帮助中心 » 【linux专栏 03】进程管理与调度
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!