001-进程管理与进程调度算法

进程管理与进程调度算法


一、进程管理基础

1. 进程与线程对比

特性 进程 (Process) 线程 (Thread)
资源分配 独立内存空间、文件、I/O资源 共享进程资源
创建开销 大(需复制父进程资源) 小(仅需栈和寄存器)
通信方式 IPC(管道、共享内存等) 直接读写进程内存
切换开销 高(需切换地址空间) 低(共享地址空间)
安全性 高(隔离性强) 低(线程间可能相互破坏)
代表实例 Chrome多进程架构 Java多线程程序

2. 进程控制块 (PCB)

操作系统中每个进程对应一个PCB,包含:

1
2
3
4
5
6
7
8
9
10
struct task_struct {        // Linux内核PCB结构
long state; // 进程状态(运行/就绪/阻塞)
unsigned int flags; // 进程标志位
int prio; // 动态优先级
struct mm_struct *mm; // 内存管理信息
struct files_struct *files; // 打开文件表
pid_t pid; // 进程ID
struct list_head tasks; // 进程链表
// ... 超过100个字段
};

3. 进程生命周期与状态转换

1
2
3
4
5
6
7
8
stateDiagram-v2
[*] --> New: 创建进程
New --> Ready: 资源就绪
Ready --> Running: 被调度器选中
Running --> Ready: 时间片用完
Running --> Blocked: 等待I/O事件
Blocked --> Ready: 事件完成
Running --> Terminated: 执行结束

4. 进程控制原语

操作 系统调用 操作说明
创建进程 fork() / CreateProcess() 复制父进程PCB创建子进程
终止进程 exit() / TerminateProcess() 释放资源并通知父进程
等待进程 wait() / WaitForSingleObject() 父进程等待子进程结束
加载程序 exec() / CreateProcess() 替换进程内存空间为新程序
进程同步 semaphore / mutex 控制进程执行顺序

二、进程调度算法详解

1. 调度层次结构

1
2
3
4
5
6
7
┌───────────────────────┐
│ 长程调度 (Job) │◄── 控制内存进程数量
├───────────────────────┤
│ 中程调度 (Swapping) │◄── 内存↔外存进程交换
├───────────────────────┤
│ 短程调度 (CPU) │◄── 纳秒级CPU分配 (重点)
└───────────────────────┘

2. 调度算法性能指标

  • 周转时间 = 完成时间 - 到达时间
  • 响应时间 = 首次运行时间 - 到达时间
  • 等待时间 = 就绪队列等待总时间
  • 吞吐量 = 单位时间完成进程数
  • 公平性 = 资源分配均衡度

3. 经典调度算法对比

算法 类型 特点 优点 缺点 适用场景
FCFS 非抢占 按到达顺序执行 简单公平 护航效应(长进程阻塞) 批处理系统
SJF 非抢占 执行时间最短优先 最小平均等待时间 需预知执行时间 嵌入式系统
SRTF 抢占 SJF的抢占版 响应更快 长进程可能饥饿 交互式系统
Priority 抢占/非抢占 按优先级执行 高优先级快速响应 低优先级饥饿 实时系统
Round Robin 抢占 固定时间片轮转 公平性好 上下文切换开销大 分时系统
Multilevel Queue 混合 多队列不同策略 灵活适应不同需求 配置复杂 通用操作系统
Multilevel Feedback Queue 混合 动态调整队列优先级 平衡响应和吞吐量 实现最复杂 Linux/Windows

4. 算法执行过程图示

FCFS (先来先服务)

进程 | 到达时间 | 执行时间
P1  | 0       | 24
P2  | 1       | 3
P3  | 2       | 3

执行顺序: 
[ P1 ████████████████████████ ] 0-24
[ P2 ███ ] 24-27
[ P3 ███ ] 27-30
平均等待时间 = (0 + 23 + 25)/3 = 16

SJF (最短作业优先)

进程 | 到达时间 | 执行时间
P1  | 0       | 6  
P2  | 2       | 8
P3  | 4       | 7  
P4  | 5       | 3

执行顺序:
[ P1 ██████ ] 0-6
[ P4 ███ ] 6-9
[ P3 ███████ ] 9-16
[ P2 ████████ ] 16-24
平均等待时间 = (0 + 4 + 5 + 1)/4 = 2.5

Round Robin (时间片=4)

进程 | 执行时间
P1  | 24
P2  | 3
P3  | 3

