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

linux:进程详解(2)

目录

1.进程创建

1.1初识fork()函数

1.2 写时拷贝

1.3 fork常规用法

2.进程终止

2.1进程退出场景

2.2进程常见退出方法

2.2.1 退出码

2.3_exit函数

2.4 exit函数

2.5 return退出

3.进程等待

3.1进程等待必要性

3.2 进程等待的方法

3.2.1wait方法

3.2.2 waitpid 

3.2.3获取子进程status

4.进程程序替换

4.1 替换原理

4.2 替换函数


1.进程创建

1.1初识fork()函数

在Linux系统中,fork函数是一个关键的系统调用,它通过复制当前进程来创建新的子进程。调用fork后,系统会生成两个独立运行的进程:原始进程被称为父进程,新建的进程则称为子进程。

子进程返回0,父进程返回子进程ID,出错时返回-1。

当进程调用fork函数时,控制权会转移到内核中的fork执行流程。内核会执行以下操作:

  1. 为子进程分配新的内存空间和内核数据结构
  2. 将父进程的部分数据结构内容复制到子进程
  3. 将新创建的子进程添加到系统进程列表中
  4. 完成fork调用后返回,由调度器开始进行进程调度

当⼀个进程调⽤fork之后,就有两个⼆进制代码相同的进程。⽽且它们都运⾏到相同的地⽅。但每个进 程都将可以开始它们⾃⼰的旅程,看如下程序。

看到上图 进程27174打印了before,而进程27175没有打印before,这又是为什么呢?那就一起来看看下图所示吧!!

在fork操作之前,父进程独立运行;fork之后,父进程和子进程将各自执行不同的代码路径。需要注意的是,fork之后两个进程的执行顺序完全由系统调度器决定。

1.2 写时拷贝

父子代码通常共享同一数据,在双方都不写入时数据保持共享。当任意一方尝试写入时,系统会通过写时复制机制创建各自的独立副本。详见下图:

那么问题来了为什么要进行写时拷贝呢?

  1. 性能优化

    • 避免不必要的拷贝操作:在只读场景下,多个进程/线程可以共享同一份数据,无需立即创建副本
    • 延迟拷贝时机:只有当真正需要修改数据时才进行拷贝,减少即时开销
    • 典型应用:Linux的fork()系统调用创建子进程时,父子进程最初共享物理内存页
  2. 资源节约

    • 减少内存占用:共享的只读数据不需要多份拷贝
    • 降低CPU消耗:避免不必要的拷贝操作节省CPU周期
    • 示例场景:虚拟内存管理系统、数据库的快照隔离机制

注意:

得益于写时拷贝技术的实现,父子进程得以完全隔离,从而确保了进程运行的独立性。写时拷贝作为一种延迟资源分配机制,有效提升了系统内存的整体利用率。

1.3 fork常规用法

• 父进程可通过复制自身来创建子进程,实现父子并行执行不同代码段。例如,父进程持续监听客户端请求,而子进程负责处理具体请求。
• 进程可通过调用exec函数来运行新程序。例如,子进程在fork返回后调用exec替换当前程序。

注意:

fork可能会调用失败,那为什么会失败呢?

• 系统进程数量过多
• 实际用户进程数超出限制

2.进程终止

前提:

进程终止的本质在于释放系统资源,具体包括释放进程申请的内核数据结构及其对应的代码与数据。

2.1进程退出场景

• 代码执行完成,结果正确

• 代码执行完成,结果不正确

• 代码异常终止

2.2进程常见退出方法

正常终止(可通过以下方式实现):

  1. main 函数返回
  2. 调用 exit 函数
  3. 调用 _exit 函数

