最初的设计:
有A、B、C三个进程使用共享内存shm_file。
在进程拉起时按照A->B->C的顺序拉起。
A进程使用
#define SHM_SIZE 1000
app_init()
{
int shm_id =0;
shm_unlink("/shm_file");
shm_id = shm_open("/shm_file", O_CREAT | O_EXCL |O_RDWR, 0644);
if(shm_id < 0)
{
printf("create error\n");
return -1;
}
if(0 != ftruncate(shm_id, SHM_SIZE ))
{
printf("ftruncate error\n");
return -1;
}
char addr *=mmap(0, SHM_SIZE, PORT_READ |PORT_WRITE,MAP_SHARED,shm_id,0);
if(addr == (void*)-1)
{
close(shm_id);
return -1;
}
close(shm_id);
}
B 和C使用共享内存:
lib_init()
{
int shm_id =0;
shm_id = shm_open("/shm_file", O_RDWR, 0644);
if(shm_id < 0)
{
printf("create error\n");
return -1;
}
char addr *=mmap(0, SHM_SIZE, PORT_READ |PORT_WRITE,MAP_SHARED,shm_id,0);
if(addr == (void*)-1)
{
close(shm_id);
return -1;
}
close(shm_id);
}’
这样的使用方式有两个局限:
(1)A进程虽然最先启动,但是A进程还没运行到app_init()的时候B进程先运行了lib_init(),并且往共享内存中写入数据,之后A进程才运行app_init(), 这时候使用shm_unlink把共享内存的数据删掉了,导致数据丢失。–这个也许可以通过信号量来解决时序问题。
(2)在不同是设备上部署的是A、B、C的不同排列组合。比如在设备类型1中可能部署A和B,在设备类型2中部署B和C,在设备类型3中部署A和C,但是代码是共用一套,无法固定哪个进程来使用app_init—除非使用宏隔开。
为了解决上述问题,让进程A、B、C使用相同的共享内存初始化接口lib_init(),主要修改点在于第一个启动的进程创建共享内存,其他的进程使用共享内存:
lib_init()
{
int shm_id =0;
shm_id = shm_open("/shm_file", O_CREAT | O_EXCL |O_RDWR, 0644);
if(shm_id < 0)
{
shm_id = shm_open("/shm_file", O_RDWR, 0644);if(shm_id < 0)
{
printf("create error\n");
return -1;
}
}
else
{
if(0 != ftruncate(shm_id, SHM_SIZE ))
{
printf("ftruncate error\n");
return -1;
}
}
char addr *=mmap(0, SHM_SIZE, PORT_READ |PORT_WRITE,MAP_SHARED,shm_id,0);
if(addr == (void*)-1)
{
close(shm_id);
return -1;
}
close(shm_id);
}’
但是上述方法还存在问题:A, B和C,第一个启动的进程会创建共享内存并使用ftruncate设置共享内存大小,后面的进程判断共享内存已创建就会直接mmap并使用共享内存,但是当前共享内存可能正在被第一个进程ftruncate,导致后面的进程在使用共享内存时出现Bus error并退出。
3.针对上述问题,加上了信号量的使用:sem_wait()/sem_post()/sem_create()是信号量的实现
lib_init()
{
int shm_id =0;
shm_id = shm_open("/shm_file", O_CREAT | O_EXCL |O_RDWR, 0644);
if(shm_id < 0)
{
shm_id = shm_open("/shm_file", O_RDWR, 0644);if(shm_id < 0)
{
printf("create error\n");
return -1;
}
}
sem_wait();
else
{
sem_create();
if(0 != ftruncate(shm_id, SHM_SIZE ))
{
printf("ftruncate error\n");
return -1;
}
sem_post();
}
char addr *=mmap(0, SHM_SIZE, PORT_READ |PORT_WRITE,MAP_SHARED,shm_id,0);
if(addr == (void*)-1)
{
close(shm_id);
return -1;
}
close(shm_id);
}’
本以为能完美的解决问题,没想到还有绝杀,有个服务在启动时rm /dev/shm/*,shm_open()创建的文件就在这个目录下,这就导致A、B、C已经正常运行了,然后被执行了rm /dev/shm/*之后,如果有个D进程也使用了lib_init()接口(D是维测进程,为了打印出共享内存中的维测信息),这时候D会认为共享内存没创建,重新创建共享内容,这样A、B、C写入的数据都没有了。
尴尬的是shm_open()创建的文件只能在/dev/shm下,尝试使用文件链接的方式,但是/dev/shm下的东西被清空,共享内存还是用不了。
4.使用另一种共享内存接口
可以指定key或者随机生成key
随机生成key,示例:
#define
char filename[128] = {0};
snprintf(filename, 128,"/tmp/myshm");
FILE *file = fopen(filename, "w");
if(!file)
{
printf("open file");
return -1;
}
key_t key = ftok(pathname,66);
if(key == -1){
printf("ftok failed");
return -1;
}
也可以指定key
#define SHM_SIZE 1000
lib_init()
{
char *addr = NULL;
int shmid = shmget(MEM_KEY, SHM_SIZE , 0666 | IPC_CREAT | IPC_EXCL);
if(shmid > 0)
{
addr = shmat(shmid,NULL,0);
if(addr == (void*)-1)
{
goto shmat_err;
}
memset(addr,0,SHM_SIZE );
}
else if(shmid == -1 && errno == EEXIST)
{
printf("key already exist\n");
shmid == shmget(MEM_KEY, SHM_SIZE, 0666);
if(shmid < 0)
goto shmget_err;
addr = shmat(shmid, NULL,0);
if(addr == (void*)-1)
{
goto shmat_err;
}
}
else
{
goto shmat_err;
}
return 0;
shmget_err:
return -1;
shmat_err:
return -1;
}
这种方式创建的共享内存在文件系统上没有对应的文件,但是也防不住通过在脚本中ipcs -m 查看系统共享内存信息,然后通过ipcrm -M key的方式删除共享内存,删除之后代码读出来的也是空的。
5、还是使用open()+mmap()的方式。
lib_init()
{
int shm_id =0;
shm_id = open("/tmp/shm_file", O_CREAT | O_EXCL |O_RDWR, 0644);
if(shm_id < 0)
{
shm_id = open("/tmp/shm_file", O_RDWR, 0644);if(shm_id < 0)
{
printf("create error\n");
return -1;
}
}
sem_wait();
else
{
sem_create();
if(0 != ftruncate(shm_id, SHM_SIZE ))
{
printf("ftruncate error\n");
return -1;
}
sem_post();
}
char addr *=mmap(0, SHM_SIZE, PORT_READ |PORT_WRITE,MAP_SHARED,shm_id,0);
if(addr == (void*)-1)
{
close(shm_id);
return -1;
}
close(shm_id);
}’
open()可以指定文件在任何目录,但是shm_open的文件一定在/dev/shm目录,open()方式和shm_open()有什么区别呢?一下内容来自ds的回答:
我理解是如果是进程间要共享数据,使用open+mmap或者shm_open+mmap都可以,但是如果是自己一个进程存储临时数据要用到匿名内存,就用shm_open