进程间通信

本文介绍了Linux中的进程间通信,重点讨论了管道(匿名和命名)以及System V共享内存的原理、使用方法和相关函数。通过示例代码展示了如何在父子进程中实现通信,并探讨了共享内存的创建、释放和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

进程间通信介绍

进程间通信的目的

 进程间通信发展与分类

管道

管道概念

 匿名管道:

注意:匿名管道只用于父子进程之间交换资源~

父子进程控制管道原理

匿名管道之间读写规则

 命名管道

boy.c 

gril.c

System v 共享内存

 共享内存是什么:

共享内存数据结构

共享内存释放

共享内存函数解读

shmget函数

shmat函数

notice:

 shmdt函数

shmctl函数

 server.c

client.c


进程间通信介绍

        进程通信(Interpeocess communication)简称IPC;所谓进程间通信就是两个相互独立的进程之间完成交互工作,我们在之前学习到的所有的程序都是进程,他们之间相互独立,那么他们之间是怎么完成交互工作的呢,就比如你和你的微信好友是怎么发信息聊天的呢?

进程间通信的目的

  • 数据传输:一个进程将自己的数据发送给另外一个进程
  • 资源共享:多个进程之间享受同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

 进程间通信发展与分类

  • 管道
  • System V进程间通信
  • POSIX进程间通

管道

  • 匿名管道
  • 命名管道

System V进程间通信

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

管道概念

  • 管道是unix中最原始的进程之间通信的方式

  • 我们把一个进程连接到另一个进程的数据流称为管道 

 

 通过这个|我们可以查看code.c这个文件包含了多少行代码。但是cat和wc是两个进程,这就完成两个进程之间的互动,cat进程将查看代码数量命令通过管道交给wc进程

 

 匿名管道:

通过pipe函数创建一个匿名管道:

        

 int pipe(int id[2]);

参数解读:

id:文件描述符数组,id【0】表示读端,id【1】表示写端,创建成功返回0,失败返回错误代码

 用户可以指定关闭或者打开管道的读端或者写端,比如让父进程读取,子进程写入~

注意:匿名管道只用于父子进程之间交换资源~

父子进程控制管道原理

父子进程被创建之后进程PCB当中都有了一个指针数组,struct file_struct,这个是用来描述组织文件的。而且父子进程都拥有这个指针数组,而且子进程继承了父进程的数据的。所以子进程就会和父进程拥有同样的数据并且指向相同的空间。在创建好管道要完成父子间通信的时候,父子进程同时指向这个文件,父进程想要对文件进行写入操作,会先将数据写入到文件缓冲区当中,而不是直接写入然后就打印到屏幕上了。结论:对于程序的操作当中,所发送的命令并不是立即执行,而是存入到相对应的缓冲区当中,在合适的时候再完成相应命令~

#include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<string.h>
    5 int main()
    6 {
    7   int pipefd[2]={0};
    8   //pipef为一个输出型参数 我们可以通过这个参数读取到打开的两个fd
    9   if(pipe(pipefd)!=0)
   10   {
   11     perror("pipe error\n");
   12     return 1;
   13   }
   14   printf("pipefd[0]:%d\n",pipefd[0]);
   15   printf("pipefd[1]:%d\n",pipefd[1]);
   16   //创建父子进程完成他们之间的通信工作
   17   if(fork()==0)
   18   {
   19     close(pipefd[0]);
   20     const char*msg="hello world";
W> 21     int count=0;
   22     while(1)
   23     {
   24       write(pipefd[1],msg,strlen(msg));
   25       //write(pipefd[1],"a",1);
   26       //printf("child:%d\n",count);
   27       //count++;
   28       sleep(1);
   29     }
   30     exit(0);                                                                                                                                                      
   31   }
   32   close(pipefd[1]);
   33   while(1)
   34   {
   35     sleep(1);
         }
   30     exit(0);                                                                                                                                                      
   31   }
   32   close(pipefd[1]);
   33   while(1)
   34   {
   35     sleep(1);
   36     char c[1024*2]={0};
   37     read(pipefd[0],c,sizeof(c));
   38     printf("father take:%s\n",c);
   39   //  char buffer[64]={0};
   40   //  ssize_t s=read(pipefd[0],buffer,sizeof(buffer)-1);
   41   //  if(!s)
   42   //  {
   43   //    break;
   44   //  }
   45   //  else if(s>0)
   46   //  {
   47   //    buffer[s]=0;
   48   //    printf("child say to father:%s\n",buffer);
   49   //  }
   50   //  else
   51   //  {
   52   //    break;
   53   //  }
   54   }
   55   
   56   return 0;
   57 }

 我们关闭了子进程的读操作,父进程端的写入操作操作看看结果是什么

 本来是子进程写入到缓冲区的hello world可以被父进程读取到,这就是父子间通信了~