异常终止

  • 通过信号终止(如 Ctrl+C
  • 使用 echo $? 可查看进程退出码

2.2.1 退出码

退出码(或退出状态)能反映上一条命令的执行结果。命令完成后,我们可以通过它判断命令是成功执行还是出现错误。通常,程序返回0表示成功运行,而返回1或其他非零值则表明执行失败。

Linux Shell 中的常见退出状态码:

  1. 0:成功执行
  2. 1:一般性错误
  3. 2:命令用法错误
  4. 126:命令不可执行
  5. 127:命令未找到
  6. 128:无效退出参数
  7. 130:通过 Ctrl+C 终止
  8. 137:进程被强制终止 (kill -9)
  9. 255:退出状态超出范围

这些状态码是 Shell 脚本和程序执行后返回的数值,用于判断执行结果。

2.3_exit函数

1 2 3  
#include <unistd.h>  
void _exit(int status);  参数说明:  
status - 指定进程的终止状态值,父进程可通过wait()系统调用获取该状态值  

• 说明:即使status是int类型,但只有低8位会被父进程使用。因此当调用_exit(-1)时,在终端执行$?会显示返回值为255。

2.4 exit函数

函数exit最终会调用_exit,但在调用前会执行以下操作:

  1. 调用用户通过atexiton_exit注册的清理函数
  2. 关闭所有已打开的流,确保缓冲数据全部写入
  3. 最后调用_exit完成终止

看下图,可以更好的理解:

2.5 return退出

return是一种更常见的进程退出方式。执行return n等同于执行exit(n),因为调用main函数的运行时会将返回值作为exit的参数传递。

3.进程等待

3.1进程等待必要性

• 若子进程退出后父进程未及时处理,可能导致"僵尸进程"问题,进而引发内存泄漏
• 一旦进程进入僵尸状态,便无法被常规手段终止——即便是强制终止命令kill -9也无济于事,因为无法杀死一个已死亡的进程
• 此外,父进程需要了解子进程的任务执行情况:是否正常完成、结果是否正确、退出状态是否正常
• 通过进程等待机制,父进程可以回收子进程资源并获取其退出信息

3.2 进程等待的方法

3.2.1wait方法

3.2.2 waitpid 

基本概念

waitpid 是 Unix/Linux 系统中的一个系统调用函数,用于父进程等待子进程的状态变化。它比 wait 方法提供了更精细的控制,允许父进程等待特定的子进程,并且可以控制等待的方式。

函数原型

#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);

参数详解

  1. pid 参数

    • pid > 0:等待进程ID等于pid的子进程

    • pid = -1:等待任意子进程,与wait等效

    • pid = 0:等待与调用进程同进程组的任何子进程

    • pid < -1:等待进程组ID等于pid绝对值的任何子进程

  2. status 参数: 这是一个指向整数的指针,用于存储子进程的退出状态。可以使用以下宏来检查状态:

    • WIFEXITED(status):判断子进程是否正常退出

    • WEXITSTATUS(status):获取子进程的退出码

    • WIFSIGNALED(status):判断子进程是否被信号终止

    • WTERMSIG(status):获取导致子进程终止的信号编号

    • WIFSTOPPED(status):判断子进程是否被停止

    • WSTOPSIG(status):获取导致子进程停止的信号编号

  3. options 参数

    • WNOHANG:如果没有子进程退出,立即返回,不阻塞

    • WUNTRACED:如果子进程被停止,也返回其状态

    • WCONTINUED(Linux特有):如果停止的子进程被继续,也返回其状态

返回值

  • 成功时返回状态已改变的子进程的PID

  • 如果指定了WNOHANG且没有子进程退出,返回0

  • 出错时返回-1,并设置errno

使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {  // 子进程printf("Child process (PID: %d) running\n", getpid());sleep(3);printf("Child process exiting\n");exit(42);  // 子进程退出码为42} else if (pid > 0) {  // 父进程int status;printf("Parent process (PID: %d) waiting for child\n", getpid());pid_t child_pid = waitpid(pid, &status, 0);if (WIFEXITED(status)) {printf("Child %d exited with status %d\n", child_pid, WEXITSTATUS(status));}} else {perror("fork failed");exit(1);}return 0;
}

• 调用wait/waitpid时,若子进程已退出,函数会立即返回并释放资源,同时获取子进程的退出状态信息
• 当子进程仍在正常运行的情况下调用wait/waitpid,可能导致进程阻塞
• 若指定的子进程不存在,则函数会立即返回错误

3.2.3获取子进程status

  • waitwaitpid 函数都有一个 status 参数,该参数属于输出型参数,由操作系统负责填充。
  • 若传入 NULL,表示父进程不关心子进程的退出状态信息。
  • 若传入有效指针,操作系统会通过该参数将子进程的退出状态信息返回给父进程。
  • 不能将 status 简单地视为整型变量,而应将其看作位图结构(具体分析时只需关注低 16 位),详情如下图所示:

4.进程程序替换

4.1 替换原理

使用fork创建子进程后,子进程会执行与父进程相同的程序(但可能运行不同的代码分支)。通常,子进程需要调用exec系列函数来执行另一个程序。当进程调用exec函数时,其用户空间的代码和数据将被新程序完全替换,并从新程序的入口点开始执行。

需要注意的是,exec调用不会创建新进程,因此在调用前后该进程的ID保持不变。

4.2 替换函数

有六种以exec开头的函数,统称为exec系列函数。

• 若函数调用成功,将加载新程序并从启动代码开始执行,不再返回原调用处。
• 若调用出错,则返回错误代码-1。
• 因此,exec系列函数仅会在出错时返回-1,成功执行时无返回值。

这些函数原型看似容易混淆,但掌握规律后就能轻松记忆:

  • l (list):表示参数采用列表形式
  • v (vector):参数使用数组形式
  • p (path):自动搜索环境变量PATH
  • e (env):表示自行维护环境变量

#include <unistd.h>
#include <stdlib.h>int main() {char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};// execl: 需指定完整路径,参数列表逐个传入execl("/bin/ps", "ps", "-ef", NULL);// execlp: 使用PATH环境变量查找程序,无需完整路径execlp("ps", "ps", "-ef", NULL);// execle: 需指定完整路径,并自定义环境变量execle("/bin/ps", "ps", "-ef", NULL, envp);// execv: 需指定完整路径,参数通过数组传递execv("/bin/ps", argv);// execvp: 使用PATH环境变量查找程序,参数通过数组传递execvp("ps", argv);// execve: 需指定完整路径,参数通过数组传递,并自定义环境变量execve("/bin/ps", argv, envp);exit(0);
}

