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 提高计算效率。
  • 长期运行的守护进程:独立执行后台任务。
  • 提高安全性:隔离不同的任务和数据,防止安全问题扩散。

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

父进程等待子进程结束后自己再结束

原因分析

当 fork() 创建子进程后,子进程是独立的进程,和父进程共享部分资源,但运行是相互独立的。
如果父进程提前结束,子进程仍然可以继续执行,直到它自己终止或被系统杀死。
如果父进程退出,而子进程仍在运行,那么子进程会成为孤儿进程。
孤儿进程会被 init 进程(PID=1) 领养,init 进程会负责等待它结束,回收它的资源。

但通常我们还是会让父进程等待子进程结束后自己再结束,原因如下:

1. 防止子进程变成僵尸进程(Zombie Process)

  • 问题
    • 当子进程结束时,它的退出状态信息会保留在系统的进程表中,直到父进程使用 wait()waitpid() 读取该信息。
    • 如果父进程没有处理这个信息,子进程的进程表项不会被释放,导致僵尸进程(Zombie Process)。
    • 如果系统中存在大量僵尸进程,会占用进程表,可能导致新进程无法创建。

2. 确保父进程在子进程完成任务后再退出

  • 有时候子进程的任务和父进程相关,比如:
    • 子进程负责处理数据,父进程等待结果
    • 子进程执行重要任务,父进程需要等它完成才能继续

3. 避免创建大量孤儿进程

  • 虽然孤儿进程会被 init 进程接管,但不是所有情况下都希望这样
    • 如果有多个子进程,可能会导致 进程管理变得混乱
    • 如果子进程运行时间较长,可能会造成 不必要的资源占用
    • 更好的方法
      • 直接让父进程 wait() 等待子进程。
      • 或者 让子进程调用 setsid() 使自己变成独立的进程组。

4. 让父进程控制子进程的执行

  • 例如:
    • 父进程动态分配任务给子进程
    • 父进程需要获取子进程的退出状态来决定下一步操作

5 最佳实践

  1. 如果子进程需要完成某些任务,父进程应该 wait() 等待,避免进程管理混乱。
  2. 如果不关心子进程的状态,但不想产生僵尸进程,可以用 SIGCHLD 信号处理
    signal(SIGCHLD, SIG_IGN);
    
    这样,子进程退出后,系统会自动回收它们的资源。

使用wait让父进程等待子进程的示例代码

下面是一个简单的 C 代码示例,演示 Linux 父进程如何等待子进程终止:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();  // 创建子进程if (pid < 0) {perror("fork failed");exit(1);} else if (pid == 0) {// 子进程执行的代码printf("Child process (PID: %d) is running...\n", getpid());sleep(2); // 模拟子进程执行任务printf("Child process (PID: %d) is exiting...\n", getpid());exit(0);} else {// 父进程执行的代码printf("Parent process (PID: %d) is waiting for child (PID: %d)...\n", getpid(), pid);int status;waitpid(pid, &status, 0); // 等待子进程结束if (WIFEXITED(status)) {printf("Child process exited with status %d\n", WEXITSTATUS(status));}printf("Parent process is exiting...\n");}return 0;
}

代码说明:

  1. fork() 创建一个子进程,返回两次:
    • 在父进程中返回子进程的 PID。
    • 在子进程中返回 0。
  2. 子进程执行自己的任务,sleep(2) 模拟耗时操作,然后退出。
  3. 父进程调用 waitpid() 等待子进程结束,并获取其退出状态。
  4. WIFEXITED(status) 判断子进程是否正常退出,WEXITSTATUS(status) 获取其退出码。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI论文】挑战推理的边界:大型语言模型的数学基准测试

摘要&#xff1a;近年来&#xff0c;大型推理模型的迅猛发展导致现有用于评估数学推理能力的基准测试趋于饱和&#xff0c;这凸显出迫切需要更具挑战性和严谨性的评估框架。为填补这一空白&#xff0c;我们推出了OlymMATH&#xff0c;这是一项全新的奥林匹克级数学基准测试&…

典范硬币系统(Canonical Coin System)→ 贪心算法

【典范硬币系统】 ● 典范硬币系统&#xff08;Canonical Coin System&#xff09;是指使用贪心算法总能得到最少硬币数量解‌的货币面值组合‌。 ● 给定一个硬币系统 &#xff0c;若使其为典范硬币系统&#xff0c;则要求其各相邻面值比例 &#xff0c;及各开区间 内各金额…

Android7 Input(二)Linux 驱动层输入事件管理

概述 在Linux系统中&#xff0c;将键盘&#xff0c;鼠标&#xff0c;触摸屏等这类交互设备交由Linux Input子系统进行管理&#xff0c;Linux Input驱动子系统由于具有良好的和用户空间交互的接口。因此Linux Input驱动子系统&#xff0c;不止于只管理输入类型的设备。也可以将其…

高清壁纸一站式获取:海量分类,免费无弹窗

软件介绍 在如今这个追求个性化与高品质视觉体验的时代&#xff0c;一款出色的壁纸应用无疑能为我们的电子设备增添别样魅力。此刻&#xff0c;要给大家重磅推荐的便是Wallpaper这款应用&#xff0c;它犹如一个绚丽多彩的壁纸宝库&#xff0c;全方位满足你的审美需求。 海量壁…

Linux安装Cmake (Centos 7.9)

cmake安装 这个虽然已经更新到了4.0.0版本了&#xff0c;但是我们要用3.5版本的&#xff0c;因为这个比较稳定 官方地址&#xff1a;https://github.com/Kitware/CMake/releases/tag/v3.5.0&#xff0c;选择那个cmake-3.5.0-Linux-x86_64.tar.gz下载&#xff0c; 首先解压文…

