Linux fork函数

ops/2024/10/19 15:02:24/

目录

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.ppmy.cn/ops/126747.html

相关文章

集群Spring定时只执行一次

方法一.配置文件 1.在application-jdbc.yml 配置一个标识 例如&#xff1a;dingshizhixing1&#xff08;0不执行 1.执行&#xff09; 2.在定时文件里引入&#xff1a; Value("${dingshirenwu}") private String dingshirenwu; 执行定时方法加判断 if&#xff08…

MySQL-多表查询

子查询[分步走] 1:一个sql的查询结果当做另一个sql的查询条件. 2:内层的那个sql语句要先执行 sql -- todo --------------子查询---(嵌套查询)--------------- -- 例如&#xff0c;使用命令完成&#xff1a; -- &#xff08;1&#xff09;使用数据库班级db_product3下的商品表和…

小猿口算炸鱼脚本

目录 写在前面&#xff1a; 一、关于小猿口算&#xff1a; 二、代码逻辑 1.数字识别 2.答题部分 三、代码分享&#xff1a; 补充&#xff1a;软件包下载 写在前面&#xff1a; 最近小猿口算已经被不少大学生攻占&#xff0c;小学生直呼有挂。原本是以为大学生都打着本…

数据分析方法(回归分析,决策树与神经网络,提升树,时间序列分析,假设检验,用户画像,竞品分析)等

1.回归分析 回归分析是一种统计方法&#xff0c;用于探索自变量&#xff08;预测变量&#xff09;和因变量&#xff08;目标变量&#xff09;之间的关系。它可以帮助预测变量的变化对目标变量的影响大小。例如&#xff0c;简单线性回归用于分析两个变量之间的线性关系&#xf…

【工程测试技术】第4章 常用传感器分类,机械式,电阻式,电容式,电感式,光电式传感器

上理考研周导师的哔哩哔哩频道 我在频道里讲课哦 目录 4.1 常用传感器分类 4.2 机械式传感器及仪器 4.3 电阻式、电容式与电感式传感器 1.变阻器式传感器 2.电阻应变式传感器 3.固态压阻式传感器 4.典型动态电阻应变仪 4.3.2 电容式传感器 1.变换原理 2.测量电路 …

Vidmore Screen Recorde 2.0.20 学习 体验 不错!

有难度&#xff0c;历时5个小时&#xff0c;两个文件交叉验证&#xff0c;网络验证&#xff0c;QT5&#xff0c;源码都在图片里面。 补充补丁类 public class PatchManager : INotifyPropertyChanged {public class PatchOperation{public long Offset { get; set; }public …

Linux中安装python3.8

源码安装 Python 3.8 安装编译所需的依赖&#xff1a;首先安装一些编译 Python 所需的依赖包&#xff1a; yum groupinstall "Development Tools" yum install gcc openssl-devel bzip2-devel libffi-devel zlib-devel下载 Python 3.8 源码&#xff1a; cd /usr/s…

前端页面使用google地图api实现导航功能,开发国外网站免费简单好用

开发国外软件的时候&#xff0c;想使用goole map实现导航等功能&#xff0c;可以使用google的api来做&#xff0c;官方文档地址&#xff1a;https://developers.google.com/maps/documentation/urls/get-started?hlzh-cn &#xff0c;比如&#xff1a; 支持的请求的操作&…