文章目录

    • 引言
    • 一、进程创建:`fork()`系统调用的奥秘
      • 1.1 `fork()`的基本原理
      • 1.2 代码示例与解读
      • 1.3 写时复制(COW)优化
    • 二、进程终止:`exit()`与`_exit()`的抉择
      • 2.1 `exit()`和`_exit()`的区别
      • 2.2 代码示例与分析
    • 三、进程替换:`exec()`函数族的魔法
      • 3.1 `exec()`函数族的概述
      • 3.2 代码示例与执行过程
    • 四、进程等待:`wait()`与`waitpid()`的作用
      • 4.1 僵尸进程的危害
      • 4.2 `wait()`和`waitpid()`的功能
      • 4.3 代码示例与关键函数解读
    • 五、进程控制综合应用:多进程任务调度
      • 5.1 代码示例与工作流程
    • 六、进程控制进阶技巧
      • 6.1 僵尸进程处理
      • 6.2 信号处理
      • 6.3 进程组与会话
      • 6.4 守护进程
    • 七、总结

在这里插入图片描述

引言

在 Linux 操作系统的宏大舞台上,进程犹如活跃的舞者,是程序执行的鲜活实例,更是资源分配的基本单元。对系统编程而言,精通进程控制技术就如同掌握了舞蹈的精髓,是实现高效、稳定系统的关键所在。本文将全方位、深入地解析 Linux 的进程控制机制,不仅会对fork()exec()exit()wait()等核心系统调用进行细致解读,还会辅以大量详细注释的代码示例,同时探讨一些进阶的进程控制技巧。

一、进程创建:fork()系统调用的奥秘

1.1 fork()的基本原理

fork()是 Linux 系统中用于创建新进程的核心系统调用,它的神奇之处在于能够创建当前进程的一个副本,这个副本被称为子进程。父子进程在创建之初共享代码段,这意味着它们执行的是相同的程序代码,但拥有独立的数据段和堆栈,这保证了它们在运行过程中可以独立地处理各自的数据。

1.2 代码示例与解读

#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();  // 创建子进程if (pid < 0) {perror("Fork failed");  // 错误处理return 1;} else if (pid == 0) {// 子进程代码printf("Child PID: %d, Parent PID: %d\n", getpid(), getppid());} else {// 父进程代码printf("Parent PID: %d, Child PID: %d\n", getpid(), pid);}return 0;
}

代码解读

  • fork()调用会返回两次,这是其独特之处。在父进程中,fork()返回子进程的进程 ID(PID);而在子进程中,fork()返回 0。通过判断fork()的返回值,我们可以区分父子进程并执行不同的代码逻辑。
  • getpid()函数用于获取当前进程的 PID,getppid()函数用于获取当前进程的父进程的 PID。这两个函数在进程控制中非常实用,可以帮助我们跟踪进程之间的关系。
  • 典型的输出结果可能如下:
Parent PID: 1234, Child PID: 1235
Child PID: 1235, Parent PID: 1234

1.3 写时复制(COW)优化

fork()使用了写时复制(Copy-On-Write,COW)技术,这是一种重要的优化策略。在fork()创建子进程时,父子进程实际上共享物理内存页,只有当其中一个进程试图修改某个内存页时,才会为该进程复制一份该内存页。这种技术减少了内存的使用,提高了进程创建的效率。

二、进程终止:exit()_exit()的抉择

2.1 exit()_exit()的区别

函数行为适用场景
exit()清理 I/O 缓冲区,执行atexit()注册的函数,然后终止进程正常终止进程,需要进行资源清理和执行收尾工作
_exit()立即终止进程,不清理缓冲区,不执行atexit()注册的函数子进程终止后避免重复清理,或者在需要立即终止进程的场景

2.2 代码示例与分析

#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == 0) {printf("Child exiting...");// exit(0);      // 会输出字符串_exit(0);       // 可能不输出字符串(无缓冲区刷新)} else {wait(NULL);     // 等待子进程结束}return 0;
}

代码分析

  • 在子进程中,如果使用exit(0)exit()函数会先清理 I/O 缓冲区,将printf()输出的字符串刷新到标准输出,然后再终止进程。
  • 如果使用_exit(0),由于_exit()函数不会清理缓冲区,printf()输出的字符串可能不会显示在屏幕上。

三、进程替换:exec()函数族的魔法

3.1 exec()函数族的概述

