调度算法:操作系统中的进程是如何调度的?

709 阅读9分钟

在许多中间件、语言设计甚至日常开发的业务系统中遇到问题时,我们常常会参考操作系统中成熟的解决办法,进程调度就是这样一种常常被借鉴的场景,在不少语言的线程或者协程机制的设计里都有应用。

那操作系统的进程调度到底是如何设计的呢?

进程是什么?

我们知道计算机是可以同时进行很多任务的,比如边开着浏览器阅读文章,边打开着微信软件随时处理好友的消息。计算机就像一个真正的时间管理大师一样,并发而有条不紊地处理着各种复杂的任务。

但事实上,每个 CPU 核在同一时间只能同时运行一个程序,那计算机是如何做到看起来可以同时执行很多任务的呢?

这就需要用到进程、线程之类的抽象了,这也是早期计算机引入多进程的主要目的,让计算机看起来可以同时执行不同的任务。

我们通常会把不同的程序分配给不同的独立进程去执行,让计算机基于一定的策略,把 CPU 的计算时间调度给不同的进程使用;但因为进程间切换的时间一般比较短,并不能达到人们能感知到的阈值,所以用户在使用计算机的时候就会觉得多个程序或者任务是同时,也就是并发,执行的。

简单来说,当你启动一个应用程序时,操作系统会为该程序创建一个或多个进程来执行其代码。每个进程都有独立的内存空间、系统资源以及自己的状态,这使得它们在执行时不会相互干扰。

进程主要由以下几个部分组成:

  1. 程序代码:即要执行的实际指令序列。
  2. 数据集:包括全局变量、局部变量、堆栈等存储数据的地方。
  3. 系统资源:如打开的文件、网络连接等,这些资源由操作系统分配给进程使用。
  4. 状态:进程在其生命周期中可以处于不同的状态,常见的有新建(创建但尚未开始运行)、就绪(等待CPU时间片)、运行(正在CPU上执行)、阻塞(等待某个事件发生,如I/O操作完成)和终止(执行完毕或被强制结束)。

每个进程都拥有独立的虚拟地址空间,这意味着即使两个进程运行相同的程序,它们也各自拥有独立的数据拷贝,并且不能直接访问对方的内存空间。这种隔离机制提高了系统的稳定性和安全性。

操作系统通过进程控制块(Process Control Block, PCB)来管理进程。PCB包含了进程的所有信息,如进程ID、状态、优先级、寄存器内容、内存分配情况等,操作系统根据这些信息来进行进程调度、资源管理和状态转换等操作。当操作系统需要切换当前运行的进程时,它会保存当前进程的上下文(即处理器状态),然后加载下一个将要运行的进程的上下文,这一过程称为上下文切换。

如果你在 Linux 系统上运行一下ps命令,就可以看到你的计算机当前正在运行的许多进程了:

c8dcb1b07e65893dcd3bfd6a1e919614.webp

可以看到进程都会被分配一个 PID,也就是进程的标识符。

而每个进程在执行程序的时候显然也要访问内存,也需要自己的程序计数器等资源,操作系统都会给每个进程独立分配这些资源。

调度算法

进程调度算法是操作系统用于决定哪个进程将在CPU上执行的策略。其主要目的是优化系统资源的使用,提高系统的整体性能和响应速度,同时确保所有进程都能公平地获得CPU时间,旨在解决以下几个关键问题:

  1. 最大化CPU利用率:通过合理安排进程执行顺序,减少CPU空闲时间。
  2. 最小化响应时间:对于交互式应用尤为重要,缩短从用户输入到系统响应的时间。
  3. 保证公平性:确保每个进程都能得到合理的CPU时间,避免某些进程长时间得不到执行。
  4. 支持优先级处理:允许高优先级的进程优先获得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到达时间执行时间优先级完成时间周转时间等待时间
1053550
2131874
3284222012
436214115

平均周转时间: 10.75
平均等待时间: 5.25