【Linux】探索进程的父与子

目录

  • 1.获取进程PID
    • 1.1进程PPID
  • 2.通过系统调用创建进程-fork初识
    • 2.1为什么fork函数要给子进程返回0,给父进程返回pid?
    • fork函数如何做到返回两次的?fork干了什么事情?
    • 怎么理解一个变量为什么有两个不同的值?
    • 如果父子进程同时创建好,fork()往后,父子进程谁先运行呢?
    • 理解bash
  • 🍀小结🍀

在这里插入图片描述
在这里插入图片描述

🎉博客主页:小智_x0___0x_

🎉欢迎关注:👍点赞🙌收藏✍️留言

🎉系列专栏:Linux入门到精通

🎉代码仓库:小智的代码仓库


1.获取进程PID

一个进程想要获取自己的PID可以通过调用系统调用接口getpid(),它会返回调用这个函数进程的PID,返回值是pid_t类型。
在这里插入图片描述
我们来写代码使用一下这个函数:

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

int main()
{

    pid_t id=getpid();
    while(1)
    {
        printf("我是一个进程,我的pid是%d",id);
        sleep(1);
    }

    return 0;
}

我们在编译前可以写一个监控脚本:

while :; do ps axj | head -1 ; ps axj | grep proc | grep -v grep; sleep 1; done

这条命令的作用是每隔一秒检查并显示与 “proc” 相关的进程信息。下面是对这条命令的详细解释:

  1. while :; do ... done: 这是一个无限循环。: 是一个 shell 内建命令,什么都不做,只是返回一个状态码 0(表示成功)。while : 的意思就是无限循环,因为 : 总是返回成功,所以这个循环会一直执行下去,直到被外部中断。
  2. ps axj | head -1: ps axj 是一个显示所有进程的命令,j 选项表示显示作业控制信息,a 表示显示终端上的所有进程(包括其他用户的进程),x 表示显示没有控制终端的进程。| head -1 的意思是只显示第一行,也就是列头信息。
  3. ps axj | grep proc | grep -v grep: 这个命令是用来查找与 “proc” 相关的进程。grep proc 是查找包含 “proc” 的行,grep -v grep 是排除掉包含 “grep” 的行,也就是排除掉查找 “proc” 的这个 grep 命令自身。
  4. sleep 1: 这个命令是让循环暂停一秒,然后再继续执行。

我们下来编译运行一下看结果>
在这里插入图片描述
可以看到我们用ps命令查询出来的进程pid和进程自己打印出来的pid相同。

1.1进程PPID

当然进程除了pid之外呢,进程还有所谓的叫做父进程pid,那么当然我们查的时候呢,我们看到这儿有一个pid,那么这还有一个ppid,这个ppid什么意思呢,就第1个p呢,就相当于叫做parent啊,父母的意思,然后第2个p呢就是process ID ,所以PPID呢就是的父进程id号,所以呢,我们一般在进行我们进程所对应的一个操作的时候呢,除了能获得自己的id也能获得父进程的id值。
获取父进程pid的函数接口是getppid()返回值类型参数跟getpid()相同。

我们再来编一段代码,来获取父进程id值:

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

int main()
{
    while(1)
    {
        printf("我是一个进程,我的pid是%d,我的父进程ppid是%d\n",getpid(),getppid());
        sleep(1);
    }

    return 0;
}

在这里插入图片描述
我们可以看到进程可以打印出他的id和他的父进程id,我们将进程重新跑起来>
在这里插入图片描述
这里我们发现我们每次运行的时候进程pid是会发生变化的,那为什么父进程id值不变呢,那这个进程id号为7361的进程到底是个什么东西呢?

我们来使用下面指令来观察

ps axj | head -1 ; ps axj | grep 7361

在这里插入图片描述

我们的命令行解释器,那么它的核心那么命令行解释器那么它的核心是帮助获取用户的输入然后呢,帮助用户进行命令行解释。
给大家讲一个关于张三、王婆和操作系统的小故事,王婆每次接收到张三的请求都会尽力去执行,但总是失败。为了维护自己的声誉,王婆决定招募一批实习生来帮忙。自从有了这些实习生后,每当你需要命令行解释时,王婆不再亲自出马了,而是直接派遣实习生去协助你。在这个故事中,bash就像传说中的王婆一样。当我们运行一个进程时,命令行解释器会解释并执行你的指令。即使某个执行者出现问题,也不会影响到我们的bash进程。

而当我们退出xshell重新登录,我们对应的bash进程的id也会不同。

2.通过系统调用创建进程-fork初识

