WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Этот раздел объясняет шаги, принимаемые в течение трансляции Linux ядра.
Процесс построения зависит от архитектуры, так что я хотел бы подчеркнуть,
что мы рассматриваем только формирование ядра Linux/x86.
Когда пользователь вводит 'make zImage' или 'make bzImage' создаваемый в
результате самозагружающийся ядерный образ будет сохранен как
Мы увидим позже, где это ограничение происходит.
Верхнее ограничение размера bzImage около 2.5M для загрузки с LILO и
0xFFFF параграфов (0xFFFF0=1048560 байт) для загрузки raw image, например, с
дискеты или CD-ROM.
Обратите внимание, что, в то время как инструментальные средства
tools/build утверждают размер загрузочного сектора, ядерного
изображения и блока настройки, это не проверяет верхний предел кода
настройки. Следовательно можно легко сформировать ошибочное ядро, добавив
большой ".space" в конце Детали процесса начальной загрузки специфичны для архитектуры, так что мы
сосредоточим наше внимание на IBM PC/IA32. Из-за старого проекта и обратной
совместимости, программируемое оборудование PC загружает операционную систему
старомодным способом. Этот процесс может разделиться на следующие шесть
логических стадий:
Загрузочный сектор (bootsector), используемый, чтобы загрузить ядро Linux,
может быть таким:
Мы подробно рассматриваем здесь загрузочный сектор Linux. Первые его
строки инициализируют простые и удобные макрокоманды, которые нужно
использовать для значений сегмента:
Числа слева представляют собой номера строк из файла bootsect.S. Значения
Теперь рассмотрим фактический код Строки 54-63 перемещают код загрузочного сектора с адреса 0x7C00 на новый
адрес 0x90000. Это достигается так:
Причина того, почему этот код не использует Строка 64 обходит метку Строки 77-103 исправляют дисковую таблицу параметров для первого диска,
чтобы использовать многосекторное чтение:
Контроллер гибкого диска сброшен, используя обслуживание BIOS int 0x13
(функция 0, reset FDC), и сектора установки загружены сразу после кода
загрузочного сектора, то есть в физическом адресе 0x90200 ($INITSEG:0x200),
снова используя сервис BIOS int 0x13, функция 2 (read sector(s)). Эти
действия реализованы строками 107-124:
При загрузке setup_sects секторов кода настройки обходится метка
Затем мы продолжаем чтение данных, чтобы загрузить сжатый образ ядра на
адрес 0x10000. Это выполнено, чтобы сохранить микропрограммные области
данных в нижней памяти (0-64K). После того, как ядро загружено, мы переходим
на $SETUPSEG:0 ( Обратите внимание, что старые загрузчики (прежние версии LILO) могут
загружать только первые 4 сектора кода настройки, а код из них должен сам
подкачать все остальное, если нужно. Кроме того, этот код должен заботиться о
различных комбинациях типа и версии загрузчика, правильной обработке
zImage/bzImage и прочим, так что он довольно сложен.
В качестве примера рассмотрим хитрость, позволяющую загрузчику выполнять
загрузку больших ядер, известных как bzImage. Установщик загружается как
обычно, в адреса с 0x90200, а ядро, с помощью специальной вспомогательной
процедуры, вызывающей BIOS для перемещения данных из нижней памяти в верхнюю,
загружается блоками по 64К. Эта процедура определена в Специализированные загрузчики (например, LILO) имеют ряд преимуществ перед
чисто Linux-загрузчиком (bootsector):
Старые версии LILO (v17 и более ранние) не в состоянии загрузить ядро
bzImage. Более новые версии (не старше 2-3 лет) используют ту же методику,
что и bootsect+setup, для перемещения данных из нижней в верхнюю память
посредством функций BIOS. Отдельные разработчики (особенно Peter Anvin)
выступают за отказ от поддержки ядер zImage. Тем не менее, поддержка zImage
остается в основном из-за (согласно Alan Cox) существования некоторых BIOS,
которые не могут загружать ядра bzImage, в то время как zImage грузятся
ими без проблем.
В заключение, LILO передает управление Под инициализацией высокого уровня следует понимать действия,
непосредственно не связанные с начальной загрузкой, даже несмотря на то, что
часть кода, выполняющая ее, написана на ассемблере, а именно в файле
Функция Важно обратить внимание, что на Еще стоит отметить способность Linux выполнять альтернативные программы
init посредством прохождения параметра загрузки init=. Это полезно для
ремонта поврежденного /sbin/init или отладки инициализационных
скриптов (rc) и На SMP BP проходит нормальную последовательность загрузки пока не
достигает шага Загружаемый CPU создает копию кода трамплина для каждого CPU в нижней
памяти. Код AP пишет волшебный номер в собственном коде, который проверен BP,
чтобы удостовериться, что AP выполняет код трамплина. Требование, чтобы код
трамплина был в нижней памяти, предписано спецификацией Intel MP.
Код трамплина просто устанавливает регистр %bx в 1, вводит защищенный
режим и переходит к startup_32, который является основным входом в
Теперь AP начинает выполнять Обратите внимание, что init_task может быть разделен, но каждый неактивный
поток должен иметь собственный TSS. Это то, почему
Когда операционная система инициализирует себя, большинство кода и
структур данных никогда не понадобятся ей снова. Большинство операционных
систем (BSD, FreeBSD и т. д.) не могут распорядиться этой ненужной
информацией, таким образом тратя впустую драгоценную физическую ядерную
память. Оправдание, которое они используют (см., например, McKusick's 4.4BSD
book) таково: релевантный код распространен вокруг различных подсистем так,
что невозможно освободить его. Linux, конечно, не может использовать такие
оправдания потому, что под Linux действует другое правило: если что-то
возможно в принципе, значит это уже выполнено, или кто-то работает над этим.
Как я сказал ранее, ядро Linux может компилироваться только как ELF, и
теперь мы выясним причину (или одну из причин) этого. Причина в том, чтобы
избавиться от кода и данных инициализации. Linux обеспечивает две
макрокоманды, которые нужно использовать:
Они оценивают спецификаторы атрибутов gcc (также известные как gcc magic)
как определено в файле Это означает, что, если код компилируется статически в ядро (то есть,
MODULE не определен), то это будет помещено в специальный раздел ELF
Что случается в течение начальной загрузки? Поток ядра init (функция
На типичной системе (мое автоматизированное рабочее место), это кончается
освобождением приблизительно 260K памяти.
Функции, зарегистрированные через Имеются еще две макрокоманды, которые работают подобным способом, вызывая
Рассмотрим то, что случается с командной строкой, переданной
ядру в течение начальной загрузки:
Как писать код для обработки командной строки? Мы используем макрокоманду
Вы обычно использовали бы это в Вашем коде примерно так (образец взят из
кода реального драйвера, BusLogic HBA Обратите внимание, что
1. Загрузка
1.1 Создание образа ядра Linux
arch/i386/boot/zImage
или
arch/i386/boot/bzImage
соответственно. Как создается образ:
Размер загрузочного сектора всегда равен 512 байт. Размер установки должен
быть больше, чем 4 сектора, но ограничен примерно 12 КБ: 0x4000 байт>=512+
setup_sects*512+участок памяти для стека.
vmlinux
, который является статически связанным, non-stripped ELF
32-bit LSB 80386 исполняемым файлом.System.map
создается с помощью команды nm vmlinux,
несоответствующие или неинтересные символы вырезаются.arch/i386/boot
.bootsect.S
обрабатывается с опредением -D__BIG_KERNEL__ или без него, в
зависимости от того, является ли результат bzImage или zImage. После этого
получается файл bbootsect.s
или bootsect.s
.bbootsect.s
будет собран и затем преобразован в формат 'raw
binary' под именем bbootsect
(для bootsect.s
все так же, только результат называется bootsect
).setup.S
(setup.S
включая
video.S
) преобразуется в bsetup.s
для bzImage или в
setup.s
для zImage. Таким же образом, как и разница в коде
загрузчика, здесь разница помечается опредением -D__BIG_KERNEL__ для
bzImage. Результат затем будет преобразован в форму 'raw binary' под именем
bsetup
.arch/i386/boot/compressed
и
/usr/src/linux/vmlinux
конвертируется в $tmppiggy (имя
временного файла) в формате raw binary, удалением секций .note
и
.comment
из ELF.piggy.o
.head.S
и
misc.c
(все еще в каталоге
arch/i386/boot/compressed
) в объекты ELF head.o
и
misc.o
.head.o
, misc.o
и
piggy.o
в bvmlinux
(или в vmlinux
для
zImage, не перепутайте это с /usr/src/linux/vmlinux
!). Обратите
внимание на различие между параметром -Ttext 0x1000, используемом для
vmlinux
и -Ttext 0x100000 для bvmlinux
, то
есть для образа bzImage загрузчик располагается выше.bvmlinux
в 'raw binary'
bvmlinux.out
через удаление секций ELF .note
и
.comment
.arch/i386/boot
и, используя
инструментальные средства tools/build, склеиваются вместе
bbootsect
, bsetup
и
compressed/bvmlinux.out
в файл bzImage
(для
zImage
все так же). Это пишет важные переменные, например,
setup_sects
и root_dev
в конец загрузочного
сектора.setup.S
.
1.2 Загрузка: обзор
1.3 Загрузка: BIOS POST
1.4 Загрузка: bootsector и настройка
arch/i386/boot/bootsect.S
).
29 SETUPSECS = 4 /* default nr of setup-sectors */
30 BOOTSEG = 0x07C0 /* original address of boot-sector */
31 INITSEG = DEF_INITSEG /* we move boot here - out of the way */
32 SETUPSEG = DEF_SETUPSEG /* setup starts here */
33 SYSSEG = DEF_SYSSEG /* system loaded at 0x10000 (65536) */
34 SYSSIZE = DEF_SYSSIZE /* system size: # of 16-byte clicks */
DEF_INITSEG
, DEF_SETUPSEG
, DEF_SYSSEG
и DEF_SYSSIZE
принимаются из include/asm/boot.h
:
/* Don't touch these, unless you really know what you're doing. */
#define DEF_INITSEG 0x9000
#define DEF_SYSSEG 0x1000
#define DEF_SETUPSEG 0x9020
#define DEF_SYSSIZE 0x7F00
bootsect.S
:
54 movw $BOOTSEG, %ax
55 movw %ax, %ds
56 movw $INITSEG, %ax
57 movw %ax, %es
58 movw $256, %cx
59 subw %si, %si
60 subw %di, %di
61 cld
62 rep
63 movsw
64 ljmp $INITSEG, $go
65 # bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde). We
66 # wouldn't have to worry about this if we checked the top of memory. Also
67 # my BIOS can be configured to put the wini drive tables in high memory
68 # instead of in the vector table. The old stack might have clobbered the
69 # drive table.
70 go: movw $0x4000-12, %di # 0x4000 is an arbitrary value >=
71 # length of bootsect + length of
72 # setup + room for stack;
73 # 12 is disk parm size.
74 movw %ax, %ds # ax and es already contain INITSEG
75 movw %ax, %ss
76 movw %di, %sp # put stack at INITSEG:0x4000-12.
rep movsd
сводится к определению .code16).
go:
в недавно сделанной копии
загрузочного сектора, то есть в сегменте 0x9000. Эта и следующие три команды
(строки 64-76) готовят стек в $INITSEG:0x4000-12, то есть %ss=$INITSEG
(0x9000) и %sp=0x3FEE (0x4000-12). Это причина ограничения размера установки
ядра, о котором я писал выше.
77 # Many BIOS's default disk parameter tables will not recognise
78 # multi-sector reads beyond the maximum sector number specified
79 # in the default diskette parameter tables - this may mean 7
80 # sectors in some cases.
81 #
82 # Since single sector reads are slow and out of the question,
83 # we must take care of this by creating new parameter tables
84 # (for the first disk) in RAM. We will set the maximum sector
85 # count to 36 - the most we will encounter on an ED 2.88.
86 #
87 # High doesn't hurt. Low does.
88 #
89 # Segments are as follows: ds = es = ss = cs - INITSEG, fs = 0,
90 # and gs is unused.
91 movw %cx, %fs # set fs to 0
92 movw $0x78, %bx # fs:bx is parameter table address
93 pushw %ds
94 ldsw %fs:(%bx), %si # ds:si is source
95 movb $6, %cl # copy 12 bytes
96 pushw %di # di = 0x4000-12.
97 rep # don't need cld -> done on line 66
98 movsw
99 popw %di
100 popw %ds
101 movb $36, 0x4(%di) # patch sector count
102 movw %di, %fs:(%bx)
103 movw %es, %fs:2(%bx)
При неудачной по каким-то причинам загрузке (плохая дискета, или кто-то ее
вынул) возвращается код ошибки и попытка загрузки повторяется бесконечно.
Единственный способ выйим из этого состоит в том, чтобы перезагрузить машину.
107 load_setup:
108 xorb %ah, %ah # reset FDC
109 xorb %dl, %dl
110 int $0x13
111 xorw %dx, %dx # drive 0, head 0
112 movb $0x02, %cl # sector 2, track 0
113 movw $0x0200, %bx # address = 512, in INITSEG
114 movb $0x02, %ah # service 2, "read sector(s)"
115 movb setup_sects, %al # (assume all on head 0, track 0)
116 int $0x13 # read it
117 jnc ok_load_setup # ok - continue
118 pushw %ax # dump error code
119 call print_nl
120 movw %sp, %bp
121 call print_hex
122 popw %ax
123 jmp load_setup
124 ok_load_setup:
ok_load_setup:
.
arch/i386/boot/setup.S
). Как только данные
больше не требуются (то есть, не будет впредь обращений к BIOS), весь код
загрузчика будет стерт перемещением сжатого образа ядра с физического адреса
0x10000 на 0x1000. Это выполнено setup.S
, который устанавливает
многие вещи для защищенного режима и переходит к адресу 0x1000, который
является началом сжатого ядра, то есть
arch/386/boot/compressed/{head.S,misc.c}
. Это устанавливает стек
и вызывает функцию decompress_kernel()
, которая распаковывает
ядро на адрес 0x100000 и переходит к нему.
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 в качестве загрузчика
setup.S
, а далее
загрузка продолжается как обычно.
1.6 Инициализация высокого уровня
arch/i386/kernel/head.S
, который является началом расжатого
ядра. При инициализации выполняются следующие действия:
start_kernel()
, все остальные вызовут
arch/i386/kernel/smpboot.c:initialize_secondary()
, если
переменная ready=1, это только переустанавливает esp/eip и ничего не вернет.
init/main.c:start_kernel()
написана на C и
выполняет следующие действия:
kmem_cache_init()
запускает распределитель памяти.mem_init()
вычисляет параметры max_mapnr
,
totalram_pages
и high_memory
на основании чего
печатает строку "Memory: ... ".kmem_cache_sizes_init()
завершает
запуск распределителя памяти.fork_init()
созданный uid_cache
, запускает
max_threads
, исходя из доступного объема памяти, и конфигурирует
RLIMIT_NPROC
для init_task
, чтобы он был равен
max_threads/2
.init()
, который
выполняет execute_command, заданную параметром загрузки init=, или пробует
выполнить процессы /sbin/init, /etc/init, /bin/init,
/bin/sh (именно в этом порядке). Если ничего не получилось, выдается
предложение применить параметр init=.init()
ядерный поток вызывает
do_basic_setup()
, который в свою очередь вызывает
do_initcalls()
, который проходит список функций,
зарегистрированных посредством макросов __initcall
или
module_init()
и вызывает их. Эти функции или не зависят друг от
друга, или их зависимости были вручную фиксированы порядком связи в Makefile.
Это означает, что, в зависимости от позиции каталогов в деревьях и структуре
Makefile, порядок, в котором функции инициализации вызываются, может
изменяться. Иногда это важно потому, что Вы можете представить две подсистемы
А и B, причем B работает в зависимости от некоторой инициализации,
выполненной A. Если A компилируется статически, и B является модулем, то
точка входа B вызовется после подготовки всей необходимой среды. Если А
модуль, то B также обязательно модуль, так что не имеется никаких проблем. Но
что, если А и B статически связаны в ядре? Порядок, в котором они вызываются,
зависит от относительных смещений точки входа в ELF-секции
.initcall.init
образа ядра. Rogier Wolff предложил представить
иерархическую "приоритетную" инфраструктуру, посредством чего модули могли бы
позволять компоновщику выяснить, в каком относительном порядке они должны
быть скомпонованы, но пока не имеется никаких заплат, которые выполняют это
достаточно изящным способом. Следовательно, удостоверьтесь, что Ваш порядок
связи правилен. Если, в примере выше, А и B работают прекрасно, когда
компилируются статически, они будут всегда работать, если они перечислены
последовательно в том же самом Makefile. Если они не работают, измените
порядок, в котором их объектные файлы были перечислены.
/etc/inittab
вручную.
1.7 SMP-загрузка на x86
start_kernel()
, а затем выполняет
smp_init()
и
src/i386/kernel/smpboot.c:smp_boot_cpus()
. Функция
smp_boot_cpus()
входит в цикл для каждого apicid (пока не
достигнут NR_CPUS
) и вызывает do_boot_cpu()
. Здесь
do_boot_cpu()
создает (то есть, fork_by_hand
)
неактивную задачу для целевого процессора и пишет в известных расположениях,
определенных спецификацией Intel MP (0x467/0x469), EIP кода трамплина,
найденный в trampoline.S
. Затем это генерирует STARTUP IPI на
целевой процессор, который заставляет AP выполнить код в
trampoline.S
.
arch/i386/kernel/head.S
.
head.S
и обнаруживает, что это
не BP, так что это пропускает код, который очищает BSS, а затем вводит
initialize_secondary()
, который только создает неактивную задачу
для этого CPU: выбирает, что init_tasks[cpu]
был уже
инициализирован BP, выполняющим do_boot_cpu(cpu)
.
init_tss[NR_CPUS]
представляет собой массив.
1.8 Освобождение кода и данных инициализации
__init
для кода инициализации и__initdata
для данных.include/linux/init.h
:
#ifndef MODULE
#define __init __attribute__ ((__section__ (".text.init")))
#define __initdata __attribute__ ((__section__ (".data.init")))
#else
#define __init
#define __initdata
#endif
.text.init
, который объявлен в карте компоновщика в файле
arch/i386/vmlinux.lds
. Иначе (то есть, если это модуль),
макрокоманды ничего не дадут.
init/main.c:init()
) вызывает архитектурно-специфическую функцию
free_initmem()
, которая освобождает все страницы между адресами
__init_begin
и __init_end
.
module_init()
, помещены в
.initcall.init
, который также освобожден в статическом случае.
Текущая тенденция в Linux: при проектировании подсистемы (не обязательно
модулей), следует обеспечить точки входа и выхода на ранних стадиях проекта
так, чтобы в будущем рассматриваемая подсистема могла быть переделана в
модуль, если это понадобится. Пример этого: pipefs, см. файл
fs/pipe.c
. Даже если данная подсистема никогда не станет
модулем, например, bdflush (см. fs/buffer.c
), это все еще хорошо
и опрятно, чтобы использовать макрокоманду module_init()
вместо
функции инициализации при вызове функции.
__exit
и __exitdata
, но они более непосредственно
связаны с поддержкой модулей, а, следовательно, будут объясняться в
более позднем разделе.
1.9 Обработка ядерной командной строки
arch/i386/kernel/head.S
копирует первые 2k этого блока
данных в zeropage. Обратите внимание, что текущая версия (21) LILO прерывает
командную строку после 79 байт. Это нетривиальная ошибка в LILO (она
происходит, когда допускается поддержка большого EBDA), и Werner обещал это
когда-нибудь исправить. Если Вы действительно должны передать командные
строки длиннее 79 байтов, то Вы можете использовать BCP или жестко вписать
Вашу командную строку в функцию
arch/i386/kernel/setup.c:parse_mem_cmdline()
.arch/i386/kernel/setup.c:parse_mem_cmdline()
(вызывается
setup_arch()
, которая непосредственно вызвана из
start_kernel()
) копирует 256 байт из zeropage в
saved_command_line
, которая отображается в
/proc/cmdline
. Эта же самая подпрограмма обрабатывает опцию
mem=, если она представлена, и делает соответствующие
корректировки параметров VM.parse_options()
(вызывается из start_kernel()
), которая обрабатывает некоторые
внутриядерные параметры (в настоящее время это init= и параметры для init) и
передает каждое слово checksetup()
.checksetup()
проходит код в ELF-разделе
.setup.init
и вызывает каждую функцию, передавая это слово, если
оно соответствует. Обратите внимание, что использование значения возврата 0
из функции, зарегистрированной через __setup()
, то же самое, что
передать пару "variable=value" больше, чем одной функции с value,
недопустимым в одной, и имеющим силу в другой. Почему? Потому, что это
специфический ld-порядок, то есть ядро, связанное в одном порядке, будет
иметь вызов functionA до functionB.__setup()
, определенную в файле
include/linux/init.h
:
/*
* Used for kernel command line parameter setup
*/
struct kernel_param {
const char *str;
int (*setup_func)(char *);
};
extern struct kernel_param __setup_start, __setup_end;
#ifndef MODULE
#define __setup(str, fn) \
static char __setup_str_##fn[] __initdata = str; \
static struct kernel_param __setup_##fn __initsetup = \
{ __setup_str_##fn, fn }
#else
#define __setup(str,func) /* nothing */
endif
drivers/scsi/BusLogic.c
):
static int __init
BusLogic_Setup(char *str)
{
int ints[3];
(void) get_options(str, ARRAY_SIZE(ints), ints);
if (ints[0] != 0)
{
BusLogic_Error("BusLogic: Obsolete Command Line Entry "
"Format Ignored\n", NULL);
return 0;
}
if (str == NULL || *str == '\0') return 0;
return BusLogic_ParseDriverOptions(str);
}
__setup("BusLogic=", BusLogic_Setup);
__setup()
не делает ничего для
модулей, так что хотя код, желающий обработать командную строку начальной
загрузки, может быть модулем или статически связанным, но должен вызвать
функцию синтаксического анализа вручную в подпрограмме инициализации модуля.
Это также означает, что можно писать код, который обрабатывает параметры,
когда компилируется как модуль, но не когда собран статически (или наоборот).
Вперед
Оглавление
Назад
Найди своих коллег! |