exec()函数族用于用新的程序替换当前进程的映像,替换后,进程的 PID 保持不变,但执行的程序变成了新的程序。常用的exec()函数包括:

  • execl():通过参数列表传递新程序的参数。
  • execv():通过参数数组传递新程序的参数。
  • execvp():自动搜索PATH环境变量指定的路径,查找新程序。

3.2 代码示例与执行过程

#include <unistd.h>int main() {char *args[] = {"ls", "-l", "/tmp", NULL};pid_t pid = fork();if (pid == 0) {// 子进程替换为ls命令execvp("ls", args);  // 参数1:命令名,参数2:参数数组perror("execvp failed");  // 只有失败时执行_exit(1);} else {wait(NULL);  // 等待子进程结束}return 0;
}

执行过程

  1. 父进程调用fork()创建子进程。
  2. 子进程调用execvp("ls", args)execvp()函数会在PATH环境变量指定的路径中查找ls命令。
  3. 找到ls命令后,将子进程的映像替换为/bin/ls的映像,子进程开始执行ls -l /tmp命令。
  4. 子进程执行完ls命令后退出。
  5. 父进程调用wait(NULL)等待子进程结束,回收子进程的资源。

四、进程等待:wait()waitpid()的作用

4.1 僵尸进程的危害

在 Linux 系统中,如果子进程先于父进程结束,而父进程没有及时回收子进程的资源,子进程就会变成僵尸进程。僵尸进程虽然已经终止,但它的进程描述符仍然存在于系统中,会占用系统资源。如果僵尸进程过多,会导致系统资源耗尽,影响系统的正常运行。

4.2 wait()waitpid()的功能

父进程可以通过wait()waitpid()函数回收子进程的资源,防止僵尸进程的产生。

4.3 代码示例与关键函数解读

#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == 0) {sleep(2);       // 子进程休眠2秒_exit(42);      // 退出状态码42} else {int status;pid_t child_pid = waitpid(pid, &status, 0);  // 阻塞等待if (WIFEXITED(status)) {printf("Child %d exited with status: %d\n", child_pid, WEXITSTATUS(status));  // 输出42}}return 0;
}

关键函数解读

  • waitpid(pid, &status, options)
    

    • pid:指定要等待的子进程的 PID。如果pid为 - 1,表示等待任意子进程。
    • status:用于存储子进程的退出状态。
    • options:可以设置一些选项,如WNOHANG表示非阻塞等待。
  • 状态宏:

    • WIFEXITED(status):用于判断子进程是否正常退出。如果子进程正常退出,该宏返回真。
    • WEXITSTATUS(status):用于获取子进程的退出码。

五、进程控制综合应用:多进程任务调度

