• 【1++的Linux】之进程间通信(共享内存)


    👍作者主页:进击的1++
    🤩 专栏链接:【1++的Linux】

    我们在前面的文章中提到过,进程间的通信本质都是先看到同一块资源,然后通过这同一块资源进行通信,并且是单向的通信,只能一端发,一端进行读,共享内存也是基于这样的原理而进行的通信,与管道有异曲同工之处,管道是基于文件,拿到同一个文件的文件描述符而进行的通信,需要调用对文件的读写操作函数,因此要经过内核。而共享内存是不会的,其是内存级的通信,因此它的效率非常高。

    ps:共享内存因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。

    那么是什么是共享内存呢?
    我们其实早已经和它见过面了!!!
    在这里插入图片描述
    我们的共享内存就在共享区中。
    在这里插入图片描述

    我们通过OS申请一块物理内存,作为共享内存,进程间是独立的,有自己的页表,进程地址空间。两进程通过页表将这块物理内存映射到各自的进程地址空间,此时他们就能够看到同一块资源啦!
    共享内存的提供者是OS,共享内存不止有一块,此时就需要将他们进行管理,怎么管理呢?六字真言:先描述,后组织!!!
    那么共享内存实质是什么呢?----共享内存块+对应的内核数据结构。

    共享内存的建立:

    shmget()函数

    int shmget(key_t key, size_t size, int shmflg);

    它的参数都是指什么呢?

    key : 通信的双方要保证看到的是同一块共享内存,,它是几不重要,只要它在系统里唯一就行。相同的key就可以看到同一块空间了。
    size : 共享内存的大小
    shmflg :标志位 IPC_CREAT 创建共享内存:若存在则获取,不存在则创建 IPC_CREAT | IPC_EXCL 创建共享内存:若存在,报错返回,若不存在,创建。
    IPC_EXCL单独使用没有意义。

    返回值是一个整数 ,其类似于文件描述符一样,是共享内存用户层的标识符。

    那么key 值怎么获取,才能使系统中唯一的呢?
    key_t ftok(const char *pathname, int proj_id) 我们调用ftok函数去生成一个唯一的key。
    pathname:指定的文件,此文件必须存在且可存取
    proj_id:计划代号(project ID)

    ftok的典型实现是调用stat函数,然后组合以下三个值:
    ① pathname所在的文件系统的信息(stat结构的st_dev成员)。
    ② 该文件在本文件系统内的索引节点号(stat结构的st_ino成员)。
    ③ proj_id的低序8位(不能为0)。
    上述三个值的组合产生一个32位键。

    创建出共享内存后,我们需要将其挂接到我们需要进行通信的地址空间上,我们用shmat()来实现。
    void *shmat(int shm_id, const void *shm_addr, int shmflg);

    第一个参数,shm_id是由shmget()函数返回的共享内存标识。
    第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
    第三个参数,shm_flg是一组标志位,通常为0。
    调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回

    挂接完成后我们就可以进行通信了。
    若向结束通信,我们可以选择让进程和共享内存分离
    我们使用shmdt() 函数实现。
    int shmdt(const void *shmaddr);

    参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

    该函数只是让进程和其分离,并不会删除共享内存。
    删除共享内存我们用shmctl()函数。
    int shmctl(int shm_id, int command, struct shmid_ds *buf);

    第一个参数,shm_id是shmget()函数返回的共享内存标识符。
    第二个参数,command是要采取的操作,它可以取下面的三个值 :
    IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
    IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值 IPC_RMID:删除共享内存段
    第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

    出来使用函数删除共享内存,我们还可以用命令手动去删除:

    ipcs -m 用来查询存在的共享内存
    ipcrm -m+shmid 可以删除对应的共享内存

    共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问

    下面我们利用管道来实现一个同步的共享内存:

    #pragma once
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include"LOG.hpp"
    #define IPC_PATH "/home/hyp"
    #define PROJ_id 0x66  //8位
    #define SHM_SIZE 4096 //最好是页的整数倍
    #define Fifo_Name "./fifo"
    
    class Init
    {
        public:
        Init()
        {
            umask(0);
             int n=mkfifo(Fifo_Name,0666);//创建管道
             if(n==-1)
             {
                perror("mkfifo");
                exit(1);
             }
        }
    
        ~Init()
        {
            unlink(Fifo_Name);
        }
    
    
    };
    
    
    int OPen_fifo(std::string fifo_name,std::string flag)
    {
        int fd=-1;
        if(flag=="READ")
        {
            fd=open(fifo_name.c_str(),O_RDONLY);
    
        }
        else if(flag=="WRITE")
        {
            fd=open(fifo_name.c_str(),O_WRONLY);
        }
        else{
            exit(2);
        }
        assert(fd!=-1);
        return fd;
    }
    void Sendmessage(int fd)
    {
        int commd=0;
        int s=write(fd,&commd,sizeof commd);
        log("发送中.....",DEBUG)<<std::endl;
        assert(s!=-1);
        
    }
    
    void Wait(int fd)
    {
        log("等待中.....",DEBUG)<<std::endl;
        int commd=1;
        int s=read(fd,&commd,sizeof(commd));
        assert(s!=-1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    //日志打印

    
    #pragma once
    #include
    #include
    #ifndef _LOG_H_
    #define _LOG_H_
    
    #define DEBUG 0
    #define NOTICE 1
    #define WARNING 2
    #define ERROR 3
    
    std::string mes[4]={
        "DEBUG","NOTICE","WARNING","ERROR"
    };
    
    std::ostream& log(const std::string& message,int level)
    {
       std::cout<<"|"<<mes[level]<<"|"<<message<<"|";
       return std::cout;
    }
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    //发送端

    #include"comm.hpp"
    #include"LOG.hpp"
    
    int main()
    {
        key_t k=ftok(IPC_PATH,PROJ_id);
        if(k==-1)
        {
            perror("ftok");
            exit(1);
        }
        log("creat key done",DEBUG)<<" client# k:"<<k<<std::endl;
    
        int shmid=shmget(k,SHM_SIZE,0);
        assert(shmid>0);
        log("creat shm done",DEBUG)<<std::endl;
        //将创建出的共享内存挂接到自己的地址空间中
        char* shmaddr=(char*)shmat(shmid,nullptr,0);
        if(shmaddr==nullptr)
        {
            perror("shmat");
            exit(1);
        }
        log("attach shm success",DEBUG)<<std::endl;
    
        //使用
       int fd= OPen_fifo(Fifo_Name,"WRITE");
        while(true)
        {
            //sleep(1);
            //char puts[1024];
            //fgets(shmaddr,10,stdin);
            //Sendmessage(shmaddr);
            Sendmessage(fd);
            int s=read(0,shmaddr,SHM_SIZE-1);
            if(s>0)
            {
                shmaddr[s-1]='\0';
               if(strcmp(shmaddr,"quit")==0)
                {
                    break;
                }
            }
    
        }
        close(fd);
    
         int n=shmdt(shmaddr);
        if(n==-1)
        {
            perror("shmdt");
            exit(1);
        }
        log("detach shm success",DEBUG)<<std::endl;
    
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    //收端

    #include"comm.hpp"
    #include"LOG.hpp"
    
    Init init;
    int main()
    {
        key_t k=ftok(IPC_PATH,PROJ_id);
        if(k==-1)
        {
            perror("ftok");
            exit(1);
        }
        log("creat key done",DEBUG)<<" server# k:"<<k<<std::endl;
    
        int shmid=shmget(k,SHM_SIZE,IPC_CREAT|IPC_EXCL|0666);//通信的发起者
        assert(shmid>0);
        log("creat shm done",DEBUG)<<std::endl;
        //将创建出的共享内存挂接到自己的地址空间中
        char* shmaddr=(char*)shmat(shmid,nullptr,0);
        if(shmaddr==nullptr)
        {
            perror("shmat");
            exit(1);
        }
        log("attach shm success",DEBUG)<<std::endl;
        //使用
    
        int fd=OPen_fifo(Fifo_Name,"READ");
        while(true)
        {
            Wait(fd);
            printf("%s\n",shmaddr);
            if(strcmp(shmaddr,"quit")==0) break;
           
        }
    
        int x=shmctl(shmid,IPC_RMID,0);
        assert(x!=-1);
        log("delete shm success",DEBUG)<<std::endl;
    
        
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    为了让进程之间能够通信,我们让其能够看到同一份资源,但看到同一份资源也会带来一些时序问题,从而造成数据不一致的问题。
    我们上述的代码中是用加入管道的方式从而保证其同步性使得:只有一个进程写完后另一个进程才能够去读。信号量也是解决同步机制的一种方法。

    我们将多个进程看到的公共的一份资源称为临界资源。
    把进程访问临界资源的代码称为临界区
    多个执行流运行时互相干扰,主要是我们不加保护的访问了临界资源(在非临界区是没有影响的),为了更好的进行临界区的保护,我们让多执行流在任何时刻都只有一个进程能够进入临界区----我们将其称为互斥。
    原子性:要么不做,要么做完,没有中间状态。

    关于信号量我们在后面会有更加详细的解读。

  • 相关阅读:
    编译ncurses-5.9出错
    LeetCode-739-每日温度-单调栈
    Go语言学习笔记——Golang 1.18新特性工作区workspace
    盘点8款流行的网红纱帘,以及它们的特点 - 江南爱窗帘十大品牌
    CentOS常见问题应对,升级make和gcc版本
    自动化搜索ARX密码差分特征的方法
    Spring Security OAuth2的基本使用
    Oracle LiveLabs实验:Configure network environment for Oracle Database 21c
    Cell 重磅丨不依赖泛素蛋白酶降解途径的新型 PROTAC - MCE
    判断动物知识竞猜答案正误
  • 原文地址:https://blog.csdn.net/m0_63135219/article/details/134058285