操作系统(3) 多进程图像

410 阅读9分钟

本文引用代码与图片均来自 李治军: 操作系统32讲

CPU管理

CPU管理的目的是让CPU更高效的工作,假设我们现在有一堆任务每个任务对应一个程序,为了完成这些任务很容易想到的一个方法就是一个一个地去执行

我们知道CPU的工作是取指-执行-取指-执行...这样不断循环的过程,执行任务的时候先把程序读进内存然后设定起始指令的地址(设置CSIP寄存器)CPU就会自动地往下一条条指令地执行程序

如下把程序读进内存50处,然后设PC(Program Counter, 在x86体系中由CS和IP共同表示)等于50计算机就开始执行该程序

image.png

当一个程序执行完了再把下一个程序读进来执行就可以完成所有任务,但像这样一个一个任务的顺序执行(串行)最大的问题是CPU利用率低

我们看一段简单的计数累加的代码

for(int i = 0; i < to; i ++) {
    sum += i;
  //fprint(fp, "%d", sum);
}

分别累加10000000次不执行fprint1000次执行fprint耗时对比如下

image.png

从上图可以知道执行涉及磁盘操作的指令的时间远远大于执行普通指令的时间,因为CPU要等待磁盘控制器的返回,这一过程相对于CPU的执行速度来说是非常慢的

串行模式中,当程序中有磁盘操作指令时CPU只能干等着直到磁盘读写完成再继续,排在后面的程序也只能等着。磁盘操作在程序中是很常见的,CPU在读写磁盘的这段时间啥也干不了就导致了低利用率

能不能在读写磁盘的时候让CPU先执行后面的指令呢?这是不行的,因为有可能后面的指令就等着前面从磁盘中读出来的数据,数据没拿到就执行那结果就乱套了

要怎样才能让CPU在类似的等待时间里忙起来呢?其实遇到这种情况的时候可以让CPU先去执行其他程序,等磁盘读写完了发个信号让CPU回来继续执行即可,如下

image.png

任务执行不再是串行的而是并发的(在同一个时间段CPU不停地在"程序"之间切换,每个程序执行一小段时间,当计算机有多CPU时多个程序可以并行。本文以单CPU计算机为例),并发提高了CPU利用率

image.png

上面的做法其实会遇到一些问题,我们知道从一个程序跳到另一个程序只要设置PC就可以,但现在我们要做的是在程序之间相互跳转执行,跳出去之后怎么回来?回哪里?跳出去之前在做什么?回来后怎么继续?

为了解决这些问题,我们要将返回地址和程序参数等数据保存起来,一般操作系统将这些信息保存在PCB(Processing Control Block)中,PCB中还有进程标识符优先级等数据。跳出去前将有用的信息保存进PCB中,跳回来的时候查看PCB取出信息继续之前的工作

PCB译作进程控制块,这里引出程序和进程之间的对比。程序是静态的文件比如我们写的代码,进程是执行中的程序。进程是操作系统分配资源的基本单位,通过PCB进行管理

我们日常使用计算机执行不同的任务(听歌、写文档)其实是创建了不同的进程,操作系统通过管理进程来管理用户对计算机的使用,多个进程的执行由串行转为并发提高了CPU的利用率和用户的舒适度

多进程

操作系统以多个进程并发的方式提高CPU的利用率和用户舒适度,那么系统是如何对多进程进行组织管理的呢?比如什么时候应该执行哪个进程?

组织

操作系统以PCB等数据结构为基础创建一个个队列,如下图,有由随时可以执行的进程组成的就绪队列,有由等待磁盘访问的进程组成的磁盘等待队列等,每个进程只能位于其中一个队列

image.png

交替

位于不同队列的进程有不同的状态,例如正在执行的进程处于运行态、等待执行的进程处于就绪态而等待磁盘的进程则处于阻塞态

image.png

进程的状态是不断发生改变的,例如当前正在执行的进程时间片结束后会转为就绪态放入就绪队列,如果是需要读写磁盘则可能转变为阻塞态放入阻塞队列

操作系统根据系统资源(CPU,内存和磁盘等)的使用情况不断调整进程的状态以推进进程的执行和交替

进程交替过程具体是怎样的呢?如下图所示,交替主要是队列操作、调度和切换

image.png

