• Linux 进程间通信(管道、共享内存、消息队列、信号量)


               进程通信 : 不同进程之间传播或交换信息 

            为什么要进程通信呢? 协同运行,项目模块化  

          通信原理 : 给多个进程提供一个都能访问到的缓冲区

          

          根据使用场景,我们能划分为以下几种通信 :

        1.管道(匿名管道、命名管道) 

            因为是半双工通信(单向传递信息),所以叫"管道"。原理是在内核中创建一个缓冲区让通信双方传递信息。

                                   

                 匿名管道 :创建的缓冲区没有标识 , 只能用于具有亲缘关系的进程通信

                     通信流程及代码: 

                    

                                                                      

    /*
    *  匿名管道接口的基本使用
    */
    
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    
    int main()
    {
        //创建管道 必须在子进程创建之前
        //int pipe(int pipefd[2]);
        //pipefd : 用于获取管道中的操作描述符
        //pipefd[0] : 用于从管道中读取数据
        //pipefd[1] : 用于向管道中写入数据
        //返回值 : 成功 0 失败 -1
        int pipefd[2];
        int ret = pipe(pipefd);
        if(ret < 0)
        {
            perror("pipe error");
            return -1;
        }
        
        int pid = fork();
        if(pid < 0)
        {
            return -1;
        }
        else if(pid == 0)
        {
            //子进程关闭写端
            close(pipefd[1]);
            //子进程--读取管道中的数据
            char buff[1024] = {0};
            read(pipefd[0],buff,1023);
            printf("buff:[%s]
    ",buff);
        }
        else
        {
            //父进程关闭读端
            close(pipefd[0]);
            //父进程--向管道中写入数据
            char *ptr = "do you like me ?";
            write(pipefd[1],buff,strlen(ptr));
        }
        
        return 0;
    }    

                    管道的读写特性 :

                       如果管道中没有数据,则read会阻塞,直到读取到数据

                         如果管道中数据满了,则write会阻塞,直到有数据被读取出去

                         如果管道中所有写端都被关闭,那么读端读完管道中的数据之后,会返回0

                         如果管道中所有读端都被关闭,那么写端写入数据的时候会触发异常,退出进程

                    管道特点 :

                       1.半双工通信,数据只能一个方向流动

                       2.读写特性

                       3.内核会对管道进行同步与互斥操作(如果管道读写数据大小<=PIPE_BUF,读写操作将是原子性操作,是不可中断的)

                       4.提供字节流(不包含边界的连续流)服务(数据的传输比较灵活,但是有可能造成数据粘连(数据没有边界) )

                         5.生命周期随进程退出而退出 

                    下面,用匿名管道实现ls | grep 命令:

    //ls|grep的模拟实现
    #include<stdio.h>
    #include<unistd.h>
    #include<errno.h>
    
    int main()
    {
        //创建匿名管道
        int pipefd[2];
        int ret = pipe(pipefd);
        if(ret<0)
        {
            perror("pipe error
    ");
            return -1;
        }
        
        int pid1=fork();
        if(pid1==0)
        {
            //ls --- 写入到标准输出进行打印
            //标准输出重定向到管道写端
            close(pipefd[0]);           
            dup2(pipefd[1],1);
            execlp("ls","ls",NULL);
            exit(0);
        }
        int pid2=fork();
        if(pid2==0)
        {
            //grep make --- 从标准输入读取数据
            //标准输入重定向到管道读端
            close(pipefd[1]);  
            dup2(pipefd[0],0);
            execlp("grep","grep","make",NULL);
            exit(0);
        }
        
        //wait之前关闭管道,防止影响子进程之间的管道交流
        close(pipefd[0]);   
        close(pipefd[1]);
        wait(NULL);
        wait(NULL);
        return 0;
    }

                    命名管道 : 是在文件系统中创建的一个管道(FIFO)文件,所有进程都可以通过打开这个文件来进行通信。

                      特点 : 能让同一机器任意进程都可以进行通信

                      打开特性 : 

                        管道文件如果被只读/只写方式打开,将阻塞,直到该文件被只写/只读方式打开;

                        被读写方式打开,不阻塞。

                      通信流程及代码 : 

                         写端mkfifo创建管道文件->读端打开管道->两端可以进行单向通信了

    //命名管道 读端demo
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    #include<errno.h>
    
    int main()
    {
        int fd=open("./test.fifo",O_RDONLY);
        while(1)
        {
            char buff[1024]={0};
            int ret=read(fd,buff,1023);
            if(ret>0)
            {
            printf("client say:%s
    ",buff);
            }
            else if(ret == 0)
            {
            printf("write close!
    ");
            return -1;
            }
            else 
            {
            perror("read error");
            return -1;
            }
        }    
        close(fd);
        return 0;
    }
    //命名管道基本使用 写端demo
    //   int mkfifo(cosnt char* pathname,mode_t mode)
    //   mode : 权限 返回值: 成功0  失败-1
    #include<stdio.h>
    #include<unistd.h>
    #include<errno.h>
    #include<string.h>
    #include<fcntl.h>
    #include<sys/stat.h>
    
    int main()
    {
        int ret = mkfifo("./test.fifo",0664);
        if(ret<0)
        {
            perror("mkfifio error");
            return -1;
        }
        
        int fd = open("./test.fifo",O_WRONLY);
        if(fd<0)
        {
            perror("open error");
            return -1;
        }
        printf("open fifo success!
    ");
    
        while(1)
        {
            char buff[1024] = {0};
            scanf("%s",buff);
            write(fd,buff,strlen(buff));
            printf("buff:[%s]
    ",buff);
        }        
        close(fd);
        return 0;
    }

                   运行演示 : 

                    

                      

                    匿名管道和命名管道特点相同(除了亲缘关系)。

                    匿名管道和命名管道的区别

                        1.匿名管道用pipe函数创建打开,命名管道由mkfifo函数创建,并用open打开

                        2.匿名管道只能用于亲缘关系的进程,命名管道能适用于同一机器上任意进程间通信

                     本质上他们都是在内核中创建的一块缓冲区

          

        2.共享内存(最快通信)

              相关命令:   ipcs --- 查看所有进程间通信方式

                                 -m 查看共享内存   -s  查看信号量   -q 查看消息队列    

                   ipcrm --- 删除进程间通信方式  -m 删除共享内存(删除时先判断链接数,链接数为0才会删除)

              原理是 , 开辟一块内存,把内存映射到虚拟地址空间,然后直接通过虚拟地址空间对数据进行操作

                                      

                                        如上图,总共发生了两次数据拷贝: 1.用户空间到物理内存  2.物理内存到用户空间

              代码及流程如下:

                  

    //共享内存写入 
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/shm.h>
    #define IPC_KEY 0x123456
    #define SHM_SIZE 4096
    
    int main()
    {
        //key_t ftok(const char *pathname,int proj_id);
        //pathname : 文件名
        //proj_id : 自定义数字
        // 通过文件名找到inode节点号与proj_id合在一起生成一个key值(相当于宏)
        //比如: key_t key = ftok(".",PROJ_ID);
        
        //int shmget(key_t key,size_t size,int shmflg);
        //key : 共享内存在操作系统中的标识符
        //size : 共享内存大小
        //shmflg (选项标志): 
        //      IPC_CREAT: 存在打开,否则创建
        //       IPC_EXCL : 存在报错,否则创建
        //       mode_flags  : 权限
        //返回值 : 进程对共享内存的操作句柄   失败返回-1
        int shmid = shmget(IPC_KEY,32,IPC_CREAT | 0664);
        if(shmid < 0)
        {
            perror("shmget error!");
            return -1;
        } 
        
        //void *shmat(int shmid,const void *shamaddr,int shmflg);
        // shmat : 共享内存的操作句柄
        // shmaddr : 用户指定共享内存映射在虚拟地址空间的首地址
        //            置NULL,让操作系统分配
        //shmflg :  SHM_RDONLY -- 只读 
        //               0     -- 可读可写
        //返回值 : 成功:映射首地址  失败:(void*)-1
        void *shm_start = (char*)shmat(shmid,NULL,0);
        if(shm_start == (void*)-1)
        {
            perror("shmat error");
            return -1;
        }
        
        //进行内存操作
        int i=0;
        while(i!=10)
        {
            sprintf(shm_start,"haha~~+%d
    ",i++);
            sleep(1);
        }
        
        //解除映射
        //int shmdt(const void *shmaddr);
        //shmaddr :映射首地址 
        //return : success 0    failure -1
        shmdt(shm_start);
        
        //int shmctl(int shmid,int cmd,struct shmid_ds *buf);
        //shmid : 句柄
        //cmd : 对共享内存的操作
        //      IPC_RMID  删除共享内存
        //buf : 设置/获取属性信息  不想设置 NULL
        //删除共享内存,并不会直接删除而是判断当前链接数
        //若不为0,则拒绝连接
        //直到为0,才会删除共享内存
    return 0;
    }
    //读共享内存
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/shm.h>
    #define IPC_KEY 0x123456
    #define SHM_SIZE 4096
    
    int main()
    {
            int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT | 0664);
            void* shm_start = (char*)shmat(shmid,NULL,0);
            while(1)
            {
                    printf("%s
    ",shm_start);
                    sleep(1);
            }
            shmdt(shm_start);
            shmctl(shmid,IPC_RMID,NULL);
            return 0;
    }

            共享内存特点 : 

                1.共享内存是最快的ipc,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递不用在内核和用户态之间来回切换

                2.没有保证进程的同步互斥

        3.消息队列

                就是一个存储消息的链表。消息有特定的格式和优先级。对消息队列有写权限的进程可以添加消息,有读权限的进程可以读出消息。

                原理 : 内核中创建一个优先级队列,实现进程间数据传输

                

        4.信号量    

              本质是一个计数器,用来计数资源  

              作用 : 保护共享(临界)资源,保证进程间的同步与互斥

              工作原理 : PV操作 --- 本身具有原子性,因为要保护资源

              P(申请): 如果计数>0,资源-1;如果计数=0,则挂起等待;

              V(释放): 如果有进程挂起等待则唤醒;没有进程等待则计数+1;

                 

  • 相关阅读:
    24点游戏算法
    汉诺塔算法
    台阶算法
    质因数分解算法
    全排列递归算法
    DFS 深度优先搜索例题
    容器
    数细胞
    C++栈和队列
    C++STL中vector容器 begin()与end()函数、front()与back()的用法
  • 原文地址:https://www.cnblogs.com/Duikerdd/p/11737374.html
Copyright © 2020-2023  润新知