文章目录
- 一、区分父子进程
-
- 1.1 父子进程的相同点
- 1.2 父子进程的不同点
- 1.3 写时复制机制(Copy-On-Write, COW)
- 1.4 文件描述符的共享
- 二、代码示例
-
- 2.1 通过 `fork()` 区分父子进程
- 2.2 父子进程地址空间相互独立
- 三、父子进程小结
- 四、常用进程相关命令
-
- 4.1 ps 命令
- 4.2 kill 命令
一、区分父子进程
1.1 父子进程的相同点
当一个进程调用 fork() 之后,子进程会复制父进程的大部分内容。它们初始状态一致,包括:
用户空间内容(通过写时复制机制实现):
- 代码段 .text(共享)
- 数据段 .data(初始相同)
- BSS段(未初始化全局变量)
- 堆区(malloc 动态分配)
- 栈区(函数局部变量)
全局变量、局部变量:
- 初始值相同,但内存空间独立(写时复制)
环境变量
进程元信息(初始继承):
- 用户 ID(UID)/ 有效用户 ID(EUID)
- 工作目录
- 宿主目录(HOME)
- 信号处理方式(signal handlers)
打开的文件描述符:
- 父子共享文件表(如指向同一个文件,偏移量共享,引用计数增加)
共享的内存映射(mmap 区)
1.2 父子进程的不同点
一般来说,在 fork() 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
需要注意的是,在子进程的地址空间里,子进程是从 fork() 这个函数后才开始执行代码。
1.3 写时复制机制(Copy-On-Write, COW)
子进程不是立即拷贝整个地址空间,而是延迟拷贝,用来节省内存。
虽然 fork() 看似只复制“少量不同信息”,但:
- 仍涉及创建新的进程控制块(PCB)
- 分配 PID 和其他资源
- 如果触发写操作,需复制内存页
因此通过写时复制技术最大程度降低成本:
- 读时共享:父子进程共享物理内存(只读权限)
- 写时复制:当一方尝试写入:系统才真正复制该页面 → 实现分离
优点:
- 节省资源,避免不必要的内存拷贝
- 提高性能(尤其是立即 exec() 的场景)
1.4 文件描述符的共享
父子进程的文件描述符指向相同的文件表项
- 表现为:
- 引用计数增加
- 文件偏移量共享
- 文件状态标志共享(如 O_APPEND)
mmap 创建的映射区共享
注意:文件描述符共享 ≠ 文件内容共享,但操作影响对方的读写位置
二、代码示例
2.1 通过 fork() 区分父子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
pid = fork(); // 创建子进程
if (pid < 0) {
// 创建失败
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程
while (1) {
printf("I am son\\n");
sleep(1);
}
} else {
// 父进程
while (1) {
printf("I am father\\n");
sleep(1);
}
}
return 0;
}
运行效果: 父子进程交替(或连续)输出不同的字符串,顺序不固定。
2.2 父子进程地址空间相互独立
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int a = 10; // 全局变量
int main() {
int b = 20; // 局部变量
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程修改变量
a = 111;
b = 222;
printf("son: a = %d, b = %d\\n", a, b);
} else {
// 父进程等待子进程先运行
sleep(1);
printf("father: a = %d, b = %d\\n", a, b);
}
return 0;
}
运行效果:
son: a = 111, b = 222
father: a = 10, b = 20
三、父子进程小结
1. 父子进程的区别
-
创建方式:通过 fork() 系统调用创建子进程。
-
代码关系:子进程是父进程的“复制品”,初始代码和数据一致,但运行是相互独立的。
-
返回值区别:
- 父进程中 fork() 返回 子进程的 PID(> 0)。
- 子进程中 fork() 返回 0。
- 如果返回值 < 0,表示创建失败。
-
运行顺序:父进程先执行还是子进程先执行 不确定,取决于内核调度算法。
-
子进程执行位置:在子进程地址空间中,从 fork() 调用之后的代码开始执行。
2. 父子进程的地址空间
- 相互独立:父子进程有独立的地址空间,互不影响。
- 变量修改互不干扰:
- 全局变量和局部变量在 fork() 之后各自拥有一份拷贝。
- 子进程修改变量,不会影响父进程的值(反之亦然)。
四、常用进程相关命令
4.1 ps 命令
- 功能:查看进程的详细状态。
- 常用选项:
-a | 显示所有终端上的进程,包括其他用户的 |
-u | 显示详细状态 |
-x | 显示无控制终端的进程 |
-j | 显示作业控制相关信息 |
- 常用用法:
- ps aux:显示当前所有进程。
- ps ajx:以更完整格式显示所有进程。
4.2 kill 命令
-
功能:向指定进程发送信号。
-
基本用法:
- kill PID → 默认发送 SIGTERM(终止信号)。
- kill -l → 查看所有信号编号与名称。
- kill -9 PID 或 kill -SIGKILL PID → 强制终止进程。
-
注意:
- SIGKILL(信号编号 9)不可被捕获或忽略,能强制结束进程。
评论前必须登录!
注册