Запуск ядра linux вручную

Обновлено: 04.07.2024

Когда процессор ARM включен, его указатель регистра ПК указывает на начало фрагмента ПЗУ, встроенного в ИС.Этот фрагмент ПЗУ называется BROM (загрузочный диск), и система загружается через этот фрагмент BROM. Пространство BROM относительно невелико, обычно 32/64 КБ, и размер ShareRAM на ИС также отличается, поэтому процесс загрузки ИС также будет другим.
BROM будет хранить загрузочную программу при включении. Эта программа обычно содержит следующее содержимое:

Программа загрузки в BROM настраивается и разрабатывается производителем микросхемы. Он будет определять, входить ли в режим флэш-памяти или режим запуска в соответствии с другим оборудованием, а также определять, с какого носителя загружаться. Затем прочитайте загрузочную программу на MBREC с загрузочного носителя на ShareRAM и перейдите к выполнению. BROM доступен только для чтения и фиксируется при потоковой передаче SOC, поэтому в основном он не будет изменен после получения SOC, а все настраиваемые элементы реализованы в загрузчике.

2. Загрузка загрузчика (загрузка второй стадии):

Загрузка BROM, используемая в архитектуре ARM, немного похожа на загрузку BIOS под X86. Загрузка микропрограммы в BROM, как правило, не меняется. Начиная со второго этапа, она перешла в настраиваемый этап. Как правило, программа загрузки второго этапа Он будет помещен на раздел загрузочного носителя отдельно, мы называем его загрузочным разделом или разделом MBR (главный загрузочный раздел).
Мы можем разделить загрузку второго этапа на многоуровневую загрузку:
Например, он разделен на следующий трехуровневый процесс загрузки:
(1) firstMBRC
Загрузочная программа первого уровня должна соответствовать формату, необходимому для загрузки BROM. Она вызовет функцию драйвера в BROM, чтобы скопировать второй MBRRC в shareRAM для проверки и выполнения перехода. Это независимый код. , Как правило, используйте сборку, чтобы сделать.
(2) secondMBRC(uboot-spl)
Функция загрузочной программы второго уровня заключается в вызове функции драйвера в BROM для копирования mainMBRC в DDR для проверки и выполнения перехода. Второй этап может быть достигнут с помощью spl в uboot или с помощью собственного независимого кода.
(3) mainMBRC(uboot)
Третий уровень - основная загрузочная программа. Первые два уровня загрузки предназначены для загрузки mainMBRC. Его основная функция - отображение логотипа запуска, загрузка ядра, файловой системы dtb, rootfs и запуск. ядро. Обычно используйте uboot.
Итак, в загрузочном разделе мы должны запрограммировать три части загрузочного кода: mbrc, uboot-spl, uboot.

3. загрузочный загрузчик

Ядро, которое для запуска использует uboot, - это uImage. Это ядро ​​состоит из двух частей, одна из которых является заголовком, а другая - настоящим ядром. Таким образом вы можете выразить uImage = uboot header + zImage.
Определение заголовка:

Нам нужно заботиться о том, чтобы:

ih_load - это адрес загрузки ядра, то есть там, где должно быть ядро ​​до его запуска, ih_ep - это адрес входа в ядро, адрес входа и адрес загрузки могут совпадать.

Процесс загрузки ядра с помощью Uboot состоит в том, чтобы определить, как запустить ядро, прочитав bootcmd в переменной среды env. Например, uboot хочет прочитать раздел ядра с флэш-памяти nand по адресу памяти 0x30007FC0 и запустить ядро. Вы можете использовать следующую команду: bootcmd = nand read.jffs2 ядро ​​0x30007FC0, bootm 0x30007FC0. Ключом к запуску ядра является команда bootm.
Реализация команды bootm находится в функции do_bootm () в uboot:
Исходный файл: cmd_bootm.c

Сначала определите, следует ли за адресом загрузки команда bootm.Если параметр адреса загрузки не указан, адрес загрузки по умолчанию назначается для addr, в противном случае адрес, присоединенный к команде bootm, будет использоваться в качестве адреса загрузки. Затем ключ должен прочитать заголовок uboot с адреса загрузки и проанализировать его. Исходя из приведенной выше логики кода, мы видим, что он будет судить, согласован ли адрес загрузки, передаваемый ih_load и bootm в ubootheader, и, таким образом, различать два случая:

  • (1) Если оно отличается, ядро ​​(zImage) с удаленным заголовком (64Byte) будет скопировано по адресу, указанному в ih_load, и ядро ​​будет запущено из ih_ep. Следовательно, в этом случае ih_load и ih_ep должны быть одинаковыми.
  • (2) Если это то же самое, пусть он будет размещен там в целости и запускает ядро ​​(zImage) из ih_ep. Следовательно, в этом случае существует разница заголовка uboot между адресом функции входа выполнения и адресом загрузки. Поэтому ih_ep = ih_load + 64Byte.

С учетом вышеизложенного знания, как нам установить адрес в заголовке uboot, фактически этот шаг заключается в использовании инструмента mkimage для указания адреса загрузки и рабочего адреса при создании uImage.

Есть две ситуации для создания зеркальных головок и загрузки адресов:
Первое:

Адрес загрузки совпадает с адресом входа, а адреса, стоящие за tftp и bootm, являются произвольными адресами (за исключением адреса, указанного -a).

Адрес записи составляет 64 байта после адреса загрузки, а адреса после tftp и bootm должны быть по адресу загрузки, указанному -a.

Как правило, мы привыкли использовать второй метод и загружать его по адресу, указанному -a, чтобы вам не приходилось беспокоиться об обработке загрузок, что экономит время запуска.

В приведенном выше do_bootm мы получили информацию, относящуюся к образу ядра, проанализировав заголовок uboot, который включает адрес входа в ядро, и на последнем этапе загрузки он перейдет к ядру для выполнения. Этот шаг реализован в функции do_bootm_linux (). Перейдите через указатель на функцию thekernel () с тремя параметрами к точке входа ядра (zImage) и начните выполнение. На этом этапе задача u-boot завершена, и управление полностью передано ядру (zImage).

