
大家好,接下来的一段时间我将开始学习野火的Linux 系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。
目录
[2、fork 基础介绍](#2、fork 基础介绍)
[1. 函数基础信息](#1. 函数基础信息)
[2. fork 执行流程核心结论](#2. fork 执行流程核心结论)
[1. 示例源码](#1. 示例源码)
[2. 程序运行输出](#2. 程序运行输出)
[3. 逐行执行拆解](#3. 逐行执行拆解)
[4. 高频坑点](#4. 高频坑点)
一、查看进程
#查看系统全部进程并分页展示:
ps -ef | more
-
ps -efps:查看系统进程快照-e:显示所有进程(全部用户的进程)-f:完整格式输出(UID、PID、PPID、CMD 等详细信息) 作用:列出系统全部进程的详细信息。
-
|管道符 把左边命令ps -ef的标准输出 ,直接当作右边命令more的标准输入。 -
more分页查看工具。当输出内容太长、一屏放不下时,一页一页显示;- 回车:下一行
- 空格:下一页
- q:退出
二、查看进程之间关系
pstree:以树状结构打印进程父子关系 ,直观看到父进程、子进程的派生链条,比ps -ef看 PPID 更直观。
Linux 内核中所有进程起源于
systemd(老系统是 init,PID=1)。
1、常用参数
pstree:默认,只显示进程名,精简树形pstree -p:同时显示 PID 号(最常用,面试 / 调试必用)pstree -u:显示进程所属用户pstree -ap:显示 PID + 完整命令行
三、创建进程
2、fork 基础介绍
1. 函数基础信息
头文件:#include <unistd.h>
函数原型:pid_t fork(void);
pid_t:是进程 ID 类型,本质是整型,专门存放 PID。
返回值:
| 执行结果 | 进程类型 | 返回值含义 |
|---|---|---|
| 创建成功 | 父进程(原进程) | 返回正整数,该数字就是新建子进程的 PID |
| 创建成功 | 子进程(新建进程) | 返回 0 |
| 创建失败 | 父进程(无子进程产生) | 返回 -1,失败原因:内存不足、系统进程数量达到上限 |
2. fork 执行流程核心结论
- fork 之前 :操作系统只有1 个父进程 ,fork 前面的代码只会执行 1 次。
- fork 之后 :操作系统存在两个几乎完全相同的进程(父 + 子) ,fork后面 的代码,父子进程各自执行一遍,一共执行 2 次。
子进程会拷贝父进程的内存空间、变量、文件描述符、程序计数器;现代 Linux 采用写时复制 COW :刚 fork 时父子共享同一份物理内存,只有任意进程修改数据时,内核才单独复制内存页面,节省开销。 父子进程是完全独立的进程,变量互相隔离,互不影响;CPU 调度器随机决定父子谁先运行,顺序不固定。
3、代码示例讲解
1. 示例源码
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t i;
printf("before fork!\r\n"); // fork【之前】代码,仅父进程执行
i = fork(); // 在此处分裂,产生子进程
printf("after fork:i=%d\r\n",i); // fork【之后】代码,父子各跑一次
return 0;
}
2. 程序运行输出
before fork!
after fork:i=28108
after fork:i=0
3. 逐行执行拆解
- 程序启动,只有父进程,执行到
printf("before fork!\r\n");因为带\n换行符,刷新 stdout 缓冲区,打印一行before fork!(只打印一次)。 - 执行
i = fork();- 内核创建子进程,复制父进程上下文,两个进程都停在 fork 返回位置。
- 父进程 :变量
i = 28108(28108 就是子进程 PID) - 子进程 :变量
i = 0
- 父子两个进程并行继续执行 fork 之后的代码:
- 父进程执行
printf("after fork:i=28108\r\n",i);→ 输出after fork:i=28108 - 子进程执行
printf("after fork:i=0\r\n",i);→ 输出after fork:i=0
- 父进程执行
4. 高频坑点
- 缓冲区陷阱: 如果
printf("before fork!");不加换行\n,会打印两次before fork!。 原因:printf 输出保存在用户缓冲区,没有刷新;fork 拷贝内存的时候,连同缓冲区里未输出的字符串一起复制。父子进程退出时,各自刷新缓冲区,就输出两遍。
\n的作用:刷新标准输出缓冲区。
-
父子执行顺序不确定 谁先打印由操作系统调度器决定,不一定父进程一定优先输出。
-
父子进程变量相互独立 父的
i和子的i是两份独立副本,修改互不干扰。