进程间通信的共享内存
共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后,可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着,共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域,就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时,其他进程仍然有可能读到变化与不稳定的数据。
为了解决这个问题,共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小,同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。
信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力,我们在这里则是支持的。一般来说,互斥体要比信号量效率更高。
用于消息传递的共享内存
共享内存与消息传递可以结合起来以提供支持以下功能的IPC:
- 非常高的性能(共享内存)
- 同步(消息传递)
- 网络透明(消息传递)
客户端使用消息传递向一个服务器端发送请求并阻塞。服务器端按优先级接收客户端发送的消息,处理消息并满足一个请求之后再回复。这时,客户端就解除阻塞并继续运行。发送消息这个动作就自然的在客户端与服务器端之间做了同步。这时可以不必将全部数据复制到消息中进行传输,只要让消息中包含一个共享内存区范围的引用,服务器端就可以直接读写数据了。这通过一个简单的例子就能解释清楚了。
假设一个图形服务器从客户端接收绘图的请求并在一个图像卡上将其着色为一个帧缓冲。如果只使用消息传递,客户端就需要将图像数据包含在一个消息中传到服务器。结果就是从客户端的地址空间中的图像数据要复制到服务器的地址空间。之后服务器端为该图像着色并发送一个简短的回复。
如果客户端没有在消息中直接插入图像数据,而是发送了包含图像数据的共享内存区域的引用,那么服务器端就能够直接访问客户端的数据了。
由于客户端向服务器端发送消息而被阻塞,服务器端就知道共享内存中的数据是固定的并在服务器端回复之前是不会被改变的。这种消息传递与共享内存的结合就能自然同步并能提供很高的性能。
这个动作模式也可以倒过来——让服务器端产生数据并将其发送给客户端。例如,一个客户端先服务器端发送一条消息请求其从CD-ROM中读取视频数据并将其直接写入客户端所提供的共享内存缓存中。服务器端在写入共享内存时,客户端是阻塞的。当服务器端回复,客户端继续运行时,共享内存就已经是固定的了并可以让客户端访问了。这样的设计可以通过使用多个共享内存区域而流水线化。
对于通过网络连接的不同计算机上的进程之间是不能够使用简单共享内存的。而消息传递才是网络透明的。一个服务器端可以使用共享内存用于本地客户端,而使用全消息传递用于远程客户端。这样就可让服务器端既能高效也能网络透明。
实际使用中,消息传递原的速度已经足够主要进程间通信的需求了。只有在需要非常高带宽的特殊情况下才需要考虑这种复杂的各方式结合的手段。
创建共享内存对象
同一个进程中的多个线程共享该进程的内存。要在进程之间共享内存,就先要创建一个共享内存区域并将其映射到你的进程的地址空间。共享内存区域的创建与操作可以使用如下函数:
| Function | Description | Classification |
|---|---|---|
| shm_open() | Open (or create) a shared-memory region. | POSIX |
| close() | Close a shared-memory region. | POSIX |
| mmap() | Map a shared-memory region into a process’s address space. | POSIX |
| munmap() | Unmap a shared-memory region from a process’s address space. | POSIX |
| munmap_flags() | Unmap previously mapped addresses, exercising more control than possible with munmap() | QNX Neutrino |
| mprotect() | Change protections on a shared-memory region. | POSIX |
| msync() | Synchronize memory with physical storage. | POSIX |
| shm_ctl(), shm_ctl_special() |
Give special attributes to a shared-memory object. | QNX Neutrino |
| shm_unlink() | Remove a shared-memory region. | POSIX |
POSIX共享内存函数在Neutrino中是通过进程管理器(procnto)来实现的。以上的函数都是作为发送到procnto的消息来实现的。
Shm_open()函数所使用的参数与open()函数一致并向对象返回一个文件描述符。与对常规文件的操作一样,这个函数可以用于创建一个新的共享内存对象或是打开一个已有的共享内存对象。
当一个新的共享内存对象创建之后,其大小为0。要设置其大小需要使用ftruncate()函数(设置文件大小也是使用同一个函数),或是使用shm_ctl()函数。
mmap()
一旦你有了指向共享内存对象的文件描述符,就可以使用mmap()函数将这个共享内存的全部或部分映射到你的进程的地址空间。mmap()函数是Neutrino系统内存管理的基石并有必要在下面对其功能详细讨论。
该函数的详细定义如下:
void * mmap( void *where_i_want_it,
size_t length,
int memory_protections,
int mapping_flags,
int fd,
off_t offset_within_shared_memory );
简单的说就是:将fd所关联的共享内存对象的offset_within_shared-memory处开始的length字节的共享内存映射到进程中。
mmap()函数将尝试将该内存放到你的地址空间的where_i_want_it地址处。该内存将按照指定的memory_protections进行保护并按照mapping_flag的模式映射。
三个参数fd、offset_within_shared_memory和length定义了要映射的共享内存的区域。通常都是映射整个共享对象,在这时便宜量就是0而长度就是共享对象的以字节计算的大小。在使用Intel处理器的时候,该长度将是页面大小的整倍数,使用Intel处理器时一个页面的大小为4096字节。
Related posts:
近期评论