Linux进程控制

进程控制

1. 进程创建 (fork/vfork)

1.1 fork 函数初识

fork 函数用于从一个已存在的进程(父进程)中创建一个新进程(子进程)。

  • 函数原型 : pid_t fork(void);
  • 返回值 :
    • 子进程 : 返回 0
    • 父进程: 返回子进程的 PID (大于0)。
    • 出错 : 返回 -1
  • 为什么有两个返回值? : 调用 fork 后,内核会为子进程分配新的内存和内核数据结构,并将父进程的部分数据拷贝给子进程。之后,父子进程从 fork 返回处开始各自独立执行,因此会产生两个返回值。

1.2 写时拷贝 (Copy On Write)

为了提高效率,fork 创建的父子进程在初始时共享代码和数据

  • 机制: 只有当父子进程中任意一方试图修改数据时,操作系统才会为修改方复制一份数据副本。
  • 优点: 这是一种延迟申请技术,避免了不必要的数据拷贝,提高了内存使用率。

1.3 fork 的常规用法

  1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段(如网络服务器中,父进程等待请求,子进程处理请求)。
  2. 一个进程要执行一个不同的程序 ,子进程在 fork 返回后,立即调用 exec 函数族。

1.4 fork 调用失败的原因

  • 系统中已经有太多的进程。
  • 实际用户的进程数超过了系统限制。

2. 进程终止

进程终止的本质是释放系统资源,包括进程申请的内核数据结构和对应的代码数据。

2.1 进程退出场景

  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果不正确。
  • 代码异常终止(如 Ctrl+C 信号终止)。

2.2 进程常见退出方法

正常终止
  1. main 函数返回 (return)。
  2. 调用 exit 函数。
  3. 调用 _exit 函数。
异常终止
  • 通过信号终止,如 Ctrl+C (SIGINT)。

2.3 退出码 (Exit Code)

退出码用于告知父进程或 Shell 命令的执行状态。可以通过 echo $ ? 查看。

退出码 解释
0 命令成功执行
1 通用错误
126 权限被拒绝或无法执行
127 未找到命令
128+n 命令被信号 n 终止
130 被 Ctrl+C (SIGINT, 信号2) 终止
143 被 SIGTERM (信号15) 终止

2.4 exit 与 _exit 的区别

  • _exit(int status) : 系统调用。直接终止进程,status 的低8位作为退出码返回给父进程。
  • exit(int status) : 库函数。在调用 _exit 之前,会先执行一些清理工作:
    1. 执行用户通过 atexiton_exit 定义的清理函数。
    2. 关闭所有打开的流,并刷新缓冲区(将缓存数据写入文件)。
    3. 最后调用 _exit

示例 : printf("hello"); exit(0); 会打印 "hello",因为 exit 会刷新缓冲区。而 printf("hello"); _exit(0); 则不会打印。

2.5 return 退出

main 函数中,return n 等同于 exit(n),因为调用 main 的运行时函数会将 main 的返回值作为 exit 的参数。


3. 进程等待 (wait/waitpid)

3.1 进程等待的必要性

  1. 回收资源 : 子进程退出后,如果父进程不处理,会变成僵尸进程 (Zombie),其 PCB 仍占用内存,造成内存泄漏。
  2. 获取状态: 父进程需要知道子进程的任务完成得如何(正常退出还是被信号杀死,退出码是多少)。
  3. 僵尸进程无法被杀死 : kill -9 对已经死去的僵尸进程无效。

3.2 进程等待的方法

wait 函数
  • 函数原型 : pid_t wait(int* status);
  • 功能: 阻塞等待任意一个子进程退出。
  • 返回值: 成功返回被等待的子进程 PID,失败返回 -1。
  • 参数 : status 是输出型参数,用于获取子进程的退出状态。若不关心可设为 NULL