我们一直在讨论如何创建进程以及维护进程之间的父子关系。现在我们来谈一下实际创建进程的方法和如何维护这些关系。如果我想创建进程,我应该采取哪些方法呢?今天我们已经了解了其中一种创建进程的方法,那就是通过直接执行命令行来创建并运行一个进程。执行./proc命令就可以创建一个新的进程并在其上运行。但是,如果我想手动创建一个进程呢?为了满足这个需求,我们可以使用Linux系统中的fork函数来手动创建进程。这个函数允许我们创建一个新的进程,并在其中运行我们想要的代码。
在这里插入图片描述
fork()函数通过复制调用它的进程(我们称之为父进程)来创建一个新的进程(我们称之为子进程)
我先给大家写一个demo代码,来演示一下效果:

int main()
{
    printf("before:only one line\n");
    fork();
    printf("after:onlu one line\n");
    sleep(1);
    return 0;
}

在这里插入图片描述
这里我们看到fork之前的代码被执行了一次,但是fork之后的代码被执行了两次,这是因为我们在创建用fork创建进程的时候,fork之前只有一个执行流,fork之后就会变成两个进程.
那么怎么证明呢,我们再来使用man手册来查看fork函数:
在这里插入图片描述
fork()成功执行时,它会在父进程中返回子进程的进程ID (PID),而在子进程中返回0。这种方式使得父进程可以得知子进程的PID,如果fork()执行失败,它会在父进程中返回-1,并且不会创建子进程。同时,全局变量errno会被设置为一个表示错误原因的值。
我们再来写一段代码演示一下:

int main()
{
    printf("begin:我是一个进程,pid:%d,ppid:%d",getpid(),getppid());
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        while (1)
        {
            printf("我是子进程,pid:%d,ppid:%d",getpid(),getppid());
            sleep(1);
        }
    }
    else if(id>0)
    {
        //父进程
        while(1)
        {
            printf("我是父进程:pid:%d,ppid:%d",getpid(),getppid());
            sleep(1);
        }
    }
    else
    {
        //error
    }

    return 0;
}

在这里插入图片描述
通过打印结果我们可以得出,在上面的一份代码中 id 大于0和 id 等于0同时存在, if 和 else if 同时满足,这个现象说明此时一定存在两个进程,即原来的 proc 进程和在 proc 进程中创建的子进程,因为在一个进程中 if 和 else if 是不可能同时满足的。这也符合 fork 函数创建子进程的目的,fork 函数创建子进程后,子进程会执行if中的代码段,而父进程会执行else if中的代码段,会从原来的一个执行流变成两个执行流。

2.1为什么fork函数要给子进程返回0,给父进程返回pid?

返回不同的返回值,是为了区分让不同的执行流,执行不同的代码块!
一般而言啊,fork之后的代码父子共享啊,也就是说呢,其实当我们fork之后,后续的所有代码父子进父子进程他都能看到啊,只不过呢,我们是需要通过一定程度去区分,我们可以让父进程和子进程执行不同的代码块,那么当然应该这样,要不然我为什么创建子进程呢,我不就是想让你的子进程过来帮我忙嘛,把你创建出来,让你和我做不同的事情,所以我们的返回值一定是需要不同的,即便是返回值未来,如果假设系统设计者把它设成相同的了,未来我们一定也要有方法区分父子进程,这是这个,当然这不是这个问题的答案,问题是为什么要给子进程反回0给父进程反回pid,上面回答的是为什么父子进程反回值不同,我们现在知道了,因为父子进程,那么后续的代码是共享的,而我们可以通过不同的返回值来区分不同的执行流,让父子执行不同的代码块好也没有毛病,具体他执行的代码块要干啥我们后面来介绍啊 现在的问题是为什么要给子进程返回0给父进程返回pid

给大家举一个生活当中的例子啊:
在现实生活中,一个父亲可以有多个子女,而每个子女只有一个父亲,这是容易理解的。在进程的世界中,一个父进程也可能有多个子进程。但有时,我们可能需要对特定的子进程进行控制。想象一下,如果一个家庭有10个孩子,当父亲喊“儿子,你过来”时,所有10个孩子都过来了,那么父亲到底是想叫哪个孩子呢?
因此,对于父进程来说,它必须有一种方法来区分每一个子进程。这就是为什么fork()在父进程中返回子进程的PID。这样,父进程就可以使用这个PID来标定和识别每一个子进程的唯一性。而对于子进程来说,情况就简单了。它只需要调用getppid()函数就可以直接获取其父进程的PID。所以,子进程要找到父进程并不需要花费太多成本,它只需要通过返回0来表示成功就可以了。这就是为什么我们在父进程中返回子进程的PID的原因,因为未来我们可能想通过父进程使用PID来明确控制我们要访问的是哪一个子进程。

