Linux2.6對新型CPU的支援(2)(轉)
由使用者態經庫函式進入核心態
為了配合核心使用新的系統呼叫方式,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 : push %ebp0x08048205 : mov %esp,%ebp0x08048207 : sub $0x8,%esp0x0804820a : and $0xfffffff0,%esp0x0804820d : mov $0x0,%eax0x08048212 : sub %eax,%esp0x08048214 : call 0x804cb20 <__getuid>0x08048219 : leave0x0804821a : ret
可以看出,main函式中呼叫了__getuid函式,接著反編譯__getuid函式。
(gdb) disassemble 0x804cb200x0804cb20 <__getuid>: push %ebp0x0804cb21 <__getuid>: mov 0x80aa028,%eax0x0804cb26 <__getuid>: mov %esp,%ebp0x0804cb28 <__getuid>: test %eax,%eax0x0804cb2a <__getuid>: jle 0x804cb40 <__getuid>0x0804cb2c <__getuid>: mov $0x18,%eax0x0804cb31 <__getuid>: call *0x80aa0540x0804cb37 <__getuid>: pop %ebp0x0804cb38 <__getuid>: 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 : push %ebp0x08048205 : mov %esp,%ebp0x08048207 : sub $0x8,%esp0x0804820a : and $0xfffffff0,%esp0x0804820d : mov $0x0,%eax0x08048212 : sub %eax,%esp0x08048214 : call 0x80482880x08048219 : leave0x0804821a : 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>: push %ebp0x40219e51 <__getuid>: mov %esp,%ebp0x40219e53 <__getuid>: push %ebx0x40219e54 <__getuid>: call 0x40219e59 <__getuid>0x40219e59 <__getuid>: pop %ebx0x40219e5a <__getuid>: add $0x84b0f,%ebx0x40219e60 <__getuid>: mov 0xffffd87c(%ebx),%eax0x40219e66 <__getuid>: test %eax,%eax0x40219e68 <__getuid>: jle 0x40219e80 <__getuid>0x40219e6a <__getuid>: mov $0x18,%eax0x40219e6f <__getuid>: call *%gs:0x100x40219e76 <__getuid>: pop %ebx0x40219e77 <__getuid>: pop %ebp0x40219e78 <__getuid>: 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 等等。因此,採取靈活的手段,針對不同的系統呼叫採取不同的方式,才能得到最最佳化的效能和實現最完美的功能。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-940031/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- JRockitJVM對AOP的支援,第2部分(轉)JVM
- 檢查CPU是否支援MMX指令的程式碼 (轉)
- Linux2.6 核心的 Initrd 機制解析(1)(轉貼好文)Linux
- Linux2.6 核心的 Initrd 機制解析(4)(轉貼好文)Linux
- spring中對hibernate的支援(轉)Spring
- 巧用Linux2.6核心新功能配置檔案ACL(轉)Linux
- MFC對COM介面編寫的支援分析 (轉)
- LOTO新型號支援串列埠藍芽示波器串列埠藍芽
- 核心新增對yaffs2檔案系統的支援
- 警惕!新型“撒旦”病毒來襲,360率先支援解密解密
- 使用ATL建立支援IClassFactory2的COM元件 (轉)元件
- Visual Basic的類對於物件導向的支援 (轉)物件
- jQuery對Ajax的支援jQuery
- TeXmacs對中文的支援Mac
- CPU對指令長度的判斷
- 檢視sql對cpu 的開銷SQL
- 微軟新型PM職業道路(轉)微軟
- [轉帖]海光CPU
- get CPU id (很全的) (轉)
- dos2unix, unix2dos 對應的命令(轉)
- 支援雙系統挖礦,警惕新型挖礦病毒入侵
- Spring 對JDBC的支援SpringJDBC
- Spring 對Ajax的支援Spring
- 配置nginx對php的支援NginxPHP
- 后羿 - TenSunS v1.1.0 支援對接AWS的EC2、RDS、ElastiCacheAST
- 微軟準備推送:Win10 5月更新對CPU支援方面跟之前沒變化微軟Win10
- 計算cpu速度的小程式 (轉)
- 對軟體專案管理的探討(2)(轉)專案管理
- 我對專案管理的一點看法 2(轉)專案管理
- 微軟將在資料庫軟體中增加對java的支援 (轉)微軟資料庫Java
- JAVA 多使用者商城系統b2b2c-Feign對Hystrix的支援Java
- 昂貴LLM的救星?Nature新研究提出新型憶阻器,比Haswell CPU高效460倍
- 國密2 (sm2)非對稱加密解密工具--支援生成公鑰私鑰對及加密解密加密解密
- Linux對ipsec的支援Linux
- MyBatis對動態SQL的支援MyBatisSQL
- rpa對json的支援JSON
- DBSync新增對MongoDB、ES的支援MongoDB
- bitShark對Android版本的支援Android