无论是发送数据,还是共享数据或协同、控制
进程间通信的本质:是让不同的进程看到同一份资源
为了不破坏进程的独立性,这份资源不属于任何一个进程,而是由OS提供的一块特定形式的内存空间。
进程(用户)利用该空间进行通信,本质上就是访问OS。该空间的建立、释放,底层设计和接口都由OS来完成。
而管道是一种基于文件级别(不与磁盘交互)的通信方式。
如图所示,父子进程(有血缘关系的进程)通过继承体系,继承Task_struct的文件描述符表
父进程以WR方式打开该文件,引用计数为2,fork创建子进程,引用计数变为4.
思考:通信时产生的 访问冲突,临界资源竞争
OS在设计上为了简化整个过程,采用了单向通信的模式,即一个读,另一个写。
为实现双向通信,可以再创建一个管道,让通信方向相反即可。
输出型参数 pipefd[0] pipefd[1]分别为读写的fd。
#define N 2
#define NUM 128
void Writer(int wfd)
{
string str = "I am a child ";
pid_t self = getpid();
int number=0;
//充当缓冲区
char buffer[NUM];
while(true)
{
//字符串清空,只是为了提醒阅读代码的人,把这个数组当成字符串了
buffer[0]=0;
snprintf(buffer,sizeof(buffer),"msg:%s--pid:%d--num:%d",str.c_str(),self,number++);
write(wfd,buffer,strlen(buffer));//不用+1,系统层面不用加\0
sleep(1);
}
}
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';
printf("father get msg:%s father_pid:%d\n",buffer,getpid());
// cout << "father get a message[" << getpid() << "]# " << buffer << endl;
}
else if(n==0)
{
cout<<"father read file done";
}
else break;
}
}
// child--w father--r
//IPC code
int main()
{
int pipefd[N] = {0};
int n = pipe(pipefd);
if (n < 0)
return 1;
// cout<<"pipefd[0]:"<
进程退出,文件都关闭了,管道作为内存级文件也会被OS自动释放
1、如果read
完了管道内的所有数据,如果写端没有继续写入数据,那么读取端就只能继续等待。
w:1s写一次 r:读完后,也要等1s
2、如果写端把管道写满了就不能继续写入了。
w:短时间写多次直至写满,等待读取 r:5s读一次,一次读整个缓冲区大小
64kb 65536b
读写atomic原子性问题。一次读写的数据作为一个整体的单位 PIPE_BUF为4kb
例如hello world是一体的,不会只读一部分hello
3、如果我关闭了写入端,读取完了管道内的数据,继续读就会返回0,表示读取到了文件的结尾。
子进程退出后,父进程一直read(不阻塞),返回0表示读到文件结尾(多次调用read,每次都返回0,表明读到了文件结尾)
4、写端一直写入,但是把读端关闭,操作系统会直接杀死一直在写入的进程,并且关闭管道,操作系统会通过信号来终止进程13)SIGPIPE
读5s后关闭读端,立即发送13信号杀进程