【无标题】
文章目录
操作系统内部布局回顾
内存布局详细解析

进程概念
进程的重新定义
首先,第一个核心概念:
进程实际上是在执行流(Execution Flow)概念封装下,承担系统资源分配的基本实体。
重新定义进程:
进程是操作系统进行系统资源分配的基本实体。
那么进程如何承担资源分配?
它依靠的是操作系统内核中的相关数据结构。
例如:
- PCB(Process Control Block,进程控制块);
- 地址空间(Address Space);
- 页表(Page Table);
- 文件描述符表(File Descriptor Table);
- 信号相关结构;
- 其他内核管理结构。
这些资源管理结构,以及进程对应的:
- 代码;
- 数据;
共同组成:
进程资源(Process Resources)。
##进程资源与地址空间
进程拥有的资源最终通过:
地址空间 + 页表映射关系
实现对资源的访问。
例如:
- 代码区;
- 数据区;
- 堆区;
- 栈区;
- 共享区域;
- 动态库映射区域等。
也就是说,一个进程能够访问哪些资源,本质上由它自己的地址空间以及页表映射关系决定。
每一个进程都有自己的:
- PCB;
- 地址空间;
- 页表。
其中 Linux 内核中的 PCB 对应的数据结构为:

task_struct
进程地址空间
每个进程都有独立的虚拟地址空间。
一个进程的虚拟地址空间通常划分为:
- 用户空间(User Space):0~3GB
- 内核空间(Kernel Space):3~4GB
其中用户空间主要包括:
- 代码区(Text Segment)
- 已初始化全局数据区(Data Segment)
- 未初始化全局数据区(BSS Segment)
- 堆区(Heap)
- 栈区(Stack)
- 共享库映射区等
内核空间部分在信号、用户态与内核态切换等内容中已经涉及。
虚拟地址与物理地址映射
一个进程中的所有地址实际上都是:
虚拟地址(Virtual Address)
虚拟地址经过页表转换后,最终映射到:
物理地址(Physical Address)
其中:
- 虚拟地址空间属于进程视角;
- 物理内存属于硬件实际存储;
- 页表负责维护虚拟地址到物理地址的映射关系。
实际系统中页表通常采用多级页表结构。
简化理解:
虚拟地址
|
↓
多级页表
|
↓
物理地址
|
↓
物理内存
物理内存进一步划分为固定大小的页框(Page Frame),通常大小为:
4KB
因此:
- 代码区;
- 数据区;
- 堆区;
- 栈区;
这些虚拟地址区域最终都会通过页表映射到物理内存中的不同位置。
地址转换过程
例如进程访问某个变量:
- 获取变量对应的虚拟地址;
- CPU 中的 MMU(Memory Management Unit,内存管理单元)查询页表;
- 完成虚拟地址到物理地址转换;
- 访问对应物理内存。
过程如下:
虚拟地址
|
↓
MMU + 页表
|
↓
物理地址
|
↓
物理内存
软硬件结合的地址转换机制
虚拟地址转换为物理地址,并不是单纯依靠软件完成,而是采用:
软件维护 + 硬件执行
的方式。
其中:
- 页表属于软件维护的数据结构;
- MMU 属于硬件模块,负责地址转换。
为什么采用软硬件结合?
因为:
硬件优势:
- 执行速度快;
- 可以通过电路直接完成固定逻辑操作。
但是:
- 灵活性较低;
- 修改成本高;
- 扩展困难。
软件优势:
- 易维护;
- 易扩展;
- 可以动态调整。
但是:
- 执行速度较慢。
因此现代计算机系统采用:
软件负责维护页表,硬件 MMU 负责完成虚拟地址到物理地址的快速转换。
##完整进程组成
一个完整进程除了包含:
- PCB;
- 地址空间;
- 页表;
- 代码和数据;
还包括:
- 系统调用;
- 文件描述符表;
- 信号机制;
- 父子进程关系;
- CPU 调度;
- 上下文切换;
- 进程状态;
- 优先级等。
进程的运行过程
在线程学习之前,对进程的定义:
进程 = 内核数据结构 + 代码和数据
一个程序最开始存储在磁盘中。
当程序运行时:
- 操作系统通过加载机制(例如 ELF 文件加载)将程序加载到内存;
- 创建对应的 PCB;
- 建立进程虚拟地址空间;
- 创建页表,完成虚拟地址到物理地址的映射;
- CPU 根据调度策略执行该进程。
可执行程序在编译阶段已经按照虚拟地址空间布局生成。
因此加载时,操作系统主要完成:
- 创建进程控制结构;
- 建立地址空间;
- 建立页表映射;
- 加载代码和数据。
最终:
CPU 调度该进程执行,完成程序运行。
线程概念与进程关系
当我们理解线程这个概念时,首先需要思考:
线程和进程之间是什么关系?
这里研究:
Linux 操作系统中的多线程实现机制。
不同操作系统对于线程底层实现方式可能不同。
类似于:
不同学校都有:
- 学生;
- 老师;
- 校长。
但是不同学校的管理方式不同。
同样:
不同操作系统都有线程概念,但是底层实现机制可能不同。
Linux 中的线程实现具有自身特点,但最终满足操作系统理论中线程的基本定义。
Linux 中线程实现方式
之前创建子进程时,例如调用:
fork()
操作系统需要:
- 创建新的 PCB;
- 创建新的地址空间;
- 创建新的页表;
- 通过写时复制(Copy On Write)机制共享父进程代码和数据。
结构如下:
父进程
|
+-- PCB
+-- 地址空间
+-- 页表
+-- 代码数据
子进程
|
+-- PCB
+-- 地址空间
+-- 页表
虽然父子进程初始可以共享代码和数据,但是它们拥有独立的资源管理结构。
重新理解进程资源
一个进程真正拥有的核心资源包括:
- 代码区;
- 已初始化数据区;
- 未初始化数据区;
- 堆区;
- 栈区;
- 共享区;
- 动态库映射区域等。
这些资源主要由:
虚拟地址空间 + 页表
决定。
其中:
虚拟地址空间决定:
一个进程能够看到哪些资源。
可以将进程理解为:
- 房间 = 虚拟地址空间;
- 窗户 = 访问资源的入口;
- 窗外景象 = 进程能够访问的数据和代码。
一个进程能够看到什么,本质上由它的地址空间决定。
Linux 线程实现模型
如果不创建新的地址空间,而只是创建多个 PCB:
PCB1
|
|
地址空间A
PCB2
|
|
地址空间A
PCB3
|
|
地址空间A
多个 PCB 共享同一个地址空间。
那么:
- 每个 PCB 都可以访问同一份代码和数据;
- 每个 PCB 可以执行不同的代码区域。
此时:
同一个进程内部存在多个执行流。
因此:
Linux 中线程的本质可以理解为:
线程就是进程内部的一个执行流。
具体实现:
Linux 线程并不是传统意义上的独立资源实体,而是创建独立的任务控制结构(task_struct),共享所属进程的地址空间、页表以及大部分资源。
线程栈与 mmap
为什么子线程栈存放在 mmap 区域?
因为:
除主线程之外,其他动态创建的线程,其线程栈通常通过 mmap 系统调用动态分配。
传统栈区限制
经典进程虚拟地址空间中:
- 栈区默认只有一份;
- 位于地址空间顶部;
- 服务于主线程。
主线程栈具有:
- 动态增长;
- 缺页异常自动扩展。
但是多线程情况下:
每个线程都必须拥有独立栈空间,用于保存:
- 局部变量;
- 函数调用信息;
- 返回地址;
- 寄存器上下文。
否则多个线程会互相覆盖栈数据。
因此:
子线程不能继续使用传统栈区。
mmap 作为线程栈的原因
① 动态创建和销毁方便
线程运行期间可能动态创建:
例如:
pthread_create()
使用 mmap 创建匿名映射区域:
- 可以向内核申请独立虚拟空间;
- 可以指定大小;
- 线程结束后使用 munmap 回收。
管理更加方便。
② 支持保护页机制
为了防止线程栈溢出影响其他线程:
Linux 会在线程栈附近设置:
Guard Page(保护页)
例如:
高地址
+----------------------+
| 子线程A 栈空间 |
+----------------------+
| Guard Page |
+----------------------+
| 子线程B 栈空间 |
+----------------------+
低地址
保护页权限通常设置为:
PROT_NONE
没有读写权限。
当线程栈溢出访问保护页:
- CPU 检测非法访问;
- 触发异常;
- 内核捕获;
- 产生 Segmentation Fault。
避免破坏其他线程数据。
③ 子线程栈大小固定
主线程:
- 启动时由内核建立;
- 位于传统 Stack 区;
- 可以动态扩展。
子线程:
- 创建时通过 mmap 分配;
- 栈大小提前确定;
- 通常默认 8MB;
- 可以通过线程属性修改。
因此:
子线程栈不能像主线程一样自由增长。
进程与线程总结
进程:
- 拥有独立 PCB;
- 拥有独立地址空间;
- 拥有独立页表;
- 是系统资源分配的基本单位。
线程:
- 拥有独立 task_struct(Linux 中线程控制结构);
- 共享所属进程的地址空间;
- 共享代码段、数据段、堆等资源;
- 拥有独立栈空间;
- 是 CPU 调度的基本单位;
- 是进程内部的执行流。
最终:
一个进程可以包含多个线程,每个线程拥有自己的执行上下文,但共享同一个进程资源。
线程重新定义
当进程完成资源分配后,可以进一步思考:
是否可以将进程中的部分资源,分配给某一个执行流?
答案:
可以。
因此:
线程属于进程内部的一个执行流,是 CPU 调度的基本单位。
两个核心概念:
进程:
承担系统资源分配的基本实体。
线程:
进程内部的执行流,是操作系统调度的基本单位。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)