fork函数如何做到返回两次的?fork干了什么事情?

理解一个函数如何被返回两次,尤其是fork(),首先需要深入了解fork()的工作原理。在没有调用fork()之前,系统中只有一个进程。进程由内核数据结构和与之关联的代码和数据组成。当创建一个新进程时,需要为它创建相应的进程控制块(PCB)以及与之关联的代码和数据。CPU随后会调度这个新进程,执行它的代码和数据。

调用fork()后,会创建一个新的子进程。创建子进程的本质是系统中多了一个新进程。由于进程由内核数据结构和代码及数据组成,因此新创建的子进程首先需要创建自己的task_struct(Linux内核中的进程描述符)。这个子进程的大部分属性是基于父进程的属性创建的,相当于复制了父进程的对象并对部分属性进行了修改。例如,子进程会有自己的PID,而其父进程ID(PPID)则设置为父进程的PID。这样,父子进程就有了自己的ID关系。

然而,子进程在刚创建时并没有自己的代码和数据,因此它只能访问父进程的代码和数据。这就是为什么fork()之后父子进程的代码是共享的。当CPU调度并运行父进程的代码时,它执行的是父进程的代码;当调度并运行子进程的代码时,它执行的仍然是父进程的代码。
在这里插入图片描述

我们为什么要创建子进程呢?不就是为了让父子进程执行不同的事情,那就需要让父子进程执行不同的代码段,所以fork就必须要返回两个不同的返回值。

我们来看看fork函数具体做了哪些事情:
在这里插入图片描述
所以我们最终的结果也就来了:当我们准备在fork()之后进行return时,实际上子进程的创建工作已经完成。一旦fork()创建完毕,系统中就存在了子进程,于是CPU可以分别调度父进程和子进程来运行。需要注意的是,在创建子进程之后,父子进程的代码是共享的。由于return语句也是代码的一部分,当执行到这里时,父进程和子进程都已经存在。因为return语句是父子进程共享的,所以当父进程调度并执行这个函数时,它会返回一次;同样地,当子进程调度并执行时,它也会返回一次。这就是为什么fork()函数最终会被返回两次的原因。

怎么理解一个变量为什么有两个不同的值?

当使用fork()创建子进程时,系统中确实多了一个进程,这也意味着操作系统内必须为这个新进程创建一个对应的PCB(进程控制块)。这个子进程的PCB同样可以被CPU调度运行。与父进程不同,子进程在创建之初并没有自己的代码和数据。

需要明确的是,进程在运行时具有独立性。这意味着一个进程的崩溃或退出不会影响到其他进程。例如,如果在Windows上运行的QQ崩溃了,它不会影响到同时运行的画图软件或XShell等其他进程。每个进程都是独立的,它们的崩溃或退出不会影响其他进程。

为了保证进程的独立性,不能让父进程和子进程共享同一份数据,因为数据可能会被修改,修改后可能会影响其他进程。因此,虽然父子进程的代码是共享的,但数据不一定是共享的。理论上,子进程需要为自己拷贝一份父进程的数据。但实际上,数据的拷贝工作是由操作系统自动完成的。当子进程需要访问父进程的某部分数据时,操作系统会识别出这一需求,并在系统内存中为子进程重新开辟一段空间。子进程在新开辟的空间内进行数据的修改,这样就不会影响到父进程的数据。这种技术被称为写时拷贝(Copy-on-Write)。它确保了父子进程在数据层面的独立性,同时也避免了不必要的数据冗余。

总结来说,fork()创建的子进程具有独立的PCB,可以被CPU调度运行。虽然父子进程的代码是共享的,但数据不一定是共享的。操作系统通过写时拷贝技术确保父子进程在数据层面的独立性,同时也避免了数据冗余。
在这里插入图片描述

fork 函数在执行 return 返回的时候是往 id 变量里面写入嘛,父进程 return 一次,子进程 return 一次,子进程会执行写时拷贝,这就是为什么一个 id 变量有两个不同的数据,本质上是因为有两块空间。为什么同一个变量名可以有不同的内容,这个问题我们在后面介绍进程地址空间的时候为大家介绍。

如果父子进程同时创建好,fork()往后,父子进程谁先运行呢?

一般而言,当一个进程被创建后,作为用户,我们并不需要关心其具体的运行时机,因为这是由操作系统自动管理的。例如,我们打开网易云音乐、浏览器或画图软件时,并不需要知道这些软件在操作系统层面何时运行。操作系统负责底层进程的调度,而我们只需直接使用这些软件。

