Вопрос по linux-kernel – вызовы функций из fork () в do_fork ()

3

Пройдя через текст и исходный код, я понял, чтоfork, vfork а такжеclone все три выполненыdo_fork вfork.c с разными параметрами.

Но как именноfork() звонкиdo_fork()..

При звонкеfork() какие все функции называются?

Что такое пошаговый класс дляdo_fork() отfork()?

вилка () - & GT; системный вызов - & gt; переход в режим ядра - & gt; поиск в таблице системных вызовов - & gt; sys_fork () - & gt; do_fork () ninjalj
как система понимает, что при обнаружении fork () ее следует рассматривать как системный вызов. Шаги, о которых вы упомянули, т.е. переход процесса, переход в режим ядра и т. Д., Происходят внутри ENTRY.S. Mr.Ocean
я вроде знаю что .. как именно fork () вызывает NR_fork ?? тогда как NR_fork переходит к sys_fork () ??? Mr.Ocean
fork () - & gt; программа использует системно-зависимый способ выполнения системного вызова - & gt; процессор переходит в режим ядра по адресу, указанному в специфичной для арки инициализации ядра - & gt; специфичный для арки обработчик системного вызова обращается к специфической для арки таблице системных вызовов - & gt; sys_fork () ninjalj

Ваш Ответ

1   ответ
14

libcреализацияfork() и другие системные вызовы содержат специальные инструкции процессора, которые вызывают системный вызов. Вызов системного вызова зависит от архитектуры и может быть довольно сложной темой.

Давайте начнем с "простого" Например, MIPS:

В системе MIPS системные вызовы выполняются с помощью инструкции SYSCALL. Итак, реализация libcfork() заканчивает тем, что помещает некоторые аргументы в некоторые регистры, номер системного вызова в регистреv0и выдачаsyscall инструкция.

На MIPS это вызываетSYSCALL_EXCEPTION (исключение № 8). При загрузке ядро связывает исключение 8 с процедурой обработки вarch/mips/kernel/traps.c:trap_init():

set_except_vector(8, handle_sys);

Поэтому, когда процессор получает исключение 8, потому что программа выпустилаsyscall инструкция, процессор переходит в режим ядра и начинает выполнять обработчик вhandle_sys в/usr/src/linux/arch/mips/kernel/scall*.S (есть несколько файлов для различных 32/64 битных комбинаций kernelspace / userspace). Эта процедура ищет номер системного вызова в таблице системных вызовов и переходит к соответствующему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 вызывается, генерируется прерывание, ЦП просматривает специальную таблицу, называемую IDT (Таблица дескрипторов прерываний), видит, что у него есть шлюз (шлюз ловушки в x86, шлюз прерывания в x86-64), и переходит в кольцо 0, начиная выполнениеsystem_call/ia32_syscall обработчик вarch/x86/kernel/entry_32.S/arch/x86/ia32/ia32entry.S (для x86 / x86_64 соответственно).

Но, начиная с 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_target а такжеia32_cstar_target (последний только для x86_64) (вarch/x86/kernel/entry_32.S или жеarch/x86/ia32/ia32entry.S).

Choosing which syscall mechanism to use

Ядро 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, помимо прочего, содержит соответствующую последовательность вызовов системного вызова для используемого ЦП, например:

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 0x80, sysenter а такжеsyscallядро выбирает соответствующий.

Чтобы пользовательское пространство узнало, что есть vdso, и где оно находится, ядро устанавливаетAT_SYSINFO а такжеAT_SYSINFO_EHDR записи во вспомогательном векторе (auxv4-й аргументmain(), послеargc, argv, envp, который используется для передачи некоторой информации из ядра во вновь запущенные процессы).AT_SYSINFO_EHDR указывает на заголовок ELF vdso,AT_SYSINFO указывает на реализацию vsyscall:

$ 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 ← the traditional way call *%gs:offsetof(tcb_head_t, sysinfo)%gs points to the TCB, so this jumps indirectly through the pointer to vsyscall stored in the TCB. This is prefered for objects compiled as PIC. This requires TLS initialization. For dynamic executables, TLS is initialized by ld.so. For static PIE executables, TLS is initialized by __libc_setup_tls(). call *_dl_sysinfo ← this jumps indirectly through the global variable. This requires relocation of _dl_sysinfo, so it is avoided for objects compiled as 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()

Похожие вопросы