do_bootm_linux (), определенный в arch \ arm \ lib \ bootm.c, потому что мы уже знаем адрес записи, поэтому просто перейдите к адресу записи, чтобы запустить ядро ​​linux.

hdr-> ih_ep ---- Адрес точки входа, точка входа в ядро, указанная в uImage, помните ih_ep? Второй параметр - это идентификатор машины, машинный код, установленный ядром, и машинный код, установленный uboot, должен быть согласован для запуска ядра, а третий параметр - это первый адрес, который u-boot передает параметрам ядра, хранящимся в памяти.

4. Загрузка ядра

После загрузки при загрузке система начинает работать в zImage. zImage - это зеркальное отображение, которое включает в себя понимание кода сжатия и vmlinux, поэтому его выполнение можно разделить на три части, а именно распаковка zImage, ядро ​​vmlinux начинает этап сборки, ядро ​​vmlinux запускает этап языка C.

(1) декомпрессия zImage
(2) ядро ​​начинает этап сборки
(3) Ядро запускает стадию языка c (start_kernel для создания первого процесса)

zImage распаковать
На этом этапе задействованы только три файла:
(1)arch/arm/boot/compressed/vmlinux.lds
(2)arch/arm/boot/compressed/head.S
(3)arch/arm/boot/compressed/misc.c
Сначала перейдите к функции start в head.S, чтобы начать выполнение. Вы можете увидеть конкретный процесс в сочетании с файлом lds. Этот раздел не представляет слишком много.

Ядро начинает этап сборки
(Ссылочный файл ссылок на этом этапе: arch / arm / kernel / vmlinux.lds)
Код для запуска фазы сборки начинается с arch / arm / kernel / head.S, а отправной точкой выполнения является функция stext. Функция ввода указывается с помощью ENTRY (stext) в vmlinux.lds. Следует отметить, что в сборочном .S-файле также есть макроопределение ENTRY, которое должно отображаться в паре с ENDPROC для обозначения определенной функции. Кроме того, раздел, в котором находится текущий код, также должен быть указан в файле .S, например:

__HEAD - это определение макроса, объявленное как раздел .head.text, а ENTRY и ENDPROC используются для определения функции stext. Соответствующий файл lds показан ниже, где _text является константой, определенной в lds, и разные связанные константы будут существовать в разных разделах, таких как константа _text в разделе .head.text, _stext и _ в разделе .text. константы etext:

Основными задачами, выполняемыми в этой части, являются проверка идентификатора процессора, проверка идентификатора компьютера, создание таблицы страниц инициализации, настройка среды выполнения кода C, переход к первой реальной функции C ядра start_kernel для запуска выполнения.
Этот этап включает в себя две важные структуры:

  • (1) Одним из них является struct proc_info_list, который в основном описывает информацию, связанную с процессором, структура определяется в файле arch / arm / include / asm / procinfo.h, а связанные функции и переменные находятся в файле arch / arm / mm / proc_xxx.S Определение и назначение, такие как файл arch / arm / mm / proc-v7.S, используется armv7.
  • (2) Другая структура - это структура struct machine_desc, которая описывает плату разработки или информацию о машине. Структура определена в файле arch / arm / include / asm / mach / arch.h, а ее определение функций и назначение переменных находятся на плате. Очень актуальный файл arch / arm / mach-s3c2410 / mach-smdk2410.c, который также является очень важным файлом для трансплантации ядра.

Этот этап обычно вызывается предыдущим кодом распаковки, и для входа на этот этап требуется:
MMU = off, D-cache = off, I-cache = dont care,r0 = 0, r1 = machine id.
Все списки идентификаторов машин сохраняются в файле arch / arm / tools / mach-types, и соответствующий файл заголовка будет сгенерирован в kernel / include / generate / mach-types.h во время компиляции. в. Ядро найдет соответствующую структуру struct machine_desc в соответствии с входящим идентификатором машины и использует функцию обратного вызова для запуска ядра.

Во время компиляции две структурные переменные, определенные выше, struct proc_info_list, будут связаны с сегментом между __proc_info_begin и __proc_info_end файла образа ядра vmlinux. struct machine_desc будет связана с сегментом между __arch_info_begin и __arch_info_end файла образа ядра vmlinux. Соответствуя * (. Proc.info.init) и * (. Arch.info.init) соответственно, вы можете обратиться к следующему сценарию подключения vmlinux.lds.

В конце этапа сборки он перейдет к этапу языка C. и продолжит запуск. В head-common.S инструкция b start_kernel переходит к коду C для выполнения.

Ядро запускает стадию языка Си
Функция входа языка C определена в kernel / init / main.c. Она запускает выполнение через функцию start_kernel. Она вызовет много функций, связанных с платформой. Определение этой части функции все еще находится в каталог kernel / arch / arm / mach-XXX.

Например, в определении машины вы можете использовать функцию, определенную для соответствующей машины в start_kernel:

Для платформы smdk2410 соответствующая структура machine_desc инициализируется в файле linux / arch / arm / mach-s3c2410 / mach-smdk2410.c:

Макрос MACHINE_START определяется в файле arch / arm / include / asm / mach / arch.h:

attribute((section(".arch.info.init"))) указывает, где структура будет сохранена в будущем.

Помните информацию в vmlinux.lds, упомянутую выше?

Таким образом, содержимое, определенное выше, будет размещено в этом сегменте между __arch_info_begin и __arch_info_end. Это позволяет явно найти структуру в файле сборки.

Далее кратко представим процесс запуска процессора следующим образом:
Для SMP загрузочный ЦП будет выполнять функцию cpu_init во время инициализации системы для инициализации ЦП. Конкретная последовательность вызовов: start_kernel—> setup_arch—> setup_processor—> cpu_init. Для других процессоров в системе загрузочный процессор инициализирует каждый оперативный процессор в конце инициализации системы. Конкретная последовательность вызова:

Функция __cpu_up связана с архитектурой процессора. Для ARM вызывающая последовательность

В данном разделе рассматриваются этапы сборки ядра Linux и обсуждается результат работы каждого из этапов. Процесс сборки в значительной степени зависит от аппаратной платформы, поэтому особое внимание будет уделено построению ядра Linux для платформы x86.

