进程间通信是什么?如何实现进程间的通信

本文介绍了进程间通信的概念,强调了在独立执行的进程中需要共享区域进行通信。详细讲解了管道(匿名和命名管道)的工作原理和使用方法,以及共享内存的实现方式。通过实例展示了如何使用管道进行父子进程间的通信,以及如何通过共享内存实现不相关进程间的通信。

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

进程间通信

  • 由于进程之间在执行时,是完全独立执行的,所有,进程间如果需要进行通信,就需要一块共享的区域,我们可以通过创建一个多个进程共享的区域,将所需要共享的数据放在这个共享区当中,当进程间需要通信的时候,写数据的进程将数据写入该区域,需要读数据的进程将数据从该区域中读取出来。
    ##进程间通信的分类
  • 管道
  • System V进程间通信
  • POSIX IPC
    ##管道
  • 管道是进程间通信的一种手段,通过在内核中创建一个共享区域,来实现进程间的通信,注意,所在区域是 内核,内核,内核
    ###管道应该如何理解呢 ?
  • 管道在逻辑结构上就是一根管道,你可以理解为水管道,这根管道有两端,一个是读端,一个是写端
  • 管道的设计是半双工的,也就是,一个进程在对管道进行写操作的时候,其他进程只能读取,这个写的进程占据了这个管道的写端,除非这个进程关闭这个管道的写端,否则其他进程不能对这个管道进行写入操作,读端同理,也是只能有一个进程进行读取,直到这个进程关闭读端。那么为什么不能多个进程同时对这个管道进行读取或者写入呢? 因为如果多个进程对管道进行写入的话,那管道里存储的数据顺序就会乱,因为操作系统在进行进程调度的时候,每一个进程都可能会被随时切走和切入,这样的话,如果多个进程同时拥有同一个管道的读端或写端,就会出现读写混乱.大概可以理解为,一根管道同时只能被两个进程所使用,如果有多个进程同时使用一根管道的话,就会出现读写混乱的情况。
  • 在使用管道的时候,如果进程只进行写操作,那就关闭读操作。反之亦然。
  • 在linux中,一切皆文件,既然如此,那管道是就是文件,那进程在打开文件的时候,就会在PCB中的file_struct记录文件的文件描述符,后面对管道的一切操作都是对文件描述符的操作。(如果你不是很懂文件描述符是什么东西,我还有一个博客是介绍文件描述符的,可以去看看)

如何开辟一个匿名管道以及使用这个匿名管道

  • 在linux中,系统已经提供了管道的系统调用 ,下面的这个是匿名管道,在这个后面还有命名管道
  • 匿名管道只能进行有亲缘关系的进程进行通信,常用于父子进程间的通信
  • 命名管道可以进行任意进程间的通信
#include<unistd.h>

int pipe(fd[2]);//匿名管道
  • 这里面的参数 fd[0] 是管道的读端 fd[1]是管道的写端。我们等会对管道进行操作的时候,就是通过这两个文件描述符进行操作的。返回值如果是0,就表示管道申请成功。如果失败,返回失败码
  • 在下面的例子中,我用两个进程的方式,进行了一个进程间管道通信的演示,父进程读取,子进程写数据,最后,父进程将数据打印在屏幕上
  • 这个代码是匿名管道的创建方法,匿名管道只能进行有亲缘关系的进程进行通信,常用于父子进程
#include<stdio.h>
  #include<string.h>
  #include<unistd.h>
  #include<error.h>
  
  int main()
  {
      if(pipe(fd) != 0)//创建管道
      {perror("pipe create");}
      int id = fork();//创建进程
      if(id > 0)//父进程
      {
          char buf[100];
          close(fd[1]);
          sleep(5);
          ssize_t s = read(fd[0],buf,sizeof(buf));//从读端读取数据
          if(s < 0)
          {perror("read");}
          buf[s] = 0;//将读到的最后一个字符的后一个字符设置为 \0,方便打印
          printf("%s\n",buf);
      }
      else if( id == 0 )//子进程
      {
          int i = 0;
          const char *msg = "hello world\n";
          close(fd[0]);
          while(1)
          {
              write(fd[1],msg,strlen(msg));//向写端写数据
              sleep(1);
              i++;
              if(i > 5)
              {break;}
          }
      }
      else//创建进程失败
      {perror("fork create");}
      return 0;
  }

  • 以上便是匿名管道的创建和使用了

