分享
 
 
 

Linux2.6对新型CPU的支持(2)

王朝system·作者佚名  2008-05-21
窄屏简体版  字體: |||超大  

由用户态经库函数进入内核态

为了配合内核使用新的系统调用方式,glibc中要做一定的修改。新的glibc-2.3.2(及其以后版本中)中已经包含了这个改动,在glibc源代码的sysdeps/unix/sysv/linux/i386/sysdep.h文件中,处理系统调用的宏INTERNAL_SYSCALL在不同的编译选项下有不同的结果。在打开支持sysenter/sysexit指令的选项I386_USE_SYSENTER下,系统调用会有两种方式,在静态链接(编译时加上-static选项)情况下,采用"call *_dl_sysinfo"指令;在动态链接情况下,采用"call *%gs:0x10"指令。这两种情况由glibc库采用哪种方法链接,实际上最终都相当于调用某个固定地址的代码。下面我们通过一个小小的程序,配合gdb来验证。

首先是一个静态编译的程序,代码很简单:

main(){

getuid();}

将代码加上static选项用gcc静态编译,然后用gdb装载并反编译main函数。[root@test opt]# gcc test.c -o ./static -static[root@test opt]# gdb ./static(gdb) disassemble main0x08048204 <main+0>:

push

%ebp0x08048205 <main+1>:

mov

%esp,%ebp0x08048207 <main+3>:

sub

$0x8,%esp0x0804820a <main+6>:

and

$0xfffffff0,%esp0x0804820d <main+9>:

mov

$0x0,%eax0x08048212 <main+14>:

sub

%eax,%esp0x08048214 <main+16>:

call

0x804cb20 <__getuid>0x08048219 <main+21>:

leave0x0804821a <main+22>:

ret

可以看出,main函数中调用了__getuid函数,接着反编译__getuid函数。(gdb) disassemble 0x804cb200x0804cb20 <__getuid+0>:

push

%ebp0x0804cb21 <__getuid+1>:

mov

0x80aa028,%eax0x0804cb26 <__getuid+6>:

mov

%esp,%ebp0x0804cb28 <__getuid+8>:

test

%eax,%eax0x0804cb2a <__getuid+10>:

jle

0x804cb40 <__getuid+32>0x0804cb2c <__getuid+12>:

mov

$0x18,%eax0x0804cb31 <__getuid+17>:

call

*0x80aa0540x0804cb37 <__getuid+23>:

pop

%ebp0x0804cb38 <__getuid+24>:

ret

上面只是__getuid函数的一部分。可以看到__getuid将eax寄存器赋值为getuid系统调用的功能号0x18然后调用了另一个函数,这个函数的入口在哪里呢?接着查看位于地址0x80aa054的值。

(gdb) X 0x80aa054

0x80aa054 <_dl_sysinfo>:

0x0804d7f6

看起来不像是指向内核映射页面内的代码,但是,可以确认,__dl_sysinfo指针的指向的地址就是0x80aa054。下面,我们试着启动这个程序,然后停在程序第一条语句,再查看这个地方的值。(gdb) b mainBreakpoint 1 at 0x804820a(gdb) rStarting program: /opt/staticBreakpoint 1, 0x0804820a in main ()(gdb) X 0x80aa0540x80aa054 <_dl_sysinfo>:

0xffffe400

可以看到,_dl_sysinfo指针指向的数值已经发生了变化,指向了0xffffe400,如果我们继续运行程序,__getuid函数将会调用地址0xffffe400处的代码。

接下来,我们将上面的代码编译成动态链接的方式,即默认方式,用gdb装载并反编译main函数[root@test opt]# gcc test.c -o ./dynamic[root@test opt]# gdb ./dynamic(gdb) disassemble main0x08048204 <main+0>:

push

%ebp0x08048205 <main+1>:

mov

%esp,%ebp0x08048207 <main+3>:

sub

$0x8,%esp0x0804820a <main+6>:

and

$0xfffffff0,%esp0x0804820d <main+9>:

mov

$0x0,%eax0x08048212 <main+14>:

sub

%eax,%esp0x08048214 <main+16>:

call

0x80482880x08048219 <main+21>:

leave0x0804821a <main+22>:

ret

由于libc库是在程序初始化时才被装载,所以我们先启动程序,并停在main第一条语句,然后反汇编getuid库函数。

(gdb) b mainBreakpoint 1 at 0x804820a(gdb) rStarting program: /opt/dynamicBreakpoint 1, 0x0804820a in main ()(gdb) disassemble getuidDump of assembler code for function getuid:0x40219e50 <__getuid+0>:

push

%ebp0x40219e51 <__getuid+1>:

mov

%esp,%ebp0x40219e53 <__getuid+3>:

push

%ebx0x40219e54 <__getuid+4>:

call

0x40219e59 <__getuid+9>0x40219e59 <__getuid+9>:

pop

%ebx0x40219e5a <__getuid+10>:

add

$0x84b0f,%ebx0x40219e60 <__getuid+16>:

mov

0xffffd87c(%ebx),%eax0x40219e66 <__getuid+22>:

test

%eax,%eax0x40219e68 <__getuid+24>:

jle

0x40219e80 <__getuid+48>0x40219e6a <__getuid+26>:

mov

$0x18,%eax0x40219e6f <__getuid+31>:

call

*%gs:0x100x40219e76 <__getuid+38>:

pop

%ebx0x40219e77 <__getuid+39>:

pop

%ebp0x40219e78 <__getuid+40>:

ret

