36-System V——创建共享内存

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)

  1. 如果key = IPC_PRIVATE(IPC_PRIVATE宏值为0),表示创建一个新的IPC对象并返回其id值
  2. 如果key不等于0,创建或者获取IPC对象的id号(创建还是获取由参数shmflg决定)
  3. 还可以通过ftok函数来随机生成一个键(具体使用看创建共享内存方式)

参数size: 指定创建共享内存的大小(size只有在创建IPC对象的时候才有效),这个是系统分配的一个物理页内存,默认为4k(可修改)。

参数shmflg : 可选项参数

  1. 如果shmflg = IPC_CREAT,内核会将shmget函数传入的key与内核中的共享内存中的key进行对比,如果不存在相同的key则创建新的IPC对象。如果存在相同的key,但未指定IPC_EXCL,则说明IPC对象已经存在,此时就返回IPC对象的id。
  2. IPC_EXCL总是和IPC_CREAT一起使用,如果shmflg = IPC_CREAT | IPC_EXCL,如果IPC对象已经存在就返回出错,设置errno = EEXIST。
  3. 权限位(读写权限),如果是创建新的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值不相同的话,这里提供有两种方法:

  1. 判断生成的key和传入的key值是否相同,如果相同,则对生成的key值+1
  2. 也使用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值