• 【linux进程(二)】如何创建子进程?--fork函数深度剖析


    💓博主CSDN主页:杭电码农-NEO💓

    ⏩专栏分类:Linux从入门到精通

    🚚代码仓库:NEO的学习日记🚚

    🌹关注我🫵带你学更多操作系统知识
      🔝🔝


    在这里插入图片描述

    1. 前言

    我们已经会使用getpid/getppid
    函数来查看pid和ppid了,本篇文章
    会介绍第二种查看进程的方式

    本章重点:

    本篇文章着重介绍创建子进程
    的函数:fork的概念以及返回值
    本篇文章主要解决以下问题:

    • fork函数干了什么事?
    • 为什么fork有两个返回值?
    • 为啥fork的返回给父子进程的内容不同?
    • fork之后,父子进程谁先运行?
    • 如何理解同一个变量有不同的值?

    这些问题的答案会在文章中给出


    2. 查看进程的第二种方式

    Linux系统中,有一个动态文件proc
    它里面存放着所有进程的信息,之所以
    叫动态文件是因为它会随着进程的改变
    而随时更新它的内容!

    查看所有进程文件:

    使用指令: ls /proc/

    查看特点的进程文件:

    使用指令: ls /proc/pid

    在这里插入图片描述

    比如我现在写一个死循环代码
    然后通过此文件来查看我这个进程:

    查看动态文件

    可以发现,在自行创建的进程中
    有很多我们看不懂的文件,这些文件
    也不需要掌握,但是有两个文件需要
    大家注意,一个是cwd一个是exe

    在这里插入图片描述

    exe指向可执行程序的位置
    cwd代表默认的当前文件

    我们经常听见一句话:在当前文件
    创建一个文件,在当前文件怎么怎么样
    这个当前文件就是cwd指向的文件
    并且Linux外壳的bash中,pwd指令
    其实就是从cwd中找到当前路径的!


    3. 如何创建一个子进程?

    众所周知啊,Linux系统是用C语言写的
    所以Linux中创建一个进程实际上也要
    调用C语言的函数,也就是用代码创建
    进程,用户使用代码创建进程叫系统调用

    使用函数: fork

    使用man指令查看fork函数信息:

    在这里插入图片描述

    写一段代码创建子进程观察情况:

    #include  
    #include  
    #include  
    int main()  
    {
       	printf("我是一个进程,我的pid:%d\n",getpid());
      
        fork();
      
        printf("i am a process,pid:%d\n",getpid());
        sleep(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    请看下面的图片观察情况:
    在这里插入图片描述

    接下来再打一个死循环观察情况:

    #include    
    #include    
    #include    
    int main()    
    {    
        printf("我是一个进程,pid:%d ppid:%d\n",getpid(),getppid());                                                                                                         
        while(1)    
        {              
            fork();                                                          
            printf("i am a process,pid:%d ppid:%d\n",getpid(),getppid());    
            sleep(1);    
        }       
        return 0;
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    请看以下图片观察情况:
    在这里插入图片描述

    它会循环打印pid和ppid,可以发现
    蓝色框里面的ppid明显是命令行解释器
    bash的pid,这个进程的pid是31063
    创建的子进程的pid是31064,并且子进程
    的ppid也就是父亲id是31063,这就已经
    说明了一个情况:fork之后,已经创建了子进程
    并且此进程的父进程是我们自己写的程序!


    4. fork函数详解(一)

    通过上面的代码和图文,可以发现
    fork之前的代码只有父进程执行
    然而fork之后的代码父子进程都要执行

    fork函数不仅会帮我们创建子进程
    它还有两个返回值,父进程会接受到
    子进程的pid,子进程会接收到值:0
    那么你可能有疑问?既然fork之后
    父子进程会执行一样的代码,那么子进程
    的意义是什么?其实fork是这样用的:

    int forkid = fork();
    if(forkod==0)
    {
    	执行子进程的专有代码
    }
    else
    {
    	执行父进程的专有代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实际上我们创建子进程的意义就是
    为了让子进程执行和父进程不一样
    的代码,实现和父进程不一样的功能
    比如我们可以一边下载软件一边播放
    音乐,这两个过程就是不同的进程在执行!

    改修代码后查看fork的返回值:

    #include                                                                                                                                                       
    #include    
    #include    
    int main()    
    {    
        printf("我是一个父进程,我的pid是: %d\n",getpid());    
        
        pid_t id = fork();    
        
        if(id==0)//子进程的代码片段    
        {    
            while(1)    
            {    
                printf("我是子进程: pid:%d ppid: %d ret:%d,我在进行下载任务\n",getpid(),getppid(),id);    
                sleep(1);    
            }    
        }    
        else if(id>0)//父进程的代码片段    
        {    
        
            while(1)    
            {    
                printf("我是父进程: pid:%d ppid: %d ret:%d,我在进行播放任务\n",getpid(),getppid(),id);    
            sleep(1);    
            }    
        } 
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    请看下图观察情况:

    在这里插入图片描述


    5. fork函数详解(二)

    观察上面的情况,fork函数到底做了什么?
    现在我来回答这个问题:

    fork会创建子进程,系统中会多出
    一个子进程,操作系统以父进程为
    模板为子进程创建PCB,但是子进程
    中是没有代码和数据的,当前状态
    子进程和父进程共享代码和数据
    所以fork之后,父子进程会执行一样的代码

    父子进程的关系可以用下图来理解:

    在这里插入图片描述

    理解了这一点后,第三点也很好理解
    首先,一个父进程可以创建很多个子
    进程,然而一个子进程只对应一个父
    进程,所以fork函数会返回子进程的id
    给父进程,方便父进程管理它的子进程

    现在就已经解决了开头的第1.3问题了


    6. fork函数详解(三)

    现在我想来解答第二个问题,众所周知啊
    C/C++函数只能有一个返回值,然而这里
    的fork函数既然也是C函数,为什么会有两个
    返回值呢?请看以下的解释:

    首先,fork之后,父子进程都会执行
    代码的本质是它们都被内存调度了
    而当一个函数执行到return时,它的
    核心工作才算执行完成,于是我们可以
    想象一下fork函数内部的一些代码信息:

    在这里插入图片描述

    可以发现,在fork函数return之前,
    就已经创建了子进程,并且将子进程
    放入调度队列中运行了,所以当子进程
    在调度队列时,它和父进程就已经分流了
    而不是真正在fork函数return之后才分流的

    并且创建完子进程后代码是共享的
    很明显return也是一句代码,所以父子进程
    都会执行return语句,fork函数有两个返回值


    7. fork函数详解(四)

    现在,我来回答第四个问题
    fork之后,父子进程谁先运行?

    在讲解第二问时我们知道,创建完成
    子进程后,这只是一个开始,系统的其他
    进程,父进程,子进程接下来会被调度执行!

    问题是先调度谁?先创建就先调度吗?

    答案明显不是!在调度队列中,CPU
    会选择一个进程去运行它,谁先被调度
    谁就先运行!所以fork之后父子进程谁
    先运行用户是不确定的,这是由各自
    进程PCB中的调度信息决定的,比如
    优先级,算法信息等等

    下面有一篇拓展阅读,有兴趣可以看看:

    fork函数拓展阅读


    8. fork函数详解(五)

    最后来回答第五个问题
    前面一个函数有两个返回值你可能
    能够理解,因为两个进程都被调度了
    但是同一个变量怎么可能有两个不同
    的值呢?变量id在父进程和子进程中值不同

    首先我们要清楚一点:

    干掉父进程不会影响子进程运行,反之也是

    请看下面的视频验证:

    kill父进程不会影响子进程

    通过以上实验看出,进程是有独立性的
    首先是表现在进程各自的PCB运行时
    不会相互影响,很明显,代码本身只是可读
    的,所以不会影响代码,但是对于数据来说
    父子的数据是可能不同的(可能会被修改)

    所以系统是怎样做到让数据在各个
    进程都自己私有一份的?答案是写时拷贝
    类似于学习类和对象时的深浅拷贝
    数据会在需要使用时被写时拷贝到PCB
    然而fork返回值赋值给变量时,本质也是
    写入,返回时也会发生写时拷贝,所以不同
    的进程执行的代码中的变量id获取的值不同!


    9. 总结以及拓展

    fork函数的细节还有很多,但是以目前
    的学习进度来说,要完全理解它很困难
    所以文章采用了较为简单的方式帮助理解
    只要理解了fork函数的五个问题的答案
    那么在目前阶段就已经干的很好了!

    拓展阅读:

    Linux下的PCB源码解析


    🔎 下期预告:Linux进程状态信息 🔍
  • 相关阅读:
    AIGC | LLM 提示工程 -- 如何向ChatGPT提问
    基于MATLAB的图像条形码识别系统(matlab毕毕业设计2)
    Android 实现登录功能
    基于51单片机的自动售货机Proteus仿真
    Xinlinx zynq7045国产替代 FMQL45T900全国产化 ARM开发板
    【Docker 基础教程】容器数据持久化(一)------ 数据持久化概述
    mock的使用——生成表格数据并展示
    解析带有表头的数据文件
    服务器与客户端交互小栗子(java代码实现最基本的服务器实例)
    latex,没有边框的表格
  • 原文地址:https://blog.csdn.net/m0_61982936/article/details/133559958