Внутреннее устройство ядра Linux 2.4

       

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


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

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

  • Выполняется глобальная блокировка (необходимая для того, чтобы через процесс инициализации проходил только один CPU)
  • Выполняются платформо-зависимые настройки (анализируется раскладка памяти, копируется командная строка и пр.).
  • Вывод "баннера" ядра, который содержит версию, компилятор, использованные при сборке, и пр., в кольцевой буфер для сообщений. Текст "баннера" задается в переменной linux_banner, определенной в init/version.c. Текст этот можно вывести на экран командой cat /proc/version.
  • Инициализация ловушек.


  • Инициализация аппаратных прерываний (irqs).
  • Инициализация данных для планировщика.
  • Инициализация данных хранения времени.
  • Инициализация подсистемы программных прерываний (softirq).
  • Разбор параметров командной строки.
  • Инициализация консоли.
  • Если ядро было скомпилировано с поддержкой загружаемых модулей, инициализируется подсистема динамической загрузки модулей.
  • Инициализируются профилирующие буферы, если командная строка содержит указание "profile=".

  • kmem_cache_init(), начало инициализации менеджера памяти.


  • Разрешаются прерывания.


  • Подсчет значения BogoMips для данного CPU.


  • Вызывается mem_init() которая подсчитывает max_mapnr, totalram_pages и high_memory и выводит строку "Memory: ...".


  • kmem_cache_sizes_init(), завершение инициализации менеджера памяти.


  • Инициализация структур данных для procfs.


  • fork_init(), создает uid_cache, инициализируется max_threads исходя из объема доступной памяти и конфигурируется RLIMIT_NPROC для init_task как max_threads/2.


  • Создаются различные кэши для VFS, VM, кэш буфера и пр..


  • Инициализируется подсистема IPC, если имеется поддержка System V IPC. Обратите внимание, что для System V shm, это включает монтирование внутреннего (in-kernel) экземпляра файловой системы shmfs.


  • Создается и инициализируется специальный кэш, если поддержка квот (quota) включена.


  • Выполняется платформо-зависимая "проверка ошибок" ("check for bugs") и, если это возможно, активируется обработка ошибок процессора/шины/проч. Сравнение различных архитектур показывает, что "ia64 не имеет ошибок" а "ia32 имеет несколько дефектов", хороший пример - "дефект f00f" который проверен только для ядра, собранного под процессор ниже, чем 686.


  • Устанавливается флаг, указывающий на то, что планировщик должен быть вызван "при первой возможности" и создается поток ядра init(), который выполняет execute_command, если она имеется среди параметров командной строки в виде "init=", или пытается запустить /sbin/init, /etc/init, /bin/init, /bin/sh в указанном порядке; если не удается ни один из запусков то ядро "впадает в панику" с "предложением" задать параметр "init=".


  • Переход в фоновый поток с pid=0.


  • Здесь важно обратить внимание на то, что задача init() вызывает функцию do_basic_setup(), которая в свою очередь вызывает do_initcalls() для поочередного (в цикле) вызова функций, зарегистрированных макросом __initcall или module_init() Эти функции либо являются независимыми друг от друга, либо их взаимозависимость должна быть учтена при задании порядка связывания в Makefile - ах. Это означает, что порядок вызова функций инициализации зависит от положения каталогов в дереве и структуры Makefile - ов. Иногда порядок вызова функций инициализации очень важен. Представим себе две подсистемы: А и Б, причем Б существенным образом зависит от того как была проинициализирована подсистема А. Если А скомпилирована как статическая часть ядра, а Б как подгружаемый модуль, то вызов функции инициализации подсистемы Б будет гарантированно произведен после инициализации подсистемы А. Если А - модуль, то и Б так же должна быть модулем, тогда проблем не будет. Но что произойдет, если и А, и Б скомпилировать с ядром статически? Порядок, в котором они будут вызываться (иницализироваться) зависит от смещения относительно точки .initcall.init ELF секции в образе ядра (грубо говоря - от порядка вызова макроса __initcall или module_init() прим. перев.). Rogier Wolff предложил ввести понятие "приоритетной" инфраструктуры, посредством которой модули могли бы задавать компоновщику порядок связывания, но пока отсутствуют заплаты, которые реализовали бы это качество достаточно изящным способом, чтобы быть включенным в ядро. А посему необходимо следить за порядком компоновки. Если А и Б (см. пример выше) скомпилированы статически и работают корректно, то и при каждой последующей пересборке ядра они будут работать, если порядок следования их в Makefile не изменяется. Если же они не функционируют, то стоит изменить порядок следования объектных файлов.

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

    вручную, запуская их по одному за раз


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