如何开辟一个命名管道,并使用这个命名管道进行进程间的通信

  • 命名管道相比于匿名管道,更像一个文件,命名管道通过在目录下创建一个管道文件,通过这个管道文件进行通信,而不是匿名管道那样,在内核中进行管道的创建。

相关函数

int fifo(const char *filename,mode_t mode);
  • 如果使用命名管道,需要注意的是mode的值,权限mode决定了这个管道可以进行写和读。
  • 为了验证管道可以进行不同非亲缘进程间的通信,我们可以采用两个程序,一个负责向管道里写,一个负责读,然后打印出来,这样的程序,来验证进程间的通信。
  • 命名管道在打卡的时候,进程必须知道,要打开的管道文件的路径,然后才能打开这个管道文件
  #include<stdio.h>   //这个进程负责读取管道的数据
  #include<stdlib.h>
  #include<unistd.h>
  #include<fcntl.h>
  #include<sys/types.h>
  #include<sys/stat.h>
  
  #define FIFO "./myfifo"
  
  int main()
  {
      umask(0);
  
      if(mkfifo(FIFO,0644) < 0)
      {
          perror("mkfifo");
          return 1;
      }
  
      char buf[1024];
      int fd = open(FIFO,O_RDONLY);
      if(fd > 0)
      {
          while(1)
          {
  
  
          ssize_t s = read(fd, buf, sizeof(buf) - 1);
          if(s > 0)
          {
              buf[s]= 0;
              printf("client->server : %s",buf);
  
          }else{
              printf("read error\n");
              exit(1);
  
          }
  
          }
      }
      else{
          perror("open file");
          return 1;
      }
      close(fd);
      return 0;
  }

 #include<stdio.h>//这个进程负责往管道里面写数据
 #include<string.h>
 #include<stdlib.h>
 #include<unistd.h>
 #include<fcntl.h>
 #include<sys/types.h>
 #include<sys/stat.h>
 
 #define FIFO "./myfifo"
 
 int main()
 {
     char buf[1024];                                         
     int fd = open(FIFO,O_WRONLY);
     if(fd < 0)
     {
         perror("open file");
     }
 
     while(1)
     {
         printf("Please Enter# ");
         fflush(stdout);
         ssize_t s = read(0,buf,sizeof(buf) - 1);
         if(s > 0)
         {
             buf[s] = 0;
             write(fd,buf,s);
         }
         else{
             break;
         }
     }
 
 
     close(fd);
     return 0;
 }

管道的四种情况

  • 读端不读,但是写端已经把缓冲区写满了
    这种情况的话,由于写端已经把缓冲区写满了,但是读端不读,管道就会阻塞,写端不能进行写入,直到读端开始将数据读走,才能继续进行写入
  • 写端不写,但是读端已经把缓冲区读完了
    如果读端已经将缓冲区读完了,缓冲区里已经没有数据了,读端就会阻塞,直到写端往缓冲区里写数据,读端才能继续读
  • 读端不读,同时关闭了读端
    读端如果不读数据,同时关闭了读端的话,就说明,写端写入的数据不会被读端读走,那么写端所写的数据就是没有意义的,所以,操作系统对此的处理就是,结束写进程
  • 写端不写,同时关闭了写端
    写端不写数据,但是关闭了写端,由于已经没有人会给写端里写数据了,读端在读完缓冲区里的所有数据后,读进程就会被操作系统杀掉

