Какая функция ядра Linux создает «процесс 0»?

Я пытаюсь понять больше о process 0, например, есть ли у него дескриптор памяти (поле, отличное от NULL task_struct->mm) или нет, и как он связан с процессом подкачки или бездействия. Мне кажется, что на загрузочном процессоре создается один «процесс 0», а затем для каждого другого процессора создается поток бездействия с помощью idle_threads_init, но я не нашел, где был создан первый (я предполагаю, что это process 0).

Обновить

В свете живой книги, на которую ссылается Tychen, вот мое самое последнее понимание относительно process 0 (для x86_64), может ли кто-нибудь подтвердить/опровергнуть пункты ниже?

  1. init_task с типом task_struct определяется статически, со стеком ядра задачи init_task.stack = init_stack, дескрипторами памяти init_task.mm=NULL и init_task.active_mm=&init_mm, где область стека init_stack и mm_struct init_mm определены статически.
  2. Тот факт, что только active_mm не равен NULL, означает, что process 0 является процессом ядра. Кроме того, init_task.flags=PF_KTHREAD.
  3. Вскоре после начала выполнения несжатого образа ядра загрузите процессор начинает использовать init_stack в качестве стека ядра. Это делает макрос current осмысленным (впервые с момента загрузки машины), что делает возможным fork(). После этого ядро ​​буквально работает в контексте process 0.
  4. start_kernel -> arch_call_rest_init -> rest_init, и внутри этой функции process 1&2 разветвляются. В функции kernel_init, запланированной для process 1, новый поток (с CLONE_VM ) создается и подключается к очереди выполнения ЦП rq->idle для каждого другого логического ЦП.
  5. Интересно, что все бездействующие потоки используют один и тот же tid 0 (а не только tgid). Обычно потоки разделяют tgid, но имеют разные tid, которые на самом деле являются process id в Linux. Я думаю, это ничего не ломает, потому что простаивающие потоки привязаны к своим собственным процессорам.
  6. kernel_init загружает исполняемый файл init (обычно /sbin/init), переключает current->mm и active_mm на ненулевой mm_struct и сбрасывает флаг PF_KTHREAD, что делает process 1 законным процессом пользовательского пространства. Хотя process 2 не настраивает mm, то есть он остается процессом ядра, таким же, как process 0.
  7. В конце rest_init берет на себя do_idle, что означает, что все процессоры имеют простаивающий процесс.
  8. Раньше меня что-то смущало, но теперь становится ясно: init_task/init_mm/init_stack все объекты/метки, такие как init_task/init_mm/init_stack, используются process 0, а не init process, который является process 1.

person QnA    schedule 04.06.2020    source источник
comment
Пожалуйста, не редактируйте ответ в вопросе. Разместите это как ответ, где он может получить голоса отдельно от вопроса. (И в какой-то момент в будущем, если он устареет, он не будет помещен выше других ответов.)   -  person Peter Cordes    schedule 09.06.2020
comment
Ok. Есть вещи, в которых я не уверен, поэтому они появляются в вопросе, но то, что вы сказали, имеет смысл. Я могу поместить их в отдельную ветку ответов, чтобы собрать отзывы.   -  person QnA    schedule 10.06.2020
comment
О, если это все еще то, что вы просите подтвердить, и не уверены, что это правильно, то вопрос имеет смысл.   -  person Peter Cordes    schedule 10.06.2020
comment
Хорошо, тогда я прямо скажу об этом в тексте.   -  person QnA    schedule 10.06.2020


Ответы (1)


На самом деле мы запускаем ядро ​​Linux с start_kernel, а процесс 0/idle также начинается здесь.

В начале start_kernel мы вызываем set_task_stack_end_magic(&init_stack). Эта функция установит границу стека init_task, который является процессом 0/idle.

void set_task_stack_end_magic(struct task_struct *tsk)
{
    unsigned long *stackend;

    stackend = end_of_stack(tsk);
    *stackend = STACK_END_MAGIC;    /* for overflow detection */
}

Легко понять, что эта функция получает адрес ограничения и устанавливает нижний предел в STACK_END_MAGIC в качестве флага переполнения стека. Вот график структуры.

введите здесь описание изображения

The process 0 is statically defined . This is the only process that is not created by kernel_thread nor fork.

/*
 * Set up the first task table, touch at your own risk!. Base=0,
 * limit=0x1fffff (=2MB)
 */
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    __init_task_data
#endif
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
    .thread_info    = INIT_THREAD_INFO(init_task),
    .stack_refcount = REFCOUNT_INIT(1),
