Linux fork函数

news2024/11/29 4:50:43

目录

0.前言

1.fork函数初识

2.写时拷贝

3.fork常规用法

4.fork调用失败的原因

5.小结


(图像《分叉之光:科幻视角下的Linux进程复制》由AI生成) 

0.前言

在Linux操作系统中,进程是系统资源管理的核心单元。每一个程序的执行都对应着一个进程,进程管理成为操作系统的重要组成部分。进程的创建、终止和管理,是理解操作系统工作原理的关键内容。在介绍了Linux进程相关的基本概念之后,接下来我们将介绍一个非常重要的系统调用——fork函数,它是创建进程的主要工具。

1.fork函数初识

在Linux系统中,fork()函数是用来创建新进程的非常重要的系统调用。通过调用fork(),一个进程可以创建一个几乎完全相同的子进程。fork函数的原型如下:

#include <unistd.h>
pid_t fork(void);
  • 返回值fork()在父进程和子进程中返回不同的值:
    • 子进程中,fork()返回0,表示这个进程是子进程。
    • 父进程中,fork()返回新创建的子进程的PID,用于父进程识别。
    • 如果fork()调用失败,函数返回-1,同时设置全局变量errno来指出具体的错误原因。

fork()函数用于从一个已经存在的进程中创建一个新进程,新进程被称为子进程,而原进程称为父进程。这个过程的核心步骤如下:

  1. 分配内存和数据结构:内核为新创建的子进程分配独立的内存块和数据结构。
  2. 复制父进程数据:部分父进程的数据结构会被复制到子进程中,如文件描述符表、信号处理方式等。
  3. 系统进程表更新:子进程被添加到系统的进程列表中,成为独立的进程。
  4. 返回值和调度fork()调用结束后,父进程和子进程分别开始执行,并根据调度器的安排决定谁先运行。

下面我们通过一个简单的示例代码来展示fork()函数的基本用法:

#include <stdio.h>
#include <unistd.h>

