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

Linux进程管理之子进程的创建(fork函数)、子进程与线程的区别、fork函数的简单使用例子、子进程的典型应用场景

收尾

进程终止:子进程通过exit()或_exit()终止,父进程通过wait()或waitpid()等待子进程终止,并获取其退出状态。?其实可以考虑在另一篇博文中来写

fork函数讲解

fork函数概述

fork() 是 Linux 中用于创建新进程的系统调用。当一个进程调用 fork() 时,系统会创建一个与原进程几乎完全相同的子进程。
新的子进程在有相关写操作时,会复制父进程的资源(即写时复制的概念)。
父进程的PID和子进程的PID是不同的。
父进程和子进程会从 fork() 调用的返回值处开始继续执行,但返回值在父进程和子进程中是不同的。

fork函数的工作原理和流程

其具体工作原理和流程如下:
当某个进程调用fork() 创建子进程后,系统会创建一个与原进程几乎完全相同的子进程,然后父进程和子进程会从fork()返回的地方继续执行。
不过两个进程得到的fork()的返回值不一样:

  • 对于父进程,它得到的fork()的返回值是子进程PID;
  • 对于子进程,它得到的fork()的返回值是0;

即:
如果 fork() 成功,它会返回两次:在父进程中返回子进程的 PID,在子进程中返回 0。
如果 fork() 失败(如资源不足),返回 -1,并设置 errno 以说明错误。

子进程的特点

  • 父子进程的几乎相同:子进程是父进程的副本,它们俩几平是相同的,除了fork() 的返回值和 PID。
  • 资源复制:父子进程共享文件描述符、内存映射等资源,但会使用“写时复制”(Copy-On-Write, COW)技术优化内存使用。这意味着在父子进程开始执行时,内存不会立即复制,而是在修改内存时才复制。
  • 进程独立性:父进程和子进程是独立的,执行过程中互不影响,但它们会共享某些资源(如打开的文件)。

进程与线程的区别

fork() 创建的子进程在某些方面与线程(特别是线程的创建和管理)有相似之处,但它们在操作系统级别的实现和资源管理上有一些关键的区别。

  1. 进程(Process)
  • 进程是操作系统分配资源的基本单位。每个进程都有独立的地址空间、文件描述符、栈、堆等资源。
  • 进程之间是独立的,它们不会共享内存空间(除非显式使用共享内存或其他进程间通信机制),每个进程有自己的 PID 和独立的资源。
  • 进程的创建(通过 fork())是相对重的操作,需要操作系统为子进程分配新的资源(如内存)。
  1. 线程(Thread)
  • 线程是进程内部的执行单元,同一个进程中的多个线程共享进程的资源(如内存、文件描述符等),它们共享相同的地址空间。
  • 线程之间是轻量级的,因为它们共享进程的资源,而不需要像进程那样拥有完全独立的资源。
  • 线程的创建通常比进程轻量,操作系统管理线程的开销较小,线程之间可以很容易地进行共享数据和通信(如使用互斥锁、条件变量等)。

二者的关键差异

  1. 地址空间和资源

    • 进程:子进程有独立的地址空间。父进程与子进程之间没有直接共享内存,除非使用共享内存(mmap())或其他进程间通信(IPC)方式。
    • 线程:线程在同一进程内共享内存、文件描述符等资源。线程间的通信非常高效,因为它们共享相同的地址空间。
  2. 创建开销

    • 进程:通过 fork() 创建子进程时,操作系统需要为新进程分配独立的资源(如内存空间)。这使得进程创建的开销相对较大。
    • 线程:创建线程时,操作系统只需要分配线程控制块(TCB)等较小的资源,不需要分配独立的内存空间,因此线程创建比进程轻量。
  3. 执行独立性

    • 进程:父进程与子进程相互独立,父进程的退出不会影响子进程,反之亦然。它们各自拥有独立的控制流。
    • 线程:同一进程中的多个线程共享控制流,互相协作。线程的退出会影响到进程的状态,甚至可能导致整个进程退出。
  4. 调度和切换

    • 进程:操作系统调度时,进程之间的切换需要保存和恢复更多的状态,因为每个进程有独立的地址空间。
    • 线程:线程之间的切换较轻量,操作系统只需要保存和恢复少量状态(如寄存器、栈指针等),因为线程共享地址空间。

fork() 创建的子线程和线程的相似性和差异

相似性:

  • 并行执行:无论是 fork() 创建的子进程还是线程,它们都可以并行执行(多核 CPU 上);
  • 并发性:父进程与子进程之间的调度是并发的,线程间的调度也是如此。

差异:

  • 资源分配fork() 创建的子进程拥有独立的资源(如地址空间、PID),而线程共享进程的资源。
  • 进程控制:父进程与子进程是完全独立的,退出父进程不会直接影响子进程;线程则不同,进程退出时会导致所有线程结束。

