sysenter/sysexit

sysenter/sysexit 比傳統 int 0x80 的系統呼叫有更快的執行速度。原因在傳統的 int 中,user space application 的 CS/EIP/SP…等返回時需要用到的資料都被存放在堆疊(記憶體)中,而自 Intel Pentiun II 之後就支援的 CPU 指令 sysenter/sysexit 則是將這些資料放在 model-specific registers (MSRs) 中。放在 register 要存取當然比在記憶體中快多了。


另一個會造成 sysenter/sysexit 比較快的原因是它在 CPU pre-defined state 之間切換,它大大降低因為 long jump 而需要的 privilege level check。


sysenter 可以在 privilege 0~3 呼叫,但 sysexit 只能在 privilege 0 呼叫。


以 Intel 架構來說,呼叫 sysenter 時用到以下幾個 MSR:


IA32_SYSENTER_CS:目標的 code segment
IA32_SYSENTER_EIP:目標的 instruction
IA32_SYSENTER_ESP:堆疊指標


如果是 AMD 架構,則它們使用了 SYSENTER_CS (address 0x174)/SYSENTER_ESP (address 0x175)/SYSENTER_EIP (address 0x176) 等幾個 MSR。


至於參數傳遞方面和傳統 int 一樣都是使用 EBX/ECX/EDX/ESI/EDI/EBP 這六個 register。


以 Linux 來說,當我們呼叫一個無參數的 system call(比如:fork)時,傳統 int 實際上是呼叫了 _syscall0 這個巨集函式。而 sysenter 方式則是呼叫了 sysenter_0 這個巨集函式。它大概長這樣:


#define sysenter_0(n)   ({ \
  int ret;     \
    asm volatile ("    \
 pushl %%ebp    \n\
 movl %%esp,%%ebp   \n\
 call .L__sysenter   \n\
 popl %%ebp"    \
 : "=a"(ret)    \
 : "a"(n)    \
 : "ecx","edx");    \
    ((unsigned int)ret<=0xfffff000) ? ret : \
       (__set_errno(-ret), ret=-1); })


將使用者堆疊位置存下來後,隨即呼叫 .L__sysenter,這是一個函式:


asm ("
.text
.align 8
.L__sysenter:
 sysenter
 hlt
.align 8
");


它呼叫 sysenter 之後便將 process halt 住。而在 kernel 中負責處理 system call 的函式為 sysenter_handler,它會將 system call 的號碼放在 ECX:


movl 20(%%esp),%%ecx # pull syscall nr.
...
cmpl %0,%%ecx # NR_syscall
jae .Lbad_sys
...
call %%ss:*(%%eax)


檢查完 ECX 內 system call 號碼無誤後,便會直接呼叫位於 SS 段中由 EAX 所保存的位址,就完成了一個系統呼叫。最後當然還要用 sysexit 來返回 user space。


留言

熱門文章