• 进程间通信(3)——共享内存和信号量


    【4】共享内存

    (1)概述

    • 两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新,反之,进程B也可以即时看到进程A对共享内存中数据的更新。

    • 共享内存是存在于内核级别的一种资源

    • 在系统内核为一个进程分配内存地址时,通过分页机制可以让一个进程的物理地址不连续,同时也可以让一段内存同时分配给不同的进程。共享内存机制就是通过该原理来实现的,共享内存机制只是提供数据的传送,如何控制服务器端和客户端的读和写操作互斥,这就需要一些其他的辅助工具,例如信号量的概念。

    • 共享内存可以说是Linux下最快速、最有效的进程间通信方式

    • 因为进程可以直接读写内存,而不需要任何数据的拷贝
    • 管道和消息队列等通信方式,需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件
    • 进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的
    • 共享内存的不足之处:由于多个进程对同一块内存区域具有访问的权限,各个进程间的同步问题尤为重要。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则将造成数据的混乱。同步控制问题可以通过下一节介绍的信号量来解决。

    (2)共享内存相关操作

    用于Linux进程通信共享内存。共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。

    shmat函数原型

    • shmat(把共享内存区对象映射到调用进程的地址空间)

    • 所需头文件

        #include <sys/types.h>
        #include <sys/shm.h>
      
    • 函数说明

        连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
      
    • 函数原型

        void *shmat(int shmid, const void *shmaddr, int shmflg)
      
    • 函数传入值

        shmid	共享内存标识符
        shmaddr  指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
        shmflg   SHM_RDONLY:为只读模式,其他为读写模式
      
    • 函数返回值

        成功:附加好的共享内存地址
        出错:-1,错误原因存于errno中
      

    shmget()

    • 创建或打开共享内存的函数

        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/shm.h>
        int shmget(key_t key,int size,int flag);
      

    返回值:成功返回共享内存ID,出错返回-1

    key:创建或打开的共享内存的键值

    size:共享内存区域大小,只在创建一个新的共享内存时生效

    flag:调用函数的操作类型,也可用于设置共享内存的访问权限,两者通过逻辑或表示

    shmdt函数原型

    • shmdt(断开共享内存连接)

    • 所需头文件

        #include <sys/types.h>
        #include <sys/shm.h>
      
    • 函数说明

        与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
      
    • 函数原型

        int shmdt(const void *shmaddr)
      
    • 函数传入值

        shmaddr:连接的共享内存的起始地址
      
    • 函数返回值

        成功:0
        出错:-1,错误原因存于error中
      

    (3)使用shmget函数创建共享内存的例子,create_shm.c:

    程序中在调用shmget函数时指定key参数值为IPC_PRIVATE,这个参数的意义是创建一个新的共享内存区,当创建成功后使用shell命令ipcs来显示目前系统下共享内存的状态。命令参数-m为只显示共享内存的状态。

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdlib.h>
    #include <stdio.h>
    #define BUFSZ 4096
    int main(void)
    {
            int shm_id;/*共享内存标识符*/
            shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);
    		/*创建共享内存*/
            if(shm_id < 0)
            {
                    printf("shmget failed!
    ");
                    exit(1);/*shmget出错退出*/
            }
            printf("create a shared memory segment successfully:%d
    ",shm_id);
            system("ipcs -m");/*调用ipcs命令查看IPC*/
            exit(0);
    }
    
    运行结果:
    
    hyx@hyx-virtual-machine:~/test$ ./create_shm
    create a shared memory segment successfully:2949133
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     连接数  状态      
    0x00000000 65536      hyx        600        524288     2          目标       
    0x00000000 1114113    hyx        600        524288     2          目标       
    0x00000000 196610     hyx        600        524288     2          目标       
    0x00000000 393219     hyx        600        524288     2          目标       
    0x00000000 950276     hyx        600        524288     2          目标       
    0x00000000 589829     hyx        600        524288     2          目标       
    0x00000000 622598     hyx        600        16777216   2                       
    0x00000000 655367     hyx        600        16777216   2          目标       
    0x00000000 753672     hyx        600        524288     2          目标       
    0x00000000 819209     hyx        600        2097152    2          目标       
    0x00000000 1146890    hyx        600        1048576    2          目标       
    0x00000000 1245195    hyx        600        524288     2          目标       
    0x00000000 1933324    hyx        600        67108864   2          目标       
    0x00000000 2949133    hyx        666        4096       0                       
    

    执行ipcs,打印共享内存,信号量和消息队列的信息:

    hyx@hyx-virtual-machine:~/test$ ipcs
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     连接数  状态      
    0x00000000 65536      hyx        600        524288     2          目标       
    0x00000000 1114113    hyx        600        524288     2          目标       
    0x00000000 196610     hyx        600        524288     2          目标       
    0x00000000 393219     hyx        600        524288     2          目标       
    0x00000000 950276     hyx        600        524288     2          目标       
    0x00000000 589829     hyx        600        524288     2          目标       
    0x00000000 622598     hyx        600        16777216   2                       
    0x00000000 655367     hyx        600        16777216   2          目标       
    0x00000000 753672     hyx        600        524288     2          目标       
    0x00000000 819209     hyx        600        2097152    2          目标       
    0x00000000 1146890    hyx        600        1048576    2          目标       
    0x00000000 1245195    hyx        600        524288     2          目标       
    0x00000000 1933324    hyx        600        67108864   2          目标       
    0x00000000 2949133    hyx        666        4096       0                       
    
    --------- 信号量数组 -----------
    键        semid      拥有者  权限     nsems     
    
    --------- 消息队列 -----------
    键        msqid      拥有者  权限     已用字节数 消息  
    

    执行ipcs -m,只打印共享内存段信息:

    hyx@hyx-virtual-machine:~/test$ ipcs -m
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     连接数  状态      
    0x00000000 65536      hyx        600        524288     2          目标       
    0x00000000 1114113    hyx        600        524288     2          目标       
    0x00000000 196610     hyx        600        524288     2          目标       
    0x00000000 393219     hyx        600        524288     2          目标       
    0x00000000 950276     hyx        600        524288     2          目标       
    0x00000000 589829     hyx        600        524288     2          目标       
    0x00000000 622598     hyx        600        16777216   2                       
    0x00000000 655367     hyx        600        16777216   2          目标       
    0x00000000 753672     hyx        600        524288     2          目标       
    0x00000000 819209     hyx        600        2097152    2          目标       
    0x00000000 1146890    hyx        600        1048576    2          目标       
    0x00000000 1245195    hyx        600        524288     2          目标       
    0x00000000 1933324    hyx        600        67108864   2          目标       
    0x00000000 2949133    hyx        666        4096       0                       
    
    hyx@hyx-virtual-machine:~/test$ 
    

    (4)使用共享内存进行通信的实例

    共享内存ID以命令行参数的形式传递给进程。

    write_shm.c是写共享内存,即分10次向共享内存中写入people结构体的成员数据,(姓名和年龄)数据的值由for循环自动生成。程序的最后使用shmdt将共享内存段从当前的进程空间中脱离掉。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    #include <unistd.h>
    typedef struct
    {
            char name[4];
            int age;
    }people;
    int main(int argc,char** argv)
    {
            int shm_id,i;
            char temp;
            people *p_map;
            if(argc != 2)/*命令行参数错误*/
            {
                    printf("USAGE:atshm < d=identifier>");/*打印帮助消息*/
                    exit(1);
            }
            shm_id = atoi(argv[1]);/*得到要引入的共享内存段,atoi将字符转换成整型*/
            p_map = (people *)shmat(shm_id,NULL,0);/*shmat:把共享内存区对象映射到调用进程的地址空间*/
            temp = 'a';
            for(i = 0;i < 10;i++)
            {
                    temp+=1;
                    memcpy((*(p_map+i)).name,&temp,1);
                    (*(p_map+i)).age = 20+i;
    				/*memcpy:c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标des所指的内存地址的起始位置中,void *memcpy(void *dest, const void *src, size_t n);*/
            }
            if(shmdt(p_map)==-1)
            {
                    perror("detach error!
    ");
            }
            return 0;
    }
    

    read_shm.c是读共享内存,即分10次从共享内存中读出people结构体的成员数据。程序的最后同样使用shmdt将共享内存段从当前的进程空间中脱离掉。

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    #include <unistd.h>
    typedef struct
    {
            char name[4];
            int age;
    }people;
    int main(int argc,char** argv)
    {
            int shm_id,i;
            people *p_map;
            if(argc != 2)
            {
                    printf("USAGE:atshm <idenfifier>");
                    exit(1);
            }
            shm_id = atoi(argv[1]);/*得到要引入的共享内存段,atoi将字符转换成整型*/
            p_map = (people*)shmat(shm_id,NULL,0);/*shmat:把共享内存区对象映射到调用进程的地址空间*/
            for(i=0;i<10;i++)
            {
                    printf("name:%s  ",(*(p_map+i)).name);
                    printf("age %d
    ",(*(p_map+i)).age);
            }
            if(shmdt(p_map)==-1)
            {
                    perror("detach error!
    ");
    
            }
           return 0;
    }
    

    运行结果:

    hyx@hyx-virtual-machine:~/test$ ./write_shm 2949133
    hyx@hyx-virtual-machine:~/test$ ./read_shm 2949133
    name:b  age 20
    name:c  age 21
    name:d  age 22
    name:e  age 23
    name:f  age 24
    name:g  age 25
    name:h  age 26
    name:i  age 27
    name:j  age 28
    name:k  age 29
    hyx@hyx-virtual-machine:~/test$ 
    

    【5】信号量

    • 信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他的通信资源(如文件、外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,其只是一种外部资源的标识。

    • 信号量,有时也被称为信号灯,是在多进程环境下使用的一种设施,它负责协调各个进程,以保证它们能够正确、合理的使用公共资源。信号量分为单值和多值两种,前者只能被一个进程获得,后者可以被若干个进程获得。

    • 以停车场为例:假设停车场只有三个车位,一开始三个车位都为空。这时如果同时来了第五辆车,看门人允许其中三辆直接进入,然后放下车栏,剩下的车则必须在入口等待,在此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车栏,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

    • 车位:公共资源
    • 每辆车:一个进程
    • 看门人:信号量的作用
    • 抽象来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的进程/线程(车辆)都会将该整数减1(通过它是为了使用公共资源),当该整数值为时,所有试图通过它的进程都处于等待状态。在信号量上我们定义两种操作:wait(等待)和releas(释放)。当一个进程调用wait操作时,它要么得到资源探后将信号量减1,要么一直等下去(指放入阻塞队列),直到信号量大于等于1时。release实际上是在信号量上执行加1操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了信号量守护的资源。
  • 相关阅读:
    html的基本框架和常用标签
    防火墙
    Zenmap
    每日一招:熟练掌握变盘方向
    每日一招:赚钱最快的选股策略
    操盘策略:黄金做单时间
    每日一招:坚守六大方式选出优质股
    如何保卫你的牛市胜利果实?
    名家看后市:长阴破位不必慌
    每日一招:补仓需遵守的技巧
  • 原文地址:https://www.cnblogs.com/myidea/p/5037454.html
Copyright © 2020-2023  润新知