我们知道内核管理物理内存,其实除了管理本身内存外,还必须管理用户空间中进程的内存,我们称这个内存为进程地址空间,也就是系统中每个用户空间进程所看到的内存。
传统的C语言编译出来的进程地址空间包含哪些对象呢?可以参照ELF文件格式对应看:
-->可执行文件代码的内存映射,称为代码段。【ELF中的代码段】
-->可执行文件的已初始化的全局变量的内存映射,成为数据段。【ELF中的数据段】
-->未初始化的全局变量,也就是bss段的零页。由于未初始化的变量没有对应的值,所以不需要放在可执行对象中,但是C标准规定了所有未初始化全局变量要被赋予特殊的值,所以内核需要将变量从可执行代码载入到内存中,然后把零页映射到该片内存上。【ELF中的bss段】
所以全局变量是否初始化影响该变量在可执行文件中的位置,初始化的全局变量在数据段,未初始化的全局变量在bss段且默认置0。
另外,静态变量也就是static变量,和全局变量在内存中的处理相同,即初始化的static变量放在数据段,未初始化的static变量放在bss段,static变量和全局变量的不同在于语言层面上的作用域不同。
-->用于进程用户空间栈的零页内存映射(不同于内核进程栈)。堆栈段是进程运行时使用,所以在可执行文件中不需要实际分配。【动态分配】
-->每一个诸如c库或者动态链接库等共享库的代码段,数据段和bss段也会被载入进程的地址空间。当然静态库会编译到可执行文件中,而动态的共享库需要运行时载入。【动态分配】
-->任何内存映射文件,任何共享内存段,任何匿名的内存映射,比如malloc分配的内存。【动态分配】
每个用户进程看到的内存或多或少都是上面的几部分,但是这些逻辑地址是怎么和CPU交互,CPU能够理解这些逻辑地址么?假设需要寻址一个全局变量,CPU可以通过段中的偏移量找到实际的物理地址么?需要内核参与么?
地址的分类:
逻辑地址:包含在机器语言中用来指定一个操作数或者一条指令的地址。每一个逻辑地址由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。
所以,逻辑地址一般是表示在可执行文件中机器语言所使用的地址,语境一般在可执行文件的机器指令中。内核一般不使用逻辑地址这个概念。
线性地址(或叫虚拟地址):是否用于描述内核中的地址。
物理地址:用于内存芯片级内存单元寻址。
CPU中有一个用于将逻辑地址直接映射为物理地址的控制线路,它叫MMU。内存控制单元(MMU)通过一种分段单元的硬件电路把一个逻辑地址转换成线性地址,接着,通过一个分页单元的硬件电路将线性地址转换为一个物理地址。就是说,一个hello可执行文件的机器指令,在使用比如一个定义的全局变量时,可以不通过内核,结合当前进程页表,通过MMU将全局变量的逻辑地址转换为实际的物理地址。
以用户进程的的角度来看逻辑地址的转换,比如一条机器指令想获取一个全局变量值g_aaa,也就是说是数据段内的一个偏移地址。
当进程启动后,CS代码段寄存器被赋予代码段值,和ELF中描述的偏移量配合起来,我们知道g_aaa的逻辑地址。MMU的分段硬件查找段描述符,将逻辑地址转换为虚拟地址或者叫线性地址。接着MMU的分页硬件电路根据当前进程的页目录和页表,再配合全局page表,就得到了实际的物理地址。换一种角度说,一个规整的段页地址空间,不论内核对虚拟地址的实际划分有多混乱,都不需要与内核的交互,就可以通过CPU内置部件之间查到物理地址。
这说明,进程运行过程中,机器指令可以直接使用逻辑地址的,不需要内核的帮助。只有在进程发生切换时,内核才需要保存好上下文信息,等到进程再次切回来后,恢复段寄存器信息,恢复段描述符信息,以及恢复此进程的页表信息。
总结回答一下上面的问题:
但是这些逻辑地址是怎么和CPU交互?
CPU获取对应体系结构的机器码,要么机器码是指令要么是数据。当不需要寻址时,CPU顺序一条一条的执行指令,CPU有内部流水线,会将IP寄存器指针加一,而顺序执行指令。当需要寻址数据时,通过段页寄存器就可以找到数据段内容。
CPU能够理解这些逻辑地址么?假设需要寻址一个全局变量,CPU可以通过段中的偏移量找到实际的物理地址么?
CPU有MMU单元,借助进程的段页表就可以看懂逻辑地址。
需要内核参与么?
内核在load程序时,做好段页表即可。