Linux进程间通信

进程间通信(IPC)

命令 :ipcs 查看系统的进程间通信方式

ipcs:-m 查看共享内存

​ -s 查看

​ -q 消息队列

ipcrm -m 0

操作系统为用户提供的几种进程间通信方式的学习

操作系统为什么要为用户提供进程间通信方式:因为进程的独立性(都操作的是自己虚拟地址空间中的虚拟地址,无法访问别人的地址)因此无法直接通信

因为进程将通信的场景不同因此提供了不同的几种进程间通信方式:

​ system V:管道,共享内存,消息队列,信号量

​ posix:信号量

管道

​ 本质:内核中的一块缓冲区

​ 实现进程间通信的原理:让多个进程通过访问到相同的缓冲区来实现通信,管道实现通信使用的是系统调用的IO接口(遵循一切皆文件的思想)

匿名管道

​ 一个进程创建匿名管道,操作系统在内核重建一块缓冲区,并返回两个文件描述符作为管道的操作句柄(一个用于读,一个用于写,方向选择权交给用户),但是这个缓冲区在内核中没有标识。

操作接口:

int pipe(int pipefd[2]);

pipefd:至少具有两个int型元素的数组

创建一个管道,通过pipefd获取系统返回的管道操作句柄,其中:

​		pipefd[0]:用于从管道读取数据

​		pipefd[1]:用于从管道写入数据
返回:0     失败:-1
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>

int main()
 {
   //管道必须创建于创建子进程之前,这样子进程才能复制到管道的操作句柄
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret < 0)
    {
      perror("pipe error");
      return -1;
    }
   int pid = fork();
    if(pid == 0)
    {
      //子进程
      //close(pipefd[1]);
      char buf[1024] = {0};
      int ret = read(pipefd[0],buf,1023);
      printf("child read:%d--%s\n",ret,buf);
      exit(0);
    }
    else if(pid > 0)
    {
     //close(pipefd[0]);                                                                                                                             
      write(pipefd[1],"hello world",11);
    }
    return 0;
  }

运行结果:
在这里插入图片描述

管道的读写特性:
  1. 若管道中没有数据,则read会阻塞;直到数据被写入(缓冲区中有数据)

  2. 若管道中数据满了,则write会阻塞;直到数据被读取(缓冲区有空闲空间)

  3. 若管道的所有读端被关闭,则write会触发异常,进程退出

  4. 若管道所有写端被关闭,则read会返回0

    管道的read返回0,不仅仅指的是没有读到数据 ----所有写端被关闭

    匿名管道的简单实现:

    ls | grep make
    ls:浏览目录,并且将结果写入到标准输出
    grpe make:从标准输入循环读取数据,对读到的数据进行过滤匹配
    

    匿名管道的实现就是创建两个进程,一个运行ls,一个运行grep make

    让ls这个进程标准输出,重定向写入到管道写入端

    让grep这个进程标准输入,重定向到管道的读取端

在这里插入图片描述
实现

  #include<stdio.h>                                                                                                                                   
  #include<unistd.h>
  #include<stdlib.h>
  #include<string.h>
  #include<errno.h>
  #include<fcntl.h>
  #include<sys/wait.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
      close(pipefd[1]);
      dup2(pipefd[0],0);
      execlp("grep","grep","make",NULL);
      exit(0);
    }
    close(pipefd[1]);
    close(pipefd[0]);
    waitpid(pid1,NULL,0);
    waitpid(pid2,NULL,0);
    return 0;        
 }        
命名管道

命名管道可以用户同一主机上的任意进程间通信

​ 命名管道在内核中这块缓冲区是有标识的,意味着所有的进程都可以通过这个标识找到这块缓冲区实现通信

命名管道的标识实际上是一个文件,可见于文件系统,意味着所有进程都可以通过打开文件进而访问到内核中的缓冲区

命名管道的打开特性:

​ 若文件当前没有已经被已读的方式打开,则以O_WRONLY打开时会阻塞

​ 若文件当前没有已经被已写的方式打开,则以O_RDONLY打开时会阻塞

命名管道实现

    #include<stdio.h>                                                                                                                                 
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    #include<errno.h>
    #include<sys/stat.h>
    int main()
    {
   char *fifo = "./test.fifo";
      umask(0);
      //创建命名管道
      int ret = mkfifo(fifo,0664);
      if(ret < 0)
      {
        if(errno != EEXIST)
        {
          perror("mkfifo error!");
          return -1;
    
        }
    
      }
    printf("---start open---\n");
    //以只读打开命名管道
      int  fd = open(fifo,O_WRONLY);
      printf("end open------\n");
      if(fd < 0)
      {
        perror("open error");
        return -1;
    
      }
      printf("fifo:%s open success\n",fifo);
      while(1)
      {
        char buf[1024] = {0};
        printf("i say: ");
        fflush(stdout);
        scanf("%s",buf);
        write(fd,buf,strlen(buf));
      }
      close(fd);
      return 0; 
    }                     

    #include<stdio.h>                                                                                                                                 
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    #include<errno.h>
    #include<sys/stat.h>
    int main()
    {
   char *fifo = "./test.fifo";
      umask(0);
      //创建命名管道
      int ret = mkfifo(fifo,0664);
      if(ret < 0)
      {
        if(errno != EEXIST)
        {
          perror("mkfifo error!");
          return -1;
    
        }
    
      }
    printf("---start open---\n");
    //以只读打开命名管道
      int  fd = open(fifo,O_WRONLY);
      printf("end open------\n");
      if(fd < 0)
      {
        perror("open error");
        return -1;
    
      }
      printf("fifo:%s open success\n",fifo);
      while(1)
      {
        sleep(3);
        char buf[1024] = {0};
        read(fd,buf,1023);
        printf("peer say: %s\n",buf);

      }
      close(fd);
      return 0; 
    }                     
