进程

进程

    进程可看做是正在执行的程序。进程需要一定的资源(如CPU时间,内存,文件和I/O设备)来完成其任务。这些资源在创建进程或执行进程时被分配。

    进程是大多数系统中的工作单元。这样的系统由一组进程组成:操作系统进程执行系统代码,用户进程执行用户代码。所有这些进程可以并发执行。

    虽然从传统意义上讲,进程运行时只包含一个控制线程,但目前大多数现代操作系统支持多线程进程。

    操作系统负责进程和线程管理,包括用户进程与系统进程的创建于删除,进程调度,提供进程同步机制,进程通信机制与进程死锁处理机制。

    早期的计算机系统只允许一次执行一个程序。这种程序对系统有完全的控制,能访问所有的系统资源。现代计算机系统允许将多个程序调入内存并发执行。这一要求对各种进程提供更严格的控制和更好的划分。这些需求产生了进程的概念,即执行中的程序。进程是现代分时系统的工作单元。

    操作系统越复杂,就越能为用户做更多的事。虽然操作系统的主要目标是执行用户程序,但是也需要顾及内核之外的各种系统任务。因此,系统有一组进程组成;操作系统进程执行系统代码而用户进程执行用户代码。通过CPU多路复用,所有这些进程可以并发执行。通过进程之间的切换,操作系统能使计算机更为高效。

进程概念

进程

正如前述,进程是执行中的程序,这是一种非正式的说法。进程不只是程序代码,程序代码有时称为文本段(代码段)。进程还包括当前活动,通过程序计数器的值和处理器寄存器的内容来表示。另外,进程通常还包括进程堆栈段(包括临时数据,如函数参数、返回地址和局部变量)和数据段(包括全局变量)。进程还可能包括堆(heap),是在进程运行期间动态分配的内存。内存中的进程结构如下所示:

 

    这里强调:程序本身不是进程;程序只是被动实体,如存储在磁盘上包括一系列指令的文件内容(常被称为可执行文件),而进程是活动实体,它有一个程序计数器用来表示下一个要执行的命令和相关资源集合。当一个可执行文件被装入内存时,一个程序才能成为进程。

进程状态

    进程在执行时会改变状态。进程状态在某种程度上是由当前活动所定义的。每个进程可能处于下列状态之一:

新的:进程正在被创建

运行:指令正在被执行

等待:进程等待某个事件的发生(如I/O完成或收到信号)

就绪:进程等待分配处理器

终止:进程完成执行

必须认识到一次只有一个进程可在一个处理器上运行,但是多个进程可处于就绪或等待状态。

进程控制块

