Linux操作系统,为什么需要内核空间和用户空间?

作者:sparkdev
来源:
本文以 32 位系统为例介绍内核空间(kernel space)和用户空间(user space) 。
内核空间和用户空间对 32 位操作系统而言 , 它的寻址空间(虚拟地址空间 , 或叫线性地址空间)为 4G(2的32次方) 。 也就是说一个进程的最大地址空间为 4G 。
操作系统的核心是内核(kernel) , 它独立于普通的应用程序 , 可以访问受保护的内存空间 , 也有访问底层硬件设备的所有权限 。 为了保证内核的安全 , 现在的操作系统一般都强制用户进程不能直接操作内核 。
具体的实现方式基本都是由操作系统将虚拟地址空间划分为两部分 , 一部分为内核空间 , 另一部分为用户空间 。 针对 Linux 操作系统而言 , 最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用 , 称为内核空间 。 而较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用 , 称为用户空间 。
对上面这段内容我们可以这样理解:「每个进程的 4G 地址空间中 , 最高 1G 都是一样的 , 即内核空间 。 只有剩余的 3G 才归进程自己使用 。 」
「换句话说就是 ,最高 1G 的内核空间是被所有进程共享的!」下图描述了每个进程 4G 地址空间的分配情况(此图来自互联网):
Linux操作系统,为什么需要内核空间和用户空间?文章插图
为什么需要区分内核空间与用户空间在 CPU 的所有指令中 , 有些指令是非常危险的 , 如果错用 , 将导致系统崩溃 , 比如清内存、设置时钟等 。 如果允许所有的程序都可以使用这些指令 , 那么系统崩溃的概率将大大增加 。
所以 , CPU 将指令分为特权指令和非特权指令 , 对于那些危险的指令 , 只允许操作系统及其相关模块使用 , 普通应用程序只能使用那些不会造成灾难的指令 。
比如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3 。 其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是一样的) 。
当进程运行在 Ring3 级别时被称为运行在用户态 , 而运行在 Ring0 级别时被称为运行在内核态 。
内核态与用户态好了我们现在需要再解释一下什么是内核态、用户态:「当进程运行在内核空间时就处于内核态 , 而进程运行在用户空间时则处于用户态 。 」
在内核态下 , 进程运行在内核地址空间中 , 此时 CPU 可以执行任何指令 。 运行的代码也不受任何的限制 , 可以自由地访问任何有效地址 , 也可以直接进行端口的访问 。
在用户态下 , 进程运行在用户地址空间中 , 被执行的代码要受到 CPU 的诸多检查 , 它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址 , 且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问 。
对于以前的 DOS 操作系统来说 , 是没有内核空间、用户空间以及内核态、用户态这些概念的 。 可以认为所有的代码都是运行在内核态的 , 因而用户编写的应用程序代码可以很容易的让操作系统崩溃掉 。
对于 Linux 来说 , 通过区分内核空间和用户空间的设计 , 隔离了操作系统代码(操作系统的代码要比应用程序的代码健壮很多)与应用程序代码 。
即便是单个应用程序出现错误也不会影响到操作系统的稳定性 , 这样其它的程序还可以正常的运行(Linux 可是个多任务系统啊!) 。
「所以 , 区分内核空间和用户空间本质上是要提高操作系统的稳定性及可用性 。 」
如何从用户空间进入内核空间其实所有的系统资源管理都是在内核空间中完成的 。 比如读写磁盘文件 , 分配回收内存 , 从网络接口读写数据等等 。
我们的应用程序是无法直接进行这样的操作的 。 但是我们可以通过内核提供的接口来完成这样的任务 。
比如应用程序要读取磁盘上的一个文件 , 它可以向内核发起一个 "系统调用" 告诉内核:"我要读取磁盘上的某某文件" 。