Инициализация ядра Linux версии 2.4.х для IA-32
Randy Dunlap, <rddunlap@ieee.org.>
v1.0, 2001-05-17
Этот документ содержит описание процесса инициализации ядра Linux
версии 2.4.х для 32-битных процессоров Intel (IA-32)
Содержание
1. Введение
1.1. Обзор
1.2. Об этом документе
1.3. Исправления и дополнения
1.4. Торговые марки
1.5. Лицензия
2. Инициализация Linux (в "ASCII-картинках")
3. Начальная настройка Linux
3.1. IA-32 настройка ядра
3.1.1. start_of_setup:
3.1.1.1 Чтение DASD-типа второго жесткого диска
3.1.1.2 Проверка того, что LILO загрузил нас правильно
3.1.1.3 Проверка на старый загрузчик попыткой загрузить большое ядро
3.1.1.4 Выяснение объема памяти системы
3.1.1.5 Режимы видеоадаптера
3.1.1.6 Получаем параметры жесткого диска
3.1.1.7 Получаем информацию о шине Micro Channel
3.1.1.8 Проверяем мышь
3.1.1.9 Ищем поддержку APM в BIOS
3.1.1.10 Готовимся к переключению в защищенный режим
3.1.1.11 Задействуем адресную линию A20
3.1.1.12 Убеждаемся, что любой возможный сопроцессор корректно сброшен
3.1.1.13 Маскируем все прерывания
3.1.1.14 Переключаемся в защищенный режим
3.1.1.15 Вызываем startup_32
3.2. Запуск видео
3.2.1. video:
3.2.1.1 basic_detect:
3.2.1.2 mode_params:
3.2.1.3 mopar_gr:
3.2.1.4 mode_menu:
3.2.1.5 mode_set:
3.2.1.6 store_screen:
3.2.1.7 restore_screen:
3.2.1.8 mode_table:
3.2.1.9 mode_scan:
3.2.1.10 svga_modes:
4. Инициализация архитектурно-зависимых частей Linux
4.1. startup_32:
4.2. Устанавливаем сегментные регистры в известные значения
4.3. Проверка BSP (Bootstrap Processor) в случае SMP (Symmetric MultiProcessing)
4.4. Инициализация страничных таблиц
4.5. Включение управления страницами
4.6. Очистка BSS
4.7. Настройка 32-битного режима
4.8. Сохранение параметров загрузки и командной строки
4.9. checkCPUtype
4.10. Учет этого процессора (п.4.9)
4.11. Загрузка дескриптора таблицы указателя регистров
4.12. Запуск других процессоров
5. Архитектурно-независимая инициализация Linux
5.1. start_kernel:
5.1.1. Еще архитектурно-зависимой инициализации
5.1.2. Продолжение архитектурно-независимого старта
5.1.3. Разбор параметров коммандной строки
5.1.4. trap_init
5.1.5. init_IRQ
5.1.6. sched_init
5.1.7. time_init
5.1.8. softirq_init
5.1.9. console_init
5.1.10. init_modules
5.1.11. Установка профилирования
5.1.12. kmem_cache_init
5.1.13. sti
5.1.14. alibrate_delay
5.1.15. Инициализация INITRD (INITial Ram-Disk)
5.1.16. mem_init
5.1.17. kmem_cache_sizes_init
5.1.18. proc_root_init
5.1.19. mempages = num_physpages;
5.1.20. fork_init(mempages)
5.1.21. proc_caches_init()
5.1.22. vfs_caches_init(mempages)
5.1.23. buffer_init(mempages)
5.1.24. page_cache_init(mempages)
5.1.25. kiobuf_setup()
5.1.26. signals_init()
5.1.27. bdev_init()
5.1.28. inode_init(mempages)
5.1.29. ipc_init()
5.1.30. dquot_init_hash()
5.1.31. check_bugs()
5.1.32. В случае SMP запускаем другие процессоры
5.1.33. Запуск init потоком
5.1.34. unlock_kernel()
5.1.35. current->need_resched = 1;
5.1.36. cpu_idle()
5.2. setup_arch
5.2.1. Копирование и преобразование системных параметров
5.2.2. Для конфигураций с включенным RAMdisk (CONFIG_BLK_DEV_RAM)
5.2.3. setup_memory_region
5.2.4. Выставление границ памяти
5.2.5. parse_mem_cmdline
5.2.6. Установка страничных кадров
5.2.7. Обработка конфигураций SMP и IO APIC
5.2.8. paging_init()
5.2.9. Сохранение SMP-конфигурации времени загрузки
5.2.10. Резервирование памяти для INITRD
5.2.11. Поиск и работа с ROM
5.2.12. Резервирование системных ресурсов
5.3. Поток init
5.4. do_basic_setup {часть потока init}
5.4.1 Собираем сирот
5.4.2 MTRRs
5.4.3 SYSCTLs
5.4.4 Инициализируем много устройств
5.4.5 PCI
5.4.6 Micro Channel
5.4.7 ISA PnP
5.4.8 Поднимаем сеть
5.4.9 Начальный RamDisk
5.4.10 Запускаем контекстный поток ядра (keventd)
5.4.11 Initcalls
5.4.12 Файловые системы (ФС)
5.4.13 IRDA
5.4.14 PCMCIA
5.4.15 Монтируем корневую ФС
5.4.16 Монтируем ФС устройств (devfs)
5.4.17 Переключаемся к начальному RamDisk'у
6. Глоссарий
7. Ссылки
1. Введение
Части этого текста взяты из комментариев к исходному коду ядра
(очевидно). Плюс к этому я добавил много комментариев в разных местах.
Я надеюсь, что это все окажется полезным для "ядерных" программистов -
как начинающих, так и опытных, нуждающихся в такого рода информации.
Если, тем не менее, информации в этом документе вам не хватит - "use
the source" (обратитесь к исходному коду).
1.1. Обзор
Этот пункт является поверхностным обзором, перечисляющим секции,
которые подробнее описаны ниже.
Документ состоит из 3х секций. Первая секция описывает начальную
инициализацию ядра на IA-32 (но только после того, как ваш загрузчик
выполнит действия, связанные с выбором вами пункта меню загрузки и
после работы других загрузчиков, если они есть - т.о. документ не
касается процесса загрузки самого ядра). Эта секция базируется на коде
файлов "linux/arch/i386/boot/setup.S" и "linux/arch/i386/boot/video.S".
Вторая секция описывает ту инициализацию Linux, которая является
x86-(или i386-, или IA-32-) зависимой. Эта секция основывается на
файлах "linux/arch/i386/kernel/setup.c" и "linux/arch/i386/kernel/head.S".
Последняя, третья, секция касается архитектурно-независимой части
процесса запуска ядра. Здесь основой является последовательность
выполнения из файла "linux/init/main.c".
Загляните в секцию "Ссылки", если вам нужны другие документы по этой
теме.
1.2. Об этом документе
Этот документ описывает инициализацию ядра Linux на процессорах IA-32
(i386 или x86) после того, как отработает загрузчик ядра (или
нескольно, если они присутствуют).
Вы можете отформатировать этот документ следующими командами (для
примера):
% sgml2txt ia32_init_240.sgml
или
% sgml2html ia32_init_240.sgml
Тем самым на выходе вы получите соответственно ASCII и HTML файлы.
Также имеется возможность получать на выходе LaTeX, GNU и RTF файлы
при помощи соответствующего sgmltool (man sgmltools).
1.3. Исправления и дополнения
Они приветствуются, высылайте их мне - rddunlap@ieee.org. Авторы
всех использованных исправлений и дополнений будут обязательно
упомянуты.
1.4. Торговые марки
Все торговые марки являются собственностью их владельцев.
1.5. Лицензия
Copyright (C) 2001 Randy Dunlap.
Этот документ может распространяться только на условиях лицензии LDP
(Linux Documentation Project), размещенной по адресу
"http://www.linuxdoc.org/COPYRIGHT.html".
2. Ицициализация Linux (в "ASCII-картинках")
Иллюстрированно (грубо говоря :) инициализация Linux выглядит примерно
следующим образом, где "[...]" обозначает опциональное (зависимое от
конфигурации ядра) и "{...}" является комментарием.
+----------------------------------------+
| arch/i386/boot/setup.S:: + |
| arch/i386/boot/video.S:: |
|----------------------------------------|
| start_of_setup: |
| проверяем, что загрузились хорошо |
| получаем размер системной памяти |
| получаем видеорежим(ы) |
| получаем параметры жесткого диска |
| получаем инфу о шине MC |
| получаем инфу о мыши |
| получаем инфу о поддержке APM в BIOS |
| поднимаем линию A20 |
| сбрасываем сопроцессор |
| маскируем все прерывания |
| переключаемся в защищенный режим |
| вызываем startup_32 |
+----------------------------------------+
|
v
+----------------------------------------------+
| arch/i386/kernel/head.S:: |
|----------------------------------------------|
| startup_32: |
| выставляем сегментные регистры |
| в известные значения |
| инициализируем основные страничные таблицы |
| выставляем указатель стека |
| очищаем "ядерный" BSS |
| выставляем IDT |
| checkCPUtype |
| загружаем GDT, IDT, и LDT |
| указатель регистров |
| start_kernel |
| {безвозвратная функция} |
+----------------------------------------------+
|
v
+-------------------------------+ +---------------------------------------+
| init/main.c:: | +->| arch/i386/kernel/setup.c:: |
|-------------------------------| | |---------------------------------------|
| start_kernel(): | | | setup_arch(): |
| lock_kernel | | | копируем параметры загрузки |
| setup_arch |--+ | инициализируем ramdisk |
| parse_options |<-+ | setup_memory_region |
| trap_init | | | parse_cmd_line |
| cpu_init | | | используем карту памяти BIOS для |
| init_IRQ | | | установки инфы страничного кадра |
| sched_init | | | резервируем физическую страницу 0 |
| init_timervecs | | | [find_smp_config] |
| time_init | | | paging_init |
| softirq_init | | | [get_smp_config] |
| console_init | | | [init_apic_mappings] |
| [init_modules] | | | [резервируем память под INITRD] |
| [настраиваем профилирование] | | | probe_roms для поиска |
| kmem_cache_init | | | возможных ROMs |
| sti | | | request_resource для резервирования |
| calibrate_delay | | | памяти под видео RAM |
| [настраиваем INITRD] | | | request_resource для резервирования |
| mem_init | | | всех стандартных ресурсов |
| free_all_bootmem | +--| I/O системной платы ПК |
| kmem_cache_sizes_init | +---------------------------------------+
| [proc_root_init] |
| fork_init |
| proc_caches_init |
| vfs_caches_init |
| buffer_init |
| page_cache_init |
| kiobuf_setup |
| signals_init | +-----------------------------------------+
| bdev_init | | init/main.c:: |
| inode_init | | init(): {...поток init'а...} |
| [ipc_init] | | do_basic_setup |
| [dquot_init_hash] | | {иниц-я шин/устр-в и нач-ые вызовы} |
| check_bugs | | free_initmem |
| [smp_init] {*below} | | открываем /dev/console |
| пускаем поток init'а {--->} |.....| выполняем init скрипт или шелл |
| unlock_kernel | | или паникуем |
| cpu_idle | +-----------------------------------------+
+-------------------------------+
+---------------------------------------+
| smpboot.c::smp_init |
|---------------------------------------|
| arch/i386/kernel/smpboot.c:: |
| smp_boot_cpus(): |
| [mtrr_init_boot_cpu] |
| smp_store_cpu_info |
| print_cpu_info |
| сохраняем соотв-ия CPU ID/APIC и ID |
| verify_local_APIC |
| connect_bsp_APIC |
| setup_local_APIC |
| для каждого корректного APIC ID |
| do_boot_cpu(apicid) |
| setup_IO_APIC |
| setup_APIC_clocks |
| synchronize_tsc_bp |
+---------------------------------------+
3. Начальная настройка Linux
(из linux/arch/i386/boot/setup.S и linux/arch/i386/boot/video.S)
Замечание: Регистры записываются как '%регистр', константы
записываются как номера с или без '$' впереди.
3.1. IA-32 настройка ядра
"setup.S" отвечает за получение системной информации от BIOS и
размещение ее в подходящих местах системной памяти.
И "setup.S", и ядро загружаются в память загрузочным сектором диска
(размещенным там загрузчиком).
"setup.S" собран как 16-битная программа реального режима. Она
переключает процессор в 32-битный защищенный режим и запускает
32-битный код ядра.
Этот код спрашивает у BIOS параметры памяти/диска/какие-то-еще и
помещает их в "безопасное" место: 0x90000-0x901FF, то есть туда, где
они могут быть использованы загрузочным сектором. Затем код
переключается в систему защищенного режима для чтения этих параметров
оттуда до того, как они будут перезаписаны блоками буферного кэша.
Код "setup.S" начинается с инструкции jmp около "установочного
заголовка" ("setup header"), который должен начинаться с адреса %cs:2.
Установочный заголовок выглядит следующим образом:
.ascii "HdrS" # сигнатура заголовка
.word 0x0202 # номер версии заголовка
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG
.word kernel_version # указатель на строку версии ядра
type_of_loader: .byte 0
loadflags:
LOADED_HIGH = 1 # если установлено, то ядро грузится в адреса повыше
#ifndef __BIG_KERNEL__
.byte 0
#else
.byte LOADED_HIGH
#endif
setup_move_size: .word 0x8000 # размер для перемещения если установка
# не загрузилась по адресу 0x90000.
code32_start: # сюда загрузчики могут положить другой
# стартовый адрес для 32-битного кода.
#ifndef __BIG_KERNEL__
.long 0x1000 # по умолчанию для zImage
#else
.long 0x100000# по умолчанию для большого ядра
#endif
ramdisk_image: .long 0 # адрес загруженного образа ramdisk'а
ramdisk_size: .long 0 # его размер в байтах
bootsect_kludge: .word bootsect_helper, SETUPSEG
heap_end_ptr: .word modelist+1024 # (версия заголовка 0x0201 или поздняя)
# место отсюда (исключительно) и до конца кода
# установки может быть использовано этим кодом
# для работы с локальной кучей.
pad1: .word 0
cmd_line_ptr: .long 0 # (версия заголовка 0x0202 или поздняя)
# если != 0, то это 32-битный указатель
# на командную строку ядра.
trampoline: call start_of_setup # из start_of_setup нет возврата
.space 1024
# Окончание установочного заголовка #####################################
3.1.1. start_of_setup:
----------------------
3.1.1.1. Чтение DASD-типа второго жесткого диска
Читаем тип DASD второго жесткого жесткого диска (BIOS int. 0x13,
%ax=0x1500, %dl=0x81).
# Bootlin требует, чтобы это было сделано заранее [TBD:почему?]
3.1.1.2. Проверяем того, что LILO загрузил нас правильно
Проверяем сигнатуру в конце кода запуска. Сигнатура используется для
проверки того, что LILO загрузил нас правильно. Если она не найдена,
то копируем секторы с запускающим кодом и ищем ее снова. Если опять
безуспешно, то паникуем ("No setup signature found ...")
3.1.1.3. Проверка на старый загрузчик попыткой загрузить большое ядро
Если образ ядра слишком большой (и поэтому загружен в верхнюю память)
и при этом загрузчик не может работать с образами, расположенными в
верхней памяти, то паникуем ("Wrong loader, giving up...").
3.1.1.4. Выяснение объема памяти системы
Получаем размер расширенной памяти {выше первого МБ} в КБ. Сначала
сбрасываем размер расширенной памяти в 0.
#ifndef STANDARD_MEMORY_BIOS_CALL
Сбрасываем счетчик области памяти E820.
Пробуем три различных способа выяснения размера. Сначала вызываем
E820h, которая позволит нам собрать карту памяти, затем пробуем E801h,
которая вернет 32-битный размер памяти и последней пробуем 88h,
которая вернет размер в диапазоне 0-64 МБ.
Метод E820h заполняет таблицу в empty_zero_block, которая содержит
список подходящих адрес/размер/тип значений. В
"linux/arch/i386/kernel/setup.c" эта информация передается в e820map и
в "linux/arch/i386/mm/init.c", затем новые данные используются для
обозначения страниц занятыми или свободными.
Метод E820h:
Получаем карту памяти BIOS. E820h возвращает память,
классифицированную по типам и допускает существование дыр (memory
holes). Мы сканируем эту память и строим список первых 32х областей
{до 32х областей либо до сообщения от BIOS что записей больше нет},
который мы вернем в районе "E820MAP" [Гляньте URL:
http://www.teleport.com/acpi/acpihtml/topic245.htm ]
Метод E801h:
Мы храним размер памяти от 0xe801 в совершенно другом месте, потому
как скорее всего это значение будет длиннее 16 бит.
Оно является суммой значений двух регистров, выраженной в 1 КБ блоках:
%C9x = размер памяти в диапазоне от 1 МБ до 16 МБ, в 1 КБ блоках +
%CAx = размер памяти выше 16 МБ в 64 КБ блоках.
И еще один традиционный метод.
BIOS int. 0x15/AH=0x88 возвращает размер памяти (до 16 либо 64 МБ, в
зависимости от BIOS). Мы всегда используем этот метод, не взирая на
результаты предыдущих двух методов.
#endif
Выставляем частоту повторов клавиатуры на максимум при помощи BIOS int
0x16.
3.1.1.5. Режимы видеоадаптера
Ищем видеоадаптер и выясняем поддерживаемые им режимы и выдаем
пользователю их список.
call video # {см. секцию "Запуск видео" ниже}
3.1.1.6. Получаем параметры жесткого диска
Получаем данные hd0: сохраняем дескриптор hd0 (полученный от вектора
прерывания 0x41) в INITSEG:0x80 размером 0x10 (17 байт).
Получаем данные hd1: Сохраняем дескриптор hd1 (полученный от вектора
прерывания 0x46) в INITSEG:0x90 размером 0x10.
Проверяем, что hd1 присутствует, при помощи BIOS int. 0x13. Если его
нет, то очищаем его дескриптор.
3.1.1.7. Получаем информацию о шине Micro Channel
Делаем проверки для Micro Channel (MCA):
- Устанавливаем длину конфигурационной таблицы MCA в 0, если шина не
найдена.
- Получаем параметры конфигурации системы (BIOS int. 0x15/%ah=0xc0).
После этого %es:%bx будет указывать на конфигурационную таблицу
системы.
- Мы сохраняем только первые 16 байт системной конфигурационной
таблицы если будут найдены: размер структуры, байт модели, байт
подмодели, версия BIOS и байты 1-5 таблицы конфигурации. Биты 0 или 1
(чаще 1) байта 1 конфигурации содержат информацию о наличии в системе
шины Micro Channel.
3.1.1.8. Проверяем мышь
Ищем PS/2 мышь при помощи прерывания BIOS 0x11 {возвращает список
оборудования}
- Очищаем флаг мыши (по умолчанию).
- BIOS int. 0x11: получаем список оборудования.
- Если бит 2 (значение 0x04) установлен, то мышь подключена и ее флаг
устанавливается, показывая тем самым что мышь подключена.
3.1.1.9. Ищем поддержку APM в BIOS
Проверяем поддержку APM BIOS (если ядро сконфигурировано с поддержкой APM):
- start: сбрасываем поле версии в 0, это означает что поддержки APM в BIOS нет.
- Проверяем наличие APM в BIOS при помощи BIOS int. 0x15.
- Если нет, то выходим.
- Проверяем наличие сигнатуры "PM" в %bx.
- Если ее нет, то поддержки APM BIOS нет: выходим.
- Проверяем 32-битную поддержку в %cx.
- Если ее нет, то у нас нет полноценной поддержки APM BIOS: выходим.
Для использования в Linux поддержка APM BIOS должна быть 32-битной.
- Сохраняем сегмент кода BIOS, смещение точки входа BIOS, 16-битный
сегмент кода BIOS, сегмент данных BIOS, длину сегмента кода BIOS и
длину сегмента данных BIOS.
- Сохраняем версию APM BIOS и флаги.
3.1.1.10. Готовимся к переключению в защищенный режим
Строим инструкцию перехода к адресу функции code32_start ядра.
(Загрузчик мог его изменить)
Перемещаем ядро в подходящее место, если это необходимо.
Загружаем дескрипторы сегментов (загружаем %ds = %cs).
Убеждаемся в том, что мы находимся в подходящем месте памяти для того,
чтобы разместить параметры загрузки и командной строки.
Загружаем 0,0 в регистр указателя IDT.
Вычисляем линейный базовый адрес ядерной GDT (таблицы) и загружаем
этот адрес и границу таблицы в регистр указателя GDT. Эта первичная
ядерная GDT описывает сегмент кода ядра как 4-ГБайтный, с базовым
адресом 0, как код/для-чтения/исполняемый, поделенный на блоки
размером 4 КБ. Сегмент данных ядра описан как 4-ГБайтный, с базовым
адресом 0, как данные/для-чтения/для-записи, поделенный на блоки
размером 4 КБ.
3.1.1.11. Задействуем адресную линию A20
- Очищаем 8042 (контроллер клавиатуры) от значений нажатых клавиш.
- Записываем 0xd1 (Выходной Порт Записи) в порт регистра команды 0x64.
- Снова очищаем 8042 (контроллер клавиатуры) от значений нажатых клавиш.
- Записываем 0xdf (Вентиль A20 + еще кое-чего) в Выходной порт 0x60.
- Снова очищаем 8042 (контроллер клавиатуры) от значений нажатых клавиш.
- Устанавливаем бит 1 (значение 0x02: FAST_A20) в "port 0x92" регистра
управления системой. Тем самым поднимается A20 на некоторых системах,
в зависимости от использованных в них чипсетов.
- Дожидаемся действительного подъема A20, это может потребовать
довольно много времени на некоторых системах. Области памяти,
использованные здесь (0x200), являются вектором прерывания 0x80 (?),
которым можно пользоваться без опаски. Когда A20 выключена, проверка
этих областей показывает, что они являются алиасами сами на себя
(сегмент_0:смещение_0x200 и сегмент_0xfff:смещение_0x210). {0xffff0 +
0x210 = 0x100200, а когда A20 выключена, результат равен 0x000200} Мы
просто ждем (заняты ожиданием/повтором) до тех пор, пока эти области
памяти перестанут быть алиасами.
3.1.1.12. Убеждаемся, что любой возможный сопроцессор корректно сброшен
- Пишем 0 в порт 0xf0 для сброса сигнала '-busy' математического сопроцессора.
- Пишем 0 в порт 0xf1 для сброса математического сопроцессора.
3.1.1.13. Маскируем все прерывания
Теперь мы маскируем все прерывания, остальное доделывается в init_IRQ().
- Маскируем все прерывания на ведомом контроллере прерываний: пишем
0xff в порт 0xa1.
- На ведущем контроллере прерываний маскируем все прерывания кроме
IRQ2, которое используется для каскадирования с ведомым контроллером:
пишем 0xfb в порт 0x21.
3.1.1.14. Переключаемся в защищенный режим
Теперь самое время действительно переключиться в защищенный режим.
Чтобы сделать вещи простыми насколько это возможно, мы не
устанавливаем регистры или еще чего-нибудь такого, мы просто позволяем
сделать это GNU-компилированным 32-битным программам. Мы просто
переходим к абсолютному адресу 0x1000 (или к тому, что укажет
загрузчик) в 32-битном защищенном режиме.
Заметьте, что этот прямой переход не является необходимым, однако есть
соображения в пользу того, что это хорошая идея. В любом случае это
безопасно.
Устанавливаем PE (Protected mode Enable - защищенный режим включен)
бит в MSW и переходим к следующей инструкции чтобы очистить очередь
выборки команд.
Сбрасываем %bx чтобы показать, что это загружающий процессор - BSP
(только для первого CPU).
3.1.1.15. Вызываем startup_32
Переходим к 32-битному коду ядра (startup_32).
Замечание: Для загруженных в верхнюю память больших ядер нам
необходимо сделать следующее:
jmpi 0x100000,__KERNEL_CS
но так как мы не перезагрузили %cs, то размер по умолчанию
результирующего смещения остался 16-битным. Однако процессор корректно
обработает наш 48-битный дальний указатель используя префикс операнда
(0х66). (INTeL 80386 Programmer's Reference Manual, Mixing 16-bit and
32-bit code, стр. 16-6).
.byte 0x66, 0xea # префикс + код-операнда-jmpi
code32: .long 0x1000 # или 0x100000 для больших ядер
.word __KERNEL_CS
В результате мы перейдем к "startup_32" в
"linux/arch/i386/kernel/head.S".
3.2. Настройка видео
Файл "linux/arch/i386/boot/video.S" включается в
"linux/arch/i386/boot/setup.S", поэтому они собираются вместе.
Разделение файлов служит для логического разнесения программных
модулей, даже не смотря на то, что модули собираются вместе.
"video.S" отвечает за работу с дисплейным адаптером в Linux/i386 и
установку видеорежимов. Более подробную информацию о видеорежимах в
Linux/i386 можно найти "linux/Documentation/svga.txt", написанном
Martin Mares [mj@ucw.cz].
Возможность выбора видеорежима зависит от конфигурации ядра. Когда она
включена, вы можете указать специфичный (предопределенный) режим,
который будет использован в процессе загрузки ядра либо запросить меню
со списком режимов и выбрать его оттуда.
Существует несколько эзотерических опций сборки "video.S", которые
здесь не описаны. Их все можно найти в "linux/Documentation/svga.txt".
Опция CONFIG_VIDEO_SVGA (для автоматического определения SVGA
адаптеров и режимов) по умолчанию выключена. Нормальный метод для
обнаружения видеоадаптера в Linux/i386 это VESA (опция
CONFIG_VIDEO_VESA для автоопределения VESA режимов).
"video:" является основной точкой входа, вызываемой из "setup.S".
Регистр %ds должен указывать на бутсектор. Код "video.S" и основной
код из "setup.S" используют разные сегменты.
Ниже следует упрощенное описание работы "video.S". Оно не содержит
описания опций CONFIG_VIDEO_LOCAL, CONFIG_VIDEO_400_HACK и
CONFIG_VIDEO_GFX_HACK и не уходит слишком глубоко в вызовы видеоBIOS
или доступ к видеорегистрам.
3.2.1. video:
- %fs перезаписывается значением %ds
- %ds и %es перезаписываются значением %cs
- %gs сбрасывается в 0
- Выясняем тип видеоадаптера и поддерживаемые режимы. (вызываем basic_detect)
- #ifdef CONFIG_VIDEO_SELECT
- Если пользователь хочет увидеть список поддерживаемых VGA-адаптером
режимов, то выводим его. (вызываем mode_menu)
- Устанавливаем выбранный видеорежим. (вызываем mode_set)
- #ifdef CONFIG_VIDEO_RETAIN
- Сохраняем параметры режима для ядра. (вызываем mode_params)
- Восстанавливаем исходное значение регистра DS.
3.2.1.1. basic_detect:
- Выясняем тип имеющегося у нас адаптера (CGA, MDA, HGA, EGA или VGA)
и передаем его ядру.
- Ищем EGA/VGA при помощи вызовов BIOS int. 0x10. Они также помогут
нам в случае с адаптерами CGA/MDA/HGA.
- Переменная "adapter" устанавливается в 0 для CGA/MDA/HGA, в 1 для
EGA и в 2 для VGA.
3.2.1.2. mode_params:
- Сохраняем параметры видеорежима для дальнейшего использования ядром.
Параметры берутся из BIOS кроме количества строк/столбцов в дефолтовом
режиме 80х25 - они выставляются напрямую, потому как некоторые очень
непонятные BIOS возвращают нелепые значения.
- #ifdef CONFIG_VIDEO_SELECT
- В случае графического режима с линейным буфером кадров переходим к mopar_gr.
- #endif /* CONFIG_VIDEO_SELECT */
- Для MDA/CGA/HGA/EGA/VGA:
- Считываем и сохраняем положение курсора.
- Считываем и сохраняем видео страницу/режим/ширину.
- Для MDA/HGA, изменяем video_segment на $0xb000. (либо оставляем его
исходное значение $0xb800 для всех остальных адаптеров.)
- Получаем размер шрифта (работает только с EGA/VGA).
- Сохраняем число видео столбцов и строк.
#ifdef CONFIG_VIDEO_SELECT
3.2.1.3. mopar_gr:
- Получаем параметры кадрового буфера VESA.
- Получаем размер видеопамяти и информацию об интерфейсе защищенного
режима при помощи вызовов BIOS int. 0x10.
3.2.1.4. mode_menu:
Создаем таблицу списка режимов и выдаем меню режимов.
3.2.1.5. mode_set:
Выставляем часть или все из нижеследующего для выбранного видеорежима
с помощью вызовов BIOS int. 0x10 или записи в регистры при
необходимости:
- Сброс видеорежима
- Количество строк сканирования
- Размер точки (пиксела) шрифта
- Сохраняем размер шрифта в force_size. "force_size" используется
вместо переменных BIOS для перекрытия встречающихся кривых интерфейсов
видеоBIOS.
Некоторые видеорежимы требуют прямой записи в регистры для
выставления:
- Размещения линий сканирования курсора
- Запуска вертикальной синхронизации
- Останова вертикальной синхронизации
- Размера дисплея по вертикали
- Запуска вертикального гашения
- Останова вертикального гашения
- Всего вертикального (Vertical total)
- (вертикального) переполнения
- Корректировки полярности синхронизации
- Сохранения битов выбора генератора и бита цвета
{конец mode_set}
#ifdef CONFIG_VIDEO_RETAIN /* по умолчанию включена */
3.2.1.6. store_screen:
CONFIG_VIDEO_RETAIN требует сохранения содержимого экрана при
переключении режимов. Содержимое экрана сохраняется во временном
буфере (если хватает памяти) из которого экран может быть перерисован
позже.
- Сохраняем текущее количество строк и столбцов, позицию курсора и режим.
- Вычисляем размер изображения.
- Сохраняем изображение экрана.
- Поднимаем флаг "do_restore", благодаря чему содержимое экрана будет
восстановлено в конце определения/выбора видеорежима.
3.2.1.7. restore_screen:
Восстанавливает экран из временного буфера (если он был сохранен).
- Получаем параметры текущего режима.
- Выставляем позицию курсора.
- Восстанавливаем экран.
#endif /* CONFIG_VIDEO_RETAIN */
Ищем видеорежимы.
- Начинаем с режима 0.
- Проверяем режим.
- Проверяем текстовый/не-текстовый.
- OK, сохраняем режим.
- Возвращаемся обратно к режиму 3.
#ifdef CONFIG_VIDEO_SVGA
3.2.1.10. svga_modes:
Пытаемся определить тип SVGA карты и применить к ней (обычно
приблизительно) таблицу видеорежимов.
- Пробуем все известные SVGA адаптеры.
- Вызываем процедуру проверки для каждого адаптера.
- Если адаптер найден, копируем видеорежимы.
- Сохраняем указатель на название карты.
#endif /* CONFIG_VIDEO_SVGA */
#endif /* CONFIG_VIDEO_SELECT */
4. Инициализация архитектурно-зависимых частей Linux
(из "linux/arch/i386/kernel/head.S")
Код загрузки в "linux/arch/i386/boot/setup.S" перемещает выполнение к
началу кода в "linux/arch/i386/kernel/head.S" (помеченному
"startup_32:").
Чтобы эта точка стала доступной, небольшая разжатая функция ядра
разжимает оставшийся сжатый ядерный образ и затем переходит к
полученному коду.
Ниже следует описание действий кода из "head.S".
4.1. startup_32:
swapper_pg_dir является указателем на самую верхнюю страницу по адресу 0x00101000.
На входе %esi содержит 32-битный указатель на код реального режима.
4.2. Устанавливаем сегментные регистры в известные значения
Выставляем регистры %ds, %es, %fs и %gs в __KERNEL_DS.
4.3. Проверка BSP (Bootstrap Processor) в случае SMP (Symmetric MultiProcessing)
#ifdef CONFIG_SMP
Если %bx занулен, то мы загружаемся на Загружающем процессоре (BSP) и
следующее пропускаем, иначе мы на Процессоре Приложений (Application
Processor) и делаем следующее:
Если запрошенное значение %cr4 ненулевое, то выставляем опции
управления страницами (PSE, PAE, ...) и пропускаем "Инициализацию
страничных таблиц" (переход к "Включению управления страницами").
#endif /* CONFIG_SMP */
4.4. Инициализация страничных таблиц
Стартуем с pg0 (страница 0) и инициируем все страницы в 007 (PRESENT +
RW + USER).
4.5. Включение управления страницами
Выставляем %cr3 (указатель страничной таблицы) в swapper_pg_dir.
Выставляем бит страничной организации ("PG") регистра %cr0 в состояние
********** страничная организация задействована **********.
Вызываем $ чтобы сбросить очередь выборки команд.
Переходим к *[$] для уверенности в том, что %eip содержит корректное
значение.
Если это не BSP (Bootstrap Processor), сбрасываем все флаги и
переходим к checkCPUtype.
#endif /* CONFIG_SMP */
4.6. Очистка BSS
BSP очищает все BSS (область между __bss_start и _end) для ядра.
4.7. Настройка 32-битного режима
Настраиваем IDT для 32-битного режима (вызываем setup_idt). setup_idt
настроит IDT с 256-ю записями, указывающими на дефолтовый обработчик
прерываний "ignore_int" (as interrupt gates - ?). На самом деле она не
загружает IDT - это можно сделать только после включения страничной
организации и переноса ядра к PAGE_OFFSET. Прерывания будут включены в
другом месте, когда мы сможем быть относительно спокойны по поводу
того, что все в порядке.
Очищаем регистр eflags (перед переключением в защищенный режим).
4.8. Сохранение параметров загрузки и командной строки
Первые 2 КБ _empty_zero_page отводятся для параметров загрузки, вторые
2 KB - для командной строки.
4.9. checkCPUtype
Устанавливаем X86_CPUID в -1.
Используем регистр флагов, результаты инструкций push/pop и инструкции
CPUID для выяснения производителя и типа CPU. Устанавливаем X86,
X86_CPUID, X86_MODEL, X86_MASK и X86_CAPABILITY. Выставляем
соответствующие биты в %cr0.
Также проверяем наличие сопроцесора 80287 или 80387. Выставляем
X86_HARD_MATH, если найден математический сопроцессор или модуль
вычислений с плавающей точкой.
4.10. Учет этого процессора (п.4.9)
Для конфигураций с CONFIG_SMP увеличиваем счетчик "готов" для подсчета
количества проинициализированных процессоров.
Загружаем GDT из gdt_descr и IDT из idt_descr. GDT содержит 2 записи
для ядра (4 ГБ каждая для кода и для данных начиная с 0) и 2 записи
пространства пользователя (4 ГБ каждая для кода и для данных начиная с
0). Между дескрипторами пространства пользователя и дескрипторами APM
имеется два нулевых (null) дескриптора.
GDT также содержит 4 записи для сегментов APM. APM сегменты имеют
байтовую организацию и их базы и границы выставляются в процессе
работы. Оставшееся в gdt_table место (после APM сегментов)
используется для TSS и LDT.
Переходим к __KERNEL_CS:%eip чтобы задействовать GDT. Теперь мы в
********** защищенном режиме **********.
Перезагружаем все сегментные регистры: выставляем %ds, %es, %fs и %gs
в __KERNEL_DS.
#ifdef CONFIG_SMP
Перезаписываем значением __KERNEL_DS только сегмент указателя стека
(%ss).
Сбрасываем указатель LDT в 0.
Сбрасываем Direction Flag (DF) процессора в 0 для gcc.
4.12. Запуск других процессоров
Для сборок с CONFIG_SMP и в случае если текущий процессор не первичный
(Bootstrap, загружающий), вызываем initialize_secondary(), из которой
выполнение не возвращается. Вторичный(е) (AP) процессор(ы)
инициализируется(ются) и переводится(ятся) в режим простоя до тех пор,
пока процессы его(их) не займут.
Если это первый или единственный процессор, вызываем start_kernel().
(см. ниже)
/* вызовы выше не должны возвращать управление, но в некоторых ситуациях это
случается: */
L6: jmp L6
5. Архитектурно-независимая инициализация Linux
(из "linux/init/main.c")
"linux/init/main.c" начинает работу с функции start_kernel(), которая
вызывается из "linux/arch/i386/kernel/head.S". start_kernel() не
возвращает управления. Она завершается вызовом функции cpu_idle().
5.1. start_kernel:
Прерывания все еще запрещены. Выполняем необходимые установки и
разрешаем их.
Блокируем ядро (BKL: big kernel lock - большая блокировка ядра).
Выводим строку linux_banner (она находится в "linux/init/version.c")
при помощи printk().
ЗАМЕЧАНИЕ: printk() на самом деле на консоль пока еще ничего не
выводит, она просто буферизует строку до тех пор, пока устройство
консоли не зарегистрирует себя в ядре, затем ядро передает
буферизованные консольные сообщения зарегистрированному консольному
устройству. Зарегистрированных консольных устройств может быть
несколько.
********** printk() можно вызывать сколько угодно рано, потому как она
реально ничего нигде не печатает. Она просто помещает сообщение в
"log_buf", память под который выделяется статически в
"linux/kernel/printk.c". Сообщения, находящиеся в "log_buf",
передаются зарегистрированным консольным устройствам сразу как только
они зарегистрируются. **********
5.1.1. Еще архитектурно-зависимой инициализации
Вызываем setup_arch(&command_line):
Этим выполняются архитектурно-специфичные инициализации (детали ниже).
Затем возвращаемся к архитектурно-независимой инициализации....
Напоминаем, что start_kernel() работает как описано ниже для всех
процессорных архитектур, хотя некоторые из вызываемых функций являются
архитектурно-специфичными.
5.1.2. Продолжение архитектурно-независимого старта
Выводим содержимое командной строки ядра.
5.1.3. Разбор параметров командной строки
parse_options(command_line): разбирает параметры ядра из командной
строки. Это простая функция для парсинга параметров ядра из командной
строки. Она разбирает командную строку и соответственно инициализирует
аргументы и окружение init'а (который поток). Каждый аргумент
командной строки рассматривается как переменная окружения, если он
содержит знак "=". Она также ищет опции, предназначенные для ядра, при
помощи вызова checksetup(), который проверяет командную строку на
параметры ядра - при их наличии они объявляются при помощи "__setup",
например:
__setup("debug", debug_kernel);
Если при наличии такого объявления обнаружится строка "debug", то
будет вызвана функция debug_kernel(). Список параметров ядра можно
найти в "linux/Documentation/kernel-parameters.txt".
Эти опции не передаются init'у - они предназначены для использования
исключительно внутри ядра. Список аргументов по умолчанию для init'а
есть {"init", NULL}, максимум с 8-ю аргументами командной строки.
Установки окружения по умолчанию для потока init'а есть {"HOME=/",
"TERM=linux", NULL}, с максимум 8-ю установками переменных окружения в
командной строке. Если LILO грузит нас с командной строкой по
умолчанию, то он помещает "auto" перед всей строкой, благодаря чему
шелл думает, что он должен выполнить скрипт с таким именем. Так что мы
игнорируем все аргументы, введенные _перед_ init=... [MJ]
5.1.4. trap_init
(в linux/arch/i386/kernel/traps.c)
Устанавливаем обработчики исключений для основных процессорных
исключений, т.е. это не обработчики аппаратных прерываний.
Устанавливаем обработчик системного вызова программного прерывания.
Устанавливаем обработчики для lcall7 (для iBCS) и lcall27 (для
бинарников Solaris/x86).
Вызываем cpu_init() чтобы:
- инициализировать каждый процессор
- перезагрузить GDT и IDT
- демаскировать бит NT (Nested Task) регистра eflags
- установить и загрузить TSS и LDT для каждого процессора
- очистить 6 отладочных регистров (0, 1, 2, 3, 6 и 7)
- stts(): установить бит 0x08 (TS: Task Switched) в CR0 для включения
задержанной (lazy) записи в регистры при контекстных переключениях.
5.1.5. init_IRQ
(в linux/arch/i386/kernel/i8259.c)
Вызываем init_ISA_irqs() чтобы инициалзировать для котроллера
прерываний 8259A и установить дефолтовые обработчики прерываний для
ISA.
Устанавливаем вход прерывания (interrupt gate - ?) для всех
неиспользованных векторов прерывания.
Для конфигураций с CONFIG_SMP выставляем IRQ 0 заранее, потому как оно
используется перед установкой IO APIC.
Для CONFIG_SMP устанавливаем обработчик прерывания для CPU-to-CPU IPI,
которые используются для "reschedule helper."
Для CONFIG_SMP устанавливаем обработчик прерывания для IPI, который
используется для сброса (здесь - to invalidate) TLB.
Для CONFIG_SMP устанавливаем обработчик прерывания для IPI, который
используется для вызовов основных функций.
Для конфигураций с CONFIG_X86_LOCAL_APIC устанавливаем обработчик
прерывания для IPI от независимо работающего локального таймера APIC.
Для конфигураций с CONFIG_X86_LOCAL_APIC устанавливаем обработчики
прерываний для ложных или ошибочных прерываний.
Настраиваем микросхему системных часов для генерации прерываний каждые
HZ герц.
Если система содержит внешний FPU, то устанавливаем обработчик IRQ 13
для обработки исключений при операциях с плавающей точкой.
5.1.6. sched_init
(в linux/kernel/sched.c)
- Устанавливаем ID процессора для структур init_task
- Очищаем таблицу pidhash (хешей PID'ов). TBD: Почему? Разве она не в BSS?
- Вызываем init_timervecs()
- Вызываем init_bh() для инициализации "верхней половины" (bottom
half) очередей для timer_bh, tqueue_bh и immediate_bh.
5.1.7. time_init
(в linux/arch/i386/kernel/time.c)
Выставляем текущее время системы (xtime) из CMOS.
Устанавливаем обработчик прерывания irq0 для тиков от таймера.
5.1.8. softirq_init
(в linux/kernel/softirq.c)
5.1.9. console_init
(в linux/drivers/char/tty_io.c)
ХАК! Это преждевременно. Мы включаем консоль до того, как мы выполним
настройки PCI и т.д., поэтому console_init должна обработать это. В то
же время нам необходимо делать вывод заранее в случае если что-то
пойдет не так.
5.1.10. init_modules
(в linux/kernel/module.c)
Для конфигураций с CONFIG_MODULES вызываем init_modules(). Тем самым
выставялется размер (или кол-во символов) таблицы символов ядра.
5.1.11. Установка профилирования
Если профилирование включено ("profile=#" в командной строке ядра):
вычисляем размер "сегмента" текста (кода) профиля ядра; вычисляем
размер буфера профиля в страницах (с округлением); выделяем буфер для
профиля: prof_buffer = alloc_bootmem(size);
5.1.12. kmem_cache_init
(в linux/mm/slab.c)
5.1.13. sti
********** Здесь прерывания уже включены. **********
Теперь можно выполнить "calibrate_delay()" (ниже).
5.1.14. calibrate_delay
Подсчитываем кол-во циклов задержки "loops_per_jiffy" и выводим его в
BogoMIPS.
5.1.15. Инициализация INITRD (INITial Ram-Disk)
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok && initrd_start < (min_low_pfn << PAGE_SHIFT)) {
printk("initrd overwritten (initrd_start < (min_low_pfn << PAGE_SHIFT)) - disabling it.n");
initrd_start = 0; // mark initrd as disabled
}
#endif /* CONFIG_BLK_DEV_INITRD */
5.1.16. mem_init
(в linux/arch/i386/mm/init.c)
- Очищаем empty_zero_page.
- Вызываем free_all_bootmem() и добавляем всю освобожденную память к
totalram_pages.
- Подсчитываем количество зарезервированных страниц RAM.
- Выводим размер системной памяти (свободно/всего), размер кода ядра,
объем зарезервированной памяти, размер данных ядра, "начальный" размер
ядра и объем верхней памяти.
- Для CONFIG_SMP вызываем zap_low_mappings().
********** get_free_pages() можно использовать после mem_init(). **********
5.1.17. kmem_cache_sizes_init
(в linux/mm/slab.c)
Устанавливаем оставшиеся внутренние и основные кэши. Функции,
вызванные после "get_free_page()", остаются доступными и перед
smp_init().
********** kmalloc() можно использовать после kmem_cache_sizes_init(). **********
5.1.18. proc_root_init
(в linux/fs/proc/root.c)
Для конфигураций с CONFIG_PROC_FS:
- вызываем proc_misc_init()
- mkdir /proc/net
- для CONFIG_SYSVIPC выполняем mkdir /proc/sysvipc
- для CONFIG_SYSCTL выполняем mkdir /proc/sys
- mkdir /proc/fs
- mkdir /proc/driver
- вызываем proc_tty_init()
- mkdir /proc/bus
5.1.19. mempages = num_physpages;
5.1.20. fork_init(mempages)
(в linux/kernel/fork.c)
Устанавливаем максимальное кол-во потоков по умолчанию в безопасное
значение: потоковые структуры могут использовать максимум половину
памяти.
5.1.21. proc_caches_init()
(в linux/kernel/fork.c)
Вызываем kmem_cache_create() для создания отдельных кэшей для
signal_act (работа с сигналами), files_cache (files_struct), fs_cache
(fs_struct), vm_area_struct и mm_struct.
5.1.22. vfs_caches_init(mempages)
(в linux/fs/dcache.c)
Вызываем kmem_cache_create() для создания отдельных кэшей для
buffer_head, names_cache, filp и для CONFIG_QUOTA, dquot.
Вызываем dcache_init() чтобы создать dentry_cache и dentry_hashtable.
5.1.23. buffer_init(mempages)
(в linux/fs/buffer.c)
Выделяем память под хэш-таблицу буферного кэша и создаем пустой
список. Используем get_free_pages() для хэш-таблицы чтобы уменьшить
TLB-промахи; используем SLAB-кэш для заголовков буфера. Устанавливаем
цепочки хэша, пустые списки и списки LRU.
5.1.24. page_cache_init(mempages)
(в linux/mm/filemap.c)
Выделяем и очищаем память под хэш-таблицу страничного кэша.
5.1.25. kiobuf_setup()
(в linux/fs/iobuf.c)
Вызываем kmem_cache_create() чтобы создать кэш буфера ввода-вывода
ядра.
5.1.26. signals_init()
(в linux/kernel/signal.c)
Вызываем kmem_cache_create() чтобы создать SLAB-кэш "sigqueue"
(очереди заданий).
5.1.27. bdev_init()
(в linux/fs/block_dev.c)
Инициализируем заголовки списка bdev_hashtable.
Вызываем kmem_cache_create() чтобы создать SLAB-кэш "bdev_cache".
5.1.28. inode_init(mempages)
(в linux/fs/inode.c)
- Выделяем память под inode_hashtable.
- Инициализируем заголовки списка inode_hashtable.
- Вызываем kmem_cache_create() SLAB-кэш инодов (inodes).
5.1.29. ipc_init()
(в linux/ipc/util.c)
Для конфигураций с CONFIG_SYSVIPC вызываем ipc_init().
Здесь инициализируются различные ресурсы System V IPC
(семафоры,сообщения и разделяемая память).
5.1.30. dquot_init_hash()
(в linux/fs/dquot.c)
Для конфигураций с CONFIG_QUOTA вызываем dquot_init_hash().
- Очищаем dquot_hash. TBD: Почему? Разве он не в BSS? Там.
- Очищаем dqstats. TBD: Почему? Разве она не в BSS? Там.
5.1.31. check_bugs()
(в linux/include/asm-i386/bugs.h)
- identify_cpu()
- Для не-CONFIG_SMP конфигураций вызываем print_cpu_info()
- check_config()
- check_fpu()
- check_hlt()
- check_popad()
- Корректируем system_utsname.machine{байт 1} в соотв-вии с
boot_cpu_data.x86
5.1.32. В случае SMP запускаем другие процессоры
Выполнение smp_init(), в зависимости от конфигурации ядра, возможно
тремя способами.
Для однопроцессорных (UP) систем без IO APIC (CONFIG_X86_IO_APIC не
определена), smp_init() пуста - соответственно ничего не происходит.
Для однопроцессорных UP систем с IO APIC для маршрутизации прерываний
она вызывает IO_APIC_init_uniprocessor().
Для многопроцессорных (SMP) систем ее основная задача заключается в
вызове архитектурно-специфичной функции "smp_boot_cpus()", которая
выолняет следующее:
- Для ядер с CONFIG_MTRR вызывает mtrr_init_boot_cpu(), которая должна
отработать до того, как загрузятся другие процессоры.
- Сохраняет и выводит информацию о BSP CPU.
- Сохраняет ID BSP APIC и BSP ID логического CPU (последний равен 0).
- Если не найдена таблица маршрутизации прерываний MP BIOS, то
возвращается к использованию только одного CPU и завершается.
- Проверяет существование локального APIC для BSP.
- Если использована опция загрузки "maxcpus" со значением 1 (без SMP),
то игнорирует таблицу маршрутизации прерываний MP BIOS.
- Переключает систему из PIC-режима в режим прерывания симметричного
ввода-вывода.
- Устанавливает локальный APIC BSP.
- Использует карту наличия (presence map) CPU для последовательной
загрузки AP. Ожидает загрузки предыдущего AP перед запуском загрузки
следующего.
- В случае использования IO APIC {что справедливо всегда кроме случаев
с опцией загрузки "noapic"} устанавливает IO APIC (или каждый, если их
несколько).
5.1.33. Запуск init потоком
Мы считаем, что начальный поток работает нормально.
Как и бездействующие потоки (idlers), init является неблокируемым
потоком ядра, который делает системные вызовы (и поэтому не может быть
блокируемым).
Эта функция остается как процесс номер 0. Ее цель заключается в
использовании пустых циклов CPU. Если ядро собрано с поддержкой APM
или ACPI, то cpu_idle() вызывает поддерживаемые энергосберегающие
свойства этих спецификаций. В противном случае она просто выполняет
инструкцию "hlt".
{конец start_kernel()}
5.2. setup_arch
(в "linux/arch/i386/kernel/setup.c")
5.2.1. Копирование и преобразование системных параметров
Копируем и преобразуем параметры из 16-битного реального режима в
32-битный запускающий код.
5.2.2. Для конфигураций с включенным RAMdisk (CONFIG_BLK_DEV_RAM)
Инициализируем rd_image_start, rd_prompt и rd_doload из параметров
реального режима.
5.2.3. setup_memory_region
Используем карту памяти от BIOS для установки областей памяти.
5.2.4. Выставление границ памяти
Устанавливаем значения для начала кода ядра, конца кода ядра, конца
данных ядра и "_end" (конец кода ядра = адресу "brk").
Устанавливаем значения для начала и конца code_resource и начала и
конца data_resource.
5.2.5. parse_mem_cmdline
Разбираем любые параметры "mem=" командной строки ядра и запоминаем их.
5.2.6. Установка страничных кадров
Используем карту памяти от BIOS для установки страничных кадров.
Регистрируем доступные страницы нижней RAM в распределителе bootmem.
Резервируем физическую страницу 0: "это особенная страница BIOS во
многих системах, включающая чистые перезагрузки, SMP-операции и
laptop-функции."
5.2.7. Обработка конфигураций SMP и IO APIC
Для CONFIG_SMP резервируем страницу, следующую за страницей 0 для
стэка и запуска, затем вызываем smp_alloc_memory() для выделения
нижней памяти под запускающий (trampoline) код реального режима для AP
процессора(ов).
Для конфигураций с CONFIG_X86_IO_APIC вызываем find_smp_config() чтобы
найти и зарезервировать любую память с SMP-конфигурационной
информацией времени загрузки вроде таблицы данных MP (Multi Processor)
от BIOS.
5.2.8. paging_init()
paging_init() устанавливает страничные таблицы - вспомните, что первые
8 МБ уже зарезервированы в head.S.
Эта процедура также освобождает страницу по виртуальному адресу ядра
0, так что мы теперь можем вылавливать в ядре надоедливые ошибки с
нулевыми ссылками (pesky NULL-reference errors).
5.2.9. Сохранение SMP-конфигурации времени загрузки
Для конфигураций с CONFIG_X86_IO_APIC вызываем get_smp_config() для
чтения и записи данных конфигурации MP-таблицы маршрутизации
прерываний IO APIC.
Для конфигураций с CONFIG_X86_LOCAL_APIC вызываем
init_apic_mappings().
5.2.10. Резервирование памяти для INITRD
Для конфигураций с CONFIG_BLK_DEV_INITRD при наличии достаточного
кол-ва памяти под начальный RamDisk вызываем reserve_bootmem() чтобы
зарезервировать RAM для него.
5.2.11. Поиск и работа с ROM
Вызываем probe_roms() и если находим корректные ресурсы ROM, то
резервируем их. Это делается для образа ROM стандартной видеоBIOS,
любых других найденных ROM и для расширения ROM системной платы.
5.2.12. Резервирование системных ресурсов.
Вызываем request_resource() для резервирования видеоRAM.
Вызываем request_resource() для резервирования всех стандартных
ресурсов ввода-вывода системной платы PC.
{конец setup_arch()}
5.3. Поток init
Поток init начинается функцией init() в "linux/init/main.c". Он всегда
должен становиться процессом с номером 1.
Прежде всего init() блокирует ядро и затем вызывает do_basic_setup()
для выполнения множества инициализаций шин и/или устройств {подробнее
ниже}. После do_basic_setup() большая часть процесса инициализации
ядра завершена. Затем init() освобождает любую память, которая была
использована в процессе инициализации [помеченная "__init",
"__initdata", "__init_call" или "__initsetup"] и разблокирует ядро
(BKL).
После этого init() открывает /dev/console и дублирует этот файловый
дескриптор два раза для создания stdin, stdout и stderr файлов для
init'а и всех его потомков.
Под занавес init() пытается выполнить команду, указанную в параметрах
командной строки ядра если она там есть, либо программу или скрипт
если она сможет их найти {/sbin/init, /etc/init, /bin/init} или хотя
бы /bin/sh. Если init() не сможет выполнить ничего из указанного, то
она паникует ("No init found. Try passing init= option to kernel." -
"init не найден. Попробуйте указать его через опцию ядра init=").
5.4. do_basic_setup {часть потока init}
Теперь машина инициализирована. Устройства пока еще нетронуты, но
подсистема CPU поднята и работает, также функционирует управление
памятью и процессами.
5.4.1. Собираем сирот
Процесс init обрабатывает все осиротевшие задачи.
5.4.2. MTRRs
// Перед этим должна быть завершена инициализация SMP.
Для CONFIG_MTRR вызываем mtrr_init() [в
linux/arch/i386/kernel/mtrr.c].
5.4.3. SYSCTLs
Для конфигураций с CONFIG_SYSCTL вызываем sysctl_init() [в
linux/kernel/sysctl.c].
5.4.4. Инициализируем много устройств
/*
* Ok, на этот момент все CPU должны быть инициализированы, так что
* мы можем приступить к поиску устройств.
*/
5.4.5. PCI
Для конфигураций с CONFIG_PCI вызываем pci_init() [в
linux/drivers/pci/pci.c].
5.4.6. Micro Channel
Для конфигураций с CONFIG_MCA вызываем mca_init() [в
linux/arch/i386/kernel/mca.c].
5.4.7. ISA PnP
Для конфигураций с CONFIG_ISAPNP вызываем isapnp_init() [в
linux/drivers/pnp/isapnp.c].
5.4.8. Поднимаем сеть
/* Инициализация сети требует контекста процесса */
sock_init();
[в linux/net/socket.c]
5.4.9. Начальный RamDisk
#ifdef CONFIG_BLK_DEV_INITRD
real_root_dev = ROOT_DEV;
real_root_mountflags = root_mountflags;
if (initrd_start && mount_initrd)
root_mountflags &= ~MS_RDONLY; // change to read/write
else
mount_initrd =0;
#endif /* CONFIG_BLK_DEV_INITRD */
5.4.10. Запускаем контекстный поток ядра (keventd)
[в linux/kernel/context.c]
5.4.11. Initcalls
Вызываем функции, отмеченные как "__initcall":
do_initcalls();
[в linux/init/main.c]
Здесь инициализируется много функций и некоторые подсистемы (в
произвольном или гарантированном порядке, если это не зафиксировано в
их Makefile'ах) если они были встроены в ядро, такие как:
- APM: apm_init() {в linux/arch/i386/kernel/apm.c}
- cpuid: cpuid_init() {в linux/arch/i386/kernel/cpuid.c}
- DMI: dmi_scan_machine() {в linux/arch/i386/kernel/dmi_scan.c}
- микрокод: microcode_init() {в linux/arch/i386/kernel/microcode.c}
- MSR: msr_init() {в linux/arch/i386/kernel/msr.c}
- разделы: partition_setup() {в linux/fs/partitions/check.s}
- файловые системы, каналы, управление буферами кэшами, загрузчики
различных двоичных форматов, наборы символом NLS (Native Language
Support): слишком много всего, чтобы здесь перечислять {в linux/fs/*}
- пользовательский кэш (для границ): uid_cache_init() {в
linux/kernel/user.c}
- kmem_cpu_cache: kmem_cpucache_init() {в linux/mm/slab.c}
- shmem: init_shmem_fs() {в linux/mm/shmem.c}
- kswapd: kswapd_init() {в linux/mm/vmscan.c}
- работа с сетью, TCP/IP, IPv6, сокеты, 802.2, SNAP, LLC, X.25, AX.25,
IPX, kHTTPd, ATM LAN эмуляция (LANE), IP цепочки/форвардинг,
NAT/маскарадинг, пакетные соответствие/фильтрация/журналирование,
файрволинг, DECnet, мосты и много других протоколов {в linux/net/*}
- драйверы, некоторые из которые не являются точно драйверами, но
помогают при поиске и подсчете шин/устройств вроде следующих:
- ACPI: acpi_init() {в linux/drivers/acpi/*}
- PCI: pci_proc_init() {в linux/drivers/pci/*}
- контроллеры PCMCIA {в linux/drivers/pcmcia/*}
- и...
- atm-драйверы {в linux/drivers/atm/*}
- драйверы блочных устройств {в linux/drivers/block/*}
- CD-ROM-драйверы {в linux/drivers/cdrom/*}
- драйверы символьных устройств {в linux/drivers/char/*}
- I2O-драйверы {в linux/drivers/i2o/*}
- IDE-драйверы {в linux/drivers/ide/*}
- драйверы устройств ввода (клавиатура/мышь/джойстик) {в
linux/drivers/input/*}
- ISDN-драйверы {в linux/drivers/isdn/*}
- md, LVM и RAID драйверы {в linux/drivers/md/*}
- драйверы радиоустройств {в linux/drivers/media/radio/*}
- драйверы видеоустройств {в linux/drivers/media/video/*}
- MTD-драйверы {в linux/drivers/mtd/*}
- сетевые драйверы, включая PLIP, PPP, dummy, Ethernet, bonding,
Arcnet, hamradio, PCMCIA, Token Ring и WAN
- драйверы логических и физических устройств SCSI {в
linux/drivers/scsi/*}
- драйверы звуковых устройств {в linux/drivers/sound/*}
- драйверы телефонных устройств {в linux/drivers/telephony/*}
- драйверы хост-контроллеров и устройств USB {в linux/drivers/usb/*}
- драйверы кадрового видеобуфера {в linux/drivers/video/*}
5.4.12. Файловые системы (ФС)
Вызываем filesystem_setup():
- init_devfs_fs(); /* Заголовочный файл может оставить это пустым */
- Для конфигураций с CONFIG_NFS_FS вызываем init_nfs_fs().
- Для конфигураций с CONFIG_DEVPTS_FS вызываем init_devpts_fs().
[в linux/fs/filesystems.c]
5.4.13. IRDA
Для конфигураций с CONFIG_IRDA вызываем irda_device_init().
/* Это обязательно надо сделать после инициализации протокола */
[в linux/net/irda/irda_device.c]
5.4.14. PCMCIA
/* Это делаем в последнюю очередь */
Для конфигураций с CONFIG_PCMCIA вызываем init_pcmcia_ds(). [в
linux/drivers/pcmcia/ds.c]
5.4.15. Монтируем корневую ФС
mount_root();
[в linux/fs/super.c]
5.4.16. Монтируем ФС устройств (devfs)
mount_devfs_fs ();
[в linux/fs/devfs/base.c]
5.4.17. Переключаемся к начальному RamDisk'у
#ifdef CONFIG_BLK_DEV_INITRD
if (mount_initrd && MAJOR(ROOT_DEV) == RAMDISK_MAJOR && MINOR(ROOT_DEV) == 0) {
// Start the linuxrc thread.
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
if (pid > 0)
while (pid != wait(&i));
if (MAJOR(real_root_dev) != RAMDISK_MAJOR
|| MINOR(real_root_dev) != 0) {
error = change_root(real_root_dev,"/initrd");
if (error)
printk(KERN_ERR "Change root to /initrd: "
"error %dn",error);
}
}
#endif /* CONFIG_BLK_DEV_INITRD */
Смотрите "linux/Documentation/initrd.txt" для подробной информации про
RAM-диски.
{конец do_basic_setup()}
6. Глоссарий
AP: Application Processor - процессор приложений, любой x86-процессор
кроме загружающего процессора на IA-32 SMP-системах
ACPI: Advanced Configuration and Power Interface - расширенный
интерфейс конфигурации и питания
BSS: Block Started by Symbol - блок, начинающийся с символа,
неинициализированный сегмент данных
BKL: Big Kernel Lock - большая блокировка ядра, глобальная блокировка
ядра Linux
CRn: Control Register n - управляющий регистр n, i386-специфичные
управляющие регистры
FPU: Floating Point Unit - модуль расчетов с плавающей точкой,
отдельное устройство мат. сопроцессора
GB: гигабайт (1024 * 1024 * 1024 байт)
GDT: Global Descriptor Table - глобальная дескрипторная таблица,
таблица управления памятью в i386
IA: Intel Architecture - архитектура Intel (также i386, x86)
IDT: Interrupt Descriptor Table - таблица дескрипторов прерываний,
i386-специфичная таблица, которая содержит информацию, используемую
при обработке прерываний
initrd: initial RAM disk - начальный RAM-диск (см.
"linux/Documentation/initrd.txt")
IPC: Inter-Process Communication - межпроцессное взаимодействие
IPI: Inter-processor Interrupt - межпроцессорное прерывание, метод
сигнализирующих прерываний между многими процессорами в SMP-системах
IRDA: InfraRed Data Association - (литературно - ?)
IRQ: Interrupt ReQuest - запрос прерывания
ISA: Industry Standard Architecture - стандартная промышленная
архитектура
KB: килобайт (1024 байт)
LDT: Local Descriptor Table - локальная дескрипторная таблица,
i386-специфичная таблица управления памятью, которая используется для
описания памяти для каждого неядерного процесса
MB: мегабайт (1024 * 1024 байт)
MCA: Micro Channel Architecture - микроканальная архитектура,
используется в компьютерах IBM PS/2
MSW: Machine Status Word - машинное слово состояния
MTRR: Memory Type Range Registers - регистры типа диапазона памяти
PAE: Physical Address Extension - расширение физической адресации:
расширяет адресное пространство до 64 ГБ вместо 4 ГБ
PCI: Peripheral Component Interconnect - межсоединения периферийных
компонентов, промышленный стандарт для соединения устройств на
локальной шине в компьютерной системе
PCMCIA: Personal Computer Memory Card International Association -
международная ассоциация производителей карт памяти персональных
компьютеров, определяет стандарты для карт PCMCIA и PC-карт CardBus
17.Mindshare, Inc., Tom Shanley, "Pentium(r) Pro and Pentium(r) II
System Architecture," second edition. Addison-Wesley, 1998.
18.Allesandro Rubini, "Linux Device Drivers." O'Reilly and Associates, 1998.
19.URL: ftp://linux01.gwdg.de/pub/cLIeNUX/interim/Janet_Reno.tgz
20.URL: http://www.eecs.wsu.edu/~cs640/ (was dead at last check)
21.URL: http://www.linuxbios.org + "Papers"