共享内存

  • 共享内存就和他的名字一样,是一个被多个进程所共享的一个内存区域,共享内存所在的区域是用户态,不需要像管道一样,如果需要数据的访问,就必须进行用户态和内核态的切换,提高了效率。
  • 共享内存存在的时间,是不受进程结束限制的,即使一块共享内存没有一个进程挂接,但是依旧是存在于内存中的数据区的,所以,在使用完共享内存之后,一定要记得释放共享内存。
    
  • 共享内存在使用的时候,需要进程显示的挂接共享内存,才能进行共享内存的使用,当共享内存的挂接数不为0时,是不能被释放的。一块共享内存可以被多个进程所挂接,一个进程也可以挂接多个共享内存
  • 共享内存在开辟的时候,需要一个唯一的共享内存标识,记住,这个共享内存标识,是多少不重要,重要的是,必须是唯一的,要产生这个唯一的共享内存标识, 就需要调用一个共享内存标识生成方法 ---- ftok ,通过这个方法,可以产生一个这样的唯一的标识,然后用这个标识,去向操作系统申请共享内存

共享内存接口展示

int shmget(key_t key,size_t size,int shmflg);//申请共享内存
//key :共享内存段的名字,必须是唯一的  
//size:你所要开辟的共享内存的大小
//shmflg :权限,具体是啥,可以查看man手册
void *shmat(int shmid, const *shmaddr, int shmflg);//挂接共享内存
//shmid :在进行shmget的时候,返回的值就是id
//shmaddr:连接指定的地址,想让核心自动选择的话,设置为NULL
//shmflg: 也是权限 两个值 SHM_RND和SHM_RDONLY
int shmdt(const void *shmaddr);//删除挂接共享内存
//shmaddr:由shmat所返回的指针
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//删除共享内存
//shmid:有shmget所获得的标识
//cmd:要采取的动作 三个 有:IPC_STAT IPC_SET IPC_RMID,最后一个是删除共享内存段
//指向一个保存着共享内存模式状态和访问权限的数据结构,一般是NULL
  • 为了实现两个不相干的进程间进行通信,我和前面一样,通过创建两个进程,一个负责向共享内存里写数据,一个负责从共享内存里读数据,通过这种形式,实现共享内存的进行间通信

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666 
int main()
{
	int ret;
	key_t key = ftok(PATH_NAME,PROJ_ID);//ftok函数通过路径名加一个proj_id来生成一个唯一的共享内存标识符 key
	if(key < 0)
	{
		perror("ftok");
		ret = 1;
		return ret;
	}
	int shm_id = shmget(key,4096,IPC_CREAT);//创建一个共享内存,如果这个共享内存已经存在了,就返回这个共享内存,如果不存在,就返回错误
	if(shm_id == -1)
	{
		perror("shmget");
		ret = 2;
		return ret;
	}
	void *str = shmat(shm_id,NULL,0);//挂接共享内存
	if((signed long long )str < 0)
	{
		perror("shmat");
		ret = 3;
		return ret;
	}
	int i;
	for(i = 0;i < 26;i++)
	{
		((char *)str)[i] = 'A' + i;//向这个共享内存里写数据
		sleep(1);
	}
	return 0;
}              

 #include<unistd.h>
 
 #include<string.h>
 #include<stdio.h>
 #include<sys/types.h>
 #include<sys/ipc.h>
 #include<sys/shm.h>
 
 #define PATH_NAME "/tmp"
 #define PROJ_ID 0x6666 
 int main()
 {
     int ret;
     key_t key = ftok(PATH_NAME,PROJ_ID);
     if(key < 0)
     {
         perror("ftok");
         ret = 1;
         return ret;
     }
     int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);//创建共享内存,如果这个共享内存不存在就创建它,然后返回
     if(shm_id == -1)
     {
         perror("shmget");
         ret = 2;
         return ret;
     }
     void *str = shmat(shm_id,NULL,0);//挂接共享内存
     if((signed long long )str < 0)
     {
         perror("shmat");
         ret = 3;
         return ret;
     }
     int i = 0;
     sleep(3);
     for(i = 0;i < 26; i++)
     {
         printf("cilent -> server:%s\n",(char *)str);//输出这个共享内存里的数据
         sleep(1);
     }
     shmdt(str);
     shmctl(shm_id,IPC_RMID,NULL);
 }

以上就是进程间通信的一部分东西了,包括匿名管道,命名管道,共享内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值