Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Linux fork机制

一文详解Linux中的fork机制

作者:.17.

fork() 是一种系统调用,用于在操作系统中创建新进程,当 fork() 被调用时,会生成一个当前进程的副本(称为子进程),而原始进程则被称为父进程,本文给大家详细介绍了Linux中的fork机制,需要的朋友可以参考下

前言

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

一、进程PID

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

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

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

在这里插入图片描述

注意上图中还有一个 getppid 是什么呢?不难猜到,这应该是用来获取父进程 PID 的系统调用接口,接下来我们写段代码来具象化 PID 吧。

注意上图中还有一个 getppid 是什么呢?不难猜到,这应该是用来获取父进程 PID 的系统调用接口,接下来我们写段代码来具象化 PID 吧。

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

int main()    
{    
    while(1)    
    {    
        printf("I am a process, my id is: %d, parent id is: %d\n", getpid(), getppid());                                  
        sleep(1);    
    }    
    return 0;    
}

我们可以写一个脚本来实时获取上面这段代码执行起来后的进程信息。

在这里插入图片描述

在这里插入图片描述

可以看到,我一个将这段代码执行了两次,每一次的子进程 PID 都在发生变化,但是父进程的 PID 从未更改。

为了保证数据的准确性,我们再使用 ps 指令对比以下获取到的进程 PID 是否真的一样。

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

在这里插入图片描述

在这里插入图片描述

结论:我们用 getpidgetppid 得到的父子进程的 PIDps 指令获取到的进程 PID 是一样的

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

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

在这里插入图片描述

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

在这里插入图片描述

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

2.1 调用fork函数后的现象

#include <stdio.h>      
#include <unistd.h>      
#include <sys/types.h> 
int main()                                                           
{    
    printf("before:only one line\n");    
    fork();    
    printf("after:only one line\n");    
    
    return 0;    
}

在这里插入图片描述

如上图所示,fork 后面的代码执行了两次!这是什么原因呢?我们再写一段代码跑跑。

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

int main()    
{    
    printf("begin:我是一个进程,pid:%d, ppid:%d\n",getpid(), getppid());    
    
    pid_t id = fork();    
    if(id > 0)    
    {    
        while(1)    
        {    
            printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());    
            sleep(1);    
        }    
    }    
    else if(id == 0)    
    {    
        while(1)    
        {    
            printf("我是子进程,pid:%d,ppid:%d\n",getpid(),getppid());    
            sleep(1);    
        }    
    }    
    else    
    {    
        perror("子进程创建失败!\n");    
    } 
    
    return 0;    
}

在这里插入图片描述

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

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

1. fork 返回值的设计目的

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

这种设计的核心目的正如您提到的,用于区分不同执行流,即便父子共享同一套代码,也可以根据返回值选择性地执行不同代码。

2. 现实类比的深入解读

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

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

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

在这里插入图片描述

进程的 PCB 对象会找到相应的代码和数据,然后 CPU 就要去调度这个进程,也就是找到该进程的代码和数据去执行。调用 fork 函数创建子进程,本质上是操作系统多了一个进程,因此 fork 函数创建出来的子进程,它要先创建自己的 PCB 对象,子进程的 PCB 对象大部分都是以父进程的 PCB 对象为模板创建的,即从父进程的 PCB 对象中拷贝过来,再对部分属性稍作修改,子进程的 PCB 对象就有了。但是它没有自己的代码和数据,所以只能用父进程的,所以 fork 函数之后,父子进程的代码共享,这就解释了为什么上面 fork 函数之后的代码输出了两次,其实就是父子进程各自执行了一次。

创建子进程的目的就是为了帮助父进程做不同的事情,但是父子进程共享一份代码,所以我们应该在代码中对它们加以区分。fork 函数就帮我们完成了这个需求,它会在父子进程中返回不同的值,用户只需要根据返回值的不同让父子进程执行不同的代码。
fork 函数的实现过程:

pid_t fork():

  • 创建子进程
  • 创建子进程的PCB
  • 填充PCB对应的内容
  • 让子进程和父进程指向同样的代码
  • 此时父子进程都有独立的task_struct对象,可以被CPU调度运行了
  • return ret;

由于父子进程会共享一份代码,所以在 fork 函数执行 return 语句之前,子进程的 PCB 对象就已经被创建出来了,CPU 已经可以去同时调度父子进程。由于 fork 函数中的 return 语句也是被共享的,所以 fork 函数有两个返回值。

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

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

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

2. fork 中变量 id 的本质

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

结语

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

以上就是一文详解Linux中的fork机制的详细内容,更多关于Linux fork机制的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文