1.进程与线程的基本概念
1.操作系统如何管理CPU?
操作系统作为用户和硬件之间的桥梁,其本质就是代替用户来操作和管理硬件,其中最重要的硬件就是CPU。CPU的工作原理很简单,就是一句话:取指执行。CPU通过地址总线获取一个地址,然后根据地址去到内存找到该地址对应的内存,再将里面的数据取回来,最后执行。其中,CPU寄存器中的程序计数器存储着下一条要执行的指令的地址,通常来说,下一条指令的地址就是当前指令地址的下一段,因此就会形成自动的按顺序取指执行的工作原理。
CPU取指执行的具体步骤:
第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
第二步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行;
第三步,CPU 执行完指令后,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由 CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4;
2.为什么需要多道程序同时执行?
根本原因是因为CPU对IO设备的读取远慢于对存储设备的读取,远远慢于CPU的计算速度。如果按照自动取指执行的操作,当遇到一条IO指令时CPU就只能原地等待指令送达,效率低下,因此,在等待的时候去执行别的程序指令才能提高CPU利用率。
3.什么是进程?为什么需要进程?
进程即进行中的程序,上文提到为了提高CPU的利用率需要CPU在等待一个程序的下一条指令送达时去执行别的程序的指令,这里的一个个程序就是进程,计算机中的所有启动程序都是进程,操作系统最主要的功能就是管理这些进程。
4.操作系统如何管理多进程?
一则小故事:
我们写好的一行行代码,为了让其工作起来,我们还得把它送进城(进程)里,那既然进了城里,那肯定不能胡作非为了。
城里人有城里人的规矩,城中有个专门管辖你们的城管(操作系统),人家让你休息就休息,让你工作就工作,毕竟摊位不多,每个人都要占这个摊位来工作,城里要工作的人多着去了。
所以城管为了公平起见,它使用一种策略(调度)方式,给每个人一个固定的工作时间(时间片),时间到了就会通知你去休息而换另外一个人上场工作。
另外,在休息时候你也不能偷懒,要记住工作到哪了,不然下次到你工作了,你忘记工作到哪了,那还怎么继续?
有的人,可能还进入了县城(线程)工作,这里相对轻松一些,在休息的时候,要记住的东西相对较少,而且还能共享城里的资源。
简而言之,操作系统通过控制进程的工作和休息来实现公平的将计算机资源分配给每一个程序。
在这个过程中,操作系统需要完成两个步骤:调度与切换。在此之前,首先了解一下进程的状态,进程粗略来说分为三种状态:
就绪态:进程已准备就绪,CPU调度它它就马上能工作。
阻塞态:进程因某些原因停止(比如等待IO),即便CPU调度也不能工作。
运行态:进程正在被CPU执行。
考虑到进程的创建和消亡,再此基础上增加了两个状态:
创建态:进程正在被创建
结束态:进程正在被结束
最后,考虑到无论是就绪态还是阻塞态的进程都会存在于内存中,占用内存空间,为了节省内存空间的使用可以将这两种状态的进程放到磁盘中,调度时再放回来,这种行为成为挂起,再新增两种状态:
就绪挂起态:被放到磁盘中的就绪态进程
阻塞挂起态:被放到磁盘中的阻塞态进程
有了这些状态的知识,接下来就可以学习调度,切换与中断了。
4.1调度
一个进程发生调度会有两种原因:时间片用尽和发生中断(后文讲解)。无论哪一种原因,CPU都不会继续执行当前进程了,那么下一个进程执行谁呢?这就由操作系统中的调度策略决定:
FIFO(先进先出):该策略将进入就绪态的进程像排队一样排起来,前面的进程执行完了就按先后顺序调度后一个进程。
SJF(短作业优先):将进入就绪态的进程按照完成所需时间排序,短的先执行。
RR(时间片轮转调度):给每个进程安排一个最大时间片,当进程时间片用尽或在时间片内发生中断进入阻塞态,则发生调度。
上述调度策略都有各自的优势,在选择调度策略时主要考虑两个指标,
周转时间:指一个进程从进入就绪态到完成所耗费的时间。
响应时间:指进程从进入就绪态到CPU第一次响应(处理)的时间
FIFO策略的平均周转时间一般比SJF要长,但是平均响应时间更短;RR策略保证了每个进程的最大响应时间。具体计算步骤不在此列出,有兴趣的同学可以自行查找资源。
Linux 0.11中采取的是具有一定优先级的RR策略,既保证了最大响应时间,也兼顾了前后台进程的优先级,还能动态的调整优先级使得低优先级进程不会一直饥饿。
4.2 切换
切换又叫做进程上下文切换,进程上下文指的是当前进程的寄存器、程序计数器等状态,是进程能够继续执行的基础。关于进程上下文的信息一律存储在一个叫做PCB的数据结构中。PCB是一个进程存在的证明,就好比一个人的身份证,进程创建了,PCB就会与该进程唯一绑定,进程结束了,它的PCB也会被释放。进程的上下文也是存在放PCB中,因此进程切换的步骤就是:
将进程1的进程上下文保存在PCB1中——CPU寻找进程2的PCB2——CPU根据PCB2的进程上下文还原现场以便继续执行进程2
在对操作系统内存管理的学习中我们知道了虚拟内存的概念,每一个进程都会被分配一个虚拟内存,该虚拟内存又通过映射表与物理内存一一对应,在虚拟内存中有一块特殊的区域,位于高位地址的内核区,PCB实际上就是存在于内核区。也就是说进程的切换发生了由用户态到内核态的转换。
4.3 中断
先来看一个生活中的例子:
小林中午搬完砖,肚子饿了,点了份白切鸡外卖,这次我带闪了,没有被某团大数据杀熟。虽然平台上会显示配送进度,但是我也不能一直傻傻地盯着呀,时间很宝贵,当然得去干别的事情,等外卖到了配送员会通过「电话」通知我,电话响了,我就会停下手中地事情,去拿外卖。
这里的打电话,其实就是对应计算机里的中断,没接到电话的时候,我可以做其他的事情,只有接到了电话,也就是发生中断,我才会停下当前的事情,去进行另一个事情,也就是拿外卖。
中断的意义就在于提高系统的并发能力,让CPU不用干等着,在等待的时候去干别的事情,因此,中断是操作系统进程调度和切换的基础。
5.什么是线程?为什么需要线程?
线程是轻量级的进程,它依托于进程存在(一个进程中可以有多个线程),与进程不同的是,每个进程都有独立的虚拟内存空间(堆、栈等),而一个进程中的所有线程有各自独立的栈区,而共享进程中的其他资源(代码区、数据区)。这带来的好处是:
- 线程可以并发,比起进程并发来说,线程间的通信更加方便,因为所有的信息都可以在其所在进程中传递。
- 比进程并发的开销更小,每创建一个进程操作系统就要为其分配虚拟内存等资源,而创建线程只需分配一个栈区即可。
线程也有一些缺点,比如:
- 进程中的某个线程崩溃会导进程内的其他线程崩溃。原因见:5.7 线程崩溃了,进程也会崩溃吗? | 小林coding
6.进程与线程的区别和共性?
进程 | 线程 |
资源分配的单位 | 系统调度的单位 |
占有所有的虚拟地址空间 | 只占据寄存器和栈空间 |
开销更大,并发效率相对低 | 开销更小,并发效率高 |
都具有就绪态、运行态和阻塞态,因此都可以进行切换 |