您的位置:首页 > 运维架构 > Linux

Linux内核空间与用户空间的实现

2017-04-16 19:30 127 查看
       实现用户内核空间和用户空间的分离是现代操作系统的必备能力。开始接触Linux时就就困惑于内核空间和用户空间的分离是如何实现的。查阅过一些材料,但很多都只是罗列了一些概念和一些简单的关系,并不能让我对这个问题有一个清晰的理解。后来在公司前架构师的建议下开始阅读《Linux内核情景分析》这本书,这里把学到的东西做一个小小的总结,做一个备忘。

       内核很多功能的实现要依赖于硬件。对于我们要讨论的这个问题,一个重要的依赖就是cpu的页式内存管理机制。其实X86 cpu提供了段式管理+页是管理的功能,但是对于Linux内核来说,并没有实际运用段式管理,所以Linux内核的内存管理本质上说只有页式管理。这也就导致了Linux系统中虚拟地址和线性地址是一致的,可以不做区分。

       为实现页式管理,cpu配备了一个专用寄存器CR3,用来存放当前进程的页目录的物理地址。当拿到虚拟地址需要访问内存时,cpu就可以从CR3寄存器找到当前进程的页目录的物理地址,进而找到相应的页表和页表项,从而查询到该虚拟地址对应的真实的物理地址。由于页目录和页表是由内核设置的,而实际的地址转换是由硬件完成的,所以对于普通进程来说,他就无法获知某一虚拟地址所对应的物理地址,它对物理地址的访问都在内核的控制之中,这也就实现了内核空间和用户空间的分离。

       有一个问题需要说明一下,CR3寄存器中的值是哪里来的。CR3寄存器中的值是在进程切换时由内核设置好的。内核是怎样知道该进程页目录的的物理地址的呢?这是因为所有进程的页目录都在内核空间并由内核设置,而内核空间的虚拟地址和物理地址的映射关系内核是知道的,实际上是一个非常简单的线性转换。当内核挑选出下一个要执行的进程,就可以将其页目录的地址放入CR3。

       上面说的内核空间和用户空间是如何分离,下面说一下内核空间和用户空间是如何打通的,也就是内核空间和用户空间的切换。两个完全隔绝的东西对我们来说是没有意义的,如果一个进程只能运行于用户空间,那它的功能也势必是非常有限的。

      大家都知道,一个用户空间的进程可以通过系统调用进入内核空间,使用内核为其提供的服务。这个过程涉及到两个重要的切换,一个是cpu运行级别的切换,一个是系统堆栈的切换。

      首先说cpu运行级别的切换。我们知道x86 cpu有四个运行级别0,1,2,3,内核使用级别0,而用户空间的进程使用级别3。实际上,只要想切换cpu运行的优先级别,就要通过一道”门“,当进程通过int $0x80指令来到陷阱门时(中断门和陷阱门区别只在是否允许嵌套中断),cpu会两次检查cpu的运行级别。首先是准入级别的检查,为系统调用设置的陷阱门的准入级别是3,所以是肯定可以通过的。穿过陷阱门之后,还要将目标代码段描述项(内核空间)中的优先级别(DPL)和当前cpu的运行级别(CPL)比较。DPL必须小于等于CPL,也就是说只允许提高cpu运行级别。

       cpu运行级别的变化也会引起堆栈的变更。Linux系统中,用户空间的每个线程在内核空间都有对应的内核空间堆栈,与其tast_struct结构体相临存放。当cpu运行级别切换时,cpu会根据TR寄存器的内容找到TSS结构。TSS结构存存储了当前进程的内核空间堆栈的物理地址,cpu会从中找出该地址并装入堆栈段寄存器SS和堆栈指针寄存器ESP,从而完成堆栈的切换。

      TSS结构中的内容同样是在进程切换时内核设置的。值得一提的是Intel的设计意图是让每个进程都有自己的TSS结构,但Linux在实现中只使用一个,只是在进程切换是改变其内容。

     从上面也可以看出,系统的很多功能也是内核与cpu相互配合实现的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: