进程间通信(IPC)

给你一囗甜甜゛ 提交于 2019-12-23 00:31:14

以下是阿鲤对Linux下进程间通信的总结,希望对大家有所帮助;若有误请慷慨指出。

因为每一个进程都要有一个独立的虚拟地址空间,在保证了进程的独立性同时,却使得进程间无法通信;所以必须要借助一定的方法进行进程间通信,阿鲤在这里主要介绍以下几种通信方式(以下均为SystemV标准)

1:管道 -- 用于进程间的数据传输

2:共享内存 -- 用于进程间的数据共享

3:消息队列 -- 用于进程间的数据传输

4:信号量 -- 用于时间进程间控制

注:以下的代码实现均为centos7环境;

 


一:管道 -- 用于进程间的数据传输

1:本质:通过让多个进程都能访问到同一块内核中的缓冲区,通过半双工通信实现数据传输 (半双工通信:方向可选择的单项通信)

2:分类: 匿名管道 和 命名管道

3:匿名管道:这块内核中的缓冲区没有标识符

     3.1:因为匿名管道没有标识符,故只能用于具有亲缘关系之间的进程通信;

     3.2:在创建管道时,操作系统会提供两个操作句炳(文件描述符),其中一个用于从管道读取数据,一个用于向管道写入数据;但是我们在使用时往往会关闭一个使用一个

     3.3:我们都知道子进程是通过复制父进程进行创建的所以,子进程也会复制到父进程所创建管道的操作句柄,故父子进程便可以通过父进程所创建的匿名管道进行进程间通信。

     3.4:int pipe(int pipefd[2]):

创建一个匿名管道,通过参数pidefd向用户返回管道的操作句柄,其中pipedf[0]用于向管道读取数据,pipedf[1]用于向管道写入数据。返回值 0代表成功 -1代表失败

     3.5 eg:

#include<stdlib.h>    
  #include<unistd.h>    
  #include<string.h>    
      
  int main()    
  {    
      int pipefd[2] = {0};//创建匿名进程    
      int ret = pipe(pipefd);    
      if(ret < 0)    
      {    
          perror("pipe erroe");    
          return -1;    
      }    
      int pid = fork();//创建子进程    
      if(pid < 0)                 
      {                                            
          perror("fork error");                    
          return -1;                               
      }                                            
      else if(pid == 0)//子进程                    
      {                                            
          //sleep(1);                              
          //close(pipefd[1]);//用于关闭写端,父子进程均有写端,所以均需要关闭    
                                                   
          //close(pipefd[0]);//用于关闭读端        
          //pause();//用于关闭读端                 
                                                   
          char buf[1024] = {0};                    
          int ret = read(pipefd[0], buf, 1023);//子进程用于读    
          printf("buf:[%s]-[%d]\n",buf,ret);       
      }                                            
      else//父进程                                 
      {                                            
          //close(pipefd[1]);//用于关闭写端        
                                                   
          //sleep(1);                                                                                                                                                                        
          //close(pipefd[0]);//用于关闭读端    
                                                       
          char *ptr = "BelongAl";                      
          write(pipefd[1], ptr, strlen(ptr));//父进程用于写    
                                                       
      }  
                                                                                                                                                                                                     
      return 0;    
  }    

 

     3.6:匿名管道的特性:

     1:管道自带同步与互斥(同步:若管道写满了则write阻塞;若管道中没有数据则read阻塞;互斥:在对管道进行数据操作的大小不超过PIPE_BUF=4096字节的时候,则保证操作的原子性(原子性:在一个任务未完成前不可被打断))。     

     2:若管道所有写端被关闭,read读完管道中的数据后就不会阻塞,而是返回0。

     3:若管道所有读端被关闭,若继续写入(write)会触发异常程序退出。

     3.7匿名管道的应用

我们在查看某个信息时候会采用这样的操作:比如:ps -ef | grep ssh  这个的命令来对信息进行过滤,那么它的原理是怎样实现的呢?