Размер загрузочного сектора (bootsector) всегда равен 512 байт. Размер установщика (setup) должен быть не менее чем 4 сектора, и ограничивается сверху размером около 12K - по правилу:

512 + setup_sects * 512 + место_для_стека_bootsector/setup <= 0x4000 байт

Откуда взялось это ограничение станет понятным дальше.

На сегодняшний день верхний предел размера bzImage составляет примерно 2.5M, в случае загрузки через LILO, и 0xFFFF параграфов (0xFFFF0 = 1048560 байт) для загрузки raw-образа, например с дискеты или CD-ROM (El-Torito emulation mode).

Следует помнить, что tools/build выполняет проверку размеров загрузочного сектора, образа ядра и нижней границы установщика (setup), но не проверяет *верхнюю* границу установщика (setup). Следовательно очень легко собрать "битое" ядро, добавив несколько больший размер ".space" в конец setup.S .

1.2 Загрузка: Обзор

Процесс загрузки во многом зависит от аппаратной платформы, поэтому основное внимание будет уделено платформе IBM PC/IA32. Для сохранения обратной совместимости, firmware-загрузчики загружают операционную систему устаревшим способом. Процесс этот можно разделить на несколько этапов:

  1. BIOS выбирает загрузочное устройство.
  2. BIOS загружает bootsector с загрузочного устройства.
  3. Код bootsector-а загружает установщика, процедуры декомпрессии и сжатый образ ядра.
  4. Ядро декомпрессируется в защищенном режиме (protected mode).
  5. Выполняется низкоуровневый инициализирующий ассемблерный код.
  6. Выполняется высокоуровневый инициализирующий C код.

1.3 Загрузка: BIOS POST

1.4 Загрузка: bootsector и setup

Для загрузки ядра Linux можно воспользоваться следующими загрузочными секторами:

  • Загрузочным сектором Linux из ( arch/i386/boot/bootsect.S ),
  • Загрузочный сектор LILO (или другого менеджера загрузки), или
  • обойтись без загрузочного сектора (loadlin и т.п.)

А теперь подробнее рассмотрим загрузочный сектор. В первых нескольких строках инициализируются вспомогательные макросы, используемые как значения сегментов:

(числа в начале - это номера строк в файле bootsect.S file) Значения DEF_INITSEG , DEF_SETUPSEG , DEF_SYSSEG и DEF_SYSSIZE берутся из файла include/asm/boot.h :

Рассмотрим поближе код bootsect.S :

Строки 54-63 перемещают код начального загрузчика из адреса 0x7C00 в адрес 0x90000. Для этого:

  1. в регистровую пару %ds:%si заносится значение $BOOTSEG:0 (0x7C0:0 = 0x7C00)
  2. в регистровую пару %es:%di заносится значение $INITSEG:0 (0x9000:0 = 0x90000)
  3. в регистр %cx записывается число 16-битовых слов (256 слов = 512 байт = 1 сектор)
  4. В регистре флагов EFLAGS сбрасывается флаг направления DF (Direction Flag) (копирование с автоинкрементом адресных регистров) (cld)
  5. копируется 512 байт (rep movsw)

Здесь умышленно не используется инструкция rep movsd (обратите внимание на директиву - .code16).

В строке 64 выполняется переход на метку go: , в только что созданную копию загрузчика, т.е. в сегмент 0x9000. Эта, и следующие три инструкции (строки 64-76) переустанавливают регистр сегмента стека и регистр указателя стека на $INITSEG:0x4000-0xC, т.е. %ss = $INITSEG (0x9000) и %sp = 0x3FF4 (0x4000-0xC). Это и есть то самое ограничение на размер setup, которое упоминалось ранее (см. Построение образа ядра Linux).

Для того, чтобы разрешить считывание сразу нескольких секторов (multi-sector reads), в строках 77-103 исправляются некоторые значения в таблице параметров для первого диска :

Контроллер НГМД переводится в исходное состояние функцией 0 прерывания 0x13 в BIOS (reset FDC) и секторы установщика загружаются непосредственно после загрузчика, т.е. в физические адреса, начиная с 0x90200 ($INITSEG:0x200), с помощью функции 2 прерывания 0x13 BIOS (read sector(s)). Смотри строки 107-124:

Если загрузка setup_sects секторов кода установщика прошла благополучно, то производится переход на метку ok_load_setup: .

Далее производится загрузка сжатого образа ядра в физические адреса начиная с 0x10000, чтобы не затереть firmware-данные в нижних адресах памяти (0-64K). После загрузки ядра управление передается в точку $SETUPSEG:0 ( arch/i386/boot/setup.S ). Поскольку обращений к BIOS больше не будет, данные в нижней памяти уже не нужны, поэтому образ ядра перемещается из 0x10000 в 0x1000 (физические адреса, конечно). И наконец, установщик setup.S завершает свою работу, переводя процессор в защищенный режим и передает управление по адресу 0x1000 где находится точка входа в сжатое ядро, т.е. arch/386/boot/compressed/ . Здесь производится установка стека и вызывается decompress_kernel() , которая декомпрессирует ядро в адреса, начиная с 0x100000, после чего управление передается туда.

Следует отметить, что старые загрузчики (старые версии LILO) в состоянии загружать только первые 4 сектора установщика (setup), это объясняет присутствие кода, "догружающего" остальные сектора в случае необходимости. Кроме того, установщик содержит код, обрабатывающий различные комбинации типов/версий загрузчиков и zImage/bzImage.