5.1 代码示例与工作流程

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main() {for (int i = 0; i < 3; i++) {pid_t pid = fork();if (pid == 0) {// 子进程执行不同任务char *cmds[] = {"./task1", "./task2", "./task3"};execl(cmds[i], cmds[i], NULL);_exit(1);  // exec失败时退出}}// 父进程等待所有子进程int status;while (wait(&status) > 0) {if (WIFEXITED(status)) {printf("Child exited with %d\n", WEXITSTATUS(status));}}return 0;
}

工作流程

  1. 父进程通过for循环创建 3 个子进程。
  2. 每个子进程使用execl()函数执行不同的任务(./task1./task2./task3)。
  3. 父进程使用while循环和wait(&status)函数等待所有子进程退出。
  4. 每当有子进程退出时,父进程使用WIFEXITED(status)WEXITSTATUS(status)宏判断子进程是否正常退出,并获取子进程的退出码,然后打印出来。

六、进程控制进阶技巧

6.1 僵尸进程处理

父进程必须调用wait()waitpid()函数回收子进程的资源,防止僵尸进程的产生。可以通过信号处理机制,使用SIGCHLD信号异步回收子进程,提高系统的效率。

6.2 信号处理

使用signal()sigaction()函数注册SIGCHLD信号处理函数,当子进程结束时,系统会发送SIGCHLD信号给父进程,父进程在信号处理函数中调用waitpid()函数回收子进程的资源。

6.3 进程组与会话

setpgid()函数用于设置进程组 ID,setsid()函数用于创建新的会话。通过这两个函数,可以控制进程之间的关系,实现进程的分组管理和会话管理。

6.4 守护进程

守护进程是在后台运行的服务进程,不与任何终端关联。可以通过双重fork()的方式创建守护进程,使进程在后台持续运行,为系统提供服务。

七、总结

通过深入理解 Linux 的进程控制原语,包括fork()exec()exit()wait()等系统调用,以及掌握一些进阶的进程控制技巧,开发者能够构建高效、稳定的 Linux 应用程序,实现进程管理、任务调度等高级功能。在实际开发中,要根据具体的需求选择合适的进程控制方法,合理管理系统资源,确保系统的正常运行。

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

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

相关文章

PJSIP 中的 TCP 传输配置指南

PJSIP 支持通过 TCP 传输 SIP 消息&#xff0c;相比 UDP 提供了更可靠的传输机制。以下是关于在 PJSIP 中使用 TCP 的详细指南。1. 创建 TCP 传输基本 TCP 传输配置cpjsua_transport_config tcp_cfg; pjsua_transport_config_default(&tcp_cfg); tcp_cfg.port 5060; // SI…

小菜狗的云计算之旅,今天学习MySQL数据库基础知识及操作

目录 一、概述 数据库概念 数据库的类型 关系型数据库模型 关系数据库相关概念 二、安装 1、mariadb安装 2、mysql安装 3、启动并开机自启 4、本地连接&#xff08;本地登录&#xff09; 三、mysql数据库配置与命令 yum安装后生成的目录 mysql服务器的启动脚本 数…

为什么是直接在**原型(prototype)上**添加函数

这是一个非常经典、核心的 JavaScript 面向对象编程问题&#xff1a;> 为什么是直接在**原型&#xff08;prototype&#xff09;上**添加函数&#xff0c;而不是在类/构造函数内部直接添加&#xff1f;你提到的代码中&#xff1a;javascript function TopSearchComponent() …

深入理解 classnames:React 动态类名管理的最佳实践

在现代前端开发中&#xff0c;我们经常需要根据组件的状态、属性或用户交互来动态切换 CSS 类名。虽然 JavaScript 提供了多种方式来处理字符串拼接&#xff0c;但随着应用复杂性的增加&#xff0c;传统的类名管理方式很快就会变得混乱不堪。这时&#xff0c;classnames 库就像…

C++系列(七):深度探索C++内存 --- 分区、堆栈、new/delete与高效编程实践

引言 程序运行的本质是对数据的处理&#xff0c;而内存则是程序执行的核心舞台。理解内存的物理与逻辑分区&#xff0c;是掌握程序底层行为、编写高效可靠代码的关键基石。内存并非混沌一片&#xff0c;而是被严格划分为代码区、全局区、栈区和堆区。每个区域拥有独特的生命周…

微信小程序71~80

1.总结小程序生命周期 小程序冷启动&#xff0c;钩子函数执行的顺序保留当前页面&#xff0c;进入下一个页面&#xff0c;钩子函数执行的顺序销毁当前页面&#xff0c;进入下一个页面&#xff0c;钩子函数执行的顺序小程序热启动&#xff0c;钩子函数执行的顺序 2.使用Componen…

[Pytest][Part 3]检测python package状态

目录 实现需求1&#xff1a; 检查python package状态——pkg_resource hook实现自动检测包状态 conftest.py hook钩子函数 Part1: https://blog.csdn.net/x1987200567/article/details/144915315?spm1001.2014.3001.5501 从这里开始逐个实现Part1中的需求 实现需求1&a…

自定义时间范围选择组件使用教程(基于 Vue 3 + Element Plus)

&#x1f553; 自定义时间范围选择组件使用教程&#xff08;基于 Vue 3 Element Plus&#xff09;✅ 一个灵活实用的时间范围选择器&#xff0c;支持开始时间、结束时间、快捷时间选项、本地双向绑定、插槽扩展等功能。–&#x1f4d8; 一、功能介绍 该组件基于 Element Plus …

YOLOv8 模型转换 ONNX 后 C# 调用异常:一个参数引发的跨平台适配难题

一、问题背景&#xff1a;从 Python 训练到 C# 部署的跨平台需求 作为一名 C# 开发者&#xff0c;我在完成 YOLOv8 模型训练&#xff08;使用 Ultralytics 官方框架&#xff0c;训练数据为自定义目标检测数据集&#xff0c;输入尺寸 640x640&#xff0c;训练轮次 100 轮&#…

Apache Cloudberry 亮相 2025 IvorySQL 生态大会暨 PostgreSQL 高峰论坛

6 月 27 日至 28 日&#xff0c;IvorySQL 2025 生态大会暨 PostgreSQL 高峰论坛在泉城济南顺利召开。本届大会由 IvorySQL 开源数据库社区主办、瀚高基础软件股份有限公司承办&#xff0c;吸引了来自国内外的数据库技术专家、开发者与开源爱好者齐聚一堂&#xff0c;聚焦数据库…

CMake之CMakeLists.txt语法规则

本文主要参考正点原子的应用开发手册&#xff0c;仅作为本人学习笔记使用。 目录 cmake 的使用方法其实还是非常简单的&#xff0c;重点在于编写 CMakeLists.txt&#xff0c;CMakeLists.txt 的语法规则也简单&#xff0c;并没有 Makefile的语法规则那么复杂难以理解&#xff01…

Mysql专题复习

重点内容&#xff1a;1. Mysql架构&#xff1a;客户端 Server层 存储引擎2. 索引数据结构&#xff1a;B树4. 索引优化&#xff1a;覆盖索引、排序、JOIN、分页&#xff1b; COUNT; 索引下推&#xff1b;单/双路排序5. 数据库事务&#xff1b; 锁&#xff1b;隔离级别&#xff…

CLIP的tokenizer详解

一、bytes_to_unicodedef bytes_to_unicode():"""Returns list of utf-8 byte and a corresponding list of unicode strings.The reversible bpe codes work on unicode strings.This means you need a large # of unicode characters in your vocab if you wa…

【如何判断Linux系统是Ubuntu还是CentOS】

要确定您的操作系统是 Ubuntu 还是 CentOS&#xff0c;可以通过以下方法快速检查&#xff1a; 方法 1&#xff1a;通过终端命令&#xff08;推荐&#xff09; 在终端中执行以下命令之一&#xff1a; 查看 /etc/os-release 文件 cat /etc/os-releaseUbuntu 特征&#xff1a;显示…

RISCV Linux 虚拟内存精讲系列二 -- Linux 入口 head.S

通过 Linux 的构建系统&#xff0c;即 Linux 源代码的根目录下的 Makefile&#xff0c;能够找到 vmlinux 的链接文件&#xff0c;从而能够查看其入口代码 head.S:_start&#xff0c; 如下&#xff1a; Linux 构建系统主Makefile: vmlinux.lds: head.S: 找到该入口后&#xff0c…

springAI学习:Advisors

spring AI Advisors类似于拦截器&#xff0c;会对请求的prompt做出特定的修改和增强&#xff08;比如传入历史沟通记录、搜索信息等等&#xff09;&#xff0c;以达到完善prompt的目的。通过Advisors API&#xff0c;开发人员可以创建更为复杂、可重用、可维护的AI组件。下面介…

MySQL CDC与Kafka整合指南:构建实时数据管道的完整方案

一、引言&#xff1a;现代数据架构的实时化需求 在数字化转型浪潮中&#xff0c;实时数据已成为企业的核心资产。传统批处理ETL&#xff08;每天T1&#xff09;已无法满足以下场景需求&#xff1a; 实时风险监控&#xff08;金融交易&#xff09;即时个性化推荐&#xff08;电商…

MATLAB | 绘图复刻(二十一)| 扇形热图+小提琴图

前段时间在小红书刷到了一个很有特色的热力图&#xff0c;由大佬滚筒洗衣机创作&#xff0c;感觉很有意思&#xff0c;尝试 MATLAB 复刻&#xff1a; 作者使用的是 python 代码&#xff0c;赶快去瞅瞅。 复刻效果 正文部分 0.数据准备 数据需要一个用来画热图的矩阵以及一个…

批量PDF转换工具,一键转换Word Excel

软件介绍 今天为大家推荐一款高效的Office文档批量转换工具&#xff0c;能够快速将Word和Excel文件批量转换为PDF格式。 软件特点 这款名为"五五Excel word批量转PDF"的工具体积小巧&#xff0c;不到2M大小&#xff0c;却能实现强大的批量转换功能&#xff0c…

面试150 基本计算器

思路 利用栈&#xff08;stack&#xff09;来保存进入括号前的计算状态&#xff08;包括当前计算结果和符号&#xff09;&#xff0c;以便在括号结束后正确恢复计算上下文。代码通过遍历字符串&#xff0c;识别数字、加号、减号和括号。遇到数字时构造完整数值&#xff1b;遇到…