fork

如果我在程序中先进行pipe,创建了一个管道:P,有P[1]与P[2],然后进行fork,那么之后程序会怎么进行,这两个进程中的管道P直接有什么联系和区别?

答案

fork()不仅创建出与父进程代码相同的子进程,而且父进程在fork执行点的所有上下文场景也被自动复制到子进程中,包括:
—全局和局部变量
—打开的文件句柄
—共享内存、消息等同步对象

由于pipe调用相当于创建了2个文件句柄,因此在fork后这两个句柄也被自动复制到子进程中,对这两个句柄的操作效果与在主进程中的操作效果是一致的,这就使父子进程之间很容易通过该机制实现数据交换,如:

假设pipe产生的句柄为P[0],P[1],在fork后父子进程都拥有了P[0],P[1]句柄,那么:
—父进程可向自己的P[1]中写入数据,子进程从P[0]中即可读出该数据;切记此时父进程不能也从P[0]读数据,否则自己写入的数据可能先被自己读走了
—反之亦然,子进程向P[1]中写入数据后,父进程从P[0]中可读出该数据;切记此时子进程不要从P[0]读走数据

你可能难以理解为什么进程内部的数据读写会被传递到另一个进程,但别忘了,pipe匿名管道和文件,socket等一样是属于操作系统的管理对象,对其进行读写都是由OS的内核代码来进行控制的。在父进程调用pipe创建出管道后,该管道对象是存储在OS内部的,父进程得到的P[0]和P[1]都只是对该对象的引用(相当于指针);在fork出子进程后,子进程复制出的也只是同一个指针,所指向的还是OS中的同一个对象,这就是为什么父子进程能通过其进行互相通信的原因

有个问题

那么fork后父子进程谁先执行啊?比方说我想通过管道进行父子进程间的通信,父进程写,子进程读,要这样的话,必须父进程先执行。

所以fork的执行顺序是咋样的呀!QWQ

开始测试!

首先我们利用vsc打开WSL:

img

然后,新建一个C语言文件,测试fork与pipe

注意

pipe() 对应的头文件是:#include<unistd.h> // pipe

fork() 对应的头文件是:#include<sys/types.h> // fork

第一个:儿子从管道接受,父亲从管道发送

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    pipe(pipes);

    if (fork() == 0){
        close(pipes[1]);
        int t = 0;
        read(pipes[0], &t, sizeof t);
        printf("我是儿子: %d\n", t);
    }else{
        close(pipes[0]);
        int t = 3;
        write(pipes[1], &t, sizeof t);
        printf("我是爸爸: %d\n", t);
    }

    return 0;
}

然后,运行结果:

img

这里可以发现,fork后,程序相当于先运行了父进程,然后再运行子进程,进而能够成功通信,将 t=3 送给儿子。

第二个:儿子从管道发送,父亲接受

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    pipe(pipes);

    if (fork() != 0){
        close(pipes[1]);
        int t = 0;
        read(pipes[0], &t, sizeof t);
        printf("我是爸爸: %d\n", t);
    }else{
        close(pipes[0]);
        int t = 3;
        write(pipes[1], &t, sizeof t);
        printf("我是儿子: %d\n", t);
    }

    return 0;
}

可以发现,我超,也成功发送了,此时先运行了子进程,然后运行了父进程。逆天了。

总结,似乎有管道要发送和接受的时候,优先运行发送的进程,然后运行接受的进程?

那么再测试一下!

第三次:儿子从管道接受,父亲从管道发送

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    pipe(pipes);

    if (fork() == 0){
        close(pipes[1]);
        int t = 0;
        printf("我是儿子before read\n");
        read(pipes[0], &t, sizeof t);
        printf("我是儿子after read\n");
        printf("我是儿子: %d\n\n", t);
    }else{
        close(pipes[0]);
        int t = 3;
        printf("我是爸爸before write\n");
        write(pipes[1], &t, sizeof t);
        printf("我是爸爸after write\n");
        printf("我是爸爸: %d\n\n", t);
    }

    return 0;
}

见证奇迹了!

img

反过来想必也是一样的。

错误!反过来不一样

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    pipe(pipes);

    if (fork() != 0){
        close(pipes[1]);
        int t = 0;
        printf("我是爸爸before read\n");
        printf("1\n");
        read(pipes[0], &t, sizeof t);
        printf("我是爸爸after read\n");
        printf("我是爸爸: %d\n\n", t);
    }else{
        close(pipes[0]);
        int t = 3;
        printf("我是儿子before write\n");
        write(pipes[1], &t, sizeof t);
        printf("我是儿子after write\n");
        printf("我是儿子: %d\n\n", t);
    }

    return 0;
}

