1. System V共享存储
所谓共享存储就是允许两个或多个进程共享物理内存的同一存储区,进程在访问这一内存空间时,因为数据不需要在进程间复制,所以效率比较高。
但这是有代价的,由于内核没有对访问共享内存进行同步,必须提供自己的同步措施,比如有进程在对存储区写入数据之前,不允许其他进程对其进行读写操作(这里我们暂时不讨论同步问题)。
2. 操作共享内存步骤
共享内存操作函数:shmget,shmat,shmdt,shmctl
1. 首先我们需要调用shmget函数来创建一块共享内存,然后将该共享内存的内核标识符转换为外键并返回,获取共享内存也可以通过shmget函数来完成。
2. 如果进程想要访问该共享内存的话,必须把进程空间的虚拟地址映射到该共享内存的物理地址,可以调用shmat函数来完成挂接。
3. 当某个进程不再访问该共享内存的话,可以通过shmdt函数取消该进程对共享内存的挂接。
4. 最后在释放内存的时候,可以调用shmctl函数删除这块共享内存。需要注意的是:如果当前有其它进程在使用共享内存时,最好不要调用shmctl函数删除共享内存。
3. 使用shmget函数创建和获取IPC对象
shmget函数用于创建一个共享内存区,或者访问一个已存在的共享内存区,新创建的共享内存区域中的内容会初始化为0。
#include<sys/ipc.h>
#include<sys/shm.h>
#include <sys/types.h>
int shmget(key_t key, size_t size, int shmflg );
返回值说明:成功返回IPC对象的标识符shmid,错误时返回-1。
参数key : 进程间约定好的键(key)
- 如果key = IPC_PRIVATE(IPC_PRIVATE宏值为0),表示创建一个新的IPC对象并返回其id值
- 如果key不等于0,创建或者获取IPC对象的id号(创建还是获取由参数shmflg决定)
- 还可以通过ftok函数来随机生成一个键(具体使用看创建共享内存方式)
参数size: 指定创建共享内存的大小(size只有在创建IPC对象的时候才有效),这个是系统分配的一个物理页内存,默认为4k(可修改)。
参数shmflg : 可选项参数
- 如果shmflg = IPC_CREAT,内核会将shmget函数传入的key与内核中的共享内存中的key进行对比,如果不存在相同的key则创建新的IPC对象。如果存在相同的key,但未指定IPC_EXCL,则说明IPC对象已经存在,此时就返回IPC对象的id。
- IPC_EXCL总是和IPC_CREAT一起使用,如果shmflg = IPC_CREAT | IPC_EXCL,如果IPC对象已经存在就返回出错,设置errno = EEXIST。
- 权限位(读写权限),如果是创建新的IPC对象,flags 还需要位或IPC对象的权限位,比如 0664。
如果想要获取已存在的内核对象,需要指定该对象的key,其他参数都为0即可。
//获取key值指定的共享内存IPC对象
shmget(key , 0 , 0);
4. shmget函数需要注意的几点
1.在创建共享内存指定shmget函数的size参数时,size必须大于0,因为第一次调用shmget函指定参数size = 0是获取共享内存,但是此时并没有创建共享内存,所以直接调用shmget函数且size参数设置为0的话,那么shmget函数会调用失败。
//因此,在第一次创建时应该指定size参数应该大于0。
if(size > 0){
//创建共享内存
}
2. 对于已经创建的共享内存,调用shmget函数使用相同的key获取共享内存时,size只能调小,而不能调大。
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(void)
{
key_t key = 0x112233;
int id = 0;
//创建共享内存页大小为8192
id = shmget(key, 8192, IPC_CREAT | 0644);
if(id < 0){
puts("shmget 8192 error:");
}
//不会出错
id = shmget(key, 4096, IPC_CREAT | 0644);
if(id < 0){
puts("shmget 4096 error");
}
//会出错
id = shmget(key , 10240 , IPC_CREAT | 0664);
if(id < 0){
puts("shmget 10240 error");
perror("shmget error:");
}
return 0;
}
程序执行结果:
从程序的执行结果来看,第三次调用shmget出错了
可以使用ipcs -m命令查看创建的共享内存:
当该共享内存有多个进程访问的时候,如果把共享内存的大小调小后,这可能会造成第一个进程的内存不足,导致程序崩溃,所以共享内存一旦创建好之后就不要修改了。为了解决这个问题,可以在调用shmget函数时指定IPC_EXCL选项来完成。
shmget(key , 10240 , IPC_CREAT | IPC_EXCL 0664);
5. 关于使用ftok的注意点
通常只要给出文件路径或文件名,proj_id序列号不变,那么key值是不变的。
考虑这么一种情况:多个进程间在访问共享内存时可能会多次调用ftok函数,如果path参数指定的文件或文件目录被删除,或者U盘等设备被卸载,然后再重新创建。
这时文件系统会分配一个inode号(这个inode号极有可能是一个新的inode号,不排除系统会分配原来的inode号,但是这个几率实在是太低了,这运气好到可以买彩票了,开个玩笑),可能会改变文件的inode号,根据ftok算法的原理,如果进程调用ftok函数得到的key值不一样,那么进程间映射的不是同一共享内存,因此这是个非常严重的错误,会导致无法实现进程间通信。
如果要保证key值不变的话,要么不使用ftok,手动指定key值,要么保证文件不被删除。在通知其他进程挂接的时候,建议不使用ftok方式来获取Key,而使用文件或者进程间通信的方式告知,而在实际工程中用进程间通信的方式会比较多。
如果进程每次创建新的IPC对象,需要保证ftok函数每次产生的key值不相同的话,这里提供有两种方法:
- 判断生成的key和传入的key值是否相同,如果相同,则对生成的key值+1
- 也使用mv命令改变文件名或文件目录
以第一种方法为例:
//生成不同的key
int create_key(key_t key2) {
key_t key1 = ftok("." , 'a');
if (key1 == -1) {
perror("ftok");
return -1;
}
printf("key1 = 0x%08x\n", key1);
//生成不同的key
if(key1 == key2){
key1 += 1;
}
printf("key1 = 0x%08x\n" , key1);
return 0;
}
关于ftok函数的详细用法参考:55-键值与 ftok