Ядро Linux в комментариях

       

Start_kernel


Дескриптор __init заставляет компилятор gcc помещать эту функцию в специальный раздел ядра. После завершения собственной инициализации ядро может попытаться освободить этот специальный раздел. Подобного рода разделов существует два: .text.init и .data.init. Первый из них предназначен для кода, а второй — для данных. (Термин «text» относится к таким «чистым» разделам исполняемого модуля, как код и строковые литералы, которые могут совместно использоваться множеством процессов.) Кроме того, можно заметить и дескриптор __initfunc, который, как и __init, отмечает код инициализации, и __initdata, отмечающий данные инициализации.

Как упоминалось ранее, даже в мультипроцессорной системе загрузку выполняет один ЦП. В терминологии Intel его называют процессором начальной загрузки (bootstrap processor, или BSP), на который во многих местах кода ядра ссылаются как на ВР. Следовательно, BSP достигает этой точки кода первым, пропускает следующий if и обнуляет флаг boot_cpu, так что когда другие ЦП попадут в эту точку, они зайдут внутрь if. Пока другие ЦП активизируются и добираются до этой точки, BSP находится в цикле ожидания (описанном ниже в главе), а initialize_secondary (строка ) следит за тем, когда другие ЦП присоединятся к BSP. Таким образом, другие ЦП не выполняют остаток start_kernel, что само по себе приятно, поскольку позволяет избежать повторной инициализации большого числа аппаратных средств и т.п.

Подобного рода странные телодвижения необходимы только для процессоров х86: для других платформ достаточно вызова smp_init, который выполнит остаток инициализации для систем SMP. Соответственно, другие платформы работают с пустыми определениями initialize_secondary.

Выводит сообщение заголовка ядра (строка ), которое предоставляет различную информацию о том, как компилировалось ядро — на каком компьютере, когда, версия используемого компилятора и т.п. Если что-то сбоит, информация о происхождении ядра может сослужить хорошую службу в плане поиска причин сбоев.


Инициализация некоторых компонентов ядра: памяти, аппаратных прерываний, планировщика и т.д. В частности, функция setup_arch (строка ) выполняет аппаратно-зависимую установку, после чего возвращает значения в command_line (параметры, передаваемые ядру), а также memory_start и memory_end (диапазон физических адресов, доступных ядру). Некоторые из следующих функций резервируют память небольшого объема: они получают memory_start и memory_end и после получения требуемого возвращают новое значение memory_start.

Выполняет разбор параметров, передаваемых ядру. Функция parse_options (строка ), которая обсуждается ниже, также устанавливает начальные значения argv и envp.

Ядро может профилировать себя во время выполнения, периодически выясняя, какие операторы выполняются, и обновляя полученными результатами таблицу. Это завершается обращением к x86_do_profile (строка ) во время прерывания по таймеру, как описано в . Таблица разделяет ядро на области одинаковых размеров (см. рис. 4.1) и просто отслеживает, сколько раз выполнялся оператор во время прерывания в данной области. Такое профилирование является, как и было задумано, грубым (отслеживание производится не по функциям или строкам кода, а по приближенным адресам), тем не менее, оно отличается низкими накладными расходами, высокой скоростью, небольшими размерами, и определенно помогает идентифицировать узкие места.



Рис. 4.1. Буфер профилирования

Кроме того, путем изменения значения prof_shift (строка ) можно настраивать количество адресов для каждого элемента таблицы (и, соответственно, точность «попадания»). profile_setup (строка ) позволяет устанавливать значение prof_shift во время загрузки, что гораздо предпочтительнее перекомпиляции всего ядра лишь только для изменения этого значения.

Данный блок if устанавливает отдельную область памяти для таблицы профилирования и обнуляет все ее элементы. Следует отметить, что если prof_shift равно 0 (значение по умолчанию), профилирование не выполняется и память под таблицу не выделяется.



Разрешение аппаратных прерываний путем вызова sti (строка для однопроцессорных компьютеров; более подробно эта тема рассматривается в ). Необходимо, чтобы прерывания по таймеру были активны, поэтому следующее обращение к calibrate_delay (строка ) вычислит BogoMIPS для данного компьютера (см. ). Поскольку значение BogoMIPS требуется для некоторых драйверов устройств, ядро должно определить эту величину перед инициализацией большинства оборудования, файловой системы и т.д.

Тестирует ЦП на предмет наличия определенных ошибок, например, ошибка Pentium F00F (см. ), и сохраняет информацию о найденных ошибках для остальных частей ядра, обеспечивая им возможность обходить ошибки. (Ради экономии бумаги код функции check_bugs не приводится.)

Вызов функции smp_init (строка ), которая, в свою очередь, обращается к другим функциям, активизирующим дополнительные ЦП в мультипроцессорной системе: для случая платформы х86 функция smp_boot_cpus (строка ) инициализирует определенные структуры данных ядра, которые отслеживают дополнительные ЦП и переводят их в режим ожидания; дальнейшее обращение к smp_commence (строка ) разрешает функционирование этих ЦП.

Инициирует выполнение функции init как поток ядра. Дополнительную информацию по этому поводу можно найти далее в главе.

Взводит флаг need_resched ожидающего процесса по причинам, на данный момент пока не вполне очевидным. Все станет ясно по прочтении глав , и . До возникновения следующего прерывания по таймеру (см. ) функция system_call (строка , обсуждаемая в ) убеждается, что флаг need_resched ожидающего процесса взведен и вызывает schedule (строка , обсуждаемая в ) для того, чтобы переключить ЦП на другой процесс.

Завершив инициализацию ядра (во всяком случае, передав ответственность за это функции init), все что осталось — это войти в цикл ожидания и отбирать у ЦП неиспользуемые циклы. Поэтому в данной строке производится обращение к cpu_idle (строка ), представляющей собой цикл ожидания. Из кода cpu_idle видно, что возврата из функции никогда не происходит. Однако, любая реальная задача эту функцию вытесняет.

Несложно заметить, что cpu_idle периодически выполняет системный вызов idle (системные вызовы рассматриваются в ), который проходит через sys_idle (строка ) для организации реального цикла ожидания — это видно из строки для однопроцессорной и строки для симметричной мультипроцессорной версии. Здесь производится вызов оператора hlt (останов), переводящего ЦП в «спящее» состояние, характеризующееся снижением энергопотребления. Такой режим означает, что ЦП не выполняет никакой реальной работы.


Содержание раздела