Linux进程间通信

进程间通信

  • 进程的独立性–因为进程独立, 因此想要通信必须能够共同访问相同的媒介

  • 进程间通信目的(数据传输,数据共享,进程间的访问控制

    因为通信的目的不同,使用场景不同,因此操作系统提供了多种进程间通信方式:

    • 管道 (命名管道/匿名管道)数据传输
    • 共享内存 共享数据
    • 消息队列 传输数据
    • 信号量 进程间的访问控制
  • 管道

    进程间数据资源传输

    原理:内核中的一块缓冲区

    匿名管道:pipe

    • 只能用于具有亲缘关系的进程间通信

       /*  匿名管道的基本使用
       *  #include <unistd.h>
       *  int pipe(int pipefd[2]);
       *      pipefd  输出型参数
       *      pipefd[0]   用于从管道读数据
       *      pipefd[1]   用于向管道写数据
       *  返回值:0   失败:-1
       */
      #include <stdio.h>    
      #include <unistd.h>    
          
      int main(){    
        int pipefd[2];    
        int ret = pipe(pipefd);    
        if (ret < 0){    
          perror("pipe error!");    
          return -1;    
        }    
          
        pid_t pid = fork();    
          
        if (pid < 0){    
          return -1;    
        } else if (pid == 0){    
          // 写入    
          int ret = write(pipefd[1], "hello", 5);    
          printf("write %d\n", ret);    
          close(pipefd[1]);    
        } else {    
          // 读取    
          sleep(3);    
          char buf[1024] = {0};    
          int ret = read(pipefd[0], buf, 1023);                                                                                                        
          printf("read: %d %s\n", ret, buf);    
          close(pipefd[1]);    
        }    
        return 0;    
      }    
      
      
    • 特性

      1. 读写特性:

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

        如果管道中数据满了, 则write阻塞,直到数据被读取,管道中有空闲位置,写入数据后返回

        若管道所有读端被关闭,则写端会触发异常 SIGPIPE 导致进程退出

        若管道所有写端被关闭, 则读端读完管道数据后返回0

      2. 管道自带同步与互斥特性

        当读写大小小于PIPE_BUF时保证 操作原子性–操作不会被打断

      3. 管道是一个半双工通信(可选择方向的单向通信)

      4. 管道的生命周期跟随进程

  • 命名管道

    可见于文件系统,因为创建命名管道会随之在文件系统中创建一个命令管道文件

    因为其可见于文件系统,所以所有进程都能够通过打开管道文件,进而获取管道操作句柄,因此命名管道可以用于同一主机上任意通信

    管道的原理依旧是内核中的缓存区, 只是通过文件向所有进程都提供了能够访问管道的方法

    • 特性

      若管道没有被以写打开, 只以只读打开则会阻塞,直到文件被以写的方式打开

      若管道没有被以读的方式打开,只以只写打开则会阻塞, 直到文件被以读的方式打开

      若管道以读写方式打开,则不会阻塞

    • 管道提供流式服务

      面向字节流数据传输 传输灵活,但是会造成数据粘连

    • 实例:

      // 读端
      #include <stdio.h>    
      #include <unistd.h>    
      #include <stdlib.h>    
      #include <sys/types.h>    
      #include <sys/stat.h>    
      #include <errno.h>    
      #include <fcntl.h>    
            
      int main(){    
         char* file = "./pipe.fifo";    
         umask(0);    
         int ret = mkfifo(file, 0664);    
         if (ret < 0){    
           if (errno != EEXIST){    
             perror("mkfifo error!");    
             return -1;    
           }    
         }    
         printf("open file!\n");    
         int fd = open(file, O_RDWR);    
         if (fd < 0){                                                                                                                                  
           perror("open error!");    
           return -1;    
         }    
         printf("open success\n");    
         while(1){    
           char buf[1024] = {0};    
           int ret = read(fd, buf, 1023);    
           if (ret > 0){    
              printf("read %s\n", buf);    
           } else if (ret == 0) {  
                    printf("write closed \n");
           } else {
             perror("read error");
           }
           printf("-------\n");
         }
         close(fd);
         return 0;
        }    
      // 写端
      #include <stdio.h>                                                                                                                             
        #include <unistd.h>    
        #include <sys/types.h>    
        #include <sys/stat.h>    
        #include <errno.h>    
        #include <fcntl.h>    
        #include <string.h>    
            
            
        int main(){    
        char* file = "./pipe.fifo";    
          umask(0);    
          int ret = mkfifo(file, 0664);    
          if (ret < 0){    
            if (errno != EEXIST){    
              perror("mkfile error");    
              return -1;    
            }    
          }    
          printf("already open file\n");    
          int fd = open(file, O_WRONLY);    
          if (fd < 0){    
            perror("open error");    
            return -1;    
          }    
          printf("open success!\n");    
          while(1){    
            char buf[1024] = {0};    
            scanf("%s", buf);    
            write(fd, buf, strlen(buf));    
          }    
       	close(fd);
          return 0;
        }                                                                   
      
  • 共享内存

    最快的进程间通信方式,因为相较于其它进程间通信方式(先将数据从用户态方式拷贝到内核态,用的时候,从内核态拷贝到用户态), 共享内存直接将一块内存映射到用户空间,用户可以直接通过地址对内存进行操作,并反馈到其它进程,少了两步数据拷贝的过程

    • 通信原理

      在Linux中, 每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

      在这里插入图片描述

    • 共享内存使用流程

      流程API
      创建共享内存shmget
      将共享内存映射到虚拟地址空间shmat
      对共享内存进行基本的内存操作memcpy
      解除映射关系shmdt
      删除共享内存shmctl
    • 共享内存的接口函数以及指令

      1. 查看系统中的共享存储段

      ​ ipcs -m

      1. 删除系统中的共享存储段

      ​ ipcrm -m [shmid]

      1. shmget ( ):创建共享内存

      ​ int shmget(key_t key, size_t size, int shmflg);
      ​ [参数key]:由ftok生成的key标识,标识系统的唯一IPC资源。

      ​ [参数size]:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页

      是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
      

      ​ [参数shmflg]:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已

      ​ 经存在的,可以使用IPC_CREAT或直接传0。

      ​ [返回值]:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参

      ​ 数。失败返回-1并设置错误码。

      1. shmat ( ):挂接共享内存

      ​ void *shmat(int shmid, const void *shmaddr, int shmflg);
      ​ [参数shmid]:共享存储段的标识符。

      ​ [参数*shmaddr]:shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推

      ​ 荐使用)。

      ​ [参数shmflg]:若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连

      ​ 接此段。

      ​ [返回值]:成功返回共享存储段的指针(虚拟地址),并且内核将使其与该共享存储段

      ​ 相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1。

      1. shmdt ( ):去关联共享内存

        当一个进程不需要共享内存的时候,就需要去关联。该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。

      ​ int shmdt(const void shmaddr);
      ​ [参数
      shmaddr]:连接以后返回的地址。

      ​ [返回值]:成功返回0,并将shmid_ds结构体中的 shm_nattch计数器减1;出错返

      ​ 回-1。

      1. shmctl ( ):销毁共享内存

      ​ int shmctl(int shmid, int cmd, struct shmid_ds *buf);
      ​ [参数shmid]:共享存储段标识符。

      ​ [参数cmd]:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。

      ​ [参数*buf]:设置为NULL即可。

      ​ [返回值]:成功返回0,失败返回-1。

      注意: 删除一块共享内存,并不会立即删除,而是判断映射连接数,若为0则删除,不为0则拒绝后续连接,直到为0删除

    • 实例

      // 写端
      #include <sys/shm.h>                                                                 #include <stdio.h>            
        #include <unistd.h>      
        #define IPC_KEY 0x12345678
        #define PROJ_ID 12345
        #define SHM_SIZE 4096
                        
        int main(){          
          int shmid;                                            
          // 创建共享内存    
          shmid = shmget(IPC_KEY, SHM_SIZE, IPC_CREAT|0666);
          if (shmid < 0){ 
            perror("shmget error");
            return -1;       
          }                                                  
          // 映射共享内存                   
          char* shm_start = (char*)shmat(shmid, NULL, 0);
          if (shm_start == (void*) - 1){
            perror("shmat error");
            return -1;  
          }          
          int i = 0;   
          // 操作                                             
          while(1){      
      	  sprintf(shm_start, "i write something!", i++);
            sleep(1);    
          }                    
          // 解除映射        
          shmdt(shm_start);                 
          // 删除共享内存
          shmctl(shmid, IPC_RMID, NULL);
          return 0;
      }
      // 读端
      #include <stdio.h>                                                                   #include <unistd.h>           
        #include <sys/shm.h>     
        #define IPC_KEY 0x12345678
        #define SHM_SIZE 4096
        #define PROJ_ID 12345
                        
        int main(){          
          int shmid;                                            
          // 创建共享内存    
          shmid = shmget(IPC_KEY, SHM_SIZE, IPC_CREAT|0666);
          if (shmid < 0){ 
            perror("shmget error!");
            return -1;           
          }                                                  
          // 映射虚拟地址空间              
          char* shm_start = (char*)shmat(shmid, NULL, 0);
          if (shm_start == (void*)- 1){
            perror("shmat error");
            return -1;  
          }          
      	int i = 0;   
          // 操作                         
          while(1){      
            printf("%s\n", shm_start);
            sleep(1);    
          }                    
          // 解除映射        
          shmdt(shm_start);                 
          // 删除虚拟空间
          shmctl(shmid, IPC_RMID, NULL);
      	return 0;
      }
      
    • 消息队列

      流程API
      创建msgget
      添加数据节点msgsnd
      获取数据节点msgrcv
      删除msgctl

      消息队列传输的是有类型的数据块,用户可以根据自己的需要选择性的获取某些类型的数据

    • 信号量

      内核中的计数器 具有等待队列(具有等待与唤醒的功能)

      用于资源技术

      若计数>0, 则表示有资源,则可以获取资源,然后计数-1

      若计数<= 0, 则表示没有资源,没有资源则需等待

      如果放置了资源,则计数+1, 并且唤醒等待的进程

      实现进程间的同步与互斥

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值