介绍一下操作系统内存管理
操作系统每个系统都有自己的虚拟内存,我们所写的程序不会直接与物理内存打交道
虚拟内存的好处:
- 虚拟内存使进程的运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU访问内存会有很明显的重复访问的倾向性,对于那些没有经常被使用到的内存,我们可以把他们换出到物理内存之外
- 由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的。进程也无法访问其他进程的页表,所以页表是私有的,这就解决了多进程之间地址冲突的问题
- 页表项中除了物理地址之外,还有一些标记属性的比特,在内存访问层面为操作系统提供了更好的安全性
linux通过对内存分页的方式进行内存管理。
虚拟地址和物理地址之间通过页表来进行映射
页表是存放在内存里的,内存管理单元(MMU)来进行虚拟内存地址和物理内存地址的转换工作
当进程访问的虚拟地址在内存中查不到时,系统会产生缺页异常,进入系统的内核空间分配物理内存,更新进程页表,最后返回用户空间,恢复进程运行。
什么是虚拟内存和物理内存
- 虚拟内存:是操作系统为每个程序 “虚拟” 出来的独立地址空间,程序运行时会认为自己拥有一块连续的、专属的内存区域,但这块区域并不完全对应物理内存,而是由操作系统通过 “内存 - 磁盘” 的交换机制来管理
- 物理内存:计算机的实际内存硬件,是处理器可以直接访问的存储空间,用于临时存储正在运行的程序、数据和操作系统指令
讲一下页表
- 页表是虚拟内存分页机制的核心数据结构,它的核心作用是建立 “虚拟页面” 到 “物理页帧” 的映射关系
- 页表的结构由多个页表项组成,每个页表项对应一个虚拟页面,包含这些关键信息:一是物理页号,直接指明虚拟页映射到物理内存的具体页帧;二是状态标志位,比如 “存在位” 标记该页是否在物理内存中,“修改位” 记录页面是否被修改过。
- 当程序访问虚拟地址时,首先会被拆成 “虚拟页号” 和 “页内偏移”;MMU会用 VPN 作为索引查页表,找到对应的页表项;如果存在,就用页表项里的 物理页号 结合页内偏移,算出物理地址,完成访问;如果缺页,就触发操作系统的缺页异常处理,从磁盘加载页面到物理内存,更新页表后再继续执行
页表存在的作用:
- 它让每个进程拥有独立的虚拟地址空间,保证了进程隔离;
- 通过映射实现物理内存的灵活分配,提高了内存利用率;
- 结合缺页机制和磁盘交换,让程序可以运行在比物理内存更大的地址空间中
讲一下段表
- 段表是分段内存管理机制的核心数据结构,核心作用是实现 “逻辑段” 到 “物理内存块” 的映射。
- 段表的结构由多个段表项组成,包含三个核心信息:一是段基址,也就是这个段在物理内存中的起始地址;二是段长度,记录该段的总大小,用于判断访问是否越界;三是属性位,比如读写执行权限、存在位
- 当程序访问某个虚拟地址时,首先会拆成 “段号” 和 “段内偏移”;接着用段号查段表,找到对应的段表项后,先做越界检查 —— 如果段内偏移超过段长度,就触发 “段越界异常”;如果没问题,就用段基址加上段内偏移,得到最终的物理地址,完成访问。如果段表项的存在位为 0,则会触发 “缺段异常”,由操作系统从磁盘加载段到物理内存,更新段表后再继续执行
段表的实际意义:
- 通过逻辑分段和权限控制,保障了内存安全
- 简化程序开发,程序员无需关心物理内存布局,只需按功能组织代码和数据;
页表与段表的区别
- 分页是按固定大小划分内存,更侧重物理内存的高效利用,但逻辑上不够直观
- 分段是按逻辑功能划分,段的大小不固定,优势在于逻辑隔离清晰,权限控制更细粒度,但可能产生外部碎片
- 简单说,页表解决 “物理内存怎么高效用”,段表解决 “逻辑功能怎么安全隔”
虚拟地址怎么转化为物理地址
当程序访问虚拟地址时,首先会被拆成 “虚拟页号” 和 “页内偏移”;MMU会用 VPN 作为索引查页表,找到对应的页表项;如果存在,就用页表项里的 物理页号 结合页内偏移,算出物理地址,完成访问;如果缺页,就触发操作系统的缺页异常处理,从磁盘加载页面到物理内存,更新页表后再继续执行
程序的内存布局是什么样的
用户的内存空间,从低到高分为六种不同的内存段:
- 代码段:包括二进制代码
- 数据段:包含已初始化的静态常量和全局变量
- BSS段:包含未初始化的静态变量和全局变量
- 堆段:包含动态分配的内存,从低地址向上增长
- 文件映射段:包括动态库,共享内存等
- 栈段:包含局部变量和调用上下文的函数等
堆和栈的区别
- 分配方式:堆是动态分配内存的,由程序员手动申请和释放,通常用于存储动态数据结构或对象。栈是静态分配内存的,由编译器静态申请或释放,用于存储函数的局部变量和函数调用信息
- 内存管理:堆需要程序员手动管理内存的分配和释放,如果管理不当可能导致内存泄漏或内存溢出。栈由编译器自动管理内存,遵循后进先出的原则,变量的生命周期由作用域决定,函数调用时分配内存,函数结束时释放内存
- 大小和速度:堆通常比栈大,内存空间较大,动态分配和释放内存需要时间开销。栈大小有限,通常比较小,内存分配和释放速度较快,因为是编译器自动管理
fork()会复制哪些东西
- fork阶段会复制父进程的页表
- fork之后,如果发生了写时复制,就会复制物理内存
介绍写时复制(copy on write)
主进程执行fork()时,操作系统会把主进程的页表复制一份给子进程,这个页表记录着虚拟地址和物理地址的映射关系,而不会复制物理内存,也就是说虚拟空间不同,物理地址相同
这样能节约物理内存资源,页表对应的页表项会标记该物理内存的权限为只读
当父进程在向这个内存发起写操作时,CPU会触发写保护中断,这个写保护中断是由于违反权限导致的,然后操作系统会在【写保护中断处理函数】里进行物理内存复制,并重新设置其内部的映射关系,将父子进程的内存权限设置为可读写,最后才对内存进行写操作。这个过程被称为写时复制。
写时复制:在触发写操作时,操作系统才会去复制物理内存,这是为了防止fork创建子进程时由于物理内存数据复制时间过长导致父进程长时间阻塞的问题。
写时复制节省了什么资源
节省了物理内存的资源,因为fork时,子进程不需要复制父进程的物理内存,避免了不必要的内存复制开销,子进程只需要复制父进程的页表,这时候父子进程指向相同的物理内存
只有当父子进程任意一方对这片共享内存有修改操作,才会触发写时复制,这时候才会复制发生修改操作的物理内存地址
malloc 1KB和1MB有什么区别
- 如果用户分配的内存小于1KB,则用brk()申请内存
- 如果大于128KB,则用mmap()申请内存
介绍一下brk()和mmap()
malloc申请内存时,拥有两种方式向操作系统申请堆内存:
- 通过brk()系统调用从堆申请内存
- 通过mmap()在文件映射区域分配内存
对于brk(),即把堆顶的指针向高地址移动,获得新的内存空间
mmap()用于将文件或设备的地址空间直接映射到进程的虚拟地址空间。通过这种映射,进程可以像访问内存一样直接读写文件 / 设备,无需传统的 read/write 系统调用,从而显著提升 IO 效率。
调用 mmap() 后,内核仅分配虚拟内存,不会立即将文件数据加载到物理内存,首次访问触发缺页异常,减少内存浪费
- 减少数据拷贝:传统文件读写需要经过 “磁盘→内核缓冲区→用户缓冲区” 两次拷贝,而 mmap 直接映射到用户空间,仅需一次磁盘到内存的拷贝。
- 共享性:支持多进程共享映射区域,实现进程间高效通信。
- 灵活性:可通过参数指定映射模式、权限等。
操作系统内存不足的什么会发生什么
当没有空闲的物理内存时,内核就开始内存回收工作,回收的方式包括直接内存回收和后台内存回收
- 后台内存回收:在内存紧张的时候,会唤醒kswapd内核线程来回收内存,这个回收内存的过程是异步的,不会阻塞进程的执行
- 直接内存回收:如果后台异步回收跟不上进程申请的速度,就开始直接回收,这个回收内存的过程是同步的,会阻塞进程执行
如果直接回收内存后,空闲内存仍然无法满足物理内存的申请,那就会触发OOM机制
OOM killer机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存仍然不足,会继续这样过程,直到释放足够的内存位置
回收内存的类型和回收方式
- 文件页:内核缓存的磁盘数据和内核缓存的文件数据都叫文件页。大部分文件页都可以直接释放内存,以后有需要时再从磁盘重新读取就可以。而那些被应用程序修改过,暂时没有写入磁盘的数据,就得先写入磁盘,然后才能进行内存释放。所以回收干净内存页的方式是直接释放内存,回收脏页的方式是先写回磁盘再释放
- 匿名页:这部分内存没有实际载体(堆、栈等),这部分内存可能还要再访问,所以不能直接释放内存,他们的回收方式是通过linux的swap机制,swap会把不常用的内存先写到磁盘中,然后释放内存。再次访问这些内存,直接从磁盘读入就可以了
文件页和匿名页的回收都基于LRU算法,也就是优先回收不常访问的内存。
页面置换算法有哪些
最佳页面置换算法
原理:置换在未来最长时间不访问的页面
该算法实现需要计算内存中每个页面下一次访问时间,然后比较,选择未来最长时间不访问的页面
实际系统中无法实现,因为程序访问页面是动态的,无法预知每个页面在下一次访问前的等待时间
所以最佳页面置换算法是为了衡量算法效率,算法效率越接近于该算法的效率,说明越高效
先进先出置换算法(FIFO)
原理:选择在内存中滞留时间最长的页面进行置换
最近最久未使用算法(LRU)
选择最长时内存间没有被访问的算法进行置换,也就说该算法假设已经很久没有使用的页面很有可能在未来较长一段时间仍然不会被使用
时钟页面置换算法
原理:把页面都保存在一个类似时钟的环形链表中,一个指针指向最老的页面。
当发生缺页中断:
- 如果他的访问位是0就淘汰该页面,并把新页面插入这个位置,然后把表指针前移一个位置
- 如果访问位是1就清除访问位,并把表指针前移一个位置,重复这个过程直到找到访问位为0的页面
最不常用算法(LFU)
原理:对每个页面设置一个访问计数器,每当页面被访问时,该页面的访问计数器就累加1,发生缺页中断时,选择访问次数最少的页面并将其淘汰
LFU只考虑了频率问题,没有考虑时间问题,比如有些页面过去访问频率很高但现在不再访问了,而当频繁访问的页面由于没有这些页面访问的频率高,在发生缺页中断时就可能先淘汰刚开始频繁访问的页面
可以定期减少访问次数,比如发生时间中断时,把过去时间访问的页面访问次数除以2就可以,也就是说,随着时间流逝,过去访问频率高的页面的访问次数会逐渐减少,相当于加大了被置换的概率

-2025-10-22-19-28-23.png)