注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

FY

Johnson 's Blog

 
 
 

日志

 
 

[原创]LINUX0.12笔记  

2016-10-05 12:59:16|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

 

所谓内核态,用户态,无非就是一个运行时的特权级。再本质来看,无非就是当前运行时,CS寄器的CPL值。是0代表最高特权级,我们通常叫为内核态,是因为一般我们linux内核代码一般都要求在这个特权级0下为运行。一方面是为了安全和方便。安全是指把内核代码指明只有特权级0才能运行,这样用户态(特权级为3)的就无法运行;方便是指运行在特权级0的代码可以访问任何数据段(=0,1,2,3特权级的数据)。另一方面是80386有些新寄存器比如构成保护模式很重要的GDT,LDT,IDT等寄存器,intel硬件规定只有特权级=0的代码才能加载数据到这些寄存器。(也确实应该如此,否则一个用户程序随便就能修改GDTLDTIDT的话。。。)。那么怎样才能修改CS寄存器的值(也就是改变当前特权级)呢?

首先,CPU不允许通过MOV这样的指令来直接修改CSCS的加载都是CPU隐形做的。比如进行call指令/IRET指令。并且CS的加载只有符合特权级检查后才能被加载。CS寄存器中不仅有特权级,也有代码段选择符。

用于CPU逻辑控制转移指令有:(CALL/JMP,RET(INT,IRET)两大类。

CALL/JMP 可以作用于一致性/非一致性代码段,调用门,任务门,任务TSS

INT可以作用于中断门,陷阱门,任务门。

这两类指令都会隐含的让CPU改变CS寄存器,

CALL/JMP只能让特权级往高处改变,例如特权级2的情况下使用CALL只能往特权级210改变,不能往特权级3改变。

RET只能让特权级往低处改变,例如特权级2的情况下使用RET只能往特权级2,3改变,不能往10改变。

INT指令发生时,要根据中断门描述符中的DPL和现在程序的CPL进行比较

只有CPL <= 门描述符中的DPL时,才能将中断门描述符中保存的段选择符加载到CS寄存器中去。IRET指令正好反过来,才只能让特权级从高到低改变。

所以一个用户进程使用INT触发中断,从而进入特权级0运行中断处理,结束后再调用IRET从特权级0退回到用户态(特权级3)

具体的这些特权级来回变更的检查,可以见intel编程手册。

 

Linux0.12main()函数中,本来使用GDT([1]:代码段[2]:/数据)运行在特权级0的内核态,通过move_to_user_mode(本质是一个IRET)指令,改变CS,使用特权级为3.并且使用LDT表,从而把它看作处在用户态的一个任务:任务0。只是由于这个任务0LDT,它的代码段/数据段描述符中,段基址都是0x0000,段限长640KB,可以说和之前的GDT的段描述符基本一样,估计和LDT唯一区别就是段描述符DPL了。GDT代码段/数据段描述符中DPL=0,表示只有特权级=0的代码才能来加载我GDT中的代码段/数据段。而LDT代码段/数据段描述符中DPL=3,表示用户态。于是在任务0前运行时,特权级为0move_to_user_mode后特权级为3,但代码还是继续执行下去,还是继续接着move_to_user_mode前的代码流往下走。都还是处在原先的那一处代码。唯一惊变的只是CS特权级的变化

GDTLDT指向同样的线性空间,并且又由于前后都使用同样的CR3表,所以前后的物理地址都是没有改变的。代码就像什么也没发生一样继续往下走,只是特权级改变了而已。这就是内核态,用户态,就这么简单。

但平常我们说到LINUX内核态/用户态,都会下意识到进入内核态的话,代码执行流就会真正走到内核代码,返回用户态的话,代码执行流会走到用户程序的代码。是的,这也没错。我们要注意到,上面任务0的做法,它在切换特权级时,使用的段选择符也是GDT/LDT间走,但是不要忘了,切换前后的段描述符中记录的段基地是一样的,并且使用分页的页表内容是一样的,这才造成了任务0前后就像代码流没有改变,仅仅是CS的特权级改变了一下而已的感觉。

而平常LINUX中,当然是实实在在的在切换到内核态特权级时,保证了新的段描述符->新段基址->新页表映射 ,最后让你感觉到代码执行流真正是切换到内核所在的代码中去了。

 

在来说一下内核数据的保护,比如GDT表,LDT表,分页的页表,各进程结构等数据,但把它们归到内核中,希望用户进程访问不到。在没有分页机制前,CPU的分段机制也确实可以做到。内核的数据可以在GDT中映射到某段线性空间,而其他用户进程让它们使用LDT且只要保证被映射到和内核数据不在一个线性空间,就可以达到用户进程访问不了内核数据。因为不开启PG下线性地址等于物理地址。但是不要忘了,后面有分页机制。你甚至可以在分页机制中再把上面不相等的线性地址再映射回相同的物理地址。这样一来,用户进程运行在用户态就像正常的访问自己的数据(其实也确实是自己的,因为它使用自己的LDT)一样。

Linux0.12中的任务0和任务1就是这样。这2个是内核开发者自己写的“用户进程”,这2个进程最后实际访问的物理空间和内核数据重叠。

内核数据在物理地址0-1MB间,其中分页页表数据在地址0开始处。而任务0使用LDT,它运行在用户态,它的数据段/代码段在物理地址0-640KB,任务1也是一样(至少在任务0 中刚fork)。所以任务0,任务1可以干“坏事来破坏页表数据之类的,但可惜,这2个进程是内核开发者写的,他们不会干那事。

只是想说,分段机制让数据代码隔离起来只能管到线性地址,它管不了物理地址,而分页机制是将线性地址转换成物理地址,这过程中就有可能将两个不同的线性地址空间再映射到同一个物理空间中去,所以只有分段机制和分页机制联合有效合理的配置,才能起到真正的隔离保护作用。当然我们也知道2个进程间共享数据时可以使用最后分页机制让它们映射到一块。

  评论这张
 
阅读(4)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017