共享内存实践
前言
工作中需要使用共享内存,学习一下。
环境:centos7、C
一、系统V共享内存
1.共享内存实现进程间通信
简单来说,共享内存的实现就是一个物理内存被映射到多个进程的地址空间。映射该共享内存的进程可以对共享内存进行直接内存存取对数据进行修改访问,从而实现不同进程之间的数据通信。好处是实现简单,效率高,不需要数据拷贝。
二、共享内存的C语言实现
1.shmget
创建或获取一块共享内存,返回共享内存的标识shmid。
int shmget(key_t key, size_t size, int shmflg)
根据key值获取或创建一个共享内存。
size指定了共享内存区块大小。
shmflg:创建时用 IPC_CREAT|xxxx;第一位表示进制数,第二位表示当前用户的权限,第三位表示用户组权限,第四位表示其他用户权限。
权限说明:7可读可写可执行,对应rwx,6可读可写,对应rw-,4表示可读,对应r–。
常用的有0666,0644
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1.
2.shmmat
将共享内存映射到进程的地址空间,此时共享内存的attach数加一。
void *shmat(int shmid, const void *shmaddr, int shmflg)
shmid:共享内存标识
shmaddr:指定连接地址,NULL则让内核自动选择合适的地址。
shmflg:SHM_RND(取整对齐)或SHM_RDONLY(只读),其他为(可读可写)
返回值: 成功返回一个指针,指向共享内存第一个字节;失败返回-1.
进程通过返回的指针向共享内存写入或读取数据。
3.shmdt
将共享内存与当前进程脱离,此时共享内存的attach数减一。
int shmdt(const void *shmaddr)
返回值: 成功返回0,失败返回-1.
4.shmctl
用于控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid: 由shmget返回的共享内存标识码
cmd:将要采取的动作(由三个可取的值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值: 成功返回0,失败返回-1.
目前我主要使用shmctl(shmid,IPC_RMID,NULL)来指定删除共享内存。
三、共享内存注意点
1.shmctl删除共享内存
如果是多个进程装载了同一个共享内存,其中有一个进程执行了shmctl(shmid,IPC_RMID,NULL)删除了共享内存,则该段共享内存被标记为dest。如果所有进程解除了对该段共享内存的连接,即attach为0,共享内存才真正删除。
2.key值创建的共享内存标记为dest后,又有进程创建了该key值的共享内存怎么办
共享内存标记为dest后,只要attach不为0就不会真正删除。此时,连接该段共享内存的进程仍然可以互相通信。再次使用相同key值创建共享内存的进程会创建一个新的shmid的共享内存,不能与原来的key创建的共享内存通信。
3.共享内存不使用shmctl删除
共享内存不使用shmctl删除,这样即使连接数attach为0,该共享内存仍然存在,下一次连接仍然可以使用。事实上,共享内存可以通过ipcs -m shmid统一删除。
四、共享内存实现
1.代码实现
光说不练假把式,写了个小demo测试一下
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#define SHM_SIZE 1024
int main()
{
int shmid = -1;
int key = 10;
int size = 1024;
shmid = shmget(key,size,IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget");
exit(1);
}
//映射
char *ptr = NULL;
ptr = shmat(shmid,NULL,0);//0表示共享内存可读可写
if(ptr == (void *)-1)
{
perror("shmat");
exit(1);
}
while(1)
{
char sym;
printf("enter r/w/d to read or write or delete the shared memory or e to exit this process:\n");
scanf(" %c",&sym);//%c前有空格,因为scanf会将回车保存在缓冲区,使用空格可以识别并丢弃一个结束字符,而普通的字符不受影响
if(sym=='e')
{
break;
}
if(sym=='r')
{
//读数据
if(ptr[0] != '\0')
{
printf("ptr = %s\n",ptr);
}
continue;
}
if(sym=='w')
{
//必须要手动清空
memset(ptr,0,size);//清空内存
char buf[1024];
memset(buf,0,sizeof(buf));
printf("input the context:");
scanf("%s",&buf);
memcpy(ptr,buf,strlen(buf));
continue;
}
if(sym=='d')
{
if(shmctl(shmid,IPC_RMID,NULL) == -1)
{
perror("shmctl");
exit(1);
}
}
if(sym!='r'&&sym!='w'&&sym!='d')
{
printf("unknown command!\n");
continue;
}
}
//解除共享内存映射
if(shmdt(ptr) < 0)
{
perror("shmdt");
exit(1);
}
return 0;
}
2.测试1
开启两个进程互相通信,权限0666,shmid为327721,大小1024,连接数2,当前用户xd
3.测试2
其中一个执行删除操作,发现并不影响所有进程继续使用共享内存,除非退出连接。