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 的方式在子进程中运行。
相关推荐
ltl6 小时前
实时 OS 巡礼:VxWorks、QNX、Zephyr 与 PREEMPT_RT
linux
我是谁??7 小时前
Ubuntu22.04更换清华源
linux·运维·服务器
上海安当技术7 小时前
固件签名与Secure Boot:安当CAS如何构建汽车固件信任链
网络·汽车·信任链·boot·secure·固件签名·安当cas
圣殿骑士-Khtangc7 小时前
Go字符串高效拼接性能对比与底层原理分析
服务器·前端·golang
上海安当技术7 小时前
诊断接入认证:安当CAS如何堵住汽车网络安全的物理后门
网络·web安全·汽车·access·secure·安当cas·诊断接入认证
ze^07 小时前
【软考信息安全】第2章 网络攻击原理与常用方法
网络·web安全·网络安全
Shell运维手记7 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
这个DBA有点耶8 小时前
数据库一体机架构演进:从硬件堆叠到软硬深度耦合
服务器·网络·数据库·硬件架构·运维开发·database·数据库架构
arbboter9 小时前
【网络工具】NetProxy网络代理用户手册
开发语言·网络·c#·网络代理·网络异常·代理工具
测试运维日常笔记9 小时前
Linux系统安装配置TigerVNC服务器完整指南
linux·运维·服务器