1 概述
共享内存区是可用IPC形式中最快的。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再涉及内核【即进程不再通过执行任何内核的系统调用来彼此传递数据】。显然,内核必须允许各个进程共享该内存区的内存映射关系,然后一直管理该内存区(处理页面故障等)。
反面样例
考虑用来传递各种类型信息的一个示例客户-服务器文件复制程序中涉及的通常步骤:
- 服务器从输入文件读。 该文件的数据由内核读入自己的内存空间,然后从内核复制到服务器进程【文件输入->内核内存->服务器进程】
- 服务器往一个管道、FIFO或消息队列以一条消息的形式写入这些数据【从进程复制到内核】
- 客户从该IPC通道读出这些数据,即将这些数据从内核复制到进程
- 将这些数据从由write函数的第二个参数指定的客户缓冲区复制到输出文件
使用共享内存区将文件数据从服务器复制到客户
- 服务器使用一个信号量取得访问某个共享内存区对象的权力;
- 服务器将数据从输入文件读入到该共享内存区对象。read函数的第二个参数所指定的数据缓冲区指向这个共享内存区对象。
- 服务器读入完毕时,使用一个信号量通知客户。
- 客户将这些数据从共享内存区对象写出到输出文件中。
Note:默认情况下通过fork派生的子进程并不与其父进程共享内存区。下述例子让父子进程给一个名为count的全局整数加1.
- 把标准输出设置为非缓冲,因为父子进程都要往它写结果,这样可以防止这两个进程的输出不恰当地交叉。
#include "unpipc.h" #define SEM_NAME "mysem" int count = 0; int main(int argc, char const *argv[]) { int i, nloop; sem_t *mutex; if (argc != 2) err_quit("usage: incr1 <#loops>"); nloop = atoi(argv[1]); // creat, initialize, and unlink semaphore mutex = Sem_open(Px_ipc_name(SEM_NAME), O_CREAT | O_EXCL, FILE_MODE, 1); Sem_unlink(Px_ipc_name(SEM_NAME)); // 把标准输出设置为非缓冲,因为父子进程都要往它写结果,这样可以防止这两个进程的输出 // 不恰当地交叉 setbuff(stdout, NULL); if ((Fork() == 0)) { for ( i = 0; i < nloop; ++i) { Sem_wait(mutex); printf("child: %d\n", count++); Sem_post(mutex); } exit(0); } for ( i = 0; i < nloop; ++i) { Sem_wait(mutex); printf("parent: %d\n", count++); Sem_post(mutex); } exit(0); }
2 mmap、munmap和msync函数
mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数有3个目的:
-
(1) 使用普通文件以提供内存映射I/O;
-
(2) 使用特殊文件以提供匿名内存映射;
-
(3) 使用shm_open以提供无亲缘关系进程间的Posix共享内存区。
#include <sys/mman.h> // 成功则返回被映射区的起始地址,否则返回MAP_FAILED void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
-
(1)其中addr可以指定描述符fd被映射到进程内空间的起始地址,通常为NULL,以告诉内核自己去选择起始地址。
-
(2)len是映射到进程地址空间中的字节数。
-
(3) len是从被映射文件开头起第offset个字节处开始算。
-
(4)内存映射区的保护由prot参数指定:
prot 说明 PROT_READ 数据可读 PROT_WRITE 数据可写 PROT_EXEC 数据可执行 PROT_NONE 数据不可访问 -
flag
Flags 说明 备注 MAP_SHARED MAP是共享的 调用进程对被映射数据所做的修改对于共享该对象的所有进程都可见。【父子进程之间共享内存区的方法之一是,父进程在调用fork之前先指定MAP_SHARED调用mmap】 MAP_PRIVATE MAP是私有的 调用进程对被映射数据所做的修改只对该进程可见,而不改变其底层支撑对象(文件对象 / 共享内存区对象) MAP_FIXED
munmap从某个进程的地址空间删除一个映射关系:
#include <sys/mman.h>
// 成功返回0.否则返回-1
int munmap(void *addr, size_t len);
3 在内存映射文件中给计数器持续加1
-
使用Posix有名信号量
将值为0的整数写入该文件
#include "unpipc.h" #define SEM_NAME "mysem" int main(int argc, char const *argv[]) { int fd, i, nloop, zero = 0; int *ptr; sem_t *mutex; if (argc != 3) err_quit("usage: incr2 <#pathname> <#loops>"); nloop = atoi(argv[2]); // open file, initialize to 0, map into memory fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE); // 将值为0的整数写入该文件 Write(fd, &zero, sizeof(int)); ptr = Mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // mmap后关闭描述符 Close(fd); // 使用Posix有名信号量 mutex = Sem_open(Px_ipc_name(SEM_NAME), O_CREAT | O_EXCL, FILE_MODE, 1); Sem_unlink(Px_ipc_name(SEM_NAME)); // 把标准输出设置为非缓冲 setbuff(stdout, NULL); if ((Fork() == 0)) { for ( i = 0; i < nloop; ++i) { Sem_wait(mutex); printf("child: %d\n", (*ptr)++); Sem_post(mutex); } exit(0); } for ( i = 0; i < nloop; ++i) { Sem_wait(mutex); printf("parent: %d\n", (*ptr)++); Sem_post(mutex); } exit(0); }
-
使用Posix基于内存的信号量
struct shared
{
sem_t mutex; // Posix基于内存的信号量
int count;
} shared;
#include "unpipc.h"
struct shared
{
sem_t mutex; // Posix基于内存的信号量
int count;
} shared;
int main(int argc, char const *argv[])
{
int fd, i, nloop;
struct shared *ptr;
if (argc != 3)
err_quit("usage: incr3 <#pathname> <#loops>");
nloop = atoi(argv[2]);
// open file, initialize to 0, map into memory
fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
Write(fd, &shared, sizeof(struct shared));
ptr = Mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Close(fd);
Sem_init(&ptr->mutex, 1, 1);
// 把标准输出设置为非缓冲
setbuff(stdout, NULL);
if ((Fork() == 0))
{
for ( i = 0; i < nloop; ++i)
{
Sem_wait(mutex);
printf("child: %d\n", ptr->count++);
Sem_post(&ptr->mutex);
}
exit(0);
}
for ( i = 0; i < nloop; ++i)
{
Sem_wait(mutex);
printf("child: %d\n", ptr->count++);
Sem_post(&ptr->mutex);
}
exit(0);
}
4 BSD匿名内存映射
- BSD提供匿名内存映射,彻底避免了文件的创建和打开。其办法是把mmap的flags参数指定成MAP_SHARED | MAP_ANON,把fd参数指定为-1.
5 小结
- 共享内存区是可用IPC形式中最快的,因为共享内存区中的单个数据副本对于共享该内存区的所有线程或进程都是可用的。