父子进程的运行顺序不是由用户决定的,而是由操作系统的调度器决定的。调度器是操作系统中的一个组件,负责决定哪个进程在何时运行。调度器的工作是基于特定的算法,从多个进程中选择一个合适的进程放到CPU上运行。

调度器的调度原则通常是尽可能平均或公平地分配时间片。例如,如果一个进程已经被调度了10毫秒,而另一个进程从未被调度过,那么调度器可能会选择这个未被调度过的进程来运行,以确保每个进程都有机会运行。这就是调度器的作用,确保所有进程都能得到公平的调度,并且不会有进程被遗漏。

需要注意的是,不同系统或不同时间段内,父子进程的运行顺序可能会有所不同,因为调度器的工作是基于特定的算法和当前系统的状态来决定的。因此,我们不能确定父子进程的确切运行顺序。

理解bash

我们接着从这个故事说起:

“王婆为了保证自己在给别人说媒时,如果失败了,不会影响到自己,她是通过创建子进程来实现的。现在我的问题是,bash是如何创建子进程的?虽然我们还没有看或写bash的源代码,但我可以推断一下这个过程。

bash一定会调用fork来创建子进程,并让自己的代码继续执行命令行解释,而让子进程去执行解释新的命令。那么,bash自己就会继续接受用户的输入,并打印出提示符,等待用户输入新的命令。这就是为什么我们运行的所有命令都是在bash内部,因为所有的进程都是通过fork创建出来的。

当然,这只是问题的一半,后面还有更多内容需要探讨。但我们已经理解了创建子进程的过程,核心结论就是bash也是一个进程,当执行命令时,bash内部一定会为我们fork创建新的子进程,然后执行我们的代码。”

🍀小结🍀

今天我们学习了"【Linux】探索进程的父与子"相信大家看完有一定的收获。种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/196704.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

[SaaS] 淘宝AI淘淘秀

AIGC技术在淘淘秀场景的探索与实践关键词&#xff1a;图像类AI创新应用、用户轻松创作、内容分享、结合商家品牌。https://mp.weixin.qq.com/s/-3a3_nKeKGON-9-Prd7JKQ 1.生成模版 利用定制的prompt&#xff0c;生成一些比较好的素材图片案例。 最终的用的是通义万相。 2.仿…

elk:filebeat也是一个日志收集工具

filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动使用的资源要小的多 filebeat可以允许在非java环境&#xff0c;他可以代替logstash在非java环境上收集日志 filebeat无法实现数据的过滤&#xff0c;一般是结合logstash的数据过滤功能一…

深入理解强化学习——马尔可夫决策过程:贝尔曼期望方程-[举例与代码实现]

分类目录&#xff1a;《深入理解强化学习》总目录 在文章《深入理解强化学习——马尔可夫决策过程&#xff1a;贝尔曼期望方程-[基础知识]》中我们讲到了贝尔曼期望方程&#xff0c;本文就举一个贝尔曼期望方程的具体例子&#xff0c;并给出相应代码实现。 下图是一个马尔可夫…

深度学习:全面了解深度学习-从理论到实践

深度学习&#xff1a;全面了解深度学习-从理论到实践 摘要&#xff1a;本文旨在为读者提供一份全面的深度学习指南&#xff0c;从基本概念到实际应用&#xff0c;从理论数学到实践技术&#xff0c;带领读者逐步深入了解这一领域。我们将一起探讨深度学习的历史、发展现状&#…

Mysql8.1.0 安装问题-缺少visual studio 2019x64组件

缺少visual studio x64组件的问题 使用Mysql8以上的安装包mysql-8.1.0-winx64.msi进行安装&#xff0c; 提示缺少visual studio 2019 x64可再发行组件 在微软官网下载vc可再发行程序包 Microsoft Visual C 可再发行程序包最新支持的下载 在Visual Studio 2015、2017、2019 和…

C++学习——类和对象(上)

C学习——类和对象 一、面向对象和面向过程的初步认识二、什么是类 一、面向对象和面向过程的初步认识 我们之前学习了C语言&#xff0c;我们知道 ① C语言&#xff1a;C语言是一门面向过程的语言&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函…

vue的生命周期及不同阶段状态可以进行的行为

什么是vue的生命周期&#xff1f; Vue 的实例从创建到销毁的过程 &#xff0c;就是生命周期 &#xff0c;也就是从开始创建 &#xff0c;初始化数据 &#xff0c;编译模板 &#xff0c;挂载Dom到渲染DOM &#xff0c;更新数据再到渲染 &#xff0c;卸载等一系列的过程 &#x…

羽隔已就之图像处理之BP神经网络入门

