操作系统设计与实现(第二章 进程-MINIX进程概述)

2.5 MINIX 进程概述

以MINIX为例说明进程管理、进程间通信以及进程调度的原理。MINIX与UNIX不同,UNIX的核心是一个不分模块的单块程序,而MINIX本身就是一组进程的集合,它们相互之间、以及与用户进程之间使用进程间通信机制 - 消息传递来进行通信。优点:结构更加模块化和灵活。例如,这使得很容易就可以将整个文件系统替换成另一个完全不同的文件系统,而无需重新编译核心。

2.5.1 MINIX 的内部结构

图2-26 MINIX的四层结构。

最底层捕获所有的中断和陷入,完成进程调度,并向高层提供一个采用消息进行通信的独立顺序进程模型。该层的代码有两大主要功能。第一是捕获陷入和中断、保存和恢复寄存器、调度以及向高层提供一个独立顺序进程模型。第二是处理消息机制:检查目标进程的合法性、定位物理内存中的发送和接收缓冲区、以及从发送方向接收方拷贝数据。其中中断处理的最底层部分用汇编语言编写,其余部分和其他层次用C语言编写。

第二层包括I/O进程,每类设备都有一个I/O进程。为了将其与普通用户进程相区别,我们称之为任务(tasks)。但任务与进程间的差别微乎其微。在许多系统中I/O任务被称作设备驱动程序(device driver)。这里“任务”和“设备驱动程序”可以换用。每一类设备都需要一个任务,包括磁盘、打印机、终端、网络接口以及时钟。如果有其他I/O设备,则它们也需要相应的任务。有一个任务 - 系统任务有些与众不同,它不对应于任何I/O设备,我们将在下一章对这些任务进行讨论。第二层的所有任务和第一层的代码链接成一个单一的二进制程序,称作核心(kernel)。某些任务共享公共的子例程,但它们相互之间完全独立,分别进行调度,并采用消息进行通信。从286开始的Intel处理器为每个进程赋予四种特权级中的一种。尽管任务与核心被编译在一起,但当核心和中断处理程序被执行时,它们被赋予较任务更高的特权级。所以真正的核心代码可以访问任一部分内存,以及任一处理器寄存器 - 实质上,核心可以使用系统中任何地方的数据执行任何指令。任务不能执行全部的机器指令,也不能访问所有CPU寄存器或所有的内存。但在为较低特权级的进程执行I/O时,任务可以访问属于这些进程的内存区域。有一个任务 - 系统任务,它并不执行一般意义的I/O,其作用提供某些特定服务,例如当进程本身不允许在不同的内存区域间进行拷贝时,由系统任务执行此操作。当然在不提供多特权级的机器上,例如老式的Intel处理器,无法强制执行这些限制。

第三层包含向用户进程提供有用服务的进程。这些服务器进程低于核心和任务的特权级上运行,不能直接访问I/O端口。它们也不能访问属于自己的段以外的内存。内存管理器(Memory Manager - MM)负责执行所有牵涉到内存管理的系统调用,如FORK、EXEC和BRK。文件系统(File System - FS)负责执行文件系统的调用,READ、MOUNT和CHDIR。此处正适于指出尽管服务器是独立的进程,但它们和用户进程有一点不同,即它们在系统启动的同时被启动,而且在系统活跃期间不会终止。此外,尽管从它们禁用的机器指令来看,它们与用户进程运行在相同的特权级上,但它们的执行优先级比用户进程高为了加入一个新的服务器,核心必须重新编译。核心的启动代码在用户进程开始运行之前将服务器进程安装在进程表的特权表项中。

第四层包含所有的用户进程 - shell、编译器、编辑器以及用户的a.out程序。一个运行系统通常有一些进程在系统引导时启动,并一直运行,例如,一个精灵程序就是一个周期性运行或总是等待某个事件(例如网络上一个包的到达)的后台进程。从某种意义上说,精灵进程是一个单独启动而作为一个用户进程运行的服务器。但与装入特权进程表项的真正的服务器不同,这些程序无法象内存和文件服务器那样受到核心的特殊对待。

2.5.2 MINIX 中的进程管理

MINIX中的进程遵从本章前边所描述的通用进程模型。整个系统中所有的用户进程都属于以init(见图2 - 26)为根节点的一棵进程树。

1、当计算机开机时,硬件从引导盘上将第一道第一扇区读入内存并从那里开始执行。

软盘:第一扇区包含了引导程序(bootstrap)。引导程序很小,因为它必须能容纳在一个扇区里。MINIX引导程序装入一个更大的程序boot,由boot装入操作系统。

硬盘:硬盘需要一个中间步骤。硬盘被分成若干分区(partition),整个硬盘的第一个扇区包括一段小程序和磁盘分区表,通常称为主引导记录。程序部分被执行以读入分区表并选择活跃分区。活跃分区的第一个扇区有一个引导程序,它随后被装入并执行以查找并启动程序boot,这与从软盘引导完全相同。

软盘启动过程:引导程序<第一扇区> -- 装入boot程序 -- 由boot装入操作系统。

硬盘启动过程:主引导<硬盘第一个扇区:小段程序+磁盘分区表> -- 选择活跃分区<程序启动,读取分区表> -- 活跃分区<第一个扇区引导程序> -- 查找并启动程序boot -- boot装入操作系统。

boot将在软盘或硬盘分区上找一个包含多个部分的文件,并将各部分装到内存的适当位置。这些部分包括核心、内存管理器、文件系统以及init - 第一个用户进程。这个启动过程并不简单。所有那些属于磁盘任务和文件系统范围的操作在这两部分活跃之前都要由boot完成。一旦装入操作完成,核心便开始运行。

