进程间通讯 管道,操作系统实验进程的管道通信

  进程间通讯 管道,操作系统实验进程的管道通信

  进程间通信——管道IPC——进程间通信的方式有很多种,管道是最基本的一种。流水线是半双工的,即单向的。这是一个管道先进先出(先入先出)。在实际的多进程通信中,可以理解为有一个管道,每个进程有两个可以使用管道的‘端口’,分别负责读取和发送数据。单个进程中的管道:int fd[2]使用文件描述符fd[1]将数据写入管道。使用文件描述符fd[0]从管道中读取数据。

  注意:单个进程中的管道没有实际用途。管道用于多进程通信。管道创建函数的原型:int pipe(int pipe FD[2]);返回值:成功:返回0。失败:返回-1。注意:获取两个“文件描述符”,分别对应于流水线的读端和写端。Fd[0]:是流水线的读取端;Fd[1]:是流水线的写结束;如果写入fd[0]并读取fd[1],可能会导致意外错误。管道1的使用示例:单个进程使用管道进行通信。注意:创建管道后,可以得到管道的两个文件描述符,不需要使用普通文件操作中的open操作。如下图所示:

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void)

  {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  strcpy(buff1,你好!);

  write(fd[1],buff1,strlen(buff 1));//写一个你好

  printf(发送信息:%s\n ,buff 1);

  bzero(buff2,sizeof(buff 2));

  read(fd[0],buff2,sizeof(buff 2));//读hello

  printf(接收的信息:%s\n ,buff 2);

  返回0;

  }

  示例2:多进程使用管道进行通信。注意:创建管道后,将创建子进程。此时,有4个文件描述符和4个端口。父进程和子进程分别有一个读端口和一个写端口,如下图所示:

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void)

  {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  pid _ t pd

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  PD=fork();

  if (pd==-1) {

  printf(fork错误!\ n’);

  出口(1);

  } else if (pd==0) {

  //子进程先读写

  bzero(buff2,sizeof(buff 2));

  read(fd[0],buff2,sizeof(buff 2));//数据被没收时read会阻塞。

  printf(process(%d)收到的信息:%s,buff2的地址:%p\n ,getpid(),buff2,buff 2);

  睡眠(5);

  你好,爸爸!);

  write(fd[1],buff1,strlen(buff 1));

  }否则{

  //父进程先写后读

  strcpy(buff1, Hello Kid );

  write(fd[1],buff1,strlen(buff 1));

  睡眠(5);

  bzero(buff2,sizeof(buff 2));

  read(fd[0],buff2,sizeof(buff 2));

  printf(process(%d)收到的信息:%s,buff2的地址:%p\n ,getpid(),buff2,buff 2);

  }

  if (pd 0) {

  wait();

  }

  返回0;

  }

  注:大家可以看到,我们都在父子进程中打印了buff2的地址,发现打印出来的(虚拟)地址是一样的,只是内容不同。一个是你好孩子,另一个是你好爸爸。其实是两个不同的地址。调用fork()函数创建子进程后,子进程会复制父进程的所有资源。例3:子进程用execl启动新程序时管道的作用细节:有两个程序p1和p2,它们通过管道相互通信。p1发送一个字符给p2,p2接收后在屏幕上打印出来。具体操作流程:p1创建管道。创建子进程。在子进程中使用execl()函数将子进程替换为程序p2。(使用execl函数时,将管道的读取端作为的参数。)在父进程中,通过管道向子进程发送一个字符串。P2从参数(该参数是p2的主函数的参数)中获取管道的读取端。阅读管道。打印出读取的字符串。Execl()函数原型int execl (constchar * path,constchar * arg,);函数——学习Linux下的execl函数当一个进程调用一个exec函数时,这个进程就被一个新的程序完全取代,新的程序从它的main函数开始执行。因为调用exec不会创建新的进程,所以它前后的进程ID没有改变。Exec只是用另一个新程序替换了当前进程的主体、数据、堆和堆栈段。

  Argc和argv—— [c]主函数argc和argv argc的参数:是argument count的缩写,保存运行时传递给主函数的参数个数。Argv:是argument vector的缩写,它保存运行时传递给main函数的参数。类型是一个字符指针数组,每个元素都是一个字符指针,指向一个命令行参数。例如:main3.c

  #包括unistd.h

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void) {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  pid _ t pd

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  PD=fork();

  if (pd==-1) {

  printf(fork错误!\ n’);

  出口(1);

  } else if (pd==0) {

  //bzero(buff2,sizeof(buff 2));

  sprintf(buff2, %d ,FD[0]);//读取

  execl(main3_2 , main3_2 ,buff2,0);//子进程被程序main3_2替代。

  printf(execl错误!\ n’);

  出口(1);

  }否则{

  strcpy(buff1,你好!);

  write(fd[1],buff1,strlen(buff 1));//写

  printf(process(%d)发送信息:%s\n ,getpid(),buff 1);

  }

  if (pd 0) {

  wait();

  }

  返回0;

  } main3 _ 2.c

  #包括unistd.h

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(int argc,char* argv[])

  {

  int fd

  char buff[1024]={0,};

  sscanf(argv[1], %d ,FD);

  read(fd,buff,sizeof(buff));

  printf(Process(%d)收到的信息:%s\n ,getpid(),buff);

  返回0;

  }

  示例4:关闭管道的读/写端。注意:以下所有情况都在两个进程下,即一个主进程和一个子进程。小例子1:主进程关闭写进程后,无法通过管道向子进程发送数据。此时子进程使用read函数读取数据,如果没有数据读取,就会阻塞。代码结果如下:

  说明:主进程循环5次向子进程发送数据。5次之后,子进程无法接收主进程的数据,read()开始阻塞。#包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void) {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  pid _ t pd

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  PD=fork();

  if (pd==-1) {

  printf(fork错误!\ n’);

  出口(1);

  } else if (pd==0) {

  for(;){

  bzero(buff2,sizeof(buff 2));

  睡眠(3);

  read(fd[0],buff2,sizeof(buff 2));

  printf(process(%d)收到的信息:%s\n ,getpid(),buff 2);

  }

  }否则{

  for(int I=0;i i ){

  strcpy(buff1,你好!);

  write(fd[1],buff1,strlen(buff 1));

  睡眠(3);

  printf(process(%d)发送信息:%s\n ,getpid(),buff 1);

  }

  }

  if (pd 0) {

  wait();

  }

  返回0;

  }

  例2:管道是‘共享’的,个人理解。注意,实际上,它不是同一个内存地址。

  读数据的时候,流水线读端的数据会越读越少,而写数据的时候,会把写好的数据累加到尾部。

  如下图所示,

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void) {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  pid _ t pd

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  PD=fork();

  if (pd==-1) {

  printf(fork错误!\ n’);

  出口(1);

  } else if (pd==0) {

  for(;){

  bzero(buff2,sizeof(buff 2));

  睡眠(3);

  strcpy(buff1,爸爸!);

  //子进程写入数据

  write(fd[1],buff1,strlen(buff 1));

  }

  }否则{

  for(int I=0;i i ){

  bzero(buff2,sizeof(buff 2));

  strcpy(buff1,你好!);

  //父进程写入数据

  write(fd[1],buff1,strlen(buff 1));

  睡眠(10);

  //父进程读取数据

  read(fd[0],buff2,sizeof(buff 2));

  printf(dad进程(%d)收到的信息:%s\n ,getpid(),buff 2);

  睡眠(3);

  }

  }

  if (pd 0) {

  wait();

  }

  返回0;

  }

  摘要:当没有要读取的数据时,读取将会阻塞。比如有两个进程,主进程给子进程发送数据,主进程的写端是关闭的,所以不能给子进程发送数据,那么子进程的读就会被阻塞。写端关闭后,写不会阻塞。这里应该注意的是,除非关闭写入端,否则写入不会阻塞。关闭读取端后,读取不会被阻止。上面说的关闭是针对一个进程的,每个进程都有一个写者和一个读者。如果有多个进程,并且每个进程的写端都是关闭的,read()就不会阻塞。提示:为了避免不必要的麻烦,比如没有可读数据时读功能被阻塞,我们可以关闭无用的管道端口。例如,如果主进程只负责写数据,子进程只负责读数据,那么可以关闭父进程的读端和子进程的写端(当然要看实际情况),将 4端口管道变成单向 2端口管道,如下图所示:

  示例5:使用管道作为标准输入和标准输出的优势:

  当一个子进程用exec启动一个新进程时,不再需要将管道的文件描述符传递给新程序。一种可以是标准输入(或标准输出)的程序。实施流程:

  使用dup复制文件描述符。用exec启动新程序后,在原始进程中打开的文件描述符被抛出。您可以在原始进程中共享文件描述符。补充:

  Dup函数:使用dup函数复制原文件描述符指向的内容,使用当前系统(进程)可用的最小文件描述符。示例:先关闭标准输入文件描述符,然后用dup复制一个当前文件描述符,再关闭原文件描述符,完成文件描述符的替换。原型:int dup(int oldfd);返回值:成功:返回一个新的文件描述符。失败:返回-1并设置错误号。Execlp函数:exec函数可以用新进程替换当前进程,新进程与原进程具有相同的PID。参考—— linux系统编程流程(五):exec系列函数(execl、execlp、execv、execvp)使用main5.c

  #包括unistd.h

  #包含stdlib.h

  #包含stdio.h

  #包含字符串. h

  int main(void) {

  int FD[2];

  int ret

  char buff 1[1024];

  char buff 2[1024];

  pid _ t pd

  ret=管道(FD);

  如果(ret!=0) {

  printf(创建管道失败!\ n’);

  出口(1);

  }

  PD=fork();

  if (pd==-1) {

  printf(fork错误!\ n’);

  出口(1);

  } else if (pd==0) {

  //bzero(buff2,sizeof(buff 2));

  //sprintf(buff2, %d ,FD[0]);

  close(FD[1]);

  关闭(0);//关闭标准输入文件描述符

  dup(FD[0]);//复制fd[0]并使用最小的可用文件描述符作为该文件描述符。

  //也就是说,这个子进程用管道的读取端替换了标准的输入文件描述符。

  close(FD[0]);//关闭原始阅读器

  execlp(。/od.exe“,”。/od.exe ,-c ,0);

  //如果execlp成功执行,将不会执行以下内容

  printf(execl错误!\ n’);

  出口(1);

  }否则{

  close(FD[0]);//关闭读取端

  //写

  strcpy(buff1,你好!);

  write(fd[1],buff1,strlen(buff 1));

  printf(发送.\ n’);

  close(FD[1]);//关闭写入端

  }

  返回0;

  }od.c

  #包含stdio.h

  #包含stdlib.h

  int main(void){

  int ret=0;

  char buff[80]={0,};

  //scanf从标准输入读取——。在这个例子中,它实际上从管道中读取3354。

  ret=scanf(%s ,buff);

  printf([ret: %d]buff=%s\n ,ret,buff);

  ret=scanf(%s ,buff);

  printf([ret: %d]buff=%s\n ,ret,buff);//第二次scanf失败,返回-1

  返回0;

  }

  使用popen/pclosepopen的作用:用于在两个进程之间传递数据:在程序a中使用popen调用程序b时,有两种用法:程序a读取程序b的输出(使用fread读取);程序A向程序B发送数据,作为程序B的标准输入(用fwirte编写)。原型:file * popen (constchar * command,const char * type);返回值:成功:返回文件*(文件指针)。失败:返回null。1:读取外部程序# includesdio.h的输出

  #包含stdlib.h

  #定义BUFF_SIZE 1024

  int main(void){

  文件*文件;

  char BUFF[BUFF _ SIZE 1];

  int cnt

  //system( ls-l result . txt );

  file=popen(ls -l , r );//通过reading读取这个程序ls -l的输出结果。

  如果(!File) {//确定打开是否成功。

  printf(fopen失败!\ n’);

  出口(1);

  }

  cnt=fread(buff,sizeof(char),BUFF_SIZE,file);//从文件指针读取fread。

  if (cnt 0) {

  buff[CNT]= \ 0 ;

  printf(%s ,buff);

  }

  pclose(文件);//关闭

  返回0;

  }

  示例:将输出写入外部程序main7.c

  #包含stdio.h

  #包含stdlib.h

  #包含字符串. h

  #定义BUFF_SIZE 1024

  int main(void){

  文件*文件;

  char BUFF[BUFF _ SIZE 1];

  int cnt

  file=popen(。/p2 , w );

  如果(!文件){

  printf(fopen失败!\ n’);

  出口(1);

  }

  strcpy(buff, hello world!我是123456789testtest!);

  cnt=fwrite(buff,sizeof(char),strlen(buff),file);

  pclose(文件);

  返回0;

  }p2.c

  #包含stdio.h

  #包含stdlib.h

  #包含字符串. h

  int main(int argc,char* argv[]){

  int fd

  char buff[1024]={ \ 0 };

  int cnt=read(0,buff,sizeof(buff));

  if(CNT 0)buff[CNT]= \ 0 ;

  printf(receive: %s\n ,buff);

  返回0;

  }

  popen的原理是先用fork创建一个子进程,然后在子进程中用exec执行指定的外部程序,并返回一个指向父进程的文件指针(FILE*)。当使用 r 时,此文件*指向外部进程的标准输出。当使用 w 时,该文件*指向外部程序的标准输入。popen的优缺点:可以使用shell扩展(比如命令中可以使用通配符)。好用。缺点:每次调用popen都会启动两个进程(shell和指定程序)。资源消耗大。

  转载请联系作者取得转载授权,否则将追究法律责任。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: