Part4. 管道通信

往期回顾:
Part0. 实验环境
Part1-1.熟悉UKylin环境
Part1-2.熟悉UKylin环境
Part2.进程控制
Part3.进程通信

一、实验目的

1.了解管道的概念。
2.掌握Linux支持的管道通信方式。

二、实验内容

  1. 编写一段程序,实现进程的管道通信。使用pipe()建立一个管道。子进程p1向管道写一句话:
    Child process is sending message!
    而父进程则从管道中读取来自于子进程的信息,显示在屏幕上。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<stdlib.h>
int pid1;   //存储子进程的PID号;/******************主函数*****************/
int main()
{int fd[2];                     //打开文件的文件描述符,fd[1]是写入端,fd[0]是读出端char OutPipe[100],InPipe[100];  //存储要写入管道的字符串pipe(fd);                      //创建管道---建立一无名管道while((pid1 = fork()) == -1);   //创建子进程if(pid1 == 0)                   //子进程{ sprintf(OutPipe,"Child process is sending message!");    //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);    //写进程从管道的写入端(句柄1)将50个字节数据写入管道sleep(1);                   //延时1秒   exit(0);                    //子进程结束}else{wait(0);                    //等待子进程执行完毕read(fd[0],InPipe,50);      //读进程从管道的读出端(句柄0)读出50个字节数据printf("%s\n",InPipe);      //父进程将字符串显示在屏幕上exit(0);                    //或者 return 0;}return  0;
}

代码修改:
要求:修改程序实现10次读、写管道文件的操作,应如何实现,请编写代码,并运行程序分析结果。

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<stdlib.h>
int pid1;   //存储子进程的PID号;
/**************************主函数***********************/
int main()
{int fd[2];                     //打开文件的文件描述符,fd[1]是写入端,fd[0]是读出端char OutPipe[100],InPipe[100];  //存储要写入管道的字符串int cnt = 10;             pipe(fd);                       //创建管道---建立一无名管道while((pid1 = fork()) == -1);   //创建子进程if(pid1 == 0)                   //子进程{ for(int i=0;i<cnt;i++){sprintf(OutPipe,"Child process is sending message!");    //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);  //从管道的写入端(句柄1)将50个字节数据写入管道printf("#%d:Message sent from child process successfully!\n",i);sleep(2);                  //延时2秒}}else                               //父进程{for(int i=0;i<cnt;i++){read(fd[0],InPipe,50);       //从管道的读出端(句柄0)读出50个字节数据printf("#%d:%s\n",i,InPipe); //父进程将字符串显示在屏幕上}wait(0);exit(0);}return  0;
}

2.编写一段程序,实现进程的管道通信。使用pipe()建立一条管道线。两个子进程p1和p2分别向管道各写一句话:
Child 1 is sending message!
Child 2 is sending message!
而父进程则从管道中分别读出来自于两个子进程的信息,显示在屏幕上。

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>int pid1,pid2;    //存储两个子进程的PID号;
/*************************主函数**********************/
int main()
{int fd[2];                     //打开文件的文件描述符char OutPipe[100],InPipe[100]; //存储要写入管道的字符串pipe(fd);                      //创建管道---建立一无名管道while((pid1 = fork()) == -1);  //创建子进程if(pid1 == 0)                  //子进程{lockf(fd[1],1,0);          //lockf(fd,1,0)给文件上锁,使实现互斥访问sprintf(OutPipe,"Child 1 is sending message!");  //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);   //从管道的写入端(句柄1)将50个字节数据写入管道lockf(fd[1],0,0);          //lockf(fd,0,0)是文件解锁sleep(1);                  //延时1秒 exit(0);                   //子进程结束}else {while((pid2 = fork()) == -1);  //创建子进程if(pid2 == 0){sleep(0);lockf(fd[1],1,0);          //lockf(fd,1,0)给文件上锁,使实现互斥访问sprintf(OutPipe,"Child 2 is sending message!");    //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);  //从管道的写入端(句柄1)将50个字节数据写入管道lockf(fd[1],0,0);         //lockf(fd,0,0)是文件解锁sleep(1);exit(0);                  //子进程结束}else                          //父进程{wait(0);                  //等待子进程执行完毕wait(0);                  //等待子进程执行完毕read(fd[0],InPipe,50);    //从管道的读出端(句柄0)读出50个字节数据printf("%s\n",InPipe);    //父进程将字符串显示在屏幕上//wait(0);read(fd[0],InPipe,50);printf("%s\n",InPipe);exit(0);                  //或者 return 0;}}     return 0;
}


