vsyscall


Rev.1を表示中。最新版はこちら

86ではシステムコールとしてint80とペンティアムで実装されているsysenterとがあるのですが、glibcでは直接int80ないしsysenterとしてコード展開していません。glibcは__kernel_vsyscallとしてリンクしています。

カーネルは立ち上がり時、ラベルを__kernel_vsyscallとして、int80ないしsysenterで、システムコールするモジュールをカーネルのページに読み込み、ユーザプロセスを起動する時に、このページのモジュールをプロセスにリンクすることで、int80ないしsysenterに透過的な動作を実現しています。

start_kernel()関数からcheck_bugs()関数が呼ばれ、そこからidentify_boot_cpu()そして、sysenter_setup()とコールされます。ここでシステムコールのモジュールがページに読み込まれます。

get_zeroed_page関数で取得したページのリニアアドレスを、vdso32_pagesにセットします。syscallのCPUなら、vsyscallにsyscallにそのモジュールのアドレスを、vsyscall_lenにそのサイズを設定します。同様にsysenterならsysenterのモジュールを、どちらでもないなら、int80のモジュールを設定しています。

そして、memcpy関数でvdso32_pagesに複写し、relocate_vdso関数でモジュールをリロケーションしています。
int __init sysenter_setup(void)
{
       void *syscall_page = (void *)get_zeroed_page(GFP_ATOMIC);
       const void *vsyscall;
       size_t vsyscall_len;

       vdso32_pages[0] = virt_to_page(syscall_page);

#ifdef CONFIG_X86_32
       gate_vma_init();
#endif

       if (vdso32_syscall()) {
               vsyscall = &vdso32_syscall_start;
               vsyscall_len = &vdso32_syscall_end - &vdso32_syscall_start;
       } else if (vdso32_sysenter()){
               vsyscall = &vdso32_sysenter_start;
               vsyscall_len = &vdso32_sysenter_end - &vdso32_sysenter_start;
       } else {
               vsyscall = &vdso32_int80_start;
               vsyscall_len = &vdso32_int80_end - &vdso32_int80_start;
       }

       memcpy(syscall_page, vsyscall, vsyscall_len);
       relocate_vdso(syscall_page);

       return 0;
}
root/arch/x86/vdso/vdso32.S
__INITDATA

       .globl vdso32_int80_start, vdso32_int80_end
vdso32_int80_start:
       .incbin "arch/x86/vdso/vdso32-int80.so"
vdso32_int80_end:

       .globl vdso32_syscall_start, vdso32_syscall_end
vdso32_syscall_start:
#ifdef CONFIG_COMPAT
       .incbin "arch/x86/vdso/vdso32-syscall.so"
#endif
vdso32_syscall_end:

       .globl vdso32_sysenter_start, vdso32_sysenter_end
vdso32_sysenter_start:
       .incbin "arch/x86/vdso/vdso32-sysenter.so"
vdso32_sysenter_end:
__FINIT
syscallはAMDプロセッサーで実装されているsysenter相当のコマンドだそうです。

root/arch/x86/vdso/vdso32/syscall.S
       .globl __kernel_vsyscall
       .type __kernel_vsyscall,@function
       ALIGN
__kernel_vsyscall:
.LSTART_vsyscall:
       push    %ebp
.Lpush_ebp:
       movl    %ecx, %ebp
       syscall
       movl    $__USER32_DS, %ecx
       movl    %ecx, %ss
       movl    %ebp, %ecx
       popl    %ebp
.Lpop_ebp:
       ret
root/arch/x86/vdso/vdso32/sysenter.S
       .text
       .globl __kernel_vsyscall
       .type __kernel_vsyscall,@function
       ALIGN
__kernel_vsyscall:
.LSTART_vsyscall:
       push %ecx
.Lpush_ecx:
       push %edx
.Lpush_edx:
       push %ebp
.Lenter_kernel:
       movl %esp,%ebp
       sysenter

       /* 7: align return point with nop's to make disassembly easier */
       .space 7,0x90

       /* 14: System call restart point is here! (SYSENTER_RETURN-2) */
       jmp .Lenter_kernel
       /* 16: System call normal return point is here! */
VDSO32_SYSENTER_RETURN: /* Symbol used by sysenter.c via vdso32-syms.h */
       pop %ebp
.Lpop_ebp:
       pop %edx
.Lpop_edx:
       pop %ecx
.Lpop_ecx:
       ret
root/arch/x86/vdso/vdso32/int80.S
       .text
       .globl __kernel_vsyscall
       .type __kernel_vsyscall,@function
       ALIGN
__kernel_vsyscall:
.LSTART_vsyscall:
       int $0x80
       ret
プロセスがelfフォーマットで起動される時、load_elf_binary関数で、そのプロセスの動作環境が整えられます。そして、そこからarch_setup_additional_pages関数がコールされ、VDSO(vertual dinamic share object)としての、vsyscallの処理がなされます。

vdso_enabledの値によって、処理が異なるようです。vdso_enabledはsysctrlのvdso値で設定できるようです。とりあえず、vdso_enabledがVDSO_COMPATなら、vsyscallのアドレスはVDSO_HIGH_BASEとなり、そうでないならプロセスの空いているエリアから取得するようです。

そしてそのアドレスでもって、vdso32_pagesをプロセス内にマッピングしているようですが・・・
enum {
       VDSO_DISABLED = 0,
       VDSO_ENABLED = 1,
       VDSO_COMPAT = 2,
};

int arch_setup_additional_pages(struct linux_binprm *bprm, int exstack)
{
       struct mm_struct *mm = current->mm;
       unsigned long addr;
       int ret = 0;
       bool compat;

       if (vdso_enabled == VDSO_DISABLED)
               return 0;

       down_write(&mm->mmap_sem);
       compat = (vdso_enabled == VDSO_COMPAT);

       map_compat_vdso(compat);

       if (compat)
               addr = VDSO_HIGH_BASE;
       else {
               addr = get_unmapped_area_prot(NULL, 0, PAGE_SIZE, 0, 0, 1);
               if (IS_ERR_VALUE(addr)) {
                       ret = addr;
                       goto up_fail;
               }
       }

       if (compat_uses_vma || !compat) {
              ret = install_special_mapping(mm, addr, PAGE_SIZE,
                                             VM_READ|VM_EXEC|
                                             VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC|
                                             VM_ALWAYSDUMP,
                                             vdso32_pages);

               if (ret)
                       goto up_fail;
       }

       current->mm->context.vdso = (void *)addr;
       current_thread_info()->sysenter_return =
               VDSO32_SYMBOL(addr, SYSENTER_RETURN);

  up_fail:
       up_write(&mm->mmap_sem);

       return ret;
}

補足

上記処理を、sysctrlでvdso値を2として、vdsoのマップアドレスがどのように変化するか検証してみましたが、思うような結果が得られませんでした。


最終更新 2010/12/17 16:21:39 - north
(2010/12/17 16:21:39 作成)


検索

アクセス数
2728976
最近のコメント
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
Adsense
広告情報が設定されていません。