waitpid 函数
  • 函数原型 : pid_t waitpid(pid_t pid, int *status, int options);
  • 功能: 更灵活的等待方式。
  • 参数 :
    • pid:
      • pid = -1: 等待任一子进程,等效于 wait
      • pid > 0: 等待指定 PID 的子进程。
    • status: 输出型参数,获取退出状态。
    • options:
      • 0: 阻塞等待。
      • WNOHANG: 非阻塞等待。若子进程未退出,立即返回 0。

3.3 获取子进程退出状态 (status)

status 是一个位图,不能简单看作整型。

  • 正常终止 :
    • WIFEXITED(status): 判断子进程是否正常终止。
    • WEXITSTATUS(status): 若正常终止,提取其退出码。
  • 被信号终止 :
    • status 的低7位存储了终止该进程的信号编号。

3.4 阻塞与非阻塞等待

  • 阻塞等待 : 父进程调用 waitwaitpid(..., 0) 后会暂停执行,直到子进程退出。
  • 非阻塞等待 : 父进程调用 waitpid(..., WNOHANG) 后,如果子进程未退出,函数立即返回 0,父进程可以继续做其他事情(如处理其他任务),实现并发。

4. 进程程序替换 (exec 函数族)

4.1 替换原理

fork 创建的子进程默认执行与父进程相同的程序。若想让子进程执行一个全新的程序,需要调用 exec 函数族。

  • 核心: 用磁盘上的一个新程序,完全替换掉当前进程的代码和数据段。
  • 特点 : 不创建新进程,进程的 PID 保持不变。

4.2 exec 函数族

共有六个以 exec 开头的函数,它们最终都调用系统调用 execve

函数名 参数格式 是否带路径 是否使用当前环境变量
execl 列表 (list) 否 (需全路径)
execlp 列表 (list) 是 (搜索 PATH)
execle 列表 (list) 否 (需全路径) 否 (需自己提供)
execv 数组 (vector) 否 (需全路径)
execvp 数组 (vector) 是 (搜索 PATH)
execve 数组 (vector) 否 (需全路径) 否 (需自己提供)
  • l (list) : 参数以列表形式逐个传递,最后以 NULL 结尾。
  • v (vector) : 参数以指针数组 char *const argv[] 的形式传递。
  • p (path) : 可以只提供文件名,函数会自动在 PATH 环境变量中搜索。
  • e (env) : 需要自己组装并传递环境变量数组 char *const envp[]

注意 : exec 函数调用成功则不返回,直接从新程序的 main 函数开始执行。只有调用失败时才返回 -1。


5 内建命令 vs 外部命令

  • 内建命令 (Built-in Command) : 如 cd, export。它们必须由 Shell 进程自己执行,因为它们的目的是改变 Shell 自身的状态(如工作目录、环境变量)。如果在子进程中执行,子进程的更改不会影响到父 Shell。
  • 外部命令 : 如 ls, ps。它们是独立的可执行程序,通过 fork + exec 的方式在子进程中运行。
相关推荐
xian_wwq4 小时前
【案例分析】Hugging Face生产基础设施入侵攻击分析
网络·安全
灵机一物4 小时前
企业选型参考:2026 CXL 内存扩展方案六强测评与落地指南
服务器·网络·数据库·人工智能
Web极客码4 小时前
如何用三段式确定性剪枝,为 LLM Agent 砍掉 35% 的 Token 成本?
服务器·人工智能·算法·机器学习
yeflx4 小时前
速腾Airy雷达使用记录
java·服务器·网络
CPETW5 小时前
企业版配套软件介绍 ---- USB TO SPI 3.0-Slave
网络·科技·单片机·嵌入式硬件·电子
公众号:fuwuqiBMC5 小时前
(转自“服务器BMC”)服务器BMC芯片——多Die(芯片)封装
运维·服务器·人工智能
jsons15 小时前
autofs挂载
linux·服务器·网络
liwulin05065 小时前
【ollama】自定义结构化输出
linux·前端·数据库·ollama
gs801406 小时前
【实战】记一次 Linux 服务器入侵排查:grok-agent 后门木马深度剖析与彻底清理
linux·运维·服务器
你驴我6 小时前
WhatsApp Cloud API 速率限制下的令牌桶与退避策略实践
网络·后端·python