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関数でモジュールをリロケーションしています。
root/arch/x86/vdso/vdso32/syscall.S
install_special_mapping()で、VDSO空間をcurrent->mm下に登録する。VDSO_ENABLEDならそもそもユーザ空間ということもあり、必ず登録されるが、VDSO_COMPATの場合、compat_uses_vmaは、以下のように定義されていて、CONFIG_X86_64の時、current->mm下に登録される。・
glibから直接コールする。(たぶん)
0xb7738000のlinux-gate.so.1からコールする。これはユーザ空間に割り当てられたVDSOである。
0xffffe000のlinux-gate.so.1からコールする。これはカーネル空間に割り当てられたVDSOである。
検証したカーネルは、CONFIG_X86_32。従ってinstall_special_mappingの処理はスキップされているはず。しかし/proc/self/mapsの結果はvdsoが表示されている。/proc/pid/mapの処理は、プロセスのvm_structをリストをたどって、メモリ空間を表示しているはず。ちょっとこのあたりを調べてみると、最初にvdsoを領域をvm_structのtailとしてセットしていた。
カーネルは立ち上がり時、ラベルを__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でのカーネルを実行した場合とかで、いまひとつわかりませんです。