#endif
    .state      = 0,
    .stack      = init_stack,
    .usage      = REFCOUNT_INIT(2),
    .flags      = PF_KTHREAD,
    .prio       = MAX_PRIO - 20,
    .static_prio    = MAX_PRIO - 20,
    .normal_prio    = MAX_PRIO - 20,
    .policy     = SCHED_NORMAL,
    .cpus_ptr   = &init_task.cpus_mask,
    .cpus_mask  = CPU_MASK_ALL,
    .nr_cpus_allowed= NR_CPUS,
    .mm     = NULL,
    .active_mm  = &init_mm,
    ......
    .thread_pid = &init_struct_pid,
    .thread_group   = LIST_HEAD_INIT(init_task.thread_group),
    .thread_node    = LIST_HEAD_INIT(init_signals.thread_head),
    ......
};
EXPORT_SYMBOL(init_task);

Вот несколько важных вещей, которые нам нужно сделать, чтобы сделать это ясно.

  1. INIT_THREAD_INFO(init_task) устанавливает thread_info как на графике выше.
  2. init_stack определяется, как показано ниже
extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];

где THREAD_SIZE равно

#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif
#define THREAD_SIZE_ORDER   (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)

поэтому размер по умолчанию определен.

  1. Процесс 0 будет работать только в пространстве ядра, но в некоторых случаях, как я упоминал выше, ему требуется пространство виртуальной памяти, поэтому мы устанавливаем следующее
    .mm     = NULL,
    .active_mm  = &init_mm,

Давайте вернемся к start_kernel, rest_init инициализирует kernel_init и kthreadd.

noinline void __ref rest_init(void)
{
......
    pid = kernel_thread(kernel_init, NULL, CLONE_FS);
......
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
......
}

kernel_init запустит execve, а затем перейдет в пространство пользователя, переключится на процесс init, запустив , который является процессом 1.

if (!try_to_run_init_process("/sbin/init") || 
    !try_to_run_init_process("/etc/init")  || 
    !try_to_run_init_process("/bin/init")  || 
    !try_to_run_init_process("/bin/sh")) 
   return 0;

kthread становится процессом демона для управления и планирования другого ядра task_struts, то есть процесса 2.

После всего этого процесс 0 станет незанятым процессом и выпрыгнет из rq, что означает, что он будет работать только тогда, когда rq пуст.

noinline void __ref rest_init(void)
{
......
    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    schedule_preempt_disabled();
    /* Call into cpu_idle with preempt disabled */
    cpu_startup_entry(CPUHP_ONLINE);
}


void cpu_startup_entry(enum cpuhp_state state)
{
    arch_cpu_idle_prepare();
    cpuhp_online_idle(state);
    while (1)
        do_idle();
}

Наконец, если вы хотите лучше понять ядро Линукс.

person tyChen    schedule 05.06.2020
comment
спасибо, очень полезная информация! Чтобы быть точным, вместо set_task_stack_end_magic, который все еще выполняет подготовительную работу, справедливо ли будет сказать, что процесс 0 запускается этой строкой movq initial_stack(%rip), %rsp в /arch/x86/kernel/head_64.S, поскольку он эта строка заполняет RSP стеком ядра процесса 0? - person QnA; 08.06.2020
comment
Я думаю, вы смешиваете init_task и init_stack. Это не одно и то же. Процесс 0 создается путем инициализации структуры gcc, как я упоминал выше, вы можете думать, что это так же, как мы помещаем int a = 1 в программу. - person tyChen; 08.06.2020
comment
Не совсем. Я обновлю пост, чтобы было понятно. - person QnA; 08.06.2020
comment
Может быть, вы можете отредактировать мой ответ, чтобы сделать его полным. Я думаю, что ваша редакция сейчас может полностью решить проблему. - person tyChen; 09.06.2020
comment
Да, изначально я бы предложил заменить описание функции set_task_stack_end_magic на указанную выше инструкцию mov и покончить с этим. Но затем, когда я начал записывать резюме для себя, появилось гораздо больше важных вещей, некоторые из которых я не совсем уверен, например, «все простаивающие потоки разделяют tid 0», что звучит немного нереально для меня. - person QnA; 10.06.2020
comment
Я думаю, что ваш пост почти правильный, но все же не идеальный. Поэтому я редактирую свой ответ сам. - person tyChen; 10.06.2020
comment
Пожалуйста, помогите мне проверить, если что-то осталось или неправильно - person tyChen; 11.06.2020