操作系统概念拾遗(二)
不为考试,重读操作系统概念——Operating System Concepts
现代计算机系统允许将多个程序调入内存并发执行。这一发展要求对各种程序提供更严格的控制和更好的划分。这些需求产生了进程(process)的概念,即执行中的程序。进程是现代分时系统的工作单元。操作系统越复杂,就越能为用户做更多的事,虽然操作系统的主要关注点是执行用户程序,但是也需要顾及各种系统任务(放在内核之外会更好)。因此,系统有一组进程组成:操作系统进程执行系统代码,用户进程执行用户代码。通过CPU多路复用,所有这些进程可以并发执行,通过在晋城之间切换CPU,操作系统能是计算机更为高效。
进程是执行中的程序,是一种给正式的说法。进程不只是程序代码,程序代码有时称为文本段(text section)。进程还包括当前活动,通过程序计数器(program counter)的值和处理器寄存器的内容来表示。另外,进程通常还包括进程栈(stack)(包括临时数据,如函数参数、返回地址和局部变量)和数据段(data section)(包括全局变量)。进程还可能包括堆(heap),是在进程运行期间动态分配的内存。虽然两个进程可以是与同一程序相关,但是他们被当做两个独立的执行序列。例如多个用户可运行电子邮件的不同副本,或者同一用户能调用Web浏览器程序的多个副本,这些都是独立的进程,虽然文本段相同,当时数据段、堆、栈段不同。
进程控制块,每个进程在操作系统内用进程控制块(process control block,PCB)来表示。它包含许多与一个特定进程相关的信息:进程状态(process state),状态可包括新建、就绪、运行、等待、终止等;程序计数器(program counter),计数器表示进程要执行的下一条指令的地址;CPU寄存器(CPU register),根据计算机体系结构的不同,寄存器的数量和类型也不同,他们包括累加器、索引寄存器、堆栈指针、通用寄存器和其信息寄存器。与程序计数器一起,这些状态信息再出现中断时也需要保存,一边进城以后能正确地继续执行;CPU调度信息(CPU scheduling information),这类信息包括进程优先级、调度队列的指针和其他调度参数;内存管理信息(memory management information),根据操作系统所使用的内存系统,这类信息包括基址和界限寄存器的值、页表或段表;记账信息(accounting information),这类信息包括CPU事件、实际使用时间、时间界限、记账数据、作业或进程数量等;IO状态信息(IO status information),这类信息包括分配给进程的IO设备列表、打开文件的列表等。
上下文切换,当发生一个中断时,系统需要保存当前运行在CPU中进程的上下文(context),以便在其处理完后能恢复上下文,即先中断进程,之后再继续。进程的上下文有进程的PCB来表示,它包括CPU寄存器的值、进程状态和内存管理信息等。通常,通过执行一个状态保存(state save)来保存CPU的当前状态(不管室内和模式还是用户模式),之后执行一个状态回复(state restore)重新开始运行。上下文切换时间是额外开销,上下文切换时间与硬件支持密切相关,依赖于内存的存取速率、必须复制的寄存器的数量,是否有特殊指令(如装入或保存所有寄存器的单个指令)。
Java中的进程,当一个Java程序开始执行时,Java虚拟机(JVM)的一个实例就会被创建。在大多数系统中,JVM做一个独立的进程,以一般应用程序的形式运行于宿主操作系统中。JVM的每个实例为多线程控制提供支持,但是Java并不支持或允许JVM在同一个虚拟机中创建多个进程。目前JVM不支持多进程模型的主要原因是在同一个虚拟机中,将不同进程间的内存空间隔离开是很困难的。然而在JVM之外创建进程有可能的,这可以通过ProcessBuilder类指定操作系统的一个本地进程(例如/usr/bin/ls或C:\\windows\\system32\\mspaint.exe)来实现。也可以使用ProcessBuilder类的start()方法来创建新进程,这个方法返回一个Process对象的实例。这个进程将会在虚拟机外部运行而不会影响虚拟机,反之亦然。虚拟机和外部进程的通信通过外部进程的InputStream和OutputStream进行。
进程终止,当今成完成执行最后的语句并使用系统调用exit()请求操作系统删除自身时,进程终止。这时,进程可以返回状态值(通常为整数)到父进程(通过系统调用wait())。所有进程资源——包括物理和虚拟内存、打开文件和IO缓冲——会被操作系统重新分配。在其他情况下也会出现终止。进程通过适当的系统调用(如Win32中TerminateProcess())能终止另一个进程。通常,只有被终止进程的父进程才能执行这一系统调用。
进程通信,协作进程需要一种进程间通信(interprocess communicaion,IPC)机制来允许进程之间相互交换数据与信息。在许多情况下我们需要进程协作:信息共享(information sharing),由于多个用户可能对同样的信息感兴趣(如共享的文件),所以必须提供一个环境已允许对这些信息的并发访问;加快计算(computation speedup),如果希望一个特定任务更快地运行,那么必须将它分成子任务,每个子任务可以与其他子任务并行执行,注意,如果要实现这样的加速,需要计算机有多个处理单元(例如CPU或IO通道);模块化(modularity),可能需要按模块化方式构造系统,可将系统功能分成独立进程或线程;方便(convenience),单个用户也可能同时执行许多任务,例,一个用户可以并行编辑、打印、编译。进程间通信有两种基本模型:共享内存(shared memory)和消息传递(message passing)。在共享内存模型中,建立起一块供协作进程共享的内存区域,进程通过向此共享区域读出或写入数据来交换信息。在消息传递模型中,通过在协作进程间交换消息来实现通信。消息传递对于交换较少数量的数据很有用,因为不需要避免冲突。对于计算机间的通信,消息传递也比共享内存更易于实现。共享内存允许最快、最方便的通信,在计算机中它可以达到内存的速度
消息传递,消息传递工具提供这少两种操作:发送(消息)和接收(消息)。由进程发送的消息可以是定长的或变长的,如果只能发送定长消息,那么系统级的实现相当简单。不过,这一限制使得编程任务更困难。相反地,变长消息要求更复杂的系统级实现,但是编程任务变得比较简单。这是贯穿整个操作系统设计的一种常见的折中问题。如果进程P或Q需要同心,那么他们必须彼此相互发送消息和接收消息,他们之间必须有通信链路(communication link)。如下是一些逻辑视线链路和send()/receive()操作的方法:直接或间接通信;同步或异步通信;自动或显式缓冲。
需要通信的进程必须有一个方法以相互引用。他们可以使用直接或间接通信。对于直接通信(direct communication),需要通信的每个进程必须明确地命令通信的接受者或发送者。这种方案有两种形式:对称寻址,即发送进程和接收进程必须命名对方以便通信;非对称寻址,即只要发送者命名接受者,而接受者不需要命名发送者。对称寻址或非对称寻址方案的缺点是限制了进程定义的模块化,改变进程的名称可能必须检查所有其它进程定义,所有旧名称的引用都必须找到,以便修改成为新名称。再间接通信(indirect communication)中,通过邮箱(mailbox)或端口(port)来发送和接收消息。邮箱可以抽象成一个对象,进程可以向其中存放消息,也可以从中删除消息。每个邮箱都有一个唯一的标识符,例,POSIX消息队列采用一个整数值来标识一个邮箱,对于这种方案,一个进程可能通过许多不同的邮箱与其他进程通信,但两个进程仅在共享至少一个邮箱时才能相互通信。两个通信进程之间可有多条不同的链路,每条链路对应于一个邮箱。邮箱可以为进程或操作系统所拥有,如果邮箱为进程所有(即邮箱是进程地址空间的一部分),那么需要区分拥有着(只能能够通过邮箱接收消息)和使用者(只能向邮箱发送消息)。与此相反,操作系统所拥有的邮箱上独立存在的,并不属于某个特定的进程。因此,操作系统必须提供机制已允许进程进行如下操作:创建新邮箱;通过邮箱发送和接受消息;删除邮箱。创建新邮箱的进程默认为邮箱的拥有者。开始,拥有者是唯一能通过该邮箱接收消息的进程,不过,通过操作系统调用,拥有权和接受特权可能传递给其他进程。
进程间的通信可以通过send()/receive()来进行,send()/receive()的实现由不同的设计选项。消息传递可以是阻塞(blocking)或非阻塞(nonblocking)——也成为同步(synchronous)或异步(asynchronous)。阻塞发送,发送进程阻塞,直到消息被接收进程或邮箱所接收;非阻塞发送,发送进程发送消息并继续操作;阻塞接收,接收者阻塞,直到有消息可用;非阻塞接收,接受者收到一个有效消息或空消息。注意,同步或异步的概念常常出现在操作系统的IO算法中。
不管通信时直接的或是间接的,通信进程所交换的消息都驻留在临时队列中。队列的实现基本上有三种方法:零容量(zero capacity),队列的最大长度为0;因此,链路中不能有任何消息处于等待,对于这种情况,必须阻塞发送者,直到接收者接受到消息;有限容量(bounded capacity),队列具有有限长度N;因此,最多只能有N条消息驻留其中,如果在发送新消息是队列未满,那么该消息可以放在队列中(或者复制消息,或者保留消息的指针),且发送者可继续执行而不必等待,不过,链路只有有限容量,如果连鲁曼,必须阻塞发送者知道队列中的空间可用为止;无限容量(unbounded capacity),队列长度可以无限,因此,不管多少消息都可在其中等待,从不阻塞发送者。