执行轮转:
[ P1 ████ ] 0-4 → [ P2 ███ ] 4-7 → [ P3 ███ ] 7-10 
→ [ P1 ████ ] 10-14 → [ P1 ████ ] 14-18 → [ P1 ████ ] 18-22 
→ [ P1 ████ ] 22-26 → [ P1 ██ ] 26-28
平均等待时间 = (0+4+7)/3 = 3.67

5. Linux CFS 调度器 (Completely Fair Scheduler)

核心思想:虚拟运行时间(vruntime)决定调度顺序

1
2
3
4
5
6
7
8
9
// 关键数据结构
struct sched_entity {
u64 vruntime; // 虚拟运行时间
u64 exec_start; // 本次执行开始时间
u64 sum_exec_runtime; // 总实际运行时间
};

// 虚拟时间计算
vruntime = actual_runtime * NICE_0_LOAD / weight

调度过程

  1. 选择红黑树最左侧(vruntime最小)进程
  2. 运行进程直到:
    • 时间片用完(由sched_latency_ns控制)
    • 主动放弃CPU(如I/O阻塞)
    • 更高优先级进程就绪
  3. 更新vruntime并重新插入红黑树

动态优先级调整

1
2
bonus = sleep_avg / (sleep_avg + run_avg) * MAX_BONUS
priority = static_priority - bonus + 5

6. Windows 调度算法

多优先级队列结构

┌──────────────┐
│ 实时优先级   │ 31-16 (固定优先级)
├──────────────┤
│ 可变优先级   │ 15-0  (动态调整)
│  ├───────────┤
│  │ 时间配额  │ 每个线程分配CPU时间单元
│  └───────────┤
└──────────────┘

动态调整规则

  • I/O型线程:优先级提升(+1~+2)
  • CPU密集型线程:优先级降低(-1)
  • 前台进程:时间配额加倍
  • 饥饿线程:优先级提升至15

三、高级调度技术

1. 多核调度策略

策略 实现方式 优势
对称多处理 (SMP) 所有核共享就绪队列 负载均衡简单
非对称多处理 (AMP) 特定核运行特定任务 减少缓存失效
核心亲和性 绑定进程到指定CPU 提高缓存命中率
负载均衡 定期迁移进程 避免核心空闲

2. 实时调度算法

关键指标

  • **截止期限 (Deadline)**:任务必须完成的时间点
  • 松弛时间 (Laxity) = 截止期限 - 剩余执行时间 - 当前时间
算法 策略 适用场景
RM 周期越短优先级越高 周期性任务
EDF 截止时间越早优先级越高 动态任务集
LLF 选择松弛时间最小的任务 高利用率系统

3. 容器调度 (Docker/Kubernetes)

核心机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Kubernetes 调度配置示例
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14
resources:
requests:
cpu: "500m" # 0.5个CPU核心
memory: "512Mi"
limits:
cpu: "1000m" # 不超过1个核心
memory: "1Gi"
nodeSelector: # 节点选择
disktype: ssd

调度器类型

  • BinPack:尽可能填满节点(提高资源利用率)
  • Spread:分散部署(提高可用性)
  • Custom:用户自定义策略

容器调度系统深度解析(以 Kubernetes 为例)

容器调度需解决 资源分配负载均衡高可用 问题,其核心流程分为 过滤打分绑定

1. 调度核心机制

  • 节点过滤(Predicates)
    淘汰不满足条件的节点,例如:
    • PodFitsResources:节点剩余资源需 ≥ 容器需求。
    • NoDiskConflict:存储卷冲突检测。
  • 节点打分(Priorities)
    对合格节点多维度评分:
    • 资源平衡分BalancedResourceAllocation):优选 CPU/内存利用率接近的节点(避免单一资源瓶颈)。
    • 低负载分LeastRequestedPriority):选择资源空闲率高的节点。

2. 高级调度策略

  • 亲和性(Affinity)
    1
    2
    3
    4
    5
    6
    7
    8
    affinity:
    nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchExpressions:
    - key: disktype
    operator: In
    values: [ssd] # 强制调度到 SSD 节点
  • 反亲和性(Anti-Affinity)
    避免同一服务的多个实例共存于同一节点(提升容灾能力)。
  • 跨域调度
    将容器调度至用户地理最近的节点(减少延迟)。

