我一开始没注意这个问题,只是通过陷阱门觉得很绕弯子,为何不在3级用户代码里直接调用write_char,今天自己写程序想用call调用代码段,才发现了大问题。我写了类似于write_char的过程,代码如下:
dividing_line: push %gs pushl %ebx pushl %ecx movl $1,%ecx/*输出1个空格‘ ‘*/ movl $SCRN_SEL,%ebx movw %bx,%gs 1: movl scr_loc,%ebx shl $1,%ebx movb $32,%gs:(%ebx)/*‘ ‘空格ASCII码*/ shr $1,%ebx incl %ebx cmpl $2000,%ebx jb 2f movl $0,%ebx 2: movl %ebx,scr_loc loop 1b popl %ecx popl %ebx pop %gs ret
其功能为在屏幕上输出一个空格而已。简直和write_char几乎一样,只不过我指定了要输出的字符,不是通过ax获取罢了。还有一个不同就是我要通过call来调用dividing_line,而不是像write_char一样被陷阱门调用。
结果程序挂了,没法运行。
后来分析了下,原因很简单。我在task1里面直接调用write_char也是不行。这两者原因是相同的,那就是这两行代码:
movl $SCRN_SEL,%ebx movw %bx,%gs
在源代码中SCRN_SEL= 0x18是GDT中显示数据段的selector,甭管他是什么数据段,反正他是数据段就对了,这个数据段描述符DPL=0,属于高特权级,但是我们现在用特权级3级的代码去调用这个过程,那么这个处理过程此时就算作3级程序的一员,即他的特权级也是3级,CPL=3,尽管SCRN_SEL= 0x18中的RPL=0,但是CPL不够级别,这种加载SCRN_SEL= 0x18的请求必将被处理器驳回。处理器在加载selector进段寄存器时会将当前运作程序的CPL、selector的RPL、被指向描述符的DPL做综合比较,除非CPL和RPL在数值上都小于DPL,也就是特权级较高。否则,这种加载selector的行为都将被处理器拦截并产生保护异常。
回到我的程序,所以只要这个加载0x18 这个selector的行为存在,这个过程的调用就必然失败,因为0x18 这个selector所指向的数据段描述符DPL超高。于是乎,在3级程序里直接调用write_char也好,dividing_line也好,都会失败,无权访问此数据段,有意图也不行。
这就是为什么要通过陷阱门去调用write_char,处理器规定通过陷阱门只能向高特权级代码段转移,不得转入低特权级代码段,意思就是我从3级用户代码转入内核0级代码这是天经地义的。但是注意,3级代码的确可以通过陷阱门转入0级代码,但前提是陷阱门的门描述符DPL=3才行。不然用户程序根本没权限去访问陷阱门。好了,我们假设陷阱门描述符DPL=3,陷阱门描述符里面的目标代码selector指向一段内核0级代码。
这样当3级程序去加载陷阱门描述符,完全可行,因为CPL=3,我们再用RPL=3的selector去访问陷阱门,陷阱门DPL=3,这样完全可以访问陷阱门。然后陷阱门里面的目标selector部分指向0级代码,这没有关系,通过陷阱门我们完全可以转过去,处理器就要我们这样做!这才是应该的过程。低级程序访问低级陷阱门,低级陷阱门里有高特权级的代码段selector,这样可以通过陷阱门转入高特权级的内核代码当中去。这为一些系统调用提供了方便。
最后有一点,我们把这两行导致问题的代码去掉,能不能正常调用呢?答案是肯定的,既然把这这两行去了,那么这个处理过程的功能就废了,好不如写个简单点的,比如:
example: pushl %eax movl $0x250,%eax pop %eax ret
这个够简单,在3级用户代码里call example就可以调用了,没有输出提示所以我们可以通过bochs调试来确认,结果就是调用成功,执行过程成功进入此example代码。这就是个普通近调用,没啥特殊的。关于陷阱门特权级我们就说到这里。
说完了。
本文出自 “mirage1993” 博客,请务必保留此出处http://mirage1993.blog.51cto.com/2709744/1576602
Linux0.00内核为什么要自己设置0x80号陷阱门来调用write_char过程?
原文地址:http://mirage1993.blog.51cto.com/2709744/1576602