这起始就是采用管道进行实现的,ps -ef将信息写入管道中,grep再对管道中的信息进行过滤打印;实现代码如下(兄弟进程)

 #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/types.h>    
      
  int main()    
  {    
      int pipefd[2] = {0};//创建管道    
      int ret = pipe(pipefd);    
      if(ret < 0)    
      {    
          perror("pipe error");                
          return -1;                           
      }                                        
                                                     
      int pid1 = fork();//为ps创建子进程             
      if(pid1 == 0)                                  
      {                                              
          dup2(pipefd[1], 1);//重定向                
          execl("/usr/bin/ps", "ps", "-ef", NULL);    
          exit(0);                                   
      }                                              
                                                     
      int pid2 = fork();//为grep创建子进程           
      if(pid2 == 0)                                  
      {                                              
          close(pipefd[1]);//关闭写端则读端退出返回0                                                                                                                                         
          dup2(pipefd[0], 0);//重定向                
          execl("/usr/bin/grep", "grep", "sshd", NULL);    
          exit(0);                                   
                                                     
      }                                              
      close(pipefd[0]);//关闭读端                    
      close(pipefd[1]);//关闭写端                    
                                                     
      waitpid(pid1, NULL, 0);                        
      waitpid(pid2, NULL, 0);                        
                                                     
      return 0;                                      
  }             

4:命名管道:

内核中的缓冲区具有标识符,其他进程可以通过标识符找到这块缓冲区,进而实现进程间通信

     4.1:这个标识符是一个可见于文件系统的管道文件

     4.2:命名管道的创建

          mkfifo命令创建,如下 test.fifo

 

          int mkfifo(const char *pathname, mode_t mode);接口创建    (pathname:管道文件名称  mpde:文件权限   返回值:成功返回0 失败返回-1) 例子如下

      4.3:命名管道的特性:

           1:若管道以只读的方式打开,则会阻塞,直到这个管道被以写的方式打开。

           2:命名管道不会再创建时就开辟缓冲区,而是等有人写的时候才会开辟缓冲区。

           3:若管道以只写的方式打开,则会阻塞,直到这个管道以读的方式打开。

           4:若以读写的方式打开就不会阻塞。

           5:管道的声明周期随进程

           6:提供字节流服务--有序,链接,可靠的字节流传输

       4.5:命名管道举例

读端代码:

  #include<stdio.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/types.h>    
  #include<errno.h>    
  #include<fcntl.h>    
      
  int main()    
  {    
      umask(0);    
      char *file = "./test.fifo";//创建命名管道    
      int ret = mkfifo(file, 0664);    
      if(ret < 0 && errno != EEXIST)//如管道文件已经存在便直接退出    
      {    
          perror("mkfifo erroe");    
          return -1;    
      }    
      
      int fd = open(file, O_RDONLY); //打开管道文件   
      if(fd < 0)    
      {    
          perror("open error");    
          return -1;                                                                                                                                                                         
      }    
      printf("open fifo succes\n");    
      
      while(1)    
      {    
          char buf[1024] = {0};    
          ret = read(fd, buf, 1023);    
          if(ret < 0)    
          {    
              perror("read error");    
              return -1;    
          }    
          else if(ret == 0)//如果写端被全部关闭,则读端返回0 直接退出避免死循环 
          {    
              printf("All write ends are closed\n");    
              return -1; 
          }
          printf("buf[%s]\n",buf);
      }
  
      return 0;
  }      

写端代码:

 #include<stdio.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/types.h>    
  #include<errno.h>    
  #include<fcntl.h>    
  #include<string.h>                                                                                                                                                                         
      
  int main()    
  {    
      umask(0);    
      char *file = "./test.fifo";//创建命名管道    
      int ret = mkfifo(file, 0664);    
      if(ret < 0 && errno != EEXIST)//如管道文件已经存在便直接退出    
      {    
          perror("mkfifo erroe");    
          return -1;    
      }    
      
      int fd = open(file, O_WRONLY);    
      if(fd < 0)    
      {    
          perror("open error");    
          return -1;    
      }    
      printf("open fifo succes\n");    
      
      while(1)    
      {    
          char buf[1024] = {0};    
          scanf("%s",buf);    
          ret = write(fd, buf, strlen(buf));    
          if(ret < 0)    
          {    
              perror("write error");    
              return -1;    
          }    
      } 
   
      return 0;
  }   

:共享内存 -- 用于进程间的数据共享

1:共享内存的原理

     1.1:在物理内存上开辟一块内存空间

     1.2:将这块物理内存映射到进程的虚拟地址空间

     1.3:进程就可以通过虚拟地址直接访问这块物理内存

     1.4:多个进程要是同时映射一块物理内存,就可以通过这块内存实现数据共享

2:为什么共享内存时最快的进程间通信?