Теперь рассмотрим хитрость, позволяющую загрузчику выполнить загрузку "больших" ядер, известных под именем "bzImage". Установщик загружается как обычно, в адреса с 0x90200, а ядро, с помощью специальной вспомогательной процедуры, вызывающей BIOS для перемещения данных из нижней памяти в верхнюю, загружается кусками по 64К. Эта процедура определена в setup.S как bootsect_helper , а вызывается она из bootsect.S как bootsect_kludge . Метка bootsect_kludge , определенная в setup.S , содержит значение сегмента установщика и смещение bootsect_helper в нем же, так что для передачи управления загрузчик должен использовать инструкцию lcall (межсегментный вызов). Почему эта процедура помещена в setup.S ? Причина банальна - в bootsect.S просто больше нет места (строго говоря это не совсем так, поскольку в bootsect.S свободно примерно 4 байта и по меньшей мере еще 1 байт, но вполне очевидно, что этого недостаточно) Эта процедура использует функцию прерывания BIOS 0x15 (ax=0x8700) для перемещения в верхнюю память и переустанавливает %es так, что он всегда указывает на 0x10000. Это гарантирует, что bootsect.S не исчерпает нижнюю память при считывании данных с диска.

1.5 LILO в качестве загрузчика.

Специализированные загрузчики (например LILO) имеют ряд преимуществ перед чисто Linux-овым загрузчиком (bootsector):

  1. Возможность выбора загрузки одного из нескольких ядер Linux или одной из нескольких ОС.
  2. Возможность передачи параметров загрузки в ядро (существует патч BCP который добавляет такую же возможность и к чистому bootsector+setup).
  3. Возможность загружать большие ядра (bzImage) - до 2.5M (против обычного 1M).

Старые версии LILO ( версии 17 и более ранние) не в состоянии загрузить ядро bzImage. Более новые версии (не старше 2-3 лет) используют ту же методику, что и bootsect+setup, для перемещения данных из нижней в верхнюю память посредством функций BIOS. Отдельные разработчики (особенно Peter Anvin) выступают за отказ от поддержки ядер zImage. Тем не менее, поддержка zImage остается в основном из-за (согласно Alan Cox) существования некоторых BIOS-ов, которые не могут грузить ядра bzImage, в то время как zImage грузятся ими без проблем.

В заключение, LILO передает управление в setup.S и далее загрузка продолжается как обычно.

1.6 Высокоуровневая инициализация

Под "высокоуровневой инициализацией" следует понимать действия, непосредственно не связанные с начальной загрузкой, даже не смотря на то, что часть кода, выполняющая ее, написана на ассемблере, а именно в файле arch/i386/kernel/head.S , который является началом декомпрессированного ядра. При инициализации выполняются следующие действия:

  1. Устанавливаются сегментные регистры (%ds = %es = %fs = %gs = __KERNEL_DS = 0x18).
  2. Инициализируются таблицы страниц.
  3. Разрешается листание страниц, установкой бита PG в %cr0.
  4. Обнуляется BSS (для SMP (мультипроцессорных систем (прим. перев.)), это действие выполняет только первый CPU).
  5. Копируются первые 2k bootup параметров (kernel commandline).
  6. Проверяется тип CPU, используя EFLAGS и, если возможно, cpuid, позволяющие обнаружить процессор 386 и выше.
  7. Первый CPU вызывает start_kernel() , все остальные - arch/i386/kernel/smpboot.c:initialize_secondary() , если переменная ready=1, которая только переустанавливает esp/eip.

Функция init/main.c:start_kernel() написана на C и выполняет следующие действия:

Еще одна замечательная особенность Linux - это возможность запуска "альтернативной программы инициализации", если ядру передается командная строка "init=". Эта особенность может применяться для перекрытия /sbin/init или для отладки скриптов инициализации (rc) и /etc/inittab вручную, запуская их по одному за раз

1.7 SMP Загрузка на x86

В случае SMP (многопроцессорной системы), первичный процессор проходит обычную последовательность - bootsector, setup и т.д., пока не встретится вызов функции start_kernel() , в которой стоит вызов функции smp_init() , откуда вызывается arch/i386/kernel/smpboot.c:smp_boot_cpus() . Функция smp_boot_cpus() в цикле (от 0 до NR_CPUS ) вызывает do_boot_cpu() для каждого apicid. Функция do_boot_cpu() создает (т.е. fork_by_hand ) фоновую задачу для указанного CPU и записывает, согласно спецификации Intel MP (в 0x467/0x469) трамплин-код, содержащийся в trampoline.S . Затем генерирует STARTUP IPI, заставляя вторичный процессор выполнить код из trampoline.S .

Ведущий процессор создает трамплин-код для каждого процессора в нижней памяти. Ведомый процессор, при исполнении "трамплина", записывает "магическое число", чтобы известить ведущий процессор, что код исполнен. Требование, по размещению трамплин-кода в нижней памяти, обусловлено спецификацией Intel MP.

Трамплин-код просто записывает 1 в %bx, переводит процессор в защищенный режим и передает управление на метку startup_32, которая является точкой входа в arch/i386/kernel/head.S .

При исполнении кода head.S , ведомый CPU обнаруживает, что он не является ведущим, перепрыгивает через очистку BSS и входит в initialize_secondary() которая переходит в фоновую задачу для данного CPU - минуя вызов init_tasks[cpu] , поскольку она уже была проинициирована ведущим процессором при исполнении do_boot_cpu(cpu) .

Характерно, что код init_task может использоваться совместно, но каждая фоновая задача должна иметь свой собственный TSS. Именно поэтому init_tss[NR_CPUS] является массивом.

1.8 Освобождение памяти после инициализации

После выполнения инициализации операционной системы, значительная часть кода и данных становится ненужной. Некоторые системы (BSD, FreeBSD и пр.) не освобождают память, занятую этой ненужной информацией. В оправдание этому приводится (см. книгу McKusick-а по 4.4BSD): "данный код располагается среди других подсистем и поэтому нет никакой возможности избавиться от него". В Linux, конечно же такое оправдание невозможно, потому что в Linux "если что-то возможно в принципе, то это либо уже реализовано, либо над этим кто-то работает".

Как уже упоминалось ранее, ядро Linux может быть собрано только в двоичном формате ELF. Причиной тому (точнее одна из причин) - отделение инициализирующего кода/данных, для создания которых Linux предоставляет два макроса:

  • __init - для кода инициализации
  • __initdata - для данных

Макросы подсчитывают размер этих секций в спецификаторах аттрибутов gcc, и определены в include/linux/init.h :

Что означает - если код скомпилирован статически (т.е. литерал MODULE не определен), то он размещается в ELF-секции .text.init , которая объявлена в карте компоновки arch/i386/vmlinux.lds . В противном случае (т.е. когда компилируется модуль) макрос ничего не делает.