何时选择使用进程,何时使用线程?

  • 如果需要完全隔离的执行环境,或者需要实现进程间的严格隔离,应该选择进程(使用 fork())。例如,fork() 在服务器中常用于创建多个独立的工作进程,每个进程可以处理独立的任务。
  • 如果需要多个轻量级的并发任务,并且共享资源是必须的,应该选择线程。线程通常适用于需要大量并发操作且共享内存的场景,例如 Web 服务器的请求处理。

小结

虽然子进程和线程在某些方面表现得有点相似——例如它们可以并行执行,但它们本质上是不同的:子进程具有独立的资源和地址空间,而线程则是共享同一进程的资源。fork() 创建的子进程是“重型”的,而线程则是“轻型”的。因此,子进程和线程虽然在并发执行上有相似之处,但它们的实现和适用场景有很大区别。

fork函数使用的简单例子

下面是一个简单的示例,展示如何使用 fork() 创建一个子进程:

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

int main() {
    pid_t pid = fork();  // 创建新进程

    if (pid == -1) {
        // 错误处理:fork()失败
        perror("fork failed");
        return 1;
    } else if (pid > 0) {
        // 父进程
        printf("This is the parent process, PID: %d, child PID: %d\n", getpid(), pid);
        sleep(2);  // 父进程睡眠 2 秒,确保子进程有时间执行,否则有可能出现子进程还没执行完父进程就执行完的情况
    } else {
        // 子进程
        printf("This is the child process, PID: %d, parent PID: %d\n", getpid(), getppid());
    }

    return 0;
}

代码解释:
fork() 被调用时,操作系统会在内核中创建一个新进程,子进程会复制父进程的大部分资源(包括内存、文件描述符等)。然后父进程和子进程都会从 fork() 语句的下一行代码开始继续执行,但它们的执行路径有所不同:

  • 父进程:父进程得到的fork() 返回值是子进程的 PID(大于 0 的值),父进程会执行属于它的if条件分支代码块。
  • 子进程:子进程得到的fork() 返回值的值是 0,子进程会执行属于它的if分支条件代码块。

由于父进程和子进程得到的fork() 的返回值不一样,即各自的pid变量的值不一样,所以在后续的代码中各自执行了不同的条件分支。

在Linux开发板上测试上面这个例子

代码文件复制到Ubuntu中

在这里插入图片描述

交叉编译

运行下面的命令进行交叉编译

cd /home/book/mycode/C0034_fork
arm-buildroot-linux-gnueabihf-gcc -o fort_test fort_test.c

在这里插入图片描述
复制到NFS目录中,备用。
在这里插入图片描述

在开发板上测试

打开串口终端→打开开发板→挂载网络文件系统

执行下面的代码运行测试程序

/mnt/fork_test/fort_test

运行结果如下:
在这里插入图片描述
可见,符合我们的预期,所以测试成功。

子进程的应用场景

子进程的创建通常是为了实现进程间的隔离并行化或者并发处理,以及提升程序的响应性资源管理。虽然有时线程可能更合适,但子进程在某些场景下非常重要。下面我将列举一些典型的子进程应用场景,帮助你更好地理解它们的使用场景。

注意:要理解下面的应用场景关键要明白子进程与父进程为何能执行不同的代码,其实在实际代码中就是通过判断fork的返回值来进行的,通过前面的例子我们已经知道fork()函数对父进程和子进程的返回值不一样嘛。

1. 服务器模型中的进程池

  • 场景:Web 服务器(如 Apache)或数据库服务器常常需要处理大量并发请求。这时,创建一个子进程来处理每一个请求,可以保证每个请求都在独立的进程中运行,互不干扰。
  • 原因:使用子进程可以实现进程间隔离,每个请求都在独立的进程中执行,即使其中一个请求出现崩溃或问题,也不会影响其他请求。父进程只负责接收和分发任务,而实际处理任务的工作交给多个子进程。
  • 举例
    • Web 服务器:比如 Apache 使用子进程处理不同的客户端请求,这样即使一个子进程崩溃,其他请求仍然能继续处理。
    • 数据库服务:一些数据库管理系统通过子进程处理不同的查询,确保查询之间不会互相干扰。

2. 任务调度与并行处理

  • 场景:当某个程序需要同时进行多个独立的计算任务时,创建子进程可以将这些任务分配给不同的进程,使它们并行执行,从而提高执行效率。
  • 原因:多核 CPU 环境下,父进程可以通过 fork() 创建多个子进程,这样每个子进程都可以在不同的 CPU 核心上并行执行任务,从而提高计算效率。
  • 举例
    • 科学计算:例如,处理大规模数据分析时,可以将数据分成多个块,每个子进程处理一个数据块,最后将结果合并。
    • 视频处理:在进行视频转码或图片渲染时,创建多个子进程来并行处理不同帧或不同部分的图像。

