3

いくつかのテキストとソースコードを調べた後、私はに気づきましたforkvforkそしてclone、3つすべてが異なるパラメータで実行さdo_forkれます。fork.c

しかし、どのように正確にfork()..do_fork()

fork()どのすべての関数が呼び出されるかを呼び出すとき?

ステップバイステップのクラスdo_fork()から何までfork()ですか?

4

1 に答える 1

14

libcの実装fork()およびその他のシステムコールには、システムコールを呼び出す特別なプロセッサ命令が含まれています。システムコールの呼び出しはアーキテクチャ固有であり、非常に複雑なトピックになる可能性があります。

「単純な」例、MIPSから始めましょう。

MIPSでは、システムコールはSYSCALL命令を介して呼び出されます。したがって、libcの実装は、fork()いくつかのレジスタにいくつかの引数を置き、regiterv0にシステムコール番号を置き、命令を発行することになりsyscallます。

MIPSでは、これによりSYSCALL_EXCEPTION(例外番号8)が発生します。起動時に、カーネルは例外8を次の処理ルーチンに関連付けますarch/mips/kernel/traps.c:trap_init()

set_except_vector(8, handle_sys);

したがって、プログラムが命令を発行したためにCPUが例外8を受け取ると、CPUはsyscallカーネルモードに移行し、でハンドラーの実行を開始しますhandle_sys/usr/src/linux/arch/mips/kernel/scall*.S32/64ビットのカーネルスペース/ユーザースペースの組み合わせごとにいくつかのファイルがあります)。このルーチンは、システムコールテーブルでシステムコール番号を検索し、適切なsys_...()関数(この例では)にジャンプしsys_fork()ます。

現在、x86はより複雑です。従来、Linuxは割り込み0x80を使用してシステムコールを呼び出していました。これは、次のx86ゲートに関連付けられていarch/x86/kernel/traps_*.c:trap_init()ます。

set_system_gate(SYSCALL_VECTOR,&system_call);

x86プロセッサには、いくつかのレベル(リング)の特権があります(80286以降)。カーネルによって設定された特別な種類のセグメント記述子である事前定義されたゲートを介してのみ、下位リング(=より多くの特権)にアクセス(ジャンプ)することができます。したがって、int 0x80が呼び出されると、割り込みが生成され、CPUはIDT(割り込み記述子テーブル)と呼ばれる特別なテーブルを検索し、ゲート(x86ではトラップゲート、x86-64では割り込みゲート)があることを確認します。リング0に遷移し、/で/ハンドラーの実行を開始します(system_callそれぞれia32_syscallx86 / x86_64の場合)。arch/x86/kernel/entry_32.Sarch/x86/ia32/ia32entry.S

ただし、Pentium Pro以降、システムコールを呼び出す別の方法があります。SYSENTER命令を使用することです(AMDにも独自のSYSCALL命令があります)。これは、システムコールを呼び出すためのより効率的な方法です。この「新しい」メカニズムのハンドラーは次のように設定されarch/x86/vdso/vdso32-setup.c:syscall32_cpu_init()ます。

#ifdef CONFIG_X86_64
[...]
void syscall32_cpu_init(void)
{
    if (use_sysenter < 0)
            use_sysenter = (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL);

    /* Load these always in case some future AMD CPU supports
       SYSENTER from compat mode too. */
    checking_wrmsrl(MSR_IA32_SYSENTER_CS, (u64)__KERNEL_CS);
    checking_wrmsrl(MSR_IA32_SYSENTER_ESP, 0ULL);
    checking_wrmsrl(MSR_IA32_SYSENTER_EIP, (u64)ia32_sysenter_target);

    wrmsrl(MSR_CSTAR, ia32_cstar_target);
}
[...]
#else
[...]
void enable_sep_cpu(void)
{
    int cpu = get_cpu();
    struct tss_struct *tss = &per_cpu(init_tss, cpu);

    if (!boot_cpu_has(X86_FEATURE_SEP)) {
            put_cpu();
            return;
    }

    tss->x86_tss.ss1 = __KERNEL_CS;
    tss->x86_tss.sp1 = sizeof(struct tss_struct) + (unsigned long) tss;
    wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0);
    wrmsr(MSR_IA32_SYSENTER_ESP, tss->x86_tss.sp1, 0);
    wrmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) ia32_sysenter_target, 0);
    put_cpu();
}
[...]
#endif  /* CONFIG_X86_64 */

上記では、マシン固有のレジスタ(MSR)を使用してセットアップを行います。ハンドラールーチンはia32_sysenter_targetand ia32_cstar_target(これはx86_64の場合のみ)(inarch/x86/kernel/entry_32.Sまたはarch/x86/ia32/ia32entry.S)です。

使用するシステムコールメカニズムの選択

Linuxカーネルとglibcには、システムコールを呼び出すさまざまな方法から選択するメカニズムがあります。

カーネルは、プロセスごとに仮想共有ライブラリをセットアップします。これはVDSO(仮想動的共有オブジェクト)と呼ばれ、次の出力で確認できますcat /proc/<pid>/maps

