Эта статья -- практическое пособие по написанию кода для создания загрузочного сектора диска (boot sector). В первой ее части рассматриваются теоретические основы того, что происходит после включения питания компьютера. Заодно излагается план наших действий. Во второй части речь идет о том, что нам понадобится для того, чтобы двигаться дальше, а в третьей мы уже будем иметь дело с самой программой. Программа не будет загружать Linux, она просто покажет кое-что на экране.
Процессор -- это сердце компьютера. При включении питания, каждый микропроцессор представляет из себя всего лишь еще один 8086-й. (В этой статье речь идет об x86-совместимых процессорах. Прим.перев.). Даже если у вас самый последний Pentium, в этот момент он обладает лишь возможностями своего далекого предка. И только программно переключив процессор в малоизвестный защищенный режим, мы получим доступ ко всей его мощи.
Первоначально, контроль находится в руках BIOS (Basic Input/Outpuit System; Базовая Система Ввода-Вывода). Это набор программ, которые хранятся в ROM (Read Only Memory; ПЗУ -- программируемое запоминающее устройство). После включения питания BIOS выполняет POST (Power On Self Test). Это программа проверки целостности компьютера (проверка корректности работы памяти, клавиатуры и других подключенных к компьютеру периферийных устройств). Все это происходит в тот момент, когда вы слышите звуковой сигнал. (Если в процессе проверки обнаруживается ошибка, то компьютер опять подает звуковые сигналы, но уже другой длительностью и в иной последовательности -- в описаниях к материнским платам иногда встречаются звуковые последовательности, соответствующие той или иной неисправности. Если такого такого нет в вашем описании, то вы можете его найти на сайте производителя прошивки для BIOS. Например, сайт компании Phoenix Technologies Ltd [http://www.phoenix.com/products/specs.html] (ищите на нем pdf-файл userman.pdf). Только учтите, у них сейчас идет реконструкция, поэтому карта сайта может измениться. Прим.перев.) Если все в порядке, то BIOS выбирает загрузочное устройство. Он копирует первый сектор (boot sector) с устройства в ОЗУ по адресу 0x7C00. (Следует уточнить, что если речь идет о жестких дисках, то первый сектор называется не boot sector, а master boot record -- главная загрузочная запись или MBR. Прим.перев.) Затем управление передается по этому адресу. Загрузочным устройством может служить флоппи-диск, CD-ROM, жесткий диск или любое другое устройство по вашему выбору. В качестве такового мы воспользуемся флоппи-диском. Если записать исполняемый код в загрузочный сектор гибкого диска, то он будет выполнен при попытке загрузки с дискеты. Наша задача проста: написать небольшую программу и разместить ее в загрузочном секторе.
Для начала напишем программу на ассемблере процессора 8086 (не паникуйте; я объясню как) и скопируем ее в загрузочный сектор флоппи-диска. Для копирования мы напишем программу на C. Загрузим компьютер с дискеты и будем наслаждаться. 8-) (немножко протащимся? -- прим. редактора:)
as86 и ld86 присутствуют в большинстве стандартных дистрибутивов. Если же их у вас нет, то вы можете взять их на сайте http://www.cix.co.uk/~mayday/ . Они оба включены в один пакет -- bin86. Кое-что из документации вы можете найти здесь www.linuxdoc.org/HOWTO/Assembly-HOWTO/as86.html.
Хватайте ваш любимый редактор и наберите следующее:
entry start start: mov ax,#0xb800 mov es,ax seg es mov [0],#0x41 seg es mov [1],#0x1f loop1: jmp loop1
Это понятный as86 диалект ассемблера. Первая инструкция описывает точку входа в программу. Мы объявляем, что управление первоначально должно быть передано на метку start. Вторая строка описывает расположение этой метки (не забудьте поставить ":" после "start"). Первая инструкция, которая должна быть выполнена в этой программе, расположена сразу после нее.
0xb800 -- это адрес сегмента видеопамяти в текстовом режиме. Символ # указывает на непосредственное значение. После выполнения команды
mov ax,#0xb800
регистр ax будет содержать значение 0xb800, это адрес расположения видеопамяти. Теперь мы копируем это значение в сегментный регистр es. Помните, что 8086 имеет сегментную архитектуру. У него четыре сегментных регистра, указывающих на: сегмент кода ( CS ), сегмент данных (DS), сегмент стека ( SS ) и дополнительный сегмент (ES ). Фактически, мы сделали видеопамять нашим дополнительным сегментом, так что все записанное в него, будет записано в видеопамять.
Для отображения любого символа на экране, вам нужно записать в видеопамять два байта. Первый -- это значение ascii-кода символа, который вы хотите вывести на экран. Второй -- атрибут символа. Атрибут содержит следующую информацию: цвет символа, цвет фона, мерцание. Инструкция seg es фактически является префиксом, указывающим относительно какого сегмента должна выполняться следующая операция. Итак, мы заносим в первый байт видеопамяти (адрес 0xb800:0) значение 0x41, которое соответствует ascii-коду символу "A" (большая английская A). Затем мы должны прописать значение атрибута символа в следующем байте видеопамяти. Сюда мы записываем значение 0x1f, соответствующее белому символу на синем фоне. (Чтобы понять, где что спрятано в байте атрибута, лучше преобразовать 0x1f в двоичное представление: 00011111. Здесь первые четыре бита [справа налево] -- цвет символа, следующие три -- цвет фона и последний 7-й бит [отсчет ведется с нуля] -- это признак мерцания. Из вышесказанного можно сделать следующий вывод: в текстовом режиме символ может иметь 16 цветов, фон -- 8. Прим.перев.) Теперь, если мы выполним эту программу, то получим белую "A" на синем фоне. В конце стоит программная "петля" (команда jmp указывающая на саму себя). Нам нужно, либо остановить выполнение кода после того, как отобразим символ, либо "намертво" замкнуть цикл. Сохраните файл как boot.s .
Идея с манипуляцией видеопамятью может быть не совсем понятна, поэтому позвольте мне объяснить на будущее. Предположим мы используем экран размером 80 на 25 (80 символов в строке и 25 строк). Для каждой строки нам нужно по 160 байт: 80 байт, содержащие коды символов и столько же, содержащие их атрибуты. Если нам нужно вывести 3-й символ в 1-й строке, то мы должны пропустить байты 0 и 1, как относящиеся к первому символу; байты 2-й и 3-й, как относящиеся ко второму символу и только после этого записать в 4-м байте ascii-код выводимого символа и в 5-м -- его атрибут.
Теперь мы должны написать программу на C, которая скопирует наш код (код нашей ОС) в первый сектор дискеты. Вот она:
#include <sys/types.h> /* unistd.h needs this */ #include <unistd.h> /* contains read/write */ #include <fcntl.h> int main() { char boot_buf[512]; int floppy_desc, file_desc; file_desc = open("./boot", O_RDONLY); read(file_desc, boot_buf, 510); close(file_desc); boot_buf[510] = 0x55; boot_buf[511] = 0xaa; floppy_desc = open("/dev/fd0", O_RDWR); lseek(floppy_desc, 0, SEEK_CUR); write(floppy_desc, boot_buf, 512); close(floppy_desc); }
Сперва мы открываем файл boot в режиме только для чтения и копируем файловый дескриптор в переменную file_desc. Затем читаем первые 510 байт, либо, если файл размером меньше 510 байт, читаем весь файл. Наш код невелик, поэтому последний случай наш. Будьте паинькой -- не забудьте закрыть файл. 8-)
Последние четыре строки кода открывают устройство флоппи-диска (которым, как правило, является /dev/fd0). Затем переводим указатель в начало файла, используя lseek и записываем 512 байт из буфера на дискету.
Страницы справочного руководства функций read, write, open и lseek (смотрите man 2) дадут вам достаточно информации о том, что обозначают другие параметры функций и как их использовать. Есть две строки, которые выглядят немного таинственно. Вот они:
boot_buf[510] = 0x55; boot_buf[511] = 0xaa;
Это информация для BIOS. Если предполагается, что BIOS должна распознать устройство как загрузочное, то устройство должно содержать значения 0x55 и 0xaa, расположенные по смещениям 510 и 511. (Не берусь утверждать на все 100%, но раньше [по слухам], во времена MS DOS 2.0 и ниже, этой сигнатуры не было вообще. Она появилась позднее, как ответ на загрузочный вирус ( помните такие? -- прим. ред.), который при помощи этой сигнатуры проверял , заражал ли он этот компьютер или нет. Если нет, то вирус делал свое "черное дело" и "приписывал" эти два байта. Прим.перев.). Теперь почти все. Программа читает файл boot в буфер boot_buf. Делает нужные изменения в 510 и 511 байтах и записывает boot_buf на флоппи-диск. Сохраним файл как write.c .
Для создания исполняемых файлов вам нужно выполнить следующие команды:
as86 boot.s -o boot.o ld86 -d boot.o -o boot cc write.c -o write
Сперва, мы компилируем объектный файл boot.o из boot.s . Затем конвертируем его в двоичный, boot . Ключ -d заставляет компоновщик ld86 удалить все заголовки и создать "голый" двоичный файл. Если у вас возникли сомнения или появились неясности в этом вопросе, прочтите страницы справочного руководства по as86 и ld86. Последним мы компилируем C-программу и получаем исполняемый файл write.
Вставьте пустую дискету в дисковод и наберите команду (Убедитесь, что у вас есть права на запись в /dev/fd0. И вообще, никто вам не мешает использовать для тех же целей команду dd [dd if=boot of=/dev/fd0 ]. или команду копирования cp [cp boot /dev/fd0 ]. Возможно, автор планирует расширить возможности этой программы для дальнейшего использования в следующих статьях. Прим.перев.):
./write
Перезагрузите машину. Настройте BIOS так, чтобы система грузилась в первую очередь с дискеты. Вставьте дискету в дисковод и дождитесь, пока компьютер загрузится с нее.
Теперь вы можете видеть символ 'A' (белого цвета на синем фоне). Это означает, что программа, которую мы написали и скопировали в загрузочный сектор, была загружена с дискеты и выполнена. Теперь она находится в бесконечном "программном" цикле в конце кода загрузчика. Чтобы вернуться в привычную среду обитания (читай -- Linux 8-) нужно перезагрузить компьютер, предварительно удалив дискету из дисковода.
В дальнейшем, мы сможем вставлять больше кода в нашу программу загрузки, заставляя ее делать более сложные вещи (используя прерывания BIOS, переключение в защищенный режим и прочее.) Следующие части (вторая, третья и пр.) этой статьи станут вашими проводниками на пути усовершенствования кода нашего загрузчика. До встречи!
Кришнакумар -- студент последнего курса B.Tech в Govt. Engg. College Thrissur, Kerala, Индия. Его путешествие в земли Операционных Систем началось с программирования модулей для Linux. Он создал операционную систему GROS, основная цель которой -- выполнение функции маршрутизатора. (Детали вы можете найти на его домашней странице: www.askus.way.to ) Другие его интересы -- это сетевые драйвера, драйвера устройств, портирование компиляторов и встроенные системы (Compiler Porting and Embedded systems).
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван
Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр
Куприн
Со всеми предложениями, идеями и комментариями обращайтесь к Сергею Скороходову ([email protected]). Убедительная просьба: указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в рассылке.
Сайт рассылки: http://gazette.linux.ru.net
Эту статью
можно взять здесь: http://gazette.linux.ru.net/lg77/articles/rus-krishnakumar.html
В предыдущий выпуск вкралась ошибка. Исправляюсь. Вот diff для того, чтобы быть в стиле:)
--- toy-os.html.old Thu May 02 15:13:12 2002 +++ toy-os.html Thu May 02 15:12:15 2002 @@ -291,7 +291,7 @@ class="sdfootnotesym"></a><sup><u><a href="#sdfootnote1anc">1</a></u></sup> На <a href="http://www.linuxgazette.com">Linux Gazette</a> на эту тему была лишь небольшая статья Константина Болдышева "Introduction to UNIX - Assembly Programming". Issue #52 апрель 2000г.</div> + Assembly Programming". Issue #53, май 2000г.</div> </div> <div id="sdfootnote2"> <div class="sdfootnote"><a name="sdfootnote2sym" href="#sdfootnote2anc"