3. 工业级优化策略

  • 动态资源预测(阿里 Sigma 系统)
    基于历史监控数据(如 Prophet 算法)预测资源需求,实现错峰部署(如日间计算型 + 夜间存储型容器混部)。
    1
    2
    3
    # 伪代码:利用时间序列预测资源峰值
    predicted_cpu = prophet.predict(historical_cpu_data)
    schedule_to_node_with_lowest_peak_overlap()
  • 资源超售与回收
    • 生产任务(Prod)优先使用物理资源。
    • 非生产任务(Non-prod)使用剩余资源,可被强占。
  • 公平调度(DRF)
    按“主导资源”(如 GPU 密集型容器的 GPU 占比)分配资源,避免小任务饿死。

4. Kubernetes 调度流程示例

1
2
3
4
5
6
7
8
graph TD
A[新 Pod 创建] --> B{过滤节点}
B --> C[资源足够]
B --> D[端口冲突]
C --> E[生成候选节点列表]
E --> F[按策略打分]
F --> G[选择最高分节点]
G --> H[绑定 Pod 到节点]

实践案例与性能优化

1. 阿里双 11 调度实战

  • 挑战:应对瞬时 100 倍流量增长。
  • 方案
    • 混部技术:在线业务(CPU 敏感)与离线任务(I/O 敏感)混合部署,提升资源利用率 40%。
    • 打散部署:同服务容器分散至不同机架/机房,单点故障影响下降 90%。

2. 调度算法优化方向

  • 装箱算法改进
    结合 BinPack(减少节点数)与 Spread(均衡分布),平衡成本与可用性。
  • 实时迁移
    基于节点负载预测(如线性回归)自动迁移容器,避免热点。

总结

  • 磁盘调度LOOK/C-LOOK 是现代系统首选,兼顾效率与公平性。
  • 容器调度动态预测 + DRF 公平策略 + 亲和性规则 是 Kubernetes 高性能调度基石。
  • 工业实践:阿里、Google 等通过 混部技术资源超售 实现超大规模集群利用率提升(如 Borg 系统资源利用率达 60% 以上)。

更详细的调度过程动态演示可参考 Kubernetes 调度器文档


四、现代调度器实现分析

Linux CFS 调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 调度主循环 (kernel/sched/core.c)
void schedule(void) {
struct task_struct *prev, *next;
prev = current;

// 1. 选择下一个任务
next = pick_next_task(rq);

// 2. 上下文切换
if (prev != next) {
rq->curr = next;
context_switch(prev, next);
}
}

// 选择下一个任务 (kernel/sched/fair.c)
static struct task_struct *
pick_next_task_fair(struct rq *rq) {
struct cfs_rq *cfs_rq = &rq->cfs;
struct sched_entity *se;

// 从红黑树取最左节点
se = __pick_first_entity(cfs_rq);
return task_of(se);
}

Windows 调度器关键流程

1
2
3
4
5
6
7
8
9
; Windows内核调度入口 (KiDispatchInterrupt)
KiDispatchInterrupt:
call KiFindReadyThread ; 寻找就绪线程
test eax, eax
jz no_thread_ready
call KiSwapContext ; 执行上下文切换
ret
no_thread_ready:
call KiIdleLoop ; 进入空闲循环

五、性能优化实践

1. 调度延迟优化

  • 禁用内核抢占preempt_disable()
  • CPU隔离isolcpus=1,2(隔离CPU核)
  • 实时优先级sched_setscheduler(policy, SCHED_FIFO)

2. 负载均衡策略

1
2
3
4
5
6
graph TD
A[检测负载不均衡] --> B{迁移类型}
B -->|任务迁移| C[选择迁移进程]
B -->|CPU迁移| D[调整进程亲和性]
C --> E[在目标CPU唤醒进程]
D --> F[更新硬件上下文]

3. NUMA调度优化

1
2
3
4
5
6
7
8
Node0 (CPU0-CPU3)      Node1 (CPU4-CPU7)
├── 本地内存 ├── 本地内存
└── 远程访问延迟高 └── 远程访问延迟高

优化策略:
1. 进程绑定到Node
2. 内存分配优先本地
3. 中断绑定到指定CPU

六、调度算法实战分析

场景:Web服务器进程调度

1
2
3
4
5
6
7
8
9
10
11
12
13
要求:
- 高并发I/O密集型
- 低延迟响应
- 避免CPU饥饿

解决方案:
1. 使用多级反馈队列 (MLFQ)
- 高优先级队列:时间片短 (10ms)
- 低优先级队列:时间片长 (50ms)
2. 动态优先级调整:
- 完成I/O后提升优先级
- CPU长时间运行降低优先级
3. 设置最低保证时间片

调度器配置示例 (Linux)

1
2
3
4
5
6
# 设置CFS参数
echo 1000000 > /proc/sys/kernel/sched_latency_ns
echo 100000 > /proc/sys/kernel/sched_min_granularity_ns

