Перевод linux-bootstrap-6.md

pull/709/head
proninyaroslav 6 years ago
parent 1ea0c43e7e
commit 07e365d423

@ -1,12 +1,12 @@
Kernel booting process. Part 6.
Процесс загрузки ядра. Часть 6.
================================================================================
Introduction
Введение
--------------------------------------------------------------------------------
This is the sixth part of the `Kernel booting process` series. In the [previous part](https://github.com/0xAX/linux-insides/blob/v4.16/Booting/linux-bootstrap-5.md) we have seen the end of the kernel boot process. But we have skipped some important advanced parts.
Это шестая часть серии `Процесса загрузки ядра`. В [предыдущей части](Booting/linux-bootstrap-5.md) мы увидели конец процесса загрузки ядра. Но мы пропустили некоторые важные дополнительные детали.
As you may remember the entry point of the Linux kernel is the `start_kernel` function from the [main.c](https://github.com/torvalds/linux/blob/v4.16/init/main.c) source code file started to execute at `LOAD_PHYSICAL_ADDR` address. This address depends on the `CONFIG_PHYSICAL_START` kernel configuration option which is `0x1000000` by default:
Как вы помните, точкой входа ядра Linux является функция `start_kernel` из файла [main.c](https://github.com/torvalds/linux/blob/v4.16/init/main.c), которая начинает выполнение по адресу `LOAD_PHYSICAL_ADDR`. Этот адрес зависит от параметра конфигурации ядра `CONFIG_PHYSICAL_START`, который по умолчанию равен `0x1000000`:
```
config PHYSICAL_START
@ -19,18 +19,18 @@ config PHYSICAL_START
...
```
This value may be changed during kernel configuration, but also load address can be selected as a random value. For this purpose the `CONFIG_RANDOMIZE_BASE` kernel configuration option should be enabled during kernel configuration.
Это значение может быть изменено во время конфигурации ядра, но также может быть выбрано случайно. Для этого во время конфигурации ядра должна быть включена опция `CONFIG_RANDOMIZE_BASE`.
In this case a physical address at which Linux kernel image will be decompressed and loaded will be randomized. This part considers the case when this option is enabled and load address of the kernel image will be randomized for [security reasons](https://en.wikipedia.org/wiki/Address_space_layout_randomization).
В этом случае будет рандомизирован физический адрес, по которому будет загружен и распакован образ ядра Linux. В этой части рассматривается случай, когда эта опция включена и адрес загрузки образа ядра будет рандомизирован из [соображений безопасности](https://en.wikipedia.org/wiki/Address_space_layout_randomization).
Initialization of page tables
Инициализация таблиц страниц
--------------------------------------------------------------------------------
Before the kernel decompressor will start to find random memory range where the kernel will be decompressed and loaded, the identity mapped page tables should be initialized. If a [bootloader](https://en.wikipedia.org/wiki/Booting) used [16-bit or 32-bit boot protocol](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt), we already have page tables. But in any case, we may need new pages by demand if the kernel decompressor selects memory range outside of them. That's why we need to build new identity mapped page tables.
Перед тем как декомпрессор ядра начнёт поиск случайного адреса из диапазона, по которому ядро будет распаковано и загружено, таблицы страниц, отображённые "один в один" (identity mapped page tables), должны быть инициализированы. Если [загрузчик](https://en.wikipedia.org/wiki/Booting) использует [16-битный или 32-битный протокол загрузки](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt), у нас уже есть таблицы страниц. Но в любом случае нам могут понадобиться новые страницы по требованию, если декомпрессор ядра выберет диапазон памяти за их пределами. Вот почему нам нужно создать новые таблицы таблиц, отображённые "один в один".
Yes, building of identity mapped page tables is the one of the first step during randomization of load address. But before we will consider it, let's try to remember where did we come from to this point.
Да, создание таблиц является одним из первых шагов во время рандомизации адреса загрузки. Но прежде чем мы это рассмотрим, давайте попробуем вспомнить, откуда мы пришли к этому вопросу.
In the [previous part](https://github.com/0xAX/linux-insides/blob/v4.16/Booting/linux-bootstrap-5.md), we saw transition to [long mode](https://en.wikipedia.org/wiki/Long_mode) and jump to the kernel decompressor entry point - `extract_kernel` function. The randomization stuff starts here from the call of the:
В [предыдущей части](linux-bootstrap-5.md), мы увидели переход в [long mode](https://en.wikipedia.org/wiki/Long_mode) и переход к точке входа декомпрессора ядра - функции `extract_kernel`. Рандомизация начинается с вызова данной функции:
```C
void choose_random_location(unsigned long input,
@ -41,7 +41,7 @@ void choose_random_location(unsigned long input,
{}
```
function. As you may see, this function takes following five parameters:
Как мы можем видеть, эта функция принимает следующие пять параметров:
* `input`;
* `input_size`;
@ -49,7 +49,7 @@ function. As you may see, this function takes following five parameters:
* `output_isze`;
* `virt_addr`.
Let's try to understand what these parameters are. The first `input` parameter came from parameters of the `extract_kernel` function from the [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/misc.c) source code file:
Попытаемся понять что это за параметры. Первый параметр, `input`, поступает из параметров функции `extract_kernel`, расположенной в файле [arch/x86/boot/compressed/misc.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/misc.c):
```C
asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
@ -71,13 +71,13 @@ asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
}
```
This parameter is passed from assembler code:
Этот параметр передаётся из кода ассемблера:
```C
leaq input_data(%rip), %rdx
```
from the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S). The `input_data` is generated by the little [mkpiggy](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/mkpiggy.c) program. If you have compiled linux kernel source code under your hands, you may find the generated file by this program which should be placed in the `linux/arch/x86/boot/compressed/piggy.S`. In my case this file looks:
в файле [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S). `input_data` генерируется маленькой программой [mkpiggy](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/mkpiggy.c). Если вы компилировали ядро Linux своими руками, вы можете найти сгенерированный этой программой файл, расположенный в `linux/arch/x86/boot/compressed/piggy.S`. В моём случае этот файл выглядит так:
```assembly
.section ".rodata..compressed","a",@progbits
@ -91,21 +91,21 @@ input_data:
input_data_end:
```
As you may see it contains four global symbols. The first two `z_input_len` and `z_output_len` which are sizes of compressed and uncompressed `vmlinux.bin.gz`. The third is our `input_data` and as you may see it points to linux kernel image in raw binary format (all debugging symbols, comments and relocation information are stripped). And the last `input_data_end` points to the end of the compressed linux image.
Как вы можете видеть, он содержит четыре глобальных символа. Первые два, `z_input_len` и `z_output_len`, являются размерами сжатого и несжатого `vmlinux.bin.gz`. Третий - это наш `input_data` и он указывает на образ ядра Linux в бинарном формате (все отладочные символы, комментарии и информация о релокации удаляются). И последний, `input_data_end`, указывает на конец сжатого образа ядра.
So, our first parameter of the `choose_random_location` function is the pointer to the compressed kernel image that is embedded into the `piggy.o` object file.
Таким образом, наш первый параметр функции `choose_random_location` является указателем на сжатый образ ядра, встроенный в объектный файл `piggy.o`.
The second parameter of the `choose_random_location` function is the `z_input_len` that we have seen just now.
Второй параметр функции `choose_random_location` - `z_input_len`, который мы уже видели.
The third and fourth parameters of the `choose_random_location` function are address where to place decompressed kernel image and the length of decompressed kernel image respectively. The address where to put decompressed kernel came from [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S) and it is address of the `startup_32` aligned to 2 megabytes boundary. The size of the decompressed kernel came from the same `piggy.S` and it is `z_output_len`.
Третий и четвёртый параметры функции `choose_random_location` - это адрес, по которому размещено распакованное ядро и размер образа распакованного ядра. Адрес, по которому будет размещён образ ядра, получен из [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S) и это адрес `startup_32`, выровненный по границе 2 мегабайт. Размер распакованного ядра также получен из `piggy.S`, как и `z_output_len`.
The last parameter of the `choose_random_location` function is the virtual address of the kernel load address. As we may see, by default it coincides with the default physical load address:
Последним параметром функции `choose_random_location` является виртуальный адрес физического адреса загрузки ядра. По умолчанию он совпадает с физическим адресом загрузки по умолчанию:
```C
unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
```
which depends on kernel configuration:
который зависит от конфигурации ядра:
```C
#define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \
@ -113,7 +113,7 @@ which depends on kernel configuration:
& ~(CONFIG_PHYSICAL_ALIGN - 1))
```
Now, as we considered parameters of the `choose_random_location` function, let's look at implementation of it. This function starts from the checking of `nokaslr` option in the kernel command line:
Теперь посмотрим на реализацию функции `choose_random_location`. Она начинается с проверки опции `nokaslr` из командной строки ядра:
```C
if (cmdline_find_option_bool("nokaslr")) {
@ -122,31 +122,30 @@ if (cmdline_find_option_bool("nokaslr")) {
}
```
and if the options was given we exit from the `choose_random_location` function ad kernel load address will not be randomized. Related command line options can be found in the [kernel documentation](https://github.com/torvalds/linux/blob/v4.16/Documentation/admin-guide/kernel-parameters.rst):
и если параметр установлен, `choose_random_location` завершает свою работу и адрес загрузки ядра не будет рандомизрован. Связанные параметры командной строки можно найти в [документации ядра](https://github.com/torvalds/linux/blob/v4.16/Documentation/admin-guide/kernel-parameters.rst):
```
kaslr/nokaslr [X86]
Enable/disable kernel and module base offset ASLR
(Address Space Layout Randomization) if built into
the kernel. When CONFIG_HIBERNATION is selected,
kASLR is disabled by default. When kASLR is enabled,
hibernation will be disabled.
Включение/выключение базового смещения ASLR ядра и модуля
(рандомизация размещения адресного пространства), если оно встроено в ядро.
Если выбран CONFIG_HIBERNATION, kASLR отключён по умолчанию.
Если kASLR включён, спящий режим будет выключен.
```
Let's assume that we didn't pass `nokaslr` to the kernel command line and the `CONFIG_RANDOMIZE_BASE` kernel configuration option is enabled. In this case we add `kASLR` flag to kernel load flags:
Предположим, что мы не передали `nokaslr` в командную строку ядра, а также включён параметр конфигурации ядра `CONFIG_RANDOMIZE_BASE`. В этом случае мы добавляем флаг `kASLR` к флагам загрузки ядра:
```C
boot_params->hdr.loadflags |= KASLR_FLAG;
```
and the next step is the call of the:
и следующим шагом является вызов функции:
```C
initialize_identity_maps();
```
function which is defined in the [arch/x86/boot/compressed/kaslr_64.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr_64.c) source code file. This function starts from initialization of `mapping_info` an instance of the `x86_mapping_info` structure:
расположенной в файле [arch/x86/boot/compressed/kaslr_64.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr_64.c). Эта функция начинается с инициализации экземпляра структуры `x86_mapping_info`:
```C
mapping_info.alloc_pgt_page = alloc_pgt_page;
@ -155,7 +154,7 @@ mapping_info.page_flag = __PAGE_KERNEL_LARGE_EXEC | sev_me_mask;
mapping_info.kernpg_flag = _KERNPG_TABLE;
```
The `x86_mapping_info` structure is defined in the [arch/x86/include/asm/init.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/asm/init.h) header file and looks:
Определение структуры `x86_mapping_info` расположено в файле [arch/x86/include/asm/init.h](https://github.com/torvalds/linux/blob/v4.16/arch/x86/include/asm/init.h):
```C
struct x86_mapping_info {
@ -168,18 +167,18 @@ struct x86_mapping_info {
};
```
This structure provides information about memory mappings. As you may remember from the previous part, we already setup'ed initial page tables from 0 up to `4G`. For now we may need to access memory above `4G` to load kernel at random position. So, the `initialize_identity_maps` function executes initialization of a memory region for a possible needed new page table. First of all let's try to look at the definition of the `x86_mapping_info` structure.
Эта структура предоставляет информацию об отображениях памяти. Как вы помните из предыдущей части, мы уже настроили начальные страницы с 0 до `4G`. На данный момент нам может потребоваться доступ к памяти выше `4G` для загрузки ядра в случайном месте. Таким образом, функция `initialize_identity_maps` выполняет инициализацию области памяти для возможной новой таблицы страниц. Прежде всего, давайте взглянем на определение структуры `x86_mapping_info`.
The `alloc_pgt_page` is a callback function that will be called to allocate space for a page table entry. The `context` field is an instance of the `alloc_pgt_data` structure in our case which will be used to track allocated page tables. The `page_flag` and `kernpg_flag` fields are page flags. The first represents flags for `PMD` or `PUD` entries. The second `kernpg_flag` field represents flags for kernel pages which can be overridden later. The `direct_gbpages` field represents support for huge pages and the last `offset` field represents offset between kernel virtual addresses and physical addresses up to `PMD` level.
`alloc_pgt_page` - это функция обратного вызова, которая будет вызываться для выделения пространства под запись в таблице страниц. Поле `context` является экземпляром структуры` alloc_pgt_data`, которая в нашем случае будет использоваться для отслеживания выделенных таблиц страниц. Поля `page_flag` и` kernpg_flag` являются флагами страниц. Первый представляет флаги для записей `PMD` или `PUD`. Второе поле `kernpg_flag` представляет флаги для страниц ядра, которые позже можно переопределить. Поле `direct_gbpages` представляет поддержку больших страниц, а последнее поле, `offset` представляет смещение между виртуальными адресами ядра и физическими адресами до уровня `PMD`.
The `alloc_pgt_page` callback just validates that there is space for a new page, allocates new page:
`alloc_pgt_page` просто проверяет, есть ли место для новой страницы, и выделяет новую страницу:
```C
entry = pages->pgt_buf + pages->pgt_buf_offset;
pages->pgt_buf_offset += PAGE_SIZE;
```
in the buffer from the:
в буфере из структуры:
```C
struct alloc_pgt_data {
@ -189,36 +188,36 @@ struct alloc_pgt_data {
};
```
structure and returns address of a new page. The last goal of the `initialize_identity_maps` function is to initialize `pgdt_buf_size` and `pgt_buf_offset`. As we are only in initialization phase, the `initialze_identity_maps` function sets `pgt_buf_offset` to zero:
и возвращает адрес новой страницы. Последняя цель функции `initialize_identity_maps` заключается в инициализации `pgdt_buf_size` и` pgt_buf_offset`. Поскольку мы только в фазе инициализации, функция `initialze_identity_maps` устанавливает` pgt_buf_offset` в ноль:
```C
pgt_data.pgt_buf_offset = 0;
```
and the `pgt_data.pgt_buf_size` will be set to `77824` or `69632` depends on which boot protocol will be used by bootloader (64-bit or 32-bit). The same is for `pgt_data.pgt_buf`. If a bootloader loaded the kernel at `startup_32`, the `pgdt_data.pgdt_buf` will point to the end of the page table which already was initialzed in the [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S):
и `pgt_data.pgt_buf_size` будет установлен в `77824` или `69632` в зависимости от того, какой протокол загрузки использует загрузчик (64-битный или 32-битный). Тоже самое и для `pgt_data.pgt_buf`. Если загрузчик загрузил ядро в `startup_32`, `pgdt_data.pgdt_buf` укажет на на конец таблицы страниц, которая уже была инициализирована в [arch/x86/boot/compressed/head_64.S](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/head_64.S):
```C
pgt_data.pgt_buf = _pgtable + BOOT_INIT_PGT_SIZE;
```
where `_pgtable` points to the beginning of this page table [_pgtable](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/vmlinux.lds.S). In other way, if a bootloader have used 64-bit boot protocol and loaded the kernel at `startup_64`, early page tables should be built by bootloader itself and `_pgtable` will be just overwrote:
где `_pgtable` указывает на начало этой таблицы страниц [_pgtable](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/vmlinux.lds.S). В случае, если загрузчик использовал 64-битный протокол загрузки и загрузил ядро в `startup_64`, ранние таблицы страниц должны быть созданы самим загрузчиком и ` _pgtable` будет просто перезаписан:
```C
pgt_data.pgt_buf = _pgtable
```
As the buffer for new page tables is initialized, we may return back to the `choose_random_location` function.
После инициализации буфера для новых таблиц страниц мы можем вернуться к функции `select_random_location`.
Avoid reserved memory ranges
Избежание зарезервированных диапазонов памяти
--------------------------------------------------------------------------------
After the stuff related to identity page tables is initilized, we may start to choose random location where to put decompressed kernel image. But as you may guess, we can't choose any address. There are some reseved addresses in memory ranges. Such addresses occupied by important things, like [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), kernel command line and etc. The
После того как таблицы страниц, отображённые "один в один", инициализированы, мы можем начать выбор случайного местоположения, по которому мы поместим распакованный образ ядра. Но, как вы можете догадаться, мы не можем выбрать абсолютно любой адрес. Существует зарезервированные области памяти. Эти адреса занимают некоторые важные вещи, например, [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), командная строка ядра и т.д. Функция
```C
mem_avoid_init(input, input_size, *output);
```
function will help us to do this. All non-safe memory regions will be collected in the:
поможет нам это сделать. Все небезопасные области памяти будут собраны в массив:
```C
struct mem_vector {
@ -229,7 +228,7 @@ struct mem_vector {
static struct mem_vector mem_avoid[MEM_AVOID_MAX];
```
array. Where `MEM_AVOID_MAX` is from `mem_avoid_index` [enum](https://en.wikipedia.org/wiki/Enumerated_type#C) which represents different types of reserved memory regions:
Где `MEM_AVOID_MAX` находится в [перечислении](https://en.wikipedia.org/wiki/Enumerated_type#C) `mem_avoid_index`, который представляет собой различные типы зарезервированных областей памяти:
```C
enum mem_avoid_index {
@ -243,9 +242,9 @@ enum mem_avoid_index {
};
```
Both are defined in the [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr.c) source code file.
Оба расположены в файле [arch/x86/boot/compressed/kaslr.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr.c).
Let's look at the implementation of the `mem_avoid_init` function. The main goal of this function is to store information about reseved memory regions described by the `mem_avoid_index` enum in the `mem_avoid` array and create new pages for such regions in our new identity mapped buffer. Numerous parts fo the `mem_avoid_index` function are similar, but let's take a look at the one of them:
Давайте посмотрим на реализацию функции `mem_avoid_init`. Основная цель этой функции - хранить информацию о зарезервированных областях памяти, описанных в перечислении `mem_avoid_index` в массиве` mem_avoid`, и создавать новые страницы для таких областей в нашем новом буфере, отображённом "один в один". Многочисленные части для функции `mem_avoid_index` аналогичны, давайте посмотрим на одну из них:
```C
mem_avoid[MEM_AVOID_ZO_RANGE].start = input;
@ -254,7 +253,7 @@ add_identity_map(mem_avoid[MEM_AVOID_ZO_RANGE].start,
mem_avoid[MEM_AVOID_ZO_RANGE].size);
```
At the beginning of the `mem_avoid_init` function tries to avoid memory region that is used for current kernel decompression. We fill an entry from the `mem_avoid` array with the start and size of such region and call the `add_identity_map` function which should build identity mapped pages for this region. The `add_identity_map` function is defined in the [arch/x86/boot/compressed/kaslr_64.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr_64.c) source code file and looks:
В начале функция `mem_avoid_init` пытается избежать области памяти, которая используется для текущей декомпрессии ядра. Мы заполняем запись из массива `mem_avoid` с указанием начала и размера такой области и вызываем функцию ` add_identity_map`, которая должна создать страницы, отображённые "один в один", для этого региона. Функция `add_identity_map` определена в файле [arch/x86/boot/compressed/kaslr_64.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr_64.c):
```C
void add_identity_map(unsigned long start, unsigned long size)
@ -271,18 +270,18 @@ void add_identity_map(unsigned long start, unsigned long size)
}
```
As you may see it aligns memory region to 2 megabytes boundary and checks given start and end addresses.
Как мы можем видеть, она выравнивает область памяти по границе 2 мегабайт и проверяет заданные начальные и конечные адреса.
In the end it just calls the `kernel_ident_mapping_init` function from the [arch/x86/mm/ident_map.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/mm/ident_map.c) source code file and pass `mapping_info` instance that was initilized above, address of the top level page table and addresses of memory region for which new identity mapping should be built.
В конце она вызывает функцию `kernel_ident_mapping_init` из файла [arch/x86/mm/ident_map.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/mm/ident_map.c) и передаёт экземпляр `mapping_info`, который мы инициализировали ранее, адрес таблицы страниц верхнего уровня и адреса области памяти, для которой необходимо создать новое отображение "один в один".
The `kernel_ident_mapping_init` function sets default flags for new pages if they were not given:
Функция `kernel_ident_mapping_init` устанавливает флаги по умолчанию для новых страниц, если они не были заданы:
```C
if (!info->kernpg_flag)
info->kernpg_flag = _KERNPG_TABLE;
```
and starts to build new 2-megabytes (because of `PSE` bit in the `mapping_info.page_flag`) page entries (`PGD -> P4D -> PUD -> PMD` in a case of [five-level page tables](https://lwn.net/Articles/717293/) or `PGD -> PUD -> PMD` in a case of [four-level page tables](https://lwn.net/Articles/117749/)) related to the given addresses.
и начинает создание 2 мегабайтных (из-за бита `PSE` в `mapping_info.page_flag`) страничных записей (`PGD -> P4D -> PUD -> PMD` в случае [пятиуровневых таблиц страниц](https://lwn.net/Articles/717293/) или `PGD -> PUD -> PMD` в случае [четырёхуровневых таблиц страниц](https://lwn.net/Articles/117749/)), относящихся к указанным адресам.
```C
for (; addr < end; addr = next) {
@ -299,32 +298,32 @@ for (; addr < end; addr = next) {
}
```
First of all here we find next entry of the `Page Global Directory` for the given address and if it is greater than `end` of the given memory region, we set it to `end`. After this we allocate a new page with our `x86_mapping_info` callback that we already considered above and call the `ident_p4d_init` function. The `ident_p4d_init` function will do the same, but for low-level page directories (`p4d` -> `pud` -> `pmd`).
Прежде всего, мы находим следующую запись `глобального каталога страниц` для данного адреса, и если она больше, чем `end` данной области памяти, мы устанавливаем её в `end`. После этого мы выделяем новую страницу с нашим обратным вызовом `x86_mapping_info`, который мы уже рассмотрели выше, и вызываем функцию` ident_p4d_init`. Функция `ident_p4d_init` будет делать то же самое, но для низкоуровневых каталогов страниц (` p4d` -> `pud` ->` pmd`).
That's all.
На этом всё.
New page entries related to reserved addresses are in our page tables. This is not the end of the `mem_avoid_init` function, but other parts are similar. It just build pages for [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), kernel command line and etc.
Новые страницы, связанные с зарезервированными адресами, находятся в наших таблицах страниц. Это не конец функции `mem_avoid_init`, но другие части схожи. Они просто создают страницы для [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk), командной строки ядра и т.д.
Now we may return back to `choose_random_location` function.
Теперь мы можем вернуться к функции `choose_random_location`.
Physical address randomization
Рандомизация физического адреса
--------------------------------------------------------------------------------
After the reserved memory regions were stored in the `mem_avoid` array and identity mapping pages were built for them, we select minimal available address to choose random memory region to decompress the kernel:
После сохранения зарезервированных областей памяти в массиве `mem_avoid` и создания для них страниц, отображённых "один в один", мы выбираем минимальный доступный адрес для произвольного выбора области памяти:
```C
min_addr = min(*output, 512UL << 20);
```
As you may see it should be smaller than `512` megabytes. This `512` megabytes value was selected just to avoid unknown things in lower memory.
Он должен быть меньше чем `512` мегабайт. Значение `512` мегабайт было выбрано для того, чтобы избежать неизвестных вещей в нижней части памяти.
The next step is to select random physical and virtual addresses to load kernel. The first is physical addresses:
Следующим шагом будет выбор случайных физических и виртуальных адресов для загрузки ядра. Сначала физические адреса:
```C
random_addr = find_random_phys_addr(min_addr, output_size);
```
The `find_random_phys_addr` function is defined in the [same](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr.c) source code file:
Функция `find_random_phys_addr` определена в [том же](https://github.com/torvalds/linux/blob/v4.16/arch/x86/boot/compressed/kaslr.c) файле:
```
static unsigned long find_random_phys_addr(unsigned long minimum,
@ -340,7 +339,7 @@ static unsigned long find_random_phys_addr(unsigned long minimum,
}
```
The main goal of `process_efi_entries` function is to find all suitable memory ranges in full accessible memory to load kernel. If the kernel compiled and runned on the system without [EFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) support, we continue to search such memory regions in the [e820](https://en.wikipedia.org/wiki/E820) regions. All founded memory regions will be stored in the
Основная задача `process_efi_entries` - найти все подходящие диапазоны памяти в доступной для загрузки ядра памяти. Если ядро скомпилировано и запущено на системе без поддержки [EFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface), поиск областей памяти продолжиться в регионах [e820](https://en.wikipedia.org/wiki/E820). Все найденные области памяти будут сохранены в массиве:
```C
struct slot_area {
@ -353,20 +352,18 @@ struct slot_area {
static struct slot_area slot_areas[MAX_SLOT_AREA];
```
array. The kernel will select a random index of this array for kernel to be decompressed. This selection will be executed by the `slots_fetch_random` function. The main goal of the `slots_fetch_random` function is to select random memory range from the `slot_areas` array via `kaslr_get_random_long` function:
Для декомпрессии ядро выберет случайный индекс из этого массива. Этот выбор будет выполнен функцией `slots_fetch_random`. Основная задача функции `slots_fetch_random` заключается в выборе случайного диапазона памяти из массива `slot_areas` с помощью функции `kaslr_get_random_long`:
```C
slot = kaslr_get_random_long("Physical") % slot_max;
```
The `kaslr_get_random_long` function is defined in the [arch/x86/lib/kaslr.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/lib/kaslr.c) source code file and it just returns random number. Note that the random number will be get via different ways depends on kernel configuration and system opportunities (select random number base on [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter), [rdrand](https://en.wikipedia.org/wiki/RdRand) and so on).
Функция `kaslr_get_random_long` определена в файле [arch/x86/lib/kaslr.c](https://github.com/torvalds/linux/blob/v4.16/arch/x86/lib/kaslr.c) и просто возвращает случайное число. Обратите внимание, что случайное число будет получено разными способами, зависящими от конфигурации ядра (выбор случайного числа, основываясь на [счётчике времени](https://en.wikipedia.org/wiki/Time_Stamp_Counter), [rdrand](https://en.wikipedia.org/wiki/RdRand) и т.д.).
That's all from this point random memory range will be selected.
Virtual address randomization
Рандомизация виртуального адреса
--------------------------------------------------------------------------------
After random memory region was selected by the kernel decompressor, new identity mapped pages will be built for this region by demand:
После того как декомпрессором ядра была выбрана случайная область памяти, для неё будут созданы новые страницы, отображённые "один в один":
```C
random_addr = find_random_phys_addr(min_addr, output_size);
@ -377,7 +374,7 @@ if (*output != random_addr) {
}
```
From this time `output` will store the base address of a memory region where kernel will be decompressed. But for this moment, as you may remember we randomized only physical address. Virtual address should be randomized too in a case of [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture:
После этого `output` будет хранить базовый адрес области памяти, где будет распаковано ядро. Но на данный момент, как вы помните, мы рандомизировали только физический адрес. В случае архитектуры [x86_64](https://en.wikipedia.org/wiki/X86-64) виртуальный адрес также должен быть рандомизирован:
```C
if (IS_ENABLED(CONFIG_X86_64))
@ -386,36 +383,34 @@ if (IS_ENABLED(CONFIG_X86_64))
*virt_addr = random_addr;
```
As you may see in a case of non `x86_64` architecture, randomzed virtual address will coincide with randomized physical address. The `find_random_virt_addr` function calculates amount of virtual memory ranges that may hold kernel image and calls the `kaslr_get_random_long` that we already saw in a previous case when we tried to find random `physical` address.
В архитектуре, отличной от `x86_64`, случайный виртуальный адрес будет совпадать со случайным физическим. Функция `find_random_virt_addr` вычисляет количество диапазонов виртуальной памяти, которые могут содержать образ ядра, и вызывает `kaslr_get_random_long`, которую мы уже видели ранее, когда пытались найти случайный `физический` адрес.
From this moment we have both randomized base physical (`*output`) and virtual (`*virt_addr`) addresses for decompressed kernel.
Теперь мы имеет как физические базовые случайные адреса (`*output`), так и виртуальные (`*virt_addr`) случайные адреса для декомпрессии ядра.
That's all.
На этом всё.
Conclusion
Заключение
--------------------------------------------------------------------------------
This is the end of the sixth and the last part about linux kernel booting process. We will not see posts about kernel booting anymore (maybe updates to this and previous posts), but there will be many posts about other kernel internals.
Next chapter will be about kernel initialization and we will see the first steps in the Linux kernel initialization code.
Это конец шестой и последней части процесса загрузки ядра Linux. Мы больше не увидим статей о загрузке ядра (возможны обновления этой и предыдущих статей), но будет много статей о других внутренних компонентах ядра.
If you have any questions or suggestions write me a comment or ping me in [twitter](https://twitter.com/0xAX).
Следующая глава посвящена инициализации ядра, и мы увидим первые шаги в коде инициализации ядра Linux.
**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-internals).**
**От переводчика: пожалуйста, имейте в виду, что английский - не мой родной язык, и я очень извиняюсь за возможные неудобства. Если вы найдёте какие-либо ошибки или неточности в переводе, пожалуйста, пришлите pull request в [linux-insides-ru](https://github.com/proninyaroslav/linux-insides-ru).**
Links
Ссылки
--------------------------------------------------------------------------------
* [Address space layout randomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization)
* [Linux kernel boot protocol](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt)
* [long mode](https://en.wikipedia.org/wiki/Long_mode)
* [Рандомизация размещения адресного пространства](https://en.wikipedia.org/wiki/Address_space_layout_randomization)
* [Протокол загрузки ядра Linux](https://github.com/torvalds/linux/blob/v4.16/Documentation/x86/boot.txt)
* [Long mode](https://en.wikipedia.org/wiki/Long_mode)
* [initrd](https://en.wikipedia.org/wiki/Initial_ramdisk)
* [Enumerated type](https://en.wikipedia.org/wiki/Enumerated_type#C)
* [four-level page tables](https://lwn.net/Articles/117749/)
* [five-level page tables](https://lwn.net/Articles/717293/)
* [Перечисляемый тип](https://en.wikipedia.org/wiki/Enumerated_type#C)
* [Четырёхуровневые таблицы страниц](https://lwn.net/Articles/117749/)
* [Пятиуровневые таблицы страниц](https://lwn.net/Articles/717293/)
* [EFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)
* [e820](https://en.wikipedia.org/wiki/E820)
* [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter)
* [Счётчик времени](https://en.wikipedia.org/wiki/Time_Stamp_Counter)
* [rdrand](https://en.wikipedia.org/wiki/RdRand)
* [x86_64](https://en.wikipedia.org/wiki/X86-64)
* [Previous part](https://github.com/0xAX/linux-insides/blob/v4.16/Booting/linux-bootstrap-5.md)
* [Предыдущая часть](linux-bootstrap-5.md)

@ -6,7 +6,7 @@
* [Инициализация видеорежима и переход в защищённый режим](Booting/linux-bootstrap-3.md)
* [Переход в 64-битный режим](Booting/linux-bootstrap-4.md)
* [Декомпрессия ядра](Booting/linux-bootstrap-5.md)
* [Kernel load address randomization](Booting/linux-bootstrap-6.md)
* [Рандомизация адреса ядра](Booting/linux-bootstrap-6.md)
* [Инициализация](Initialization/README.md)
* [Первые шаги в ядре](Initialization/linux-initialization-1.md)
* [Начальная обработка прерываний и исключений](Initialization/linux-initialization-2.md)

Loading…
Cancel
Save