概念准备
当一个进程放在cpu上运行时,是必须要把进程的代码跑完才会进行下一个进程吗?答案肯定是 不对。现在的操作系统都是基于时间片轮转执行的。
时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。现代操作系统(如:Windows、Linux、Mac OS X等)允许同时运行多个进程 —— 例如,你可以在打开音乐播放器听音乐的同时用浏览器浏览网页并下载文件。事实上,虽然一台计算机通常可能有多个CPU,但是同一个CPU永远不可能真正地同时运行多个任务。在只考虑一个CPU的情况下,这些进程“看起来像”同时运行的,实则是轮番穿插地运行,由于时间片通常很短(在Linux上为5ms-800ms),用户不会感觉到。
与分时操作系统对应的就是实时操作系统,实时操作系统比如车载操作系统,当一个进程的优先级很高必须执行完才能执行下一个,如刹车进程,必须执行完,才能执行下一个,要不然就坑用户了,所以他的进程优先级可以很低。
分时操作系统却不会这样,分时操作系统的优先级是有范围的( [60,99] ),这样能保证每个进程都能被调度到,能够公平的照顾到每个进程,同时也照顾到了进程饥饿的问题。
进程与进程之间的关系:
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
进程切换:
cpu由控制器和运算器组成,运算器上有寄存器如:eax/ebx/ecx/ ... ... ebp/ esp ... ...
寄存器是cpu内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。
进程在cpu中执行中要产生大量的数据,这些数据会被存储到寄存器上,但是当时间片耗尽时,那么这些数据该怎么办?简单来说会从cpu上剥离下来,数据存储到pcb上。其中有一个关键的寄存器叫eip,它会记录当前cpu执行到了哪一行代码的下一行。
当cpu首次执行该进程到时间片耗尽的时候,cpu内部所产生的临时数据存放在寄存器中,这些数据我们叫进程硬件的上下文,硬件上下文让我们的进程得以保存并存储到pcb上,当进程之后在被调度时,首先就要恢复硬件的上下文,从上次结束的地方再继续执行。
cpu寄存器只有一套,但是里面存储的进程的数据却可以有很多套,虽然数据存储在一个共享的cpu设备里,但是某个进程的数据,都是被某个进程所私有的!!!
进程调度
上面我们提到过Linux的优先级是可以被修改的,并且范围是[60,99]。
普通的运行队列都是FIFO的机制,Linux下的调度算法是考虑到优先级,考虑到效率和考虑到饥饿的问题。
首先Linux下有一个这样的数组,它的类型是结构体指针,他的大小是140。[0,99]号元素我们现在不谈,[100,139]号元素,我们发现这对应的不就是nice值修改的范围。至此我们发现运行对列的数组下有存储着不同优先级队列的地址,这时cpu在调度的时候就考虑到了优先级的问题。根据优先级的高低来运行调度进程。
在运行队列的上面我们有bitmap[5],它的类型是int,也就是说有32 * 5 = 160 个比特位,我们用每一个bit位的位置来对应每一个队列,因为只有140个队列,所以还剩下20个bit位置我们不用。bit位的内容来对应该队列是否为有内容。这样考虑到了调度时的效率问题。
在runqueue上还存在着两个指针,查看英文释义,一个是活跃的,一个是到期的。活跃的意思也就是该指针指向的队列正在被cpu运行,而到期的则没有被运行。
当cpu执行完活跃队列的进程之后,就会执行过期进程。而在执行活跃进程中又来的新进程则被按照优先级安插到了过期队列中。这样一共有两个队列,通过指针的不停切换就实现了调度的公平性。一定程度上解决了进程饥饿的问题。
nr_active 表示有多少个活跃的队列。如果没有直接切换。
这两个指针是怎么切换的呢?简单的来说有一个结构体数组,里面分别存放着两个队列。
把第一个元素(地址)给到active ,第二个给到 expired,然后等到活跃的进行运行完,交换两个指针的内容,实现切换。