Таким образом, в процессе загрузки, поток ядра "init" (функция init/main.c:init() ) вызывает функцию free_initmem() , которая и освобождает все страницы памяти между адресами __init_begin и __init_end .

На типичной системе (на моей рабочей станции) это дает примерно 260K памяти.

Код, регистрирующийся через module_init() , размещается в секции .initcall.init , которая так же освобождается. Текущая тенденция в Linux - при проектировании подсистем (не обязательно модулей) закладывать точки входа/выхода на самых ранних стадиях с тем, чтобы в будущем, рассматриваемая подсистема, могла быть модулем. Например: pipefs, см. fs/pipe.c . Даже если подсистема никогда не будет модулем напрмер bdflush (см. fs/buffer.c ), все равно считается хорошим тоном использовать макрос module_init() вместо прямого вызова функции инициализации, при условии, что не имеет значения когда эта функция будет вызвана.

Имеются еще две макрокоманды, работающие подобным образом. Называются они __exit и __exitdata , но они более тесно связаны с поддержкой модулей, и поэтому будет описаны ниже.

1.9 Разбор командной строки

Давайте посмотрим как выполняется разбор командной строки, передаваемой ядру на этапе загрузки:

Для написания кода, обрабатывающего командную строку, следует использовать макрос __setup() , определенный в include/linux/init.h :

Ниже приводится типичный пример, при написании собственного кода (пример взят из реального кода драйвера BusLogic HBA drivers/scsi/BusLogic.c ):

Обратите внимание, что __setup() не делает ничего в случае, когда определен литерал MODULE, так что, при необходимости обработки командной строки начальной загрузки как модуль, так и статически связанный код, должен вызывать функцию разбора параметров "вручную" в функции инициализации модуля. Это так же означает, что возможно написание кода, который обрабатывает командную строку, если он скомпилирован как модуль, и не обрабатывает, когда скомпилирован статически, и наоборот.

Все дистрибутивы строятся вокруг ядра Linux. Ядро является слоем между пользовательскими программами и оборудованием системы. Gentoo предоставляет несколько вариантов исходного кода ядра. Полный список с описанием доступен на странице статье Общие сведения о ядре.

Для систем, основанных на amd64 архитектуре, рекомендуется пакет sys-kernel/gentoo-sources.

Выберем подходящий исходный код ядра и установим его с помощью emerge :

Данная команда установит исходный код ядра Linux в /usr/src/ , в котором символьная ссылка linux будет указывать на установленную версию:

Теперь следует сконфигурировать и собрать ядро. Существует два основных подхода:

  1. Ядро конфигурируется и собирается вручную.
  2. Ядро автоматически собирается и устанавливается с помощью genkernel .

В данном руководстве мы расскажем, как провести настройку вручную, поскольку это лучший способ оптимизировать окружение.

Ручное конфигурирование

Введение

Однако одна вещь является истиной: при ручной конфигурации ядра очень важно понимать свою систему. Большую часть сведений можно почерпнуть, установив пакет sys-apps/pciutils, который содержит в команду lspci :

Заметка
Находясь внутри изолированного окружения chroot, можно спокойно игнорировать любые предупреждения pcilib (например, pcilib: cannot open /sys/bus/pci/devices), которые могут появляться в выводе lspci .

Другим источником информации о системе может стать вывод команды lsmod , по которому можно понять, какие модули ядра использует установочный носитель, чтобы потом включить аналогичные настройки.

Остаётся перейти в каталог с ядром и выполнить make menuconfig , который запустит экран меню конфигурации.

В конфигурации ядра Linux есть много-много разделов. Сначала пройдёмся по пунктам, которые должны быть обязательно включены (иначе Gentoo будет работать неправильно или же вовсе не запустится). Также в вики есть Руководство по настройке ядра Gentoo, которое поможет понять более тонкие детали.

Включение обязательных параметров

If you are using sys-kernel/gentoo-sources, we strongly recommend you enable the Gentoo-specific configuration options. These ensure that a minimum of kernel features required for proper functioning is available:

Ядро Enabling Gentoo-specific options

Naturally your choice in the last two lines depends on your choice of init system (OpenRC vs. Systemd).

If you are using sys-kernel/vanilla-sources, you will have to find the required options on your own.

Убедитесь, что все драйверы, необходимые для загрузки системы (например, контроллер SCSI) собраны прямо в ядре, а не в виде модуля. В противном случае, система может не загрузиться.

Далее следует выбрать тип процессора. Также рекомендуется включить возможности MCE (если они доступны), чтобы пользователи системы могли получать оповещение о любых проблемах с оборудованием. На некоторых архитектурах (например, x86_64) подобные ошибки выводятся не в dmesg , а в /dev/mcelog . А для него понадобится пакет app-admin/mcelog.

Также включите Maintain a devtmpfs file system to mount at /dev, чтобы критически важные файлы устройств были доступны на самом раннем этапе загрузки ( CONFIG_DEVTMPFS и CONFIG_DEVTMPFS_MOUNT ):

Убедитесь, что поддержка SCSI-дисков включена ( CONFIG_BLK_DEV_SD ):

Ядро Включение поддержки SCSI-дисков

Теперь перейдите в раздел File Systems и выберите те файловые системы, которые планируете использовать. Файловая система, используемая в качестве корневой должна быть включена в ядро, иначе Gentoo не сможет примонтировать данный раздел. Также включите Virtual memory и /proc file system. По необходимости выберете один или несколько элементов из списка ( CONFIG_EXT2_FS , CONFIG_EXT3_FS , CONFIG_EXT4_FS , CONFIG_MSDOS_FS , CONFIG_VFAT_FS , CONFIG_PROC_FS и CONFIG_TMPFS ):

Ядро Выбираем необходимые файловые системы

Если для подключения к Интернету используется PPPoE или модемное соединение, то включите следующие параметры ( CONFIG_PPP , CONFIG_PPP_ASYNC и CONFIG_PPP_SYNC_TTY ):

Ядро Выбираем необходимые драйвера для PPPoE

