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

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

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<stdio.h>  
#include<unistd.h>  
#include<sys/types.h>  
int main()  
{
    printf("我是一个进程,我的pid:%d\n",getpid());
  
    fork();
  
    printf("i am a process,pid:%d\n",getpid());
    sleep(1);
    return 0;
}

请看下面的图片观察情况:

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

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
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;
}

请看以下图片观察情况:

它会循环打印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
{
  执行父进程的专有代码
}

实际上我们创建子进程的意义就是

为了让子进程执行和父进程不一样

的代码,实现和父进程不一样的功能

比如我们可以一边下载软件一边播放

音乐,这两个过程就是不同的进程在执行!

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

#include<stdio.h>                                                                                                                                                       
#include<sys/types.h>    
#include<unistd.h>    
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;
}

请看下图观察情况:


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进程状态信息 🔍


相关文章
|
11月前
|
Shell Linux C语言
函数和进程之间的相似性
在一个C程序可以fork/exec另一个程序,其过程是先fork一个子进程,然后让子进程使用exec系列函数将子进程的代码和数据替换为另一个程序的代码和数据,之后子进程就用该程序的数据执行该程序的代码,从而达到程序之间相互调用的效果。在学了C语言、C++或是JAVA等高级语言,你会知道,在这些语言中的函数是可以相互进行见调用的,但是在学习了Linux的前面的知识后,你就会有意无意的认识到其实进程也是与函数有相同之处的,进程之间也是可以相互调用的。程序之间相互调用带来的好处之一。那么下面就将这部分内容扩展。
169 0
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
1354 5
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
721 13
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
消息中间件 存储 Linux
Linux手账—exec和fork
本文介绍了Linux系统中进程控制的核心功能——`fork`和`exec`系列函数。`fork`用于创建新进程(子进程),继承父进程的资源但拥有独立的地址空间;`exec`系列函数则在当前进程中执行新程序,替换原有地址空间。文章详细解析了这些函数的基本概念、用法及工作原理,强调了它们在多进程编程中的重要性。
333 0
|
编译器
【收藏】内核级利用通用Hook函数方法检测进程
【收藏】内核级利用通用Hook函数方法检测进程
linux内核执行fork时对写时复制的设置
linux内核执行fork时对写时复制的设置
|
存储 Linux 调度
Linux 0.11 fork 函数(二)
Linux 0.11 fork 函数(二)
276 0