一 什么是共享内存
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,因为数据不需要在客户机和服务器之间复制,所以这是最快的一种 I P C。
特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。
二 共享内存函数使用
当两个或多个无亲源关系的进程使用某种类型的IPC对象来彼此交换信息,该IPC对象必须有一个某种形式的名字(name) 或标识符(identifier)。这样服务端创建了IPC对象,其余进程可以根据某种形式的名字或标识符指定同一个IPC对象。而共享内存也必须有标识符标识特定的IPC对象。这里shmget返回的值就是指向特定的IPC对象。
1.ftok 函数
key_t ftok(const char * pathname,int id);
ftok函数会把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键,创建的key_t参数可作为shmget()参数一使用。如果pathname 路径不存在,ftok 返回-1;同时pathname 的文件不能是服务器反复创建并删除的,因为每次创建后系统赋予的索引结点号很可能就变了,这会导致ftok返回的键值不太
2.shmget()函数
int shmget(key_t key, size_t size, int shmflg);
创建或者打开一个IPC对象。
参数一:key_t key 可通过ftok获取 ,也可指定IPC_PRIVATE,这会保证创建一个新的,唯一的IPC对象
参数二:size以字节为单位指定需要共享的内存容量
参数三:shmflg是权限标志,它的作用与open函数的mode参数一样,
- shmflg设置为IPC_CREAT但不设置它的IPC_EXCL,如果IPC对象不存在,则创建一个新的IPC对象,否则会返回该已经存在的IPC对象
- 同时设置shmflg为IPC_CREAT|IPC_EXCL,如果IPC对象不存在,则创建一个新的IPC对象,否则返回EEXIST错误,因为该对象已经存在
返回值:成功返回一个非负整数,即该共享内存段的标识符;失败返回-1;
下图展示打开一个IPC对象的逻辑流程
3.shmat()函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,共享存储段连接到调用进程的哪个地址上与 addr参数以及在flag中是否指定SHM_RND位
有关。
(1) 如果addr为0,则此段连接到由内核选择的第一个可用地址上。
(2) 如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
(3) 如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr mod SHMLBA))
所表示的地址上。SHM_RND命令的意思是:取整。SHMLBA的意思是:低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数.如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。shmat的返回值是该段所连接的实际地址,如果出错则返回- 1。
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
4.shmdt()函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用,最终删除得通过shmctl()函数的IPC_RMID标志
int shmdt(const void *shmaddr);
返回值:成功返回0; 失败返回-1;
5.shmctl()函数
控制共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数,shm_id是shmget()函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即将共享内存的当前关联值赋值给buf结构体中
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
SHM_LOCK 锁住共享内存区 超级用户执行
SHM_UNLOCK 解锁共享内存区 超级用户执行
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。结构体如下图
返回值:成功返回0; 失败返回-1;
三 共享内存的进程间通信
下面通过一个例子,一个进程创建共享内存,然后往里面写数据
另一个进程根据SHM_KEY获取创建的共享内存,读里面的数据
进程shmwrite
/*
* shmwrite.c
*
* Created on: 2019-5-7
* Author: root
*/
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "vg_shm.h"
typedef struct _channel_info_t {
int chl_id;
char buf[128];
} audit_info_t;
#define SHM_KEY 1357
int main(void)
{
vg_shm_t *shm;
if (vg_shm_init(&shm, SHM_KEY, sizeof(audit_info_t), 30) < 0) {
return -1;
}
vg_shm_clear(shm);
audit_info_t chl_info ;
int i ;
for(i = 0;i<5;i++){
memset(&chl_info,0,sizeof(audit_info_t));
chl_info.chl_id = i +10;
sprintf(chl_info.buf,"shm write pid:%d >>> id:%d",getpid(),i);
printf("-----------write id:%d\n buffer:%s\n",chl_info.chl_id,chl_info.buf);
vg_shm_insert(shm,&chl_info);
}
return 1;
}
上面中自己对shm共享内存封装了一层,后面会将封装vg_shm.h实现代码贴出来
大概就是创建了一块struct audit_info_t 结构体大小 * 30的共享内存空间,
然后往里面写数据
运行结果如下
进程shmread
/*
* shmread.c
*
* Created on: 2019-5-7
* Author: root
*/
#include <stdio.h>
#include <unistd.h>
#include "vg_shm.h"
typedef struct _channel_info_t {
int chl_id;
char buf[128];
} audit_info_t;
#define SHM_KEY 1357
int main(void)
{
vg_shm_t *shm;
if (vg_shm_init(&shm, SHM_KEY, sizeof(audit_info_t), 30) < 0) {
return -1;
}
vg_shm_iterator_t mit;
audit_info_t *chl_info =vg_shm_get_first(shm,&mit);
while(chl_info){
printf("shmread id:%d >>>> buffer:%s\n",chl_info->chl_id,chl_info->buf);
chl_info = vg_shm_get_next(&mit);
}
return 1;
}
对shm封装了个迭代器,根据迭代能将共享内存中所有的struct audit_info_t 数据获取,并打印
运行结果如下
可以发现整个流程没有问题,shmwirte写入的数据,shmread 都正确的读出来了
封装的vg_shm
vg_shm.h
#ifndef VG_SHM_H_
#define VG_SHM_H_
typedef struct _vg_shm_t {
void *shm_addr;
int unit_size;
int max_unit_num;
} vg_shm_t;
typedef struct _vg_shm_iterator_t {
vg_shm_t *shm;
void *cur_unit;
} vg_shm_iterator_t;
int vg_shm_init(vg_shm_t **shm, int key, int unit_size, int max_unit_num);
void *vg_shm_insert(vg_shm_t *shm, void *unit);
void vg_shm_clear(vg_shm_t *shm);
void vg_shm_close(vg_shm_t *shm);
void *vg_shm_get_first(vg_shm_t *shm, vg_shm_iterator_t *it);
void *vg_shm_get_next(vg_shm_iterator_t *it);
void vg_shm_iterator_remove(vg_shm_iterator_t *it);
#endif /* VG_SHM_H_ */
vg_shm.c
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
/**
*
* @param shm
* @param key
* @param unit_size
* @param max_unit_num
* @return
*/
int vg_shm_init(vg_shm_t **shm, int key, int unit_size, int max_unit_num) {
if (shm == NULL || unit_size <= 0 || max_unit_num <= 0) {
vg_errno = VG_ERROR_BADARG;
return -1;
}
int shm_fd = shmget(key, unit_size * max_unit_num, 0666 | IPC_CREAT);
void *shm_addr = shmat(shm_fd, NULL, 0);
if (shm_addr == (void *) -1) {
vg_errno = VG_ERROR_BADDR;
return -1;
}
*shm = (vg_shm_t *) malloc(sizeof(vg_shm_t));
(*shm)->shm_addr = shm_addr;
(*shm)->unit_size = unit_size;
(*shm)->max_unit_num = max_unit_num;
vg_errno = VG_ERROR_SUCCESS;
return 0;
}
/**
*
* @param ptr
* @param size
* @return
*/
static int is_empty(void *unit, int size) {
int retval = 1, i;
for (i = 0; i < size; ++i) {
if (*(char *) (unit + i) != 0) {
retval = 0;
break;
}
}
return retval;
}
/**
*
* @param shm
* @param unit
* @return
*/
void *vg_shm_insert(vg_shm_t *shm, void *unit) {
if (shm == NULL || unit == NULL) {
vg_errno = VG_ERROR_BADARG;
return NULL;
}
int i;
void *ptr = shm->shm_addr;
for (i = 0; i < shm->max_unit_num; ++i) {
if (is_empty(ptr, shm->unit_size)) {
memcpy(ptr, unit, shm->unit_size);
vg_errno = VG_ERROR_SUCCESS;
return ptr;
}
ptr += shm->unit_size;
}
vg_errno = VG_ERROR_NOBUF;
return NULL;
}
/**
*
* @param shm
*/
void vg_shm_clear(vg_shm_t *shm) {
if (shm == NULL) {
vg_errno = VG_ERROR_BADARG;
return;
}
memset(shm->shm_addr, 0, shm->unit_size * shm->max_unit_num);
vg_errno = VG_ERROR_SUCCESS;
}
/**
*
* @param it
* @return
*/
static void *get_available_unit(vg_shm_iterator_t *it) {
int shm_size = it->shm->max_unit_num * it->shm->unit_size;
while (it->cur_unit < it->shm->shm_addr + shm_size) {
if (!is_empty(it->cur_unit, it->shm->unit_size)) {
return it->cur_unit;
}
it->cur_unit += it->shm->unit_size;
}
return NULL;
}
/**
*
* @param shm
* @param it
* @return
*/
void *vg_shm_get_first(vg_shm_t *shm, vg_shm_iterator_t *it) {
if (shm == NULL || it == NULL) {
vg_errno = VG_ERROR_BADARG;
return NULL;
}
it->shm = shm;
it->cur_unit = shm->shm_addr;
vg_errno = VG_ERROR_SUCCESS;
return get_available_unit(it);
}
/**
*
* @param it
* @return
*/
void *vg_shm_get_next(vg_shm_iterator_t *it) {
if (it == NULL) {
vg_errno = VG_ERROR_BADARG;
return NULL;
}
int shm_size = it->shm->max_unit_num * it->shm->unit_size;
if (it->cur_unit >= it->shm->shm_addr + shm_size) {
return NULL;
}
it->cur_unit += it->shm->unit_size;
vg_errno = VG_ERROR_SUCCESS;
return get_available_unit(it);
}
/**
*
* @param it
*/
void vg_shm_iterator_remove(vg_shm_iterator_t *it) {
if (it == NULL) {
vg_errno = VG_ERROR_BADARG;
return;
}
int shm_size = it->shm->max_unit_num * it->shm->unit_size;
if (it->cur_unit >= it->shm->shm_addr + shm_size) {
vg_errno = VG_ERROR_INVALID;
return;
}
memset(it->cur_unit, 0, it->shm->unit_size);
it->cur_unit += it->shm->unit_size;
get_available_unit(it);
vg_errno = VG_ERROR_SUCCESS;
}
/**
*
* @param shm
*/
void vg_shm_close(vg_shm_t *shm) {
if (shm == NULL) {
vg_errno = VG_ERROR_BADARG;
return;
}
shmdt(shm->shm_addr);
free(shm);
vg_errno = VG_ERROR_SUCCESS;
}
四 shm共享内存注意点
- 对共享内存的操作,多个进程同时用到共享内存时要注意同步问题,就需要加锁,或者通过信号量实现同步。
- shmctl 中可设置标志SHM_LOCK ,会有个误区会让人认为shm提供了共享内存的同步,其实SHM_LOCK并不是影响访问性,设置SHM_LOCK 只是让被锁定的区段不允许被交换出物理内存(否则它可能被交换到虚拟内存中,swap分区)。如果不设置的话一般情况下,物理内存是有限的,内核会有专门调度算法,对物理内存中不常用的,就会被置换出去,或者交换技术,将进程内的用的全部换到磁盘上(或者swap分区中),而当某个应用程序调用时再交换回到物理内存,而设置SHM_LOCK 锁定后只是不会交换到磁盘(或者swap 分区)。
- shmget() 创建或者获取共享内存的时候,当两个不同的人开发对共享内存的操作时,要注意两点
1.shmget设置的size值,不能大于最大值SHMMAX和小于最小值SHMMIN.
2.如果key值对应的段已经存在,那么后来使用shmget的size值要小于等于原来的值。否则当一方已经创建了共享内存对象,设置了size 10kb ,而你也调用shmget 去获取创建的内存,但是size的值 15kb 就会不成功。因此建议如果无法确定两个人的进程谁先创建共享内存对象。要商量好size的值。两人用一样的size创建
最后如果有兴趣想了解共享内存跟mmap的区别。可以查看楼主先前写的UNIX 环境编程 之 内存映射中最后一节内容