输出结果如上图。其中在代码中增加了两对lockf。当子进程在向管道中写数据时,加锁;写完数据后,解锁。这样可以防止多端输入发生冲突,保证写入数据的稳定性和准确性,实现资源的互斥与同步访问。其中lockf(fd,0,0)表示文件解锁;lockf(fd,1,0)表示给文件加锁。

其实,管道本身具有同步互斥的机制,即在进行管道读写操作时不会被打断,所以即使没有加锁也是可以正常执行的。当某一个子进程被调度,向管道中写入数据后,等一段时间,等待管道中数据被读,读完管道之后,才允许下一次写入。

附链接:https://blog.csdn.net/jnu_simba/article/details/11746217
https://blog.csdn.net/qq_36829091/article/details/80138836

Point1:pipe的特点:

  • 只能单向通信
  • 只能血缘关系的进程进行通信
  • 依赖于文件系统
  • 生命周期随进程
  • 面向字节流的服务
  • 管道内部提供了同步机制

Point2:四个特殊情况:

  1. 如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样
  2. 如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
  3. 如果所有指向管道读端的文件描述符都关闭了,这时有进程指向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
  4. 如果有指向管道读端的文件描述符没关闭,而持有管道写端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再write会阻塞,直到管道中有空位置了才写入数据并返回。

3.编写一段程序,实现:在父进程中用pipe()建立一条管道,往管道里写入字符串,两个子进程分别接收来自父进程写入的两个字符串。

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>int pid1,pid2;    //存储两个子进程的PID号;
/*************************主函数**********************/
int main()
{int fd[2];                     //打开文件的文件描述符char OutPipe[100],InPipe[100]; //存储要写入管道的字符串pipe(fd);                      //创建管道---建立一无名管道while((pid1 = fork()) == -1);  //创建子进程if(pid1 == 0)                  //子进程{read(fd[0],InPipe,50);     //从管道的读出端(句柄0)读出50个字节数据printf("#pid1 received: %s\n",InPipe);          //子进程将字符串显示在屏幕上exit(0);}else {while((pid2 = fork()) == -1);if(pid2 == 0){//sleep(1);read(fd[0],InPipe,50);   //从管道的读出端(句柄0)读出50个字节数据printf("#pid2 received: %s\n",InPipe);      //子进程将字符串显示在屏幕上sleep(1);                 //延时1秒exit(0);}else                          //父进程{sprintf(OutPipe,"Parent is sending first message!");       //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);                                   //从管道的写入端(句柄1)将50个字节数据写入管道sleep(1);wait(0);                  //等待子进程执行完毕            sprintf(OutPipe,"Parent is sending second message!");      //将字符串输出到OutPipe(目的字符串)中write(fd[1],OutPipe,50);//wait(0);                  //等待子进程执行完毕//wait(0);                  //等待子进程执行完毕exit(0);}}       return 0;
}


结果分析:
由父进程创建一个管道pipe,向管道中写入两个字符串,子进程读取管道中的字符串并显示到屏幕,多数情况下是pid2先打印输出(父进程写入管道的字符串),但从逻辑上分析,进程pid1和pid2谁先读管道内容具有随机性。如果希望强制pid1先读取,可以在pid2读取数据前加sleep函数,输出结果如截图所示。
其他几种输出显示:
1)去掉pid2子进程的sleep函数,输出如下图:

2)去掉父进程的一个wait函数或者“写—wait—写”模式下,输出如下图:

3)父进程“写—wait—写—wait”模式下,输出:

【实验相关资料】
一、什么是管道
UNIX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是UNIX系统的一大特色。
所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。

句柄fd[0]

句柄fd[1]
读出端

写入端

二、管道的类型:
1.有名管道
一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用open( )打开。
2.无名管道
一个临时文件。利用pipe( )建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe( )的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。二种管道的读写方式是相同的,本次实验使用无名管道。
3.pipe文件的建立
分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户文件描述符
4.读/写进程互斥
内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的直接地址项。因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。

三、本次实验所涉及的系统调用
1.pipe( )创建无名管道
建立一无名管道。
系统调用格式
pipe(filedes)
参数定义
int pipe(filedes);
int filedes[2];
其中,filedes[1]是写入端,filedes[0]是读出端。
该函数使用头文件如下:

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

2.lockf 文件锁
允许将文件区域用作信号量(监视锁),或用于控制对锁定进程的访问(强制模式记录锁定)。
系统调用格式
int lockf(int fd, int cmd, off_t len);
参数说明:
①fd 是打开文件的文件描述符。
为通过此函数调用建立锁定,文件描述符必须使用只写权限(O_WRONLY)或读写权限(O_RDWR)打开。如果调用进程是具有PRIV_LOCKRDONLY 权限的组的成员,它也可以使用lockf()来锁定使用只读权限(O_RDONLY)打开的文件。
②cmd 是指定要采取的操作的控制值,允许的值在中定义。
如下所示:

# define F_ULOCK 0 //解锁
# define F_LOCK 1 //互斥锁定区域
# define F_TLOCK 2 //测试互斥锁定区域
# define F_TEST 3 //测试区域

F_TEST 用于检测在指定的区域中是否存在其他进程的锁定。如果该区域可访问,lockf()将返回 0,否则返回−1;在这种情况下,errno 设置为[EACCES]。F_LOCK 和 F_TLOCK 都用于锁定文件的某个区域(如果该区域可用)。F_ULOCK 用于删除文件区域的锁定。
③len是要锁定或解锁的连续字节数。
要锁定的资源从文件中当前偏移量开始,对于正 len 将向前扩展,对于负 len 则向后扩展(直到但不包括当前偏移量的前面的字节数)。如果 len 为零,则锁定从当前偏移量到文件结尾的区域(即从当前偏移量到现有或任何将来的文件结束标志)。要锁定一个区域,不需要将该区域分配到文件中,因为这样的锁定可以在文件结束标志之后存在。
3.read( )
系统调用格式
read( fd ,buf ,nbyte )
功能:从fd(一般是fd[0])所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。
参数定义
int read(fd,buf,nbyte);
int fd;
char *buf;
unsigned nbyte;
4.write( )
系统调用格式
write(fd,buf,nbyte)
功能:把nbyte 个字节的数据,从buf所指向的缓冲区写到由fd(一般是fd[1])所指向的文件中。如文件加锁,暂停写入,直至开锁。
参数定义同read( )。

————
The End

那写看似毫无波澜的日复一日,会在某一天 让你突然发现努力的意义。 “小忙”加油!
无悔昨天 & 感谢今天 & 喜欢明天~