系统首先根据正在执行的进程的资源需求将其放到某个队列(队列操作),然后从就绪队列中选择一个进程(调度),最后切换到选出来的新进程中执行(切换)

其中调度是一个很复杂的也很热门的课题,是操作系统很重要的一个组成部分。常见的调度方案有FIFO(First In First Out)先进先出、基于优先级的调度等。调度的目的是从就绪队列中选出目前最合适的进程执行

切换相对来说操作简单,就是在PCB中保存进程的上下文(指令地址、参数、返回地址等),然后将调度选出来的进程PCB中的信息拿出来控制CPU跳转执行

image.png

内存隔离

多进程并发意味着内存中同时存在着多个进程的数据,进程在执行的时候很可能遇到下面的情况

image.png

进程1对地址100处的内存进行操作,但是该地址处存放着进程2的数据,如果直接让进程1操作就会对进程2造成破坏。同样的道理,也有可能出现多个进程都需要对某一处内存进行操作导致进程数据被污染的情况

为了避免进程间的相互影响,操作系统为每个进程都创建了一个地址映射表

image.png

进程访问的其实是虚拟地址,该虚拟地址在映射表中对应一个真实的物理内存地址。每个进程的映射表不一样,所以即使访问了同样的虚拟地址也不会访问到相同的真实地址,然后操作系统读入程序的时候也不会将程序放到已经被分配的内存上

通过以上内存隔离策略操作系统就避免了进程间因为访问内存造成的相互影响。当然,如果进程间想共享内存也是可以的,只需要调用shm等系统调用即可

进程合作

不同的进程间有时需要合作,以打印文件为例

image.png

应用进程(word, pdfReader等)将文件放入待打印文件队列,打印进程从队列中取出文件并控制打印机打印

既然有合作那么合作过程中就可能有冲突,下面以简化的生产者-消费者合作模式为例

// 生产者
while (true) {
    while(counter== BUFFER_SIZE)
        ;
    buffer[in] = item;
    in = (in + 1) % BUFFER_SIZE;
    counter++;
}

// 消费者
while (true) {
    while(counter== 0)
        ;
    item = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    counter--;
}

生产者往缓冲区放入产品并使计数器加一,消费者从缓冲区取出产品并使计数器减一

代码中counter增加和减少只需一行代码,但是实际上操作分为三步

// counter++;
register = counter;
register = register + 1;
counter = register;
// counter--;
register = counter;
register = register - 1;
counter = register;

由于进程交替执行,可能出现生产者counter加一还未完成就切换到消费者counter减一的操作

// 假设counter初始为5,正常流程下
// 生产者
register = counter;
register = register + 1;
counter = register;  // counter = 6
// 消费者
register = counter;
register = register - 1;
counter = register;  // counter = 5

// counter初始为5,交替流程下
// 生产者
P.register = counter;
P.register = P.register + 1;
// 消费者
C.register = counter;
C.register = C.register - 1;
// 生产者
counter = P.register;  // counter = 6
// 消费者
counter = C.register;  // counter = 4 最终值

如上生产者进程和消费者进程的合作由于进程交替而失败

进程合作失败的一个主要原因是进程间没有同步信息,上例中消费者不应该在生产者修改counter还未完成的情况下去修改counter,但是消费者不知道生产者在干什么,所以它直接就去修改了然后造成了问题

进程间同步也是一个很深刻的课题,拥有很多不同的方案,这里以锁方案为例

// 生产者获取锁
register = counter;
register = register + 1;
// 消费者尝试获取锁失败,不能进行操作

counter = register;  // counter = 6
// 生产者释放锁
// 消费者获取锁
register = counter;
register = register - 1;
counter = register;  // counter = 5
// 消费者释放锁

以上简单的案例中通过锁机制生产者和消费者之间完成了同步合作成功,多进程合作同步是很关键的

图像轮廓

前面的章节从CPU管理到进程间合作简要描述了操作系统中多进程图像,图像轮廓如下:

  • 多进程并发提高了CPU利用率
  • 操作系统通过PCB等数据结构构造多条队列组织管理多进程
  • 位于不同队列的进程有不同状态,操作系统不断改变进程状态以推进进程
  • 进程交替的主要步骤为队列操作,调度与切换
  • 进程的切换需要保存上下文到PCB以及从新进程的PCB中取出上下文
  • 进程间内存相互隔离
  • 进程间有合作需求与同步问题

参考资料