Два параметра сжатия не повредят, но и не являются обязательными, как и PPP over Ethernet. Фактически, последний используется в случае, если ppp сконфигурирован на использование ядерный PPPoE режим.

Не забудьте настроить поддержку сетевых карт (Ethernet или беспроводных).

Поскольку большинство современных систем являются многоядерными, важно включить Symmetric multi-processing support ( CONFIG_SMP ):

Заметка
Во многоядерных системах каждое ядро считается за один процессор.

Если используются USB-устройства ввода (например клавиатура и мышь) или другие устройства, то не забудьте включить и эти параметры ( CONFIG_HID_GENERIC , CONFIG_USB_HID , CONFIG_USB_SUPPORT , CONFIG_USB_XHCI_HCD , CONFIG_USB_EHCI_HCD , CONFIG_USB_OHCI_HCD ):

Ядро Включаем поддержку USB для устройств ввода

Настройка ядра, специфичная для архитектуры

Если необходимо поддерживать 32-битные программы, убедитесь, что выбран пункт IA32 Emulation ( CONFIG_IA32_EMULATION ). Gentoo устанавливает систему multilib (смешанные 32/64 битные вычисления) по умолчанию, поэтому данная опция необходима, если конечно не выбран профиль no-multilib.

Ядро Выбор типа процессора и возможностей

Включите поддержку меток GPT, если использовали их ранее при разбиении диска ( CONFIG_PARTITION_ADVANCED и CONFIG_EFI_PARTITION ):

Включите поддержку EFI stub и EFI variables в ядре Linux, если для загрузки системы используется UEFI ( CONFIG_EFI , CONFIG_EFI_STUB , CONFIG_EFI_MIXED и CONFIG_EFI_VARS ):

Компиляция и установка

Когда настройка закончена, настало время скомпилировать и установить ядро. Выйдите из настройки и запустите процесс компиляции:

Заметка
Можно включить параллельную сборку, используя make -jX , где X — это число параллельных задач, которые может запустить процесс сборки. Это похоже на инструкции, которые были даны ранее относительно файла /etc/portage/make.conf в части переменной MAKEOPTS .

По завершении компиляции, скопируйте образ ядра в каталог /boot/ . Это делается командой make install :

Данная команда скопирует образ ядра в каталог /boot/ вместе с файлом System.map и файлом настройки ядра.

Необязательно: Сборка initramfs

В некоторых случаях необходимо собрать initramfs — файловую систему инициализации, размещаемую в оперативной памяти. Одна из необходимых причин для этого — когда важные части системных путей (например, /usr/ или /var/ ) находятся на отдельных разделах. Эти разделы могут быть смонтированы средствами, расположенными внутри initramfs.

Без initramfs есть большой риск того, что система не загрузится правильно, так как программы или информация о разделах, необходимые для монтирования таких разделов, находятся непосредственно в этих разделах. initramfs вытянет все необходимые файлы в архив, который будет загружен в память сразу же после загрузки ядра, но до того, как управление будет передано системе инициализации. Сценарии initramfs выполнят все необходимые операции для правильного монтирования разделов до того, как продолжится загрузка системы.

Важно
It is recommended to use genkernel for both, building kernel and initramfs. If you decide to use genkernel only for generating initramfs it is crucial to pass --kernel-config=/path/to/kernel.config to genkernel or generated initramfs may not work with your manually built kernel.

Для установки initramfs, сперва нужен sys-kernel/genkernel, который его создаст:

Если необходима особая поддержка в initramfs, например LVM или RAID, то следует указать это через соответствующий параметр genkernel . Для более подробной информации см. genkernel --help . В следующем примере включена поддержка LVM и программного RAID ( mdadm ):

initramfs будет сохранён в /boot/ под названием, начинающимся с «initramfs»:

Теперь продолжите с раздела Модули ядра.

Альтернатива: Использование genkernel

Если ручная установка кажется слишком сложной, то можно воспользоваться утилитой genkernel , которая сконфигурирует и соберёт ядро автоматически.

genkernel конфигурирует ядро примерно так же, как это делается для установочного носителя. Это значит, что ядро, собранное genkernel , постарается определить всё оборудование в процессе загрузки. Поскольку genkernel не требует ручной конфигурации ядра, он идеально подходит для тех пользователей, кто не готов собирать собственное ядро.

Приступим. Сперва нужно установить sys-kernel/genkernel:

Далее отредактируйте файл /etc/fstab , где следует указать в строке /boot/ правильное устройство во втором поле. Если вы следовали примеру разбиения разделов из данного Руководста, то, скорее всего, это будет устройство /dev/sda1 с файловой системой ext2. Тогда строка должна выглядеть следующим образом:

Файл /etc/fstab Настройка точки монтирования /boot Заметка
В процессе настройки Gentoo /etc/fstab ещё будет изменён. На данный момент мы правим лишь /boot , так как genkernel использует эту настройку.

Осталось скомпилировать ядро, выполнив genkernel all . Учтите, что поскольку genkernel включает поддержку как можно большего диапазона оборудования, процесс сборки может занять некоторое время!

Заметка
Если для корневого раздела не используется ext2, ext3 или ext4, то возможно придётся вручную настроить ядро, выполнив genkernel --menuconfig all и добавив поддержку нужной ФС (не как модуля). Пользователям LVM2 следует также добавить --lvm в качестве аргумента.

По завершению работы genkernel будут сформированы ядро, полный набор модулей и файловая система инициализации (initramfs). Ядро и initrd нам понадобятся позднее. Запишите название файлов ядра и initrd, так как они нам понадобятся при настройке загрузчика. Initrd запускается сразу после ядра для определения оборудования (как при загрузке установочного CD), перед запуском самой системы.

Alternative: Using distribution kernels

Distribution Kernels are ebuilds that cover the complete process of unpacking, configuring, compiling, and installing the kernel. The primary advantage of this method is that the kernels are upgraded to new versions as part of @world upgrade without a need for manual action. Distribution kernels default to a configuration supporting the majority of hardware but they can be customized via /etc/portage/savedconfig .

There are other methods available to customize the kernel config such as config snippets.