匿名管道之间读写规则

我们想一下如果一端不进行写入,或者一段不进行读取,这两个进程之间还能进行正常的交互么?

管道特点

  • 管道是一个单向通信的文件,只限于父子进程之间
  • 提供流式服务
  • 自带同步与互斥机制
  • 管道生命周期跟随进程,进程结束管道文件关闭

 管道会发生的情况

  • 读端不读或者读的很慢,写段要等待读端
  • 读端关闭,写段收到另一端关闭信号后终止
  • 写端不写或者很慢,读端需要一直等待写端
  • 写段关闭,读端会读完管道内容,读到0时结束

 命名管道

一张图理解命名管道

        

boy.c 

#include"comm.h"                                                                                                                                                  
    2 int main()
    3 {         
    4   umask(0);
    5   //命名管道的创建
    6   if(mkfifo(MY_fifo,0666)<0)
    7   {                         
    8     perror("mkfifo");
    9     return 1;        
   10   }          
   11   //管道创建完成,需要完成进程之间的通信,只需要对管道进行文件操作即可
   12   int fd=open(MY_fifo,O_RDONLY);                                      
   13   if(fd<0)                      
   14   {       
   15     perror("open");
   16     return 2;      
   17   }          
   18    
   19   //进行相对应的读写操作
   20   while(1)              
   21   {       
   22     char buffer[64]={0};
   23     ssize_t s=read(fd,buffer,sizeof(buffer)-1);
   24     if(s>0)//读取成功                          
   25     {                
   26       buffer[s]=0;
E> 27       if(strcmp(buffer,"show")==0)
   28       {                           
   29         if(fork()==0)
   30         {            
   31           execl("/usr/bin/ls","ls","-l",NULL);
   32           exit(1);                            
   33         }         
E> 34         waitpid(-1,NULL,0);
   35       }          
   36         else if(strcmp(buffer,"run")==0)
   37       {
   38         if(fork()==0)
   39         {
   40           execl("/usr/bin/sl","sl",NULL);
   41         }
E> 42         waitpid(-1,NULL,0);
   43       }
   44       else
   45       {                                                                                                                                                           
   46         printf("gril# %s\n",buffer);
   47       }
   48     }
   49     else if(!s)
   50     {
   51       printf("gril quit ...\n");
   52       break;
   53     }
   54     else
   55     {
   56       perror("read");
   57       break;
   58     }
   59   }
   60 
   61 
   62   close(fd);
   63   return 0;
   64 }
         

gril.c

  1 #include"comm.h"
  2 #include<string.h>
  3 int main()
  4 {
  5   //进程之间完成通信工作,另外一个进程就不需要在创建管道文件了,只需要对以创建好的管道文件 
        进行读写操作即可
  6   //获取管道文件描述符
  7   int fd=open(MY_fifo,O_WRONLY);
  8   if(fd<0)
  9   {
 10     perror("open");
 11     return 1;
 12   }
 13   //成功打开之后就可以进行操作读写操作
 14   while(1)
 15   {
 16     printf("请输入:");
 17     fflush(stdout);
 18     char buffer[64]={0};                                                                                                                                            
 19     //先将数据从标准输入拿到这个进程内部
 20     //就是你从键盘键入的内容,需要先读到这进程当中,这个进程再做处理,通过管道发送给别的进 
      程
 21     ssize_t s=read(0,buffer,sizeof(buffer)-1);
 22     if(s>0)
 23     {
 24       buffer[s-1]=0;
 25       printf("%s\n",buffer);
 26       write(fd,buffer,strlen(buffer));
 27     }
 28   }
 29   close(fd);
 30   return 0;
 31 }

