下面详细说明客户进程和服务器进程的某些属性,这些属性受到它们之间所使用的IPC类型的影响。最简单的关系类型是使客户调用fork然后调用exec执行所希望的服务器进程。
在fork之前先创建两个半双工管道使数据可在两个方向传输。http://www.cnblogs.com/nufangrensheng/p/3561379.html中的图15-8是这种形式的一个例子。被执行的服务器程序可能是设置用户ID的程序,这使它具有了特权。服务器进程查看客户进程的实际用户ID就可以决定客户进程的身份。(回忆http://www.cnblogs.com/nufangrensheng/p/3510821.html,从中可以了解到在exec前后实际用户ID和实际组ID并没有改变。)
在这种安排下,可以构筑一个开放式服务器(open server)。它为客户进程打开文件而不是客户进程自己调用open函数。这样就可以在正常的UNIX用户/组/其他权限之上或之外,增加附加的权限检查。假定服务器进程执行的是设置用户ID程序,这给予了它附加的权限(很可能是root权限)。服务器进程用客户进程的实际用户ID以决定是否给予它对所请求文件的访问权限。使用这种方式,可以构筑一个服务器进程,它允许某种用户获得通常没有的访问权限。
在此例子中,因为服务器进程是父进程的子进程,所以它能做的一切是将文件内容传送给父进程。这种方式对普通文件完全够用,但是对特殊设备文件却不能工作。我们所希望能做的是使服务器进程打开所要的文件,并且送回文件描述符。但是实际情况却是父进程可向子进程传送打开文件描述符,而子进程则不能向父进程传回文件描述符。
http://www.cnblogs.com/nufangrensheng/p/3561632.html图15-12中示出了另一种类型的服务器进程。这种服务器进程是一个守护进程,所有客户进程用某种形式的IPC与其联系。对于这种形式的客户进程-服务器进程关系,不能使用管道。要求使用命名的IPC,例如FIFO或消息队列。对于FIFO,如果服务器进程必须将数据送回客户进程,则对每个客户进程都要有单独使用的FIFO。如果客户进程-服务器进程应用程序只有客户进程向服务器进程发送数据,则只需要一个众所周知的FIFO。
使用消息队列则存在多种可能性:
(1)在服务器进程和所有客户进程之间只使用一个队列,使用消息的类型字段指明谁是消息的接收者。例如,客户进程可以用类型字段1发送它们的消息。在请求之中应包括客户进程的进程ID。此后,服务器进程在发送响应消息时,将类型字段设置为客户进程的进程ID。服务器进程只接收类型字段为1的消息(msgrcv的第四个参数),客户进程则只接收类型字段等于它进程ID的消息。
(2)另一种方法是每个客户进程使用一个单独的消息队列。在向服务器进程发送第一个请求之前,每个客户进程先创建它自己的消息队列,创建时使用键IPC_PRIVATE。服务器进程也有它自己的队列,其键或标识符是所有客户进程都知道的。客户进程将其第一个请求送到服务器的众所周知的队列上,该请求中应包含其客户进程消息队列的队列ID。服务器进程将其第一个响应送至客户进程队列,此后的所有请求和响应都在此队列上交换。
使用这种技术的一个问题是:每个客户进程专用队列通常只有一个消息在其中——或者是对服务器的一个请求,或者是对客户进程的响应。这似乎是对有限的系统资源(消息队列)的浪费,为此可以用一个FIFO来代替。另一个问题是服务器进程需从多个队列读消息。对于消息队列,select和poll都不起作用。
使用消息队列的这两种技术都可以用共享存储段和同步方法(信号量或记录锁)实现。
这种类型的客户进程-服务器进程关系(客户进程和服务器进程是无关系进程)的问题是:服务器进程如何准确地标识客户进程?除非服务器进程正在执行一种非特权操作,否则服务器进程知道客户进程的身份是很重要的。例如,若服务器进程是一个设置用户ID程序,就有这种要求。虽然,所有这几种形式的IPC都经由内核,但是它们并未提供任何措施使内核能够标识发送者。
对于消息队列,如果在客户进程和服务器进程之间使用一个专用队列(于是一次只有一个消息在该队列上),那么队列的msg_lspid包含了对方进程的进程ID。但是当客户进程将请求发送给服务器进程时,我们想要的是客户进程的有效用户ID,而不是它的进程ID。现在还没有一种可移植的方法,在已知进程ID的情况下可以得到有效用户ID。(内核在进程表项中自然地保持有这两种值,但是除非彻底检查内核存储空间,否则已知一个,无法得到另一个。)
使服务器进程可以标识客户进程:这一技术既可使用FIFO、消息队列或信号量,也可使用共享存储。我们假定http://www.cnblogs.com/nufangrensheng/p/3561632.html图15-12使用了FIFO。客户进程必须创建它自己的FIFO,并且设置该FIFO的文件访问权限,使得只允许用户读,用户写。假定服务器进程具有超级用户特权(或者它很可能并不关心客户进程的真实标识),所以服务器进程仍可读、写此FIFO。当服务器进程在众所周知的FIFO上接收到客户进程的第一个请求时(它应当包含客户进程的专用FIFO的标识),服务器进程调用针对客户进程专用FIFO的stat或fstat。服务器进程假设客户进程的有效用户ID是FIFO的所有者(stat结构的st_uid字段)。服务器进程验证该FIFO只有用户读、用户写权限。服务器进程还应检查该FIFO的三个时间量(stat结构中的st_atime,st_mtime和st_ctime字段),要检查它们与当前时间是否很接近(例如 不早于当前时间15s或30s)。如果一个有预谋的客户进程可以创建一个FIFO,使另一个用户成为其所有者,并且设置该文件的权限为用户读和用户写,那么在系统中就存在了其他基础性的安全问题。
为了用XSI IPC实现这种技术,与每个消息队列、信号量以及共享存储段相关的ipc_perm结构,其中cuid和cgid字段标识IPC结构的创建者。以FIFO为例,服务器进程应当要求客户进程创建该IPC结构,并使客户进程将访问权限设置为只允许用户读和用户写。服务器进程也应检验与该IPC相关的时间值与当前时间是否很接近(因为这些IPC结构在显式地删除之前一直存在)。
进程间通信小结
说明了进程间通信的多种形式:管道、命名管道(FIFO)以及另外三种IPC形式(通常称为XSI IPC),即消息队列、信号量和共享存储。信号量实际上是同步原语而不是IPC,常用于共享资源的同步访问。
要学会使用管道和FIFO,因为在大量应用程序中仍可有效地使用这两种基本技术。
在新的应用程序中,要尽可能避免使用消息队列以及信号量,而应当考虑全双工管道和记录锁,它们使用起来会简单得多。
本篇博文内容摘自《UNIX环境高级编程》(第2版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。