管道总结

​ 管道有两种:匿名管道,命名管道

​ 匿名管道只能用于具有亲缘关系的进程间通信

​ 命名管道可以用于同一主机上的任意进程间通信

管道特性:

  1. 管道是半双工的通信

  2. 管道的读写特性+(命名管道的打开特性)

  3. 管道的生命周期随进程(所有管道的操作句柄被关闭)

  4. 管道自带同步与互斥(管道的读写数据大小在不超过PIPE_BUF(4096)时保证操作的原子性)

  5. 管道提供字节流服务:传输灵活,但是存在粘包问题—本质原因:没有明显边界

    同步:保证对临界资源操作的时序合理性(没有数据则读会阻塞,写满了则写会阻塞)

    互斥:保证对临界资源操作在同一时间的唯一性(我操作时别人不能操作)

共享内存

最快的进程间通信方式

管道通信:通信的本质是通过内核的缓冲区来实现通信;

进程1将数据从用户态缓冲区拷贝到内核态缓冲区

进程2将数据从拷贝内核态缓冲区到用户缓冲区

涉及两次用户态与内核态之间的数据拷贝

[外链图片转存失败(img-gdzjssGX-1566019765847)(D:\Desktop\笔记\笔记\笔记\Linux笔记\Linux笔记\Linux笔记\图片\进程间通信\2.png)]

共享内存原理:

​ 1.在物理内存中开辟一块空间

​ 2.将这块内存空间通过页表映射到进程的虚拟地址空间

​ 3.进程可以直接通过进程虚拟地址访问到这块物理空间,进行操作(若多个进程映射同一块物理内存,就可以实现相互通信–直接通过虚拟地址改变内存中的数据,其他进程也会随之改变,相较于其他通信方式,少了两步内核态与用户态之间的数据拷贝过程,因此速度最快)

​ 4.解除映射

​ 5.删除共享内存

1.int shmget(key_t key,size_t size,int shmflg)
   key:共享内存在操作系统中的标识符
   size:共享内存的大小
   shmflg:
   		IPC_CREAT 共享内存若存在则打开,否则创建
   		IPC_EXCL 与IPC_CREAT同时使用时,若共享内存存在则报错返回
   		mode 共享内存的的操作权限
   返回:正整数--共享内存的操作句柄
2.void *shmat(int shmid, const void *shmaddr, int shmflg);
  shmid:创建共享内存的操作句柄
  shmaddr:共享内存在虚拟地址空间中的首地址---通常置NULL
  shmflg:SHM_RDONLY---映射之后,共享内存只读;通常置零--可读可写
  返回值:映射首地址    失败:(void*) -1
3.共享内存的操作
	memcpy/strcpy
4.解除映射关系
   int shmdt(const void*shmaddr)
   	shmaddr:shmat建立映射时返回的映射首地址
5.删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 shmid:共享内存操作句柄
 cmd:即将进行的操作
 IPC_RMID:删除共享内存
 buf:用于获取/设置共享信息
 共享内存的删除流程:共享内存再删除的时候,首相会判断当前映射链接数是否为0,若为0则直接删除;否则表示现在还有其他进程正在使用,则共享内存不能被立即删除,但是会拒绝后续进程的映射链接,等待映射链接数为0时删除这片共享内存

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/shm.h>
    
    #define IPC_KEY 0x1234567//给一个共享内存在操作系统中的标识符
    
    int main()
    {
      //创建共享内存,返回值为操作句柄
      int shmid = shmget(IPC_KEY,32,IPC_CREAT|0664);
      if(shmid < 0)
      {
        perror("shmget error");
        return -1;
      }
      void* shm_start = shmat(shmid,NULL,0); //建立映射,返回映射首地址
      if(shm_start==(void*)-1)
      {
        perror("shmat error");
        return -1;
      }
       int i = 0;
       while(1)
       {
       sprintf(shm_start,"%s-%d","good afternoon",i++);                                                                                             
         sleep(1);
       }
       shmdt(shm_start);//解除映射关系
       shmctl(shmid,IPC_RMID,NULL);//删除共享内存
      return 0;
    }

    #include <stdio.h>                                                                                                                                
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/shm.h>
    
    #define IPC_KEY 0x1234567//给一个共享内存在操作系统中的标识符
    
     int main()
    {
      //创建共享内存,返回值为操作句柄
      int shmid = shmget(IPC_KEY,32,IPC_CREAT|0664);
      if(shmid < 0)
      {
        perror("shmget error");
        return -1;
      }
      void* shm_start = 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;
    }

消息队列

操作系统在内核中为用户创建的一个队列;其他进程可以通过访问相同的队列进行通信

消息队列传输的是有类型的数据块

消息队列生命周期随内核

在这里插入图片描述

信号量

用于实现进程间同步与互斥

内核中的一个计数器

同步:时序合理----------功能:等待与唤醒

互斥:唯一访问

在对资源进行访问操作之前,先判断信号量计数(是否有资源能够操作);

​ 若计数<=0;则等待(等待别人创建资源)计数-1;等待在等待队列上

​ 若计数》0;则直接返回(按照程序流程就可以直接操作资源了)计数-1

其他进程创建了资源,计数+1;并且唤醒等待队列上的那些进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值