以下是一个完整的 exec 函数簇示例:

http://www.lryc.cn/news/586725.html

相关文章:

  • Excel的学习
  • SQL的初步学习(二)(以MySQL为例)
  • 基于 SpringBoot 的 REST API 与 RPC 调用的统一封装
  • JavaScript 获取 URL 参数值的全面指南
  • DOS下用TC2显示Bmp文件
  • Cesium初探-CallbackProperty
  • 单页面和多页面的区别和优缺点
  • 退出登录后头像还在?这个缓存问题坑过多少前端!
  • 开发语言的优劣势对比及主要应用领域分析
  • DNS协议解析过程
  • 前端进阶之路-从传统前端到VUE-JS(第五期-路由应用)
  • 开发语言中关于面向对象和面向过程的笔记
  • 【Qt开发】Qt的背景介绍(一)
  • docker容器高级管理-dockerfile创建镜像
  • RabbitMQ面试精讲 Day 2:RabbitMQ工作模型与消息流转
  • Netty主要组件和服务器启动源码分析
  • EWSGAN:自动搜索高性能的GAN生成器架构
  • Kotlin 类和对象
  • JS红宝书pdf完整版
  • HarmonyOS组件/模板集成创新活动-开发者工具箱
  • 2025.7.13总结
  • Nature子刊 |HERGAST:揭示超大规模空间转录组数据中的精细空间结构并放大基因表达信号
  • 直流/直流电源模块:无干扰布线,避免电磁干扰的技术方案
  • C++高级编程,类模版成员函数类外实现
  • 第三章-提示词-探秘大语言基础模型:认知、分类与前沿洞察(9/36)
  • 《Linux篇》自动化构建-make/Makefile
  • 咪咕盒子Mgv3200_mgv3201九联UNT403G_UNT413G烽火HG680-GC通刷优盘强刷包及TTL线刷烧录救砖包 当贝纯净版固件
  • 基于SpringBoot3集成Kafka集群
  • CentOS 7 升级系统内核级库 glibc 2.40 完整教程
  • docker运行redis指定配置+jdk17安装在centos7