# 实时进程优先级
chrt -f -p 99 <pid>

七、未来发展趋势

  1. AI驱动调度:基于机器学习预测任务行为
  2. 异构计算调度:CPU/GPU/DPU统一调度
  3. 量子计算调度:量子比特任务分配算法
  4. 边缘计算调度:低延迟分布式调度

关键结论
进程调度是操作系统性能的核心,现代系统通过动态优先级、多级队列和公平分配策略,在响应性、吞吐量和公平性之间取得平衡。理解不同调度算法特性,是优化系统性能的关键基础。

002-死锁处理

死锁处理指南:原理、检测与实战解决方案

死锁是并发系统中资源竞争导致的僵局状态,当多个进程相互等待对方持有的资源时,系统陷入停滞。


一、死锁核心原理与必要条件

1. 死锁发生的四大必要条件(缺一不可)

条件 说明 示例
互斥访问 资源只能被一个进程独占使用 打印机、数据库锁
持有并等待 进程持有资源同时等待新资源 进程A持有文件锁,申请网络端口
不可剥夺 资源只能由持有者主动释放 已分配的内存无法强制回收
循环等待 进程间形成环形等待链 A等B,B等C,C等A

2. 死锁状态转移模型

1
2
3
4
5
stateDiagram-v2
[*] --> 安全状态
安全状态 --> 死锁状态: 四个条件同时满足
死锁状态 --> 恢复状态: 人工干预/自动恢复
恢复状态 --> 安全状态: 解除死锁

二、死锁预防策略(提前消除必要条件)

1. 破坏互斥访问

  • 适用场景:只读资源
  • 实现方案
    1
    2
    3
    // 使用无锁数据结构
    ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    map.compute("key", (k, v) -> (v == null) ? 1 : v + 1);

2. 破坏持有并等待

  • 策略:一次性申请所有资源
  • 实现
    1
    2
    3
    4
    5
    6
    # 银行账户转账的原子操作
    def transfer(account1, account2, amount):
    lock = acquire_global_lock() # 获取全局锁
    account1.balance -= amount
    account2.balance += amount
    release_lock(lock)
  • 缺点:严重降低并发性

3. 破坏不可剥夺

  • 方案:强制剥夺资源
    1
    2
    // Unix信号机制:强制终止进程
    kill -9 <pid> // SIGKILL不可捕获
  • 风险:数据不一致(如数据库事务中断)

4. 破坏循环等待

  • 资源有序分配法
    1
    2
    3
    4
    5
    资源类型排序: 
    1. 磁盘设备 → 2. 网络端口 → 3. 内存区域

    进程申请顺序:
    必须按编号递增申请(禁止乱序)
  • 工业应用:Linux内核资源管理(/proc/sys/fs/file-max控制文件句柄分配顺序)

三、死锁避免策略(运行时动态检测)

1. 银行家算法(Dijkstra算法)

核心数据结构

矩阵 说明
Max 进程最大资源需求
Allocation 已分配资源
Need 还需资源(Max-Alloc)
Available 系统可用资源

安全序列检测流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_safe_system():
work = Available.copy() # 当前可用资源
finish = [False] * n # 标记进程是否完成

# 寻找可满足的进程
for _ in range(n):
found = False
for i in range(n):
if not finish[i] and all(Need[i] <= work):
work += Allocation[i] # 模拟释放资源
finish[i] = True
found = True
if not found:
return False # 存在死锁风险
return True

2. 资源分配图算法

  • 请求边:进程 → 资源(P→R)
  • 分配边:资源 → 进程(R→P)
  • 死锁检测:图中存在环路且资源不可抢占

示例

1
2
3
4
5
graph LR
P1 -->|请求| R1
R2 -->|已分配| P1
P2 -->|请求| R2
R1 -->|已分配| P2 # 形成环:P1→R1→P2→R2→P1

四、死锁检测与恢复(发生后处理)

1. 死锁检测算法

周期扫描步骤

  1. 构建资源分配图
  2. 标记无等待的进程(无边指向)
  3. 删除其所有边(模拟释放资源)
  4. 重复直到无进程可标记
  5. 剩余进程为死锁进程

Linux实现

1
2
3
4
5
# 检测死锁进程(示例)
$ ps -eo pid,ppid,cmd,stat | grep ' D ' # D状态=不可中断睡眠

# 输出示例:
# 1234 5678 /usr/bin/deadlock_app D

2. 死锁恢复策略