每个进程在操作系统内用程序控制块(process control block,PCB,也称为任务控制块来表示,它包含许多与一个特定进程相关的信息。

 

进程状态:状态可包括新的,就绪,运行,等待,停止等。

程序计数器:计数器表示进程要执行的下个指令的地址。

CPU寄存器: 根据计算机体系结构的不同,寄存器的数量和类型也不同。它们包括累加器、索引寄存器、堆栈指针、通用寄存器和其他条件码信息寄存器。与程序计数器一起,这些状态信息在出现中断时也需要保存,以便进程以后能正确地继续执行

CPU调度信息:这类信息包括进程优先级、调度队列的指针和其他调度参数

内存管理信息:根据操作系统所使用的内存系统,这类信息包括基址和界限寄存器的值、页表或段表

记账信息:这类信息包括CPU时间、实际使用时间、时间界限、记账数据、作业或进程数量等

I/O状态信息:这类信息包括分配给进程的I/O设备列表、打开的文件列表等。

简而言之,PCB简单地作为这些信息的仓库,这些信息在进程与进程之间是不同的。

进程调度

    多道程序设计的目的是无论何时都有进程在运行,从而使CPU利用率达到最大化。分时系统的目的是在进程之间快速切换CPU以便用户在程序运行时能与其进行交互。为了达到此目的,进程调度选择一个可用的进程(可能从多个可用进程集合中选择)到CPU上执行。单处理器系统从不会有超过一个进程在运行。如果有多个进程,那么余下的则需要等待CPU空闲并重新调度。

调度队列

    进程进入系统中,会被加到作业队列中,该队列包括系统中的所有进程。驻留在内存中就绪的、等待运行的进程保存在就绪队列中。该队列通常用链表来实现,其头节点指向链表的第一个和最后一个PCB块的指针。每个PCB包括一个指向就绪队列的下一个PCB的指针域。

    操作系统也有其他队列。当给进程分配CPU后,它开始执行并最终完成,或被中断,或等待特定事件发生(如完成I/O请求)。假设进程向一个共享设备(如磁盘)发送I/O请求,由于系统有许多进程,磁盘可能会忙于其他进程的I/O请求,因此该进程可能需要等待磁盘。等待特定I/O设备的进程列表称为设备队列。每个设备都有自己的设备队列

    讨论进程调度的常用表示方法是队列图。每个长方形表示一个队列。有两种队列:就绪队列和一组设备队列。圆形表示为队列服务的资源,箭头表示系统内进程的流向。

    新进程开始处于就绪队列。它在就绪队列中等待直到被选中执行或被派遣。当进程分配到CPU并执行时,可能发生下面几种事件中的一种。

进程可能发出一个I/O请求,并被放到I/O队列中

进程可能创建一个新的子进程,并等待其结束

进程可能会由于中断而强制释放CPU,并被放回到就绪队列中

 

对于前两种情况,进程最终从等待状态切换到就绪态,并放回到就绪队列中。进程继续这一循环直到终止,到时它将从所有队列中删除,其PCB和资源将是以释放。

调度程序

进程在其生命周期中会在各种调度队列之间迁移。为了调度,操作系统必须按某种方式从这这些队列中选择进程。进程选择是由相应的调度程序(scheduler)来执行的。

    通常对于批处理系统,进程更多地被提交,而不是马上执行。这些进程被放到大容量存储设备(通常为磁盘)的缓冲池中,保存在那里以便以后执行。长期调度程序(long-term scheduler)作业调度程序(job sheduler)从该池中选择进程,并装入内存以准备执行。短期调度程序(short-term scheduler)CPU调度程序从准备执行的进程中选择进程,并为之分配CPU。

    这两个调度程序的主要差别是它们执行的频率。短期调度程序必须频繁地为CPU选择新进程。进程可能执行数毫秒(ms)就会进行I/O请求,短期调度程序通常每100ms至少执行一次。由于每次执行之间的时间较短,短期调度程序必须要快。如果需要10ms来确定执行一个运行100ms的进程,那么10/(100+10) 9%的CPU时间会用于(或浪费在)调度工作上。

    长期调度程序执行得并不频繁,在系统内新进程的创建之间可能有数分钟间隔。长期调度程序控制多道程序设计的程序(内存中的进程数量)。长期调度程序必须仔细选择。通常,绝大多数进程可分为:I/O为主或CPU为主。I/O为主的进程(I/O-bound process)在执行I/O方面比执行计算要花费更多的时间。另一方面,CPU为主的进程(CPU-bound process)很少产生I/O请求,与I/O为主的进程相比将更多的时间用在执行计算上。因此,长期调度程序应该选择一个合理的包含I/O为主的和CPU为主的组合进程。

    对于有些系统,可能没有货很少有长期调度程序。例如,UNIX或微软Windows的分时系统通常没有长期调度程序,只有简单地将所有新进程放在内存中以供短期调度程序使用。这些系统的稳定性依赖于物理限制(如可用多的终端数)或用户的自我调整。如果多用户系统性能下降到令人难以接受,那么将有用户退出。

    有的操作系统如分时系统,可能引入另外的中期调度程序(medium-term scheduler)

中期调度的核心思想是将进程从内存(或CPU竞争)中移除,从而降低多道程序设计的程序。之后,进程能被重新调入内存,并从中断处继续执行。这种方案称为交换(swapping)。通过中期调度程序,进程可换出,并在后来可被换入。为了改普进程组合,或者因内存要求的改变引起了可用内存的过度使用而需要释放内存,就有必要使用交换。

上下文切换

中断使CPU从当前任务改变为运行内核子程序,这样的操作在通用系统中发生得很频繁。当发生一个中断时,系统需要保存当前运行在CPU中进程的上下文,从而在其处理完成后能恢复上下文,即先中断进程,之后再继续。进程上下文用进程PCB表示,它包括CPU寄存器的、进程状态和内存管理信息等。通常,通过执行一个状态保存(state save)来保存CPU当前状态(不管它是内核模式还是用户模式),之后执行一个状态恢复(state restore)重新开始运行。

将CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态,这一任务称为上下文切换(context switch)。当发生上下文切换时,内核会将旧进程的状态保存在其PCB中,然后装入经调度要执行的并已保存的新进程的上下文。上下文切换时间是额外开始,因为切换时系统并不能做什么有用的工作。上下文切换速度因机器而不同,它依赖于内存速度,必须复制的寄存器的数量,是否有特权指令(如装入或保存所有寄存器的单个指令),一般需几毫秒。

上下文切换时间与硬件支持密切相关。例如,有的处理器(如Sun UltraSPARC)提供了多组寄存器集合,上下文切换只需要简单地改变当前寄存器组的指针。操作系统越复杂,上下文切换所要做的工作就越多。高级内存管理技术在各个上下文切换中要求切换更多的数据。例如,在使用下一个任务的空间之前,当前进程的地址空间需要保存。进程空间如何保存和保存它需要做多少工作,取决于操作系统的内存管理方法。

进程操作

    绝大多数系统内的进程能并发执行,它们可以动态创建和删除,因此操作系统必须提供某种机制(或工具)以创建和终止进程。

进程创建

    进程在其执行过程中,能通创建进程系统调用(create-process system call)创建多个新进程。创建进程称为父进程,而新进程称为子进程。每个新进程可以再创建其他进程,从而形成了进程树。

    大多数操作系统(包括UNIX和Windows系列操作系统)根据一个唯一的进程标识符(process identifier,pid)来识别进程,pid通常是一个整数值。

通常,进程需要一定的资源(如CPU时间,内存,文件,I/O设备)来完成其任务。在一个进程创建子进程时,子进程可能从操作系统那里直接获得资源,也可能只从其父进程那里获得资源。父进程可能必须在其子进程之间分配资源或共享资源(如内存或文件)。限制子进程只能使用父进程的资源能防止创建过多的进程带来的系统超载。

当进程创建新进程时,有两种执行可能

  1. 父进程与子进程并发执行。
  2. 父进程等待,知道某个或全部子进程执行完

新进程的地址空间也有两种可能

  1. 子进程是父进程的复制品(具有与父进程相同的程序和数据)
  2. 子进程装入另一个新程序

在Linux中,每个进程都用一个唯一的整数形式的进程标识符来标识。通过fork()系统调用,可创建新进程。新进程通过复制原来进程的地址空间而成。这种机制允许父进程与子进程方、便地进程通信。两个进程(父进程和子进程)都继续执行位于系统调用fork()之后的指令。但是,有一点不同:对于新(子)进程,系统调用fork()的返回值为0;而对于父进程,返回值为子进程的进程标识符(非零)。

    通常,在系统调用fork()之后,一个进程会使用系统调用exec(),以用新进程取代进程的内存空间。系统调用exec()将二进制文件装入内存(消除原来包含系统调用exec()的程序的内存映射),并开始执行。采用这种方式,两个进程能相互通信,并能按各自的方法执行。父进程能创建更多的子进程,或者如果在子进程运行时没有什么可做,那么它采用系统调用wait()把自己移出就绪队列来等待子进程的终止。

    考虑在Windows中的进程生成。Win32 API通过采用CreateProcess()函数(它与fork()中的父进程生成子进程类似)创建进程。然而,fork()中子进程继承了父进程的地址空间,而CreateProcess()生成函数时,需要将一个特殊程序装入子进程的地址空间。进一步讲,与fork()不需要传递参数不同,CreateProcess()至少需要传递10个参数。

进程终止

    当进程完成执行最后的语句并使用系统调用exit()请求操作系统删除自身时,进程终止。这时,进程可以返回状态值(通常为整数)到父进程(通过系统调用wait())。所有进程资源(包括物理和虚拟内存、打开文件和I/O缓冲)会被操作系统释放。

    在其他情况下也会出现终止。进程通过适当的系统调用(如Win32中的TerminatePorcess())能终止另一个进程。通常,只有被终止进程的父进程才能执行这一系统调用。否则,用户可以任意地终止彼此的作业。记住,父进程需要知道其子进程的标识符。因此,当一个进程创建新进程时,新创建进程的标识符要传递给父进程。

    父进程终止其子进程的原因有很多,如:

  • 子进程使用了超过它所分配到的一些资源。(为判定是否发生这种情况,要求父进程有一个检查其子进程状态的机制)。
  • 分配给子进程的任务已不再需要。
  • 父进程退出,如果父进程终止,那么操作系统不允许子进程继续。

为了说明进程执行和终止,可考虑一下UNIX:可以通过系统调用exit()来终止进程,父进程可以通过系统调用wait()以等待子进程的终止。系统调用wait()返回了终止子进程的进程标识符,以使父进程能够知道哪个子进程终止了。如果父进程终止,那么其所有子进程会以init进程作为父进程。因此,子进程仍然有一个父进程来收集状态和执行统计。

 

进程间通信

    操作系统内并发执行的进程可以是独立进程或协作进程。如果一个进程不能影响其他进程或被其他进程所影响,那么该进程是独立的。显然,不与任何其他进程共享数据的进程是独立的。另一方面,如果系统中一个进程能影响其他进程或被其他进程所影响,那么该进程是协作的。显然,与其他进程共享数据的进程为写作进程。

    可能需要环境以允许进程协作,这有许多理由:

    信息共享(information sharing): 由于多个用户可能对同样的信息感兴趣(例如共享的文件),所以必须提供环境以允许对这些信息进行并发访问。

    提高运算速度(computation speedup): 如果希望一个特定任务快速运行,那么必须将它分成子任务,每个子任务可以与其他子任务并行执行。注意,如果要实现这样的加速,需要计算机有多个处理单元(例如CPU或I/O通道)。

模块化(modularity): 可能需要按模块化方式构造系统,可将系统功能分成独立进程或线程。

方便(convenience): 单个用户也可能同时执行许多任务。例如,一个用户可以并行进行编辑、打印和编译操作。

协作进程需要一种进程间通信机制(interprocess communication,IPC)来允许进程相互交换数据与信息。进程间通信有两种基本模式:(1)共享内存(2)消息传递。

共享内存模式中,建立起一块供协作进程共享的内存区域,进程通过向此共享区域读或写入数据来交换信息。在消息传递模式中,通过在协作进程间交换消息来实现通信。

在操作系统中,上述两种模式都很常用,而且许多系统也实现了这两种模式。消息传递对于交换较少数量的数据很有用,因为不需要避免冲突。对于计算机间的通信,消息传递也比共享内存更易于实现。共享内存允许以最快的速度进行方便的通信,在计算机中1它可以达到内存的速度。共享内存比消息传递快,消磁传递系统通常用系统调用来实现,因此需要更多的内核介入的时间消耗。与此相反,在共享内存系统中,仅在建立共享内存区域时需要系统调用,一旦建立了共享内存,所有的访问都被处理为常规的内存访问,不需要来自内核的帮助。

共享内存系统

    采用共享内存的进程间通信需要通信进程建立共享内存区域。通常,一块共享内存区域驻留在生成共享内存段进程的地址空间。其他希望使用这个共享内存段进行通信的进程必须将此放到它们自己的地址空间上。通常操作系统试图阻止一个进程访问另一进程多的内存。共享内存需要两个或更多的进程取消这个限制,它们通过在共享区域内读或写来交换信息。数据的形式或位置取决于这些进程而不是受控于操作系统。进程还负责保证它们不向同一区域同时写数据。

    采用共享内存是解决生产者-消费者问题方法中的一种。为了允许生产者进程和消费者进程能并发执行,必须要有一个缓冲来被生产者填充并被消费者使用。可以使用两种缓冲。无限缓冲(unbounded-buffer)对缓冲大小没有限制。消费者可能不得不等待新的项,但生产者总是可以产生新项。有限缓冲(bounded-buffer)假设缓冲大小固定。对于这种情况,如果缓冲为空,那么消费者必须等待;如果缓冲为满,那么生产者必须等待。

消息传递系统

    实现同样效果的另一种方法是由操作系统提供机制,让协作进程通过消息传递工具来进行通信。

    消息传递提供一种机制以允许进程不必通过共享地址空间来实现通信和同步,这在分布式环境中特别有用。

    消息传递工具至少提供两种操作:发送(消息)和接收(消息)。由进程发送的消息可以是定长的或变长的。如果只能发送定长的消息,那么系统级的实现十分简单。不过,这一限制却使得编程任务更加困难。相反地,变长消息要求更复杂的系统级实现,但是编程任务变得简单。这是贯穿整个操作系统设计的一种常见的折中问题。

    如果进程P和Q需要通信,那么它们必须彼此相互发送消息和接收消息,它们之间必须要有通信线路(communication link)。该线路有多种实现方式。这里不关心线路的物理实习ian,而只关心逻辑实现。如下是一些逻辑实现线路和send()/receive()操作方法:

  • 直接或间接通信
  • 同步或异步通信
  • 自动或显式缓冲
  1. 命名

    需要通信的进程必须有一个方法以互相引用。它们可使用直接或间接通信。

    对于直接通信,需要通信的每个进程必须明确地命名通信的接收者或发送者。采用这种方案,原语send()和receive()定义如下:

  • send(P,message):发送消息到进程P。
  • receive(Q,message):接收来自进程Q的消息。

这种方案的通信线路具有如下属性:

  • 在需要通信的每对进程之间自动建立线路。进程仅需知道相互通信的标识符。
  • 一个线路只与两个进程相关
  • 每对进程只有一个线路

这种方案展示了对称寻址,即发送和接收进程必须命名对方以便通信。这种方案一个变形采用非对称寻址,即只要发送者命名接收者,而接收者不需要命名发送者。采用这种方案,原语send()和receive()定义如下:

  • send(P,message):发送消息到进程P。
  • receive(id,message):接收来自任何进程的消息,变量id设置成与其通信的进程名称。

对称和非对称寻址方案的缺点是限制了进程定义的模块化。改变进程的名称可能必须检查所有其他进程定义。所有旧名称的引用都必须找到,以便修改成为新名称。与下面介绍的间接调用方法相比,通常,这种标识符必须明确指出的硬编码技术用得更少。

间接通信中,通过邮箱或端口来发送和接收消息。邮箱可以抽象成一个对象,进程可以向其中存放消息,也可以从中删除消息,每个邮箱都有一个唯一的标识符。例如,POSIX消息队列采用一个整数值来标识一个邮箱。对于这种方案,一个进程可能通过许多不同的邮箱与其他进程通信,但两个进程仅在其共享至少一个邮箱时可相互通信。原语send()和receive()定义如下:

  • send(A,message):发送一个消息到邮箱A。
  • receive(A,message):接收来自邮箱A的消息。

对于这种方案,通信线路具有如下属性:

  • 只有在两个进程共享一个邮箱时,才能建立通信线路。
  • 一个线路可以与两个或更多的进程相关联。
  • 两个通信进程之间可有多个不同的线路,每个线路对应于一个邮箱。
  1. 同步

进程间的通信可以通过调用原语send()和receive()来进行。这些原语的实现由不同的设计选项。消息传递可以是阻塞非阻塞------也称为同步异步

阻塞send:发送进程阻塞,直到消息被接受或邮箱所接收。

非阻塞send:发送进程发送消息并再继续操作。

阻塞receive:接收者阻塞,直到有消息可用。

非阻塞receive:接收者收到一个有效消息或空消息。

send()和receive()可以进程多种组合。当send()和receive()都阻塞时,则在发送者和接收者之间有一个集合点(rendezvous)。

  1. 缓冲

不管通信是直接的间接的,通信进程所交换的消息都驻留在临时队列中。简单地讲,队列实现有三种方法:

零容量: 队列的最大长度为0;因此,线路中不能有任何消息处于等待。对于这种情况,必须阻塞发送,直到接收者接收到消息。

有限容量:队列的长度为有限的n;因此,最多只能有n个消息驻留其中。如果在发送新消息时队列未满,那么该消息可以放在队列中(或者复制消息或者保存消息的指针),且发送者可继续执行而不必等待。不过,线路容量有限。如果线路满,必须阻塞发送者直到队列中的空间可用为止。

无限容量:队列长度可以无限,因此,不管多少消息都可在其等待,从不阻塞发送者。

    零容量情况称为没有缓冲的消息系统,其他情况称为自动缓冲。

Linux的IPC实例

信号: 信号是由某些错误条件而生成的,如内存段冲突、浮点处理器错误或非法指令等;接收到该信号的进程会相应的采取一些行动。用术语生成(raise)表示一个信号的产生,使用术语捕获(catch)表示接收到一个信号。一个进程在接收到信号后,默认情况是立刻终止进程。信号可以被产生,捕获,相应或忽略。信号可以由shell和终端生成,来引起中断;它们还可以作为进程间传递消息和修改行为的一种方式(明确的由一个进程发送给另一个进程)。

信号的名称在头文件signal.h中定义,它们都以SIG开头,如下表所示:

信号的处理

Linux中可以使用signal()函数来处理信号

 

 

 

进程可以通过调用kill函数向包括它本身在内的其他进程发送一个信号,该函数和同名的shell命令功能一样

Linux系统中还提供一个用于定时发送信号的alarm函数

此外,还有一个用于挂起进程的pause函数

 

使用alarm函数会使编程变得简单

客户机-服务器系统通信

Socket

Socket(套接字)可定义为通信的端点。一对通过网络通信的进程需要使用一对Socket---即每个进程各有一个。Socket由IP地址与一个端口号连接组成。通常,Socket采用客户机----服务器结构。服务器通过监听指定端口来等待进来的客户请求。一旦收到请求,服务器就接受来自客户Socket的连接,从而完成连接。服务器实现的特定服务(如telnet、ftp和http)是通过监听总所周知的端口来进行的(telnet服务器监听端口23,ftp服务器监听端口21,Web或http服务器监听端口80)。所有低于1024的服务器端口都被认为是总所周知的,可以用它来实现标准服务。

    当客户机进程发出连接请求时,它被主机赋予一个端口。该端口是大于1024的某个任意数。例如,如果IP地址为146.85.5.20的主机X的客户希望与地址为161.25.19.8的Web服务器(监听端口80)建立连接,它可能被分配端口1625。该连接由一对Socket组成:主机X上的(146.86.5.20:1625),Web服务器上的(161.25.19.8:80),根据目的端口,在主机之间传输的数据包可分送给合适的进程。

    所有连接必须唯一。因此,如果主机X的另一个进程希望与同样的Web服务器建立另一个连接,那么它会被分配另一个大于1024但不等于1625的端口号。这确保了所有连接都有唯一的一对Socket。

    Java提供了三种不同类型的Socket。面向连接(TCP)Socket是用Socket类实现的。无连接(UDP)Socket使用了DatagramSocket类。最后一种类型是多点传送Socket类(MulticastSocket class),它是DatagramSocket类的子类。多点传送Socket允许数据发送给多个接收者。

远程过程调用

RPC设计成抽象过程调用机制,用于通过网络连接系统。因为在所处理的环境中,进程在不同系统上执行,所以必须提供基于消息的通信方案来提供远程服务。与IPC工具不同,用于RPC交换的消息有很好的结构,因此不再仅仅是数据包。每个消息传递给位于远程系统上监听端口号的RPC服务器,每个都包含要执行函数的名称和传递给函数的参数。该函数根据请求而执行,任何结果通过另一个消息送回给请求者。

    端口只是一个数字,并包含在消息包的开始处。虽然一个系统通常只有一个网络地址,但是它在这一地址内有许多端口号以区分所支持的多种网络服务。如果一个远程进程需要服务,那么它就向适当端口发送消息。例如,如果一个系统允许其他系统能列出其当前用户,那么它可以有一个服务器支持这样的RPC,并监听一个端口,例如3027。任何远程系统只要向位于服务器的3027端口发送一个消息,就能得到所需要的信息(即列出当前用户),数据可通过回复消息收到。

    RPC语义允许客户机调用位于远程主机上的过程,就如同调用本地过程一样。通过在客户端提供存根(stub),RPC系统隐藏了允许通信发送的必要细节。通常,对于每个独立的远程过程都有一个存根。当客户机调用远程过程时,RPC系统调用合适的存根,并传递远程过程的参数。该存根位于服务器的端口,并编组(marshal)参数。参数编组涉及将参数打包成可通过网络传输的形式。接着存根使用消息传递向服务器发送一个消息。服务器的一个类似存根接收到这一消息,并调用服务器上的过程。如果有必要,返回值可通过同样技术传回到客户机。

    RPC方法对实现分布式文件系统非常有用。这种系统可通过一组RPC服务程序和客户机来实现。消息发送到服务器的分布式文件系统端口以进程文件操作。消息包括要执行的磁盘操作。磁盘操作可能是read,write,rename,delete或status,对应通常的文件相关的系统调用。返回消息包括来自调用(分布式文件系统服务程序在客户机执行)的任何数据。

远程方法调用

    远程方法调用(remote method invocation,RMI)是一个类似于RPC的Java特性。RMI允许线程调用远程对象的方法。如果对象位于不同的JVM上,那么久认为它是远程的。因此,远程可能在同一计算机或通过网络连接的主机的不同JVM上。

    RMI和RPC在两个方面有根本的不同。第一,RPC支持子程序编程,即只能调用远程的子程序或函数;而RMI是基于对象的,它支持调用远程对象的方法。第二,在RPC中,远程过程的参数是普通数据结构,而RMI可以将对象作为参数传递给远程方法。RMI通过允许Java程序调用远程对象的方法,使得用户能够开发分布在网络上的Java应用程序。

    为了使远程方法对客户机和服务器透明,RMI采用存根(stub)骨干(skeleton)实现远程对象。存根为远程对象的代理,它驻留在客户机中。当客户机调用远程方法时,远程对象的存根被调用。这种客户端存根负责创建一个包,它具有服务器上要调用方法的名称和用于该方法的编排参数。存根将该包发送给服务器,远程对象的骨干会接收它,骨干负责重新编排参数并调用服务器上所要执行的方法。骨干接着编排返回值(或异常),然后打包,并将该包返回给客户机。存根重新编排返回值,并传递给客户机。

如果编排参数是本地(非远程)对象,那么通过称为对象串行化的技术来复制传递。不过如果参数也是远程对象,那么可通过引用传递。

    如果本地对象需要作为参数传递给远程对象,那么就必须实现接口java.io.Serializable。对象串行化允许将对象状态写入字节流。

    小结

    进程是执行中的程序。随着进程的执行,它改变状态。进程状态由进程当前活动所定义。每个进程可处于:新的,就绪,运行,等待或终止等状态。每个进程在操作系统内通过自己的进程控制块(PCB)来表示。

    当前不在执行的进程会放在某个等待队列中。操作系统有两种主要队列:I/O请求队列和就绪队列。就绪队列包括所有准备执行并等待CPU的进程。每个进程都有PCB,PCB链接起来就形成了就绪队列。长期(作业)调度通过选择进程来争用CPU。通常,长期调度会受资源分配的考虑,尤其是内存管理的影响。短期调度从就绪队列中选择进程。

    操作系统必须为父进程创建子进程提供一种机制。父进程在继续之前可以等待它的子进程终止,也可以并发执行父进程和子进程。并发执行有许多优点,例如信息共享,提供运算速度,模块化和便利性等。

    操作系统的执行进程可以是独立进程或协作进程。协作进程需要进程间互相通信的机制。主要有两种形式的通信:共享内存和消息系统。共享内存方法要求通信进程共享一些变量。进程通过使用这些共享变量来交换信息。对于共享内存系统,主要由应用程序员提供通信。操作系统只需要提供共享内存。消息系统方法允许进程交换信息。提供通信的主要责任在于操作系统本身。

    客户机-服务器系统中的通信可能使用:

  1. Socket :Socket定义为通信的端点。一对应用程序间的连接由一对Socket组成,每端各有一个通信频道。
  2. 远程过程调用(RPC):RPC是另一种形式的分布式通信。当一个进程(或线程)调用一个远程应用的方法时,就会出现了RPC。
  3. Java的远程方法调用(RMI):RMI是RPC的Java版。RMI允许线程如同调用本地对象一样来调用远程对象的方法。RPC和RMI的主要区别是RPC传递给远程过程的数据是按普通数据结构的形式的,而RMI允许把对象传递给远程方法。

posted on 2018-11-10 13:03  kexinxin  阅读(206)  评论(0编辑  收藏  举报

导航