2、初始化阶段,核心先启动各任务,然后是内存管理器、文件系统及所有在第三层运行的服务器。

当所有这些都开始运行并完成初始化之后,它们将阻塞,等待执行某种操作。当所有的任务和服务器被阻塞之后,将执行第一个用户进程 - init。init已经位于内存,不过在它启动时其他所有部分均已就位,所以它可以作为一个独立的程序从磁盘上装入。但是,由于init只启动一次,而且不会再从盘上装入,所以最简便的方法是把它与核心、任务和服务器一起包括在系统的映像文件中。

init启动后先读/etc/ttytab文件,该文件列出了所有可能的终端设备。那些可以作为登录终端(在标准MINIX中,只包括控制台)的设备都在/etc/ttytab的getty域有一项。而且init为每个这样的终端创建一个子进程。通常每个子进程都执行文件/usr/bin/getty,打印出一条信息,然后等待输入一个用户名。随后将该用户名作为参数来调用 /usr/bin/login。如果某个终端需要特殊的处理(例如,一条拨号线),则/etc/ttytab可以指定一条命令(如/usr/bin/stty),在执行getty之前执行该命令并对线路进行初始化。

在成功地登录之后,/bin/login执行用户的shell(在/etc/passwd文件中指定,通常是/bin/sh或/usr/bin/ash)。shell等待用户键入命令,并为每条命令创建一个新的进程。采用这种方式时,各个shell都是init的子进程,而用户进程则是init的孙子进程,并且所有的用户进程都是一棵进程树的组成部分。

MINIX用于进程管理的两条最重要的系统调用是FORK和EXEC。FORK是创建一个新进程的唯一途径。EXEC允许一个进程执行一个指定的程序,当一个程序被执行时,将按照文件头中指定的大小为其分配一部分内存。尽管在进程运行期间,数据段、栈段和空闲未使用部分的大小可以不时地改变,但进程分配到的内存总量将保持不变。

一个进程的所有信息被保存在进程表中,进程表划分成核心、内存管理器和文件系统三部分,分别拥有它们各自所需要的那些域。当出现一个新进程(通过FORK),或者一个老进程结束(通过EXIT或信号)时,内存管理器首先更新它那部分进程表,然后向文件系统和核心发送消息,以通知它们进行相应的操作。

2.5.3 MINIX 中的进程间通信

MINIX提供了三条原语来发送和接收消息,它们均通过C库例程调用。其中

send(dest,&message)用来向进程dest发送一条消息;

receive(source,&message)用来从进程source(或任何地方)接收一条消息;

send_rec(src_dst,&message)用来发送一条消息,并等待同一个进程的应答。

以上调用中第二个参数是消息数据的本地地址。核心中的消息传递机制将消息从发送者拷贝到接收者。应答消息(对于send_rec)将覆盖原先的消息。原则上该核心机制可以替换为另一套机制以实现分布式系统,即在网络上将消息从一台机器拷贝到另一台机器上。但在实践中这很复杂,因为有时消息的内容可能是一个指向大型数据结构的指针,于是分布式系统也必须提供网络上数据本身的拷贝功能。每个进程或任务都可以从/向同层和下一层中的进程或任务发送和接收消息,用户进程不能直接与I/O任务通信,系统强制地执行这一限制。当一个进程(作为特例,这里也包括任务)向一个当前未在等待消息的进程发送一条消息时,发送者将阻塞,直到目标进程执行receive。换言之,MINIX使用会合的方法来避免对已发送而未接收到的消息进行缓冲的问题。尽管这没有带缓冲的方案灵活,但事实证明对MINIX来说它已经足够了,而且由于不需要缓冲管理,所以简单许多。

2.5.4 MINIX 中的进程调度

中断系统使多道程序操作系统持续不断地工作。当进程请求输入时,它们将阻塞以允许其他进程执行。当输入可用时,当前运行进程被磁盘、键盘或其他硬件中断。时钟也产生中断,这种中断使正在运行的未请求输入的用户进程最终放弃CPU,以使其他进程获得运行的机会。MINIX最底层软件的任务就是通过将中断转换成消息来对其加以隐藏。就进程(以及任务)而言,当一个I/O设备完成一个操作时,它向某些进程发送一条消息,将其唤醒并使之成为就绪。

每当一个进程被中断时,不管中断源是常规的I/O设备还是时钟,都有机会重新确定哪个进程最需要运行机会。当然,在一个进程终止时也要执行该操作,但在类似MINIX这样的系统中,由I/O操作和时钟引起的中断远远多于进程终止的情况。MINIX调度程序使用一个三级排队系统,分别对应于图2-26中的第2、3、4层。任务和服务器一级的进程一直运行直到阻塞,而用户进程则采用时间片轮转调度。任务具有最高优先级,内存管理器和文件管理器次之,用户进程最低。

当调度程序选择一个进程来运行时,它首先检查是否有就绪的任务,如果有一个或多个,则队首的那个将运行。如果没有任务就绪,则检查并运行服务器进程(MM或FS)。若没有合适的服务器进程,则运行一个用户进程。如果没有进程就绪,则选择IDLE进程。这个循环一直执行到下一个中断到来。在每一个时钟滴答,都将检查当前进程是否是一个运行超过100毫秒的用户进程。如果是,则调用调度程序来查看是否有另一个用户进程在等待CPU,如果发现一个这样的进程,则当前进程被移到队列的末尾,而运行当前的队首进程。任务、内存管理器和文件系统不会被时钟剥夺,不论它们已运行了多久。

你可能感兴趣的:(操作系统设计与实现(第二章 进程-MINIX进程概述))