策略 实现方式 风险
进程终止 强制终止死锁进程 数据丢失/业务中断
资源剥夺 回滚并释放部分资源 需实现事务机制
进程回退 恢复到安全检查点 需要定期创建快照
人工干预 运维人员手动处理 响应延迟高

容器环境恢复示例(Kubernetes):

1
2
3
4
5
6
# 配置存活探针自动重启
livenessProbe:
exec:
command: ["check_deadlock.sh"] # 自定义死锁检测脚本
failureThreshold: 3
periodSeconds: 10

五、工业级死锁处理实践

1. 数据库死锁处理(MySQL为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 1. 查看最近死锁信息
SHOW ENGINE INNODB STATUS\G

-- 2. 自动死锁检测(默认开启)
SET GLOBAL innodb_deadlock_detect = ON;

-- 3. 事务重试机制
START TRANSACTION;
BEGIN TRY
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
EXCEPT
ROLLBACK; -- 发生死锁时回滚
WAIT 0.1; -- 随机等待后重试
RETRY TRANSACTION;
END TRY

2. 分布式系统死锁预防(Google Chubby锁服务)

  • 全局有序锁:所有客户端按固定顺序申请锁
  • 租约机制:锁自动超时释放(避免永久等待)
  • 乐观并发控制
    1
    2
    3
    4
    5
    // etcd事务示例(CAS操作)
    resp, err := client.Txn(ctx).
    If(clientv3.Compare(clientv3.Value("key"), "=", "old_val")).
    Then(clientv3.OpPut("key", "new_val")).
    Commit()

3. 编程语言级防护

Java并发工具包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 尝试锁(破坏持有并等待)
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();

while (true) {
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
try { /* 临界区操作 */ }
finally { lock2.unlock(); }
}
} finally { lock1.unlock(); }
}
Thread.sleep(50); // 随机退避
}

// 2. 使用并发集合(破坏互斥)
Map<String, String> safeMap = new ConcurrentHashMap<>();

六、死锁调试与诊断工具

1. Linux 平台

工具 功能 示例命令
gdb 分析进程堆栈 gdb -p <pid> ; thread apply all bt
strace 跟踪系统调用阻塞点 strace -p <pid> -f -e trace=file
perf 性能分析+锁竞争检测 perf record -g -p <pid> ; perf lock contention

2. Java 应用

1
2
3
4
5
6
7
8
9
# 1. 生成线程转储
jstack <pid> > thread_dump.txt

# 2. 分析死锁(示例输出)
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fbfd8003980 (object 0x000000076ab2c4d8)
which is held by "Thread-0"

3. 可视化诊断

  • JConsole:实时监控线程状态
  • Eclipse Memory Analyzer:分析堆转储中的锁信息
  • 线上诊断工具:阿里 Arthas、Btrace

七、典型死锁案例解析

案例1:哲学家就餐问题

1
2
3
4
5
6
7
graph LR
P1 --持有--> C1
P1 --等待--> C2
P2 --持有--> C2
P2 --等待--> C3
P3 --持有--> C3
P3 --等待--> C1 # 形成循环等待

解决方案

  1. 资源排序:筷子编号,必须按序获取
  2. 破坏等待:仅当左右筷子都可用时获取
  3. 超时释放:获取失败时释放已持有资源

案例2:数据库事务死锁

1
2
3
4
5
6
7
-- 事务A
UPDATE users SET score=score+10 WHERE id=1; -- 持有行锁1
UPDATE users SET score=score-5 WHERE id=2; -- 等待行锁2

-- 事务B(同时执行)
UPDATE users SET score=score+8 WHERE id=2; -- 持有行锁2
UPDATE users SET score=score-3 WHERE id=1; -- 等待行锁1

解决方案

  • 统一更新顺序(先id小的记录)
  • 短事务 + 重试机制
  • 使用SELECT ... FOR UPDATE提前锁定

八、死锁处理最佳实践

  1. 设计阶段

    • 统一资源申请顺序
    • 使用无锁数据结构(如Disruptor框架)
    • 限制资源持有时间
  2. 编码阶段

    • 添加超时机制(tryLock(timeout)
    • 使用事务与回滚
    • 避免嵌套锁
  3. 运维阶段

    • 监控资源等待链(如Kubernetes kubectl describe pod
    • 设置自动恢复策略(如Docker重启策略)
    • 定期压力测试

终极建议
在关键系统中,预防为主 + 自动检测 + 事务回滚 的组合策略是最可靠的死锁处理方案。对于分布式系统,优先考虑 租约机制乐观并发控制 来避免全局死锁。