1.进程间通信介绍(ipc)
- 进程是计算机系统分配资源的最小单位(严格说来是线程)。
- 每个进程都有自己的一部分独立的系统资源,彼此是隔离的,也就是说进程具有独立性。
为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。
进程间通信,简称为 ipc
(interprocess communication),顾名思义,就是进程与进程之间进行数据的交流,os保证了各进程之间相互独立,但这不意味着进程与进程之间就必须完全隔离开,在不少的情况下,进程之间需要相互配合共同完成某项任务,这就要求各进程之间能够互相交流。
1.1、进程间通信的概念
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(ipc,interprocess communication)
各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。
所以
- 我们两个要通信的进程,本质是要访问操作系统。
- 进程代表用户,“资源”从创建,使用,到释放,都是在调用系统调用接口。
- 有很多进程在通信,就会创建很多内存区域来通信,操作系统就会对这些内存区域来先描述后组织——操作系统有独立的通信模块,这个通信模块隶属于文件系统
- 进程间通信是有标准的
1.2.进程间通信的目的
进程间通信的目的和原因,有如下几个点
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
总得来说,实现进程间通信就是为了进程之间能够协同完成某项任务
1.3..进程间通信的本质
看到同一份资源还不够,通信的根本目的是要传输数据
因此通信的本质就是”数据的拷贝“
1.4.进程间通信分类
资源由谁(os的哪些模块)提供 , 就有了不同的进程间通信方式!
这里的模块可以是: (文件–管道) , (os内核ipc提供- systemv ipc) , (网络–套接字)
管道可以说是十分古老且简单了,适合深入学习,探究进程间通信时的原理及执行流程
2.管道通信
管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。
该缓冲区可以看做是一个循环队列,读和写的位置都是自动增长的,不能随意改变,一个数据只能被读一次,读出来以后在缓冲区就不复存在了。
- 描述已打开文件的结构体files_struct, 其中存储着 指向打开文件的数组
fd_array
, 此数组的类型是struct files*
. - 此外,操作系统会为每个打开的文件配置一个缓冲区
- 无论是对文件进行读和写,都得先把文件加载到内存中才行
- 操作系统可以创建一个在磁盘中不存在的内存文件
linux
中一切皆文件,所以管道本质上就是一个文件 ,但是这个文件很特殊, 向这个文件中写入数据实际上并不会真正写入磁盘中.
在介绍linux系统的文件描述符时, 简单介绍了linux系统中 描述已打开文件的结构体files_struct, 其中存储着 指向打开文件的数组fd_array
, 此数组的类型是 struct files*
.
而这个files_struct中, 直接或间接描述了文件的所有属性, 以及 此文件的缓冲区相关信息:
缓冲区信息中, 包含着描述文件的inode结构体, 而inode结构体中其实描述着一个联合体:
这个处于inode结构体中的联合体, 其实就是为了标识这个文件的类型, 其中pipe 就表示此文件的类型是管道文件.
通过文件的inode, 系统可以辨别出打开的文件是管道文件.
向管道文件写入数据实际上并不会写到磁盘上,而只是写到文件的缓冲区中, 因为管道文件主要是用来进程间通信的, 如果先写入磁盘另一个进程再读取, 整个过程就太慢了
这种不实际存储数据的行为特点, 其实也符合生活中管道的特点,——管道不能用来存储资源, 只能用来传输资源
并且, 除了管道不实际存储资源以外, 管道还有一个特点:管道是单向传输的
这是管道的特点, linux的管道也是遵循这个特点的, 也就是说, 两个进程间使用管道通信时, 其中一个进程若以只写方式打开管道, 那么另一个进程就只能以只读方式打开文件.
管道通信主要是借助文件系统来实现的,怎么理解呢?
我们假设现在系统上的进程a和进程b要互相通信,a不能直接去b里面读数据,因为进程具有独立性,那该怎么办呢?这就需要找一块空间c,空间c用来存放通信双方通信的数据,现在进程a要给b发送数据,那么a和b要向系统声明建立连接,申请一块空间c,然后a往空间c里发送数据,b从空间c里读取数据,这样a就实现了和b的通信,这块空间c就像一根管道一样,连接着a与b,整个管道通信的基本原理就是如此,当然这只解释了管道名称的由来,并没有解释管道通信是借助文件系统来实现的
我们要理清楚如何在linux系统中让两个进程读取到同一块内存空间,如果看过我的基础i/o篇的同学应该会想到,那就是通过文件,进程从磁盘中或除自身以外的其他可读写的内存区域中读取或写入数据主要是通过文件系统来解决的,只要系统在内存中创建一个文件,a进程打开这个文件,b进程也打开这个文件,那么a与b就通过这个文件连接起来进行通信了,这就是管道通信是借助文件系统来实现的原因
3.匿名管道
经过上述的说明,我们已经明白了管道通信就是用来实现进程与进程之间的通信,但是进程与进程之间的通信也分为两种
- 一种是父子进程或兄弟进程之间的通信(匿名管道)
- 另一种则是没有亲属关系的进程间的通信(命名管道)
匿名管道的创建, 不会指定打开文件的文件名、文件路径等, 即不会有目标的打开文件,只是在内存中打开一个文件, 用于进程间的通信
而由于匿名管道是非明确目标的文件, 也就意味着两个完全不相关的进程是无法一起访问这个管道的, 因为完全不相关的进程无法找到这个管道文件.
这也就意味着,匿名管道其实只能用于具有血缘关系的进程间通信.
进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行读写操作,进而实现父子进程间通信
子进程拷贝父进程的fd_array,父子进程看到同一份文件 , 这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝(文件并不存在磁盘,只在内存中存在)。
管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有io参与会降低效率。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一 一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在
3.1.创建匿名管道的原理
这样就创建了一个管道通信
因为子进程会以继承父进程的方式打开同一个文件, 即子进程打开文件的方式与父进程是相同的
如果父子进程通过想要通过管道实现进程通信, 子进程就需要先关闭已打开的文件, 再以某种方式打开同一个文件,这样比较麻烦, 如果在创建子进程之前, 父进程就已经以两种方式打开同一个文件, 那么再子进程创建之后, 只需要父进程关闭一个端口, 子进程关闭另一个端口就可以了
并不是的, 父子进程关闭哪个端口, 其实是 根据需求
关闭的.
如果子进程要向父进程传输数据, 那么关闭读取端的就应该是子进程
其实在file结构体中, 存在一个计数变量 f_count:
不过, 这个变量实际上还是一个结构体, 用于计数
如果父子进程都打开了这个文件,那么这个文件的引用计数就是2,如果父进程关闭了,引用计数就会减一,系统就会知道,做出相应措施
很好,到这里原理结束
3.2.系统调用接口——pipe函数
linux操作系统提供了一个接口来进行匿名管道的创建与使用
- 功能:创建一个无名管道
- fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
- 返回值:如果创建管道成功, 则返回0, 否则返回-1, 并设置errno
pipe系统调用的作用是, 创建一个管道文件. 其参数是一个 输出型参数
在pipe系统调用 执行成功之后, 参数数组内会存储两个元素:
pipe[0],
存储以 只读方式 打开管道时获得的fdpipe[1]
, 存储以 只写方式 打开管道时获得的fd
之后就可以根据需求, 选择父子进程的端口关闭
3.3.创建匿名管道
我们先复习一下makefile
我们将按照原理一步一步来讲解
#include<unistd.h>
#include<stdio.h>
int main() {
int fd[2]={0};//这个语句就是用来记录进程分别以读写端打开管道文件的fd
int check = pipe(fd);//创建管道
if (check != 0) {
printf("create pipe error\n");
return 0;
}
}
父进程在创建子进程时,子进程会拷贝一份父进程的进程地址空间,同样的,子进程也会拷贝父进程的文件描述符表
#include<unistd.h>
#include<stdio.h>
int main() {
int fd[2]={0};//第一个是读,第二个是写
int check = pipe(fd);
if (check != 0) {
printf("create pipe error\n");
return 0;
}
pid_t id = fork();
if (id > 0) { /*执行父进程代码*/ }
if (id == 0) { /*执行子进程代码*/ }
return 0;
}
接下来,我们明确父子进程谁是读端,谁是写端,就可以进行通信了,这里我们让父进程写数据给子进程,那么父进程就要关闭自己的读端,子进程就要关闭自己的写端
#include<unistd.h>
#include<stdio.h>
int main() {
int fd[2]={0};
int check = pipe(fd);
if (check != 0) {
printf("create pipe error\n");
return 0;
}
pid_t id = fork();
if (id > 0) { //父进程
close(fd[1]);
/*关闭父进程的写端,接着执行父进程代码*/
//……
close(fd[0]);//最好还是关掉
}
if (id == 0) { //子进程
close(fd[0]);
/*关闭子进程的读端,接着执行子进程代码*/
//……
close(fd[1]);//最好还是关掉
}
return 0;
}
这个过程大家可能又疑惑了,子进程关闭了fd[0],父进程关闭了fd[1],那么这个会互相影响吗?
答案是肯定不会,因为进程具有独立性,一旦父子双方任意一方修改,就会触发写时拷贝!!
现在读写双方都确定了,那写方如何给读方发数据,读方又如何读取写方的数据呢?
既然管道通信是借助文件系统实现的,那么是不是......没错,就是使用read和write函数,
接下来通过4种情况来示例这个通信过程
3.3.1.写端进程不写,读端进程一直读
我们让子进程每隔1秒写1次,父进程一直读
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
#define num 1024
using namespace std;
void writer(int wfd)
{
string s="hello,i am child";
pid_t self=getpid();
int number=0;
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
snprintf(buffer,sizeof(buffer),"%s - %d - %d",s.c_str(),self,number);//把上面的东西都转化成字符串写入buffer数组
//发送消息给父进程
write(wfd,buffer,strlen(buffer));//不需要加1
sleep(1);//子进程休眠,父进程没有休眠啊
number++;
}
}
void reader(int rfd)
{
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)//成功
{
buffer[n]=0;//0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
}
}
int main()
{
int fd[2]={0};
int n=pipe(fd);
if(n<0)
{
cout<<"creat pipe failed"<<endl;
return 1;
}
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id==0)//child
{
close(fd[0]);//关闭读端
writer(fd[1]);
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//关闭写端
reader(fd[0]);
pid_t rid =waitpid(id,nullptr,0);//会是
if(rid<0)
return 3;
close(fd[0]);
}
我们成功实现了父子进程之间的简单通信,我们可是没有给父进程可是没有休眠的,为什么父进程打印打的这么慢???这就是管道的特点,没有数据读,就等待
实际上,在子进程休眠的这5秒,父进程在等待子进程休眠结束,直到子进程再次写入数据时,父进程才会读取。
所以我们的 结论 就是:管道内部没有数据的时候,并且其中的写端不关闭自己的文件描述符时,读端就要进行阻塞等待,直到管道文件有数据。
3.3.2.读端进程不读,写端进程一直写
我们对上面代码进行修改,子进程一直写,父进程每隔5秒读1次
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
#define num 1024
using namespace std;
void writer(int wfd)
{
string s="hello,i am child";
pid_t self=getpid();
int number=0;
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
snprintf(buffer,sizeof(buffer),"%s - %d - %d",s.c_str(),self,number);//把上面的东西都转化成字符串写入buffer数组
//发送消息给父进程
write(wfd,buffer,strlen(buffer));//不需要加1
number++;
}
}
void reader(int rfd)
{
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)//成功
{
buffer[n]=0;//0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
sleep(5);//父进程每隔5秒读一次,子进程一直写
}
}
int main()
{
int fd[2]={0};
int n=pipe(fd);
if(n<0)
{
cout<<"creat pipe failed"<<endl;
return 1;
}
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id==0)//child
{
close(fd[0]);//关闭读端
writer(fd[1]);
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//关闭写端
reader(fd[0]);
pid_t rid =waitpid(id,nullptr,0);//会是
if(rid<0)
return 3;
close(fd[0]);
}
我们先执行一下,一瞬间就出现了下面这个 ,并且阻塞在这里了
进程运行到第5秒的时候就出现了下面这个
在之后就是下图这个情况了
来分析一下我们编译运行程序会发现,写端对管道文件一直写入字符,但是到了一定的字符时却卡在这里了。
其实这个时候 写端在阻塞,这是因为我们写入的对象,也就是 管道文件 被写满了! 注意:管道文件的大小依据平台的不同也各不相同。
所以我们得到的 结论 是:当管道内部被写满,且读端不关闭自己的文件描述符,写端写满管道之后,就要进行阻塞等待!
此外,我们发现怎么一次打印出这么多东西?
这就是读取的机制,一次性全读,管道有多少,读多少!!!!
3.3.3.写端进程将数据写完后将写端关闭
子进程每隔1秒写1次,写了5秒后就不写了,父进程一直读
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
#define num 1024
using namespace std;
void writer(int wfd)
{
string s="hello,i am child";
pid_t self=getpid();
int number=0;
char buffer[num];
while(true)
{
sleep(1);//每隔1秒写1次
char c='c';
write(wfd,&c,1);
number++;
cout<<number<<endl;
if(number>=5)
break;
}
}
void reader(int rfd)
{
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)//成功
{
buffer[n]=0;//0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
cout<<"n:"<<n<<endl;
}
}
int main()
{
int fd[2]={0};
int n=pipe(fd);
if(n<0)
{
cout<<"creat pipe failed"<<endl;
return 1;
}
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id==0)//child
{
close(fd[0]);//关闭读端
writer(fd[1]);
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//关闭写端
reader(fd[0]);
pid_t rid =waitpid(id,nullptr,0);//会是
if(rid<0)
return 3;
close(fd[0]);
}
这里每隔1秒写一次 ,还是很正常的
后面5秒过后,下面这个东西刷屏了
我们接着用监视窗口来监视一下:
前5秒是这个
5秒之后是这个
当写端写了5次之后直接退了,那么读端进程就会变为僵尸状态。
我们可查man手册,read成功了就返回读到的字节数,0表示读到了文件结尾
所以我们读到了管道的文件结尾,不会阻塞
所以我们就能得出 结论:
对于读端而言,当写端不再写入,并且关闭了pipe,那么读端将会把管道内的内容读完,最后就会读到返回值为0,表示读取结束,类似于读到了文件的结尾。
这个时候就应该把代码修改一下了
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
#define num 1024
using namespace std;
void writer(int wfd)
{
string s="hello,i am child";
pid_t self=getpid();
int number=0;
char buffer[num];
while(true)
{
sleep(1);//每隔1秒写1次
char c='c';
write(wfd,&c,1);
number++;
cout<<number<<endl;
if(number>=5)
break;
}
}
void reader(int rfd)
{
char buffer[num];
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)//成功读取
{
buffer[n]=0;//0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
else if(n==0)//读到文件尾
{
cout<<"father read file done"<<endl;
break;
}
else//出错了
break;
}
}
int main()
{
int fd[2]={0};
int n=pipe(fd);
if(n<0)
{
cout<<"creat pipe failed"<<endl;
return 1;
}
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id==0)//child
{
close(fd[0]);//关闭读端
writer(fd[1]);
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//关闭写端
reader(fd[0]);
pid_t rid =waitpid(id,nullptr,0);//会是
if(rid<0)
return 3;
close(fd[0]);
}
完美
3.3.4.读端进程将读端关闭,而写端进程还在一直向管道写入数据
我们让父进程先读他个5秒,然后不读了,子进程一直写
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include<cstring>
#define num 1024
using namespace std;
void writer(int wfd)
{
int number=0;
char buffer[num];
while(true)
{
sleep(1);//每隔1秒写1次
char c='c';
write(wfd,&c,1);
number++;
cout<<number<<endl;
}
}
void reader(int rfd)
{
char buffer[num];
int cnt=0;
while(true)
{
buffer[0]=0;//字符串清空,只是为了提醒大家,我把这个数组当字符串了
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)//成功
{
buffer[n]=0;//0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
else if(n==0)//读到文件尾
{
cout<<"father read file done"<<endl;
break;
}
else//出错了
break;
cnt++;
sleep(1);
if(cnt>5) break;//读5次就跑啦!!!!
}
}
int main()
{
int fd[2]={0};
int n=pipe(fd);
if(n<0)
{
cout<<"creat pipe failed"<<endl;
return 1;
}
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id==0)//child
{
close(fd[0]);//关闭读端
writer(fd[1]);
close(fd[1]);
exit(0);
}
//father
close(fd[1]);//关闭写端
reader(fd[0]);
close(fd[0]);//父进程不读啦
cout<<"father close read fd"<<fd[0]<<endl;
sleep(5);//为了观察子进程的僵尸状态
pid_t rid =waitpid(id,nullptr,0);//会是
if(rid<0)
return 3;
}
我们看看监控情况
而我们发现似乎也没什么不对啊?读取完之后不就直接退出了吗?
你应该仔细想想,我们仅仅是关闭了读的文件描述符,但是没有关闭写的文件描述符啊。
这就是最后一个 结论:当读端不再进行读取操作,并且关闭自己的文件描述符fd,而写端依旧在写。那么os就会通过信号(sigpipe)的方式直接终止写端的进程。
4.验证管道的大小
管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,需要了解一下管道的大小
①方法一:使用man手册
根据man手册,在2.6.11之前的linux版本中,管道的最大容量与系统页面大小相同,从linux 2.6.11往后,管道的最大容量是65536字节。
查看linux系统版本
这里使用的是linux 2.6.11之后的版本,因此管道的最大容量是65536字节。
②方法二:使用ulimit命令
可以使用ulimit -a 命令,查看当前资源限制的设定, 管道的最大容量是 512 × 8 = 4096 字节
③写代码验证管道容量
- 根据man手册得到的管道容量与使用ulimit命令得到的管道容量不同,测试验证
- 代码概述: 读进程一直不读,写进程一直写,直到管道被写满
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int fd[2] = { 0 };
if (pipe(fd) < 0){ //使用pipe创建匿名管道
perror("pipe");
return 1;
}
pid_t id = fork(); //使用fork创建子进程
if (id == 0){ //child
close(fd[0]); //子进程关闭读端
char c = '.';//一个字符一个字节
int count = 0;
while (1){
write(fd[1], &c, 1);
count++;
printf("%d\n", count); //打印当前写入的字节数
}
close(fd[1]);
exit(0);
}
//father
close(fd[1]); //父进程关闭写端
//父进程不读取数据
waitpid(id, null, 0);
close(fd[0]);
return 0;
}
- 在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前linux版本中管道的最大容量是65536字节
5.管道的特点
①管道内部自带同步与互斥机制。
我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。
临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。
实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。
子进程往管道里面写入,子进程去读取的时候,有数据就拿上来,没数据就不在读取而是阻塞式的等待管道数据写入,并非父进程sleep了,而是因为子进程写的慢,父进程必须等,而引起好像父进程sleep了,这种—个等另一个的现象叫做同步。
②管道的生命周期随进程。
管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统,那么当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说管道的生命周期随进程。
我们不需要去关闭管道,系统会自己回收
③管道提供的是流式服务。
我们一般所谓的流式概念就是,给你提供一个通信的信道,你的写端就直接写,读端直接读,但是具体写多少,读多少完全有上层决定。底层就只是提供一个数据通信的信道就完了,它不关心数据本身的一些细节格式,这叫做面向字节流。
- 流式服务: 数据没有明确的分割,一次拿多少数据都行。
- 数据报服务: 数据有明确的分割,拿数据按报文段拿。
④管道是半双工通信的。
在数据通信中,数据在线路上的传送方式可以分为以下三种:
- 单工通信:单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
- 半双工通信:半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
- 全双工通信:全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。
管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。
6.几个小问题
父子进程之间进行通信时,临时创建的这个管道文件并没有对应的文件名和inode,只是系统分配的一块内存空间,可以以文件的形式被父子进程打开或关闭,这一切工作都在不知不觉中由os全部完成了,所以称为匿名管道,等命名管道文件看完,也可以回头对比着理解
管道符|用于将一个命令的输出作为另一个命令的输入。在这个命令中,ps ajx命令的输出将作为grep pid命令的输入。 当这个命令在shell中执行时,shell会创建一个匿名管道。ps ajx命令形成的进程作为管道的写端,将其输出写入管道;而grep pid命令形成的进程作为管道的读端,从管道中读取输入。
因此,ps ajx和grep pid都作为shell的子进程,通过匿名管道进行通信。ps ajx将其输出写入管道,而grep pid从管道中读取数据,实现了两个命令之间的通信
发表评论