• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

    一文详解Linux中的fork机制

    发布者: 晋3555 | 发布时间: 2025-8-15 01:55| 查看数: 63| 评论数: 0|帖子模式

    前言

    在Linux系统中,进程是操作系统最重要的执行单元,而父子进程的创建与管理更是系统资源分配和任务并行的关键。通过fork函数,Linux能够快速高效地复制一个进程,使得父子进程协同工作成为可能。理解父子进程的运行机制不仅有助于掌握系统编程的核心技能,更能为优化资源利用与提高程序性能提供理论基础。本文将带你从基础原理出发,解析Linux父子进程的运行特性、fork的核心机制及其在实际开发中的应用。

    一、进程PID
    1. PID
    复制代码
    是用来唯一标识一个进程的属性,我们可以使用
    1. ps
    复制代码
    指令查看一个进程的部分属性。进程的属性信息是由操作系统来维护的,这些信息被存储在一个
    1. task_struct
    复制代码
    结构体中,属于操作系统内核中的数据。由于操作系统本身是不相信用户的,所以用户无法直接去访问
    1. task_struct
    复制代码
    对象中的成员,因此
    1. ps
    复制代码
    指令能够显示进程的属性信息,本质上是通过系统调用接口去实现的。

    1.1 通过系统调用接口查看进程PID

    获取进程的
    1. PID
    复制代码
    需要用到系统调用接口
    1. getpid()
    复制代码
    ,该函数会返回调用该函数的进程的
    1. PID
    复制代码
    ,返回值类型为
    1. pid_t
    复制代码
    。如下图我们使用
    1. man getpid
    复制代码
    指令去查看
    1. getpid
    复制代码
    的基础文档:

    注意上图中还有一个
    1. getppid
    复制代码
    是什么呢?不难猜到,这应该是用来获取父进程
    1. PID
    复制代码
    的系统调用接口,接下来我们写段代码来具象化
    1. PID
    复制代码
    吧。
    注意上图中还有一个
    1. getppid
    复制代码
    是什么呢?不难猜到,这应该是用来获取父进程
    1. PID
    复制代码
    的系统调用接口,接下来我们写段代码来具象化
    1. PID
    复制代码
    吧。
    1. #include <stdio.h>   
    2. #include <unistd.h>   
    3. #include <sys/types.h>   

    4. int main()   
    5. {   
    6.     while(1)   
    7.     {   
    8.         printf("I am a process, my id is: %d, parent id is: %d\n", getpid(), getppid());                                 
    9.         sleep(1);   
    10.     }   
    11.     return 0;   
    12. }
    复制代码
    我们可以写一个脚本来实时获取上面这段代码执行起来后的进程信息。


    可以看到,我一个将这段代码执行了两次,每一次的子进程
    1. PID
    复制代码
    都在发生变化,但是父进程的
    1. PID
    复制代码
    从未更改。
    为了保证数据的准确性,我们再使用
    1. ps
    复制代码
    指令对比以下获取到的进程
    1. PID
    复制代码
    是否真的一样。
    1. while :; do ps axj | head -1 ; ps axj |grep process | grep -v grep ;  sleep 1 ; done
    复制代码


    结论:我们用
    1. getpid
    复制代码
    1. getppid
    复制代码
    得到的父子进程的
    1. PID
    复制代码
    1. ps
    复制代码
    指令获取到的进程
    1. PID
    复制代码
    是一样的

    二、通过系统调用创建进程-fork初识

    之前我们自己创建进程都是通过写一份源代码,然后去编译运行,最终得到一个进程,今天给大家介绍另一种通过系统调用接口
    1. fork
    复制代码
    去创建进程的方式。一样的,我们使用
    1. man fork
    复制代码
    去查看一下 fork 的相关文档:

    大致意思就是:
    1. fork
    复制代码
    函数会以调用该函数的进程作为父进程去创建一个子进程.

    创建成功时,会在父进程中返回子进程的
    1. PID
    复制代码
    ,在子进程中返回 0 。否则就在父进程中返回 -1 ,子进程创建失败。

    2.1 调用fork函数后的现象
    1. #include <stdio.h>      
    2. #include <unistd.h>      
    3. #include <sys/types.h>
    4. int main()                                                           
    5. {   
    6.     printf("before:only one line\n");   
    7.     fork();   
    8.     printf("after:only one line\n");   
    9.    
    10.     return 0;   
    11. }
    复制代码

    如上图所示,fork 后面的代码执行了两次!这是什么原因呢?我们再写一段代码跑跑。
    1. #include <stdio.h>      
    2. #include <unistd.h>      
    3. #include <sys/types.h>

    4. int main()   
    5. {   
    6.     printf("begin:我是一个进程,pid:%d, ppid:%d\n",getpid(), getppid());   
    7.    
    8.     pid_t id = fork();   
    9.     if(id > 0)   
    10.     {   
    11.         while(1)   
    12.         {   
    13.             printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());   
    14.             sleep(1);   
    15.         }   
    16.     }   
    17.     else if(id == 0)   
    18.     {   
    19.         while(1)   
    20.         {   
    21.             printf("我是子进程,pid:%d,ppid:%d\n",getpid(),getppid());   
    22.             sleep(1);   
    23.         }   
    24.     }   
    25.     else   
    26.     {   
    27.         perror("子进程创建失败!\n");   
    28.     }
    29.    
    30.     return 0;   
    31. }
    复制代码

    通过结果我们可以得出,在上面的一份代码中
    1. id
    复制代码
    大于0和
    1. id
    复制代码
    等于0同时存在,
    1. if
    复制代码
    1. else if
    复制代码
    同时满足,并且有两个死循环在同时跑。这个现象说明此时一定存在两个进程,即原来的
    1. myprocess
    复制代码
    进程和在
    1. myprocess
    复制代码
    进程中创建的子进程,因为在一个进程中
    1. if
    复制代码
    1. else if
    复制代码
    是不可能同时满足的。这也符合
    1. fork
    复制代码
    函数创建子进程的目的,
    1. fork
    复制代码
    函数创建子进程后,会从原来的一个执行流变成两个执行流。

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


    1. fork 返回值的设计目的
    1. fork
    复制代码
    是 UNIX 系统中用于创建新进程的核心系统调用。调用一次
    1. fork
    复制代码
    ,系统会“分 裂”出两个进程:父进程和子进程。它的返回值有以下特点:

    • 在父进程中
      1. fork
      复制代码
      返回新创建的子进程的
      1. PID
      复制代码
      ,使得父进程可以通过该
      1. PID
      复制代码
      来管理和操作子进程(如使用
      1. wait
      复制代码
      1. kill
      复制代码
      等操作)。
    • 在子进程中
      1. fork
      复制代码
      返回
      1. 0
      复制代码
      ,标识自己是子进程,无需再通过
      1. PID
      复制代码
      区分。
    这种设计的核心目的正如您提到的,用于区分不同执行流,即便父子共享同一套代码,也可以根据返回值选择性地执行不同代码。

    2. 现实类比的深入解读


    • 父亲喊“儿子”:如果不区分,所有子进程都会响应,导致混乱。通过分配唯一的
      1. PID
      复制代码
      ,每个子进程可以被单独识别。
    • 子进程喊“爸爸”:由于每个子进程只能有一个父进程,所以子进程通过调用
      1. getppid()
      复制代码
      即可找到其唯一的父进程。

    3. 为什么子进程返回值为 0


    • 简单区分:子进程无需知道自己的
      1. PID
      复制代码
      来执行自己的任务,而只需通过返回值
      1. 0
      复制代码
      知道自己是子进程。效率和逻辑一致性:如果子进程也返回自己的
      1. PID
      复制代码
      ,会引入额外的复杂性,而且父进程需要一个单独机制区分这些值。

    2.3 一个函数是如何做到返回两次的?如何理解?

    在调用
    1. fork
    复制代码
    函数之前就只有一个进程,我们先来回顾一下什么是进程?进程 = 内核数据结构 + 代码和数据,其中的内核数据结构就是进程对应的
    1. PCB
    复制代码
    对象。

    进程的
    1. PCB
    复制代码
    对象会找到相应的代码和数据,然后
    1. CPU
    复制代码
    就要去调度这个进程,也就是找到该进程的代码和数据去执行。调用
    1. fork
    复制代码
    函数创建子进程,本质上是操作系统多了一个进程,因此 fork 函数创建出来的子进程,它要先创建自己的
    1. PCB
    复制代码
    对象,子进程的
    1. PCB
    复制代码
    对象大部分都是以父进程的
    1. PCB
    复制代码
    对象为模板创建的,即从父进程的
    1. PCB
    复制代码
    对象中拷贝过来,再对部分属性稍作修改,子进程的
    1. PCB
    复制代码
    对象就有了。但是它没有自己的代码和数据,所以只能用父进程的,所以
    1. fork
    复制代码
    函数之后,父子进程的代码共享,这就解释了为什么上面
    1. fork
    复制代码
    函数之后的代码输出了两次,其实就是父子进程各自执行了一次。
    创建子进程的目的就是为了帮助父进程做不同的事情,但是父子进程共享一份代码,所以我们应该在代码中对它们加以区分。
    1. fork
    复制代码
    函数就帮我们完成了这个需求,它会在父子进程中返回不同的值,用户只需要根据返回值的不同让父子进程执行不同的代码。
    1. fork
    复制代码
    函数的实现过程:
    1. pid_t fork():
    2. <ul><li>创建子进程</li></ul>
    3. <ul><li>创建子进程的PCB</li><li>填充PCB对应的内容</li><li>让子进程和父进程指向同样的代码</li><li>此时父子进程都有独立的task_struct对象,可以被CPU调度运行了</li></ul>
    4. <ul><li>return ret;</li></ul>
    复制代码
    由于父子进程会共享一份代码,所以在
    1. fork
    复制代码
    函数执行
    1. return
    复制代码
    语句之前,子进程的
    1. PCB
    复制代码
    对象就已经被创建出来了,
    1. CPU
    复制代码
    已经可以去同时调度父子进程。由于
    1. fork
    复制代码
    函数中的
    1. return
    复制代码
    语句也是被共享的,所以
    1. fork
    复制代码
    函数有两个返回值。

    2.4 一个变量怎么会有不同的内容?


    1. fork 的返回值如何写入不同的变量空间

    当调用
    1. fork
    复制代码
    时,父进程与子进程会各自接收一个返回值,并且写入同名变量
    1. id
    复制代码
    。但这并不意味着他们共享同一块内存,而是因为:

    • 独立的进程地址空间
      每个进程都有自己独立的虚拟地址空间。在
      1. fork
      复制代码
      之后,父进程与子进程的地址空间是彼此独立的。尽管子进程初始时看起来与父进程完全相同,但实际上它们的数据是分离的
    • 写时拷贝(COW)机制
      操作系统为提高效率并节省资源,采用了写时拷贝技术。在
      1. fork
      复制代码
      之后:

      • 父子进程共享同一份内存数据,直到有一方尝试修改这些数据。
      • 当某个进程试图修改数据时,操作系统会为该进程分配新的物理内存空间,并将被修改的数据复制到新分配的空间中。


    2. fork 中变量 id 的本质

    在代码中,变量
    1. id
    复制代码
    是存储
    1. fork
    复制代码
    返回值的地方。以下几点解释了为什么同名变量可以存储不同的值:

    • 父子独立运行
      1. fork
      复制代码
      返回后,父子进程的执行路径分开。父进程的
      1. id
      复制代码
      变量存储的是子进程的 PID,而子进程的
      1. id
      复制代码
      变量存储的是
      1. 0
      复制代码

    • 不同的内存空间
      由于父子进程的地址空间独立,
      1. id
      复制代码
      实际上存在于两块不同的内存区域,即父进程的
      1. id
      复制代码
      和子进程的
      1. id
      复制代码
      完全独立的变量
    • 赋值过程
      1. fork
      复制代码
      的返回值通过操作系统写入到父子进程各自的
      1. id
      复制代码
      变量中: 父进程在
      1. return
      复制代码
      时向
      1. id
      复制代码
      写入子进程的 PID。子进程在
      1. return
      复制代码
      时向
      1. id
      复制代码
      写入
      1. 0
      复制代码


    结语

    Linux父子进程的运行机制展示了操作系统设计的高效性与灵活性。从fork的返回值设计到写时拷贝(COW)的优化方案,这一切都体现了Linux在性能与资源利用上的巧妙平衡。通过深入理解父子进程的特性,不仅能够提升系统编程的能力,还能为并发和并行程序设计提供坚实的理论支持。希望本文能为你的学习和实践带来启发,在Linux系统的探索中迈向更高的层次。
    以上就是一文详解Linux中的fork机制的详细内容,更多关于Linux fork机制的资料请关注脚本之家其它相关文章!

    来源:互联网
    免责声明:如果侵犯了您的权益,请联系站长(1277306191@qq.com),我们会及时删除侵权内容,谢谢合作!

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    ×

    最新评论

    浏览过的版块

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表