• Linux进程通信之System V共享内存


    前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而System V共享内存区则是调用shmget创建共享内存区然后调用shmat进行内存区的映射。

    对每个System V共享内存区,内核会维护一个shmid_ds的数据结构,Linux 2.6.18 中的定义如下:

    <bits/shm.h>
    
    /* 连接共享内存区的进程数的数据类型 */
    typedef unsigned long int shmatt_t;
    
    struct shmid_ds
    {
        struct ipc_perm shm_perm;           /* operation permission struct */
        size_t shm_segsz;                   /* 共享存储段的最大字节数 */
    
        __time_t shm_atime;                 /* time of last shmat() */
        __time_t shm_dtime;                 /* time of last shmdt() */
        __time_t shm_ctime;                 /* time of last change by shmctl() */
    
        __pid_t shm_cpid;                   /* pid of creator */
        __pid_t shm_lpid;                   /* pid of last shmop */
    
        shmatt_t shm_nattch;                /* 连接共享内存区的进程数 */
    
    //保留字段
    #if __WORDSIZE == 32
        unsigned long int __unused1;
    	unsigned long int __unused2;
    	unsigned long int __unused3;
    #endif
        unsigned long int __unused4;
        unsigned long int __unused5;
    };

    1 System V共享内存区的创建和打开

    下面是shmget函数的接口以及说明:

    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
                         //成功返回共享内存标识符,失败返回-1

    shmget函数用于创建或打开一个共享内存区对象,shmget成功调用会返回一个共享内存区的标识符,供其它的共享内存区操作函数使用。

    key:用于创建共享内存区的键值,这个在前面其他System IPC创建的时候已经讨论过了,System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。key具体说可以有三种方式生成:

    • 不同的进程约定好的一个值;
    • 通过相同的路径名和项目ID,调用ftok()函数,生成一个键;
    • 还可以设置为IPC_PRIVATE,这样就会创建一个新的,唯一的IPC对象;然后将返回的描述符通过某种方式传递给其他进程;

    size:指定创建共享内存区的大小,单位是字节。如果实际操作为创建一个共享内存区时,必须指定一个非0值,如果实际操作是访问一个已存在的共享内存区,那么size应为0

    shmflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREATIPC_EXCL。读写权限如下图:


    1 System V共享内存区的读写权限标志

    System V共享内存区在创建后,该size大小的内存区会被初始化为0这和POSIX共享内存不同,POSIX标准并没有规定新创建的POSIX共享内存区的初始内容

    2 System V共享内存区的连接和断接

    通过shmctl创建或打开共享内存区对象后,并没有将该共享内存区映射到调用进程的地址空间中,所以无法访问该共享内存区,需要通过shmat函数,将该共享内存区连接到调用进程的地址空间中,才能进行访问,当该进程完成对该共享内存区的访问后,可以调用shmdt断接这个共享内存区,当然进程结束后会自动断接所有连接的共享内存区。下面是shmatshmdt函数的接口以及说明:

    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
                            //成功返回映射区的起始地址,失败返回-1
    int shmdt(const void *shmaddr);
                            //成功返回0,失败返回-1

    shmat用于将一个共享内存区连接到调用进程的地址空间中。

    shmid:打开的System V共享内存对象的标识符;

    shmaddrshmflg参数共同决定了共享内存区连接到调用进程的具体地址,规则如下:

    • shmaddr为空指针:连接的地址由系统内核决定,这是推荐的方法,具有可移植性
    • shmaddr非空:此时还要根据shmflg参数是否指定SHM_RND标志进行判断:
      • 没有指定SHM_RND:共享内存区连接到调用进程的shmaddr指定的地址;
      • 指定SHM_RND:共享内存区连接到shmaddr指定的地址向下舍入SHMLBA的位置。

    shmflg:除了上面说的SHM_RND外,还有可以指定SHM_RDONLY标志,限定只读访问。一般该标志置为0

    shmdt用于将一个共享内存区从该进程内断接,当一个进程终止时,它连接的所有共享内存区会自动断接。

    3 System V共享内存区的控制操作

    shmctl函数可以对共享内存区进行多种控制操作,下面是shmctl函数的接口以及说明:

    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                         //成功返回0,失败返回-1

    shmid:共享内存区的标识符。

    cmd:对共享内存区控制操作的命令,Open Group SUS定义了一下三个操作命令:

    • IPC_SET:按第三个参数buf所指定的内容,设置共享内存区的操作权限字段的:shm_perm.uidshm_perm.gidshm_perm.mode。此命令只能由以下进程执行:有效用户ID等于shm_perm.uidshm_perm.cuid,以及有超级用户权限的进程。
    • IPC_STAT:获取共享内存区的shmid_ds结构,存放到传入的第三个参数中。
    • IPC_RMID:从系统内核中删除该共享内存区。因为每个共享内存区有一个连接数据的计数,除非连接该共享内存区的最后一个进程断接或终止,否则该共享内存区不会被实际删除。这和其他的System V IPC,例如System V消息队列的删除差别很大,倒是和POSIX IPCxxx_unlink删除操作很相识。调用该命令后,该共享内存区标识符不能再继续被连接。此命令也只能由以下进程执行:有效用户ID等于shm_perm.uidshm_perm.cuid,以及有超级用户权限的进程。

    Linux中还定义了其他的控制命令,如:IPC_INFOSHM_INFOSHM_LOCKSHM_UNLOCK等,具体可以参考Linux手册。

    下面是创建System V共享内存区和查看其属性的测试代码:

    #include <iostream>
    #include <cstring>
    #include <errno.h>
    
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/shm.h>
    
    using namespace std;
    
    #define PATH_NAME "/tmp/shm"
    
    int main()
    {
        int fd;
    
        if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
        {
            cout<<"open file "<<PATH_NAME<<"failed.";
            cout<<strerror(errno)<<endl;
            return -1;
        }
    
        close(fd);
    
        key_t key = ftok(PATH_NAME, 0);
    
        int shmID;
        
        if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
        {
            cout<<"shmget failed..."<<strerror(errno)<<endl;
            return -1;
        }
    
        shmid_ds shmInfo;
        shmctl(shmID, IPC_STAT, &shmInfo);
    
        cout<<"shm key:0x"<<hex<<key<<dec<<endl;
        cout<<"shm id:"<<shmID<<endl;
        cout<<"shm_segsz:"<<shmInfo.shm_segsz<<endl;
        cout<<"shm_nattch:"<<shmInfo.shm_nattch<<endl;
    
        return 0;
    }

    执行结果如下:

    shm key:0x80e7
    shm id:5898284
    shm_segsz:4		//共享内存区的大小
    shm_nattch:0	//共享内存区的连接数目

    通过ipcs命令查看该新创建的共享内存区对象:

    [root@idcserver program]# ipcs -m -i 5898284
    Shared memory Segment shmid=5898284
    uid=0   gid=0   cuid=0  cgid=0
    mode=0666       access_perms=0666
    bytes=4 lpid=0  cpid=16422      nattch=0
    att_time=Not set                   
    det_time=Not set                   
    change_time=Tue Aug 13 15:34:35 2013 

    4 System V共享内存区的限制

    和其中的System V IPC一样,System V共享内存也存在系统的限制,关于系统范围内对共享内存的限制,在Linux 2.6.18 <bits/shm.h>中定义了shminfo结构,该结构显示了系统内核的限制,如下:

    #include <bits/shm.h>
    struct  shminfo
    {
        unsigned long int shmmax;	//一个共享内存区的最大字节数
        unsigned long int shmmin;	//一个共享内存区的最小字节数
        unsigned long int shmmni;	//系统范围内的共享内存区对象的最大个数
        unsigned long int shmseg;	//每个进程连接的最大共享内存区的数目
        unsigned long int shmall;	//系统范围内的共享内存区的最大页数
        unsigned long int __unused1;
        unsigned long int __unused2;
        unsigned long int __unused3;
        unsigned long int __unused4;
    };

    Linux shmctl中可以指定IPC_INFO来获取上面结构所示的系统范围内的限制。在Linux下,具体的限制值可以通过sysctl来查看,如下:

    [root@idcserver program]# sysctl -a | grep shm
    ...
    kernel.shmmni = 4096			//系统范围内的共享内存区对象的最大个数
    kernel.shmall = 4294967296		//系统范围内的共享内存区的最大页数
    kernel.shmmax = 68719476736		//一个共享内存区的最大字节数

    一般情况下不需要对System V共享内存区的系统限制进程修改,因为基本可以满足应用需求,如果要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf 内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例:

    [root@idcserver program]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf
    [root@idcserver program]#sysctl -p
    [root@idcserver program]#sysctl -a |grep shm
    kernel.shmmni = 1000
    kernel.shmall = 4294967296
    kernel.shmmax = 68719476736

    5 System V共享内存区的使用

    下面是System V共享内存区的使用示例,进程1通过共享内存区向进程2发送一条消息,对共享内存区的同步采用System V信号量来完成。如下:

    //process 1
    #include <iostream>
    #include <cstring>
    #include <errno.h>
    
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/shm.h>
    #include <sys/sem.h>
    
    using namespace std;
    
    #define PATH_NAME "/tmp/shm"
    
    union semun  
    {  
        int val;                             
        struct semid_ds *buf;               
        unsigned short int *array;        
        struct seminfo *__buf;            
    };  
    
    int main()
    {
        int fd;
    
        if ((fd = open(PATH_NAME, O_RDONLY | O_CREAT, 0666)) < 0)
        {
            cout<<"open file "<<PATH_NAME<<"failed.";
            cout<<strerror(errno)<<endl;
            return -1;
        }
    
        close(fd);
    
        key_t keyShm = ftok(PATH_NAME, 0);
        key_t keySem = ftok(PATH_NAME, 1);
    
        int shmID, semID;
        
        if ((shmID = shmget(keyShm, sizeof(int), IPC_CREAT | 0666)) < 0)
        {
            cout<<"shmget failed..."<<strerror(errno)<<endl;
            return -1;
        }
    
        int *buf = (int *)shmat(shmID, 0, 0);
    
        if ((semID = semget(keySem, 1, IPC_CREAT | 0666)) < 0)
        {
            cout<<"semget failed..."<<strerror(errno)<<endl;
            return -1;
        }
    
        semun arg; 
        arg.val = 0;  //初始化信号量资源的数目为0
    
        if (semctl(semID, 0, SETVAL, arg) < 0)  
        {  
            cout<<"semctl error "<<strerror(errno)<<endl;  
            return -1;  
        }  
    
        
        struct sembuf buffer;  
        buffer.sem_num = 0;  
        buffer.sem_op = 1;  
        buffer.sem_flg = 0;  
    
        *buf = 111;
        cout<<"process 1:send "<<*buf<<endl;
    
    	//将信号量资源加1,以表示共享内存区内已有资源
        semop(semID, &buffer, 1);
    
        return 0;
    }
    
    //process 2
    #include <iostream>
    #include <cstring>
    #include <errno.h>
    
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/shm.h>
    #include <sys/sem.h>
    
    using namespace std;
    
    #define PATH_NAME "/tmp/shm"
    
    union semun  
    {  
        int val;                             
        struct semid_ds *buf;               
        unsigned short int *array;        
        struct seminfo *__buf;            
    };  
    
    int main()
    {
        int fd;
    
        if ((fd = open(PATH_NAME, O_RDONLY)) < 0)
        {
            cout<<"open file "<<PATH_NAME<<"failed.";
            cout<<strerror(errno)<<endl;
            return -1;
        }
    
        close(fd);
    
        key_t keyShm = ftok(PATH_NAME, 0);
        key_t keySem = ftok(PATH_NAME, 1);
    
        int shmID, semID;
        
        if ((shmID = shmget(keyShm, sizeof(int), 0)) < 0)
        {
            cout<<"shmget failed..."<<strerror(errno)<<endl;
            return -1;
        }
    
        int *buf = (int *)shmat(shmID, 0, 0);
    
        if ((semID = semget(keySem, 1, 0)) < 0)
        {
            cout<<"semget failed..."<<strerror(errno)<<endl;
            return -1;
        }
        
        struct sembuf buffer;  
        buffer.sem_num = 0;  
        buffer.sem_op = -1;  
        buffer.sem_flg = 0;  
    
    	//获得信号量资源
    	semop(semID, &buffer, 1);
    
        cout<<"process 2:recv "<<*buf<<endl;
    
        return 0;
    }
    

    测试结果为:

    # ./send 
    process 1:send 111
    # ./recv 
    process 2:recv 111
    Aug, 13 PM 22:18 @lab
    今天貌似是个悲伤的日子。。。你们都在那啪啪啪,我们只能在这撸呀撸。。。实验室今天还一起吃饭,好久没喝酒,喝了不到两瓶啤的就晕了,哎,老了。。。15号就要放假了,好好看书,准备找工作了。。。
  • 相关阅读:
    微信小程序tabBar 不显示底部菜单的原因和解决方法
    MySQL安装教程
    表单按回车触发事件
    Linux查找最近修改的文件
    通过JS实现网站繁体简体互换
    二级联动下拉列表
    JS跳出框架返回上一页
    mysql查询所有记录,并去掉重复的记录
    查询时间戳函数
    简单信息逐条滚动显示,适用于企业动态,公告等
  • 原文地址:https://www.cnblogs.com/pangblog/p/3258019.html
Copyright © 2020-2023  润新知