本文发布且更新于个人博客 https://www.xerrors.fun/Job-Scheduling-in-Multiprogramming-System/
转载请注明出处
上一篇文章已经实现了单道程序系统的作业调度模拟程序。那么这次的要求就要更加刁钻了。
作业调度算法:采用基于「先来先服务」的调度算法或「基于优先级」的作业调度算法。其余可以参考课本中的方法进行设计。对于多道程序系统,要假定系统中具有的各种资源及数量、调度作业时必须考虑到每个作业的资源要求
核心的改变是变成了多道程序的调度系统,那么也就自然而然的带来了资源的争夺问题;
两种调度算法
先来先服务(FCFS)是按照作业到达的先后顺序来进行作业调度的。
优先级调度算法(priority-scheduling algorithm, PSA)
我们可以这样来看作业的优先级,对于先来先服务调度算法,作业的等待时间就是作业的优先级,等待时间越长,其优先级越高。对于短作业优先调度算法,作业的长短就是作业的优先级,作业所需运行的时间越短,其优先级越高。但上述两种优先级都不能反映作业的紧迫程度。而在优先级调度算法中,则是基于作业的紧迫程度,由「外部赋予」作业相应的优先级,调度算法是根据该优先级进行调度的。这样就可以保证紧迫性作业优先运行。优先级调度算法可作为作业调度算法,也可作为进程调度算法。当把该算法用于作业调度时,系统是从后备队列中选择若干个优先级最高的作业装入内存。
——摘自汤子瀛《计算机操作系统(第四版)》
从上面的说明可以看到两个算法的唯一区别就是对待优先的看法不同,放在最小堆里面来说就是比较大小的方法不一样,所以跟上个程序一样,我们采用相同的「处理逻辑」和不同的「小于运算符」
def func(jobs, method, n):
JCB.__lt__ = getLowerThan(method)
# 省略了其他部分
return None
def getLowerThan(method):
def FCFS_lt(self, other):
# 比较到达时间的大小
return self.commit_time < other.commit_time
def PSA_lt(self, other):
# 比较优先级的大小,越小代表优先级越高
return self.priority < other.priority
if method == 'FCFS':
return FCFS_lt
elif method == 'PSA':
return PSA_lt
具体实现过程
作业控制块 JCB 的数据结构
由于采用了多道程序的调度系统,所以首先对作业的类进行修改,为了实现 PSA,新增了优先级属性「priority」。
class JCB:
# 表示系统中的资源数量,其中 1 表示独占资源
srcs = np.array([3, 2, 4, 1, 3, 1])
clock = 0 # 类的时钟
def __init__(self, priority, time, srcs):
self.priority = priority
self.name = chr(ord('A') + JCB.clock) # 名称
self.time = time # 所需时间
self.status = 'Wait' # 作业的状态
self.commit_time = JCB.clock # 作业到达时间
# 所需的资源,单道程序调度用不到的
self.srcs = srcs
JCB.clock += 1 # 类的时钟 +1
初始化作业,为了写起来方便,使用了很多的单行语句,降低了部分的可读性,这里稍微解释一下
a = [ 1 if random.random() > 0.5 else 0 for i in range(len(JCB.srcs))]
# 等同于
a = []
for i in range(len(JCB.srcs)):
if random.random() > 0.5:
a.append(1)
else:
a.append(0)
初始化作业控制块 JCB
# 初始化所有 JCB
def init():
# 创建
jsb_list = [JCB(
random.randrange(1, 8), # 优先级
random.randrange(2, 6), # 所需时间
[ 1 if random.random() > 0.5 else 0 for i in range(len(JCB.srcs))] # 资源
) for i in range(random.randrange(5, 9))] # 个数限定为 5-9 个
return jsb_list
创建结果:
名称:A 到达时间:0 所需时间:3 优先级:7 资源:[0, 0, 0, 1, 0, 1]
名称:B 到达时间:1 所需时间:2 优先级:4 资源:[1, 0, 1, 1, 1, 1]
名称:C 到达时间:2 所需时间:5 优先级:6 资源:[0, 0, 0, 0, 1, 0]
名称:D 到达时间:3 所需时间:4 优先级:1 资源:[0, 1, 1, 0, 0, 1]
名称:E 到达时间:4 所需时间:2 优先级:7 资源:[1, 1, 1, 0, 0, 0]
名称:F 到达时间:5 所需时间:2 优先级:3 资源:[0, 1, 0, 0, 0, 0]
作业调度的思维逻辑
我们首先是创建一个外层循环代表一次次的时间片,循环的结束条件有三个:
- 不存在还未到达的作业了
- 作业的堆(优先队列)中已经不存在作业了
- 没有正在运行的作业了
我们使用了两个数组来保存作业,一个是作业清单 jobs 也就是一开始咱们进行初始化的 JCB 的集合,初始状态包含所有的作业,所有的作业按照创建时间(到达时间)排序。另外一个是优先序列 jobs_heap,里面保存了已经到达,但是还没有运行的作业,相当于是就绪序列;
在单道处理程序中,我们使用了一个 currentJob
来表示正在运行的作业,那么对于多道程序调度系统而言,使用一个数组 container
来表示正在运行的作业的集合。示例中所采用的是固定长度的数组,如 [0, 0, 0]
;当有作业开始运行的时候,就把作业放到数组中的值为 0 的位置,运行结束后将该位置置为 0;
因为是用的 Python 语言来编写的,可以使用变动长度的列表来实现,有作业开始运行就 append 进去,运行结束就 pop 出去,不过要限制列表的长度不能超过系统同时能够运行的最大的作业数。
container = [0] * n
# 初始化,需要考虑作业的到达时间
clock = 0
jobs_heap = []
while len(jobs) != 0 or len(jobs_heap) != 0 or any(container):
# 执行剩余部分
pass
循环的一开始,首先看一看这个时刻是否有任务「到来」,也就是判断当前系统的 clock,是否等于某个处于作业清单 jobs 的第一个作业(作业是按照到达时间排序的),如果存在就把该作业放到作业的优先队列 jobs_heap 里面去。
while len(jobs) != 0 or len(jobs_heap) != 0 or any(container):
# 作业到达并入堆
while len(jobs) > 0 and clock == jobs[0].commit_time:
heapq.heappush(jobs_heap, jobs.pop(0))
然后我们要判断系统当前状态能不能继续添加任务,首先检测 container 中是否存在 0 的位置,如果存在代表当前运行的作业总数没有达到最大值;
然后先找取出堆顶元素,判断当前系统的资源是否满足该作业对资源的需求,如果满足就开始运行该作业,如果系统资源不足以运行该作业就把该作业放到一个「临时数组」里面,这时候取出下一个堆顶元素,进行一样的判断,直到同时运行的作业数达到系统的最大值,或者优先队列 jobs_heap 已经空了为止,在此之后还需要把临时数组里面的作业挨个重新放回放进优先队列 jobs_heap 中。
while len(jobs) != 0 or len(jobs_heap) != 0 or any(container):
# 作业到达并入堆
temp = []
# 判断当前是否可以继续执行任务
while 0 in container and len(jobs_heap) > 0:
heap_top = heapq.heappop(jobs_heap)
if (JCB.srcs >= heap_top.srcs).all():
container[container.index(0)] = heap_top
JCB.srcs -= heap_top.srcs
else:
print('资源不足')
# 保存到临时数组里
temp.append(heap_top)
# 放回优先队列
while len(temp) > 0:
heapq.heappush(jobs_heap, temp.pop())
那么如何检测当前剩余的资源能否满足使用?将原本的资源用 numpy 的数组来表示,对于每一个出堆的顶点,通过 (JCB.srcs >= heap_top.srcs).all()
来对数组进行比较,是否每个资源都满足该作业的条件。
下一步就是遍历所有作业,把每个作业所需的时间 -1,然后检测是否有已经运行完成的作业,如果有的话,归还所有资源,该作业在 container 的位置置为 0,留出空间和资源给后续作业。
for i in range(n):
if container[i] != 0:
container[i].time -= 1
if container[i].time == 0:
JCB.srcs += container[i].srcs
container[i] = 0
本块的完整代码:
def func(jobs, method, n):
JCB.__lt__ = getLowerThan(method)
container = [0] * n
# 初始化,需要考虑作业的到达时间
clock = 0
jobs_heap = []
while len(jobs) != 0 or len(jobs_heap) != 0 or any(container):
# 作业到达并入堆
while len(jobs) > 0 and clock == jobs[0].commit_time:
heapq.heappush(jobs_heap, jobs.pop(0))
temp = []
# 判断当前是否可以继续执行任务
while 0 in container and len(jobs_heap) > 0:
heap_top = heapq.heappop(jobs_heap)
if (JCB.srcs >= heap_top.srcs).all():
container[container.index(0)] = heap_top
JCB.srcs -= heap_top.srcs
else:
print('资源不足')
# 保存到临时数组里
temp.append(heap_top)
# 放回优先队列
while len(temp) > 0:
heapq.heappush(jobs_heap, temp.pop())
for i in range(n):
if container[i] != 0:
print('正在运行{},还需时间 {}'.format(container[i].name, container[i].time))
container[i].time -= 1
if container[i].time == 0:
container[i].status = 'Finish'
print('作业{}已完成'.format(container[i].name))
JCB.srcs += container[i].srcs
container[i] = 0
clock += 1
print()
return None
完整代码及参考资料
最后就是加上一些输出信息供调试以及演示使用;如果有做的不好的地方、对这个算法有改进的建议、对我的表述逻辑有建议等等,欢迎提出批评建议;如果觉得做的不错的话,也可以在下方留言鼓励一下!笔芯!
import random
import heapq
import copy
import numpy as np
class JCB:
# 表示系统中的资源数量,其中 1 表示独占资源
srcs = np.array([3, 2, 4, 1, 3, 1])
clock = 0 # 类的时钟
def __init__(self, priority, time, srcs):
self.priority = priority
self.name = chr(ord('A') + JCB.clock) # 名称
self.time = time # 所需时间
self.status = 'Wait' # 作业的状态
self.commit_time = JCB.clock # 作业到达时间
# 所需的资源,单道程序调度用不到的
self.srcs = srcs
JCB.clock += 1 # 类的时钟 +1
# 初始化所有 JCB
def init():
jsb_list = [JCB(
random.randrange(1, 8),
random.randrange(2, 6),
[ 1 if random.random() > 0.5 else 0 for i in range(len(JCB.srcs))]
) for i in range(random.randrange(5, 9))]
for job in jsb_list:
print('名称:{}\t到达时间:{}\t所需时间:{}\t优先级:{}\t资源:{}'.format(
job.name,
job.commit_time,
job.time,
job.priority,
job.srcs
))
print()
return jsb_list
def func(jobs, method, n):
JCB.__lt__ = getLowerThan(method)
container = [0] * n
# 初始化,需要考虑作业的到达时间
clock = 0
jobs_heap = []
while len(jobs) != 0 or len(jobs_heap) != 0 or any(container):
# 作业到达并入堆
while len(jobs) > 0 and clock == jobs[0].commit_time:
heapq.heappush(jobs_heap, jobs.pop(0))
temp = []
# 判断当前是否可以继续执行任务
while 0 in container and len(jobs_heap) > 0:
heap_top = heapq.heappop(jobs_heap)
if (JCB.srcs >= heap_top.srcs).all():
container[container.index(0)] = heap_top
JCB.srcs -= heap_top.srcs
else:
print('资源不足')
# 保存到临时数组里
temp.append(heap_top)
# 放回优先队列
while len(temp) > 0:
heapq.heappush(jobs_heap, temp.pop())
for i in range(n):
if container[i] != 0:
print('正在运行{},还需时间 {}'.format(container[i].name, container[i].time))
container[i].time -= 1
if container[i].time == 0:
container[i].status = 'Finish'
print('作业{}已完成'.format(container[i].name))
JCB.srcs += container[i].srcs
container[i] = 0
clock += 1
print()
return None
def getLowerThan(method):
def FCFS_lt(self, other):
# 比较到达时间的大小
return self.commit_time < other.commit_time
def PSA_lt(self, other):
# 比较优先级的大小,越小代表优先级越高
return self.priority < other.priority
if method == 'FCFS':
return FCFS_lt
elif method == 'PSA':
return PSA_lt
def main():
# 定义同时可以执行的作业的个数
n = 3
jobs = init()
method = input()
# method = 'PSA'
if method == 'FCFS' or method == 'PSA':
func(copy.deepcopy(jobs), method, n)
if __name__ == '__main__':
main()
[1] 汤子瀛《计算机操作系统(第四版)》第三章