可以看出,库函数getuid将eax寄存器设置为getuid系统调用的调用号0x18,然后调用%gs:0x10所指向的函数。在gdb中,无法查看非DS段的数据内容,所以无法查看%gs:0x10所保存的实际数值,不过我们可以通过编程的办法,内嵌汇编将%gs:0x10的值赋予某个局部变量来得到这个数值,而这个数值也是0xffffe400,具体代码这里就不再赘述。

由此可见,无论是静态还是动态方式,最终我们都来到了0xffffe400这里的一段代码,这里就是内核为我们映射的系统调用入口代码。在gdb中,我们可以直接反汇编来查看这里的代码

(gdb) disassemble 0xffffe400 0xffffe414Dump of assembler code from 0xffffe400 to 0xffffe414:0xffffe400:

push

%ecx0xffffe401:

push

%edx0xffffe402:

push

%ebp0xffffe403:

mov

%esp,%ebp0xffffe405:

sysenter0xffffe407:

nop0xffffe408:

nop0xffffe409:

nop0xffffe40a:

nop0xffffe40b:

nop0xffffe40c:

nop0xffffe40d:

nop0xffffe40e:

jmp

0xffffe4030xffffe410:

pop

%ebp0xffffe411:

pop

%edx0xffffe412:

pop

%ecx0xffffe413:

retEnd of assembler dump.

这段代码正是arch/i386/kernel/vsyscall-sysenter.S文件中的代码。其中,在sysenter之前的是入口代码,在0xffffe410开始的是内核返回处理代码(后面提到的SYSENTER_RETURN即指向这里)。在入口代码中,首先是保存当前的ecx,edx(由于sysexit指令需要使用这两个寄存器)以及ebp。然后调用sysenter指令,跳转到内核Ring 0代码,也就是sysenter_entry入口处。

内核中的处理和返回

sysenter_entry整个的实现可以参见arch/i386/kernel/entry.S。内核处理SYSENTER的代码和处理INT的代码不太一样。通过sysenter指令进入Ring 0之后,由于当前的ESP并非指向正确的内核栈,而是当前CPU的TSS结构中的一个缓冲区(参见上文),所以首先要解决的是修复ESP,幸运的是,TSS结构中ESP0成员本身就保存有Ring 0状态的ESP值,所以在这里将TSS结构中ESP0的值赋予ESP寄存器。将ESP恢复成指向正确的堆栈之后,由于SYSENTER不是通过调用门进入Ring 0,所以在堆栈中的上下文和使用INT指令的不一样,INT指令进入Ring 0后栈中会保存如下的值。

低地址

返回用户态的EIP

用户态的CS

用户态的EFLAGS

用户态的ESP

用户态的SS(和DS相同)

高地址

因此,为了简化和重用代码,内核会用pushl指令往栈中放入上述各值,值得注意的是,内核在栈中放入的相对应用户态EIP的值,是一个代码标签SYSENTER_RETURN,在vsyscall-sysenter.S可以看到,它就在sysenter指令的后面(在它们之间,有一段NOP,是内核返回出错时的处理代码)。接下来,处理系统调用的代码就和中断方式的处理代码一模一样了,内核保存所有的寄存器,然后系统调用表找到对应系统调用的入口,完成调用。最后,内核从栈中取出前面存入的用户态的EIP和ESP,存入edx和ecx寄存器,调用SYSEXIT指令返回用户态。返回用户态之后,从栈中取出ESP,edx,ecx,最终返回glibc库。

其它操作系统以及其它硬件平台的支持

值得一提的是,从 Windows XP 开始,Windows 的系统调用方式也从软中断 int 0x2e 转换到采用 sysenter 方式,由于完全不再支持 int 方式,因此 Windows XP 的对 CPU 的最低配置要求是 PentiumII 300MHz。在其它的操作系统例如 *BSD 系列,目前并没有提供对 sysenter 指令的支持。

在 CPU 方面,AMD 的 CPU 支持一套与之对应的指令 SYSCALL/SYSRET。在纯 32 位的 AMD CPU 上,还没有支持 sysenter 指令,而在 AMD 推出的 AMD64 系列 CPU 上,处于某些模式的情况下,CPU 能够支持 sysenter/sysexit 指令。在 Linux 内核针对 AMD64 架构的代码中,采用的还是 SYSCALL/SYSRET 指令。至于这两种指令最终谁将成为标准,目前还无法得出结论。

未来

我们将 Intel 的 sysenter/sysexit 指令,AMD 的 SYSCALL/SYSRET 指令统称为"快速系统调用指令"。"快速系统调用指令"比起中断指令来说,其消耗时间必然会少一些,但是随着 CPU 设计的发展,将来应该不会再出现类似 Intel Pentium4 这样悬殊的差距。而"快速系统调用指令"比起中断方式的系统调用方式,还存在一定局限,例如无法在一个系统调用处理过程中再通过"快速系统调用指令"调用别的系统调用。因此,并不一定每个系统调用都需要通过"快速系统调用指令"来实现。比如,对于复杂的系统调用例如 fork,两种系统调用方式的时间差和系统调用本身运行消耗的时间来比,可以忽略不计,此处采取"快速系统调用指令"方式没有什么必要。而真正应该使用"快速系统调用指令"方式的,是那些本身运行时间很短,对时间精确性要求高的系统调用,例如 getuid、gettimeofday 等等。因此,采取灵活的手段,针对不同的系统调用采取不同的方式,才能得到最优化的性能和实现最完美的功能。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有