使用管道和system V进行进程间通信

进程通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信的认识

进程间通信的本质是让相互通信的进程能够看到(共享)同一份资源。

首先我们要知道,我们在打开一个文件时会形成文件描述符,而0 1 2默认是标准输入,标准输出,标准错误。所以我们打开的文件就会从最小的下标开始进行文件描述符的分配。

那么如果一个进程以不同的方式打开同一个文件多次的话,那会有几个文件描述符呢???

int main()
{
    int fd = open("test.txt",O_CREAT|O_WRONLY,0666);
    int fdd = open("test.txt",O_RDONLY|O_CREAT);
    int fddd = open("test.txt",O_RDONLY|O_CREAT);
    cout<<fd<<" "<<fdd<<" "<<fddd<<endl;

    return 0;
}

 在验证了之后不难看出,就这同一个文件打开三次就形成了三个文件描述符,只不过这三个文件描述符指向的是同一个文件,也就是资源共享一份,只不过区别是不同文件描述符指向同一个文件的读写位置不同。

 进程间通信分类

匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

匿名管道的原理 

匿名管道其实就是父进程创建管道,也就是分别通过读写方式打开同一个文件,然后创建子进程,创建子进程的PCB,此时子进程会和父进程的资源共享,会将进程地址空间、页表都拷贝一份。而对应的文件描述符表也会进行拷贝一份(浅拷贝)。那么此时的父子进程的文件描述符表中指向的文件都是相同的。最后分别关闭对应的文件读写端就可以构成单向通信(文件描述符指向文件会采用引用计数的方式)。此时也就形成了管道。所以可以得出结论:匿名管道允许的是具有血缘关系的进程进行通信。

管道读写规则

我们通过代码的方式来理解管道读写的规则方式,采用父进程读数据,子进程写数据的方式:(红色字体是结论)

int main()
{
    //建立管道
    int tmp[2]={0};
    int n=pipe(tmp);
    if(n==-1)
    {
        cout<<"管道建立失败"<<endl;
        exit(-1);
    }

    //创建子进程,父子进程指向同一个文件
    int id=fork();
    if(id<0)
    {
        cout<<"进程创建失败"<<endl;
        exit(-1);
    }

    //child-w
    if(id==0)
    {
        close(tmp[0]);//关闭读文件
        char str[1024];int cnt=10;
        while(1)
        {           
            snprintf(str,sizeof(str)-1,"hello parent, I am chilld,mypid = %d,cnt = %-2d",getpid(),cnt--);//防止str被写穿,预留\0
            write(tmp[1],str,strlen(str));//写入到文件中(文件中是不存在\0的)

            if(cnt==0)break;
        }
        //exit(0);
        //结束if语句就算子进程退出
    }

    //parent-r
    if(id>0)
    {
        close(tmp[1]);//关闭写文件
        char buffer[1024];
        while(1)
        {
            int sz = read(tmp[0],buffer,sizeof(buffer)-1);//防止读入buffer的数据超出临时空间,预留\0
            //正常情况sz=0的时候,是因为管道没数据,写端口阻塞
            if(sz>0)
            {
                buffer[sz]=0;//字符串末尾加0
                cout<<buffer<<"  parent_id:"<<getpid()<<endl;
            }
        }
        pid_t rid = waitpid(id,nullptr,0);//等待子进程退出
        if(rid==id)
        {
            cout<<"wait ok"<<endl;
        }
    }

   
    return 0;
}

 

分析代码内容:先创建管道,本质就是通过读写方式打开同一个文件两次,然后创建子进程,此时子进程就会拷贝父进程的文件描述符表,父子进程分别执行自己对应的代码。然后分别在父子进程中关闭关闭其不需要的操作文件方式,此时关闭并不会直接将文件给关闭,因为我们文件的指向是采用引用计数的方式的,所以关闭进程的文件,只会使引用计数--,只有减到0才会真正的关闭文件。

但是我们发现,我们的结果是按照顺序读写的,也就是子进程写完一份内容,父进程才会读取一份内容。所以就可以得出第一个结论:写端目前没有向管道中写入数据,此时读端就会等待,直到有数据。如果其次就是进程子进程cnt=0的时候,写完了以后会退出,此时父进程还在while(1)中死循环并不会退出。


此时我们更改一下代码的内容(让读端等待,写端一直写)

int main()
{
    //建立管道
    ...

    //创建子进程,父子进程指向同一个文件
    ...

    //child-w
    if(id==0)
    {
        close(tmp[0]);//关闭读文件
        char str[1024];int cnt=10000;
        while(1)
        {           
            //写入到文件中(文件中是不存在\0的)
               ...
            cout<<"write------------------"<<cnt<<endl;
        }
       

    }

    //parent-r
    if(id>0)
    {
        
        close(tmp[1]);//关闭写文件
        char buffer[1024];
        while(1)
        {
            sleep(10);
            //向管道中读取数据
               ...
        }
        //等待子进程退出
        ...
    }

   
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CR0712

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值