3. 资源隔离与安全性

  • 场景:一些需要高安全性的应用会将不同的功能模块分离成不同的进程,这样即使某个进程被攻击或崩溃,其他进程的运行不会受到影响。
  • 原因:子进程的隔离性使得每个进程拥有自己的内存空间和资源,避免了进程间的干扰。这对于处理敏感信息或者确保系统稳定性和安全性至关重要。
  • 举例
    • 浏览器:现代浏览器会为每个标签页、插件或扩展创建不同的进程,这样一个标签页崩溃不会导致整个浏览器崩溃。
    • 操作系统安全:如一些操作系统使用子进程来执行高权限操作,避免给系统带来潜在风险。

4. 守护进程(Daemon)

  • 场景:守护进程是一种长期在后台运行的进程,通常在系统启动时启动,并在系统关闭时停止。守护进程需要创建一个子进程来执行具体的工作。
  • 原因:守护进程通常不与用户直接交互,它们负责监视某些任务或提供某些服务。通过创建子进程,守护进程可以独立处理各种工作,保持系统的高效和稳定。
  • 举例
    • 系统守护进程:例如 cron 进程定期运行任务,sshd 进程监听远程连接。
    • 文件服务器:一个守护进程可以监听文件的变化并对文件进行同步或备份。

5. 进程控制与协作

  • 场景:父进程与子进程之间的协作。父进程创建子进程后,可以与子进程进行通信,通过管道、消息队列等机制协作完成任务。
  • 原因:父子进程之间可以通过进程间通信(IPC)进行数据传递,子进程执行任务的结果会影响父进程的执行。父进程和子进程之间的关系通常是控制与执行的关系。
  • 举例
    • 编译工具:在某些构建系统(如 make)中,父进程通过创建子进程来执行不同的编译任务,最后将结果汇总。
    • 日志管理:父进程可以创建多个子进程来处理日志文件,每个子进程独立地处理不同的日志任务,最后通过 IPC 汇总结果。

6. 多重任务并发处理

  • 场景:当一个应用程序需要并发执行多个不同的任务时,可以通过 fork() 创建多个子进程,分配任务给不同的子进程。
  • 原因:这种方式适合处理需要大量并行工作的任务,例如并行搜索、并行计算等。
  • 举例
    • 多线程下载管理器:如果一个文件很大,可以通过 fork() 创建多个子进程并行下载文件的不同部分,最后合并成一个完整的文件。
    • 分布式计算:某些分布式计算任务会将计算工作分配给多个进程来并行处理,提高整体计算速度。

7.小结

子进程通常用于以下几种情况:

  • 进程隔离和独立执行:避免不同任务间的干扰,提高系统稳定性。
  • 并行化计算:充分利用多核 CPU 提高计算效率。
  • 长期运行的守护进程:独立执行后台任务。
  • 提高安全性:隔离不同的任务和数据,防止安全问题扩散。

子进程的优势通常体现在资源隔离任务并行化上,尤其在需要并发处理多个独立任务或确保系统高可靠性和稳定性时,使用子进程可以大大提高系统性能和安全性。

http://www.dtcms.com/a/98118.html

相关文章:

  • 【19期获取股票数据API接口】如何用Python、Java等五种主流语言实例演示获取股票行情api接口之沪深A股实时交易数据及接口API说明文档
  • 参加李继刚线下活动启发:未来提示词还会存在吗?
  • 【初阶数据结构】线性表之双链表
  • 【数电】半导体存储电路
  • 基于Linux平台安装部署Redis全教程
  • 生物化学笔记:医学免疫学原理09 白细胞分化抗原+黏附分子
  • Supplements of My Research Proposal: My Perspectives on the RAG
  • 数据结构:探秘AVL树
  • 【华为OD技术面试真题 - 技术面】- Java面试题(15)
  • Android开发中的数据结构与算法:排序算法
  • TCP协议与wireshark抓包分析
  • 如何封装一个上传文件组件
  • 小河:团队金牌精准计划
  • QML中使用Image显示图片和使用QQuickItem显示图片
  • 告别桌面杂乱与充电焦虑,移速165W百变桌面充电站首发体验
  • Day43 | 129. 求根节点到叶节点数字之和、1382. 将二叉搜索树变平衡、100. 相同的树
  • 循相似之迹:解锁协同过滤的核心推荐逻辑
  • OpenBMC:BmcWeb 生效路由4 将路由添加到Trie中
  • Spring Boot事务管理详解(附银行转账案例)
  • 如何缩短研发周期,降低研发成本?全星APQP软件为您提供解决方案
  • 【Goalng】第九弹-----文件操作、JSON处理
  • 杂草YOLO数据集分享
  • 【AI插件开发】Notepad++插件开发实践:从基础交互到ScintillaCall集成
  • 第十五章:Python的Pandas库详解及常见用法
  • 【云原生】docker 搭建单机PostgreSQL操作详解
  • Pod 网络与 CNI 的作用
  • 结构化分析方法 数据流图详解
  • 每日一题-力扣-2360. 图中的最长环 0329
  • Java 大视界 -- 基于 Java 的大数据分布式计算在基因测序数据分析中的性能优化(161)
  • 力扣刷题1049. 最后一块石头的重量 II