From 49a80bf6cfdb4f39fff0b51f332f79654ce66c74 Mon Sep 17 00:00:00 2001 From: proninyaroslav Date: Mon, 5 Nov 2018 18:38:52 +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=20linux-initialization-1.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Initialization/linux-initialization-1.md | 355 +++++++++++++---------- 1 file changed, 208 insertions(+), 147 deletions(-) diff --git a/Initialization/linux-initialization-1.md b/Initialization/linux-initialization-1.md index 06a874d..498b0fa 100644 --- a/Initialization/linux-initialization-1.md +++ b/Initialization/linux-initialization-1.md @@ -4,20 +4,20 @@ Первые шаги в коде ядра -------------------------------------------------------------------------------- -Предыдущая [статья](../Booting/linux-bootstrap-6.md) была последней частью главы [процесса загрузки](../Booting/README.md) ядра Linux и теперь мы начинаем погружение в процесс инициализации. После того как образ ядра Linux распакован и помещён в нужное место, ядро начинает свою работу. Все предыдущие части описывают работу кода настройки ядра, который выполняет подготовку до того, как будут выполнены первые байты кода ядра Linux. Теперь мы находимся в ядре, и все части этой главы будут посвящены процессу инициализации ядра, прежде чем оно запустит процесс с помощью [pid](https://en.wikipedia.org/wiki/Process_identifier) `1`. Есть ещё много вещей, который необходимо сделать, прежде чем ядро запустит первый `init` процесс. Мы начнём с точки входа в ядро, которая находится в [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S) и будем двигаться дальше и дальше. Мы увидим первые приготовления, такие как инициализацию начальных таблиц страниц, переход на новый дескриптор в пространстве ядра и многое другое, прежде чем увидим запуск функции `start_kernel` в [init/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/init/main.c#L489). +Предыдущая [статья](../Booting/linux-bootstrap-6.md) была последней частью главы [процесса загрузки](../Booting/README.md) ядра Linux и теперь мы начинаем погружение в процесс инициализации. После того как образ ядра Linux распакован и помещён в нужное место, ядро начинает свою работу. Все предыдущие части описывают работу кода настройки ядра, который выполняет подготовку до того, как будут выполнены первые байты кода ядра Linux. Теперь мы находимся в ядре, и все части этой главы будут посвящены процессу инициализации ядра, прежде чем оно запустит процесс с помощью [pid](https://en.wikipedia.org/wiki/Process_identifier) `1`. Есть ещё много вещей, который необходимо сделать, прежде чем ядро запустит первый `init` процесс. Мы начнём с точки входа в ядро, которая находится в [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S) и будем двигаться дальше и дальше. Мы увидим первые приготовления, такие как инициализацию начальных таблиц страниц, переход на новый дескриптор в пространстве ядра и многое другое, прежде чем увидим запуск функции `start_kernel` в [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c#L489). -В последней [части](../Booting/linux-bootstrap-6.md) предыдущей [главы](../Booting/README.md) мы остановились на инструкции [jmp](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S) из ассемблерного файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/head_64.S): +В последней [части](../Booting/linux-bootstrap-6.md) предыдущей [главы](../Booting/README.md) мы остановились на инструкции [jmp](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S) из ассемблерного файла [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/head_64.S): ```assembly jmp *%rax ``` -В данный момент регистр `rax` содержит адрес точки входа в ядро Linux, который был получен в результате вызова функции `decompress_kernel` из файла [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/compressed/misc.c). Итак, наша последняя инструкция в коде настройки ядра - это переход на точку входа. Мы уже знаем, где она определена, поэтому мы можем начать изучение того, что делает ядро Linux после запуска. +В данный момент регистр `rax` содержит адрес точки входа в ядро Linux, который был получен в результате вызова функции `decompress_kernel` из файла [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/compressed/misc.c). Итак, наша последняя инструкция в коде настройки ядра - это переход на точку входа. Мы уже знаем, где определена точка входа ядра Linux, поэтому мы можем начать изучать, что делает ядро Linux после запуска. Первые шаги в ядре -------------------------------------------------------------------------------- -Хорошо, мы получили адрес распакованного образа ядра с помощью функции `decompress_kernel` в регистр `rax`. Как мы уже знаем, начальная точка распакованного образа ядра находится в файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S), а также в его начале можно увидеть следующие определения: +Хорошо, мы получили адрес распакованного образа ядра с помощью функции `decompress_kernel` в регистр `rax`. Как мы уже знаем, начальная точка распакованного образа ядра находится в файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S), а также в его начале можно увидеть следующие определения: ```assembly0 .text @@ -36,7 +36,7 @@ startup_64: #define __HEAD .section ".head.text","ax" ``` -Определение данной секции расположено в скрипте компоновщика [arch/x86/kernel/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/vmlinux.lds.S#L93): +Определение данной секции расположено в скрипте компоновщика [arch/x86/kernel/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/vmlinux.lds.S#L93): ``` .text : AT(ADDR(.text) - LOAD_OFFSET) { @@ -53,7 +53,7 @@ startup_64: . = __START_KERNEL; ``` -для [x86_64](https://en.wikipedia.org/wiki/X86-64). Определение макроса `__START_KERNEL` находится в заголовочном файле [arch/x86/include/asm/page_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/page_types.h) и представлен суммой базового виртуального адреса отображения ядра и физического начала: +для [x86_64](https://en.wikipedia.org/wiki/X86-64). Определение макроса `__START_KERNEL` находится в заголовочном файле [arch/x86/include/asm/page_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_types.h) и представлен суммой базового виртуального адреса отображения ядра и физического начала: ```C #define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START) @@ -66,29 +66,51 @@ startup_64: * Базовый физический адрес ядра Linux - `0x1000000`; * Базовый виртуальный адрес ядра Linux - `0xffffffff81000000`. -Теперь мы знаем физические и виртуальные адреса по умолчанию подпрограммы `startup_64`, но для того чтобы узнать фактические адреса, мы должны вычислить их с помощью следующего кода: +После того как мы очистили конфигурацию CPU, мы вызываем функцию `__startup_64`, которая определена в [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head64.c: ```assembly - leaq _text(%rip), %rbp - subq $_text - __START_KERNEL_map, %rbp + leaq _text(%rip), %rdi + pushq %rsi + call __startup_64 + popq %rsi ``` -Да, он определён как `0x1000000`, но может быть другим, например, если включён [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization#Linux). Поэтому наша текущая цель - вычислить разницу между `0x1000000` и тем, где мы действительно загружены. Мы просто помещаем `rip-относительный` адрес в регистр `rbp`, а затем вычитаем из него `$_text - __START_KERNEL_map`. Мы знаем, что скомпилированный виртуальный адрес `_text` равен `0xffffffff81000000`, а физический - `0x1000000`. `__START_KERNEL_map` расширяется до адреса `0xffffffff80000000`, поэтому во второй строке ассемблерного кода мы получим следующее выражение: - +```C +unsigned log __head __startup_64(unsigned long physaddr, + struct boot_params *bp) +{ + unsigned long load_delta, *p; + unsigned long pgtable_flags; + pgdval_t *pgd; + p4dval_t *p4d; + pudval_t *pud; + pmdval_t *pmd, pmd_entry; + pteval_t *mask_ptr; + bool la57; + int i; + unsigned int *next_pgt_ptr; + ... + ... + ... +} ``` -rbp = 0x1000000 - (0xffffffff81000000 - 0xffffffff80000000) + +Поскольку [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization#Linux) включен, адрес `start_64` может отличаться от адреса, скомпилированного для запуска, поэтому нам нужно вычислить дельту с помощью следующего кода: + +```C + load_delta = physaddr - (unsigned long)(_text - __START_KERNEL_map); ``` -После вычисления регистр `rbp` будет содержать `0`, который представляет разницу между адресом где мы фактически загрузились, и адресом где был скомпилирован код. В нашем случае `ноль` означает, что ядро Linux было загружено по дефолтному адресу и [kASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization#Linux) отключён. +В результате `load_delta` содержит дельту между адресом, скомпилированным для запуска, и текущим адресом. -После того как мы получили адрес `startup_64`, нам необходимо проверить, правильно ли он выровнен. Мы сделаем это с помощью следующего кода: +После того как мы получили дельту, мы проверяем правильность выравнивания адреса `_text` по `2` мегабайтам. Мы сделаем это с помощью следующего кода: ```assembly - testl $~PMD_PAGE_MASK, %ebp - jnz bad_address + if (load_delta & ~PMD_PAGE_MASK) + for (;;); ``` -Мы сравниваем нижнюю часть регистра `rbp` с дополняемым значением `PMD_PAGE_MASK`. `PMD_PAGE_MASK` указывает маску для `промежуточного каталога страниц` (см. [страничную организацию памяти](../Theory/linux-theory-1.md)) и определён как: +Если адрес `_text` не выровнен по `2` мегабайтам, мы входим в бесконечный цикл. `PMD_PAGE_MASK` указывает маску для `промежуточного каталога страниц` (см. [страничную организацию памяти](../Theory/linux-theory-1.md)) и определён как: ```C #define PMD_PAGE_MASK (~(PMD_PAGE_SIZE-1)) @@ -98,23 +120,16 @@ rbp = 0x1000000 - (0xffffffff81000000 - 0xffffffff80000000) ``` #define PMD_PAGE_SIZE (_AC(1, UL) << PMD_SHIFT) -#define PMD_SHIFT 21 +#define PMD_SHIFT 21 ``` -Размер `PMD_PAGE_SIZE` можно легко вычислить - он составляет `2` мегабайта. Здесь мы используем стандартную формулу для проверки выравнивания, и если адрес `text` не выровнен по `2` мегабайтам, то переходим на метку `bad_address`. - -После этого мы проверяем адрес на то, что он не слишком велик, путём проверки наивысших `18` бит: - -```assembly - leaq _text(%rip), %rax - shrq $MAX_PHYSMEM_BITS, %rax - jnz bad_address -``` +Размер `PMD_PAGE_SIZE` можно легко вычислить - он составляет `2` мегабайта. -Адрес не должен превышать `46` бит: +Если поддержка [SME](https://en.wikipedia.org/wiki/Zen_(microarchitecture)#Enhanced_security_and_virtualization_support) включена, мы активируем её и включаем маску шифрования SME в `load_delta`: ```C -#define MAX_PHYSMEM_BITS 46 + sme_enable(bp); + load_delta += sme_get_me_mask(); ``` Хорошо, мы сделали некоторые начальные проверки, и теперь можем двигаться дальше. @@ -122,26 +137,34 @@ rbp = 0x1000000 - (0xffffffff81000000 - 0xffffffff80000000) Исправление базовых адресов таблиц страниц -------------------------------------------------------------------------------- -Первым шагом, прежде чем начать настройку отображения страничной организации "один в один" (identity paging), является исправление следующих адресов: +На следующем этапе мы исправляем физические адреса в таблице страниц: -```assembly - addq %rbp, early_top_pgt + (L4_START_KERNEL*8)(%rip) - addq %rbp, level3_kernel_pgt + (510*8)(%rip) - addq %rbp, level3_kernel_pgt + (511*8)(%rip) - addq %rbp, level2_fixmap_pgt + (506*8)(%rip) +```C + pgd = fixup_pointer(&early_top_pgt, physaddr); + pud = fixup_pointer(&level3_kernel_pgt, physaddr); + pmd = fixup_pointer(level2_fixmap_pgt, physaddr); ``` -Все адреса: `early_top_pgt`, `level3_kernel_pgt` и другие могут быть некорректными, если `startup_64` не равен адресу по умолчанию - `0x1000000`. Регистр `rbp` содержит разницу адресов, поэтому мы добавляем его к `early_top_pgt`, `level3_kernel_pgt` и `level2_fixmap_pgt`. Давайте попробуем понять, что означают эти метки. Прежде всего посмотрим на их определение: +Давайте рассмотрим определение функции `fixup_pointer`, которая возвращает физический адрес переданного аргумента: + +```C +static void __head *fixup_pointer(void *ptr, unsigned long physaddr) +{ + return ptr - (void *)_text + (void *)physaddr; +} +``` + +Затем мы сосредоточимся на `early_top_pgt` и других табличных символах, которые мы видели выше. Давайте попробуем понять, что означают эти символы. Прежде всего посмотрим на их определение: ```assembly NEXT_PAGE(early_top_pgt) - .fill 511,8,0 - .quad level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE + .fill 512,8,0 + .fill PTI_USER_PGD_FILL,8,0 NEXT_PAGE(level3_kernel_pgt) .fill L3_START_KERNEL,8,0 - .quad level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE - .quad level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE + .quad level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE_NOENC + .quad level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE_NOENC NEXT_PAGE(level2_kernel_pgt) PMDS(0, __PAGE_KERNEL_LARGE_EXEC, @@ -149,41 +172,43 @@ NEXT_PAGE(level2_kernel_pgt) NEXT_PAGE(level2_fixmap_pgt) .fill 506,8,0 - .quad level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE + .quad level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE_NOENC .fill 5,8,0 NEXT_PAGE(level1_fixmap_pgt) .fill 512,8,0 ``` -Выглядит сложно, но на самом деле это не так. Прежде всего, давайте посмотрим на `early_top_pgt`. Он начинается с (4096 - 8) нулевых байтов, это означает, что мы не используем первые `511` записей. После этого мы видим одну запись `level3_kernel_pgt`. Обратите внимание на то, что мы вычитаем из него `__START_KERNEL_map + _PAGE_TABLE`. Как известно, `__START_KERNEL_map` является базовым виртуальным адресом сегмента кода ядра, поэтому, если мы вычтем `__START_KERNEL_map`, мы получим физический адрес `level3_kernel_pgt`. Теперь давайте посмотрим на `_PAGE_TABLE`, это просто права доступа к странице: +Выглядит сложно, но на самом деле это не так. Прежде всего, давайте посмотрим на `early_top_pgt`. Он начинается с `4096` нулевых байтов (или `8192` байт если включён `CONFIG_PAGE_TABLE_ISOLATION`), это означает, что мы не используем первые `511` записей. После этого мы видим одну запись `level3_kernel_pgt`. В начале его определения мы видим, что он заполнен `4080` байтами нулей (`L3_START_KERNEL` равен `510`). Впоследствии он хранит две записи, которые отображают пространство ядра. Обратите внимание, что мы вычитаем `__START_KERNEL_map` из `level2_kernel_pgt` и `level2_fixmap_pgt`. Как известно, `__START_KERNEL_map` является базовым виртуальным адресом текстового сегмента ядра, поэтому, если мы вычтем `__START_KERNEL_map`, мы получим физические адреса `level2_kernel_pgt` и `level2_fixmap_pgt`. ```C -#define _PAGE_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | \ - _PAGE_ACCESSED | _PAGE_DIRTY) +#define _KERNPG_TABLE_NOENC (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | \ + _PAGE_DIRTY) +#define _PAGE_TABLE_NOENC (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | \ + _PAGE_ACCESSED | _PAGE_DIRTY) ``` +`level2_kernel_pgt` - это запись в таблице страниц, содержащая указатель на промежуточный каталог страниц, которая отображает пространство ядра. Она вызывает макрос `PDMS`, который создает `512` мегабайт из `__START_KERNEL_map` для `.text` ядра (после того как эти `512` мегабайт будут областью памяти модуля). + +`level2_fixmap_pgt` - это виртуальные адреса, которые могут ссылаться на любые физические адреса даже в пространстве ядра. Они представлены `4048` байтами нулей, значением `level1_fixmap_pgt`, `8` мегабайтами, зарезервированными для отображения `vsyscalls` и `2` мегабайта пустого пространства. + Вы можете больше узнать об этом в статье [страничная организация памяти](../Theory/linux-theory-1.md). -`level3_kernel_pgt` хранит две записи, которые отображают пространство ядра. В начале его определения мы видим, что он заполнен нулями `L3_START_KERNEL` или `510` раз. `L3_START_KERNEL` - это индекс в верхнем каталоге страниц, который содержит адрес `__START_KERNEL_map` и равен `510`. После этого мы можем видеть определение двух записей `level3_kernel_pgt`: `level2_kernel_pgt` и `level2_fixmap_pgt`. Первая очень проста - это запись в таблице страниц, которая содержит указатель на промежуточный каталог страниц, который отображает пространство ядра и содержит права доступа: +Теперь, после того как мы увидели определения этих символов, вернёмся к коду. Мы инициализируем последнюю запись `pgd` с помощью `level3_kernel_pgt`: ```C -#define _KERNPG_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | \ - _PAGE_DIRTY) +pgd[pgd_index(__START_KERNEL_map)] = level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE_NOENC; ``` -Второй - `level2_fixmap_pgt` - это виртуальные адреса, которые могут ссылаться на любые физические адреса даже в пространстве ядра. Они представлены одной записью `level2_fixmap_pgt` и "дырой" в `10` мегабайт для отображения [vsyscalls](https://lwn.net/Articles/446528/). `level2_kernel_pgt` вызывает макрос `PDMS`, который выделяет `512` мегабайт из `__START_KERNEL_map` для сегмента ядра `.text` (после этого `512` мегабайт будут модулем пространства памяти). -После того как мы увидели определения этих символов, вернёмся к коду, описанному в начале раздела. Вы должны помнить, что регистр `rbp` содержит разницу между адресом символа `startup_64`, который был получен во время [компоновки](https://en.wikipedia.org/wiki/Linker_%28computing%29) ядра, и фактическим адреса. Итак, на данный момент нам просто нужно добавить эту разницу к базовому адресу некоторых записей таблицы страниц, чтобы получить корректные адреса. В нашем случае это записи: +Все адреса `p*d` могут быть неверными, если `startup_64` не равен адресу по умолчанию - `0x1000000`. Вы должны помнить, что `load_delta` содержит дельта между адресом метки `startup_64`, который был получен во время [компоновки](https://en.wikipedia.org/wiki/Linker_%28computing%29) ядра и фактическим адресом. Таким образом, мы добавляем дельту к некоторым записям `p*d`: -```assembly - addq %rbp, early_top_pgt + (L4_START_KERNEL*8)(%rip) - addq %rbp, level3_kernel_pgt + (510*8)(%rip) - addq %rbp, level3_kernel_pgt + (511*8)(%rip) - addq %rbp, level2_fixmap_pgt + (506*8)(%rip) +```C + pgd[pgd_index(__START_KERNEL_map)] += load_delta; + pud[510] += load_delta; + pud[511] += load_delta; + pmd[506] += load_delta; ``` -последняя запись `early_top_pgt` является каталогом `level3_kernel_pgt`, последние две записи `level3_kernel_pgt` являются каталогами `level2_kernel_pgt` и `level2_fixmap_pgt` соответственно, и 507 запись `level2_fixmap_pgt` является каталогом `level1_fixmap_pgt`. - После этого у нас будет: ``` @@ -191,80 +216,120 @@ early_top_pgt[511] -> level3_kernel_pgt[0] level3_kernel_pgt[510] -> level2_kernel_pgt[0] level3_kernel_pgt[511] -> level2_fixmap_pgt[0] level2_kernel_pgt[0] -> 512 Мб, отображённые на ядро -level2_fixmap_pgt[507] -> level1_fixmap_pgt +level2_fixmap_pgt[506] -> level1_fixmap_pgt ``` -Обратите внимание, что мы не исправили базовый адрес `early_top_pgt` и некоторых других каталогов таблицы страниц, потому что мы увидим это во время построения/заполнения структур для этих таблиц страниц. После исправления базовых адресов таблиц страниц, мы можем приступить к их построению. +Обратите внимание, что мы не исправили базовый адрес `early_top_pgt` и некоторых других каталогов таблицы страниц, потому что мы увидим это во время построения/заполнения структур этих таблиц страниц. После исправления базовых адресов таблиц страниц, мы можем приступить к их построению. Настройка отображения "один в один" (identity mapping) -------------------------------------------------------------------------------- -Теперь мы можем увидеть настройку отображения "один в один" начальных таблиц страниц. В страничной организации с отображением "один в один", виртуальные адреса сопоставляются с физическими адресами, которые имеют одно и то же значение, `один в один`. Давайте рассмотрим это подробнее. Прежде всего, мы получаем `rip-относительные` адреса `_text` и `_early_top_pgt` и помещаем их в регистры `rdi` и `rbx`: +Теперь мы можем увидеть настройку отображения "один в один" начальных таблиц страниц. В страничной организации с отображением "один в один", виртуальные адреса идентичны физическими адресами. Давайте рассмотрим это подробнее. Прежде всего, мы заменим `pud` и `pmd` указателем на первую и вторую запись `early_dynamic_pgts`: -```assembly - leaq _text(%rip), %rdi - leaq early_top_pgt(%rip), %rbx +```C + next_pgt_ptr = fixup_pointer(&next_early_pgt, physaddr); + pud = fixup_pointer(early_dynamic_pgts[(*next_pgt_ptr)++], physaddr); + pmd = fixup_pointer(early_dynamic_pgts[(*next_pgt_ptr)++], physaddr); ``` -После этого мы сохраняем адрес `_text` в регистр `rax` и получаем индекс записи глобального каталога страниц, который хранит адрес `_text`, путём сдвига адреса `_text` на `PGDIR_SHIFT`: +Давайте посмотри на определение `early_dynamic_pgts`: ```assembly - movq %rdi, %rax - shrq $PGDIR_SHIFT, %rax +NEXT_PAGE(early_dynamic_pgts) + .fill 512*EARLY_DYNAMIC_PAGE_TABLES,8,0 +``` + +которая будет хранить временные таблицы страниц раннего ядра. + +Затем мы инициализируем `pgtable_flags`, который позже будет использоваться при инициализации записей `p*d`: + +```C + pgtable_flags = _KERNPG_TABLE_NOENC + sme_get_me_mask(); +``` + +Функция `sme_get_me_mask` возвращает `sme_me_mask`, который был инициализирован в функции `sme_enable`. + +Далее мы заполняем две записи `pgd` с помощью `pud` плюс `pgtable_flags`, который мы инициализировали ранее: + +```C + i = (physaddr >> PGDIR_SHIFT) % PTRS_PER_PGD; + pgd[i + 0] = (pgdval_t)pud + pgtable_flags; + pgd[i + 1] = (pgdval_t)pud + pgtable_flags; ``` -где `PGDIR_SHIFT` равен `39`. `PGDIR_SHFT` указывает маску для битов глобального каталога страниц в виртуальном адресе. Существуют макросы для всех типов каталогов страниц: +`PGDIR_SHFT` обозначате маску для бит глобального каталога страниц в виртуальном адресе. Здесь мы вычисляем по модулю `PTRS_PER_PGD` (который раскрывается до `512`), чтобы не получить доступ к индексу, превышающему `512`. Для всех типов каталогов страниц есть свой макрос: ```C #define PGDIR_SHIFT 39 +#define PTRS_PER_PGD 512 #define PUD_SHIFT 30 +#define PTRS_PER_PUD 512 #define PMD_SHIFT 21 +#define PTRS_PER_PMD 512 ``` -После этого мы помещаем адрес первой записи таблицы страниц `early_dynamic_pgts` в регистр `rdx` с правами доступа `_KERNPG_TABLE` (см. выше) и заполняем `early_top_pgt` двумя записями `early_dynamic_pgts`: +Мы делаем почти то же самое: -```assembly - leaq (4096 + _KERNPG_TABLE)(%rbx), %rdx - movq %rdx, 0(%rbx,%rax,8) - movq %rdx, 8(%rbx,%rax,8) +```C + i = (physaddr >> PUD_SHIFT) % PTRS_PER_PUD; + pud[i + 0] = (pudval_t)pmd + pgtable_flags; + pud[i + 1] = (pudval_t)pmd + pgtable_flags; ``` -Регистр `rbx` содержит адрес `early_top_pgt` и здесь `%rax * 8` - это индекс глобального каталога страниц, занятого адресом `_text`. Итак, здесь мы заполняем две записи `early_top_pgt` адресами двух записей `early_dynamic_pgts`, который связан с `_text`. `early_dynamic_pgts` является массивом массивов: +Затем мы инициализируем `pmd_entry` и отфильтровываем неподдерживаемые биты `__PAGE_KERNEL_ *`: ```C -extern pmd_t early_dynamic_pgts[EARLY_DYNAMIC_PAGE_TABLES][PTRS_PER_PMD]; + pmd_entry = __PAGE_KERNEL_LARGE_EXEC & ~_PAGE_GLOBAL; + mask_ptr = fixup_pointer(&__supported_pte_mask, physaddr); + pmd_entry &= *mask_ptr; + pmd_entry += sme_get_me_mask(); + pmd_entry += physaddr; ``` -который будет хранить временные таблицы страниц для раннего ядра и которые мы не будем перемещать в `init_level4_pgt`. +Далее мы заполняем все записи `pmd`, чтобы покрыть полный размер ядра: + +```C + for (i = 0; i < DIV_ROUND_UP(_end - _text, PMD_SIZE); i++) { + int idx = i + (physaddr >> PMD_SHIFT) % PTRS_PER_PMD; + pmd[idx] = pmd_entry + i * PMD_SIZE; + } +``` -После этого мы добавляем `4096` (размер `early_top_pgt`) в регистр `rdx` (теперь он содержит адрес первой записи `early_dynamic_pgts`) и помещаем значение регистра `rdi` (теперь он содержит физический адрес `_text`) в регистр `rax`. Далее мы смещаем адрес `_text` на `PUD_SHIFT`, чтобы получить индекс записи из верхнего каталога страниц, который содержит этот адрес, и очищаем старшие биты, для того чтобы получить только связанную с `pud` часть: +Затем мы исправляем виртуальные адреса текста+данных ядра. Обратите внимание, что мы можем записать недопустимые `pmd`, если ядро было перемещено (функция `cleanup_highmap` исправляет это вместе с отображениями вне `_end`). -```assembly - addq $4096, %rdx - movq %rdi, %rax - shrq $PUD_SHIFT, %rax - andl $(PTRS_PER_PUD-1), %eax +```C + pmd = fixup_pointer(level2_kernel_pgt, physaddr); + for (i = 0; i < PTRS_PER_PMD; i++) { + if (pmd[i] & _PAGE_PRESENT) + pmd[i] += load_delta; + } ``` -Поскольку у нас есть индекс верхнего каталога таблиц страниц, мы записываем два адреса второй записи массива `early_dynamic_pgts` в первую запись временного каталога страниц: +Далее мы удаляем маску шифрования памяти для получения истинного физического адреса (помните, что `load_delta` включает в себя маску): -```assembly - movq %rdx, 4096(%rbx,%rax,8) - incl %eax - andl $(PTRS_PER_PUD-1), %eax - movq %rdx, 4096(%rbx,%rax,8) +```C + *fixup_long(&phys_base, physaddr) += load_delta - sme_get_me_mask(); ``` -На следующем шаге мы выполняем ту же операцию для последнего каталога таблиц страниц, но заполняем не две записи, а все, чтобы охватить полный размер ядра. +`phys_base` должен соответствовать первой записи в `level2_kernel_pgt`. + +В качестве последнего шага функции `__startup_64` мы зашифровываем ядро (если активен SME) и возвращаем маску шифрования SME, которая будет использоваться в качестве модификатора для начальной записи каталога страницы, запрограммированной в регистр `cr3`: + +```C + sme_encrypt_kernel(bp); + return sme_get_me_mask(); +``` -После заполнения наших начальных каталогов таблиц страниц мы помещаем физический адрес `early_top_pgt` в регистр `rax` и переходим на метку `1`: +Теперь вернемся к ассемблерному коду. Мы готовимся к следующему разделу со следующим кодом: ```assembly - movq $(early_top_pgt - __START_KERNEL_map), %rax + addq $(early_top_pgt - __START_KERNEL_map), %rax jmp 1f ``` -На данный момент это всё. Наша ранняя страничная структура настроена и нам нужно совершить последнее приготовление, прежде чем мы перейдём к коду на C и к точке входа в ядро. +который добавляет физический адрес `early_top_pgt` к регистру `rax` и теперь регистр `rax` содержит сумму адреса и маски шифрования SME. + +На данный момент это всё. Наша ранняя страничная структура настроена и нам нужно совершить последнее приготовление, прежде чем мы перейдем к точке входа в ядро. Последнее приготовление перед переходом на точку входа в ядро -------------------------------------------------------------------------------- @@ -318,14 +383,21 @@ extern pmd_t early_dynamic_pgts[EARLY_DYNAMIC_PAGE_TABLES][PTRS_PER_PMD]; ```assembly btsl $_EFER_SCE, %eax - btl $20,%edi + btl $20,%edi jnc 1f btsl $_EFER_NX, %eax btsq $_PAGE_BIT_NX,early_pmd_flags(%rip) 1: wrmsr ``` -Если бит [NX](https://en.wikipedia.org/wiki/NX_bit) поддерживается, мы включаем `_EFER_NX` и записываем в него с помощью инструкции `wrmsr`. После того как бит [NX](https://en.wikipedia.org/wiki/NX_bit) установлен, мы устанавливаем некоторые биты в [регистре управления](https://en.wikipedia.org/wiki/Control_register) `cr0`, а именно: +Если бит [NX](https://en.wikipedia.org/wiki/NX_bit) поддерживается, мы включаем `_EFER_NX` и записываем в него с помощью инструкции `wrmsr`. После того как бит [NX](https://en.wikipedia.org/wiki/NX_bit) установлен, мы устанавливаем некоторые биты в [регистре управления](https://en.wikipedia.org/wiki/Control_register) `cr0`: + +```C + movl $CR0_STATE, %eax + movq %rax, %cr0 +``` + +в частности следующие биты: * `X86_CR0_PE` - система в защищённом режиме; * `X86_CR0_MP` - контролирует взаимодействие инструкций WAIT/FWAIT с помощью флага TS в CR0; @@ -335,16 +407,6 @@ extern pmd_t early_dynamic_pgts[EARLY_DYNAMIC_PAGE_TABLES][PTRS_PER_PMD]; * `X86_CR0_AM` - проверка выравнивания включена, если установлен AM и флаг AC (в регистре EFLAGS), а уровень привелигий равен 3; * `X86_CR0_PG` - включает страничную организацию. -с помощью выполнения данного ассемблерного кода: - -```assembly -#define CR0_STATE (X86_CR0_PE | X86_CR0_MP | X86_CR0_ET | \ - X86_CR0_NE | X86_CR0_WP | X86_CR0_AM | \ - X86_CR0_PG) -movl $CR0_STATE, %eax -movq %rax, %cr0 -``` - Мы уже знаем, что для запуска любого кода и даже большего количества [C](https://en.wikipedia.org/wiki/C_%28programming_language%29) кода из ассемблера, нам необходимо настроить стек. Как всегда, мы делаем это путём установки [указателя стека](https://en.wikipedia.org/wiki/Stack_register) на корректное место в памяти и сброса [регистра флагов](https://en.wikipedia.org/wiki/FLAGS_register): ```assembly @@ -353,31 +415,29 @@ pushq $0 popfq ``` -Самое интересное здесь - `initial_stack`. Этот символ определён в файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S) и выглядит следующим образом: +Самое интересное здесь - `initial_stack`. Этот символ определён в файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S) и выглядит следующим образом: ```assembly GLOBAL(initial_stack) .quad init_thread_union + THREAD_SIZE - SIZEOF_PTREGS ``` -Макрос `GLOBAL` нам уже знаком. Он определён в файле [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/linkage.h) и раскрывается до `глобального` определения символа: +Макрос `THREAD_SIZE` определён в [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/page_64_types.h) и зависит от значения макроса `KASAN_STACK_ORDER`: ```C -#define GLOBAL(name) \ - .globl name; \ - name: -``` - -Макрос `THREAD_SIZE` определён в [arch/x86/include/asm/page_64_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/page_64_types.h) и зависит от значения макроса `KASAN_STACK_ORDER`: +#ifdef CONFIG_KASAN +#define KASAN_STACK_ORDER 1 +#else +#define KASAN_STACK_ORDER 0 +#endif -```C #define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER) #define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER) ``` -когда [kasan](http://lxr.free-electrons.com/source/Documentation/kasan.txt) отключён, а `PAGE_SIZE` равен `4096` байтам. Таким образом, `THREAD_SIZE` будет раскрыт до `16` килобайт и представляет собой размер стека потока. Почему `потока`? Возможно, вы уже знаете, что каждый [процесс](https://en.wikipedia.org/wiki/Process_%28computing%29) может иметь [родительские](https://en.wikipedia.org/wiki/Parent_process) и [дочерние](https://en.wikipedia.org/wiki/Child_process) процессы. На самом деле родительский и дочерний процесс различаются в стеке. Для нового процесса выделяется новый стек ядра. В ядре Linux этот стек представлен [объединением (union)](https://en.wikipedia.org/wiki/Union_type#C.2FC.2B.2B) со структурой `thread_info`. +когда [kasan](https://github.com/torvalds/linux/blob/master/Documentation/dev-tools/kasan.rst) отключён, а `PAGE_SIZE` равен `4096` байтам. Таким образом, `THREAD_SIZE` будет раскрыт до `16` килобайт и представляет собой размер стека потока. Почему `потока`? Возможно, вы уже знаете, что каждый [процесс](https://en.wikipedia.org/wiki/Process_%28computing%29) может иметь [родительский процесс](https://en.wikipedia.org/wiki/Parent_process) и [дочерний процессы](https://en.wikipedia.org/wiki/Child_process). На самом деле родительский и дочерний процесс различаются в стеке. Для нового процесса выделяется новый стек ядра. В ядре Linux этот стек представлен [объединением (union)](https://en.wikipedia.org/wiki/Union_type#C.2FC.2B.2B) со структурой `thread_info`. -`init_thread_union` представлен `thread_union` и определён в файле [include/linux/sched.h](https://github.com/torvalds/linux/blob/0500871f21b237b2bea2d9db405eadf78e5aab05/include/linux/sched.h): +`init_thread_union` представлен `thread_union` и определён в файле [include/linux/sched.h](https://github.com/torvalds/linux/blob/master/include/linux/sched.h): ```C union thread_union { @@ -391,9 +451,9 @@ union thread_union { }; ``` -где `CONFIG_THREAD_INFO_IN_TASK` - параметр конфигурации ядра, включённый для архитектуры `ia64`, а `CONFIG_THREAD_INFO_IN_TASK` - параметр конфигурации ядра, включённый для архитектуры `x86_64`. Таким образом, структура `thread_info` будет помещена в структуру `task_struct` вместо объединения `thread_union`. Поскольку в этой книге мы рассматриваем только архитектуру `x86_64`, экземпляр `thread_union` будет содержать только стек и задачу +где `CONFIG_THREAD_INFO_IN_TASK` - параметр конфигурации ядра, включённый для архитектуры `ia64`, а `CONFIG_THREAD_INFO_IN_TASK` - параметр конфигурации ядра, включённый для архитектуры `x86_64`. Таким образом, структура `thread_info` будет помещена в структуру `task_struct` вместо объединения `thread_union`. -`init_thread_union` определён в файле [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/blob/a6214385005333202c8cc1744c7075a9e1a26b9a/include/asm-generic/vmlinux.lds.h) как часть макроса `INIT_TASK_DATA`: +`init_thread_union` расположен в файле [include/asm-generic/vmlinux.lds.h](https://github.com/torvalds/blob/master/include/asm-generic/vmlinux.lds.h) как часть макроса `INIT_TASK_DATA`: ```C #define INIT_TASK_DATA(align) \ @@ -402,12 +462,11 @@ union thread_union { ... ``` -Данный макрос используется в [arch/x86/kernel/vmlinux.lds.S](https://github.com/torvalds/linux/blob/c62e43202e7cf50ca24bce58b255df7bf5de69d0/arch/x86/kernel/vmlinux.lds.S) следующим образом: +Данный макрос используется в [arch/x86/kernel/vmlinux.lds.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/vmlinux.lds.S) следующим образом: ``` .data : AT(ADDR(.data) - LOAD_OFFSET) { ... - /* init_task */ INIT_TASK_DATA(THREAD_SIZE) ... } :data @@ -421,7 +480,7 @@ GLOBAL(initial_stack) ``` -где символ `initial_stack` указывает на начало массива `thread_union.stack` + `THREAD_SIZE`, который равен 16 килобайтам и - `SIZEOF_PTREGS` равный 168 байтам. Здесь нам нужно вычесть `168` байт в верхней части стека. Это необходимо для обеспечения незаконного доступа следующей страницы памяти. +где символ `initial_stack` указывает на начало массива `thread_union.stack` + `THREAD_SIZE`, который равен 16 килобайтам и - `SIZEOF_PTREGS`, который является соглашением, помогающее unwinder'у ядра надёжно обнаруживать конец стека. После настройки начального загрузочного стека, необходимо обновить [глобальную таблицу дескрипторов](https://en.wikipedia.org/wiki/Global_Descriptor_Table) с помощью инструкции `lgdt`: @@ -438,15 +497,11 @@ early_gdt_descr_base: .quad INIT_PER_CPU_VAR(gdt_page) ``` -Это необходимо, поскольку теперь ядро работает в нижних адресах пользовательского пространства, но вскоре ядро будет работать в своём собственном пространстве. Теперь давайте посмотрим на определение `early_gdt_descr`. Глобальная таблица дескриптор содержит `32` записи: +Это необходимо, поскольку теперь ядро работает в нижних адресах пользовательского пространства, но вскоре ядро будет работать в своём собственном пространстве. -```C -#define GDT_ENTRIES 32 -``` +Теперь давайте посмотрим на определение `early_gdt_descr`. Макрос `GDT_ENTRIES` раскрывается до `32`, поэтому глобальная таблица дескрипторов содержит `32` записи для кода ядра, данных, сегментов локального хранилища потоков и т.д. -для кода ядра, данных, сегментов локального хранилища потоков и т.д. Теперь давайте посмотрим на определение `early_gdt_descr_base`. - -`gdt_page` определена как: +Теперь давайте посмотрим на определение `early_gdt_descr_base`. Структура `gdt_page` определена в [arch/x86/include/asm/desc.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/desc.h): ```C struct gdt_page { @@ -454,7 +509,7 @@ struct gdt_page { } __attribute__((aligned(PAGE_SIZE))); ``` -в файле [arch/x86/include/asm/desc.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/desc.h). Она содержит одно поле `gdt`, которое является массивом структур `desc_struct`: +Она содержит одно поле `gdt`, которое является массивом структур `desc_struct`: ```C struct desc_struct { @@ -473,13 +528,15 @@ struct desc_struct { } __attribute__((packed)); ``` -и представляет собой знакомый нам дескриптор `GDT`. Также мы можем отметить, что структура `gdt_page` выровнена по `PAGE_SIZE`, равному `4096` байтам. Это значит, что `gdt` займёт одну страницу. Теперь попробуем понять, что такое `INIT_PER_CPU_VAR`. `INIT_PER_CPU_VAR` это макрос, определённый в [arch/x86/include/asm/percpu.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/percpu.h), который просто совершает конкатенацию `init_per_cpu__` с заданным параметром: +который выглядит знакомым дескриптором `GDT`. Можно отметить, что структура `gdt_page` выровнена по `PAGE_SIZE`, равному `4096` байтам. Это значит, что `gdt` займёт одну страницу. + +Теперь попробуем понять, что такое `INIT_PER_CPU_VAR`. `INIT_PER_CPU_VAR` это макрос, определённый в [arch/x86/include/asm/percpu.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/percpu.h), который просто совершает конкатенацию `init_per_cpu__` с заданным параметром: ```C #define INIT_PER_CPU_VAR(var) init_per_cpu__##var ``` -После того, как макрос `INIT_PER_CPU_VAR` будет раскрыт, мы будем иметь `init_per_cpu__gdt_page`. Мы можем видеть это в [скрипте компоновщика](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/vmlinux.lds.S): +После того, как макрос `INIT_PER_CPU_VAR` будет раскрыт, мы будем иметь `init_per_cpu__gdt_page`. Мы можем видеть инициализацию `init_per_cpu__gdt_page` в [скрипте компоновщика](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/vmlinux.lds.S): ``` #define INIT_PER_CPU(x) init_per_cpu__##x = x + __per_cpu_load @@ -516,18 +573,21 @@ INIT_PER_CPU(gdt_page); #define MSR_GS_BASE 0xc0000101 ``` -Нам необходимо поместить `MSR_GS_BASE` в регистр `ecx` и загрузить данные из `eax` и `edx` (которые указывают на `initial_gs`) с помощью инструкции `wrmsr`. Мы не используем регистры сегментов `cs`, `fs`, `ds` и `ss` для адресации в 64-битном режиме, но могут использоваться регистры `fs` и `gs`. `fs` и `gs` имеют скрытую часть (как мы видели в режиме реальных адресов для `cs`) и эта часть содержит дескриптор, который отображён на [моделезависимый регистр](https://en.wikipedia.org/wiki/Model-specific_register). Таким образом, выше мы можем видеть `0xc0000101` - это MSR-адрес `gs.base`. Когда произошёл [системный вызов](https://en.wikipedia.org/wiki/System_call) или [прерывание](https://en.wikipedia.org/wiki/Interrupt), в точке входа нет стека ядра, поэтому значение `MSR_GS_BASE` будет хранить адрес стека прерываний. +Нам необходимо поместить `MSR_GS_BASE` в регистр `ecx` и загрузить данные из `eax` и `edx` (которые указывают на `initial_gs`) с помощью инструкции `wrmsr`. Мы не используем регистры сегментов `cs`, `fs`, `ds` и `ss` для адресации в 64-битном режиме, но могут использоваться регистры `fs` и `gs`. `fs` и `gs` имеют скрытую часть (как мы видели в режиме реальных адресов для `cs`) и эта часть содержит дескриптор, который отображён на [моделезависимый регистр](https://en.wikipedia.org/wiki/Model-specific_register). Таким образом, выше мы можем видеть `0xc0000101` - это MSR-адрес `gs.base`. В точке входа нет стека ядра, поэтому когда происходит [системный вызов](https://en.wikipedia.org/wiki/System_call) или [прерывание](https://en.wikipedia.org/wiki/Interrupt), значение `MSR_GS_BASE` будет хранить адрес стека прерываний. На следующем шаге мы помещаем адрес структуры параметров загрузки режима реальных адресов в регистр `rdi` (напомним, что `rsi` содержит указатель на эту структуру с самого начала) и переходим к коду на C: ```assembly + pushq $.Lafter_lret # поиещает адрес возврата в стек для unwinder'а + xorq %rbp, %rbp # очищает указатель фрейма movq initial_code(%rip), %rax pushq $__KERNEL_CS # устанавливает корректный cs pushq %rax # целевой адрес в отрицательном пространстве lretq +.Lafter_lret: ``` -Здесь мы помещаем адрес `initial_code` в `rax` и помещаем фейковый адрес `__KERNEL_CS` и адрес `initial_code` в стек. После этого мы видим инструкцию `lretq`, означающую что после неё адрес возврата будет извлечён из стека (теперь это адрес `initial_code`) и будет совершён переход по нему. `initial_code` определён в том же файле исходного кода и выглядит следующим образом: +Здесь мы помещаем адрес `initial_code` в `rax` и помещаем возвращаемый адрес `__KERNEL_CS` и адрес `initial_code` в стек. После этого мы видим инструкцию `lretq`, означающую что после неё адрес возврата будет извлечён из стека (теперь это адрес `initial_code`) и будет совершён переход по нему. `initial_code` определён в том же файле исходного кода и выглядит следующим образом: ```assembly .balign 8 @@ -538,10 +598,11 @@ INIT_PER_CPU(gdt_page); ... ``` -Как мы видим `initial_code` содержит адрес `x86_64_start_kernel`, определённой в [arch/x86/kerne/head64.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head64.c): +Как мы видим `initial_code` содержит адрес `x86_64_start_kernel`, определённой в [arch/x86/kerne/head64.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head64.c): ```C -asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data) { +asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data) +{ ... ... ... @@ -550,12 +611,10 @@ asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data) { У неё есть один аргумент - `real_mode_data` (помните, ранее мы помещали адрес данных режима реальных адресов в регистр `rdi`). -Это первый C код в ядре! - Далее в start_kernel -------------------------------------------------------------------------------- -Мы увидим последние приготовления, прежде чем сможем перейти к "точке входа в ядро" - к функции `start_kernel` в файле [init/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/init/main.c#L489). +Мы увидим последние приготовления, прежде чем сможем перейти к "точке входа в ядро" - к функции `start_kernel` в файле [init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c). Прежде всего в функции `x86_64_start_kernel` мы видим некоторый проверки: @@ -566,11 +625,11 @@ BUILD_BUG_ON(MODULES_LEN + KERNEL_IMAGE_SIZE > 2*PUD_SIZE); BUILD_BUG_ON((__START_KERNEL_map & ~PMD_MASK) != 0); BUILD_BUG_ON((MODULES_VADDR & ~PMD_MASK) != 0); BUILD_BUG_ON(!(MODULES_VADDR > __START_KERNEL)); -BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) == (__START_KERNEL & PGDIR_MASK))); +MAYBE_BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) == (__START_KERNEL & PGDIR_MASK))); BUILD_BUG_ON(__fix_to_virt(__end_of_fixed_addresses) <= MODULES_END); ``` -например, виртуальные адреса пространства модулей не меньше, чем базовый адрес кода ядра (`__STAT_KERNEL_map`), код ядра с модулями не меньше образа ядра и т.д. `BUILD_BUG_ON` является макросом и выглядит следующим образом: +например, виртуальный адрес пространства модуля не меньше, чем базовый адрес кода ядра (`__STAT_KERNEL_map`), код ядра с модулями не меньше образа ядра и т.д. `BUILD_BUG_ON` является макросом и выглядит следующим образом: ```C #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) @@ -586,21 +645,23 @@ BUILD_BUG_ON(__fix_to_virt(__end_of_fixed_addresses) <= MODULES_END); На следующем шаге мы видим вызов функции `cr4_init_shadow`, которая сохраняет копии `cr4` для каждого процессора. Переключения контекста могут изменять биты в `cr4`, поэтому нам нужно сохранить `cr4` для каждого процессора. После этого происходит вызов функции `reset_early_page_tables`, которая сбрасывает все записи глобального каталога страниц и записывает новый указатель на PGT в `cr3`: ```C -for (i = 0; i < PTRS_PER_PGD-1; i++) - early_top_pgt[i].pgd = 0; - -next_early_pgt = 0; - -write_cr3(__pa_nodebug(early_top_pgt)); + memset(early_top_pgt, 0, sizeof(pgd_t)*(PTRS_PER_PGD-1)); + next_early_pgt = 0; + write_cr3(__sme_pa_nodebug(early_top_pgt)); ``` -Вскоре мы создадим новые таблицы страниц. Далее в цикле мы проходим по всему глобальному каталогу страниц (`PTRS_PER_PGD` равен `512`) и обнуляем его. После этого мы устанавливаем `next_early_pgt` в ноль (подробнее об этом в следующей статье) и записываем физический адрес `early_top_pgt` в `cr3`. `__pa_nodebug` - макрос, который выглядит следующим образом: +Вскоре мы создадим новые таблицы страниц. Далее в цикле мы обнуляем все записи глобального каталога страниц. После этого мы устанавливаем `next_early_pgt` в ноль (подробнее об этом в следующей статье) и записываем физический адрес `early_top_pgt` в `cr3`. -```C -((unsigned long)(x) - __START_KERNEL_map + phys_base) +После этого мы очищаем `_bss` от `__bss_stop` до `__bss_start`, а также `init_top_pgt`. `init_top_pgt` определён в [arch/x86/kerne/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S): + +```assembly +NEXT_PGD_PAGE(init_top_pgt) + .fill 512,8,0 + .fill PTI_USER_PGD_FILL,8,0 ``` +Это то же самое определение, что и `early_top_pgt`. -После этого мы очищаем `_bss` от `__bss_stop` до `__bss_start` и следующим шагом будет настройка начальных обработчиков `IDT`. Это большой раздел, поэтому мы увидим его в следующей статье. +Следующим шагом будет настройка начальных обработчиков `IDT`. Это большой раздел, поэтому мы увидим его в следующей статье. Заключение --------------------------------------------------------------------------------