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

       

Загрузка: bootsector и setup


Для загрузки ядра Linux можно воспользоваться следующими загрузочными секторами:

  • Загрузочным сектором Linux из (arch/i386/boot/bootsect.S),
  • Загрузочный сектор LILO (или другого менеджера загрузки), или
  • обойтись без загрузочного сектора (loadlin и т.п.)
  • А теперь подробнее рассмотрим загрузочный сектор. В первых нескольких строках инициализируются вспомогательные макросы, используемые как значения сегментов:

    29 SETUPSECS = 4 /* число секторов установщика по умолчанию */ 30 BOOTSEG = 0x07C0 /* первоначальный адрес загрузочного сектора */ 31 INITSEG = DEF_INITSEG /* сюда перемещается загрузчик - чтобы не мешал */ 32 SETUPSEG = DEF_SETUPSEG /* здесь начинается установщик */ 33 SYSSEG = DEF_SYSSEG /* система загружается по адресу 0x10000 (65536) */ 34 SYSSIZE = DEF_SYSSIZE /* размер системы: в 16-байтных блоках */

    (числа в начале - это номера строк в файле bootsect.S file) Значения DEF_INITSEG, DEF_SETUPSEG, DEF_SYSSEG и DEF_SYSSIZE берутся из файла include/asm/boot.h:

    /* Ничего не меняйте, если не уверены в том, что делаете. */ #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 - 0xff00 изменено на 0x4000 для работы отладчика с 0x6400 и выше (bde). 66 # Если мы проверили верхние адреса, то об этом можно не беспокоиться. Кроме того, 67 # мой BIOS можно сконфигурировать на загрузку таблицы дисков wini в верхнюю память 68 # вместо таблицы векторов. Старый стек может "помесить" 69 # таблицу устройств [drive table].

    70 go: movw $0x4000-12, %di # 0x4000 - произвольное значение >= 71 # длины bootsect + длины 72 # setup + место для стека; 73 # 12 - размер параметров диска. 74 movw %ax, %ds # INITSEG уже в ax и es 75 movw %ax, %ss 76 movw %di, %sp # разместим стек по INITSEG:0x4000-12.




    Строки 54- 63 перемещают код начального загрузчика из адреса 0x7C00 в адрес 0x90000. Для этого:

  • в регистровую пару %ds:%si заносится значение $BOOTSEG:0 (0x7C0:0 = 0x7C00)


  • в регистровую пару %es:%di заносится значение $INITSEG:0 (0x9000:0 = 0x90000)


  • в регистр %cx записывается число 16-битовых слов (256 слов = 512 байт = 1 сектор)


  • В регистре флагов EFLAGS сбрасывается флаг направления DF (Direction Flag) (копирование с автоинкрементом адресных регистров) (cld)


  • копируется 512 байт (rep movsw)


  • Здесь умышленно не используется инструкция rep movsd (обратите внимание на директиву - .code16).

    В строке 64 выполняется переход на метку go:, в только что созданную копию загрузчика, т.е. в сегмент 0x9000. Эта, и следующие три инструкции (строки 64-76) переустанавливают регистр сегмента стека и регистр указателя стека на $INITSEG:0x4000-0xC, т.е. %ss = $INITSEG (0x9000) и %sp = 0x3FF4 (0x4000-0xC). Это и есть то самое ограничение на размер setup, которое упоминалось ранее (см. Построение образа ядра Linux).

    Для того, чтобы разрешить считывание сразу нескольких секторов (multi-sector reads), в строках 77-103 исправляются некоторые значения в таблице параметров для первого диска :

    77 # Часто в BIOS по умолчанию в таблицы параметров диска не признают 78 # чтение по несколько секторов кроме максимального числа, указанного 79 # по умолчанию в таблице параметров дискеты - что может иногда равняться 80 # 7 секторам. 81 # 82 # Поскольку чтение по одному сектору отпадает (слишком медленно), 83 # необходимо позаботиться о создании в ОЗУ новой таблицы параметров 84 # (для первого диска). Мы установим максимальное число секторов 85 # равным 36 - максимум, с которым мы столкнемся на ED 2.88. 86 # 87 # Много - не мало. А мало - плохо. 88 # 89 # Сегменты устанавливаются так: ds = es = ss = cs - INITSEG, fs = 0, 90 # а gs не используется.

    91 movw %cx, %fs # запись 0 в fs 92 movw $0x78, %bx # в fs:bx адрес таблицы 93 pushw %ds 94 ldsw %fs:(%bx), %si # из адреса ds:si 95 movb $6, %cl # копируется 12 байт 96 pushw %di # di = 0x4000-12. 97 rep # инструкция cld не нужна - выполнена в строке 66 98 movsw 99 popw %di 100 popw %ds 101 movb $36, 0x4(%di) # записывается число секторов 102 movw %di, %fs:(%bx) 103 movw %es, %fs:2(%bx)



    Контроллер НГМД переводится в исходное состояние функцией 0 прерывания 0x13 в BIOS (reset FDC) и секторы установщика загружаются непосредственно после загрузчика, т.е. в физические адреса, начиная с 0x90200 ($INITSEG:0x200), с помощью функции 2 прерывания 0x13 BIOS (read sector(s)). Смотри строки 107-124:

    107 load_setup: 108 xorb %ah, %ah # переинициализация FDC 109 xorb %dl, %dl 110 int $0x13 111 xorw %dx, %dx # диск 0, головка 0 112 movb $0x02, %cl # сектор 2, дорожка 0 113 movw $0x0200, %bx # адрес в INITSEG = 512 114 movb $0x02, %ah # функция 2, "read sector(s)" 115 movb setup_sects, %al # (все под головкой 0, на дорожке 0) 116 int $0x13 # читать 117 jnc ok_load_setup # получилось - продолжить

    118 pushw %ax # запись кода ошибки 119 call print_nl 120 movw %sp, %bp 121 call print_hex 122 popw %ax 123 jmp load_setup

    124 ok_load_setup:

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

    Если загрузка setup_sects секторов кода установщика прошла благополучно, то производится переход на метку ok_load_setup:.

    Далее производится загрузка сжатого образа ядра в физические адреса начиная с 0x10000, чтобы не затереть firmware-данные в нижних адресах памяти (0-64K). После загрузки ядра управление передается в точку $SETUPSEG:0 (arch/i386/boot/setup.S). Поскольку обращений к BIOS больше не будет, данные в нижней памяти уже не нужны, поэтому образ ядра перемещается из 0x10000 в 0x1000 (физические адреса, конечно). И наконец, установщик setup.S завершает свою работу, переводя процессор в защищенный режим и передает управление по адресу 0x1000 где находится точка входа в сжатое ядро, т.е. arch/386/boot/compressed/{head.S,misc.c}. Здесь производится установка стека и вызывается decompress_kernel(), которая декомпрессирует ядро в адреса, начиная с 0x100000, после чего управление передается туда.



    Следует отметить, что старые загрузчики (старые версии LILO) в состоянии загружать только первые 4 сектора установщика (setup), это объясняет присутствие кода, "догружающего" остальные сектора в случае необходимости. Кроме того, установщик содержит код, обрабатывающий различные комбинации типов/версий загрузчиков и zImage/bzImage.

    Теперь рассмотрим хитрость, позволяющую загрузчику выполнить загрузку "больших" ядер, известных под именем "bzImage". Установщик загружается как обычно, в адреса с 0x90200, а ядро, с помощью специальной вспомогательной процедуры, вызывающей BIOS для перемещения данных из нижней памяти в верхнюю, загружается кусками по 64К. Эта процедура определена в 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 не исчерпает нижнюю память при считывании данных с диска.


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