Installing correct installkernel

Before using the distribution kernels, please verify that the correct installkernel package for the system is installed. When using systemd-boot (formerly gummiboot), install:

When using a traditional /boot layout (e.g. GRUB, LILO, etc.), the gentoo variant should be installed by default. If in doubt:

Installing a distribution kernel

To build a kernel with Gentoo patches from source, type:

System administrators who want to avoid compiling the kernel sources locally can instead use precompiled kernel images:

Upgrading and cleaning up

Once the kernel is installed, the package manager will automatically upgrade it to newer versions. The previous versions will be kept until the package manager is requested to clean up stale packages. Please remember to periodically run:

to save space. Alternatively, to specifically clean up old kernel versions:

Post-install/upgrade tasks

Distribution kernels are now capable of rebuilding kernel modules installed by other packages. linux-mod.eclass provides USE=dist-kernel which controls a subslot dependency on virtual/dist-kernel.

Enabling this on packages like sys-fs/zfs and sys-fs/zfs-kmod allows them to automatically be rebuilt against the new kernel and re-generate the initramfs if applicable accordingly!

Manually rebuilding the initramfs

If required, manually trigger such rebuilds by, after a kernel upgrade, executing:

If any of these modules (e.g. ZFS) are needed at early boot, rebuild the initramfs afterward:

Модули ядра

Конфигурация модулей

Заметка
Модули оборудования не обязательно указывать вручную. В большинстве случаев, udev автоматически загрузит все необходимые модуля для обнаруженных устройств. Однако, не будет никакого вреда, если указать автоматически загружаемые модули вручную. Иногда очень специфическим устройствам необходима некоторая помощь, чтобы загрузить свои драйвера.

