深入理解Linux进程:体系结构、进程管理与fork()原理解析
文章目录

1. 冯诺依曼体系结构
1.1 体系结构的基本组成
- 存储分级的问题
- 体系结构效率问题

程序要运行,必须先加载到内存中,一个计算机以内存为主就可以让普通人以较低的价格,获得效率不错的计算机。冯诺依曼机为电脑提供性价比,被人们广泛接受。
程序的本质是逻辑+数据,在数据层面,CPU不和外设之间打交道,只会和内存打交道。为什么?体系结构规定的。
结论:在数据层面上,CPU不会和外设之间打交道,CPU读写数据只会和内存打交道
我们口中的输入输出是站在内存的角度思考问题,也就是站在加载到内存中的程序的角度,也就引出了我们的I/O。
1.2 数据的流动与效率
计算机数据流动的过程,本质就是拷贝。计算机的效率问题本质就是由设备的拷贝效率决定的,离CPU越近效率越高。
数据的运转实际上就是数据的拷贝,存储设备的效率,本质上就是拷贝的效率。
文件传送:你往云服务器上拖拽文件实际上是将在你硬盘上的数据拷到你买的云服务器的磁盘上。
数据的流动和软件没有关系,路怎么修,数据就怎么走。
2. 操作系统
2.1 操作系统的概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS),操作系统包括:
- 内核四大功能:进程管理,内存管理,文件系统,设备驱动
- 其他程序,例如库函数,shell程序等
2.2 设计操作系统的目的
操作系统的设计目的可以总结为一个手段,一个目的:
- 手段(对下):与硬件交互,对计算机所有的软硬件资源进行合理、高效的管理。
- 目的(对上):为用户程序(应用程序)提供一个稳定、安全、高效的执行环境。
就像一家餐厅,老板必须管理好底下的服务员、厨师和硬件设施(手段),最终目的是为了给顾客提供优质的就餐体验(目的)。如果操作系统管理不好,导致电脑频繁死机,用户就会选择卸载它。
2.3 核心功能
既然操作系统的本质是管理,那它具体管理什么呢?在冯诺依曼体系结构下,主要包含以下四大核心管理模块:
- 进程管理:管理程序的执行过程(创建、调度、终止进程等)。
- 内存管理:管理内存空间的分配、回收以及虚拟内存的映射。
- 文件系统:管理磁盘上的数据,提供文件的创建、读写、删除等接口。
- 设备驱动:管理各类外设(如键盘、鼠标、网卡、磁盘等),屏蔽硬件底层的复杂性。
2.4 如何理解管理
管理的本质实际上是对数据进行管理,数据的本质是提供决策依据的。
操作系统是一款软件,是一款进行软硬件资源管理的软件,它的底层逻辑实际是:先描述,再组织
校长:操作系统内核
老师:驱动程序
学生:底层硬件
站在数据结构的角度去理解:
OS内部,最核心的东西是什么?是数据结构!
那么引出了C++语言中,为什么会有类和STL?类的本质是描述,STL的本质是组织形式。先描述再组织是这个世界的真相,是把现实世界的问题进行计算机建模的核心规律。
2.5 系统调用和库函数
操作系统是怎样对上管理的?通过系统调用。
很多C/C++函数,都是对系统调用的封装。所有涉及到硬件的操作都需要通过系统调用贯穿操作系统。库函数和shell程序都是间接地通过系统调用来完成的。
3. 进程
课本中是怎样描述进程的呢?
进程:运行起来的程序,内存中的程序。
程序和可执行文件是一回事,程序的本质是磁盘上的一个普通文件。
什么叫做进程呢?操作系统内能同时运行很多程序,每个程序都要加载到内存。
操作系统内一定会同时存在很多进程,这些进程要不要被操作系统管理起来?当然要。
在我们还没有启动进程之前,第一个软件是什么?是操作系统。
操作系统应该如何管理进程:在操作系统内,先描述一个进程,需要有一个数据结构。如何证明这个进程属于操作系统?首先进程要在操作系统里,其次里面要有描述你的属性信息的结构体。实际上:
进程 = 内核数据结构 + 自己程序的代码和数据
程序就是在磁盘特定路径下的文件。
执行命令的本质就是启动进程。
启动APP的本质也是启动进程。
gcc -o text.c text
的本质也是进程,这个进程的任务是将源文件翻译为可执行文件。
3.2 进程结构的属性
- 标识符:描述本进程的唯一标识符,用来区别其他进程
- 状态:任务状态,退出代码,退出信号等
- 优先级:相对于其他进程的优先级,优先级实际上就是一个整型变量。存在优先级的本质是资源竞争
- 程序计数器:程序中即将被执行的下一条指令的地址。执行代码不是一口气执行完的,和进程切换有关
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据,记录你执行到哪里了,保存的就是寄存器中的临时数据。CPU内的寄存器只有一份,但上下文可以有多份,分别对应不同的进程,属于进程私有
一个进程执行代码,占有CPU,把自己的代码执行完才放弃CPU吗?不是。当代计算机都会给每个进程分配一个时间片,时间片执行完毕,就自动让出CPU,让另一个进程执行。所以一个进程没有执行完,就可能会把CPU让出去。
基于时间片的轮转调度,因为时间片到达,就会存在时间片的切换和调度的动作。
- I/O状态信息:包括显式的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
3.3 查看进程
上下文数据放到PCB里,临时数据保存在内核栈中。
一个进程如何获得自己的标识符:获取自己的task_struct结构内部的属性值,OS就必须给我们提供一个系统调用。
man getpid

