Глава 4. Ядро
Содержание
Эта глава рассматривает ядро.
{Прим. пер.: если вас интересует процесс сборки самого ядра и связанных с нею подробностей, рекомендуем вам ознакомиться с нашим переводом книги Программирование ядра Linux Кайваня Н Биллимория, изданной Packt Publishing в марте 2021.}
Это занимательная глава. До сих пор мы наблюдали, что вплоть до данного этапа GRUB 2 полностью контролировал
нашу процедуру запуска. Теперь он обязан передать управление своему ядру. В этой главе мы увидим как и куда наш
начальный загрузчик загружает своё ядро. Иными словами, как извлекается само ядро? Далее мы рассмотрим относящиеся
к запуску задач успешно выполняемых соответствующим ядром Linux и, в конце, как это ядро запускает
systemd
.
Замечание | |
---|---|
Тот исходный код ядра, который применяется в этой главе это версия |
Для передачи управления своему ядру нашему начальному загрузчику приходится добиться двую главных моментов.
-
Загрузить это ядро в оперативную память
-
Установить некие из имеющихся полей этого ядра согласно протоколу запуска.
Полный протокол доступен по https://www.kernel.org/doc/Documentation/x86/boot.txt. Первоначальный протокол запуска был определён никем иным как Линусом Торвальдсом.
~ ~
| Protected-mode kernel |
100000 +-------------------------------+
| I/O memory hole |
0A0000 +-------------------------------+
| Reserved for BIOS | Остаётся как можно дольше неиспользованным
~ ~
| Command line | (Может также быть ниже отметки X+10000)
X+10000 +-------------------------------+
| Stack/heap | For use by the kernel real-mode code.
X+08000 +-------------------------------+
| Kernel setup | Код ядра реального режима.
| Kernel boot sector | Наследуемый загрузочный сектор ядра.
X +-------------------------------+
| Boot loader | <- Указатель 0000:7C00 на точку входа в загрузочный сектор. Вы обнаружите то же
| | самое положение адреса в нашем файле boot.asm, который мы создали ранее.
001000 +-------------------------------+
| Reserved for MBR/BIOS |
000800 +-------------------------------+
| Typically used by MBR |
000600 +-------------------------------+
| BIOS use only |
000000 +-------------------------------+
Согласно установленному протоколу запуска именно начальный загрузчик обязан передать или установить некоторые поля
в заголовке своего ядра. Этими полями являются название корневого устройства, параметры монтирования, такие как
ro
или rw
, название initramfs,
его размер и т.п. Эти же поля имеют название параметров командной строки ядра и
мы уже знаем, что эти параметры командной строки ядра передаются GRUB/ соответствующим начальным загрузчиком в его
ядро.
GRUB не будет загружать своё ядро (/boot/vmlinuz
) в любом случайно выбранном
месте; оно всегда будет загружаться в неком конкретном месте. Значение конкретного места будет меняться для каждого
дистрибутива Linux и используемой вами версии для установленной архитектуры ЦПУ в вашей системе.
vmlinuz
является неким архивным файлом и этот архив составлен из трёх частей.
Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel)
(part-1) (part-2) (part-3)
Теперь нам необходимо представить, что GRUB 2 загрузил своё ядро в оперативную память в особом месте. Вот шаги самого
начального уровня, выполняемые архивным файлом vmlinuz
сразу после его
загрузки в память.
-
Как только наш начальный загрузчик загружает необходимое ядро в оперативную память в конкретное место, запускается исполняемый файл, сделанный из
arch/x86/boot/header.S
. -
Путаница случается когда
vmlinuz
является неким архивом, а наш начальный загрузчик ещё пока не распаковал его. Наш начальный загрузчик только загрузил это ядро в конкретное место. Тогда как же этот код внутри файла своего архиваvmlinuz
способен выполняться? -
Сначала мы рассмотрим короткий ответ, а длинный ответ мы рассмотрим в своём разделе Что распаковывает vmlinuz? в этой главе. Итак, короткий ответ состоит в исполняемом файле, сделанном из файла
/x86/boot/header.S
не является таким архивом; вместо этого он выступает частью заголовка который выполняет задачуkernel_setup
. Этот заголовок пребывает вне архива.Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel) --->Outside of archive<--- + -------->Inside archive<------->header.s file is here<---
-
Давайте пока будем считать что
vmlinuz
был извлечён и давайте продолжим свою последовательность запуска. До сих пор мы видели, что GRUB загрузил необходимое ядро в оперативную память в особом месте и запускает тот исполняемый файл, который сделан изarch/x86/boot/header.S
. Этот исполняемый файл отвечает за частьkernel_setup
. Этот файлkernel_setup
выполняет такие задачи:-
Налаживает регистры своего сегмента
-
Устанавливает стек и BSS
В каждой главе некая блок- схема снабжает нас чётким пониманием относительно изученного нами и, с точки зрения запуска,чего мы достигли. Рисунок 4-1 показывает самое начало той блок-схемы, которую мы сконструируем в этой главе по мере её развития. Она отображает те действия, которые выполняются кодом
kernel_setup
изheader.s
..
-
-
Затем он выполняет безусловный переход в функцию
main()
вarch/x86/boot/main.c
. Этот файлmain.c
также является частью заголовка ядра, а этот заголовок вне своего реального архива.Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel) --->Outside of archive<--- + -------->Inside archive<--------- --->main.c file is here<--- #vim arch/x86/boot/main.c <snip> 134 void main(void) 135 { 136 /* First, copy the boot header into the "zeropage" */ 137 copy_boot_params(); 138 139 /* Initialize the early-boot console */ 140 console_init(); 141 if (cmdline_find_option_bool("debug")) 142 puts("early console in setup code\n"); 143 144 /* End of heap check */ 145 init_heap(); 146 147 /* Make sure we have all the proper CPU support */ 148 if (validate_cpu()) { 149 puts("Unable to boot - please use a kernel appropriate " 150 "for your CPU.\n"); 151 die(); 152 } 153 154 /* Tell the BIOS what CPU mode we intend to run in. */ 155 set_bios_mode(); 156 157 /* Detect memory layout */ 158 detect_memory(); 159 160 /* Set keyboard repeat rate (why?) and query the lock flags */ 161 keyboard_init(); 162 163 /* Query Intel SpeedStep (IST) information */ 164 query_ist(); 165 166 /* Query APM information */ 167 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE) 168 query_apm_bios(); 169 #endif 170 171 /* Query EDD information */ 172 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE) 173 query_edd(); 174 #endif 175 176 /* Set the video mode */ 177 set_video(); 178 179 /* Do the last things and invoke protected mode */ 180 go_to_protected_mode(); 181 } </snip>
Как вы можете видеть, исходный код main.c
отвечает за следующее:
-
Он копирует установленные параметры запуска (параметры командной строки своего ядра) из своего начального загрузчика. Функция
copy_boot_params
будет применена для копирования следующих параметров запуска, передаваемых его начальным загрузчиком:vfs.zfs.vdev.cache.size=<10M>debug, earlyprintk, ro, root, ramdisk_image, ramdisk_size и т.п.
-
Он инициализирует свою консоль и проверяет был ли передан его пользователем параметр командной строки ядра, подобный
debug
. Если это так, наше ядро будет отображать на своём эеране сообщения подробного уровня. -
Он инициализирует кучу.
-
Когда его ЦПУ не может быть проверен, тогда код выдаёт сообщение об ошибке через функцию
validate_cpu()
. Такие дистрибутивы как Fedora и Ubuntu настраивают сообщения об этой ошибке в диапазоне от'unable to boot - please use the kernel appropriate for your cpu'
(невозможно выполнить запуск - пожалуйста, применяйте ядро, подходящее для вашего ЦПУ) до'The CPU is not supported'
(данный ЦПУ не поддерживается). Эта персонализация также установит панику своего ядра и данный запуск будет прерван. -
Затем он выявляет установленную схему оперативной памяти и выводит её на печать на экране в некой начальной стадии запуска. Эти же самые сообщения о схеме памяти можно рассмотреть позднее применив команду
dmesg
, как это отражено ниже:[ 0.000000] BIOS-provided physical RAM map: [ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable [ 0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved [ 0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009cfff] usable [ 0.000000] BIOS-e820: [mem 0x000000000009d000-0x00000000000fffff] reserved [ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007e5f7fff] usable [ 0.000000] BIOS-e820: [mem 0x000000007e5f8000-0x000000007e5f8fff] ACPI NVS [ 0.000000] BIOS-e820: [mem 0x000000007e5f9000-0x000000007e5f9fff] reserved [ 0.000000] BIOS-e820: [mem 0x000000007e5fa000-0x0000000087f62fff] usable [ 0.000000] BIOS-e820: [mem 0x0000000087f63000-0x000000008952bfff] reserved [ 0.000000] BIOS-e820: [mem 0x000000008952c000-0x0000000089599fff] ACPI NVS [ 0.000000] BIOS-e820: [mem 0x000000008959a000-0x00000000895fefff] ACPI data [ 0.000000] BIOS-e820: [mem 0x00000000895ff000-0x00000000895fffff] usable [ 0.000000] BIOS-e820: [mem 0x0000000089600000-0x000000008f7fffff] reserved [ 0.000000] BIOS-e820: [mem 0x00000000f0000000-0x00000000f7ffffff] reserved [ 0.000000] BIOS-e820: [mem 0x00000000fe010000-0x00000000fe010fff] reserved [ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000086e7fffff] usable
-
Инициализирует имеющуюся клавиатуру и её раскладку.
-
Устанавливает необходимый режим базового видео.
-
Выполняет безусловный переход в свой защищённый режим посредством функции
go_to_protected_mode()
. Для лучшего понимания обратитесь, пожалуйста, к Рисунку 4-2.
Вплоть до этого момента мы работали в реальном режиме, который обладает ограничениями в адресации 20 битами, так как
он не способен выполнять доступ к памяти свыше МБ. При помощи функции go_to_protected_mode()
наше ядро переключило свой ЦПУ с реального режима на его защищённый режим. Защищённый режим обладает ограничением в адресации
в 32 бита, а потому его ЦПУ способен выполнять доступ вплоть до 4 ГБ памяти. Проще говоря, в реальном режиме будут исполняться
лишь те программы, которые обладают 16- битным наборов инструкций, к примеру, сам BIOS. В защищённом режиме будут запускаться
только 32- битные программы. В защищённом режиме имеющееся ядро выполняет некоторые относящиеся к аппаратным средствам задачи и
далее запускаем ЦПУ в длинном режиме.
Обратите,пожалуйста внимание, что эта книга следует архитектуре x86 Intel и обсуждения, относящиеся к реальному, защищённому и длинному режимам основаны на 64- битной архитектуре x86 Intel.
Длинный режим не накладывает никаких ограничений на свой ЦПУ. Он способен применять всю установленную память.
Помещение своег ЦПУ в длинный режим будет достигаться файлом head_64.S
из
arch/x86/boot/compressed/head_64.S
. Он отвечает за следующее:
-
Подготовку к длинному режиму означает, что он проверяет поддеривается ли длинный режим или нет.
-
Вход вдлинный режим.
-
Распаковку своего ядра.
Приводимые ниже функции вызываются из файла ассемблера head_64.S
:
$ cat arch/x86/boot/compressed/head_64.S | grep -i call
call 1f
call verify_cpu
call get_sev_encryption_bit
call 1f
call 1f
call .Ladjust_got
* this function call.
call paging_prepare
* this function call.
call cleanup_trampoline
call 1f
call .Ladjust_got
call 1f
* Relocate efi_config->call().
call make_boot_params
call 1f
* Relocate efi_config->call().
call efi_main
call extract_kernel /* returns kernel location in %rax */
.quad efi_call
Функция | Действие |
---|---|
|
Проверяет наличие длинного режима ЦПУ |
|
Заботится о передаваемых начальным загрузчиком параметров времени запуска |
|
Относящееся к встроенному ПО наполнение |
|
Эта функция определена в Именно эта функция распаковывает |
Для лучшего понимания обратитесь, пожалуйста к показанной на Рисунке 4-3 блок- схеме.
Постойте, однако: если наше ядро ещё пока не распаковано, тогда как мы продолжаем выполнение в данный момент? Тут настаёт время нашего длинного ответа.
К данному моменту мы осознали, что именно GRUB загрузил необходимое ядро в память, но в то же самое время, мы отметили,
что наш образ vmlinuz
содержится в неком архиве. Итак, что же распаковывает этот
образ? Делает ли это GRUB?
Нет, это не GRUB. Вместо этого именно само ядро распаковывает себя. Да, я сказал что именно само ядро распаковывает
это ядро. Возможно, vmlinuz
единственный в мире файл операционной системы, который
сам извлекает себя. Но как это возможно распаковывать себя самостоятельно? Чтобы понять это давайте для начала разберёмся
с vmlinuz
.
"vm
" из vmlinuz
это сокращение
для "virtual memory". на самых ранних стадиях разработки Linux само понятие виртуальной памяти ещё не было
проработано, а потому когда оно было добавлено эти символы "vm
" и были
добавлены к общему названию ядра Linux. Символ "z
" это сокращение для
ziiped файла.
$ file vmlinuz-5.0.9-301.fc30.x86_64
vmlinuz-5.0.9-301.fc30.x86_64: Linux kernel x86 boot executable bzImage, version 5.0.9-301.fc30.x86_64 (mockbuild@bkernel04.phx2.fedoraproject.org) #1 SMP Tue Apr 23 23:57:35 U, RO-rootFS, swap_dev 0x8, Normal VGA
Как вы можете видетьvmlinuz
это bzImage
(bzImage
сокращение для "big zimage").
vmlinuz
это сжатый файл реального исполняемого файла
vmlinuz
. Вы не сможете распаковать этот файл при помощи
gunzip/bunzip
или даже tar
.
Самым простым способом распаковки vmlinuz
является использование файла сценария
extract-vmlinuz
, предоставляемого пакетом
kernel-devel
(в случае Fedora). Этот файл будет представлен в
/usr/src/kernels/<kernel_version>/scripts/extract-vmlinux
.
# ./extract-vmlinux /boot/vmlinuz-5.3.7-301.fc31.x86_64 >gt; /boot/temp/vmlinux
# file /boot/temp/*
/boot/temp/vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
statically linked, BuildID[sha1]=ec96b29d8e4079950644230c0b7868942bb70366, stripped
Существуют разнообразные способы раскрытия файлов ядра vmlinux
и
vmlinuz
.
$ xxd vmlinux | less
$ objdump vmlinux | less
$ objdump vmlinux -D | less
$ hexdump vmlinux | less
$ od vmlinux | less
Мы воспользуемся командой od
с некими переключателями для открытия своего
файла vmlinuz
.
$ od -A d -t x1 vmlinuz-5.0.9-301.fc30.x86_64 | less
<snip>
0000000 4d 5a ea 07 00 c0 07 8c c8 8e d8 8e c0 8e d0 31
0000016 e4 fb fc be 40 00 ac 20 c0 74 09 b4 0e bb 07 00
0000032 cd 10 eb f2 31 c0 cd 16 cd 19 ea f0 ff 00 f0 00
0000048 00 00 00 00 00 00 00 00 00 00 00 00 82 00 00 00
0000064 55 73 65 20 61 20 62 6f 6f 74 20 6c 6f 61 64 65
0000080 72 2e 0d 0a 0a 52 65 6d 6f 76 65 20 64 69 73 6b
0000096 20 61 6e 64 20 70 72 65 73 73 20 61 6e 79 20 6b
0000112 65 79 20 74 6f 20 72 65 62 6f 6f 74 2e 2e 2e 0d
0000128 0a 00 50 45 00 00 64 86 04 00 00 00 00 00 00 00
0000144 00 00 01 00 00 00 a0 00 06 02 0b 02 02 14 80 37
0000160 8e 00 00 00 00 00 80 86 26 02 f0 48 00 00 00 02
0000176 00 00 00 00 00 00 00 00 00 00 20 00 00 00 20 00
0000192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000208 00 00 00 c0 b4 02 00 02 00 00 00 00 00 00 0a 00
0000224 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000256 00 00 00 00 00 00 06 00 00 00 00 00 00 00 00 00
0000272 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000288 00 00 00 00 00 00 00 00 00 00 80 39 8e 00 48 09
0000304 00 00 00 00 00 00 00 00 00 00 2e 73 65 74 75 70
0000320 00 00 e0 43 00 00 00 02 00 00 e0 43 00 00 00 02
0000336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000352 50 60 2e 72 65 6c 6f 63 00 00 20 00 00 00 e0 45
0000368 00 00 20 00 00 00 e0 45 00 00 00 00 00 00 00 00
0000384 00 00 00 00 00 00 40 00 10 42 2e 74 65 78 74 00
0000400 00 00 80 f3 8d 00 00 46 00 00 80 f3 8d 00 00 46
0000416 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000432 50 60 2e 62 73 73 00 00 00 00 80 86 26 02 80 39
0000448 8e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000464 00 00 00 00 00 00 80 00 00 c8 00 00 00 00 00 00
0000480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff
0000496 ff 22 01 00 38 df 08 00 00 00 ff ff 00 00 55 aa
0000512 eb 66 48 64 72 53 0d 02 00 00 00 00 00 10 c0 37
0000528 00 01 00 80 00 00 10 00 00 00 00 00 00 00 00 00
0000544 00 00 00 00 50 5a 00 00 00 00 00 00 ff ff ff 7f
0000560 00 00 00 01 01 15 3f 00 ff 07 00 00 00 00 00 00
0000576 00 00 00 00 00 00 00 00 b1 03 00 00 11 f3 89 00
0000592 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00
0000608 00 c0 b4 02 90 01 00 00 8c d8 8e c0 fc 8c d2 39
0000624 c2 89 e2 74 16 ba 50 58 f6 06 11 02 80 74 04 8b
</snip>
# od -A d -t x1 /boot/vmlinuz-5.3.7-301.fc31.x86_64 | grep -i '1f 8b 08 00'
0018864 8f 1f 8b 08 00 00 00 00 00 02 03 ec fd 79 7c 54
итак, по адресу 0018864
начинается реальное ядро
(vmlinux
), в то время как сам файл
vmlinuz
стартует с 0000000
. Это
означает, что с 0000000
по 0018864
то
что у нас имеется, это соответствующий заголовок нашего файла, такой как
header.S
, misc.c
и т.п.. Именно он и
распаковывает само реальное ядро (vmlinux
) из
vmlinuz
. Вы можете рассматривать некий заголовок подобным нашлёпке поверх
исполняемого файла vmlinux
и когда эта нашлёпка доступна, он превращается в
vmlinuz
ю В своём следующем разделе мы увидим как соответствующая процедура ядра
распаковыает vmlinuz
.
Давайте вернёмся назад к своей функции extract_kernel
из
arch/x86/boot/compressed/misc.c
.
asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
unsigned char *input_data,
unsigned long input_len,
unsigned char *output,
unsigned long output_len)
Как вы можете видеть, эта функция принимает семь параметров.
Параметр | Назначение |
---|---|
|
Указатель на структуру |
|
Указатель на файл |
|
Указатель на наало самого схатого ядра или, иначе говоря, указатель на
|
|
Размер нашего сжатого ядра |
|
Значение начального адреса нашего распаковываемого дале ядра |
|
Значение размера распаковываемого ядра |
|
Общий объём пространства, необходимый для выполнения самого ядра, включая разделы
|
Наряду с самим ядром, наш начальный загрузчик также подгрузил в память и initramfs. Об initramfs мы поговорим
в Главе 5. итак, перед распаковкой необходимого образа ядра,
наш заголовок или соответствующая процедура ядра должен позаботиться о том, что распаковка vmlinuz
не перекроет уже загруженный образ initramfs. По этой причине функция extract_kernel
также заботится о вычислении адресного пространства initramfs и выполнит выравнивание соответствующим образом распакованный
образ своего ядра. После того как мы получили верный адрес по которому наш заголовок может распаковывать
vmlinuz
, он выделит это ядро там.
340 asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
341 unsigned char *input_data,
342 unsigned long input_len,
343 unsigned char *output,
344 unsigned long output_len)
345 {
346 const unsigned long kernel_total_size = VO__end - VO__text;
347 unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
348 unsigned long needed_size;
349
350 /* Retain x86 boot parameters pointer passed from startup_32/64. */
351 boot_params = rmode;
352
353 /* Clear flags intended for solely in-kernel use. */
354 boot_params->hdr.loadflags &= ~KASLR_FLAG;
355
356 sanitize_boot_params(boot_params);
357
358 if (boot_params->screen_info.orig_video_mode == 7) {
359 vidmem = (char *) 0xb0000;
360 vidport = 0x3b4;
361 } else {
362 vidmem = (char *) 0xb8000;
363 vidport = 0x3d4;
364 }
365
366 lines = boot_params->screen_info.orig_video_lines;
367 cols = boot_params->screen_info.orig_video_cols;
368
369 console_init();
370
371 /*
372 * Save RSDP address for later use. Have this after console_init()
373 * so that early debugging output from the RSDP parsing code can be
374 * collected.
375 */
376 boot_params->acpi_rsdp_addr = get_rsdp_addr();
377
378 debug_putstr("early console in extract_kernel\n");
379
380 free_mem_ptr = heap; /* Heap */
381 free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
382
383 /*
384 * The memory hole needed for the kernel is the larger of either
385 * the entire decompressed kernel plus relocation table, or the
386 * entire decompressed kernel plus .bss and .brk sections.
387 *
388 * On X86_64, the memory is mapped with PMD pages. Round the
389 * size up so that the full extent of PMD pages mapped is
390 * included in the check against the valid memory table
391 * entries. This ensures the full mapped area is usable RAM
392 * and doesnt include any reserved areas.
393 */
394 needed_size = max(output_len, kernel_total_size);
395 #ifdef CONFIG_X86_64
396 needed_size = ALIGN(needed_size, MIN_KERNEL_ALIGN);
397 #endif
398
399 /* Report initial kernel position details. */
400 debug_putaddr(input_data);
401 debug_putaddr(input_len);
402 debug_putaddr(output);
403 debug_putaddr(output_len);
404 debug_putaddr(kernel_total_size);
405 debug_putaddr(needed_size);
406
407 #ifdef CONFIG_X86_64
408 /* Report address of 32-bit trampoline */
409 debug_putaddr(trampoline_32bit);
410 #endif
411
412 choose_random_location((unsigned long)input_data, input_len,
413 (unsigned long *)&output,
414 needed_size,
415 &virt_addr);
416
417 /* Validate memory location choices. */
418 if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
419 error("Destination physical address inappropriately aligned");
420 if (virt_addr & (MIN_KERNEL_ALIGN - 1))
421 error("Destination virtual address inappropriately aligned");
422 #ifdef CONFIG_X86_64
423 if (heap > 0x3fffffffffffUL)
424 error("Destination address too large");
425 if (virt_addr + max(output_len, kernel_total_size) > KERNEL_IMAGE_SIZE)
426 error("Destination virtual address is beyond the kernel mapping area");
427 #else
428 if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
429 error("Destination address too large");
430 #endif
431 #ifndef CONFIG_RELOCATABLE
432 if ((unsigned long)output != LOAD_PHYSICAL_ADDR)
433 error("Destination address does not match LOAD_PHYSICAL_ADDR");
434 if (virt_addr != LOAD_PHYSICAL_ADDR)
435 error("Destination virtual address changed when not relocatable");
436 #endif
437
438 debug_putstr("\nDecompressing Linux... ");
439 __decompress(input_data, input_len, NULL, NULL, output, output_len,
440 NULL, error);
441 parse_elf(output);
442 handle_relocations(output, output_len, virt_addr);
443 debug_putstr("done.\nBooting the kernel.\n");
444 return output;
445 }
Необходимый метод распаковки будет выбран в соответствии с методом сжатия, использованном на момент компиляции ядра.
Доступные методы распаковки можно обнаружить в том же самом файле misc.c
.
<snip from misc.c>
57 #ifdef CONFIG_KERNEL_GZIP
58 #include "../../../../lib/decompress_inflate.c"
59 #endif
60
61 #ifdef CONFIG_KERNEL_BZIP2
62 #include "../../../../lib/decompress_bunzip2.c"
63 #endif
64
65 #ifdef CONFIG_KERNEL_LZMA
66 #include "../../../../lib/decompress_unlzma.c"
67 #endif
68
69 #ifdef CONFIG_KERNEL_XZ
70 #include "../../../../lib/decompress_unxz.c"
71 #endif
72
73 #ifdef CONFIG_KERNEL_LZO
74 #include "../../../../lib/decompress_unlzo.c"
75 #endif
</snip>
После того как наше ядро распаковано в памяти, из самой функции extract_kernelc
будет получена точка входа этого раскрытого ядра и наш ЦПУ выполнит безусловный переход вовнутрь ядра.
Наше ядро выполняет большое число вещей, но я перечислю лишь то, что представляет наибольший интерес для вас, как кого- то изучающего запуск.
-
Наше ядро настроит размер стека самого ядра на 16 кБ в случае когда архитектура 64- битная. Это означает, что всякий новый процесс будет получать сво1 собственный стек ядра, который будет обладать в размере 16 кБ.
-
page_size
будет установлен на 4 кБ, что является размером страницы по умолчанию для 64- битной архитектуры Intel. -
Наше ядро подготовит оответствующий механизм обработки прерываний и исключительных ситуаций, также именуемый таблицей дескрипторов прерываний (IDT, interrupt descriptor table).
-
Ядро настроит необходимый механизм обработки отказов страниц.
-
Установленное ядро соберёт подробности своего файла initramfs, такие как название файла, размер, адрес, адрес перемещения, старшие и младшие номера нового корневого устройства и т.п. из
/arch/x86/kernel/setup.c
. -
Далее оно выделит initramfs из его исходного кода в
init/initramfs.c
. -
Наконец, оно запустит systemd при помощи функции
start_kernel
изinit/main.c
.
Вы можете обратить внимание, что это самый первый случай, когда мы выходим за рамки своего каталога
arch
. Это означает, что мы можем рассматривать данный код как не зависимый от
архитектуры. После того как наше ядро запущено, оно выполняет множество вещей и нет практически никакой возможности
охватить их все в данной книге. С точки зрения запуска лозунгом нашего ядра выступает запуск systemd из initramfs.
Поскольку initramfs уже была загружена в память начальным загрузчиком, выделение необходимого ядра initramfs потребует
подробностей файла самой initramfs, которые наше ядро получает из
/arch/x86/kernel/setup.c
.
Initramfs file name,
Initramfs file size,
Initramfs files address,
Initramfs files relocation address,
Major and minor numbers on which initramfs will be mounted.
После того как наше ядро получило подробности и своём файле initramfs, оно выделяет сам архив initramfs из
файла init/initramfs.c
. Подробнее мы обсудим как в точности наше ядро раскрывает
в памяти initramfs в Главе 5. Для монтирования initramfs в
качестве корневого устройствапотребуются виртуальные файловые системы, такие как proc
,
sys
, dev
и т.п., а потому наше ядро
подготавливает их надлежащим образом.
err = register_filesystem(&proc_fs_type);
if (err)
return;
Наше ядро позднее смонтирует свю выделенную initramfs при помощи функции do_mount_root
из init/do_mounts.c
. Как только initramfs смонтирована в памяти, наше ядро запустит
оттуда systemd. systemd будет запущен через ту же самую функцию start_kernel
из
файла init/main.c
.
asmlinkage void __init start_kernel(void)
В целом, когда наша корневая файловая система готова, она включится вовнутрь своей корневой файловой системы и создаст два потока: PID 1 это процесс systemd, а PID 2 это kthread. Для лучшего понимания отсылаем вас к показанной на Рисунке 4-4 блок- схеме.
Рисунок 4-5. отображает окончательную последовательность запуска которую мы обсудили на данный момент.
Прежде чем мы продолжим и рассмотрим как наше ядро выделяет initramfs и запускает из него systemd, нам необходимо разобраться с основами самой initramfs, например, зачем она нам нужна, какова её структура и т.п.. После того как мы разберёмся с важностью и основами initramfs мы продолжим свою последовательность запуска ролью systemd в общей последовательности запуска.