地址空间划分(一)

研究过 32 位 Linux 内核的人都知道这个内核著名的 3G/1G 划分:低 3G 作为用户态空间,高 1G 作为内核态空间。内核态和用户态共享 4G 的 32 位地址空间。

这个划分成了 32 位内核的基本设计方式。Windows 内核也采用类似的划分(缺省为 2G/2G 划分,可以通过启动参数改为 3G/1G)。所以,可能很多人像我一样,很自然的把这种划分当成了内核的必要设计:内核态和用户态必须共处在同一个地址空间里;似乎如此内核才能管理用户态的内存。见到 Mac OS X 内核 XNU 的设计颠覆了我的观点。XNU 的内核态独占 4G 内核空间,而非与用户态共享。(如下左图显示。)

如果不是因为一个采用如此设计的内核就摆在面前(而且已经在上面工作了三年),我几乎不会想到内核的地址空间还能如此设计。看到这个设计的同时也豁然开朗:一开始认为『内核态和用户态必须共处在同一个地址空间』才能让内核『管理用户态的内存』的想法实在是短路。内核并不能了解用户态如何使用内存,更重要的是,内核并不能信任用户态内存的内容。所以,它根本不能通过和用户态『共处在同一个地址空间』这种方式来管理后者的内存空间。因为『共处在同一个地址空间』提供的唯一功能只是可以直接通过线性地址访问(也就是俗称的裸指针),而内核绝对不能碰用户态的裸指针。内核管理用户态内存的方式只能是通过设置页表和页目录来保证用户态的内存访问的合法性和一些特殊映射。

再者,Linux 和 Windows 下与内核共享地址空间的用户态只能是当前运行的进程。如果是通过系统调用进入内核,还多多少少可以说这时的当前进程和内核的关系比其它进程密切一些。如果是通过中断(时钟或者I/O)进入的内核,这时哪个进程是当前进程对内核来说完全是巧合。更何况无论如何内核也不可能只管理当前进程的内存空间。所以从这个角度说共享地址空间对内核管理用户态内存也没有帮助。

退回到 Linux 和 Windows 的设计,为什么还要让内核与用户态共享地址空间呢。其实是因为尽管每次进入内核态都可能发生进程切换,但是大多数情况下并非一定发生这样的切换。因此,共享地址空间可以避免进出内核态的时候进行地址空间(cr3)的切换。在 x86 构架下,切换地址空间要导致所有 TLB 失效,CPU 必须访问主存更新 TLB。所以,共享地址空间是一个性能 hack,仅此而已。这个性能 hack 如此历史悠久,以至于有些人如我一般完全无法想像还能有其它方式。

不过,XNU 的内核地址空间也并非和用户态空间绝对分离。在每个用户态地址空间的最高几兆也被保留为内核空间(如上右图所示)。这是因为从用户态切换到内核态(通过软中断或者比较新的 x86 syscall 指令)时,并不能同时切换地址空间,所以必须保留这段短短的共享空间来完成地址空间的切换。其实,我们可以把这个设计想像成一个处于 micro-kernel 和 monolithic kernel 之间的中间设计。和 micro-kernel 类似,内核也享有独立空间,但是和 monolithic kernel 类似,整个内核都是处在特权级,没有从微内核到内核服务的二次切换。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s


%d 博主赞过: