2.4.2 Запуск ядра

Предыдущая  Содержание  Следующая V*D*V

Запуск ядра можно разделить на следующие этапы.

 

Инициализация, зависящая от CPU/платформы

 

Если вы переносите Linux на вашу платформу, этот раздел очень важен, поскольку он знаменует важный этап в переносе BSP. Платформо-зависимая инициализация состоит из следующих шагов.

 

1.Настройка окружения для первой процедуры на Си: точка входа ядра является процедурой на языке ассемблера; название этой точки входа варьируется (stext для ARM, kernel_entry для MIPS и так далее). Чтобы узнать точку входа для вашей платформы, посмотрите скрипт компоновщика. Эта функция обычно находится в файле arch/<name>/kernel/head.S. Эта функция делает следующее:

a.На машинах, которые не имеют включённого MMU, она включает MMU. Большинство загрузчиков не работают с MMU, так что виртуальный адрес эквивалентен физическому адресу. Тем не менее, ядро собрано с виртуальным адресом. Функция должна включить MMU, чтобы ядро могло начать использовать виртуальный адрес в обычном режиме. Это не является обязательным на таких платформах, как MIPS, где MMU включён сразу при включении питания.

b.Выполняет инициализацию кэша. Это снова зависит от платформы.

c.Настраивает BSS путём его обнуления (как правило, нельзя полагаться, что это сделает загрузчик).

d.Настраивает стек, чтобы могла быть вызвана первая процедура на языке Си. Такой первой процедурой на языке Си является функция start_kernel() в init/main.c. Это большая функция, которая делает много всего, пока не заканчивается фоновой задачей (первой задачей в системе, имеющей идентификатор процесса 0). Эта функция вызывает функции остальной инициализации платформы, которые будут рассмотрены ниже.

2.Функция setup_arch(): эта функция выполняет зависимую от платформы и процессора инициализацию, так что оставшаяся инициализация может быть вызвана безопасно. Приведём только общую функциональность, так как она сильно зависит от платформы:

a.Распознавание процессора. Поскольку архитектура процессора может иметь различные особенности, эта функция распознаёт процессор (например, если выбран процессор ARM, она распознаёт особенности ARM) используя оборудование или информацию, которая может быть передана во время сборки. Опять же в этом коде могут быть сделаны какие-либо зависящие от процессора исправления.

b.Распознавание платы. Снова, поскольку ядро поддерживает множество плат, эта опция распознаёт плату и выполняет зависимые от платы исправления.

c.Анализ параметров командной строки, переданной ядру.

d.Определение электронного диска, если он был создан загрузчиком, таким образом, чтобы ядро позже могло смонтировать его в качестве корневой файловой системы. Обычно загрузчик передаёт начало области электронного диска в памяти и размер.

e.Вызов функций bootmem. Bootmem является неправильным наименованием; оно относится к начальной памяти, которую ядро может зарезервировать для различных целей, прежде чем код подкачки захватывает всю память. Например, вызовом распределителя bootmem вы можете зарезервировать большой кусок непрерывной памяти, который может быть использован вашим устройством для DMA.

f.Вызов функции инициализации подкачки, которая забирает оставшуюся память для создания страниц для системы.

3.Инициализация исключений - функция trap_init(): эта функция настраивает зависимые от ядра обработчики исключений. До этого момента, если происходит исключительная ситуация, результат зависит от платформы. (Например, на некоторых платформах вызываются обработчики исключений, зависимые от загрузчика.)

4.Инициализация процедуры обработки прерываний - функция init_IRQ(): эта функция инициализирует контроллер прерываний и дескрипторы прерываний (это структуры данных, которые используются BSP для маршрутизации прерываний; об этом в следующей главе). Обратите внимание, что на данный момент прерывания не разрешены, за их включение отвечают отдельные драйверы, владеющие линиями прерываний, что происходит во время их инициализации, которая вызывается позже. (Например, инициализация таймера убедилась бы, что линия прерывания таймера включена.)

5.Инициализация таймеров - функция time_init(): эта функция инициализирует таймерное оборудование, так что система начинает генерировать периодические временные сигналы (тики), которые являются сердцебиением системы.

6.Инициализация консоли - функция console_init(): эта функция выполняет инициализацию последовательного устройства в качестве консоли. Как только консоль включена, на экране появляются все сообщения о ходе запуска. Для печати сообщения из ядра должна быть использована функция printk(). (printk() является очень мощной функцией, она может быть вызвана из любого места, даже из обработчиков прерываний.)

7.Расчёт циклов задержки для данной платформы - функция calibrate_delay(): эта функция используется для реализации в ядре микрозадержек с помощью функции udelay(). Функция udelay() прокручивает несколько циклов за число микросекунд, указанных в качестве аргумента. Чтобы udelay работала как надо, ядру необходимо знать число тактов в микросекунду. Это как раз и делается с помощью этой функции; она калибрует количество циклов задержки. Это гарантирует, что циклы задержки работают одинаково на всех платформах. Обратите внимание, что выполнение этого зависит от прерывания таймера.

 

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

 

Она включает:

 

Инициализацию планировщика

Инициализацию контроллера памяти

Инициализацию VFS

 

Обратите внимание, что большая часть инициализации подсистем выполняется в функции start_kernel(). В конце этой функции ядро создаёт новый процесс, процесс инициализации, init, чтобы выполнить остальную инициализацию (инициализацию драйверов, initcalls, монтирование корневой файловой системы и переход в пространство пользователя) и текущий процесс становится фоновым процессом с идентификатором процесса, равным 0.

 

Инициализация драйверов

 