通过系统调用,将里面的结构复制一份出来。
执行第一行代码的时候就有id了,打印一下看看:
ps axj | grep process
加上列信息的提取:
ps axj | head -1
紧接着再执行:
ps axj | head -1 ; ps axj | grep process

PID是不一样的,计数器默认是线性递增的,分配完了会回绕。但是其他任务也在用,所以你看到的没有特定的规律。
一个进程除了有自己的PID,还有一个父进程:
获取父进程:
getppid();
在Linux系统中,新的进程往往是通过父进程的方式创建出来的。
问题是,父进程是怎样创建子进程的?每一次启动,自己的PID一直在变,但是自己的父进程一直不变。
所以父进程是谁啊?
自己的程序、执行命令本质上都是将命令名交给bash,bash是我们之前说的命令行解释器,它会给我们创建子进程,它是所有程序的父进程。
命令行解释器本身就是一个进程,我们最常见的父进程就是bash。

3.4 如何创建进程(子进程)
通过代码的方式创建子进程,创建进程的本质是系统多了一个子进程 → task_struct + 代码和数据。
创建一个子进程必定要经过系统调用,也就是使用fork()可以创建一个进程。
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before fork\n");
pid_t id = fork();
if(id == 0){
// child
printf("child: pid=%d, ppid=%d\n", getpid(), getppid());
} else {
// parent
printf("parent: pid=%d, child pid=%d\n", getpid(), id);
}
sleep(1);
return 0;
}
一个printf被打印了两次,fork之前只有一个执行流,fork之后执行流被一分为二,一个延续,另一个创建了一个子进程。实际上就是通过系统调用fork创建一个子进程。
bash解释器是用C语言写的,bash里面也是通过fork()创建进程的。
/proc目录内部包含很多用数字命名的目录(每个数字代表一个进程的PID),进程信息可以让我们以文件的形式实时展示出来。进程存在则目录存在,进程结束则目录消失。
进程会实时在/proc目录下显示,文件内容是进程所对应的属性,查出来的结果是从文件里面筛选出来的。
一个进程的属性里面会存在一个cwd(当前工作目录)。
3.5 fork()的深入理解
3.5.1 fork()的三个核心问题
问题1:为什么给子进程返回的是0,给父进程返回的是子进程的PID?
父进程可以有多个子进程,需要通过每个子进程的PID来区分和管理它们。而子进程只需要知道自己是否创建成功即可,如果需要知道父进程的PID,可以通过getppid()随时获取。这种设计让父进程和子进程能够通过返回值轻松区分彼此,便于编程。
问题2:fork()一个函数怎么会有两个返回值?
普通函数只有一个返回值,但fork()是系统调用。调用时进程进入内核态,内核完成进程复制后,在返回用户态时,父进程和子进程各自从内核返回一次,各自得到一个返回值。所以看起来“一个函数返回了两个值”。
问题3:一个id变量怎么能接收两个不同的值?即等于0又大于0?
父进程和子进程的id变量在虚拟地址空间中是相同的,但它们映射到不同的物理地址(通过写时拷贝机制)。子进程得到的返回值是0,父进程得到的返回值是子进程的PID,分别写入各自独立的物理内存中。因此,同一个变量名(虚拟地址相同)可以存储不同的值(物理地址不同)。
3.5.2 fork()的写时拷贝机制
fork()创建子进程时:
- 子进程复制父进程的页表
- 父子进程的页表项都被标记为只读
- 父子暂时共享同一块物理内存
- 当任一进程尝试写入时,触发缺页中断
- 操作系统为新进程分配新的物理页框
- 将原数据拷贝到新页框中
- 更新页表映射,恢复读写权限
这就是写时拷贝(Copy-on-Write机制,它让fork()创建进程的效率极高(只需要复制页表,不需要复制全部物理内存)。
欢迎大家批评指正!!!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)