1. 抽象
1.1 Statement
#[derive(Debug, Copy, Clone)]
pub enum Statement {
CpuBound(u64),
IoBound(u64),
}
可以看作是一系列指令的抽象, 粗糙且武断地将其分为了CPU-bound和I/O-bound. 中间的数据是执行时间, 前者需要CPU执行相应时间, 后者则需要CPU调度入等待队列, 直至I/O设备就绪. 以此来模拟CPU-bound和I/O-bound任务.
1.2 Job & Process
这里的Job不是作业调度里的作业, 只是一系列语句的抽象, 可看作一个程序. 而进程则是正在运行的作业, 维护有进程ID,到达时间,已运行时间,正执行的语句等信息.
Job分为CPU-bound, I/O-bound和平均, 代表了其中不同语句的比例.
#[derive(Debug, Clone)]
pub struct Job {
pub statements: Vec<Statement>,
pub total_duration: u64,
pub total_cpu_duration: u64,
pub total_io_duration: u64,
pub is_io_bound: bool,
}
#[derive(Debug, Clone)]
pub struct Process {
pub id: PId,
pub job: Arc<Job>,
arrival_time: u64,
completion_time: u64,
burst_time: u64,
running_statement: Option<RunningStatement>,
}
1.3 OS
操作系统的抽象, 主要有clock
字段(模拟时钟), 定时器waiting
(处理I/O), 正在运行的进程, 所有进程, 具体的调度器等字段.
操作系统执行一个循环, 直至所有进程运行完毕. 每次迭代执行tick
函数, clock
自增,waiting
自增. 然后调用调度器的on_tick
算法, 执行相应的调度操作. 操作系统暴露给调度器, 因此调度器可以在调度过程中调用操作系统的一些方法如让进程陷入等待, 完成进程, 切换进程等.
pub struct Os {
clock: u64,
processes: IndexMap<PId, Process>,
waiting: HashedWheel<PId>,
running_process_pid: Option<PId>,
// thread-safe actually not needed
scheduler: Arc<Mutex<Box<dyn Scheduler + Send>>>,
// ...
}
impl Os {
pub fn run(&mut self) {
while !self.is_completed() {
self.tick();
}
}
pub fn tick(&mut self) {
self.clock += TICK;
self.waiting.tick();
let scheduler = self.scheduler.clone();
let mut scheduler = scheduler.lock().expect("lock failed");
scheduler.on_tick(self);
}
}
1.4 Scheduler
调度器的抽象, 这里只是一个trait
, 描述了调度器应该实现的方法, 以及默认实现了部分方法.
一般情况下, 非抢占式调度算法只需实现on_process_ready
和switch_process
方法即可, 抢占式则额外实现on_process_burst
. 在调度器的on_tick
方法中, 首先检查是否有已经从等待队列中就绪的进程, 有则调用调度器的on_process_ready
方法. 然后调用burst_process
方法, 如果进程执行到下一条新的语句, 则执行相应语句, 如果是CPU-bound语句则什么都不做, 是I/O语句则让进程直接跳到下一条语句, 同时检查进程是否已经执行完毕; 如果进程已经执行完毕, 同时进程还没被切换出去, 则调用调度器的swtich_process
方法强制切换进程.
pub trait Scheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: PId);
/// FORCED TO SWITCH!!!
fn switch_process(&mut self, os: &mut Os);
fn desc(&self) -> &'static str;
fn on_tick(&mut self, os: &mut Os) {
while let Some(pid) = os.waiting.expired_timeout() {
log::trace!("Clock[{}]: Process[{}] Ready", os.clock, pid);
self.on_process_ready(os, pid);
}
self.burst_process(os);
}
fn burst_process(&mut self, os: &mut Os) {
let clock = os.clock;
if let Some((new_statement, is_completed, pid)) = os
.running_process()
.map(|process| (process.burst(clock), process.is_completed(), process.id))
{
if let Some(new_statement) = new_statement {
log::trace!(
"Clock[{}]: Process[{}] New Statement::{:?}",
clock,
pid,
new_statement,
);
self.run_statement(os, new_statement, pid);
} else if is_completed {
os.complete_process(pid);
if os.is_process_running(pid) {
self.switch_process(os);
}
}
self.on_process_burst(os, pid);
} else {
self.switch_process(os);
}
}
/// Run New Statement
fn run_statement(&mut self, os: &mut Os, statement: Statement, pid: PId) {
match statement {
Statement::CpuBound(duration) => self.run_cpu_bound_statement(os, duration, pid),
Statement::IoBound(duration) => self.run_io_bound_statement(os, duration, pid),
}
}
#[allow(unused)]
fn run_cpu_bound_statement(&mut self, os: &mut Os, duration: u64, pid: PId) {}
fn run_io_bound_statement(&mut self, os: &mut Os, duration: u64, pid: PId) {
let clock = os.clock;
if let Some((pid, is_completed)) = os.get_mut_process(pid).map(|process| {
if let Some(next_statement) = process.bump_to_next(clock) {
log::trace!(
"Clock[{}]: Process[{}] Bump to Next Statement::{:?}",
clock,
process.id,
next_statement
);
}
(process.id, process.is_completed())
}) {
if is_completed {
os.complete_process(pid);
} else {
os.await_process(pid, duration);
}
}
if os.is_process_running(pid) {
self.switch_process(os);
}
}
/// Usually be Implemented by Preemptive Algorithms.
/// CHECK THE RUNNING PROCESS BEFORE SWITCH!!!
#[allow(unused)]
fn on_process_burst(&mut self, os: &mut Os, pid: PId) {}
}
2. 调度算法具体实现
2.1 First Come First Serve(非抢占式, 作业/进程)
最简单的调度算法, 当进程就绪时放入就绪队列, 当要求进程切换时从就绪队列头部弹出相应进程然后切换. 因为switch_process
是强制切换进程, 所以即使就绪队列为空, 也应该调用操作系统的进程切换方法.这样操作系统会进入Idle状态.
#[derive(Default, Clone)]
pub struct FirstComeFirstServeScheduler {
ready_queue: VecDeque<PId>,
}
impl Scheduler for FirstComeFirstServeScheduler {
fn on_process_ready(&mut self, _os: &mut Os, pid: usize) {
self.ready_queue.push_back(pid);
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop_front());
}
}
2.2 Shortest Job First(非抢占式, 作业)
#[derive(Default, Clone)]
pub struct ShortestJobFirstScheduler {
ready_queue: KeyedPriorityQueue<PId, Reverse<u64>>,
}
impl Scheduler for ShortestJobFirstScheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: usize) {
if let Some(process) = os.get_process(pid) {
let burst_time = process.burst_time();
self.ready_queue.push(pid, Reverse(burst_time));
}
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop().map(|(pid, _)| pid));
}
}
2.3 Shortest Remaining Job First(抢占式, 作业)
最短作业优先的抢占式版本. 如果存在比当前进程剩余时间更短的进程, 则切换到它.
#[derive(Default, Clone)]
pub struct ShortestRemainingJobFirstScheduler {
ready_queue: KeyedPriorityQueue<PId, Reverse<u64>>,
}
impl Scheduler for ShortestRemainingJobFirstScheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: usize) {
if let Some(process) = os.get_process(pid) {
self.ready_queue
.push(pid, Reverse(process.remaining_time()));
}
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop().map(|(pid, _)| pid));
}
fn on_process_burst(&mut self, os: &mut Os, pid: PId) {
let process_remaining_time = os.get_process(pid).map(|p| p.remaining_time()).unwrap_or(0);
if self
.ready_queue
.peek()
.map_or(false, |(_, top_remaining_time)| {
top_remaining_time.gt(&&Reverse(process_remaining_time))
})
{
self.switch_process(os);
self.ready_queue.push(pid, Reverse(process_remaining_time));
}
}
}
2.4 Longest Job First(非抢占式, 作业)
#[derive(Default, Clone)]
pub struct LongestJobFirstScheduler {
ready_queue: KeyedPriorityQueue<PId, u64>,
}
impl Scheduler for LongestJobFirstScheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: usize) {
if let Some(process) = os.get_process(pid) {
let burst_time = process.burst_time();
self.ready_queue.push(pid, burst_time);
}
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop().map(|(pid, _)| pid));
}
}
2.5 Longest Remaining Job First(抢占式, 作业)
#[derive(Default, Clone)]
pub struct LongestRemainingJobFirstScheduler {
ready_queue: KeyedPriorityQueue<PId, u64>,
}
impl Scheduler for LongestRemainingJobFirstScheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: usize) {
if let Some(process) = os.get_process(pid) {
self.ready_queue.push(pid, process.remaining_time());
}
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop().map(|(pid, _)| pid));
}
fn on_process_burst(&mut self, os: &mut Os, pid: PId) {
if !os.is_process_running(pid) {
return;
}
let current_remaining_time = os.get_process(pid).map(|p| p.remaining_time()).unwrap_or(0);
if self
.ready_queue
.peek()
.map_or(false, |(_, remaining_time)| remaining_time.gt(¤t_remaining_time))
{
self.switch_process(os);
self.ready_queue.push(pid, current_remaining_time);
}
}
}
2.6 Highest Response Ratio Next(非抢占式, 作业)
#[derive(Default, Clone)]
pub struct HighestResponseRatioNextScheduler {
ready_queue: KeyedPriorityQueue<PId, u64>
}
impl Scheduler for HighestResponseRatioNextScheduler {
fn on_process_ready(&mut self, os: &mut Os, pid: usize) {
if let Some(process) = os.get_process(pid) {
self.ready_queue.push(pid, process.job.response_ratio());
}
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop().map(|(pid, _)| pid));
}
}
2.7 Round Robin(抢占式, 作业/进程)
时间片算法, 记录每个进程已使用的时间片, 如果已用完则调度到下一个进程, 此时为先到先服务.
#[derive(Default, Clone)]
pub struct RoundRobinScheduler {
ready_queue: VecDeque<PId>,
used_time_slice_map: HashMap<PId, u64>,
time_slice: u64,
}
impl Scheduler for RoundRobinScheduler {
fn on_process_ready(&mut self, _os: &mut Os, pid: usize) {
self.ready_queue.push_back(pid);
}
fn switch_process(&mut self, os: &mut Os) {
os.switch_process(self.ready_queue.pop_front());
}
fn on_process_burst(&mut self, os: &mut Os, pid: PId) {
let used_time_slice = self.used_time_slice_map.get(&pid).copied().unwrap_or(0);
if used_time_slice >= self.time_slice && os.is_process_running(pid) {
self.ready_queue.push_back(pid);
self.used_time_slice_map.insert(pid, 0);
self.switch_process(os);
} else {
self.used_time_slice_map.insert(pid, used_time_slice + TICK);
}
}
}
2.8 Multi-Level Feedback Queue(抢占式, 作业/进程)
三级队列, 前两级使用时间片算法调度, 两者时间片的长度不相同, 后一级则是先到先服务.在调度中, 前一级队列中的进程要先执行, 如队列已空则执行下一级队列中的进程. 前两级队列中, 如果进程的时间片用完, 该进程降级入下一级队列, 操作系统调度到下一个进程. 如果进程就绪, 进入最高级队列, 这里有两种情况, 一种是进程刚到达, 一种是进程I/O操作完毕.最后一级队列的调度是非抢占式的, 但是任何时刻前两级队列存在进程时, 则立即执行进程调度.
#[derive(Default, Clone)]
pub struct MultilevelFeedbackQueueScheduler {
ready_queues: [IndexSet<PId>; 3],
used_time_slice_map: HashMap<PId, u64>,
running_process: Option<(PId, usize)>,
time_slices: [u64; 2],
}
impl MultilevelFeedbackQueueScheduler {
/// 0/1/2, returns 0 if not found
pub fn get_process_priority(&self, pid: PId) -> usize {
self.running_process
.and_then(|(running_pid, priority)| (running_pid == pid).then_some(priority))
.unwrap_or_else(|| {
self.ready_queues
.iter()
.enumerate()
.find_map(|(i, queue)| queue.get(&pid).and(Some(i)))
.unwrap_or(0)
})
}
pub fn is_process_running(&self, pid: PId) -> bool {
self.running_process
.map_or(false, |(running_pid, _)| running_pid == pid)
}
pub fn downgrade_process(&mut self, pid: PId, clock: u64) {
let priority = self.get_process_priority(pid);
if priority >= self.ready_queues.len() - 1 {
return;
}
log::trace!("Clock[{}]: Process[{}] Downgrade to Queue[{}]", clock,pid,priority+1);
self.ready_queues[priority].remove(&pid);
self.ready_queues[priority + 1].insert(pid);
}
pub fn last_priority(&self) -> usize {
self.ready_queues.len() - 1
}
}
impl Scheduler for MultilevelFeedbackQueueScheduler {
fn on_process_ready(&mut self, _os: &mut Os, pid: usize) {
self.ready_queues[0].insert(pid);
}
fn switch_process(&mut self, os: &mut Os) {
if let Some((pid, priority)) = self
.ready_queues
.iter_mut()
.enumerate()
.find_map(|(priority, queue)| queue.pop().map(|pid| (pid, priority)))
{
self.running_process = Some((pid, priority));
os.switch_process(Some(pid));
} else {
self.running_process = None;
os.switch_process(None);
}
}
fn on_process_burst(&mut self, os: &mut Os, pid: usize) {
let priority = self.get_process_priority(pid);
let last_priority = self.last_priority();
if priority >= last_priority {
if self.ready_queues[0..last_priority]
.iter()
.any(|q| !q.is_empty())
{
self.ready_queues[last_priority].insert(pid);
self.switch_process(os);
}
} else {
let used_time_slice = self.used_time_slice_map.get(&pid).copied().unwrap_or(0);
if used_time_slice >= self.time_slices[priority] && os.is_process_running(pid) {
self.downgrade_process(pid, os.clock);
self.used_time_slice_map.insert(pid, 0);
self.switch_process(os);
} else {
self.used_time_slice_map.insert(pid, used_time_slice + TICK);
}
}
}
}
模拟执行与比较
三种不同的任务: CPU-bound, I/O-bound和平均.可以看出抢占式算法适合I/O-bound任务, 缺点是上下文切换次数多. 多级反馈队列算法是所有(实时进程调度)算法中最好的, 相比于朴素的时间片算法有着巨大的提升.
songzhi/os-learninggithub.com