当前位置: 首页 > article >正文

Linux 子进程 -- fork函数

子进程

什么是子进程?

子进程指的是由一个已经存在的进程(称为父进程或父进程)创建的进程.

如: OS (操作系统) 就可以当作是一个进程, 用来管理软硬件资源, 当我点击浏览器, 想让浏览器运行起来时, 实际上是由 OS 接收指令, 然后 OS 帮我们将浏览器运行起来, 浏览器就是一个子进程, 是由 OS 创建出来的进程, 所以 OS 就是浏览器这个进程的父进程.
( OS 并不是一个单独的进程, 是由多个进程和线程组成的复杂结构, 这里为了方便理解, 将整个 OS 当作是一个进程)

如何创建子进程

在 Linux 中, 操作系统为我们提供了一个系统调用函数: fork()

fork() 函数可以在程序中, 创建一个子进程.

#include <sys/types.h>
#include <unistd.h>
// 以上为所需的库函数

// 函数声明
pid_t fork(void);

可以看到这个函数没有参数, 有一个 pid_t 类型的返回值.

成功后, 子进程的PID将在父进程中返回, 0将在子进程中返回.

失败时, -1在父进程中返回, 不创建子进程, 并且适当设置了errno.

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

int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        std::cout << "fork error" << std::endl;
    }
    if(pid == 0)
    {
        std::cout << pid << ": this is child" << std::endl;
    }
    else
    {
        std::cout << "创建的子进程的pid: " << pid << std::endl;
    }
    return 0;
}

可以看到程序运行之后, 打印出了两条语句.
创建出来的子进程的 pid 是 598281.

但是我们只是看到了有进程被创建了, 怎么证明他们之间是父子关系?

先来介绍两个函数: 

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

pid_t getpid(void); // 返回当前进程的 pid
pid_t getppid(void); // 返回当前进程的 父进程的 pid

然后将上面的代码修改一下

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

int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        std::cout << "fork error" << std::endl;
    }
    if(pid == 0)
    {
        std::cout << "this is child ppid: " << getppid() << " pid: " << getpid() << std::endl;
    }
    else
    {
        std::cout << "this is parent pid: " << getpid() << std::endl;
    }
    return 0;
}

 

可以观察到子进程打印出来的父进程的pid, 和父进程的 pid 是相同的, 所以他们之间确实是父子关系.

fork 函数讲解 一

从上面的代码我们可以感知到, 子进程所执行的代码, 是 fork 函数之后的代码.
否者子进程也从头开始执行, 那么就形成了一个死循环, 子进程还回去创建自己的进程.
就会创建出无数的进程, 最后软件资源也就被分配完了, 电脑也可以关机重启了.

那么 fork 函数有什么作用呢?

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

int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        std::cout << "fork error" << std::endl;
    }
    if(pid == 0)
    {
        // 执行子进程的专有代码
    }
    else
    {
        // 执行父进程的专有代码
    }
    return 0;
}

比如: 我在使用 QQ 聊天, 此时有人发了一个文件给我, 我点击接收文件, 然后 QQ 就创建了一个子进程去执行文件的下载, 这样在文件下载的时候, 我还是能聊天. 

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

int main()
{
    pid_t pid = fork();
    if (pid == -1)
    {
        std::cout << "fork error" << std::endl;
    }
    if (pid == 0)
    {
        while (1)
        {
            std::cout << "我在聊天" << std::endl;
        }
    }
    else
    {
        while (1)
        {
            std::cout << "我在下载东西" << std::endl;
        }
    }
    return 0;
}

fork 函数讲解 二

那么 fork 函数究竟是怎么创建出子进程的?

fork 函数创建的子进程, 实际上是一个父进程的副本, 子进程中没有代码和数据, 子进程和父进程共享代码和数据

所以 fork 之后父子进程执行的代码是一样的.

那么问题来了, 在上面创建子进程的代码中, 我们通过 fork 的返回值 pid 来区分父子进程.

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

int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        std::cout << "fork error" << std::endl;
    }
    if(pid == 0)
    {
        // 执行子进程的专有代码
    }
    else
    {
        // 执行父进程的专有代码
    }
    return 0;
}

