父子进程 3个月前

编程语言
707
父子进程

1. 进程执行位置

在父进程中成功创建了子进程,子进程就拥有父进程代码区的所有代码,那么子进程中的代码是在什么位置开始运行的呢? 父进程是从main()函数开始运行的,子进程是在父进程中调用fork()函数之后被创建, 子进程就从fork()之后开始向下执行代码。

上图中演示了父子进程中代码的执行流程,可以看到如果在程序中对fork()的返回值做了判断,就可以控制父子进程的行为,如果没有做任何判断这个代码块父子进程都可以执行。 在编写多进程程序的时候,一定要将代码想象成多份进行分析,因为直观上看代码就一份,但实际上数据都是多份,且多份数据中变量名都相同,但是他们的值却不一定相同。


2. 循环创建子进程

掌握了进程创建函数之后,实现一个简单的功能,在一个父进程中循环创建3个子进程,也就是最后需要得到4个进程,1个父进程,3个子进程 为了方便验证程序的正确性,要求在程序中打印出每个进程的进程ID。

// process_loop.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    for(int i=0; i<3; ++i)
    {
        pid_t pid = fork();
        printf("当前进程pid: %d\n", getpid());
    }

    return 0;
}

编译并执行上面的代码,得到了如下结果:

# 编译
$ gcc process_loop.c

# 执行
$ ./a.out
# 最终得到了 8个进程
当前进程pid: 18774     ------ 1
当前进程pid: 18774     ------ 1
当前进程pid: 18774     ------ 1
当前进程pid: 18777     ------ 2
当前进程pid: 18776     ------ 3
当前进程pid: 18776     ------ 3
当前进程pid: 18775     ------ 4
当前进程pid: 18775     ------ 4
当前进程pid: 18775     ------ 4
当前进程pid: 18778     ------ 5
当前进程pid: 18780     ------ 6
当前进程pid: 18779     ------ 7
当前进程pid: 18779     ------ 7
当前进程pid: 18781     ------ 8

通过程序打印的信息发现程序循环了三次,最终得到了8个进程,也就是创建出了7个子进程,没有在程序中加条件控制,所有的代码父子进程都是有资格执行的。

image

上图中的树状结构,蓝色节点代表父进程:

  • 循环第一次 i = 0,创建出一个子进程,即红色节点,子进程变量值来自父进程拷贝,因此 i=0
  • 循环第二次 i = 1,蓝色父进程和红色子进程都去创建子进程,得到两个紫色进程,子进程变量值来自父进程拷贝,因此 i=1
  • 循环第三次 i = 2,蓝色父进程和红色、紫色子进程都去创建子进程,因此得到4个绿色子进程,子进程变量值来自父进程拷贝,因此 i=2
  • 循环第四次 i = 3,所有进程都不满足条件 for(int i=0; i<3; ++i)因此不进入循环,退出了。

解决方案:可以只让父进程创建子进程,如果是子进程不让其继续创建子进程,只需在程序中添加关于父子进程的判断即可。

// 需要在上边的程序中控制不让子进程, 再创建子进程即可
// process_loop.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    pid_t pid;
    // 在循环中创建子进程
    for(int i=0; i<3; ++i)
    {
        pid = fork();
        if(pid == 0)
        {
            // 不让子进程执行循环, 直接跳出
            break;
        }
    }
    printf("当前进程pid: %d\n", getpid());

    return 0;
}

最后编译并执行程序,查看最终结果,可以看到最后确实得到了4个不同的进程 pid最小的为父进程,其余为子进程:

# 编译
$ gcc process_loop.c

# 执行
$ ./a.out
当前进程pid: 2727
当前进程pid: 2730
当前进程pid: 2729
当前进程pid: 2728

在多进程序中,进程的执行顺序是没有规律的,因为所有的进程都需要在就绪态争抢CPU时间片,抢到了就执行,抢不到就不执行 默认进程的优先级是相同的,操作系统不会让某一个进程一直抢不到CPU时间片。


3. 终端显示问题

在执行多进程程序的时候,经常会遇到下图中的问题 看似进程还没有执行完成,貌似因为什么被阻塞了,实际上终端是正常的,通过键盘输入一些命令,终端也能接受输入并输出相关信息,那为什么终端会显示成这样呢?

image

  1. a.out 进程启动之后,共创建了3个子进程,其实 a.out 也是有父进程的就是当前的终端
  2. 终端只能检测到 a.out 进程的状态,a.out执行期间终端切换到后台,a.out执行完毕之后终端切换回前台
  3. 当终端切换到前之后,a.out的子进程还没有执行完毕,当子进程输出的信息就显示到终端命令提示符的后边了,导致终端显示有问题,但是此时终端是可以接收键盘输入的,只是看起来不美观而已。
  4. 想要解决这个问题,需要让所有子进程退出之后再退出父进程,比如:在父进程代码中调用 sleep()
pid_t pid = fork();
if(pid > 0)
{
    sleep(3);    // 让父进程睡一会儿
}
else if(pid == 0)
{
    // 子进程
}

4. 进程数数

当父进程创建一个子进程,那么父子进程之间可以通过全局变量互动,实现交替数数的功能吗?

// number.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

// 定义全局变量
int number = 10;

int main()
{
    printf("创建子进程之前 number = %d\n", number);

    pid_t pid = fork();
    // 父子进程都会执行这一行
    printf("当前进程fork()的返回值: %d\n", pid);

    //如果是父进程
    if(pid > 0)
    {
        printf("我是父进程, pid = %d, number = %d\n", getpid(), ++number);
        printf("父进程的父进程(终端进程), pid = %d\n", getppid());
        sleep(1);
    }
    else if(pid == 0)
    {
        // 子进程
        number += 100;
        printf("我是子进程, pid = %d, number = %d\n", getpid(), number);
        printf("子进程的父进程, pid = %d\n", getppid());
    }

    return 0;
}

编译程序并测试:

$ gcc number.c
$ ./a.out 
创建子进程之前 number = 10
当前进程fork()的返回值: 3513
当前进程fork()的返回值: 0
我是子进程, pid = 3513, number = 110
子进程的父进程, pid = 3512

我是父进程, pid = 3512, number = 11    
#没有接着子进程的110继续数,父子进程各玩各的,测试失败
父进程的父进程(终端进程), pid = 2175

通过验证得到结论:两个进程中是不能通过全局变量实现数据交互的 因为每个进程都有自己的地址空间,两个同名全局变量存储在不同的虚拟地址空间中,二者没有任何关联性。 如果要进行进程间通信需要使用:管道,共享内存,本地套接字,内存映射区,消息队列等方式。

image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223082
累计阅读

热门教程文档

React
18小节
React Native
40小节
CSS
33小节
Golang
23小节
HTML
32小节