Centos7,tar包方式部署rabbitmq-3.7.6

1. 环境准备 安装编译工具和依赖包 yum -y install make gcc gcc-c glibc-devel m4 perl openssl openssl-devel ncurses-devel ncurses-devel xz xmlto perl 2. Erlang环境搭建 版本对应&#xff1a;https://www.rabbitmq.com/docs/which-erlang 解压到指定目录 tar -xv…

【MySQL篇】事务管理,事务的特性及深入理解隔离级别

目录 一&#xff0c;什么是事务 二&#xff0c;事务的版本支持 三&#xff0c;事务的提交方式 四&#xff0c;事务常见操作方式 五&#xff0c;隔离级别 1&#xff0c;理解隔离性 2&#xff0c;查看与设置隔离级别 3&#xff0c;读未提交&#xff08;read uncommitted&a…

C++Primer学习(14.1 基本概念)

当运算符作用于类类型的运算对象时&#xff0c;可以通过运算符重载重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。举个例子&#xff0c;因为在Sales_item类中定义了输入、输出和加法运算符&#xff0c;所以可以通过下述形式输出两个Sales_item…

循相似之迹:解锁协同过滤的核心推荐逻辑

目录 一、引言二、协同过滤的基本原理三、协同过滤的算法类型&#xff08;一&#xff09;基于用户的协同过滤&#xff08;二&#xff09;基于物品的协同过滤 四、协同过滤的应用案例&#xff08;一&#xff09;电商平台的商品推荐&#xff08;二&#xff09;音乐平台的歌曲推荐…

RuoYi基础学习

1 若依搭建 前后端分离版本&#xff1a;RuoYi-Vue利用SpringBoot作为后端开发框架&#xff0c;与Vue.js结合&#xff0c;实现了前后端分离的开发模式。这种架构有助于提高开发效率&#xff0c;前后端可以独立开发和部署&#xff0c;更适合现代化的Web应用开发。 RuoYi-Vue3&a…

Docker 安装部署Harbor 私有仓库

Docker 安装部署Harbor 私有仓库 系统环境:redhat x86_64 一、首先部署docker 环境 定制软件源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repoyum install -y yum-utils device-mapper-persistent-data lvm2…

【Basys3】外设-灯和数码管

灯 约束文件 set_property PACKAGE_PIN W5 [get_ports CLK] set_property PACKAGE_PIN U18 [get_ports rst] set_property PACKAGE_PIN U16 [get_ports {led[0]}] set_property PACKAGE_PIN E19 [get_ports {led[1]}] set_property PACKAGE_PIN U19 [get_ports {led[2]}] set…

【Django】教程-1-安装+创建项目+目录结构介绍

欢迎关注我&#xff01;后续会更新django教程。一周2-3更&#xff0c;欢迎跟进&#xff0c;本周会更新第一个Demo的单独一个模块的增删改查【Django】教程-4-一个增删改查的Demo【Django】教程-2-前端-目录结构介绍【Django】教程-3-数据库相关介绍 1.项目创建 1.1 安装 Djan…

蓝桥杯 之 二分

文章目录 习题肖恩的n次根分巧克力2.卡牌 二分是十分重要的一个算法&#xff0c;常常用于求解一定范围内&#xff0c;找到满足条件的边界值的情况主要分为浮点数二分和整数二分二分问题&#xff0c;最主要是写出这个check函数&#xff0c;这个check函数最主要就是使用模拟的方法…

SpringBoot集成腾讯云OCR实现身份证识别

OCR身份证识别 官网地址&#xff1a;https://cloud.tencent.com/document/product/866/33524 身份信息认证&#xff08;二要素核验&#xff09; 官网地址&#xff1a;https://cloud.tencent.com/document/product/1007/33188 代码实现 引入依赖 <dependency><…

2025年3月电子学会c++五级真题

结绳 #include <bits/stdc.h> using namespace std;int n,a[10010];int main() {cin>>n;for(int i 0;i<n;i){cin>>a[i];}sort(a0,an);//将a数组从小到大排序double sum 0;for(int i 0;i<n;i){sum (suma[i])/2;}cout<<(int)sum;return 0; } 最…

Typora使用Gitee作为图床

Typora使用Gitee作为图床 文章目录 Typora使用Gitee作为图床Gitee准备图床仓库下载安装软件安装插件 配置Typora Gitee准备图床仓库 新建一个仓库右上角下拉->设置->安全设置->私人令牌->生成新令牌&#xff0c;注意将令牌保存&#xff08;只会出现一次&#xff0…

QT音乐播放器(1):数据库保存歌曲

实现功能&#xff1a;用数据库保存本地导入和在线搜索的歌曲记录 目录 一. 保存本地添加的歌曲 1. 使用QSettings &#xff08;1&#xff09;在构造函数中&#xff0c;创建对象。 &#xff08;2&#xff09;在导入音乐槽函数中&#xff0c;保存新添加的文件路径&#xff0c…

SQLAlchemy关键词搜索技术深度解析:从基础过滤到全文检索

在数据驱动的应用开发中&#xff0c;基于关键词的模糊查询是常见的业务需求。SQLAlchemy作为Python生态中最流行的ORM框架&#xff0c;提供了多种实现关键词搜索的技术方案。本文将从性能、适用场景和技术复杂度三个维度&#xff0c;系统对比分析SQLAlchemy中关键词搜索的最佳实…