Day 28 操作系统发展/进程
操作系统发展史
-
穿孔卡片
一个计算机机房,一次只能被一个卡片使用
-
联机批处理系统
支持多用户去使用一个计算机机房
-
脱机批处理系统
高速磁盘:
提高文件的读取数据
优点:
提高CPU使用率
-
多道技术
单道:
多个使用使用CPU时是串行的
多道技术:
- 多道:计算机内存中同时存放几道相互独立的程序
- 宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始各自的任务,但都未运行完毕
- 微观上串行:实际上,各道程序轮流使用CPU,并交替运行
多道系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理,处理机管理,存储器管理,外部设备管理,文件系统管理等功能
由于多个程序同时在计算机上运行,开始有了空间隔离的概念,只有内存空间的隔离,才能让数据更加安全,稳定
除了空间隔离之外,多道技术还第一次提现了时空复用的特点,遇到IO操作就切换程序,使得CPU的利用率提高了,计算机的效率也随之提高了
IO操作
- 若CPU遇到IO操作,会立即将当前执行程序CPU使用权断开
- 若一个程序使用CPU的时间过长,会立即将当前执行程序CPU使用权断开
并发与并行:
-
并发:指的是看起来像同时在运行,多个程序不停 切换+保存状态
-
并行:真正意义上的同时运行,在多核(多个CPU)的情况下,同时执行多个程序
进程
进程与程序
程序:一堆代码
进程:一堆代码执行的过程
进程调度
当代操作系统调度:时间片转轮法+分级反馈队列
-
先来先服务调度
a,b程序,若a程序先来,先占用CPU
缺点:程序a先使用,程序b必须等待程序a结束后才能使用CPU
-
短作业优先调度
a,b程序,谁用的时间短,谁先调度使用
缺点:必须等待所有短时间的程序结束后,长时间的程序才能使用
-
时间片轮法
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
在轮转法中,加入到就绪队列的进程有3种情况:
- 第一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
- 第二种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
- 第三种情况就是新创建进程进入就绪队列。
如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
-
分级反馈队列
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
- 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
- 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
- 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
进程的三个状态
就绪态
所有的进程创建时都进入到就绪态,准备调度
运行态
调度后的程序,进入运行态
阻塞态
凡是遇到IO操作的进程,都会进入阻塞态
若IO结束,必须重新进入就绪态
同步和异步
指的是提交任务的方式
-
同步
若有两个任务需要提交,在提交第一个任务时
必须等待该任务执行结束后,才能继续提交并执行第二个任务
-
异步
若有两个任务需要提交,在提交第一个任务时
不需要等待,直接提交第二个任务
阻塞和非阻塞
-
阻塞
阻塞态,遇到IO操作
-
非阻塞
就绪态
运行态
创建进程的两种方式
from multiprocessing import Process
import time
# 创建进程的方式一
def task(name):
print(f'{name}的任务开始执行')
time.sleep(1)
print(f'{name}的任务已经结束')
if __name__ == '__main__':
# target=执行函数的地址
p = Process(target=task, args=('tiny',))
# 像操作系统提交创建进程的任务
p.start()
print('主进程')
'''
windows:
创建子进程,windows会将当前父进程代码重新加载执行一次。
linux/mac:
会将当前父进程代码重新拷贝一份,再去执行。
'''
# 创建进程方式二:
# 1.自定义一个类,并继承Process
class MyProcess(Process):
# 必须要是run,否则无效,父类的方法
def run(self):
print('任务开始')
time(1)
print('任务结束')
if __name__ == '__main__':
p = MyProcess()
p.run()
print('主进程')
join()
'''
join方法:用来告诉操作系统,让子进程结束后,父进程再结束
'''
from multiprocessing import Process
import time
def task(name):
print(f'{name} start...')
time.sleep(1)
print(f'{name} end...')
if __name__ == '__main__':
p1 = Process(target=task, args=('tiny',))
p2 = Process(target=task, args=('jane',))
p1.start()
p1.join()
p2.start()
print('主进程')
tiny start...
tiny end...
主进程
jane start...
jane end...
进程间数据是相互隔离的
'''
进程间数据相互隔离:
主进程与子进程会产生各自的名称空间。
'''
from multiprocessing import Process
x = 100
def func():
print('执行func函数...')
global x
x = 200
if __name__ == '__main__':
p = Process(target=func)
p.start()
print(x)
print('主进程')
100
主进程
执行func函数...
进程对象的属性
import os
import time
from multiprocessing import Process
from multiprocessing import current_process
'''
current_process 进程的PID号
terminate 终止子进程
is_alive 判断子进程是否存活
os.getpid 获取主进程PID号
os.getppid 获取主主进程PID号
'''
def task(name):
print(f'{name} start...', current_process().pid)
time.sleep(1)
print(f'{name} end...', current_process().pid)
if __name__ == '__main__':
p = Process(target=task, args=('tiny',))
p.start()
# 判断子进程是否存活
print(p.is_alive())
# 直接告诉操作系统,终止子进程
p.terminate()
time.sleep(0.1)
print(p.is_alive())
p.join()
print('主进程', os.getpid())
print('主主进程', os.getppid())
进程号回收的两种条件
- join,可以回收子进程与主进程
- 主进程正常结束,子进程与主进程也会被回收
僵尸进程和孤儿进程
僵尸进程
指的是子进程已经结束,但PID号还存在,未销毁
会占用PID号,占用操作系统资源
孤儿进程
指的是子进程还在执行,父进程却已经结束
操作系统优化机制:
这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
守护进程
from multiprocessing import Process
import time
def task(name):
print(f'{name} start...')
time.sleep(5)
print(f'{name} end...')
def task2(name):
print(f'{name} start...')
time.sleep(3)
print(f'{name} end...')
if __name__ == '__main__':
p1 = Process(target=task, args=('tiny',))
p2 = Process(target=task2, args=('jane',))
# 添加守护进程参数,必须在start前
p2.daemon = True
# p1.daemon = True
p1.start()
p2.start()
print('fuck')
fuck
tiny start...
tiny end...