Инициализация драйверов выполняется после того, как запущен управляющий процесс и управление памятью. Это может быть сделано в контексте процесса init.

 

Монтирование корневой файловой системы

 

Напомним, что корневая файловая система является основной используемой файловой системой с помощью которой могут быть смонтированы другие файловые системы. Монтирование её знаменует собой важный процесс в стадии загрузки, так как ядро может начать свой переход в пользовательское пространство. Блочное устройство, содержащее корневую файловую систему, может быть жёстко закодировано в ядре (при сборке ядра), или оно может быть передано в качестве аргумента командной строки из загрузчика с помощью параметра загрузчика "root=".

На встроенных системах обычно используются три вида корневых файловых систем:

 

Начальный электронный диск

Сетевая файловая система, использующая NFS

Файловая система на основе флеш-памяти

 

Обратите внимание, что корневая файловая система на основе NFS используется в основном для целей отладки; две другие используются для сборок для производства. Электронный диск имитирует блочное устройство с помощью системной памяти, поэтому он может быть использован для монтирования файловых систем при условии, что образ файловой системы копируется на него. Электронный диск может использоваться в качестве корневой файловой системы; это использование электронного диска известно как initrd (краткая форма initial ram disk). Initrd - это очень мощная концепция и имеет широкое использование, особенно на начальной стадии разработки встраиваемого Linux, когда у вас нет готового драйвера флеш-памяти, но ваши приложения готовы для тестирования (часто это тот случай, когда есть отдельные команды, разрабатывающие драйверы и приложения, и работающие параллельно). Так как же обойтись без корневой файловой системы во флеш-памяти? Можно использовать сетевую файловую систему при условии, что ваш сетевой драйвер готов; если нет, то лучшим вариантом является initrd. Создание начального электронного диска более подробно объясняется в Главе 8. Этот же раздел объясняет, как ядро монтирует initrd в качестве корневой файловой системы. Если вы хотите, чтобы ядро загрузило initrd, необходимо настроить ядро в процессе сборки с помощью параметра CONFIG_BVLK_DEV_INITRD. Как объяснялось ранее, образ initrd загружается вместе с образом ядра и ядру должен быть передан начальный и конечный адрес initrd с помощью аргументов командной строки. Как только они стали известны, ядро смонтирует корневую файловую систему, загруженную на initrd. Обычно используемыми файловыми системами являются romfs и ext2.

У initrd есть много больше возможностей. Initrd является корневой файловой системой используй-и-выброси. Она может быть использована для монтирования другой корневой файловой системы. Почему это необходимо? Предположим, что корневая файловая система монтируется на устройство хранения, драйвер которого является модулем ядра. Так что он должен присутствовать в файловой системе. Это представляет собой проблему курицы и яйца; модуль должен быть в файловой системе, которая, в свою очередь, требует, чтобы сначала был загружен модуль. Чтобы обойти это, может быт использована initrd. Драйвер может быть выполнен в виде модуля в initrd; раз initrd смонтирована, то модуль драйвера может быть загружен и, следовательно, устройство хранения может быть доступно. Затем файловая система на этом устройстве хранения может быть смонтирована в качестве фактической корневой файловой системы и, наконец, initrd может быть отброшен. Ядро Linux предоставляет возможность для такого её использования и отбрасывания; оно ищет файл linuxrc в корне initrd и выполняет его. Если этот двоичный файл возвращается, то ядро предполагает, что в initrd больше необходимости нет, и оно переключается на фактическую корневую файловую систему (файл linuxrc может быть использован для загрузки модулей драйверов). NFS и файловые системы на базе флеш-памяти более подробно описаны в Главе 4.

Если корневая файловая система не смонтирована, то ядро остановит выполнение и после выдачи сообщения в консоль перейдёт в режим паники:

 

Unable to mount root fs on device

 

Выполнение Initcall и освобождение памяти, используемой при инициализации

 

Если вы откроете скрипт компоновщика для любой архитектуры, он будет иметь раздел init. Начало этого раздела помечено  с помощью __init_begin, а конец отмечен с помощью __init_end. Идея этого раздела в том, что он содержит текст и данные, которые могут быть отброшены после того, как они используются один раз во время запуска системы. Функции инициализации драйверов - это пример функции используй-и-выброси. Как только драйвер, статически скомпонованный с ядром, выполнит свою регистрацию и инициализацию, эта функция не будет вызываться снова и, следовательно, её можно отбросить. Идея состоит в том, чтобы положить все эти функции вместе, чтобы вся память, занимаемая всеми такими функциями, могла быть освобождена как большой кусок и, следовательно, будет доступна для диспетчера памяти в качестве свободных страниц. Учитывая, что память на встраиваемых системах является дефицитным ресурсом, читателю рекомендуется эффективно использовать эту концепцию. Функция или переменная используй-и-выброси объявляется с помощью директивы __init. После того, как будет сделана вся инициализация драйверов и подсистем, код запуск всю эту память освобождает. Это делается прямо перед переходом в пространство пользователя.

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

 

Переход в пользовательское пространство

 

Ядро, которое выполняется в контексте процесса инициализации, переходит в пространство пользователя, перекрывая себя (при помощи execve) исполняемым образом специальной программы, также называемом как init. Этот исполняемый файл обычно находится в корневой файловой системе в каталоге /sbin. Обратите внимание, что пользователь может указать  программу инициализации, используя аргументы командной строки ядра. Однако, если ядро не может загрузить указанную пользователем программу инициализации или заданную по умолчанию, после выдачи сообщения, оно входит в состояние паники:

 

No init found. Try passing init= option to the kernel.

 

Предыдущая  Содержание  Следующая