在许多中间件、语言设计甚至日常开发的业务系统中遇到问题时,我们常常会参考操作系统中成熟的解决办法,进程调度就是这样一种常常被借鉴的场景,在不少语言的线程或者协程机制的设计里都有应用。
那操作系统的进程调度到底是如何设计的呢?
进程是什么?
我们知道计算机是可以同时进行很多任务的,比如边开着浏览器阅读文章,边打开着微信软件随时处理好友的消息。计算机就像一个真正的时间管理大师一样,并发而有条不紊地处理着各种复杂的任务。
但事实上,每个 CPU 核在同一时间只能同时运行一个程序,那计算机是如何做到看起来可以同时执行很多任务的呢?
这就需要用到进程、线程之类的抽象了,这也是早期计算机引入多进程的主要目的,让计算机看起来可以同时执行不同的任务。
我们通常会把不同的程序分配给不同的独立进程去执行,让计算机基于一定的策略,把 CPU 的计算时间调度给不同的进程使用;但因为进程间切换的时间一般比较短,并不能达到人们能感知到的阈值,所以用户在使用计算机的时候就会觉得多个程序或者任务是同时,也就是并发,执行的。
简单来说,当你启动一个应用程序时,操作系统会为该程序创建一个或多个进程来执行其代码。每个进程都有独立的内存空间、系统资源以及自己的状态,这使得它们在执行时不会相互干扰。
进程主要由以下几个部分组成:
- 程序代码:即要执行的实际指令序列。
- 数据集:包括全局变量、局部变量、堆栈等存储数据的地方。
- 系统资源:如打开的文件、网络连接等,这些资源由操作系统分配给进程使用。
- 状态:进程在其生命周期中可以处于不同的状态,常见的有新建(创建但尚未开始运行)、就绪(等待CPU时间片)、运行(正在CPU上执行)、阻塞(等待某个事件发生,如I/O操作完成)和终止(执行完毕或被强制结束)。
每个进程都拥有独立的虚拟地址空间,这意味着即使两个进程运行相同的程序,它们也各自拥有独立的数据拷贝,并且不能直接访问对方的内存空间。这种隔离机制提高了系统的稳定性和安全性。
操作系统通过进程控制块(Process Control Block, PCB)来管理进程。PCB包含了进程的所有信息,如进程ID、状态、优先级、寄存器内容、内存分配情况等,操作系统根据这些信息来进行进程调度、资源管理和状态转换等操作。当操作系统需要切换当前运行的进程时,它会保存当前进程的上下文(即处理器状态),然后加载下一个将要运行的进程的上下文,这一过程称为上下文切换。
如果你在 Linux 系统上运行一下ps命令,就可以看到你的计算机当前正在运行的许多进程了:
可以看到进程都会被分配一个 PID,也就是进程的标识符。
而每个进程在执行程序的时候显然也要访问内存,也需要自己的程序计数器等资源,操作系统都会给每个进程独立分配这些资源。
调度算法
进程调度算法是操作系统用于决定哪个进程将在CPU上执行的策略。其主要目的是优化系统资源的使用,提高系统的整体性能和响应速度,同时确保所有进程都能公平地获得CPU时间,旨在解决以下几个关键问题:
- 最大化CPU利用率:通过合理安排进程执行顺序,减少CPU空闲时间。
- 最小化响应时间:对于交互式应用尤为重要,缩短从用户输入到系统响应的时间。
- 保证公平性:确保每个进程都能得到合理的CPU时间,避免某些进程长时间得不到执行。
- 支持优先级处理:允许高优先级的进程优先获得CPU资源。
几种常见的进程调度算法及其优缺点和适用场景:
-
先来先服务(FCFS, First-Come, First-Served)
- 优点:实现简单,易于理解和实现;对长进程较为友好。
- 缺点:平均等待时间可能较长,特别是当首个进程很长时会导致后面的进程等待过久;可能导致“凸轮效应”,即短作业被长作业阻塞。
- 适用场景:适用于批处理系统,尤其是不需要特别考虑响应时间的场合。
-
短作业优先(SJF, Shortest Job First)
- 优点:理论上可以达到最低的平均等待时间。
- 缺点:需要预先知道每个作业的运行时间;可能导致长作业长期等待(饥饿)。
- 适用场景:适合于能够预估作业长度的批处理环境。
-
优先级调度(Priority Scheduling)
- 优点:可以灵活地根据任务的重要性进行调度。
- 缺点:如果没有适当的措施,低优先级的任务可能会遭受饥饿。
- 适用场景:适用于需要区分任务重要性的实时系统和多任务环境。
-
轮转调度(Round Robin, RR)
- 优点:提供良好的响应时间,对所有进程相对公平。
- 缺点:频繁的上下文切换会带来额外开销;如果时间片设置不当,会影响效率。
- 适用场景:适用于分时系统,以及需要保证多个用户或进程之间快速响应的应用场景。
-
多级反馈队列调度(Multilevel Feedback Queue Scheduling)
- 优点:灵活性高,能适应不同类型的工作负载;通过动态调整进程优先级来优化性能。
- 缺点:配置复杂,参数调整困难。
- 适用场景:广泛应用于现代通用操作系统中,因为它能够很好地平衡各种需求,包括响应时间、周转时间和CPU利用率等。
Swift 模拟实现 先来先服务(FCFS)进程调度算法
import Foundation
// MARK: - 进程模型定义
struct Process {
let id: String // 进程标识
let arrivalTime: Int // 到达时间
let burstTime: Int // 执行时间(CPU时间)
var startTime: Int = 0 // 开始执行时间
var finishTime: Int = 0 // 完成时间
var waitingTime: Int = 0 // 等待时间
var turnaroundTime: Int = 0// 周转时间
}
// MARK: - FCFS 调度器实现
class FCFSScheduler {
private var processes: [Process]
init(processes: [Process]) {
self.processes = processes
// 按到达时间排序(先到先服务)
self.processes.sort { $0.arrivalTime < $1.arrivalTime }
}
/// 执行调度并计算时间指标
func schedule() {
var currentTime = 0
for i in 0..<processes.count {
var process = processes[i]
// 如果当前时间 < 进程到达时间,等待进程到达
if currentTime < process.arrivalTime {
currentTime = process.arrivalTime
}
// 记录开始时间和完成时间
process.startTime = currentTime
process.finishTime = currentTime + process.burstTime
process.waitingTime = currentTime - process.arrivalTime
process.turnaroundTime = process.finishTime - process.arrivalTime
// 更新当前时间
currentTime = process.finishTime
processes[i] = process
}
}
/// 打印调度结果和统计数据
func printResults() {
print("进程ID | 到达时间 | 执行时间 | 开始时间 | 完成时间 | 等待时间 | 周转时间")
print("---------------------------------------------------------------")
var totalWaitingTime = 0
var totalTurnaroundTime = 0
for process in processes {
print("\(process.id)\t| \(process.arrivalTime)\t\t| \(process.burstTime)\t\t| \(process.startTime)\t\t| \(process.finishTime)\t\t| \(process.waitingTime)\t\t| \(process.turnaroundTime)")
totalWaitingTime += process.waitingTime
totalTurnaroundTime += process.turnaroundTime
}
let avgWaitingTime = Double(totalWaitingTime) / Double(processes.count)
let avgTurnaroundTime = Double(totalTurnaroundTime) / Double(processes.count)
print("\n平均等待时间: \(String(format: "%.2f", avgWaitingTime))")
print("平均周转时间: \(String(format: "%.2f", avgTurnaroundTime))")
}
}
// MARK: - 测试案例
let testProcesses = [
Process(id: "P1", arrivalTime: 0, burstTime: 5),
Process(id: "P2", arrivalTime: 1, burstTime: 3),
Process(id: "P3", arrivalTime: 2, burstTime: 8),
Process(id: "P4", arrivalTime: 3, burstTime: 6)
]
let scheduler = FCFSScheduler(processes: testProcesses)
scheduler.schedule()
scheduler.printResults()
运行结果
进程ID | 到达时间 | 执行时间 | 开始时间 | 完成时间 | 等待时间 | 周转时间
---------------------------------------------------------------
P1 | 0 | 5 | 0 | 5 | 0 | 5
P2 | 1 | 3 | 5 | 8 | 4 | 7
P3 | 2 | 8 | 8 | 16 | 6 | 14
P4 | 3 | 6 | 16 | 22 | 13 | 19
平均等待时间: 5.75
平均周转时间: 11.25
Swift 模拟实现优先级调度(Priority Scheduling)算法
import Foundation
// 进程结构体
struct Process {
let pid: Int // 进程ID
let arrivalTime: Int // 到达时间
let burstTime: Int // 执行时间
let priority: Int // 优先级(数值越小优先级越高)
// 计算属性:完成时间、周转时间、等待时间
var completionTime: Int = 0 // 完成时间
var turnaroundTime: Int = 0 // 周转时间 = 完成时间 - 到达时间
var waitingTime: Int = 0 // 等待时间 = 周转时间 - 执行时间
}
// 优先级调度算法实现
func priorityScheduling(processes: [Process]) -> [Process] {
// 复制进程数组以便修改
var processes = processes
let n = processes.count
// 当前时间
var currentTime = 0
// 已完成的进程数
var completed = 0
// 记录进程是否已完成
var isCompleted = Array(repeating: false, count: n)
while completed != n {
// 查找当前时间点已到达且未完成的进程
var eligibleProcesses: [Int] = []
for i in 0..<n {
if !isCompleted[i] && processes[i].arrivalTime <= currentTime {
eligibleProcesses.append(i)
}
}
if eligibleProcesses.isEmpty {
// 没有符合条件的进程,时间推进到下一个进程的到达时间
var nextArrivalTime = Int.max
for i in 0..<n {
if !isCompleted[i] && processes[i].arrivalTime < nextArrivalTime {
nextArrivalTime = processes[i].arrivalTime
}
}
currentTime = nextArrivalTime
continue
}
// 找到优先级最高的进程(优先级数值越小优先级越高)
var highestPriorityIndex = eligibleProcesses[0]
for index in eligibleProcesses {
if processes[index].priority < processes[highestPriorityIndex].priority {
highestPriorityIndex = index
}
}
// 执行该进程
processes[highestPriorityIndex].completionTime = currentTime + processes[highestPriorityIndex].burstTime
processes[highestPriorityIndex].turnaroundTime = processes[highestPriorityIndex].completionTime - processes[highestPriorityIndex].arrivalTime
processes[highestPriorityIndex].waitingTime = processes[highestPriorityIndex].turnaroundTime - processes[highestPriorityIndex].burstTime
// 更新当前时间和完成状态
currentTime = processes[highestPriorityIndex].completionTime
isCompleted[highestPriorityIndex] = true
completed += 1
}
return processes
}
// 计算平均周转时间和平均等待时间
func calculateAverages(processes: [Process]) -> (averageTurnaroundTime: Double, averageWaitingTime: Double) {
let totalTurnaroundTime = processes.reduce(0) { $0 + $1.turnaroundTime }
let totalWaitingTime = processes.reduce(0) { $0 + $1.waitingTime }
let averageTurnaroundTime = Double(totalTurnaroundTime) / Double(processes.count)
let averageWaitingTime = Double(totalWaitingTime) / Double(processes.count)
return (averageTurnaroundTime, averageWaitingTime)
}
// 示例用法
func main() {
// 创建进程
let processes = [
Process(pid: 1, arrivalTime: 0, burstTime: 5, priority: 3),
Process(pid: 2, arrivalTime: 1, burstTime: 3, priority: 1),
Process(pid: 3, arrivalTime: 2, burstTime: 8, priority: 4),
Process(pid: 4, arrivalTime: 3, burstTime: 6, priority: 2)
]
// 执行优先级调度算法
let scheduledProcesses = priorityScheduling(processes: processes)
// 输出调度结果
print("进程调度结果:")
print("PID\t到达时间\t执行时间\t优先级\t完成时间\t周转时间\t等待时间")
for process in scheduledProcesses {
print("\(process.pid)\t\(process.arrivalTime)\t\t\(process.burstTime)\t\t\(process.priority)\t\t\(process.completionTime)\t\t\(process.turnaroundTime)\t\t\(process.waitingTime)")
}
// 计算并输出平均时间
let averages = calculateAverages(processes: scheduledProcesses)
print("\n平均周转时间: \(averages.averageTurnaroundTime)")
print("平均等待时间: \(averages.averageWaitingTime)")
}
运行结果
| PID | 到达时间 | 执行时间 | 优先级 | 完成时间 | 周转时间 | 等待时间 |
|---|---|---|---|---|---|---|
| 1 | 0 | 5 | 3 | 5 | 5 | 0 |
| 2 | 1 | 3 | 1 | 8 | 7 | 4 |
| 3 | 2 | 8 | 4 | 22 | 20 | 12 |
| 4 | 3 | 6 | 2 | 14 | 11 | 5 |
平均周转时间: 10.75
平均等待时间: 5.25