结果:

img

然后,我们如果把fork单独拎出来呢?

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    pipe(pipes);

    int ret = fork();

    if (ret != 0)
    {
        close(pipes[1]);
        int t = 0;
        printf("我是爸爸before read\n");
        printf("1\n");
        read(pipes[0], &t, sizeof t);
        printf("我是爸爸after read\n");
        printf("22\n");
        printf("我是爸爸: %d\n\n", t);
    }
    else
    {
        close(pipes[0]);
        int t = 3;
        printf("我是儿子before write\n");
        write(pipes[1], &t, sizeof t);
        printf("我是儿子after write\n");
        printf("我是儿子: %d\n\n", t);
    }

    return 0;
}

结果如下:

img

对比后,我们可以得出结论,从fork之后,父子进程交替执行,每人执行一句话,如此程序:

执行完 int ret = fork()后,父进程执行if,然后子进程执行if;

然后父进程进入if执行close,子进程转去执行else;

然后父进程执行 int t = 0 子进程执行close;

然后父进程执行 printf("我是爸爸before read\n");,接着子进程执行 int t=3;

然后父进程执行 printf("1\n");,接着子进程执行 printf("我是儿子before write\n");

然后父进程本来要执行 read ,但由于此时管道里没有东西,所以父进程暂停,子进程则继续执行 write

然后父进程执行 read,接着子进程执行 printf("我是儿子after write\n");

然后父进程执行 printf("我是爸爸after read\n");,接着子进程如果执行最后一句:printf("我是儿子: %d\n\n",t);,那么子进程就结束了,但程序需要使得父进程最后结束,因此父进程继续执行,直到结束,再执行子进程的最后一句。

然后!!!!

当我们重复运行这个程序,会出现以下情况:

img

此时是:先把子进程运行完,然后再运行父进程,也就是先运行含有 write 的,然后再运行read的。

img

当有多个管道时候:

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    int pipe2[2];
    pipe(pipes);
    pipe(pipe2);

    int ret = fork();

    if (ret != 0)
    {
        close(pipes[1]);
        close(pipe2[0]);
        int t = 0;
        int p = 4;
        printf("我是爸爸before read\n");
        printf("1\n");
        read(pipes[0], &t, sizeof t);
        write(pipe2[1], &p, sizeof p);
        printf("我是爸爸after read\n");
        printf("22\n");
        printf("我是爸爸p:%d\n",p);
        printf("我是爸爸t: %d\n\n", t);
    }
    else
    {
        close(pipes[0]);
        int t = 3;
        int p = 0;
        printf("我是儿子before write\n");
        write(pipes[1], &t, sizeof t);
        printf("我是儿子after write\n");
        read(pipe2[0], &p, sizeof p);
        printf("我是儿子p:%d\n", p);
        printf("我是儿子t: %d\n\n", t);
    }

    return 0;
}

结果:

img

可以发现,第一个write在子进程里面,无论之前怎么运行,总会确保运行完一个write后,再运行对应的read。

#include <stdio.h>
#include <unistd.h>  // pipe
#include <sys/types.h>  // fork

int main(){
    int pid;
    int pipes[2];
    int pipe2[2];
    pipe(pipes);
    pipe(pipe2);

    int ret = fork();

    if (ret != 0)
    {
        close(pipes[1]);
        close(pipe2[0]);
        int t = 0;
        int p = 4;
        printf("我是爸爸before read\n");
        printf("1\n");
        read(pipes[0], &t, sizeof t);
        write(pipe2[1], &p, sizeof p);
        printf("我是爸爸after read\n");
        printf("22\n");
        printf("我是爸爸p:%d\n",p);
        printf("我是爸爸t: %d\n\n", t);
    }
    else
    {
        close(pipes[0]);
        int t = 3;
        int p = 0;
        printf("我是儿子before write\n");
        read(pipe2[0], &p, sizeof p);
        write(pipes[1], &t, sizeof t);
        printf("我是儿子after write\n");
  
        printf("我是儿子p:%d\n", p);
        printf("我是儿子t: %d\n\n", t);
    }

    return 0;
}

img

可以发现,由于父子进程同时先有read,把两个管道堵死了,因此全寄啦!

(与你关不关管道的一段无关,我发现不关闭管道完全没影响)。

结论

程序交替运行父子进程,具体先运行哪一个,看情况完全随机,但却会使对同一个管道进行 write 的操作的语句优先于对应的 read的。

posted @ 2023-08-22 11:50  诗子黎  阅读(58)  评论(0编辑  收藏  举报