相关介绍
SystemV共享内存区在概念上类似于前面提到的mmap,我们将先使用shmget,然后再调用shmat,其中shmget类似于open,shmat类似于mmap。
相关系统调用
shmget函数创建一个新的共享内存区,或者访问一个已存在的内存共享区。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int oflag);
1,返回值是一个被称为共享内存区标识符的整数,在其他的shmXXX函数中,可以用它来指代这个内存区(可以理解为一个轻量级的文件描述符)。
2,key既可以是ftok的返回值,也可以是IPC_PRIVATE,关于ftok(返回IPC的键),内容比较多,有兴趣可以自己查看。
3,size看字就知道是内存共享区的大小。创建一个新的,必须指定不为0的size,如果访问一个新的,那么size将为0。
4,oflag是如下所示的读写权限值得组合。它还可以与IPC_CREATE或IPC_CREATE | IPC_EXCL按位或。
数字值 | 符号值 | 说明 |
---|---|---|
0400 | SHM_R | 由用户(属主)读 |
0200 | SHM_W | 由用户(属主)写 |
0040 | SHM_R >> 3 | 由(属)组成员读 |
0020 | SHM_W >> 3 | 由(属)组成员写 |
0004 | SHM_R >> 6 | 由其他用户读 |
0002 | SHM_W >> 6 | 由其他用户写 |
5,指定key为IPC_PRIVATE能保证创建一个唯一的IPC对象,相关IPC创建或者打开逻辑
oflag参数值 | key不存在 | key已经存在 |
---|---|---|
无特殊标志 | 出错,errno = ENOENT | 成功,引用已存在对象 |
IPC_CREAT | 成功,创建新对象 | 成功,引用已存在对象 |
IPC_CREAT或IPC_EXCL | 成功,创建新对象 | 出错,errno = EEXIST |
由shmget创建或者打开一个共享内存区后,通过调用shmat把它附接到调用进程的地址空间。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int flag);
1,shmid是由shmget返回的标识符。shmat的返回值是所指定的共享内存区在调用进程内的起始地址。
2,shmaddr如果是一个空指针,那么系统替调用者选择地址。如果不是,那么返回地址取决于调用者是否给flag参数指定了SHM_RND的值
3,flag如果指定为SHM_RND,那么相应的共享内存区附接到由shmaddr参数指定的地址向下舍入一个SHMLBA的常值。
4,flag被指定为SHM_RDONLY的时候,它只能只读访问。
此函数通过ctl后缀,就可以猜出它的作用,它提供对共享内存区的多种操作。
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buff)
1,IPC_RMID 从系统中删除由shmid标识的共享内存区并拆除它。
2,IPC_SET 给指定的共享内存区设置其shmid_ds结构的以下三个成员:shm_perm.uid , shm_perm.gid , shm_perm.mode, 它们的值来自于buff参数指向的结构中的相应成员。
3,IPC_STAT 向调用者返回指定共享内存区当前的shmid_ds结构。(ps:必须也是通过最后一个参数buff)
当一个进程完成某一个共享内存区的使用时,可以调用shmdt断接这个内存区。
#include <sys/shm.h>
int shmdt(const void * shmaddr)
相关代码
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
int id;
//没有采用ftok返回,直接采用IPC_PRIVATE返回唯一的IPC对象
id = shmget(IPC_PRIVATE, shm->size,
//可读可写可创建可引用,不过已经指定IPC_PRIVATE,相当于创建一个新的,拥有可读可写权限
(SHM_R|SHM_W|IPC_CREAT));
//shmget出错将返回-1
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将其附加到应用进程地址,由于shmaddr指定为NULL,那么将由系统为调用者选择地址。
shm->addr = shmat(id, NULL, 0);
if (shm->addr == (void *) -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed");
}
//拆除内存共享区
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;
相信各位看官都会有疑问,为何刚刚申请好的共享内存,却要“拆除”。
1,首先,我得普及一下知识,System v的内存共享区的生命周期是随内核的,也就是说共享内存不会随着程序结束而自动消除,要么我们调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。
2,其次,IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,拆除操作要等到指定共享内存区的引用计数变为0才会进行,所以再拆除以后,我们还可以使用。
3,最后当某个shmdt调用时,发现所制定的共享内存区的引用计数变为0,也顺便拆除它,这就是shmctl的IPC_RMID命令先于最后一个shmdt调用时会发生情形。
最后附上free代码压压惊,free代码比较简单,调用shmdt就ok了。
void
ngx_shm_free(ngx_shm_t *shm)
{
if (shmdt(shm->addr) == -1) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"shmdt(%p) failed", shm->addr);
}
}
#endif