实验四:《操作系统》之管道通信相关推荐

  1. 操作系统实验四——使用命名管道实现进程通信

    操作系统实验四--使用命名管道实现进程通信 一. 实验目的 (1)了解windows系统环境下的进程通讯机制. (2)熟悉Windows系统提供的进程通信API. 二. 实验准备 相关API函数介绍 ...

  2. linux管道通信题目,操作系统实训(Linux)——习题解答、例题解析、实验指导-王红-实验实验7软中断及管道通信课案.ppt...

    操作系统实训(Linux)--习题解答.例题解析.实验指导-王红-实验实验7软中断及管道通信课案.ppt 实验7 软中断及管道通信 一.实验目的(1)掌握linux系统软中断通信的实现方法.(2)掌握 ...

  3. Linux系统无名管道通信实验,Linux进程间通信(二)---管道通信之无名管道及其基础实验...

    管道简介 管道是Linux中进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入(其实我更愿意将管道比喻为农村浇地的管子).Linux的管道主要包括两种:无名管道和有名管道.这一节主要讲 ...

  4. 实验四:进程同步与通信

    一.实验目的: 1.掌握基本的同步与互斥算法,理解P,V操作. 2.理解生产者消费者模型,了解其它典型的同步互斥模型,如哲学家就餐.读者-写者模型等. 3.学习使用Windows中基本的同步对象,掌握 ...

  5. Linux操作系统实验系列之实验四管道通信

    一.实验目的 1.了解什么是管道 2.熟悉UNIX/LINUX支持的管道通信方式 二.实验内容: 编写程序实现进程的管道通信.用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句 ...

  6. 操作系统真象还原实验记录之实验三十四:实现管道

    操作系统真象还原实验记录之实验三十四:实现管道 1.管道相关知识总结 先说我们操作系统的管道实现: 上述图中,管道缓冲区就是一页内存,这一页内存被我们当成了环形缓冲区结构, 当这页管道被创建出来后,全 ...

  7. 【操作系统实验】Linux进程通信—共享内存通信、管道通信

    Linux进程通信-共享内存通信.管道通信 一.实验目的: 二.实验题目: 1. 试设计程序利用共享内存完成如下进程通信 1.shmget函数 2.shmat函数 3.shmdt函数 4.shmctl ...

  8. 2020-10-29 实验四 进程同步与通信

    实验四 进程同步与通信 一.实验目的: 二.实验环境: 三.实验内容: 四.心得体会: 一.实验目的: 1. 掌握基本的同步与互斥算法,理解P,V操作. 2. 理解生产者消费者模型,了解其它典型的同步 ...

  9. 广州大学2020操作系统实验四:文件系统

    相关资料 广州大学2020操作系统实验一:进程管理与进程通信 广州大学2020操作系统实验二:银行家算法 广州大学2020操作系统实验三:内存管理 广州大学2020操作系统实验四:文件系统 广州大学2 ...

最新文章

  1. python绘制直方图根据不同分类_如何在python中绘制具有多个类别的直方图
  2. Kindeditor中上传本地照片后需要带域名的绝对路径实际获取为相对路径
  3. locate 和 find
  4. 用python计算准确率_Python中计算模型精度的几种方法,Pytorch,中求,准确率
  5. appium for java教程_appium自动化测试入门(java版)
  6. PHP中cURL错误号对照[转]
  7. centos7 安装lsb_Docker安装与配置
  8. 随想录(内核模块的测试方法)
  9. java 静态方法 非静态变量_深度分析:Java 静态方法/变量,非静态方法/变量的区别,今天一并帮你解决!...
  10. java电脑控制对方手机_电脑控制大师手机专家多控系统-Total Control电脑控制手机助手下载V7.0.0官方电脑端32位/64位最新版-西西软件下载...
  11. hprose php,hprose和swoole区别
  12. python实现爬取网易云音乐评论,并且将评论信息存储到pymysql
  13. 聊聊技术人的“绩效考核”
  14. java 字符串像素_如何在JavaFX中计算字符串的像素宽度?
  15. Linux /usr/src/kernels 目录为空的解决方法
  16. 华为服务器安装系统流程图,华为a安装手册.docx
  17. 【毕设】基于HTML的零食商城网站大前端开发设计(含文档)
  18. twig  之基本语法
  19. EndNote中英文混排
  20. Hadoop安全之Kerberos

热门文章

  1. php curlesslcacert,Shipyard集群化docker管理平台部署
  2. 禅道开源版安装 - windows
  3. Cadence 软件快捷操作
  4. 反击!紫光集团怒斥赵伟国
  5. 秋冬季健康生活小常识
  6. 图像处理之opencv图片几何变化操作大全
  7. zcmu-1410: Polynomial Showdown
  8. 解决react native打包apk文件安装好之后进入应用闪退的问题
  9. CRC32、murmur32、SDBM32碰撞实验数据对比
  10. java poi 追加_使用POI 向Excel中追加数据