System v 共享内存

 共享内存是什么:

     

 共享内存指 (shared memory)在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。

共享内存数据结构

      我们知道在操作系统中是存在大量的进程的,如果两两进程进程进行通信,就需要多个共享内存。既然共享内存在系统中存在多份,就一定要将这些不同的共享内存管理起来,即先描述,再组织;为了保证两个或多个进程能够看到它们的同一份共享内存,那么共享内存一定要有能够唯一标识性的ID,方便让不同的进程识别它们的同一份共享内存;这个所谓的ID一定是在共享内存的数据结构中;

共享内存释放

我们在make clean之后,又make生成之后,发现显示shget: file exist。这样我们只是把这个进程结束掉了,但是,这两个进程之间创建的共享内存还是存在的,直到服务器被关闭了,这个共享内存也就没有了。共享内存不进行释放这样对用户是不友好的。所以这里我们需要手动释放共享内存;

  •  命令释放:ipcrm -m + 共享内存id
  • 函数释放:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

共享内存函数解读

shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key: 这个共享内存段名字
size: 共享内存大小
shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 -1

shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr: 指定连接的地址
shmflg: 它的两个可能取值是 SHM_RND SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1

notice:

shmaddr NULL ,核心自动选择一个地址
shmaddr 不为 NULL shmflg SHM_RND 标记,则以 shmaddr 为连接地址。
shmaddr 不为 NULL shmflg 设置了 SHM_RND 标记,则连接的地址会自动向下调整为 SHMLBA 的整数倍。公式: shmaddr -
(shmaddr % SHMLBA) shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

 shmdt函数

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: shmat 所返回的指针
返回值:成功返回 0 ;失败返回 -1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid: shmget 返回的共享内存标识码
cmd: 将要采取的动作(有三个可取值)
buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回 0 ;失败返回 -1

 server.c

  1 #include"comm.h"
  2 #include<unistd.h>
  3 int main()
  4 {
  5   //创建一个共享内存的key值
  6   key_t key=ftok(PATH_NAME,PROJ_ID);
  7   if(key<0)
  8   {
  9     perror("ftok");
 10     return 1;
 11   }
 12   //共享内存的创建,参数解读,key是刚才生成的值,size共享内存的大小,IPC_CREAT|IPC_EXCL确 
      保生成的shm是全新的
 13   int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
 14   if(shmid<0)
 15   {
 16     perror("shmget");
 17     return 2;
 18   }
 19   printf("key: %u,shmid: %d\n",key,shmid);
 20   //实现共享内存和进程之间的链接
 21   char*mem=(char*)shmat(shmid,NULL,0);
 22   printf("attaches shm success\n");
 23   sleep(15);
 24 
 25   //断开共享内存和进程链接,不等于删除
 26   shmdt(mem);
 27   printf("detaches shm success \n");
 28   sleep(5);
 29 
 30   //共享内存的删除,这里有专门的删除函数,不用在命令行使用ipcrm删除了
 31   shmctl(shmid,IPC_RMID,NULL);
 32   printf("key: %u,shmid: %d->shm delete success\n",key,shmid);
 33   sleep(10);
 34   return 0;
 35 } 

client.c

 1 #include"comm.h"
  2 #include<unistd.h>
  3 int main()
  4 {
  5   key_t key=ftok(PATH_NAME,PROJ_ID);
  6   if(key<0)
  7   {
  8     perror("ftok");
  9     return 1;
 10   }
 11   printf("%u\n",key);
 12   //用户端获取共享内存,使用创建的方法,如果存在就直接使用了
 13   int shmid=shmget(key, SIZE,IPC_CREAT);
 14   if(shmid<0)
 15   {
 16     perror("shget");
 17     return 1;
 18   }
 19   //获取成功之后就可以attach了
 20   char*mem=(char*)shmat(shmid,NULL,0);
 21   sleep(15);
 22   printf("client process attach success\n");
 23 
 24   //client端断开
 25   shmdt(mem);
 26   printf("detaches shm success\n");
 27   sleep(5);
 28   return 0;
 29 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值