进程间通讯与同步技术第一篇,共享内存
- 共享内存
- C++的shared_memory_object类
- 创建共享内存段
- 映射共享内存段
- Linux API
- 信号量同步
共享内存
共享内存是最快的进程间通信机制。操作系统将一个内存段映射到多个进程的地址空间中,这样多个进程就可以在该内存段中进行读写操作,而无需调用操作系统函数。但是,我们需要在读取和写入共享内存的进程之间进行某种同步。
要使用共享内存,我们必须执行两个基本步骤:
- 向操作系统请求一个可以在进程之间共享的内存段。用户可以使用共享内存对象来创建/销毁/打开此内存:一种表示可以同时映射到多个进程的地址空间的内存的对象。
- 将该内存的一部分或整个内存与调用进程的地址空间相关联。操作系统在调用进程的地址空间中寻找足够大的内存地址范围,并将该地址范围标记为特殊范围。该地址范围中的更改会自动被也映射了相同共享内存对象的其他进程看到。
一旦这两个步骤成功完成,进程就可以开始写入地址空间和从地址空间读取数据,以便向其他进程发送数据和从其他进程接收数据。现在,让我们看看如何使用 Boost.Interprocess 做到这一点:
C++的shared_memory_object类
创建共享内存段
正如我们提到的,我们必须使用 shared_memory_object 类来创建、打开和销毁可以由多个进程映射的共享内存段。我们可以指定该共享内存对象的访问模式(只读或读写),就像它是一个文件一样:
创建一个共享存储器段,如果它已经存在,产生一个例外using boost::interprocess;shared_memory_object shm_obj(create_only //only create,"shared_memory" //name,read_write //read-write mode);To open or create a shared memory segment:using boost::interprocess;shared_memory_object shm_obj(open_or_create //open or create,"shared_memory" //name,read_only //read-only mode);To only open a shared memory segment. Throws if does not exist:using boost::interprocess;shared_memory_object shm_obj(open_only //only open,"shared_memory" //name,read_write //read-write mode);
创建共享内存对象时,其大小为0。要设置共享内存的大小,用户必须使用truncate函数调用,在已打开的具有读写属性的共享内存中:
shm_obj.truncate(10000);
映射共享内存段
一旦创建或打开,进程只需要在进程的地址空间中映射共享内存对象。用户可以映射整个共享内存或其中的一部分。映射过程是使用mapped_region 类完成的。该类表示已从共享内存或其他具有映射功能的设备(例如文件)映射的内存区域。一个 mapping_region 可以从任何 memory_mappable 对象创建,正如你想象的那样,shared_memory_object 是一个 memory_mappable 对象:
using boost::interprocess;std::size_t ShmSize = 10000;//Map the second half of the memory
mapped_region region( shm //Memory-mappable object, read_write //Access mode, ShmSize/2 //Offset from the beginning of shm, ShmSize-ShmSize/2 //Length of the region);//Get the address of the region
region.get_address();//Get the size of the region
region.get_size();
用户可以指定从可映射对象开始映射区域的偏移量以及映射区域的大小。如果未指定偏移量或大小,则映射整个可映射对象(在本例中为共享内存)。如果指定了偏移量,但未指定大小,则映射区域将覆盖从偏移量直到可映射对象的末尾。
Linux API
Linux 系统为共享内存提供了两个独立的 API:
- System V API
- POSIX API
但是,这些 API 不应混合在单个应用程序中。 POSIX 方法的一个缺点是,功能仍在开发中并且依赖于安装的内核版本,这会影响代码的可移植性。例如,POSIX API 默认将共享内存实现为内存映射文件:对于共享内存段,系统维护一个包含相应内容的后备文件。 POSIX 下的共享内存可以在没有后备文件的情况下进行配置,但这可能会影响可移植性。我的示例使用带有后备文件的 POSIX API,它结合了内存访问(速度)和文件存储(持久性)的优点。
共享内存示例有两个程序,名为 server 和 client,使用信号量同步对共享内存的访问。每当共享内存与写入器一起出现时,无论是在多处理,还是多线程中,基于内存的竞争条件的风险也会随之而来;因此,信号量用于同步对共享内存的访问。
int shm_open(const char *name, int oflag, mode_t mode);
int ftruncate(int fildes, off_t length);
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
int shm_unlink(const char *name);
信号量同步
通用信号量也称为计数信号量,因为它具有可以递增的值。
考虑一个出租自行车的商店,有一百辆存货,供客户租用。使用计数信号量,初始化成100。
每租出一辆自行车,信号量减一;当自行车归还时,信号量加一。租赁可以持续,直到信号量为0,此时,没有更多的自行车可供租赁。一旦有自行车归还,信号量变非0,租赁可以再次进行。
二元信号量是一种特殊情况,只需要两个值:0 和 1。在这种情况下,信号量充当互斥体:互斥结构。共享内存例子使用信号量作为互斥锁。
当信号量的值为 0 时,只有server才能访问共享内存。写入后,此过程会增加信号量的值,从而允许client读取共享内存。
// ipc_1.h
#define BackingFile "shared_1.txt"
#define Sema_Server_ready "sema_snd"
#define Sema_Client_ready "sema_rcv"typedef struct personnel_s {int id;char name[20];int age;
} personnel_t;#define ByteSize sizeof(personnel_t)
#define AccessPerms 0664
服务器进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "ipc_1.h"int main(int argc, char *argv[]) {personnel_t *pPerson;caddr_t memptr;sem_t* semptr;sem_t* sema_client_ready;int count, i;if (argc < 2)return -1;count = atoi(argv[1]);int fd = shm_open(BackingFile, /* name from smem.h */O_RDWR | O_CREAT, /* read/write, create if needed */AccessPerms); /* access permissions (0644) */if (fd < 0) {return -1;}ftruncate(fd, ByteSize); /* get the bytes */memptr = (caddr_t)mmap(nullptr, /* let system pick where to put segment */ByteSize, /* how many bytes */PROT_READ | PROT_WRITE, /* access protections */MAP_SHARED, /* mapping visible to other processes */fd, /* file descriptor */0); /* offset: start at 1st byte */if ((caddr_t) -1 == memptr) {return -2;}fprintf(stderr, "shared mem address: %p %d:[0..%ld]\n", memptr, count, ByteSize - 1);fprintf(stderr, "backing file: /dev/shm%s\n", BackingFile );/* semaphore code to lock the shared mem */semptr = sem_open(Sema_Server_ready, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (semptr == (void*) -1) {return -3;}sema_client_ready = sem_open(Sema_Client_ready, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (sema_client_ready == (void*) -1) {return -3;}pPerson = (personnel_t *)memptr;for (i=0; i < count; i++){pPerson->id = 12345+i;if (sem_post(semptr) < 0) {return-4;}sem_wait(sema_client_ready);printf("client picked up data\n");}pPerson->id = 0; sem_post(semptr);munmap(memptr, ByteSize); /* unmap the storage */close(fd);sem_close(semptr);shm_unlink(BackingFile); /* unlink from the backing file */return 0;
}
客户进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "ipc_1.h"int main(int argc, char *argv[]) {caddr_t memptr;personnel_t *pPerson;sem_t* semptr;sem_t* sema_client_ready;int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* empty to begin */if (fd < 0) {return -1;}printf ("Messages:%s\n", argv[1]);memptr = (caddr_t)mmap(NULL, /* let system pick where to put segment */ByteSize, /* how many bytes */PROT_READ | PROT_WRITE, /* access protections */MAP_SHARED, /* mapping visible to other processes */fd, /* file descriptor */0); /* offset: start at 1st byte */if ((caddr_t) -1 == memptr) return -2;semptr = sem_open(Sema_Server_ready, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (semptr == (void*) -1) return -3;sema_client_ready = sem_open(Sema_Client_ready, /* name */O_CREAT, /* create the semaphore */AccessPerms, /* protection perms */0); /* initial value */if (sema_client_ready == (void*) -1) return -3;do {if (!sem_wait(semptr)) { /* wait until semaphore != 0 */int i;pPerson = (personnel_t *)memptr;printf("ID:%d\n", pPerson->id);if (pPerson->id == 0)break;}sem_post(sema_client_ready);} while (1);munmap(memptr, ByteSize);close(fd);sem_close(semptr);unlink(BackingFile);return 0;
}