当前位置: 首页 > news >正文

linux多线(进)程编程——(2)身外化身fork()

前言(前情回顾)

你捡到了一个秘籍上面写着《linux操作系统》正式摆脱了练气期,并开启了修炼,突破了第一个瓶颈,你的识海中出现了岔路,应该往哪边走呢?
在这里插入图片描述
听说在迷宫里摸着右边的墙壁是总能走出去的,我们也试一下吧。你将神识触摸了右侧的光球,一些信息进入到了你的脑海,最前面写着两个字:进程。

进程

什么是进程?

官方解释

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

我的解释

小明回家打开了英雄联盟开黑,小刚打开了steam下载小黄油。这两个软件是进程。爱编程的小黄回家用C语言写了一个hello world程序并点击运行,这也是一个进程。总结来说,进程就是一个应用程序。其余那些描述先不要深究以免走火入魔哦。

多进程

现在我们的电脑上运行这不止一个软件,至少我知道你一定打开了浏览器,因为你在看我的文章,此外你还挂着微信,这就是多进程。每个进程都有一个PID,即进程ID用来标识这个进程,类似于之前的文件描述符fd。此外每个进程有一个PCB,不是画电路板那个PCB,他的全程是进程控制块(Process Control Block)。
进程控制块在这里是超纲内容,如果各位0基础根基不稳的道友不想走火入魔,就先别去探究他,忍住你的好奇心,后面我们会介绍它的。

编程中使用多进程技术

我该如何运行两个线程呢?写两个程序,并分别同时运行?有点蠢,而且对于现在的我们,这种方法没有意义。因此我要给大家解释我们这门功法的第一个神通:fork();

神通1身外化身:pid_t fork(void);

这个函数没有参数,只有一个pid_t类型的返回值(其实就是int)。它的作用确实十分强大,他可以让我们的程序进行实现身外化身。如下图所示,父进程在执行到fork()函数调用的位置时,会克隆出一个新的进程。注意我说的词语:克隆,这代表子进程将完全复制父进程的所有变量,缓冲区,堆栈等熟悉。
在这里插入图片描述
多说无益,我们直接上代码,使用fork()克隆出子进程,并且输出hello world

#include <stdio.h>
#include <fcntl.h>

int main() {
    printf("hello, world!\n");
    pid_t id = fork();
    printf("hello, world!\n");
    return 0;
}

请问大家下面这个代码应该输出什么,我们实际验证一下。
在这里插入图片描述
使用gcc编译(前置知识,与主线无关),大家就当没看见这个经过即可,之后运行我们的可执行文件,可以看到输出了3个hello, world!。相信不少同学都在脑海中直接推断出了这个结论。

fork()的克隆

为了让大家深刻理解克隆这个词的意义,我为大家提供了下面的案例。

#include <stdio.h>
#include <fcntl.h>

int main() {
    printf("hello, world! ");
    pid_t id = fork();
    printf("hello, world!\n");
    return 0;
}

在这个代码中我将第一个hello world后面的换行符给去掉换成了空格,现在这个程序应该输出什么呢?
在这里插入图片描述
可以发现输出了4个hello world,原因大家应该知道了,就是因为没有换行符导致的,但是真正的底层原因是什么呢?
这个问题涉及到printf()函数的底层机制。printf函数管理一个数据缓冲区,要输出打印的数据不会直接显示到屏幕上,而是会存在缓冲区中,等待缓冲区满了一起输出。而当缓冲区没满如何让它提前输出呢,就是加入一个换行符,当printf检测到换行符,就会将缓冲区内所有的数据打印出来。因此当我将第一个hello world后面的\n去掉后,第一个hello world会在缓冲区中,并且缓冲区会由于fork()的调用被克隆。因此当我第二次输出hello world时,两个进程都识别到了换行符,并清空缓冲区,最终输出4个hello world。

父子进程的辨认

经过刚才的学习我们已经知道了fork()会实现进程克隆,那么我们该如何区分进程的身份呢?这个时候就要轮到fork()的返回值出场了。
在fork()函数的执行期间内,系统会克隆出一个新进程,因此到fork()执行完毕,会产生两个返回值。对于父进程中,我们会得到一个大于0的返回值,表示子进程的PID。而在子进程中,返回值为0。(返回值为-1表示进程创建失败了)。为了验证上述理论的正确性,我们编写如下代码,此外我们需要一个新函数getpid(),这个函数能获取当前进程的PID。

#include <stdio.h>
#include <fcntl.h>

int main() {
    // 在父进程中获取父进程的PID
    // pid_t getpid(void) 获得当前进程的PID
    printf("father's pid is %d!\n", getpid());
    
    pid_t id = fork();
    if(id < 0 ) {
        printf("process create fail\n");
        return 0;
    }

    if(id == 0) {
        printf("I'm son, my pid is %d\n", getpid());	// 使用函数直接获得子进程的PID
    }
    else {
        printf("I'm father, my son's pid is %d\n", id);	// 使用id表示子进程的PID
    }

    return 0;
}

在这里插入图片描述
可以看到父进程中fork()的返回值与子进程的PID保持一直,且子进程中fork()的返回值为0。

小节

恭喜道友功力又精进了,为了巩固修为,还要巩固一下下面三点:
1、fork()函数的作用,以及如何辨认父子进程
2、进程PID的获取方法
3、fork()对父进程的克隆作用
4、附加题:printf()函数的缓冲区机制
fork()函数在后续课程中会大量使用,用于测试代码功能,一定要掌握。

下一集:linux多线(进)程编程——(3)进程间的传音术(匿名管道)

结束语

最后,祝各位道友早日神功大成!

(点亮了新的区域,但但是不知道的东西好像越来越多了。。。)

在这里插入图片描述

相关文章:

  • 概率论与数理统计核心知识点与公式总结(就业版)
  • C++进阶——异常
  • 计算机组成原理-系统总线
  • Hibernate(JPA) 和 MyBatis 的对比
  • Java基础关键_036_Stream
  • 使用Python实现矢量路径的压缩、解压与可视化
  • Python | 在Pandas中按照中值对箱形图排序
  • 二叉树的最近公共祖先二叉搜索树的最近公共祖先
  • 25届双非控制硕士求职回顾
  • ARM架构FFmpeg极致优化交叉编译指南
  • Linux:DNS服务配置(课堂实验总结)
  • 怎么免费下载GLTF/GLB格式模型文件,还可以在线编辑修改
  • instructor 库实现缓存
  • 【C#】.NET 8适配器模式实战:用C#实现高可用系统集成与接口桥接艺术
  • AutoGen参数说明
  • Kubernetes中的Label和Selector核心作用与应用场景
  • AI相关视频
  • 字符串与栈和队列-算法小结
  • 驱动开发硬核特训 · Day 10 (理论上篇):设备模型 ≈ 运行时的适配器机制
  • c++中的this
  • 发射后失联,印度地球观测卫星发射任务宣告失败
  • “南昌航空一号”成功发射,赣江鄱阳湖有了专属卫星守护
  • 第十一届世界雷达展开幕,尖端装备、“大国重器”集中亮相
  • 大外交丨3天拿下数万亿美元投资,特朗普在中东做经济“加法”和政治“减法”
  • 深圳南澳码头工程环评将再次举行听证会,项目与珊瑚最近距离仅80米
  • 共建医学人工智能高地,上海卫健委与徐汇区将在这些方面合作