int main(void) {
    pid_t pid;
    printf("Before: pid is %d\n", getpid());

    pid = fork(); // 创建子进程

    if (pid == -1) {
        perror("fork failed");
        return 1;
    }

    printf("After: pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);  // 延迟以便观察进程执行情况
    return 0;
}

 运行结果如下:

从输出结果中可以看到:

  1. fork()调用之前,只有父进程在执行,输出了一行“Before”消息。
  2. fork()调用后,两个进程分别执行:父进程打印了“After”消息,并显示返回的是子进程的PID(346618);子进程同样打印了“After”消息,但它的返回值为0。

在调用fork()之后,父进程和子进程将并行执行,它们的执行顺序完全由系统的调度器决定。因此,fork()之后,父进程和子进程的执行顺序无法预测,可能是父进程先执行,也可能是子进程先执行。

2.写时拷贝

写时拷贝(Copy-on-Write,简称COW)是一种优化技术,用于在fork函数创建子进程时减少不必要的内存复制。在fork函数被调用时,操作系统会为子进程分配与父进程相同的地址空间,但并不会立刻复制父进程的整个内存数据。这意味着,父进程和子进程在创建之初是共享相同的物理内存的。

(图片来自https://blog.csdn.net/DEXTERFUTIAN/article/details/131114725,十分感谢,若侵权请联系删除) 

共享内存的策略是基于“只读共享”的原则,也就是说,父进程和子进程在不修改内存内容的情况下可以安全地共享相同的内存数据。然而,当父进程或子进程尝试修改共享的内存时,系统才会真正地复制该内存块,将它们分别分配给父子进程。这种延迟到写入时才执行的内存复制过程就是“写时拷贝”。

写时拷贝的优点在于:

  1. 节省内存:在许多情况下,父子进程不需要修改大量的内存,因此无需在fork时立即复制整个地址空间,节省了系统内存的使用。
  2. 提升性能:减少了创建子进程时的大量不必要的内存复制操作,从而加快了fork的执行速度。

举个例子,当fork后子进程和父进程共享某个数据区域时,只有当其中一个进程尝试修改这部分数据时,内核才会为这个进程创建独立的副本。而在两者都没有写入的情况下,这块数据始终是共享的。

3.fork常规用法

fork函数在Linux中有多种常规用法,主要用于进程的并发执行、后台任务创建、以及进程间通信等场景。以下是fork函数的几种常见应用场景:

创建多进程并发处理

fork常被用来创建多个并发进程,这些进程可以同时执行不同的任务。通过创建子进程,父进程和子进程可以协同处理不同的部分任务,从而提高程序的执行效率。这在需要并发处理的程序中非常常见,比如网络服务器,它可以为每个连接创建一个子进程来处理客户端的请求。

pid_t pid = fork();
if (pid == 0) {
    // 子进程执行
    printf("Child process: PID = %d\n", getpid());
} else {
    // 父进程执行
    printf("Parent process: PID = %d\n", getpid());
}

在这个例子中,父进程和子进程各自执行不同的任务,通过fork函数实现了多进程并发处理。

守护进程(Daemon)创建

守护进程是指那些在后台运行且与终端无关的进程,通常用于长时间运行的服务程序。fork常被用于创建守护进程,通过多次fork,子进程可以与终端脱离,并在后台独立运行。

守护进程的创建步骤通常是:

  1. 父进程调用fork创建子进程,父进程退出。
  2. 子进程调用setsid()创建一个新的会话,脱离终端。
  3. 子进程再调用fork生成孙进程,子进程退出,孙进程成为真正的守护进程。
pid_t pid = fork();
if (pid > 0) {
    // 父进程退出
    exit(0);
}

if (setsid() < 0) {
    // 创建新会话失败
    exit(1);
}

// 第二次fork,避免守护进程获得控制终端
pid = fork();
if (pid > 0) {
    exit(0);  // 退出中间进程
}

// 守护进程正式启动
while (1) {
    // 守护进程任务
}

执行新程序

父进程创建子进程后,通常子进程会通过exec族函数来执行另一个程序。fork结合exec可以创建一个新进程来执行不同的程序,而父进程继续执行原有任务。这种方式常用于shell等场景。

pid_t pid = fork();
if (pid == 0) {
    // 在子进程中执行新程序
    execlp("/bin/ls", "ls", NULL);
} else {
    // 父进程等待子进程结束
    wait(NULL);
}

在这个例子中,子进程通过execlp执行ls命令,而父进程则等待子进程完成任务。

4.fork调用失败的原因

fork调用可能会失败,通常由以下几个常见原因引起:

  1. 进程数限制:操作系统对每个用户可以创建的最大进程数有限制,通常可以通过ulimit -u命令查看。如果用户已达到创建进程的上限,fork调用将失败。

  2. 内存不足:虽然fork使用了写时拷贝策略,但仍需要为子进程分配必要的内核数据结构和内存。如果系统可用内存不足,fork可能无法成功分配这些资源。

5.小结

通过本文,我们初步介绍了Linux中的fork函数,它是进程创建的核心机制。我们了解了fork的基础工作原理、写时拷贝策略及其在多进程处理中的常规用法。同时,我们也探讨了可能导致fork调用失败的几种常见原因。fork函数作为Linux进程管理的重要工具,其高效的设计以及灵活的应用,使其成为多任务处理和并发编程的核心技术之一。希望通过本文的介绍,能够帮助读者更好地理解并应用fork函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2218733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习(MachineLearning)(8)——模型评估与优化

机器学习&#xff08;MachineLearning&#xff09;&#xff08;1&#xff09;——机器学习概述 机器学习&#xff08;MachineLearning&#xff09;&#xff08;2&#xff09;——线性回归 机器学习&#xff08;MachineLearning&#xff09;&#xff08;3&#xff09;——决策树…

Java网络编程-简单的API调用

Get请求 - 无参数 安装依赖库 首先需要安装一个库&#xff1a; Okhttp3&#xff0c;这是一个非常流行的 HTTP 库&#xff0c;可以简单、快速的实现 HTTP 调用。 安装 Okhttp3 的方式是在 pom.xml 文件中增加依赖&#xff1a; <!-- https://mvnrepository.com/artifact/co…

【Vue.js设计与实现】第三篇第9章:渲染器-简单Diff算法-阅读笔记

文章目录 9.1 减少 DOM 操作的性能开销9.2 DOM 复用与 key 的作用9.3 找到需要移动的元素9.4 如何移动元素9.5 添加新元素9.6 移除不存在的元素 系列目录&#xff1a;【Vue.js设计与实现】阅读笔记目录 当新旧vnode 的子节点都是一组节点时&#xff0c;为了以最小的性能…

PSPICE FOR TI笔记记录1

快捷放置器件 R旋转 连线 w,单击器件引脚方块部分 电压探测笔 创建仿真文件 Analysis Type 分析模式&#xff1a;比如时域分析&#xff0c;频域分析 Run To Time 仿真时长 Skip intial transient bias point calculation (跳过初始瞬态偏置点计算(SKIPBP))一定要勾选 编辑…

高级语言源程序转换为可执行目标文件

将高级语言源程序转换为可执行目标文件的过程通常包括以下几个主要步骤&#xff1a; ​ 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 由谁完成预处理器&#xff08;cpp&#xff09;操作处理源代码中的预处理指令&#xff08;如宏定义、文件包含、条件编译等&…

k8s 1.28.2 集群部署 harbor v2.11.1 接入 MinIO 对象存储

文章目录 [toc]提前准备什么是 HarborHarbor 架构描述Harbor 安装的先决条件硬件资源软件依赖端口依赖 Harbor 在 k8s 的高可用Harbor 部署Helm 编排YAML 编排创建 namespace导入镜像部署 Redis部署 PostgreSQL部署 Harbor core部署 Harbor trivy部署 Harbor jobservice部署 Ha…

《Sui区块链:重塑去中心化应用的新星与未来潜力》

目录 引言 一、Sui 1、 技术架构 2、 编程语言 3、Move起源 4、Move的几个关键点&#xff1a; 5、Move 智能合约编程语言 6、智能合约编程语言可以做什么 7、和其他编程语言有什么不同 8、 安全性 9、开发者体验 10、生态系统 11、 未来发展 总结 引言 在区块链技…

AIGC助力小学生编程梦:C++入门不再难!

文章目录 一、AIGC时代下的编程教育新趋势二、小学生C入门趣味编程的意义三、小学生C入门趣味编程的实践策略四、面临的挑战与应对策略五、AIGC技术在小学生C编程中的应用与前景《小学生C趣味编程从入门到精通》编辑推荐内容简介作者简介目录 随着人工智能生成内容&#xff08;…

基于百度智能体开发爱情三十六计

基于百度智能体开发爱情三十六计 文章目录 基于百度智能体开发爱情三十六计1. 爱情三十六计智能体2. 三十六计开发创意3. 智能体开发实践3.1 基础配置3.2 进阶配置3.3 调优心得3.4可能会遇到的问题 4. 为什么选择文心智能体平台 1. 爱情三十六计智能体 爱情三十六计 是一款基于…

DORA 机器人中间件学习教程(6)——激光点云预处理

文章目录 1 移植思路2 代码输入输出说明3 编写CmakeList.txt文件4 编写yml文件5 编译并启动节点参考资料 在DORA中通过驱动获取激光雷达数据后&#xff0c;激光点云预处理部分代码是参考了autoware官方代码并对其进行裁剪得到的&#xff0c;点云预处理主要包含三个节点&#xf…

vue3项目使用百度地图实现地图选择功能代码封装(开箱即用)

vue3项目使用百度地图实现地图选择功能代码封装方案(开箱即用) <template><div class="bmapgl">

音视频入门基础:FLV专题(15)——Video Tag简介

一、引言 根据《video_file_format_spec_v10_1.pdf》第75页&#xff0c;如果某个Tag的Tag header中的TagType值为9&#xff0c;表示该Tag为Video Tag&#xff1a; 这时StreamID之后紧接着的就是VideoTagHeader&#xff0c;也就是说这时Tag header之后的就是VideoTagHeader&…

MySQL常用命令大全

博客主页&#xff1a;长风清留扬-CSDN博客系列专栏&#xff1a;MySQL入门到入魔每天更新大数据相关方面的技术&#xff0c;分享自己的实战工作经验和学习总结&#xff0c;尽量帮助大家解决更多问题和学习更多新知识&#xff0c;欢迎评论区分享自己的看法感谢大家点赞&#x1f4…

windows mysql 8.0版本重置root密码

1.停止mysql服务 以管理员运行cmd 2.安全模式启动 mysqld --console --skip-grant-tables --shared-memory 3.修改密码 再开个cmd窗口就可以进入了&#xff1a;mysql 先进入mysql database&#xff1a;use mysql 修改密码&#xff1a;ALTER USER rootlocalhost IDENTIFIED …

FFmpeg 4.3 音视频-多路H265监控录放C++开发二 : 18.04ubuntu安装,linux 下build ffmpeg 4.3 源码 并测试

测试环境 ubuntu 18.04 64 位&#xff0c;安装vmware and ubuntu 安装后调整 分辨率&#xff1a; 让windows 可以和 linux 互相复制黏贴 sudo apt-get autoremove open-vm-tools sudo apt-get update sudo apt-get install open-vm-tools-desktop 一直Y reboot 依赖安装 sud…

快速在找到函数的实体的方法

当我们写了许多许多的函数&#xff0c;那我们怎么快速的找到他们呢 我们只需要按下ctrl&#xff0c;在点击函数名字就可以快速的找到我们想要的函数

从Apple Intelligence到远程机器人手术:更快、更安全的网络成企业业务关键

过去&#xff0c;企业的业务模式和网络架构相对简单&#xff0c;数据传输量不大&#xff0c;远程访问需求也不多。企业对网络的要求主要集中在确保基本的连通性和可用性。如今&#xff0c;企业通过将产品与各项高新技术深度融合&#xff0c;赋予传统产品活力和竞争力。以苹果公…

C++20中头文件span的使用

<span>是C20中新增加的头文件&#xff0c;此头文件是containers库的一部分。包括&#xff1a; 1.模板类std::span&#xff1a;连续对象序列的非拥有视图(view)。std::span可以具有static extent&#xff0c;在这种情况下&#xff0c;序列中的元素数量在编译时已知并以typ…

06.队列介绍+实现

目录 一、队列的概念 二、队列的实现 1、头文件定义 2、功能函数实现 3、主函数测试 一、队列的概念 队列就像吃饭排队类似&#xff0c;先来先吃&#xff0c;先进先出。 队头&#xff1a;队列的头部。 队尾&#xff1a;队列的尾部。 入队&#xff1a;在队尾操作。 出队&…

汽车免拆诊断案例 | 2023款零跑C01纯电车后备厢盖无法电动打开和关闭

故障现象  一辆2023款零跑C01纯电车&#xff0c;累计行驶里程约为2万km&#xff0c;车主进厂反映&#xff0c;后备厢盖无法电动打开和关闭。 故障诊断  接车后试车&#xff0c;操作后备厢盖外侧、驾驶人侧及遥控钥匙上的后备厢盖开启按钮&#xff0c;可以听到后备厢盖解锁的…