因为共享内存直接通过虚拟地址映射访问物理内存,而其他方式因为都是内核中的缓冲区,因此通信时会涉及用户态和内核态之间的两次数据拷贝。因为少了两次用户态与内核态之间的拷贝,因此通信速度最快。

3:共享内存的操作流程

     3.1:创建共享内存(开辟具有标识符的物理内存)

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

               key:内存空间的标识;

               size:共享内存的大小,以内存页为单位;

               shmflg:选项参数,IPC_CREAT | IPC_EXCL(存在则报错) | 权限

               return:成功返回操作句柄,失败返回-1

     3.2:映射到虚拟地址空间

          void *shmat(int shmid, const void *shmaddr, int shmflg);

               shmid:共享内存操作句柄

               shmaddr:映射到虚拟地址空间的首地址,通常置NULL

               shmflg:通常置0-可读可写  SHM_RDONLY-只读 取决于权限

               return:成功返回映射的虚拟空间首地址,通过对这个地址内存进行操作;失败返回-1

     3.3:内存操作

     3.4:解除映射

          int shmdt(const void *shmaddr);   

               shmaddr:映射到虚拟地址空间的首地址

               return:成功返回-1,失败返回0

     3.5:删除共享内存

          int shmctl(int shmid, int cmd, struct shmid_ds *buf);

               shmid:操作句柄

               cmd:具体的操作   常用的为IPC_RMID删除共享内存

               buf:共享内存的一些描述信息

               return:成功返回0,失败返回-1; 

               注:当删除共享内存的时候,共享内存并不会立即被删除(因为有可能会造成正在访问的进程崩溃),而时将key修改为0,表示这块内存将不再继续接收映射链接,当这块共享内存的映射连接数为0的时候,则自动释放。

4:eg:

写端代码

  #include<stdio.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/shm.h>    
      
  #define IPC_KEY 0x12345678    
      
  int main()    
  {    
      umask(0);    
      int shmid = shmget(IPC_KEY, 32, IPC_CREAT|0664);//创建共享内存  
      //key_t key = ftok("./", IPC_KEY) //比较鸡肋的造key 

      if(shmid < 0)    
      {    
          perror("shmget error");    
          return -1;    
      }    
      
      void *shmstart = shmat(shmid, NULL, 0);//建立映射    
      if(shmstart == (void*)-1)    
      {    
          perror("shmat error");    
          return -1;    
      }    
      
      int i = 0;    
      while(1)    
      {    
          sprintf(shmstart, "%s-%d\n", "share memory", i++);    
          sleep(1);    
      }    
      
      shmdt(shmstart);//解除映射    
      shmctl(shmid,IPC_KEY, NULL);//删除共享内存    
      
      return 0;    
  } 

 读端代码

#include<stdio.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/shm.h>    
      
  #define IPC_KEY 0x12345678    
      
  int main()    
  {    
      umask(0);    
      int shmid = shmget(IPC_KEY, 32, IPC_CREAT|0664);//创建共享内存或打开共享内存    
      if(shmid < 0)    
      {    
          perror("shmget error");    
          return -1;    
      }    
      
      void *shmstart = shmat(shmid, NULL, 0);//建立映射    
      if(shmstart == (void*)-1)    
      {    
          perror("shmat error");    
          return -1;    
      }    
      
      int i = 0;    
      while(1)    
      {    
          printf("%s",shmstart);    
          sleep(1);    
      }    
      
      shmdt(shmstart);//解除映射                                                                                                                                                             
      shmctl(shmid,IPC_KEY, NULL);//删除共享内存    
      
      return 0;    
  }  

5:注意事项及特性

     5.1:最快的进程间通信方式

     5.2:生命周期随内核

     5.3:共享内存操作不安全(并不会自动具备同步与互斥关系,需要操作用户自己控制)

三:消息队列 -- 用于进程间的数据传输

1:原理

本质上是内核中的一个优先级队列,多个进程通过向同一个队列中添加节点和获取节点实现通信。传输一个有类型(优先级)的数据快。

2:特性

自带同步与互斥,生命周期随内核,数据传输自带优先级

3:接口

msgget:创建   

msgsnd:添加节点

msgrcv:获取节点

msgctl:操作-删除消息队列 

 

注意:ipcs查看进程间通信资源   ipcm 删除进程间通信资源

-m:查看共享内存

-q:查看消息队列

-s:查看信号量             
 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!