共享内存(Shared Memory)是最简单的进程间通信方式,它允许多个进程访问相同的内存,一个进程改变其中的数据后,其他的进程都可以看到数据的变化。
共享内存是进程间最快速的通信方式:
①进程共享同一块内存空间。
②访问共享内存和访问私有内存一样快。
③不需要系统调用和内核入口。
④不造成不必要的内存复制。
内核不对共享内存的访问进行同步,因此程序员必须自己提供同步。
使用共享内存:
①某个进程分配内存段。
②使用这个内存段的进程要连接(attach)这个内存段。
③每个进程使用完共享内存段后,要分离(detach)这个内存段。
④在某个地方,必须有一个进程来销毁这个内存段。
Linux的内存模型:
①每个进程的虚拟内存被分为页(page)。
②每个进程维护自己的内存地址到虚拟内存页之间的映射。
③实际的数据存在于进程的内存地址上。
④尽管每个进程有自己的地址空间,多个进程的映射还是可以指向相同的页。
所有的共享内存段的大小,都是Linux内存页大小的整数倍。
Linux的页大小是4KB,不过程序员应该使用getpagesize函数来获得这个值。
分配:shmget
函数原型:shmget(key_t key, int size, int shmflg);
①:shmget用来取得参数key所关联的内存识别代码。如果参数key为IPC_PRIVATE,则会建立新的共享内存,大小是由size来决定的,
实际分配的字节数会舍弃多余部分到页大小的整数倍。
②:如果参数key不是IPC_PRIVATE,也不是已经建立的IPC key, 那么系统会根据shmflg是否有IPC_CREAT位(shmflg | IPC_CREAT)
为真来决定IPC key为参数的共享内存。
③:如果参数包含了IPC_CREAT 和 IPC_EXECL位, 那么如果key标识的共享内存已经存在, 那么会报EEXIST的错误。
④:参数也用来决定共享内存的存取权限, 相当于open()的 mode参数。
内存共享attach:shmat
函数原型: void* shmat(int shmid, const void* shmaddr, int shmflg);
①:shmat()用来将参数shmid指定的共享内存和目前进程相连(attach)
②:在经过fork()后, 子进程将继承已连接的共享内存地址,
③:在经过exec()函数后, 已连接的共享内存地址会自动脱离(detach)
④:进程结束后,已连接的共享内存会自动脱离
脱离:shmdt
函数原型:shmdt(const void *shmaddr);
①:shmdt()用来脱离先前shmat()过的共享内存。
控制共享内存的操作 shmctl
函数原型:shmctl(int shmid, int cmd, struct shmid_ds *buf);
①:参数cmd 有下面几种操作
1:IPC_STAT:把共享内存的 shmid_ds的结构copy到 buf 中。
2:IPC_SET: 把参数buf所指的shmid_ds结构中的 shm_perm.uid, shm_perm.gid, shm_perm.mode 拷贝到共享内存的
shmid_ds结构中。
3:IPC_RMID:删除共享内存和其数据结构。
看例子:
typedef struct {
u_char *addr;
size_t size;
ngx_log_t *log;
} ngx_shm_t;
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
int id;
/* 用ipc_private 来新建共享内存 */
id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));
if (id == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
"shmget(%uz) failed", shm->size);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, shm->log, 0, "shmget id: %d", id);
/* 每次有进程shmat,那么在这个共享内存中的shmid_ds 结构中的shm_nattach就会加一 */
shm->addr = shmat(id, NULL, 0);
if (shm->addr == (void *) -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed");
}
/* 只有在id所标识的共享内存中的shmid_ds结构中的shm_nattach为0的情况下才能被删除。否者无效*/
if (shmctl(id, IPC_RMID, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
"shmctl(IPC_RMID) failed");
}
return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK;
}
void
ngx_shm_free(ngx_shm_t *shm)
{
/* 每次有进程shmdt,那么在这个共享内存中的shmid_ds 结构中的shm_nattach就会减一 */
if (shmdt(shm->addr) == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
"shmdt(%p) failed", shm->addr);
}
}