从硬件看内存纸带上内存存储器动态分区法分段机制分页机制 从内核看内存为什么区分内核空间和用户空间什么是用户态和内核态如何从用户空间进入内核空间进程上下文和中断上下文 一、从硬件看内存 1、纸带上 计算机读取一张纸带就运行一条指令,从外部存储介质上直接运行指令的方法很低效 2、内存存储器 程序存在内存存储器上,首先要加载程序到内存,然后执行。开启了存储程序的新大陆,也开启了操作系统的新大陆。 3、动态分区法 操作系统将内存分成一段一段的,只要有程序来就有合适的内存大小就优先安排上,若内存大小不够时易产生程序的换入换出过程(内存地址交换),同时还产生内存碎片。 缺陷:进程地址空间保护问题、内存使用率底、程序运行地址重定位问题 4、分段机制 增加了虚拟内存。将程序所需内存空间的虚拟地址映射到某个物理地址空间。它解决了地址空间保护问题。进程运行时看到的地址是虚拟地址,需要CPU提供的地址映射方法才能找到实际的物理地址。因此每个进程的虚拟内存空间都是相互隔离的。需要操作系统维护映射关系。 分段机制将程序分成若干段,一个进程可以分成(代码段、数据段、堆段)等。有点类似动态分区法。它们在物理地址可以是不连续的,这样可以解决内存碎片问题,但是会产生外部碎片。 缺陷:分段机制对虚拟内存到物理内存的映射仍旧是以进程为单位,若某块物理内存不足时,会把进程的所有段都换出到磁盘。因此,会产生大量的磁盘访问,从而影响性能。按照局部性原理可以优化一下,不用将不常用的程序段换出到磁盘,只换出那些一直在使用的程序段即可。这样会少一些磁盘和系统带宽。 5、分页机制 解决分段地址映射粒度大 的问题,将进程地址空间细分为固定大小的页面,进程的虚拟地址空间也按页面分割,这样常用的数据和代码就可以以页的方式存储在内存中,而不常用的页面可以交换到磁盘中,从而节省物理内存。 物理内存也是以页为单位来管理,简称"物理页面"或"页帧"。进程的虚拟地址空间中的页面称"虚拟页面"。操作系统为了管理这些页帧按物理地址给每个页帧编号,简称页帧号 。 Intel的处理器支持以2MB和1GB为单位的大页面。分页机制离不开硬件支持,CPU内部有个硬件单元来负责虚拟页到物理页面的转换,称MMU 的硬件单元。ARM处理器的MMU包括TLB 和页表遍历 单元两个部件。 TLB是一个高速缓存 ,用于缓存页表转换的结果,从而减少页表查询的时间。 一个完整的页表翻译和查找的过程叫作页表查询 ,页表的查询是由硬件自动完成的,但是页表的维护需要软件来完成。当TLB没有命中时,MMU才会查询页表(例如AArch64页表分成4级页表,遍历就比较耗时),从而得到翻译后的物理地址。而页表通常存储在主存储器中,得到物理地址后,首先需要查询该物理地址的内容是否在高速缓存中有最新的副本,如果没有,则说明高速缓存未命中,需要访问主存储器。 总结 :对于多任务操作系统,每个进程都拥有独立的进程地址空间。这些进程地址空间在虚拟地址空间内是相互隔离的,但是在物理地址空间可能映射同一个物理页面。它们的映射关系主要是靠处理器的MMU提供页表映射和管理的功能 进程地址空间 又分为内核空间 和用户空间 。它们都可以通过处理器提供的页表机制映射到实际的物理地址。 二、从内核看内存 内存管理是一个非常复杂的系统,如果用分层来描述,内存空间可以分成为三个层次,分别是用户空间 、内核空间 、硬件层 。其中操作系统和驱动程序运行在内核空间,而应用程序运行在用户空间。 用户空间:0x0000 0000 0000 0000 ~ 0x0000 FFFF FFFF FFFF 内核空间:0xFFFF 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF 底位的虚拟地址空间 位于0x0000 0000 0000 0000 ~ 0x0000 FFFF FFFF FFFF 如果虚拟地址的最高位等于0 ,就使用这个虚拟地址空间,并使用TTBR0_BLx来存放页表的基地址。 高位的虚拟地址空间 位于0xFFFF 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF 如果虚拟地址的最高位等于1 ,就使用这个虚拟地址空间,并且使用TTBR1_ELx来存放页表的基地址。 例如在AArch64架构中,因为地址总线位宽最多支持48位,所以虚拟地址(VA)被划分为两个空间(用户空间、内核空间),每个空间最多支持256TB。 AArch64架构中的MMU支持单一阶段的页表转换也支持虚拟化扩展中两阶段的页表转换。 单一阶段的页表转换:把虚拟地址(VA)翻译成物理地址(PA) 两阶段的页表转换:1)把虚拟地址翻译成中间物理地址(IPA) 2)把IPA翻译成最终PA 在32位的linux系统中,一共能使用的虚拟地址空间是4GB。空间划分0-3GB为用户空间,3-4GB为内核空间。通常是按照3:1来划分的,当然也可以2:2来划分。 1、为什么区分内核空间和用户空间 在CPU的所有指令中,有些指令比较危险容易导致系统崩溃,譬如清理内存、设置时钟等。对于那些危险的指令只允许操作系统内核自己有权限操作,普通的应用程序只能使用那些不会造成灾难的指令。 例如Intel的CPU将特权等级划分为四个级别Ring0-Ring3。其实linux系统只使用了Ring0 和 Ring3两个运行级别。当运行Ring3级别 时被称为运行在用户态 ,运行在Ring0级别 时称之为运行在内核态 。 2、什么是用户态和内核态 当一个进程发起系统调用时,进程在内核代码中执行时,称进程处于内核态 。此时处理器处于特权最高的(Ring0级) 。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈 ,每个进程都有自己的内核栈 。 当一个进程在用户空间执行时,称进程处于用户态 。此时处理器在特权级最低的(Ring3级) 。当进程处于用户态时,执行的程序代码都会使用当前进程的用户空间的用户栈 ,每个进程都有自己的用户栈 。 注意 :当正在执行的用户程序突然被中断程序中断时,此时用户程序也可以象征地被称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。 内核态下:进程运行在内核地址空间中,此时CPU可以执行任何指令,运行的代码也不受任何限制,可以访问任何有效地址,也可以进行端口访问。 用户态下:进程运行在用户地址空间中,此时被执行的代码受到CPU的诸多检查,它们只能访问其他地址空间的页表项中规定在用户态下可访问页面的虚拟地址,只能对任务状态段(TSS)中I/O许可位图中规定的可访问端口进行直接访问。 注意 :对于以前的DOS操作系统而言是没有内核空间、用户空间、内核态、用户态这些概念的,统一认为代码都是运行在内核态下的,所以操作系统很容易崩溃掉。而对于Linux来说,通过区分内核空间和用户空间设计,隔离了操作系统代码与应用程序代码,使得操作系统更健壮、更具有稳定性和可用性以及安全性。 3、如何从用户空间进入内核空间 用户空间进入内核空间触发的三种场景分别是系统调用 (软中断)、异常 、硬件中断 。 进程在被创建的时候都会产生两个栈,分别是用户栈,内核栈 。用户栈位于用户地址空间,内核栈位于内核地址空间。 当进程在用户空间执行时,使用用户栈,此时CPU堆栈指针寄存器中存放的是用户栈的地址。 当进程在内核空间执行时,使用内核栈,此时CPU堆栈指针寄存器中存放的是内核栈的地址。 1)系统调用案例 当一个进程需要读取磁盘上某一资源,它会向操作系统发起一个系统调用指令,例如(Open()、Read()、Write()、Close())开放接口等。此时会发生一些CPU上下文切换动作。 保存当前CPU寄存器里原来用户态的指令位 为了执行内核态代码,CPU寄存器需要更新为内核态指令的新位置 跳转到内核态运行内核任务,例如从调用磁盘IO,读取资源存储到内核态内核缓存区中 待磁盘IO结束后,再从内核空间的内核缓存区 拷贝数据到用户空间的用户缓存区 当系统调用结束,CPU寄存器恢复到原来的用户态指令位 最后切换到用户空间,继续运行进程 总的来说 ,在内核空间中代码控制了硬件层资源的使用权,在用户空间中的代码它只能通过内核对外开放的系统调用接口来调用系统中的硬件资源。 4、进程上下文和中断上下文 处理器总处于以下三种状态之一 内核态,运行于进程上下文 ,内核代表进程运行于内核空间 内核态,运行于中断上下文 ,内核代表硬件运行于内核空间 用户态,运行于用户空间 用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的"进程上下文" ,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。 硬件通过触发信号,导致内核调用中断 处理程序,进入内核空间。这个过程中,硬件的 一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的"中断上下文" ,其实也可以看作是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。 三、参考来源 《奔跑吧linux内核》 https://zhuanlan.zhihu.com/p/52845869 https://www.cnblogs.com/hustcat/articles/1505618.html https://www.cnblogs.com/sparkdev/p/8410350.html