我们又说父子进程之间是共享代码和数据的, 那么 pid 这个变量也是两方共享的, pid 是一个变量, 一个变量怎么可能同时存在多个值.

fork 函数讲解 三

(1) 两个值从哪来

首先我们要知道, 这两个值是从哪里来的?

之前说了, 父子进程都会执行 fork 函数之后的内容. 
当 fork 函数执行到 return 时, fork 函数才算结束.

那么我们可以看到, 在 fork 函数执行 return 之前, 子进程已经被创建, 并且运行起来了.
之前所说的 子进程 执行 fork 函数之后的代码, 是不准确的,
在 fork 的内部还有一部分代码子进程是会执行的, 从放入调度队列之后.

那么也就是说, 父子进程都是执行 fork 的 return 函数, 那么 return 函数就被执行了两次, 那么也就出现了两个返回值.

(2) 一个变量怎么有多个值

我们已经知道了两个值是怎么来的.
那么下一个问题: 为什么 pid 会有两个值?

之前我们说过了, 父子进程共享数据, 那么父子进程访问同一个变量, 得到的值应该是相同的.

首先我们需要知道: 进程之间具有独立性, 进程有自己的堆栈, 数据 .......
在上面我们说了, 父子进程之间共享数据, 那么当父子进程运行之后, 可能就会修改数据
那么无论哪一方修改数据, 对另一个进程而言, 它的独立性都遭到了破坏. 这是不合理的.

所以, 为什么在创建子进程的时候, 父子进程共享代码和数据?

因为: 拷贝数据需要消耗资源, 而父子进程在运行时, 可能都不会发生修改数据的操作.

那么我就在父子进程修改数据之前, 让父子进程共享,

如果有进程发生了修改数据的操作, 此时我们在将父子进程的数据分离开来. (写时拷贝)

父子进程各自有一份独立的数据 ,各自维护

fork 函数的返回值, 赋值给 pid 时, 也就是发生了数据的修改, 那么此时就会发生写时拷贝, 将父子进程之间的资源分离开, 各自维护各自的数据.

所以当我们在父子进程使用同一个对象时, 获取的 值 不同也就可以理解了.
pid 看上去时一个变量, 实际上是在父子进程中的 两个不同的变量, 只是在父子进程中名称相同.


http://www.kler.cn/a/412212.html

相关文章:

  • day05(单片机高级)PCB基础
  • 解决SSL VPN客户端一直提示无法连接服务器的问题
  • 【GAMES101笔记速查——Lecture 19 Cameras,Lenses and Light Fields】
  • rabbitmq 启动异常问题排查
  • Python绘制太极八卦
  • 【通俗理解】隐变量的变分分布探索——从公式到应用
  • python之开发笔记
  • 力扣 LRU缓存-146
  • 基于微信小程序的校园二手交易平台设计与实现,微信小程序(定制+讲解+咨询)校园二手商品在线交易系统、校园二手市场管理与推荐工具、智能化商品交易与推荐平台
  • 网络安全、Web安全、渗透测试之笔经面经总结(一)
  • FastAPI学习最后一天: Cors跨域和token鉴权
  • MySQL 存储引擎切换场景与示例
  • 泷羽Sec学习笔记:shell(2)永久环境变量和字符串显位
  • 【Vue】计算属性
  • Leetcode 每日一题 3.无重复字符的最长子串
  • 基于springboot的雪具销售系统
  • “华为杯”研究生数学建模比赛历年赛题汇总(2004-2024)
  • localStorage缓存 接口 配置
  • python写共享内存,格式json
  • 实践篇:青果IP助理跨境电商的高效采集
  • JQuery -- 第九课
  • AWS IAM 及其功能
  • 『VUE』33. 组件保持存活,切换组件时的生命周期(详细图文注释)
  • 标记matlab曲线的x坐标
  • pyhton+yaml+pytest+allure框架封装-全局变量接口关联
  • C#调用C++ DLL方法之C++/CLI(托管C++)