创建子进程fork

举报
跳动的bit 发表于 2022/08/30 07:41:59 2022/08/30
【摘要】 上面我们写了一个死循环代码,然后 “ ./ ” 运行,一般我们称之为命令式创建进程,实际上我们也可以用代码来创建子进程。fork 也是系统调用接口,对于 fork 我们还会在 “ 进程控制 ” 章节中再深入,在此文中我们会通过 a) 程序员角度。 b) 内核角度。来学习 fork。 💦 认识 fork通过命令 man fork 来查找 fork 的相关手册: 💦 使用 fork 创建进程...

上面我们写了一个死循环代码,然后 “ ./ ” 运行,一般我们称之为命令式创建进程,实际上我们也可以用代码来创建子进程。

fork 也是系统调用接口,对于 fork 我们还会在 “ 进程控制 ” 章节中再深入,在此文中我们会通过 a) 程序员角度。 b) 内核角度。来学习 fork。

💦 认识 fork

通过命令 man fork 来查找 fork 的相关手册:
在这里插入图片描述

💦 使用 fork 创建进程

这里 fork 后,后面的代码一定是被上面的父子进程共享的,换言之,这个循环每次循环都会被父子进程执行一次(去循环已验):

在这里插入图片描述

可以看到子进程的父进程正是父进程的 pid,换言之,谁调 fork,谁就是父进程,父进程通过 fork 创建了子进程:

在这里插入图片描述

使用 ps ajx 命令来查看当前进程:

在这里插入图片描述

💦 程序员角度理解 fork

通过上面的代码知道了 fork 是创建子进程,也就意味着 fork 之后,这个子进程才能被创建成功,父进程和子进程都要运行,但是 fork 之后,父进程和子进程谁先运行,不是由 fork 决定的,而是由系统的调度优先级决定的。
也就是说父子进程共享代码 —— 只读的,不可修改或不可写入的;而用户数据各自私有一份 —— 比如使用任务管理器,结束 Visual Studio2017 进程,并不会影响 Xshell,一个进程出现了问题,并不会影响其它进程,所以操作系统中,所有进程是具有独立性的,这是操作系统表现出来的特性。所以将各自进程的用户数据私有一份,进程和进程之间就可以达到不互相干扰的特性。

注意这里私有数据的过程并不是一创建进程就给你的,而是采用写时拷贝 的技术,在 C++ 里我们和 深浅拷贝 一起谈过,这里后面我们再详谈,因为我们虽然在语言上学过了,但是在系统上还没学过。

💦 内核角度理解 fork

fork 之后,站在操作系统的角度就是多了一个进程,以我们目前有限的知识,我们知道 进程 = 程序代码 + 内核数据结构(task_struct),其中操作系统需要先为子进程创建内核数据结构,在系统角度创建子进程,通常以父进程为模板,子进程中默认使用的是父进程中的代码和数据(写时拷贝)。

💦 fork 的常规用法

如上代码,fork 之后与父进程执行一样的代码,有什么意义 ❓

  我直接让父进程做不就完了嘛,所以大部分情况下我们创建的父子进程,是想让父和子执行不同的代码。所以对我们而言,不是这样用 fork 的,而是通过 fork 的返回值来进行代码的分支功能。

在之前的学习中我们都知道 if … 、else if …,是不能同时进入的,那有没有可能它们能同时进入且跑 2 份死循环呢 ❓

  放在以前根本不可能,因为它是单进程,而现在我们使用 fork 创建父子进程(多进程),所以对于 if … 、else if …,它都会被进入,且 2 个死循环都会跑。对我们来讲这里的父进程就是自己,然后你自己 fork 创建了子进程,所以从 fork 之后,就有 2 个执行流,其中子进程执行 if,父进程执行 else if。

在这里插入图片描述

  这里 fork 之后,else 里表示创建进程失败,我们后面再说。可以看到运行后 fork 之前只有 1 个进程,但 fork 后就有 2 个进程一起运行,注意这里是系统来规定父子进程执行的先后顺序。这里肯定是并发,因为我的云服务器只是 1 核的配置,所以它底层其实是以 进程快速切换 来达到伪并行的效果。

在这里插入图片描述
在这里插入图片描述
💨小结:

  就意义而言,我们创建子进程是想帮助父进程来完成任务的,现在我们刚涉及,所以让它俩各自输出。如果我们要实现边下载边播放的功能那么价值就可以体现了,这样就可以实现一个并发执行的多进程程序。

ret == 0 && ret > 0 能同时存在吗 ❓

  按以前的知识,照现在看到的场景,用于接收 fork 返回值的 ret 是怎么可以既等于 0,又大于 0 的,在我们 C/C++ 上是绝对不可能的。这个的理解是需要我们进程控制中的 进程地址空间 的知识来铺垫才能理解的。

fork 为啥会有 2 个返回值 ❓

  我们在调用一个函数时,这个函数已经准备 return 了,那么就认为这个函数的功能完成了,return 并不属于这个函数的功能,而是告诉调用方我完成了,比如 fork 在准备 return 前,fork 创建子进程的工作已经完成了,甚至子进程已经被放在调度队列里了。我们刚刚说过,fork 之后,父子进程是共享代码的,我们认定 return 是代码,是和父子进程共享的代码,所以当我们父进程 return 时,这里的子进程也要 return,所以说这里的父子进程会使 fork 返回 2 个值。

为啥给子进程返回 0,而父进程返回子进程的 pid ❓

在这里插入图片描述

   在生活中,对于儿子,只有 1 个父亲,而对于父亲,却可以有多个儿子,比如家里有 3 个儿子,其中老二犯了错,父亲不可能说 “ 儿子,过来,我抽你一顿 ”,而应该是说 “ 老二 过来,我抽你一顿 ”;而儿子可以说 “ 爸爸,我来了 ”。可以看到父亲为了能更好的吩咐儿子,会对每个儿子进行标识,并且记住它们。所以父进程返回子进程的 pid 的原因是因为父进程可能会创建多个子进程(好比你出生后你爸就给你起了个名字),所以这为了保证父进程能拿到想拿到的子进程;而子进程返回 0 的原因是父进程对于子进程是唯一的(好比你不可能给你爸起名字)。

父进程拿子进程干嘛 ???

  那你爸拿你的名字干嘛,肯定是叫你办事呀,同样的父进程拿子进程有很多用途:比如说有 5 个子进程,我想把某个任务指派给某个子进程,这时就通过它的 pid 来指定;当然你要杀掉某个子进程,可以使用 pid 来杀掉想杀掉的子进程。

子进程的 pid 会存储在父进程的 PCB ???

  不会,因为子进程的 pid 是给你看的,你可以拿着 pid 去搞事情。而实际在内核里它们是由对应的链表结构去维护的。

如何创建多个子进程 ???

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
void DoThing()
{
	int count = 0;
 	while(count < 5)
    {
        printf("pid : %d, ppid : %d, count : %d\n", getpid(), getppid(), count);
        count++;
        sleep(1);
    }
}
int main()
{
    pid_t ids[5];
    printf("I am father : %d\n", getpid());
    for(int i = 0; i < 5; i++)
    {
        ids[i] = fork();
        if(ids[i] == 0)
        {
            //child
            DoThing();
            exit(1);
        }
    }
    printf("%d %d %d %d %d\n", ids[0], ids[1], ids[2], ids[3], ids[4]);
    getchar();                                                                                                                                                                          

    return 0;
}

运行后:

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。