今天聊聊进程地址空间这点小事。
说到进程的地址空间,大家可能都知道这样一张图:
这张图就是Linux程序运行起来后所谓的进程地址空间,这里包括我们熟悉的代码区、数据区、以及堆区和栈区,今天我们不讲解这些区域,而是重点关注这个地址空间中最上面的一块区域——内核,这里的问题是:为什么进程地址空间中要包括操作系统(内核)呢?
要想知道这个问题的答案,你需要知道操作系统到底是如何管理内存的。
现代操作系统大都利用虚拟内存系统来管理内存,我们看的上图,这一段连续的内存区域其实只是一个假象,物理内存中并不一定真的存在这样一个内存布局,利用虚拟内存系统将一些非连续的内存块(页)映射到一段连续的地址空间——也就是我们看到的上图,这就是所谓的虚拟内存。
我们看到的地址都是虚拟地址,物理内存与虚拟内存的映射关系维护在页表中,当CPU执行机器指令时需要根据页表将虚拟地址转为物理内存地址,但这个过程对程序员来说是透明的,我们看不到这样的一个转换过程。
那么为什么内核要将自己映射到进程的地址空间呢?
我们知道CPU在执行指令时是有权限状态的,x86处理器有4个权限状态,操作系统一般使用其中两个,这就是所谓的用户态与内核态,我们写的程序运行在用户态,操作系统运行在内核态。
在一些场景下,像我们读写文件、收发网络数据等都需要操作系统的帮助,也就是调用操作系统提供的服务,这个过程就是所谓的系统调用,关于系统调用我们在之前的文章中已经多次讲解过了,在系统调用这个场景下就涉及从用户态切换到内核态。
此外还有其它场景涉及用户态与内核态的切换,像中断处理以及异常处理等。
既然你知道我们的程序运行时需要频繁的进行用户态与内核态切换那么剩下的就简单了。
如果内核与用户态程序位于不同的地址空间,那么当用户态与内核态进行切换时就势必涉及页表的切换——从用户态进入内核态需要将用户进程的页表切换为内核页表,而如果从内核态退出回到用户态就涉及将内核页表切换为用户进程页表,切换页表对于计算机系统来说算是一个不小的开销。
而如果内核与用户态程序位于同一个地址空间中,那么上述页表切换的开销就可以避免了,这就是为什么内核要将自己映射到进程地址空间的重要原因。
好啦,这篇就到这里,希望对大家理解进程地址空间有所帮助。