$ cat /proc/self/maps
08048000-0804c000 r-xp 00000000 03:04 1553592    /bin/cat
0804c000-0804d000 rw-p 00003000 03:04 1553592    /bin/cat
[...]
b7ee8000-b7ee9000 r-xp b7ee8000 00:00 0          [vdso]
[...]

このvdsoには、特に、使用中のCPUに適したシステムコール呼び出しシーケンスが含まれています。例:

ffffe414 <__kernel_vsyscall>:
ffffe414:       51                      push   %ecx        ; \
ffffe415:       52                      push   %edx        ; > save registers
ffffe416:       55                      push   %ebp        ; /
ffffe417:       89 e5                   mov    %esp,%ebp   ; save stack pointer
ffffe419:       0f 34                   sysenter           ; invoke system call
ffffe41b:       90                      nop
ffffe41c:       90                      nop                ; the kernel will usually
ffffe41d:       90                      nop                ; return to the insn just
ffffe41e:       90                      nop                ; past the jmp, but if the
ffffe41f:       90                      nop                ; system call was interrupted
ffffe420:       90                      nop                ; and needs to be restarted
ffffe421:       90                      nop                ; it will return to this jmp
ffffe422:       eb f3                   jmp    ffffe417 <__kernel_vsyscall+0x3>
ffffe424:       5d                      pop    %ebp        ; \
ffffe425:       5a                      pop    %edx        ; > restore registers
ffffe426:       59                      pop    %ecx        ; /
ffffe427:       c3                      ret                ; return to caller

、、を使用したarch/x86/vdso/vdso32/実装があり、カーネルは適切なものを選択します。int 0x80sysentersyscall

ユーザースペースにvdsoがあり、それがどこにあるかを知らせるために、カーネルは補助ベクトル( 、カーネルから新しく開始されたプロセスに情報を渡すために使用される、の後の4番目の引数)にエントリを設定AT_SYSINFOします。vdsoのELFヘッダーを指し、vsyscall実装を指します。AT_SYSINFO_EHDRauxvmain()argc, argv, envpAT_SYSINFO_EHDRAT_SYSINFO

$ LD_SHOW_AUXV=1 id    # tell the dynamic linker ld.so to output auxv values
AT_SYSINFO:      0xb7fd4414
AT_SYSINFO_EHDR: 0xb7fd4000
[...]

glibcは、この情報を使用してを検索しますvsyscall。ダイナミックローダーグローバルに保存します_dl_sysinfo。例:

glibc-2.16.0/elf/dl-support.c:_dl_aux_init():
ifdef NEED_DL_SYSINFO
  case AT_SYSINFO:
    GL(dl_sysinfo) = av->a_un.a_val;
    break;
#endif
#if defined NEED_DL_SYSINFO || defined NEED_DL_SYSINFO_DSO
  case AT_SYSINFO_EHDR:
    GL(dl_sysinfo_dso) = (void *) av->a_un.a_val;
    break;
#endif

glibc-2.16.0/elf/dl-sysdep.c:_dl_sysdep_start()

glibc-2.16.0/elf/rtld.c:dl_main:
GLRO(dl_sysinfo) = GLRO(dl_sysinfo_dso)->e_entry + l->l_addr;

およびTCB(スレッド制御ブロック)のヘッダーのフィールド:

glibc-2.16.0/nptl/sysdeps/i386/tls.h

_head->sysinfo = GLRO(dl_sysinfo)

カーネルが古く、vdsoを提供しない場合、glibcは次のデフォルトの実装を提供します_dl_sysinfo

.hidden _dl_sysinfo_int80:
int $0x80
ret

プログラムがglibcに対してコンパイルされる場合、状況に応じて、システムコールを呼び出すさまざまな方法から選択が行われます。

glibc-2.16.0/sysdeps/unix/sysv/linux/i386/sysdep.h:
/* The original calling convention for system calls on Linux/i386 is
   to use int $0x80.  */
#ifdef I386_USE_SYSENTER
# ifdef SHARED
#  define ENTER_KERNEL call *%gs:SYSINFO_OFFSET
# else
#  define ENTER_KERNEL call *_dl_sysinfo
# endif
#else
# define ENTER_KERNEL int $0x80
#endif
  • int 0x80←伝統的な方法
  • call *%gs:offsetof(tcb_head_t, sysinfo)%gsはTCBを指しているため、これはTCBに格納されているvsyscallへのポインタを介して間接的にジャンプします。これは、PICとしてコンパイルされたオブジェクトに適しています。これにはTLSの初期化が必要です。動的実行可能ファイルの場合、TLSはld.soによって初期化されます。静的PIE実行可能ファイルの場合、TLSは__libc_setup_tls()によって初期化されます。
  • call *_dl_sysinfo←これは、グローバル変数を間接的にジャンプします。これには_dl_sysinfoの再配置が必要であるため、PICとしてコンパイルされたオブジェクトでは回避されます。

したがって、x86では:

                       fork()
                         ↓
int 0x80 / call *%gs:0x10 / call *_dl_sysinfo 
  |                ↓              ↓
  |       (in vdso) int 0x80 / sysenter / syscall
  ↓                ↓              ↓            ↓
      system_call     | ia32_sysenter_target | ia32_cstar_target
                          ↓
                       sys_fork()
于 2012-07-19T23:03:37.200 に答える