Укажите модули, которые должны загружаться автоматически в файлах /etc/modules-load.d/*.conf , по одному модулю в строке. Дополнительные параметры для модулей при необходимости можно указывать в файлах /etc/modprobe.d/*.conf .

Чтобы посмотреть доступные модули, выполните команду find , не забыв заменить «<kernel version>» на собранную в предыдущем шаге версию:

Например, чтобы автоматически загрузить модуль 3c59x.ko (драйвер для определённого семейства сетевых карт от 3Com), отредактируйте файл /etc/modules-load.d/network.conf , добавив туда имя модуля. Фактическое имя файла несущественно для загрузчика.

Файл /etc/modules-load.d/network.conf Принудительная загрузка модуля 3c59x во время загрузки

Продолжите установку с раздела Настройка системы.

Необязательно: Установка файлов прошивки

Каждый раз, когда вы включаете свой компьютер с Linux, он проходит ряд этапов, прежде чем, наконец, отобразится экран входа в систему, который запрашивает ваше имя пользователя или пароль. Существует 4 различных этапа, которые каждый дистрибутив Linux проходит в типичном процессе загрузки.

В этом руководстве мы выделим различные шаги, предпринятые ОС Linux с момента включения до момента входа в систему. Обратите внимание, что это руководство сфокусировано на загрузчике GRUB2 и systemd init, поскольку они используются в настоящее время подавляющим большинством современных дистрибутивов Linux. Но существуют и другие загрузчики, например, дистрибутивы на основе Arch Linux используют systemd-boot.


Процесс загрузки состоит из следующих 4 шагов, которые мы обсудим более подробно:

  1. Инициализация системы: UEFI или BIOS (POST)
  2. Запуск загрузчика (GRUB2 или systemd-boot)
  3. Инициализация ядра
  4. Запуск systemd, родителя всех процессов

1. Инициализация системы: UEFI или BIOS

Инициализация системы под UEFI

  1. Система включена, выполняется самотестирование при включении (POST).
  2. После POST UEFI инициализирует оборудование, необходимое для загрузки (диск, контроллеры клавиатуры и т. д.).
  3. Прошивка считывает загрузочные записи в NVRAM, чтобы определить, какое приложение EFI запускать и откуда (например, с какого диска и раздела).
  • Загрузочной записью может быть просто диск. В этом случае микропрограмма ищет системный раздел EFI на этом диске и пытается найти приложение EFI в резервном загрузочном пути \EFI\BOOT\BOOTX64.EFI (BOOTIA32.EFI в системах с IA32 (32-разрядным) UEFI). Так работают загрузочные съёмные носители UEFI.
  1. Прошивка запускает приложение EFI.
  • Это может быть загрузчик или само ядро Arch, использующее EFISTUB.
  • Это может быть какое-то другое приложение EFI, такое как оболочка UEFI, или менеджер загрузки, например systemd-boot или rEFInd.

Если включена безопасная загрузка, процесс загрузки будет проверять подлинность двоичного файла EFI по подписи.

Инициализация системы под BIOS

  1. Система включена, выполняется самотестирование при включении (POST).
  2. После POST BIOS инициализирует оборудование, необходимое для загрузки (диск, контроллеры клавиатуры и т. д.).
  3. BIOS запускает первые 440 байтов (область кода начальной загрузки основной загрузочной записи) первого диска в порядке дисков BIOS.
  4. Затем первый этап загрузчика в загрузочном коде MBR запускает свой второй этап (если есть) из одного из следующих источников:
  • следующие секторы диска после MBR, то есть так называемый промежуток после MBR (только в таблице разделов MBR).
  • загрузочная запись тома раздела или диска без разделов (VBR).
  • загрузочный раздел BIOS (только GRUB в BIOS/GPT).
  1. Запускается фактический загрузчик.
  2. Затем загрузчик загружает операционную систему путём последовательной или прямой загрузки ядра операционной системы.

Проверка целостности BIOS (POST)

Процесс загрузки обычно инициализируется, когда пользователь нажимает кнопку включения — если ПК уже был выключен — или перезагружает систему с помощью графического интерфейса или командной строки.

Когда система Linux включается, включается BIOS (базовая система ввода-вывода) и выполняет самотестирование при включении (POST). Это проверка целостности, которая выполняет множество диагностических проверок.

В некоторых случаях раздаётся звуковой сигнал, особенно в случае отсутствия модуля RAM. Однако, если ожидаемое оборудование присутствует и функционирует должным образом, процесс загрузки переходит к следующему этапу.

2. Загрузчик (GRUB2 или systemd-boot)


Если компьютер запускает UEFI, то обычно она запускает приложение EFI, обычно располагающееся по пути \EFI\BOOT\BOOTX64.EFI (/boot/EFI/BOOT/BOOTX64.EFI) на загрузочном диске.

Если это BIOS, то после завершения POST, BIOS проверяет MBR (главную загрузочную запись) на предмет загрузчика и информации о разделах диска.

MBR — это 512-байтовый код, расположенный в первом секторе жёсткого диска, обычно это /dev/sda или /dev/hda, в зависимости от архитектуры вашего жёсткого диска. Обратите внимание, однако, что иногда MBR может находиться на Live USB или DVD-диске Linux.

В Linux существует 2 основных типа загрузчиков: GRUB2 и systemd-boot. Загрузчик GRUB2 — распространён в дистрибутивах на основе Debian. Загрузчик systemd-boot применяется в Arch Linux и основанных на этой ОС дистрибутивах.

Загрузчик GRUB2


GRUB2 означает GRand Unified Bootloader version 2. Как только BIOS обнаруживает загрузчик grub2, он запускается и загружает его в основную память (RAM).

Современный GRUB2 может работать и с UEFI (с помощью efibootmgr). В Arch Linux поддержка BIOS и UEFI собрана в один пакет grub. В Debian и производных дистрибутивах GRUB представлен двумя версиями:

Меню grub2 позволяет вам делать несколько вещей. Оно позволяет вам выбрать версию ядра Linux, которую вы хотите использовать. Если вы несколько раз обновляли свою систему, вы можете увидеть в списке разные версии ядра. Кроме того, он даёт вам возможность редактировать некоторые параметры ядра, нажимая комбинацию клавиш клавиатуры.

Кроме того, в настройке с двойной загрузкой, когда у вас есть несколько установок ОС, меню grub позволяет вам выбрать, в какую ОС загружаться. Файл конфигурации grub2 — это файл /boot/grub2/grub2.cfg. Основная цель GRUB — загрузить ядро Linux в основную память.

Загрузчик systemd-boot

systemd-boot (сокращенно sd-boot) — простой менеджер загрузки UEFI. Он предоставляет графическое меню для выбора записи для загрузки и редактор командной строки ядра. Systemd-boot поддерживает системы только с прошивкой UEFI.

systemd-boot загружает информацию о загрузочной записи из системного раздела EFI (ESP), обычно монтируемого в /efi/, /boot/ или /boot/efi/ во время запуска ОС, а также из расширенного раздел загрузчика, если он существует (обычно монтируется в /boot/). Фрагменты файла конфигурации, ядра, initrds и другие образы EFI для загрузки обычно должны находиться на ESP или разделе расширенного загрузчика. Ядра Linux должны быть собраны с CONFIG_EFI_STUB, чтобы их можно было напрямую запускать как образ EFI. Во время загрузки systemd-boot автоматически собирает список загрузочных записей из следующих источников:

  • Загрузочные записи, определённые с помощью файлов описания спецификации загрузчика, расположенных в /loader/entries/ на ESP и в разделе расширенного загрузчика. Обычно они описывают образы ядра Linux со связанными образами initrd, но также могут описывать произвольные другие исполняемые файлы EFI.
  • Унифицированные образы ядра в соответствии со спецификацией загрузчика в виде исполняемых двоичных файлов EFI в /EFI/Linux/ на ESP и в разделе расширенного загрузчика.
  • Диспетчер загрузки Microsoft Windows EFI, если он установлен.
  • Диспетчер загрузки Apple MacOS X, если он установлен.
  • Бинарный файл EFI Shell, если он установлен
  • Перезагрузка в опцию настройки прошивки UEFI, если она поддерживается.

systemd-boot поддерживает следующие функции:

3. Инициализация ядра

Ядро — это основа любой системы Linux. Он связывает оборудование ПК с базовыми процессами. Ядро контролирует все процессы в вашей системе Linux. После того как выбранное ядро Linux загружено загрузчиком, оно должно самораспаковаться из сжатой версии перед выполнением любой задачи. После самораспаковывания выбранное ядро монтирует корневую файловую систему и инициализирует программу /sbin/init, обычно называемую init.


Init всегда запускается первой программой, и ей назначается ID процесса или PID 1. Это процесс init, который порождает различных демонов и монтирует все разделы файловых систем, указанные в файле /etc/fstab.

Затем ядро монтирует начальный RAM-диск (initrd), который является временной корневой файловой системой, пока не будет смонтирована настоящая корневая файловая система. Все ядра находятся в каталоге /boot вместе с начальным образом RAM-диска.

4. запуск Systemd

Наконец, ядро загружает Systemd, заменяющий старый SysV init. Systemd является матерью всех процессов Linux и управляет, среди прочего, монтированием файловых систем, запуском и остановкой служб, и это лишь некоторые из её функций.

Systemd использует файл /usr/lib/systemd/system/default.target для определения состояния или цели, в которую должна загружаться система Linux.

  • Для настольной рабочей станции (с графическим интерфейсом пользователя) целевое значение по умолчанию graphical.target.
  • Для сервера целью по умолчанию является multi-user.target.

Вот виды целей systemd:

  • poweroff.target: выключение системы.
  • rescue.target: запускает сеанс спасательной оболочки.
  • multi-user.target: настраивает систему на неграфическую (консольную) многопользовательскую систему.
  • graphical.target: настройка системы на использование графического многопользовательского интерфейса с сетевыми службами.
  • reboot.target: перезагружает систему.

Чтобы проверить текущую цель в вашей системе, выполните команду:


Вы можете переключаться с одной цели на другую, выполнив на терминале следующую команду:

Эта команда переводит систему в неграфическое состояние (после перезагрузки).

А эта команда возвращает в загрузку в графический интерфейс:

Процесс загрузки завершается, когда systemd загружает все демоны и устанавливает значение целевого уровня или уровня выполнения. Именно в этот момент вам будет предложено ввести имя пользователя и пароль, после чего вы получите доступ к своей системе Linux.

Читайте также: