本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
intro
- 本篇博客是对
进程
的结构与属性,虚拟内存
的布局和内容的简单讲解
进程与程序之间的关系
process 是 program的实体化
程序
包含了是一系列信息的文件
,这些信息描述了如何在运行时
创建一个进程
- 二进制格式表示:每个程序文件都包含用于描述可执行文件格式的
元信息
(meta information),内核
(kernel)利用此信息来解释文件中的其他信息 - 机器语言指令
- 程序入口地址
- 数据
- 符号表及重定位表
- 共享库和动态链接信息
- 其他
- 二进制格式表示:每个程序文件都包含用于描述可执行文件格式的
- 准确来说:process是内核定义的抽象的实体,并为该实体分配用以
执行程序
的各项系统资源
进程由用户内存空间
+ 一系列内核数据结构
组成
- 用户内存空间与内核数据结构分别存放进程的相关信息
用户内存空间:
- 程序代码
- 代码所使用变量
内核数据结构:维护进程状态信息
- 与进程相关的标识号
- 虚拟内存表
- 打开的文件描述符表
- 信号传递及处理的有关信息
- 进程资源使用及限制
- 当前工作目录
- 大量其他信息...
进程内存布局
- 每一个C/C++程序员都会遇到可恶的
Segmentation fault
,它的发生是因为对内存进行了不当操作,想要很好的理解这个错误,就要搞清楚进程内存布局
是怎么回事,我们到底是在操作了什么东西使SIGSEGV
信号产生并杀死了进程 心动的感觉
进程所分配的内存由很多部分组成,通常称之为"
段
"文本段:(text)
- 包含了程序运行的程序机器语言指令
只读属性
- 可共享,一份程序代码的拷贝可以映射到所有进程的虚拟地址空间中
初始化数据段:(data)
- 显示初始化的全局变量和静态变量
未初始化数据段:(bss)
- 未进行显示初始化的全局变量和静态变量
- BSS段(block started by symbol)
- 程序启动前,系统将本段所有内存初始化为
0
(未初始化变量为0的原因) - 可执行文件只需记录其位置及所需大小,直到运行时再有程序加载器为其分配空间
栈:(stack)
动态
增长与收缩的段,由栈帧(stack frames)
组成- 栈帧中存储了函数的
局部变量
,实参
和返回值
堆:(heap)
- 可在运行时进行动态内存分配的区域
- 堆
顶端
叫做program break
- 内存布局
- 由图可知,进程在运行中对内存的访问是有限制的,段错误发生在访问了
不可访问
的内存,这个内存要么是不存在的(例如越过了progrram break),要么是受系统保护的
虚拟内存管理
- 进程内存布局存在于虚拟内存中
虚拟内存管理技术利用了
访问局部性
:追求高效使用CPU和RAM
空间局部性
(Spatial locality):是指程序倾向于访问在最近访问过
的内存地址附近的内存(由于指令是顺序执行的,且有时会按顺序处理数据结构)时间局部性
(Temporal locality):是指程序倾向于在不久的将来再次访问最近刚访问过的内存地址(由于循环)- 由于访问局部性的存在,使程序在只有部分地址空间存在于RAM的情况下依然可以执行
虚拟内存规划:
- 将每个程序分割为小型的,固定大小的
页(page)
单元 - 将RAM(物理硬件)分为一系列与虚拟页尺寸相当的
页帧
- 任一时刻,每个程序仅有部分页需要驻留在物理内存页帧中:这些页构成了所谓
驻留集
(resident set) - 程序未使用的页拷贝保存在
交换区
(swap area)内—这是磁盘空间中的保留区域(计算机RAM的补充)
- 将每个程序分割为小型的,固定大小的
内核为每个进程维护一个
页表
- 描述了每
页
*在进程虚拟地址空间
(virtual address space)中的位置(可为进程所用的所有虚拟内存页面的集合) - 页表中每个条目要么指出一个虚拟页面在RAM中的位置,要么表明其当前驻留在磁盘上(不是在RAM中就是在磁盘上还未被加载)
- 并非所有的地址范围都需要页表条目,大段的未使用的虚拟地址空间就没必要为其维护页表条目
- 若进程试图访问的地址无页表条目与之对应,那么进程将收到
SIGSEGV
信号
- 描述了每
内核
能够为进程分配和释放页
/页表条目
,所有进程的虚拟地址范围在其生命周期可以发生变化- 栈向下增长超出之前曾达到的位置
- 在堆中分配或释放内存时,通过调用 brk()、 sbrk()或 malloc 函数族来提升 program break 的位置
- 当调用 shmat()连接 System V 共享内存区时, 或者当调用 shmdt()脱离共享内存区时
- 当调用 mmap()创建内存映射时,或者当调用 munmap()解除内存映射时
虚拟内存管理使进程的虚拟地址空间与 RAM 物理地址空间隔离开来,这带来许多优点:
- 进程与进程,进程与内核相互隔离:一个进程不能读取或修改另一进程或内核的内存
可以实现共享内存:内核可以使不同进程的页表条目指向相同的RAM页
- 执行同一程序的多个进程可共享一份(只读的)程序代码副本
- 进程可以使用 shmget()和 mmap()系统调用显式地请求与其他进程共享内存区:实现进程间通信
- 实现内存保护机制:某一进程对某一页帧为只读,而另一进程则可以读写同一页帧
- 程序员无需操心RAM的物理布局
- 加速程序的加载与运行
- RAM中容纳的程序变多,CPU可执行程序变多,效率提高
栈和栈帧
- 函数的调用和返回使栈的增长和收缩呈线性
- 专用寄存器—栈指针(stack pointer),用于跟踪当前栈顶。每次调用函数时,会在栈上新分配一帧,每当函数返回时,再从栈上将此帧移去
- 内核栈是每个进程保留在内核内存中的内存区域,在执行系统调用的过程中供(内核)内部函数调用使用
用户栈帧保存:
- 函数实参和局部变量
- 函数调用的链接信息:每当一函数调用另一函数时,会在被调用函数的栈帧中保存使用过的寄存器的副本,以便函数返回时能为函数调用者将寄存器恢复原状
- 因为函数能够嵌套调用,所以栈中可能有多个栈帧
参考
- 《TLPI》