From 610fd984d24920b1e9e6efb36106dbf01f57f733 Mon Sep 17 00:00:00 2001 From: proninyaroslav Date: Sun, 5 Aug 2018 13:58:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=20Linux=20v4.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Booting/linux-bootstrap-1.md | 28 +++++++++++----------- Booting/linux-bootstrap-2.md | 46 ++++++++++++++++++------------------ Booting/linux-bootstrap-3.md | 18 +++++++------- Booting/linux-bootstrap-4.md | 36 ++++++++++++++-------------- Booting/linux-bootstrap-5.md | 22 ++++++++--------- LINKS.md | 2 +- 6 files changed, 76 insertions(+), 76 deletions(-) diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 18cc32b..60a6e62 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -191,13 +191,13 @@ objdump -D -b binary -mi386 -Maddr16,data16,intel boot Загрузчик -------------------------------------------------------------------------------- -Существует несколько загрузчиков Linux, такие как [GRUB 2](https://www.gnu.org/software/grub/) и [syslinux](https://www.syslinux.org/wiki/index.php/The_Syslinux_Project). Ядро Linux имеет [протокол загрузки](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/x86/boot.txt), который определяет требования к загрузчику для реализации поддержки Linux. В этом примере будет описан GRUB 2. +Существует несколько загрузчиков Linux, такие как [GRUB 2](https://www.gnu.org/software/grub/) и [syslinux](https://www.syslinux.org/wiki/index.php/The_Syslinux_Project). Ядро Linux имеет [протокол загрузки](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt), который определяет требования к загрузчику для реализации поддержки Linux. В этом примере будет описан GRUB 2. Теперь, когда BIOS выбрал загрузочное устройство и передал контроль управления коду в загрузочном секторе, начинается выполнение [boot.img](https://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/boot.S;hb=HEAD). Этот код очень простой в связи с ограниченным количеством свободного пространства и содержит указатель, который используется для перехода к основному образу GRUB 2. Основной образ начинается с [diskboot.img](https://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/boot/i386/pc/diskboot.S;hb=HEAD), который обычно располагается сразу после первого сектора в неиспользуемой области перед первым разделом. Приведённый выше код загружает оставшуюся часть основного образа, который содержит ядро и драйверы GRUB 2 для управления файловой системой. После загрузки остальной части основного образа, код выполняет функция [grub_main](https://git.savannah.gnu.org/gitweb/?p=grub.git;a=blob;f=grub-core/kern/main.c). Функция `grub_main` инициализирует консоль, получает базовый адрес для модулей, устанавливает корневое устройство, загружает/обрабатывает файл настроек grub, загружает модули и т.д. В конце выполнения, `grub_main` переводит grub обратно в нормальный режим. Функция `grub_normal_execute` (из `grub-core/normal/main.c`) завершает последние приготовления и отображает меню выбора операционной системы. Когда мы выбираем один из пунктов меню, запускается функция `grub_menu_execute_entry`, которая в свою очередь запускает команду grub `boot`, загружающую выбранную операционную систему. -Из протокола загрузки видно, что загрузчик должен читать и заполнять некоторые поля в заголовке ядра, который начинается со смещения `0x01f1` в коде настроек. Вы можете посмотреть загрузочный [скрипт компоновщика](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/setup.ld#L16), чтобы убедиться в этом. Заголовок ядра [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S) начинается с: +Из протокола загрузки видно, что загрузчик должен читать и заполнять некоторые поля в заголовке ядра, который начинается со смещения `0x01f1` в коде настроек. Вы можете посмотреть загрузочный [скрипт компоновщика](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/setup.ld#L16), чтобы убедиться в этом. Заголовок ядра [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S) начинается с: ```assembly .globl hdr @@ -211,7 +211,7 @@ hdr: boot_flag: .word 0xAA55 ``` -Загрузчик должен заполнить этот и другие заголовки (которые помеченные как тип `write` в протоколе загрузки Linux, например, в [данном примере](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/x86/boot.txt#L354)) значениями, которые он получил из командной строки или значениями, вычисленными во время загрузки. (Мы не будет вдаваться в подробности и описывать все поля заголовка ядра, но мы сделаем это, когда будем обсуждать как их использует ядро; тем не менее вы можете найти полное описание всех полей в [протоколе загрузки](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/x86/boot.txt#L156).) +Загрузчик должен заполнить этот и другие заголовки (которые помеченные как тип `write` в протоколе загрузки Linux, например, в [данном примере](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt#L354)) значениями, которые он получил из командной строки или значениями, вычисленными во время загрузки. (Мы не будет вдаваться в подробности и описывать все поля заголовка ядра, но мы сделаем это, когда будем обсуждать как их использует ядро; тем не менее вы можете найти полное описание всех полей в [протоколе загрузки](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt#L156).) Как мы видим из протокола, после загрузки ядра карта распределения памяти будет выглядеть следующим образом: @@ -253,7 +253,7 @@ X + sizeof(KernelBootSector) + 1 Начальный этап настройки ядра -------------------------------------------------------------------------------- -Наконец, мы находимся в ядре! Технически, ядро ещё не работает; во-первых, часть ядра, отвественная за настройку, должна подготовить декомпрессор, вещи связанные с управлением памятью и т.д. После всех подготовок код настройки ядра должен распаковывать фактическое ядро и совершить переход на него. Выполнение настройки начинается в файле [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S), начиная с метки [_start](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L292). Это немного странно на первый взгляд, так как перед этим есть ещё несколько инструкций. +Наконец, мы находимся в ядре! Технически, ядро ещё не работает; во-первых, часть ядра, отвественная за настройку, должна подготовить декомпрессор, вещи связанные с управлением памятью и т.д. После всех подготовок код настройки ядра должен распаковывать фактическое ядро и совершить переход на него. Выполнение настройки начинается в файле [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S), начиная с метки [_start](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L292). Это немного странно на первый взгляд, так как перед этим есть ещё несколько инструкций. Давным-давно у Linux был свой загрузчик, но сейчас, если вы запустите, например: @@ -315,7 +315,7 @@ _start: // ``` -Здесь мы можем видеть опкод инструкции `jmp` (`0xeb`) к метке `start_of_setup-1f`. Нотация `Nf` означает, что `2f` ссылается на следующую локальную метку `2:`; в нашем случае это метка `1`, которая расположена сразу после инструкции jump и содержит оставшуюся часть [заголовка](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/x86/boot.txt#L156). Сразу после заголовка настроек мы видим секцию `.entrytext`, которая начинается с метки `start_of_setup`. +Здесь мы можем видеть опкод инструкции `jmp` (`0xeb`) к метке `start_of_setup-1f`. Нотация `Nf` означает, что `2f` ссылается на следующую локальную метку `2:`; в нашем случае это метка `1`, которая расположена сразу после инструкции jump и содержит оставшуюся часть [заголовка](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt#L156). Сразу после заголовка настроек мы видим секцию `.entrytext`, которая начинается с метки `start_of_setup`. Это первый код, который на самом деле запускается (отдельно от предыдущей инструкции jump, конечно). После того как настройщик ядра получил управление от загрузчика, первая инструкция `jmp` располагаетсяь по смещению `0x200` от начала реальных адресов, т.е после первых 512 байт. Об этом можно узнать из протокола загрузки ядра Linux, а также увидеть в исходном коде grub2: @@ -337,7 +337,7 @@ cs = 0x1020 * Убедиться, что все значения всех сегментных регистров равны * Правильно настроить стек, если это необходимо * Настроить [BSS](https://en.wikipedia.org/wiki/.bss) -* Перейти к C-коду в [main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c) +* Перейти к C-коду в [main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c) Давайте посмотрим, как эти условия выполняются. @@ -359,7 +359,7 @@ _start: .byte 0xeb .byte start_of_setup-1f ``` -расположеной в `512` байтах от [4d 5a](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L46). Нам также необходимо выровнять `cs` с `0x1020` на `0x1000` и остальные сегментные регистры. После этого мы настраиваем стек: +расположеной в `512` байтах от [4d 5a](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L46). Нам также необходимо выровнять `cs` с `0x1020` на `0x1000` и остальные сегментные регистры. После этого мы настраиваем стек: ```assembly pushw %ds @@ -367,12 +367,12 @@ _start: lretw ``` -кладём значение `ds` в стек по адресу метки [6](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L494) и выполняем инструкцию `lretw`. Когда мы вызываем `lretw`, она загружает адрес метки `6` в регистр [счётчика команд (IP)](https://en.wikipedia.org/wiki/Program_counter), и загружает `cs` со значением `ds`. После этого `ds` и `cs` будут иметь одинаковые значения. +кладём значение `ds` в стек по адресу метки [6](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L494) и выполняем инструкцию `lretw`. Когда мы вызываем `lretw`, она загружает адрес метки `6` в регистр [счётчика команд (IP)](https://en.wikipedia.org/wiki/Program_counter), и загружает `cs` со значением `ds`. После этого `ds` и `cs` будут иметь одинаковые значения. Настройка стека -------------------------------------------------------------------------------- -Почти весь код настройки - это подготовка для среды языка C в режиме реальных адресов. Следующим [шагом](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L569) является проверка значения регистра `ss` и создание корректного стека, если значение `ss` неверно: +Почти весь код настройки - это подготовка для среды языка C в режиме реальных адресов. Следующим [шагом](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L569) является проверка значения регистра `ss` и создание корректного стека, если значение `ss` неверно: ```assembly movw %ss, %dx @@ -389,7 +389,7 @@ _start: Давайте рассмотрим все три сценария: -* `ss` имеет верный адрес (`0x1000`). В этом случае мы переходим на метку [2](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L583): +* `ss` имеет верный адрес (`0x1000`). В этом случае мы переходим на метку [2](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L583): ```assembly 2: andw $~3, %dx @@ -405,7 +405,7 @@ _start: ![стек](http://oi58.tinypic.com/16iwcis.jpg) -* Второй сценарий (когда `ss` != `ds`). Во-первых, помещаем значение [_end](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/setup.ld#L52) (адрес окончания кода настройки) в `dx` и проверяем поле заголовка `loadflags` инструкцией `testb`, чтобы понять, можем ли мы использовать кучу (heap). [loadflags](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L321) является заголовком с битовой маской, который определён как: +* Второй сценарий (когда `ss` != `ds`). Во-первых, помещаем значение [_end](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/setup.ld#L52) (адрес окончания кода настройки) в `dx` и проверяем поле заголовка `loadflags` инструкцией `testb`, чтобы понять, можем ли мы использовать кучу (heap). [loadflags](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L321) является заголовком с битовой маской, который определён как: ```C #define LOADED_HIGH (1<<0) @@ -442,7 +442,7 @@ _start: cmpl $0x5a5aaa55, setup_sig jne setup_bad ``` -Это простое сравнение [setup_sig](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/setup.ld#L39) с магическим числом `0x5a5aaa55`. Если они не равны, сообщается о фатальной ошибке. +Это простое сравнение [setup_sig](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/setup.ld#L39) с магическим числом `0x5a5aaa55`. Если они не равны, сообщается о фатальной ошибке. Если магические числа совпадают, зная, что у нас есть набор правильно настроенных сегментных регистров и стек, нам всего лишь нужно настроить BSS. @@ -456,7 +456,7 @@ _start: shrw $2, %cx rep; stosl ``` -Во-первых, адрес [__bss_start](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/setup.ld#L47) помещается в `di`. Далее, адрес `_end + 3` (+3 - выравнивает до 4 байт) помещается в `cx`. Регистр `eax` очищается (с помощью инструкции `xor`), а размер секции BSS (`cx`-`di`) вычисляется и помещается в `cx`. Затем `cx` делится на 4 (размер 'слова' (англ. word)), а инструкция `stosl` используется повторно, сохраняя значение `eax` (ноль) в адрес, на который указывает `di`, автоматически увеличивая `di` на 4 (это продолжается до тех пор, пока `cx` не достигнет нуля). Эффект от этого кода в том, что теперь все 'слова' в памяти от `__bss_start` до `_end` заполнены нулями: +Во-первых, адрес [__bss_start](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/setup.ld#L47) помещается в `di`. Далее, адрес `_end + 3` (+3 - выравнивает до 4 байт) помещается в `cx`. Регистр `eax` очищается (с помощью инструкции `xor`), а размер секции BSS (`cx`-`di`) вычисляется и помещается в `cx`. Затем `cx` делится на 4 (размер 'слова' (англ. word)), а инструкция `stosl` используется повторно, сохраняя значение `eax` (ноль) в адрес, на который указывает `di`, автоматически увеличивая `di` на 4 (это продолжается до тех пор, пока `cx` не достигнет нуля). Эффект от этого кода в том, что теперь все 'слова' в памяти от `__bss_start` до `_end` заполнены нулями: ![bss](http://oi59.tinypic.com/29m2eyr.jpg) @@ -469,7 +469,7 @@ _start: calll main ``` -Функция `main()` находится в файле [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c). О том, что она делает, вы сможете узнать в следующей части. +Функция `main()` находится в файле [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c). О том, что она делает, вы сможете узнать в следующей части. Заключение -------------------------------------------------------------------------------- diff --git a/Booting/linux-bootstrap-2.md b/Booting/linux-bootstrap-2.md index fa0f792..1fd1ffa 100644 --- a/Booting/linux-bootstrap-2.md +++ b/Booting/linux-bootstrap-2.md @@ -4,7 +4,7 @@ Первые шаги в настройке ядра -------------------------------------------------------------------------------- -Мы начали изучение внутренностей Linux в предыдущей [части](linux-bootstrap-1.md) и увидели начальную часть кода настройки ядра. Мы остановились на вызове функции `main` (это первая функция, написанная на C) из [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c). +Мы начали изучение внутренностей Linux в предыдущей [части](linux-bootstrap-1.md) и увидели начальную часть кода настройки ядра. Мы остановились на вызове функции `main` (это первая функция, написанная на C) из [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c). В этой части мы продолжим исследовать код установки ядра, а именно @@ -177,20 +177,20 @@ lgdt gdt Полный переход в защищённый режим мы увидим в следующей части, но прежде чем мы сможем перейти в защищённый режим, нужно совершить ещё несколько приготовлений. -Давайте посмотрим на [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c). Мы можем видеть некоторые подпрограммы, которые выполняют инициализацию клавиатуры, инициализацию кучи и т.д. Рассмотрим их. +Давайте посмотрим на [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c). Мы можем видеть некоторые подпрограммы, которые выполняют инициализацию клавиатуры, инициализацию кучи и т.д. Рассмотрим их. Копирование параметров загрузки в "нулевую страницу" (zeropage) -------------------------------------------------------------------------------- -Мы стартуем из подпрограммы `main` в "main.c". Первая функция, которая вызывается в `main` - [`copy_boot_params(void)`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L30). Она копирует заголовок настройки ядра в поле структуры `boot_params`, которая определена в [arch/x86/include/uapi/asm/bootparam.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/uapi/asm/bootparam.h#L113). +Мы стартуем из подпрограммы `main` в "main.c". Первая функция, которая вызывается в `main` - [`copy_boot_params(void)`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L30). Она копирует заголовок настройки ядра в поле структуры `boot_params`, которая определена в [arch/x86/include/uapi/asm/bootparam.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/uapi/asm/bootparam.h#L113). Структура `boot_params` содержит поле `struct setup_header hdr`. Эта структура содержит те же поля, что и в [протоколе загрузки Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) и заполняется загрузчиком, а так же во время компиляции/сборки ядра. `copy_boot_params` делает две вещи: -1. Копирует `hdr` из [header.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L281) в структуру `boot_params` в поле `setup_header` +1. Копирует `hdr` из [header.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L281) в структуру `boot_params` в поле `setup_header` 2. Обновляет указатель на командную строку ядра, если ядро было загружено со старым протоколом командной строки. -Обратите внимание на то, что он копирует `hdr` с помощью функции `memcpy`, которая определена в [copy.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/copy.S). Взглянем на неё: +Обратите внимание на то, что он копирует `hdr` с помощью функции `memcpy`, которая определена в [copy.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/copy.S). Взглянем на неё: ```assembly GLOBAL(memcpy) @@ -210,7 +210,7 @@ GLOBAL(memcpy) ENDPROC(memcpy) ``` -Да, мы только что перешли в C-код и снова вернулись к ассемблеру :) Прежде всего мы видим, что `memcpy` и другие подпрограммы, расположенные здесь, начинаются и заканчиваются двумя макросами: `GLOBAL` и `ENDPROC`. Макрос `GLOBAL` описан в [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/linkage.h) и определяет директиву `globl`, а так же метку для него. `ENDPROC` описан в [include/linux/linkage.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/linkage.h); отмечает символ `name` в качестве имени функции и заканчивается размером символа `name`. +Да, мы только что перешли в C-код и снова вернулись к ассемблеру :) Прежде всего мы видим, что `memcpy` и другие подпрограммы, расположенные здесь, начинаются и заканчиваются двумя макросами: `GLOBAL` и `ENDPROC`. Макрос `GLOBAL` описан в [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/asm/linkage.h) и определяет директиву `globl`, а так же метку для него. `ENDPROC` описан в [include/linux/linkage.h](https://github.com/torvalds/linux/blob/v4.16/include/linux/linkage.h); отмечает символ `name` в качестве имени функции и заканчивается размером символа `name`. Реализация `memcpy` достаточно проста. Во-первых, она помещает значения регистров `si` и `di` в стек для их сохранения, так как они будут меняться в течении работы. Как мы видим из `REALMODE_CFLAGS` в `arch/x86/Makefile` система сборки ядра использует параметр GCC `-mregparm = 3`, поэтому функции получают первые три параметра из регистров `ax`, `dx` и `cx`. Вызов `memcpy` выглядит следующим образом: @@ -229,7 +229,7 @@ memcpy(&boot_params.hdr, &hdr, sizeof hdr); Инициализация консоли -------------------------------------------------------------------------------- -После того как `hdr` скопирован в `boot_params.hdr`, следующим шагом является инициализация консоли с помощью вызова функции `console_init`, определённой в [arch/x86/boot/early_serial_console.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/early_serial_console.c). +После того как `hdr` скопирован в `boot_params.hdr`, следующим шагом является инициализация консоли с помощью вызова функции `console_init`, определённой в [arch/x86/boot/early_serial_console.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/early_serial_console.c). Функция пытается найти опцию `earlyprintk` в командной строке и, если поиск завершился успехом, парсит адрес порта, скорость передачи данных и инициализирует последовательный порт. Значение опции `earlyprintk` может быть одним из следующих: @@ -244,7 +244,7 @@ if (cmdline_find_option_bool("debug")) puts("early console in setup code\n"); ``` -Определение `puts` находится в [tty.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/tty.c). Как мы видим, она печатает символ за символом в цикле, вызывая функцию `putchar`. Давайте посмотрим на реализацию `putchar`: +Определение `puts` находится в [tty.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/tty.c). Как мы видим, она печатает символ за символом в цикле, вызывая функцию `putchar`. Давайте посмотрим на реализацию `putchar`: ```C void __attribute__((section(".inittext"))) putchar(int ch) @@ -258,7 +258,7 @@ void __attribute__((section(".inittext"))) putchar(int ch) serial_putchar(ch); } ``` -`__attribute__((section(".inittext")))` означает, что код будет находиться в секции `.inittext`. Мы можем найти его в файле компоновщика [setup.ld](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/setup.ld#L19). +`__attribute__((section(".inittext")))` означает, что код будет находиться в секции `.inittext`. Мы можем найти его в файле компоновщика [setup.ld](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/setup.ld#L19). Прежде всего, `putchar` проверяет наличие символа `\n` и, если он найден, печатает перед ним `\r`. После этого она выводит символ на экране VGA, вызвав BIOS с прерыванием `0x10`: @@ -286,7 +286,7 @@ static void __attribute__((section(".inittext"))) bios_putchar(int ch) reg->gs = gs(); ``` -Давайте посмотри на реализацию [memset](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/copy.S#L36): +Давайте посмотри на реализацию [memset](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/copy.S#L36): ```assembly GLOBAL(memset) @@ -312,14 +312,14 @@ ENDPROC(memset) Остальная часть `memset` делает почти то же самое, что и `memcpy`. -После того как структура `biosregs` заполнена с помощью `memset`, `bios_putchar` вызывает прерывание [0x10](http://www.ctyme.com/intr/rb-0106.htm) для вывода символа. Затем она проверяет, инициализирован ли последовательный порт, и в случае если он инициализирован, записывает в него символ с помощью инструкций [serial_putchar](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/tty.c#L30) и `inb/outb`. +После того как структура `biosregs` заполнена с помощью `memset`, `bios_putchar` вызывает прерывание [0x10](http://www.ctyme.com/intr/rb-0106.htm) для вывода символа. Затем она проверяет, инициализирован ли последовательный порт, и в случае если он инициализирован, записывает в него символ с помощью инструкций [serial_putchar](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/tty.c#L30) и `inb/outb`. Инициализация кучи -------------------------------------------------------------------------------- -После подготовки стека и BSS в [header.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S) (смотрите предыдущую [часть](linux-bootstrap-1.md)), ядро должно инициализировать [кучу](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L116) с помощью функции [`init_heap`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L116). +После подготовки стека и BSS в [header.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S) (смотрите предыдущую [часть](linux-bootstrap-1.md)), ядро должно инициализировать [кучу](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L116) с помощью функции [`init_heap`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L116). -В первую очередь `init_heap` проверяет флаг [`CAN_USE_HEAP`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/uapi/asm/bootparam.h#L22) в [`loadflags`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S#L321) в заголовке настройки ядра и если флаг был установлен, вычисляет конец стека: +В первую очередь `init_heap` проверяет флаг [`CAN_USE_HEAP`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/uapi/asm/bootparam.h#L22) в [`loadflags`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S#L321) в заголовке настройки ядра и если флаг был установлен, вычисляет конец стека: ```C char *stack_end; @@ -343,9 +343,9 @@ ENDPROC(memset) Проверка CPU -------------------------------------------------------------------------------- -Следующим шагом является проверка CPU с помощью функции `validate_cpu` из [arch/x86/boot/cpu.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/cpu.c). +Следующим шагом является проверка CPU с помощью функции `validate_cpu` из [arch/x86/boot/cpu.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/cpu.c). -Она вызывает функцию [`check_cpu`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/cpucheck.c#L112) и передаёт ей два параметра: уровень CPU и необходимый уровень CPU; `check_cpu` проверяет, запущено ли ядро на нужном уровне CPU. +Она вызывает функцию [`check_cpu`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/cpucheck.c#L112) и передаёт ей два параметра: уровень CPU и необходимый уровень CPU; `check_cpu` проверяет, запущено ли ядро на нужном уровне CPU. ```c check_cpu(&cpu_level, &req_level, &err_flags); @@ -362,7 +362,7 @@ if (cpu_level < req_level) { Следующим шагом является обнаружение памяти с помощью функции `detect_memory`. `detect_memory` в основном предоставляет карту доступной оперативной памяти для CPU. Она использует различные программные интерфейсы для обнаружения памяти, такие как `0xe820`, `0xe801` и `0x88`. Здесь мы будем рассматривать только реализацию **0xE820**. -Давайте посмотрим на реализацию фуцнкции `detect_memory_e820` в [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/memory.c). Прежде всего, функция `detect_memory_e820` инициализирует структуру `biosregs`, как мы видели выше, и заполняет регистры специальными значениями для вызова `0xe820`: +Давайте посмотрим на реализацию фуцнкции `detect_memory_e820` в [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/memory.c). Прежде всего, функция `detect_memory_e820` инициализирует структуру `biosregs`, как мы видели выше, и заполняет регистры специальными значениями для вызова `0xe820`: ```assembly initregs(&ireg); @@ -424,7 +424,7 @@ static void set_bios_mode(void) Инициализация клавиатуры -------------------------------------------------------------------------------- -Следующим шагом является инициализация клавиатуры с помощью вызова функции [`keyboard_init()`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L65). Вначале `keyboard_init` инициализирует регистры с помощью функции `initregs`. Затем он вызывает прерывание [0x16](http://www.ctyme.com/intr/rb-1756.htm) для получения статуса клавиатуры. +Следующим шагом является инициализация клавиатуры с помощью вызова функции [`keyboard_init()`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L65). Вначале `keyboard_init` инициализирует регистры с помощью функции `initregs`. Затем он вызывает прерывание [0x16](http://www.ctyme.com/intr/rb-1756.htm) для получения статуса клавиатуры. ```c initregs(&ireg); @@ -446,7 +446,7 @@ static void set_bios_mode(void) Первым шагом является получение информации [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) с помощью вызова функции `query_ist`. Она проверяет уровень CPU, и если он верный, вызывает прерывание `0x15` для получения информации и сохраняет результат в `boot_params`. -Следующая функция - [query_apm_bios](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/apm.c#L21) получает из BIOS информацию об [Advanced Power Management](http://en.wikipedia.org/wiki/Advanced_Power_Management). `query_apm_bios` также вызывает BIOS прерывание `0x15`, но с `ah = 0x53` для проверки поддержки `APM`. После выполнения `0x15`, функция `query_apm_bios` проверяет сигнатуру `PM` (она должна быть равна `0x504d`), флаг переноса (он должен быть равен 0, если есть поддержка `APM`) и значение регистра `cx` (оно должено быть равным 0x02, если имеется поддержка защищённого режима). +Следующая функция - [query_apm_bios](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/apm.c#L21) получает из BIOS информацию об [Advanced Power Management](http://en.wikipedia.org/wiki/Advanced_Power_Management). `query_apm_bios` также вызывает BIOS прерывание `0x15`, но с `ah = 0x53` для проверки поддержки `APM`. После выполнения `0x15`, функция `query_apm_bios` проверяет сигнатуру `PM` (она должна быть равна `0x504d`), флаг переноса (он должен быть равен 0, если есть поддержка `APM`) и значение регистра `cx` (оно должено быть равным 0x02, если имеется поддержка защищённого режима). Далее она снова вызывает `0x15`, но с `ax = 0x5304` для отсоединения от интерфейса `APM` и подключению к интерфейсу 32-битного защищённого режима. В итоге она заполняет `boot_params.apm_bios_info` значениями, полученными из BIOS. @@ -458,9 +458,9 @@ static void set_bios_mode(void) #endif ``` -Последняя функция - [`query_edd`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/edd.c#L122), запрашивает из BIOS информацию об `Enhanced Disk Drive`. Давайте взглянем на реализацию `query_edd`. +Последняя функция - [`query_edd`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/edd.c#L122), запрашивает из BIOS информацию об `Enhanced Disk Drive`. Давайте взглянем на реализацию `query_edd`. -В первую очередь она читает опцию [edd](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/kernel-parameters.txt#L1023) из командной строки ядра и если она установлена в `off`, то `query_edd` завершает свою работу. +В первую очередь она читает опцию [edd](https://github.com/torvalds/linux/blob/v4.16/Documentation/kernel-parameters.txt#L1023) из командной строки ядра и если она установлена в `off`, то `query_edd` завершает свою работу. Если EDD включён, `query_edd` сканирует поддерживаемые BIOS жёсткие диски и запрашивает информацию о EDD в следующем цикле: @@ -477,7 +477,7 @@ for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) { } ``` -где `0x80` - первый жёсткий диск, а значение макроса `EDD_MBR_SIG_MAX` равно 16. Она собирает данные в массив структур [edd_info](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/uapi/linux/edd.h#L172). `get_edd_info` проверяет наличие EDD путём вызова прерывания `0x13` с `ah = 0x41` и если EDD присутствует, `get_edd_info` снова вызывает `0x13`, но с `ah = 0x48` и `si`, содержащим адрес буфера, где будет храниться информация о EDD. +где `0x80` - первый жёсткий диск, а значение макроса `EDD_MBR_SIG_MAX` равно 16. Она собирает данные в массив структур [edd_info](https://github.com/torvalds/linux/blob/v4.16/include/uapi/linux/edd.h#L172). `get_edd_info` проверяет наличие EDD путём вызова прерывания `0x13` с `ah = 0x41` и если EDD присутствует, `get_edd_info` снова вызывает `0x13`, но с `ah = 0x48` и `si`, содержащим адрес буфера, где будет храниться информация о EDD. Заключение -------------------------------------------------------------------------------- @@ -495,8 +495,8 @@ for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) { * [Неплохое объяснение режимов CPU с кодом](http://www.codeproject.com/Articles/45788/The-Real-Protected-Long-mode-assembly-tutorial-for) * [Как использовать сегменты с ростом вниз на CPU Intel 386 и более поздних](http://www.sudleyplace.com/dpmione/expanddown.html) * [Документация по earlyprintk](http://lxr.free-electrons.com/source/Documentation/x86/earlyprintk.txt) -* [Параметры ядра](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/kernel-parameters.txt) -* [Последовательная консоль](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/serial-console.txt) +* [Параметры ядра](https://github.com/torvalds/linux/blob/v4.16/Documentation/kernel-parameters.txt) +* [Последовательная консоль](https://github.com/torvalds/linux/blob/v4.16/Documentation/serial-console.txt) * [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) * [APM](https://en.wikipedia.org/wiki/Advanced_Power_Management) * [Спецификация EDD](http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf) diff --git a/Booting/linux-bootstrap-3.md b/Booting/linux-bootstrap-3.md index 1fa713b..d4e742f 100644 --- a/Booting/linux-bootstrap-3.md +++ b/Booting/linux-bootstrap-3.md @@ -4,7 +4,7 @@ Инициализация видеорежима и переход в защищённый режим -------------------------------------------------------------------------------- -Это третья часть серии `Процесса загрузки ядра`. В предыдущей [части](linux-bootstrap-2.md#kernel-booting-process-part-2) мы остановились прямо перед вызовом функции `set_video` из [main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L181). В этой части мы увидим: +Это третья часть серии `Процесса загрузки ядра`. В предыдущей [части](linux-bootstrap-2.md#kernel-booting-process-part-2) мы остановились прямо перед вызовом функции `set_video` из [main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L181). В этой части мы увидим: - инициализацию видеорежима в коде настройки ядра, - подготовку, сделанную перед переключением в защищённый режим, @@ -12,7 +12,7 @@ **ПРИМЕЧАНИЕ:** если вы ничего не знаете о защищённом режиме, вы можете найти некоторую информацию о нём в предыдущей [части](linux-bootstrap-2.md#protected-mode). Также есть несколько [ссылок](linux-bootstrap-2.md#links), которые могут вам помочь. -Как я уже писал ранее, мы начнём с функции `set_video`, которая определена в [arch/x86/boot/video.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/video.c#L315). Как мы можем видеть, она начинает работу с получения видеорежима из структуры `boot_params.hdr`: +Как я уже писал ранее, мы начнём с функции `set_video`, которая определена в [arch/x86/boot/video.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/video.c#L315). Как мы можем видеть, она начинает работу с получения видеорежима из структуры `boot_params.hdr`: ```C u16 mode = boot_params.hdr.vid_mode; @@ -59,13 +59,13 @@ vga= API кучи -------------------------------------------------------------------------------- -После того как мы получим `vid_mode` из `boot_params.hdr` в функции `set_video`, мы можем видеть вызов `RESET_HEAP`. `RESET_HEAP` представляет собой макрос, определённый в [boot.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/boot.h#L199): +После того как мы получим `vid_mode` из `boot_params.hdr` в функции `set_video`, мы можем видеть вызов `RESET_HEAP`. `RESET_HEAP` представляет собой макрос, определённый в [boot.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/boot.h#L199): ```C #define RESET_HEAP() ((void *)( HEAP = _end )) ``` -Если вы читали вторую часть, то помните, что мы инициализировали кучу с помощью функции [`init_heap`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L116). У нас есть несколько полезных функций для кучи, которые определены в `boot.h`: +Если вы читали вторую часть, то помните, что мы инициализировали кучу с помощью функции [`init_heap`](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L116). У нас есть несколько полезных функций для кучи, которые определены в `boot.h`: ```C #define RESET_HEAP() @@ -277,9 +277,9 @@ static int vga_set_mode(struct mode_info *mode) Последняя подготовка перед переходом в защищённый режим -------------------------------------------------------------------------------- -Мы можем видеть последний вызов функции - `go_to_protected_mode` - в [main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L184). Как говорится в комментарии: `Do the last things and invoke protected mode`, так что давайте посмотрим на эти последние вещи и перейдём в защищённый режим. +Мы можем видеть последний вызов функции - `go_to_protected_mode` - в [main.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/main.c#L184). Как говорится в комментарии: `Do the last things and invoke protected mode`, так что давайте посмотрим на эти последние вещи и перейдём в защищённый режим. -Функция `go_to_protected_mode` определена в [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/pm.c#L104). Она содержит функции, которые совершают последние приготовления, прежде чем мы сможем перейти в защищённый режим, так что давайте посмотрим на них и попытаться понять, что они делают и как это работает. +Функция `go_to_protected_mode` определена в [arch/x86/boot/pm.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/pm.c#L104). Она содержит функции, которые совершают последние приготовления, прежде чем мы сможем перейти в защищённый режим, так что давайте посмотрим на них и попытаться понять, что они делают и как это работает. Во-первых, это вызов функции `realmode_switch_hook` в `go_to_protected_mode`. Эта функция вызывает хук переключения режима реальных адресов, если он присутствует, и выключает [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt). Хуки используются, если загрузчик работает во "враждебной" среде. Вы можете прочитать больше о хуках в [протоколе загрузки](https://www.kernel.org/doc/Documentation/x86/boot.txt) (см. **ADVANCED BOOT LOADER HOOKS**). @@ -308,7 +308,7 @@ static inline void io_delay(void) Для вывода любого байта в порт `0x80` необходима задержка в 1 мкс. Таким образом, мы можем записать любое значение (в нашем случае значение из регистра `AL`) в порт `0x80`. После задержки, функция `realmode_switch_hook` завершает выполнение и мы можем перейти к следующей функции. -Следующая функция - `enable_a20` - включает [линию A20](http://en.wikipedia.org/wiki/A20_line). Она определена в [arch/x86/boot/a20.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/a20.c) и совершает попытку включения шлюза адресной линии A20 различными методами. Первым из них является функция `a20_test_short`, которая проверят, является ли A20 включённой или нет с помощью функции `a20_test`: +Следующая функция - `enable_a20` - включает [линию A20](http://en.wikipedia.org/wiki/A20_line). Она определена в [arch/x86/boot/a20.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/a20.c) и совершает попытку включения шлюза адресной линии A20 различными методами. Первым из них является функция `a20_test_short`, которая проверят, является ли A20 включённой или нет с помощью функции `a20_test`: ```C static int a20_test(int loops) @@ -340,7 +340,7 @@ static int a20_test(int loops) Если линия A20 отключена, мы пытаемся включить её с помощью других методов, которые вы можете найти в `a20.c`. Например, это может быть сделано с помощью вызова BIOS прерывания `0x15` с `AH=0x2041` и т.д. -Если функция `enabled_a20` завершается неудачей, выводится сообщение об ошибке и вызывается функция `die`. Вы можете вспомнить её из первого файла исходного кода, откуда мы начали - [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/header.S): +Если функция `enabled_a20` завершается неудачей, выводится сообщение об ошибке и вызывается функция `die`. Вы можете вспомнить её из первого файла исходного кода, откуда мы начали - [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/header.S): ```assembly die: @@ -499,7 +499,7 @@ asm volatile("lgdtl %0" : : "m" (gdt)); protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4)); ``` -которая определена в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/pmjump.S#L26). Она получает два параметра: +которая определена в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/pmjump.S#L26). Она получает два параметра: * адрес точки входа в защищённый режим * адрес `boot_params` diff --git a/Booting/linux-bootstrap-4.md b/Booting/linux-bootstrap-4.md index e3dfb1a..fe88f3b 100644 --- a/Booting/linux-bootstrap-4.md +++ b/Booting/linux-bootstrap-4.md @@ -8,7 +8,7 @@ **ЗАМЕЧАНИЕ: данная часть содержит много ассемблерного кода, так что если вы не знакомы с ним, вы можете прочитать соответствующую литературу** -В предыдущей [части](linux-bootstrap-3.md) мы остановились на переходе к 32-битной точке входа в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/pmjump.S): +В предыдущей [части](linux-bootstrap-3.md) мы остановились на переходе к 32-битной точке входа в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/pmjump.S): ```assembly jmpl *%eax @@ -46,7 +46,7 @@ gs 0x18 24 32-битная точка входа -------------------------------------------------------------------------------- -Мы можем найти определение 32-битной точки входа в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +Мы можем найти определение 32-битной точки входа в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S): ```assembly __HEAD @@ -62,10 +62,10 @@ ENDPROC(startup_32) В директории `arch/x86/boot/compressed` содержится два файла: -* [head_32.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_32.S) -* [head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) +* [head_32.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_32.S) +* [head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S) -но мы будем рассматривать только `head_64.S`, потому что, как вы помните, эта книга только о `x86_64`; `head_32.S` в нашем случае не используется. Давайте посмотрим на [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/Makefile). Здесь мы можем увидить следующую цель сборки: +но мы будем рассматривать только `head_64.S`, потому что, как вы помните, эта книга только о `x86_64`; `head_32.S` в нашем случае не используется. Давайте посмотрим на [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/Makefile). Здесь мы можем увидить следующую цель сборки: ```Makefile vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ @@ -73,7 +73,7 @@ vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \ $(obj)/piggy.o $(obj)/cpuflags.o ``` -Обратите внимание на `$(obj)/head_$(BITS).o`. Это означает, что выбор файла (head_32.o или head_64.o) для линковки будет зависеть от значения `$(BITS)`. `$(BITS)` определён в [arch/x86/Makefile](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/Makefile), основанном на .config файле: +Обратите внимание на `$(obj)/head_$(BITS).o`. Это означает, что выбор файла (head_32.o или head_64.o) для линковки будет зависеть от значения `$(BITS)`. `$(BITS)` определён в [arch/x86/Makefile](https://github.com/torvalds/linux/blob/v4.16/arch/x86/Makefile), основанном на .config файле: ```Makefile ifeq ($(CONFIG_X86_32),y) @@ -90,7 +90,7 @@ endif Перезагрузка сегментов, если это необходимо -------------------------------------------------------------------------------- -Как было отмечено выше, мы начинаем с ассемблерного файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S). Во-первых, мы видим определение специального атрибута секции перед определением `startup_32`: +Как было отмечено выше, мы начинаем с ассемблерного файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S). Во-первых, мы видим определение специального атрибута секции перед определением `startup_32`: ```assembly __HEAD @@ -98,13 +98,13 @@ endif ENTRY(startup_32) ``` -`__HEAD` является макросом, определённым в [include/linux/init.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/init.h) и представляет собой следующую секцию: +`__HEAD` является макросом, определённым в [include/linux/init.h](https://github.com/torvalds/linux/blob/v4.16/include/linux/init.h) и представляет собой следующую секцию: ```C #define __HEAD .section ".head.text","ax" ``` -с именем `.head.text` и флагами `ax`. В нашем случае эти флаги означают, что секция является [исполняемой](https://en.wikipedia.org/wiki/Executable) или, другими словами, содержит код. Мы можем найти определение этой секции в скрипте компоновщика [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S): +с именем `.head.text` и флагами `ax`. В нашем случае эти флаги означают, что секция является [исполняемой](https://en.wikipedia.org/wiki/Executable) или, другими словами, содержит код. Мы можем найти определение этой секции в скрипте компоновщика [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/vmlinux.lds.S): ``` SECTIONS @@ -155,7 +155,7 @@ Be careful parts of head_64.S assume startup_32 is at address 0. movl %eax, %ss ``` -Вы помните, что `__BOOT_DS` равен `0x18` (индекс сегмента данных в [глобальной таблице дескрипторов](https://en.wikipedia.org/wiki/Global_Descriptor_Table)). Если `KEEP_SEGMENTS` установлен, мы переходим на ближайшую метку `1f`, иначе обновляем сегментные регистры значением `__BOOT_DS`. Сделать это довольно легко, но есть один интересный момент. Если вы читали предыдущую [часть](linux-bootstrap-3.md), то помните, что мы уже обновили сегментные регистры сразу после перехода в [защищённый режим](https://en.wikipedia.org/wiki/Protected_mode) в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/pmjump.S). Так почему же нам снова нужно обновить значения в сегментных регистрах? Ответ прост. Ядро Linux также имеет 32-битный протокол загрузки и если загрузчик использует его для загрузки ядра, то весь код до `startup_32` будет пропущен. В этом случае `startup_32` будет первой точкой входа в ядро, и нет никаких гарантий, что сегментные регистры будут находиться в ожидаемом состоянии. +Вы помните, что `__BOOT_DS` равен `0x18` (индекс сегмента данных в [глобальной таблице дескрипторов](https://en.wikipedia.org/wiki/Global_Descriptor_Table)). Если `KEEP_SEGMENTS` установлен, мы переходим на ближайшую метку `1f`, иначе обновляем сегментные регистры значением `__BOOT_DS`. Сделать это довольно легко, но есть один интересный момент. Если вы читали предыдущую [часть](linux-bootstrap-3.md), то помните, что мы уже обновили сегментные регистры сразу после перехода в [защищённый режим](https://en.wikipedia.org/wiki/Protected_mode) в [arch/x86/boot/pmjump.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/pmjump.S). Так почему же нам снова нужно обновить значения в сегментных регистрах? Ответ прост. Ядро Linux также имеет 32-битный протокол загрузки и если загрузчик использует его для загрузки ядра, то весь код до `startup_32` будет пропущен. В этом случае `startup_32` будет первой точкой входа в ядро, и нет никаких гарантий, что сегментные регистры будут находиться в ожидаемом состоянии. После того как мы проверили флаг `KEEP_SEGMENTS` и установили правильное значение в сегментные регистры, следующим шагом будет вычисление разницы между адресом, по которому мы загружены, и адресом, который был указан во время компиляции. Вы помните, что `setup.ld.S` содержит следующее определение в начале секции: `.head.text`: `. = 0`. Это значит, что код в этой секции скомпилирован для запуска по адресу `0`. Мы можем видеть это в выводе `objdump`: @@ -186,7 +186,7 @@ label: pop %reg subl $1b, %ebp ``` -Как вы помните из предыдущей части, регистр `esi` содержит адрес структуры [boot_params](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/uapi/asm/bootparam.h#L113), которая была заполнена до перехода в защищённый режим. Структура `boot_params` содержит специальное поле `scratch` со смещением `0x1e4`. Это 4 байтное поле будет временным стеком для инструкции `call`. Мы получаем адрес поля `scratch` + `4` байта и помещаем его в регистр `esp`. Мы добавили `4` байта к базовому адресу поля `BP_scratch`, поскольку поле является временным стеком, а стек на архитектуре `x86_64` растёт сверху вниз. Таким образом, наш указатель стека будет указывать на вершину стека. Далее мы видим наш шаблон, который я описал ранее. Мы переходим на метку `1f` и помещаем её адрес в регистр `ebp`, потому что после выполнения инструкции `call` на вершине стека находится адрес возврата. Теперь у нас есть адрес метки `1f` и мы легко сможем получить адрес `startup_32`. Нам просто нужно вычесть адрес метки из адреса, который мы получили из стека: +Как вы помните из предыдущей части, регистр `esi` содержит адрес структуры [boot_params](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/uapi/asm/bootparam.h#L113), которая была заполнена до перехода в защищённый режим. Структура `boot_params` содержит специальное поле `scratch` со смещением `0x1e4`. Это 4 байтное поле будет временным стеком для инструкции `call`. Мы получаем адрес поля `scratch` + `4` байта и помещаем его в регистр `esp`. Мы добавили `4` байта к базовому адресу поля `BP_scratch`, поскольку поле является временным стеком, а стек на архитектуре `x86_64` растёт сверху вниз. Таким образом, наш указатель стека будет указывать на вершину стека. Далее мы видим наш шаблон, который я описал ранее. Мы переходим на метку `1f` и помещаем её адрес в регистр `ebp`, потому что после выполнения инструкции `call` на вершине стека находится адрес возврата. Теперь у нас есть адрес метки `1f` и мы легко сможем получить адрес `startup_32`. Нам просто нужно вычесть адрес метки из адреса, который мы получили из стека: ``` startup_32 (0x0) +-----------------------+ @@ -262,7 +262,7 @@ ebp 0x100000 0x100000 movl %eax, %esp ``` -Метка `boot_stack_end` определена в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) и расположена в секции [.bss](https://en.wikipedia.org/wiki/.bss): +Метка `boot_stack_end` определена в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S) и расположена в секции [.bss](https://en.wikipedia.org/wiki/.bss): ```assembly .bss @@ -284,7 +284,7 @@ boot_stack_end: jnz no_longmode ``` -Она определена в [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/verify_cpu.S) и содержит пару вызовов инструкции [CPUID](https://en.wikipedia.org/wiki/CPUID). Данная инструкция +Она определена в [arch/x86/kernel/verify_cpu.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/kernel/verify_cpu.S) и содержит пару вызовов инструкции [CPUID](https://en.wikipedia.org/wiki/CPUID). Данная инструкция используется для получения информации о процессоре. В нашем случае она проверяет поддержку `long mode` и `SSE` и с помощью регистра `eax` возвращает `0` в случае успеха или `1` в случае неудачи. Если значение `eax` не равно нулю, то мы переходим на метку `no_longmode`, которая останавливает CPU вызовом инструкции `hlt` до тех пор, пока не произойдёт аппаратное прерывание: @@ -313,7 +313,7 @@ no_longmode: (CONFIG_PHYSICAL_START) используется как минимальная локация. ``` -Проще говоря, это означает, что ядро с той же конфигурацией может загружаться с разных адресов. С технической точки зрения это делается путём компиляции декомпрессора как [адресно-независимого кода](https://en.wikipedia.org/wiki/Position-independent_code). Если мы посмотрим на [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/Makefile), то мы увидим, что декомпрессор действительно скомпилирован с флагом `-fPIC`: +Проще говоря, это означает, что ядро с той же конфигурацией может загружаться с разных адресов. С технической точки зрения это делается путём компиляции декомпрессора как [адресно-независимого кода](https://en.wikipedia.org/wiki/Position-independent_code). Если мы посмотрим на [arch/x86/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/Makefile), то мы увидим, что декомпрессор действительно скомпилирован с флагом `-fPIC`: ```Makefile KBUILD_CFLAGS += -fno-strict-aliasing -fPIC @@ -335,7 +335,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC movl $LOAD_PHYSICAL_ADDR, %ebx ``` -Следует помнить, что регистр `ebp` содержит физический адрес метки `startup_32`. Если параметр `CONFIG_RELOCATABLE` включён во время конфигурации ядра, то мы помещаем этот адрес в регистр `ebx`, выравниваем по границе, кратной `2 Мб` и сравниваем его со значением `LOAD_PHYSICAL_ADDR`. `LOAD_PHYSICAL_ADDR` является макросом, определённым в [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/boot.h) и выглядит следующим образом: +Следует помнить, что регистр `ebp` содержит физический адрес метки `startup_32`. Если параметр `CONFIG_RELOCATABLE` включён во время конфигурации ядра, то мы помещаем этот адрес в регистр `ebx`, выравниваем по границе, кратной `2 Мб` и сравниваем его со значением `LOAD_PHYSICAL_ADDR`. `LOAD_PHYSICAL_ADDR` является макросом, определённым в [arch/x86/include/asm/boot.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/asm/boot.h) и выглядит следующим образом: ```C #define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \ @@ -368,7 +368,7 @@ KBUILD_CFLAGS += -fno-strict-aliasing -fPIC Здесь мы настраиваем базовый адрес `глобальной таблицы дескрипторов` на адрес, где мы фактически загружены, и загружаем таблицу с помощью инструкции `lgdt`. -Чтобы понять магию смещений `gdt`, нам нужно взглянуть на определение `глобальной таблицы дескрипторов`. Мы можем найти его определение в том же [файле](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) исходного кода: +Чтобы понять магию смещений `gdt`, нам нужно взглянуть на определение `глобальной таблицы дескрипторов`. Мы можем найти его определение в том же [файле](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S) исходного кода: ```assembly .data @@ -456,7 +456,7 @@ Long mode является расширением унаследованного Инструкция `rep stosl` записывает значение `eax` в `edi`, увеличивает значение в регистре `edi` на `4` и уменьшает значение в регистре `ecx` на `1`. Эта операция будет повторятся до тех пор, пока значение регистра `ecx` больше нуля. Вот почему мы установили `ecx` в `6144` (или `BOOT_INIT_PGT_SIZE/4`). -Структура `pgtable` определена в конце файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +Структура `pgtable` определена в конце файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S): ```assembly .section ".pgtable","a",@nobits @@ -542,7 +542,7 @@ pgtable: wrmsr ``` -Здесь мы помещаем флаг `MSR_EFER` (который определён в [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/uapi/asm/msr-index.h#L7)) в регистр `ecx` и вызываем инструкцию `rdmsr`, которая считывает регистр [MSR](http://en.wikipedia.org/wiki/Model-specific_register). После выполнения `rdmsr`, полученные данные будут находится в `edx:eax`, которые будут зависеть от значения `ecx`. Далее мы проверяем бит `EFER_LME` инструкцией `btsl` и с помощью инструкции `wrmsr` записываем данные из `eax` в регистр `MSR`. +Здесь мы помещаем флаг `MSR_EFER` (который определён в [arch/x86/include/uapi/asm/msr-index.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/uapi/asm/msr-index.h#L7)) в регистр `ecx` и вызываем инструкцию `rdmsr`, которая считывает регистр [MSR](http://en.wikipedia.org/wiki/Model-specific_register). После выполнения `rdmsr`, полученные данные будут находится в `edx:eax`, которые будут зависеть от значения `ecx`. Далее мы проверяем бит `EFER_LME` инструкцией `btsl` и с помощью инструкции `wrmsr` записываем данные из `eax` в регистр `MSR`. На следующем шаге мы помещаем адрес сегмента кода ядра в стек (мы определили его в GDT) и помещаем адрес функции `startup_64` в `eax`. diff --git a/Booting/linux-bootstrap-5.md b/Booting/linux-bootstrap-5.md index 48a0aad..1839c4b 100644 --- a/Booting/linux-bootstrap-5.md +++ b/Booting/linux-bootstrap-5.md @@ -9,7 +9,7 @@ Подготовка к декомпрессии ядра -------------------------------------------------------------------------------- -Мы остановились прямо перед переходом к `64-битной` точке входа - `startup_64`, расположенной в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S). В предыдущей части мы уже видели переход к `startup_64` в `startup_32`: +Мы остановились прямо перед переходом к `64-битной` точке входа - `startup_64`, расположенной в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S). В предыдущей части мы уже видели переход к `startup_64` в `startup_32`: ```assembly pushl $__KERNEL_CS @@ -75,7 +75,7 @@ ENTRY(startup_64) Если вы посмотрите на исходный код ядра Linux после команды `lgdt gdt64(%rip)`, вы увидите, что есть некоторый дополнительный код. Этот код необходим для включения [пятиуровневой страничной организации](https://lwn.net/Articles/708526/), в случае необходимости. В этой книге мы рассмотрим только четырёхуровневую страничную организацию, поэтому этот код будет проигнорирован. -Как вы можете видеть выше, регистр `rbx` содержит начальный адрес кода декомпрессора ядра, и мы помещаем этот адрес со смещением `boot_stack_end` в регистр `rsp`, который представляет указатель на вершину стека. После этого шага стек будет корректным. Вы можете найти определение `boot_stack_end` в конце [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +Как вы можете видеть выше, регистр `rbx` содержит начальный адрес кода декомпрессора ядра, и мы помещаем этот адрес со смещением `boot_stack_end` в регистр `rsp`, который представляет указатель на вершину стека. После этого шага стек будет корректным. Вы можете найти определение `boot_stack_end` в конце [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S): ```assembly .bss @@ -87,7 +87,7 @@ boot_stack: boot_stack_end: ``` -Он расположен в конце секции `.bss`, прямо перед таблицей `.pgtable`. Если вы посмотрите сценарий компоновщика [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S), вы найдёте определения `.bss` и `.pgtable`. +Он расположен в конце секции `.bss`, прямо перед таблицей `.pgtable`. Если вы посмотрите сценарий компоновщика [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/vmlinux.lds.S), вы найдёте определения `.bss` и `.pgtable`. После того как стек был настроен, мы можем скопировать сжатое ядро по адресу, который мы получили выше после вычисления адреса релокации распакованного ядра. Прежде чем перейти к деталям, давайте посмотрим на этот ассемблерный код: @@ -105,7 +105,7 @@ boot_stack_end: Прежде всего, мы помещаем `rsi` в стек. Нам нужно сохранить значение `rsi`, потому что теперь этот регистр хранит указатель на `boot_params`, которая является структурой режима реальных адресов, содержащая связанные с загрузкой данные (вы должны помнить эту структуру, мы заполняли её в начале кода настройки ядра). В конце этого кода мы снова восстановим указатель на `boot_params` в `rsi`. -Следующие две инструкции `leaq` вычисляют эффективные адреса `rip` и `rbx` со смещением `_bss - 8` и помещают их в `rsi` и `rdi`. Зачем мы вычисляем эти адреса? На самом деле сжатый образ ядра находится между этим кодом копирования (от `startup_32` до текущего кода) и кодом декомпрессии. Вы можете проверить это, посмотрев сценарий компоновщика - [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/vmlinux.lds.S): +Следующие две инструкции `leaq` вычисляют эффективные адреса `rip` и `rbx` со смещением `_bss - 8` и помещают их в `rsi` и `rdi`. Зачем мы вычисляем эти адреса? На самом деле сжатый образ ядра находится между этим кодом копирования (от `startup_32` до текущего кода) и кодом декомпрессии. Вы можете проверить это, посмотрев сценарий компоновщика - [arch/x86/boot/compressed/vmlinux.lds.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/vmlinux.lds.S): ``` . = 0; @@ -191,9 +191,9 @@ relocated: popq %rsi ``` -Мы снова устанавливаем `rdi` в указатель на структуру `boot_params` и сохраняем его в стек. В то же время мы устанавливаем `rsi` для указания на область, которая должа использоваться для распаковки ядра. Последним шагом является подготовка параметров `extract_kernel` и вызов этой функции для распаковки ядра. Функция `extract_kernel` определена в [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) и принимает шесть аргументов: +Мы снова устанавливаем `rdi` в указатель на структуру `boot_params` и сохраняем его в стек. В то же время мы устанавливаем `rsi` для указания на область, которая должа использоваться для распаковки ядра. Последним шагом является подготовка параметров `extract_kernel` и вызов этой функции для распаковки ядра. Функция `extract_kernel` определена в [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/misc.c) и принимает шесть аргументов: -* `rmode` - указатель на структуру [boot_params](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973//arch/x86/include/uapi/asm/bootparam.h#L114), которая заполнена загрузчиком или во время ранней инициализации ядра; +* `rmode` - указатель на структуру [boot_params](https://github.com/torvalds/linux/blob/v4.16//arch/x86/include/uapi/asm/bootparam.h#L114), которая заполнена загрузчиком или во время ранней инициализации ядра; * `heap` - указатель на `boot_heap`, представляющий собой начальный адрес ранней загрузочной кучи; * `input_data` - указатель на начало сжатого ядра или, другими словами, указатель на `arch/x86/boot/compressed/vmlinux.bin.bz2`; * `input_len` - размер сжатого ядра; @@ -205,7 +205,7 @@ relocated: Декомпрессия ядра -------------------------------------------------------------------------------- -Как мы видели в предыдущем абзаце, функция `extract_kernel` определена [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c) и содержит шесть аргументов. Эта функция начинается с инициализации видео/консоли, которую мы уже видели в предыдущих частях. Нам нужно сделать это ещё раз, потому что мы не знаем, находились ли мы в [режиме реальных адресов](https://en.wikipedia.org/wiki/Real_mode), использовался ли загрузчик, или загрузчик использовал `32` или `64-битный` протокол загрузки. +Как мы видели в предыдущем абзаце, функция `extract_kernel` определена [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/misc.c) и содержит шесть аргументов. Эта функция начинается с инициализации видео/консоли, которую мы уже видели в предыдущих частях. Нам нужно сделать это ещё раз, потому что мы не знаем, находились ли мы в [режиме реальных адресов](https://en.wikipedia.org/wiki/Real_mode), использовался ли загрузчик, или загрузчик использовал `32` или `64-битный` протокол загрузки. После первых шагов инициализации мы сохраняем указатели на начало и конец свободной памяти: @@ -214,7 +214,7 @@ free_mem_ptr = heap; free_mem_end_ptr = heap + BOOT_HEAP_SIZE; ``` -где `heap` является вторым параметром функции `extract_kernel`, который мы получили в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +где `heap` является вторым параметром функции `extract_kernel`, который мы получили в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S): ```assembly leaq boot_heap(%rip), %rsi @@ -229,11 +229,11 @@ boot_heap: где `BOOT_HEAP_SIZE` - это макрос, который раскрывается в `0x10000` (`0x400000` в случае `bzip2` ядра) и представляет собой размер кучи. -После инициализации указателей кучи, следующий шаг - вызов функции `choose_random_location` из [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/kaslr.c#L425). Как можно догадаться из названия функции, она выбирает ячейку памяти, в которой будет разархивирован образ ядра. Может показаться странным, что нам нужно найти или даже `выбрать` место для декомпрессии сжатого образа ядра, но ядро Linux поддерживает технологию [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization), которая позволяет загрузить распакованное ядро по случайному адресу из соображений безопасности. +После инициализации указателей кучи, следующий шаг - вызов функции `choose_random_location` из [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr.c#L425). Как можно догадаться из названия функции, она выбирает ячейку памяти, в которой будет разархивирован образ ядра. Может показаться странным, что нам нужно найти или даже `выбрать` место для декомпрессии сжатого образа ядра, но ядро Linux поддерживает технологию [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization), которая позволяет загрузить распакованное ядро по случайному адресу из соображений безопасности. Мы не будем рассматривать рандомизацию адреса загрузки ядра Linux в этой части, но сделаем это в следующей части. -Теперь мы вернёмся к [misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c#L404). После получения адреса для образа ядра мы должны были совершить некоторые проверки и убедиться в том, что полученный случайный адрес правильно выровнен и является корректным: +Теперь мы вернёмся к [misc.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/misc.c#L404). После получения адреса для образа ядра мы должны были совершить некоторые проверки и убедиться в том, что полученный случайный адрес правильно выровнен и является корректным: ```C if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1)) @@ -365,7 +365,7 @@ if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || Следующим шагом после функции `parse_elf` является вызов функции `handle_relocations`. Реализация этой функции зависит от опции конфигурации ядра `CONFIG_X86_NEED_RELOCS`, и если она включена, то эта функция корректирует адреса в образе ядра и вызывается только в том случае, если во время конфигурации ядра была включена опция конфигурации `CONFIG_RANDOMIZE_BASE`. Реализация функции `handle_relocations` достаточно проста. Эта функция вычитает значение `LOAD_PHYSICAL_ADDR` из значения базового адреса загрузки ядра и, таким образом, мы получаем разницу между тем, где ядро было слинковано для загрузки и тем, где оно было фактически загружено. После этого мы можем выполнить релокацию ядра, поскольку мы знаем фактический адрес, по которому было загружено ядро, адрес по которому оно было слинковано для запуска и таблицу релокации, которая находится в конце образа ядра. -После перемещения ядра мы возвращаемся из `extract_kernel` обратно в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S). +После перемещения ядра мы возвращаемся из `extract_kernel` обратно в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S). Адрес ядра находится в регистре `rax` и мы совершаем переход по нему: diff --git a/LINKS.md b/LINKS.md index c11fd0a..f193ba9 100644 --- a/LINKS.md +++ b/LINKS.md @@ -5,7 +5,7 @@ ------------------------ * [Протокол загрузки Linux/x86](https://www.kernel.org/doc/Documentation/x86/boot.txt) -* [Параметры ядра Linux](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/Documentation/admin-guide/kernel-parameters.rst) +* [Параметры ядра Linux](https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/kernel-parameters.rst) Защищённый режим ------------------------