小y最近非常忙&#xff0c;这一年来&#xff0c;活很多&#xff0c;一直在加班、出差&#xff0c;也没好好休息过。最近在武汉出差一个多月了&#xff0c;项目逐渐完结&#xff0c;有点闲时间了&#xff0c;回首望&#xff0c;这一年设定的很多目标都没完成。 还记得&#xff0…

【攻防世界-misc】Encode

1.下载解压文件&#xff0c;打开这个内容有些疑似ROT13加密&#xff0c;利用在线工具解密&#xff1a;ROT13解码计算器 - 计算专家 得到了解密后的值 得到解码结果后&#xff0c;看到是由数字和字母组成&#xff0c;再根据题目描述为套娃&#xff0c;猜测为base编码&#xff08…

处理器及微控制器:XCZU15EG-2FFVC900I 可编程单元

XCZU15EG-2FFVC900I参数&#xff1a; Zynq UltraScale™ MPSoC 系列基于 Xilinx UltraScale™ MPSoC 架构。该 Zynq UltraScale™ MPSoC 器件集成了功能丰富的 64 位四核或双核 Arm Cortex-A53 和双核 Arm Cortex-R5F 处理系统&#xff08;基于 Xilinx UltraScale™ MPSoC 架…

Vue3挂载完毕后,隐藏dom再重新加载组件的方法

组件原本是在PC端使用的&#xff0c;现在需要把组件再封装一次&#xff0c;供app调用&#xff0c;但是在app上会显示tag栏&#xff0c;有占位影响空间&#xff0c;所以需求去掉头部tag&#xff0c;只显示下方组件。 实现方法&#xff0c;以前是直接引用的组件&#xff0c;现在改…

一天之内“三个离职群都满了”;飞行出租车的时代就此开启?丨 RTE 开发者日报 Vol.94

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

java学习part20内部类

116-面向对象(高级)-类的成员之五&#xff1a;内部类_哔哩哔哩_bilibili 1.内部类

在微服务架构中的数据一致性

当从传统的单体应用架构转移到微服务架构时&#xff0c;特别是涉及数据一致性时&#xff0c;数据一致性是微服务架构中最困难的部分。传统的单体应用中&#xff0c;一个共享的关系型数据库负责处理数据一致性。在微服务架构中&#xff0c;如果使用“每个服务一个数据库”的模式…

Java零基础——SpringBoot篇

SSM Springboot 分布式微服务 1.Spring的发展 回顾&#xff1a;Spring是一个开源框架&#xff0c;2003年兴起的一个轻量级的Java 开发框架&#xff0c;作者&#xff1a;Rod Johnson。Spring是为了解决企业级应用开发的复杂性而创建的&#xff0c;简化开发。 1.1 Spring1.x时…

HarmonyOS 数据持久化 Preferences 如何在页面中对数据进行读写

背景介绍 最近在了解并跟着官方文档尝试做一个鸿蒙app 小demo的过程中对在app中保存数据遇到些问题 特此记录下来 这里的数据持久化以 Preferences为例子展开 废话不多说 这里直接上节目(官方提供的文档示例:) 以Stage模型为例 1.明确preferences的类型 import data_prefer…

四川劳动保障杂志社四川劳动保障编辑部四川劳动保障杂志2023年第10期目录

主题报道 四川抢抓“金九银十”招聘季多措并举稳就业促就业 举措频“上新” 金秋送岗忙 张玉芳; 2-5 法眼《四川劳动保障》投稿&#xff1a;cnqikantg126.com 筑牢长期护理保险基金安全防线 李科仲;赖晓薇; 6-7 调研 提升职业技能培训工作的举措 寇爵; 8-9 城乡…

虚拟机虚拟化原理

目录 什么是虚拟化广义虚拟化狭义虚拟化 虚拟化指令集敏感指令集虚拟化指令集的工作模式监视器对敏感指令的处理过程&#xff1a; 虚拟化类型全虚拟化类虚拟化硬件辅助虚拟化 虚拟化架构裸金属架构宿主机模式架构 什么是虚拟化 虚拟化就是通过模仿下层原有的功能模块创造接口来…

电压调整型脉宽调制控制集成电路芯片D7500,工作电压范围7V ~ 40V,输出电流(Max)可达200mA,具有欠压锁定功能

D7500/D7500F SMPS 控制器电路&#xff0c;是一块电压调整型脉宽调制控制集成电路。内部包含5V 基准电压电路、两个误差放大器、触发电路、控制输出电路、脉宽调制比较 器、死区时间比较器及一个 振荡器。该电路可转换频率1kHz至300kHz&#xff0c; 基准电压(Vref)的精确度提…