个人主页:Lei宝啊
愿所有美好如期而遇
我们首先来了解几个概念
1. 进程在CPU上运行的时候,一定要运行完才行吗?答案是否定的,我们大部分的操作系统,主流就是分时操作系统,即基于时间片进程轮转执行的。
时间片是什么呢?
就是进程有一个执行时间,这个时间到了就算没执行完,也不会继续执行,而是会执行下一个进程,每个进程都有自己的时间片,时间片到了就下一个进程。
2. 竞争性:系统进程很多,但是CPU只有少数,我们大部分人用的只有一个,为了更高效完成任务,需要合理竞争相关资源,于是就有了优先级。
独立性:进程相互之间是独立的,多进程同时运行相互之间不会影响。
并行:多个进程在多个CPU下分别,同时运行。
并发:多个进程在一个CPU下通过进程切换的方式,在一段时间内,让多个进程同时推进,称之为并发。
进程的切换
我们要先知道CPU里有很多的寄存器,这些寄存器用来计算进程的代码和数据,寄存器也可以存储数据,eip这个寄存器存储将要执行的下一行代码的地址,CPU执行到哪一行,那行的代码和数据就会输入寄存器,寄存器计算后会返回一个结果,同时在寄存器内产生大量临时数据,这个临时数据我们叫做进程的硬件上下文。
在一个进程时间片结束时,会将CPU寄存器中的临时数据保存在task_struct中,下一次执行时,将数据覆盖在寄存器上,继续上一次的执行,如果是首次执行的寄存器,会从头开始执行,每个进程的数据都是私有的,寄存器中上一个进程留下的临时数据不会被下一个进程读取,下一个进程会将自己的数据在寄存器上进行覆盖。
所有的保存是为了最终数据的恢复,所有的恢复都是为了继续上次的运行。
进程的调度
我们说,一个程序本质就是二进制文件,是文件就保存在磁盘中,而想要运行这个程序,就要先将其加载到内存中,而加载到内存中,就要被操作系统所管理,于是操作系统建立了他的PCB,也就是task_struct,然后将程序加载到内存中,描述了这个程序之后,将其连入数据结构中进管理,于是这个可执行程序的代码和数据+内核数据结构就构成了进程,而进程要想被CPU运行,就要先进入CPU的运行队列进行排队,而排队的进程也有优先级,Linux实现进程调度的算法,既要考虑优先级,还要考虑饥饿问题,以及效率。
上图就是Linux2.6内核中进程队列的数据结构。
首先,queue[140]这个数组中的每个元素都是task_struct*类型,也就是指向进程的进程控制块,这个数组我们只会用到下标100~139,40个下标对应了我们40个优先级,下标越靠前,优先级越高。
CPU在执行进程时,就会去遍历这个数组,如果不为NULL,就说明这个优先级有队列,就会开始执行这个队列的进程,这个队列的进程执行完或者时间片结束,就会离开这个队列,于是活跃队列中的进程越来越少,直到全部为NULL。
我们定义一个 struct q这样的结构体,再用这样一个结构体定义一个数组,我们的active以及expired都是struct q*类型,分别指向这个数组的元素,active指向的活跃队列,expire指向过期队列,CPU执行活跃队列里的进程时,当有优先级高的进程就绪时,不会进入活跃队列,而是进入过期队列中对应下标中,当活跃队列中的进程时间片结束,但是没有执行完,也会按照优先级进入过期队列相应的下标中,当活跃队列全部为空时,执行swap(&active,&expired),交换指针,过期队列也就变成了活跃队列,活跃队列变成过期队列,CPU不会直接去访问这个数组中的结构体,而是通过这两个指针去访问队列。
再一个,CPU不会去遍历queue,而是通过其他算法,也就是通过我们的bitmap数组,32*5共160个字节,我们使用其中140个bit位,对应queue中的每个下标,如果下标为空,那么对应在bitmap中的那个bit位就是0,不为空就是1,因为bitmap有5个字节,我们会通看这个数组的每一个元素是否为0,为0,直接下一个元素,省去了遍历32个位,不为0,我们就通过位运算去取到bit位为1的位置,然后CPU去执行!这样就再次提高了效率,Linux进程调度的算法以及其结构非常优秀!