vsyscall


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の処理がなされます。

追記

arch_setup_additional_pages()でVDSOのマップが行われる。vdso_enabled = VDSO_DISABLED(0)なら、マップされない。vdso_enabled = VDSO_ENABLED(1)なら、ユーザ空間の先頭から空いている1ページ分を取得する。従って毎回アドレスは異なる。vdso_enabled = VDSO_COMPAT (2)なら、VDSO_HIGH_BASE(0xffffe000)とする。

install_special_mapping()で、VDSO空間をcurrent->mm下に登録する。VDSO_ENABLEDならそもそもユーザ空間ということもあり、必ず登録されるが、VDSO_COMPATの場合、compat_uses_vmaは、以下のように定義されていて、CONFIG_X86_64の時、current->mm下に登録される。・
#ifdef CONFIG_X86_64
  :
#define compat_uses_vma         1
  :
#else  /* CONFIG_X86_32 */
  :
#define compat_uses_vma         0
  :
#endif

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;
}
従ってシステムコールの実装は、以下の3通りの方法で実行される。

glibから直接コールする。(たぶん)
[root@localhost kitamura]# echo 0 > /proc/sys/vm/vdso_enabled
[root@localhost kitamura]# ldd a.out
       libc.so.6 => /lib/libc.so.6 (0x4ea51000)
       /lib/ld-linux.so.2 (0x4ea28000)

0xb7738000のlinux-gate.so.1からコールする。これはユーザ空間に割り当てられたVDSOである。
[root@localhost kitamura]# echo 1 > /proc/sys/vm/vdso_enabled
[root@localhost kitamura]# ldd a.out
       linux-gate.so.1 =>  (0xb7738000)
       libc.so.6 => /lib/libc.so.6 (0x4ea51000)
       /lib/ld-linux.so.2 (0x4ea28000)

0xffffe000のlinux-gate.so.1からコールする。これはカーネル空間に割り当てられたVDSOである。
[root@localhost kitamura]# echo 2 > /proc/sys/vm/vdso_enabled
[root@localhost kitamura]# ldd a.out
       linux-gate.so.1 =>  (0xffffe000)
       libc.so.6 => /lib/libc.so.6 (0x4ea51000)
       /lib/ld-linux.so.2 (0x4ea28000)
VDSOがユーザ空間に割り当てられる場合、その仮想アドレスは毎回異なるが、カーネル空間の場合、そのアドレスは0xffffe000固定である。なお、linux-gate.so.1内に、__kernel_vsyscallでシンボル定義されている。

検証したカーネルは、CONFIG_X86_32。従ってinstall_special_mappingの処理はスキップされているはず。しかし/proc/self/mapsの結果はvdsoが表示されている。/proc/pid/mapの処理は、プロセスのvm_structをリストをたどって、メモリ空間を表示しているはず。ちょっとこのあたりを調べてみると、最初にvdsoを領域をvm_structのtailとしてセットしていた。
[root@localhost ~]# echo 2 > /proc/sys/vm/vdso_enabled
[root@localhost ~]# cat /proc/self/maps | grep vdso
ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]
32ビットアーキテクチャ下で、CONFIG_X86_64でのカーネルを実行した場合とかで、いまひとつわかりませんです。




最終更新 2012/07/15 16:53:41 - north
(2010/12/17 16:21:39 作成)


検索

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