1 共享内存区
共享内存区是可用IPC形式中最快的,只有映射和解除映射需要进入内核的系统调用,映射后对共享内存区的访问和修改不再需要系统调用(内核只要负责好页表映射和处理页面故障即可),但通常需要同步手段。
一个客户-服务器间传递文件数据的例子中,FIFO或消息队列等IPC方式通常需要4次内核-进程间的数据复制(但是Posix消息队列可使用内存映射I/O实现,就不一定4次了),每次都需要切换地址空间,开销很大;共享内存区只需要2次跨内核的数据复制。
2 mmap
mmap的三个目的:
1. 使用普通文件以提供内存映射I/O。
2. 使用特殊文件以提供匿名内存映射。
3. 使用shm_open以提供Posix共享内存区。
void *mmap(void*addr, size_t len, int prot, int flags, int fd, off_t offset);
addr:调用者希望的起始地址,通常为空,表示由内核选择。
len:映射区大小。
prot:映射区的保护模式,PROT_READ | PROT_WRITE | PROT_EXEC |PROT_NONE。
flags:共享模式,MAP_SHARED和MAP_PRIVATE指定对映射区的数据修改会不会影响到底层支撑对象上;MAP_FIXED表示指定addr为起始地址。
fd和offset:用于支撑的底层文件范围。
mmap成功返回后可以关闭fd,不会影响映射。映射后对共享内存区的访问就被纳入到操作系统的虚拟内存的管理下,因此不再需要系统调用。
munmap用于从某个进程的地址空间删除一个映射关系。
msync用于同步一个指定了MAP_SHARED标志的映射区与支撑文件数据。
终端和套接字不能进行映射。
如果指定了MAP_SHARED,那么调用进程对被映射数据所做的修改对于共享该对象的所有进程都可见,而且确实改变了其底层支撑对象
如果指定了MAP_PRIVATE,那么调用进程对被映射数据所做的修改只对该进程可见,而不改变其底层支撑对象。
如果修改了处于内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应的更新文件,然而如果希望确信硬盘上的文件内容与内存映射区的内容一致,可以调用msync来执行同步。
注意:
可以管理同步的信号量写到内存区中,如
struct shared{
sem_t mutex;
int count;
}shared;
然后把结构体写入文件并映射,调用sem_init(&ptr->mutex,1,1);即可。
3. 匿名内存映射
如果只想在父子进程间使用共享内存区,可以用匿名内存映射来简化操作:
1. 4.4BSD匿名内存映射:将mmap的flags指定成MAP_SHARED(默认情况下通过fork派生的子进程并不与其父进程共享内存区,子进程是copy的父进程的内存副本) | MAP_ANON,fd为-1。
2. SVR4:打开特殊设备/dev/zero,将得到的fd用于mmap。
4 映射区长度与文件大小
mmap返回的地址一般是页对齐的,映射区最后不满一页的部分可访问,但对它的修改不会写入文件,对超出映射区所在的页的访问会产生段错误。
若映射区大小超出文件大小,则访问在映射区内但超出文件范围的页面时会引发总线错误。
映射过程中如果底层文件大小改变,也会同样改变映射区的可访问范围。
5 Posix共享内存区
两种无亲缘关系进程间共享内存的方法:
1. 内存映射文件:open一个普通文件,用它的fd进行mmap。
2. 共享内存区对象:shm_open一个IPC名字,用它的fd进行mmap。步骤:指定名字调用shm_open(..)创建共享内存 ,然后调用mmap将共享内存映射到调用进程
6 功能函数
int shm_open(constchar *name, int oflag, mode_t mode); (调用shm_open前一般先调用shm_unlink以提防所需共享内存区对象已经存在的情况)
创建或打开一个Posix共享内存区对象。Posix没有指定一个新建的共享内存区对象的初始内容。
int shm_unlink(constchar *name);(删除一个名字不会影响对于其底层支撑对象的现有引用,知道对于该对象引用的全部关闭为止,删除一个名字仅仅防止后续的open mq_open sem_open调用取得成功)
引用计数删除。
int ftruncate(intfd, off_t length);
改变文件或共享内存区对象的大小。
int fstat(int fd,struct stat *buf);
获取文件信息,对共享内存区对象只有4个成员有信息:
st_mode:读写权限;
st_uid、st_gid:所有者身份;
st_size:共享内存区大小。
注意:
同一共享内存区对象内存映射到不同进程的地址空间时,起始地址可以不一样。
7 system V 共享内存区
通过int shmget(key_t key, size_t size, int flag) 获取共享内存区,返回共享内存区标示符
shmget创建或打开一个共享内存区,但并没有给调用进程提供访问该内存区的手段,通过 void *shmat(int shmid, const void *shmaddr, int flag) ; 把共享内存区附接到调用进程的地址空间。 shmat返回的是所指定的共享内存区在调用进程内的起始地址。
8 Posix共享内存区和system v共享内存区的差别
posix共享内存区对象的大小可在任何时刻通过调用ftruncate修改,但是system v 共享内存区对象的大小是在调用shmget创建时固定下来的。