Перспективы создания ОС 3
Продолжение. Начало в КГ №№ 7-9
Здравствуйте, уважаемые читатели! На дворе творятся абсолютные аномалии: с неба все время что-то капает, термометр на автозаправке, через которую я хожу каждый день, показывает три градуса ниже нуля, когда на самом деле плюс пять… Но энтузиасты всегда видят в чем-то свои преимущества, не так ли? Допустим, нынешнее потепление может свидетельствовать о скором приближении долгожданной весны. Вот поэтому мы сейчас продолжим делать нашу мини-ОС, оснастив ее некоторыми дополнительными функциями.
Прежде всего, разберем почту. Первое, что хотелось бы заметить: не надо в исходном коде записывать процедуры выше основного кода. Наш FASM транслирует команды по принципу "что вижу, то и транслирую". Поэтому, если вы объявите процедуры перед основным кодом, исполняться начнут процедуры, и после первого же исполнения команды RET наша система улетит неизвестно куда. Когда вы получили бинарный файл с кодом в 512 байт, необходимо закинуть его на дискету с помощью программы rawwrite или подобных ей. Кстати, прошу прощения за небольшую погрешность в прошлой статье. BIOS записывает загрузочный сектор по адресу 0x7C00:0x0000, но не в адрес 0x0000:0x7C00, как я написал в предыдущий раз. Что же мы сегодня сделаем? Мы попытаемся вывести некоторый текст, записанный на дискету, на экран. Естественно, сильно разогнаться в плане комфортабельности мы не сможем, а посему поместим текст сразу после 512 байт нашего загрузочного сектора. Нам понадобится прерывание BIOS под номером 0x13 для оперирования диском. Номер функции равен 0x2. Какие параметры нам понадобятся для выполнения этой функции? Вот их полное перечисление в соответствии с регистрами:
AH — 0x2 (номер функции);
AL — количество секторов для чтения;
CH — номер цилиндра;
CL — номер первого сектора;
DH — номер головки;
DL — номер диска;
ES:BX — адрес назначения.
Определим некоторые термины, применяющиеся в данном случае. Сектор — это некоторая область диска фиксированного размера. Она может быть равна 128, 256, 512 или 1024 байта. Цилиндр — это, скажем так, плоскость, на которой расположены дорожки диска. Головка (или магнитная головка) — небольшое устройство, непосредственно производящее чтение с диска. В данном случае этот параметр должен быть всегда равен единице, т.к. в дисководе есть только одна головка, производящая чтение и запись на диск. Номер диска определяется следующим образом: если мы обращаемся к дисководу, то номер диска должен находиться в диапазоне 0x0-0x3. Если же мы обращаемся к жесткому диску, то нумерация дисков начинается с 0x80. Попробуем написать процедуру, выводящую текст на экран. Сперва решим, как нам надо передавать параметры. Т.к. параметров нам придется передавать целых три штуки, то реализуем для них отдельные переменные, находящиеся вне 512 байт загрузчика. Тут нам на помощь придут возможности FASM'а. Для начала спустимся в самый нижний участок кода, где у нас определена сигнатура 0x55AA, и под ней добавим метку virtual_space. После этого напишем следующую строку: virtual at virtual_space. Данная функция позволит нам не мучиться с адресами, а организовывать доступ к переменным, которые на самом деле находятся вне 512 байт загрузчика. Далее все стандартно. Определяем три переменные для передачи параметров функции:
sectorsc db ?
cylinder db ?
stsector db ?
Теперь надо закончить блок следующей строкой: end virtual. Все. Теперь начиная с адреса 0x7C00:0x0200 находятся наши три переменные. Пишем функцию для чтения дискеты. Сперва лучше определить адрес, в который нам надо поместить прочтенный код. Начало видеобуфера в данной ситуации расположено по адресу 0xB800:0x0000. Занесем в регистр AX значение 0xB8000, а затем командой MOV скопируем это значение в регистр ES, т.к. напрямую поместить значение не получится. Регистр BX обнуляем. Теперь определяем в EH номер функции, заносим в регистры AL, CH и CL значения sectorsc, cylinder и stsector соответственно. Теперь обнулим регистр DX. Почему? Потому, что головка в дисководе только одна, и она имеет номер 0, и дисковод, чей номер отражается в DL, имеет номер 0. Теперь просто вызываем прерывание 0x13. Не забудьте добавить в конце процедуры команду RET, чтобы управление перешло к коду, вызвавшему эту процедуру. Вот мой код:
read_fdd:
mov ax, 0xB800
mov es, ax
mov bx, 0x0000
mov ah, 0x2
mov al, byte [sectorsc]
mov ch, byte [cylinder]
mov cl, byte [stsector]
mov dh, byte [head]
xor dx, dx
int 0x13
ret
И в конце кода:
virtual_space:
virtual at virtual_space
sectorsc db ?
cylinder db ?
stsector db ?
end virtual
Осталось только дать понять операционной системе, что мы от нее хотим. Для этого заходим в блок main_os_loop и после строки je os_reboot пишем следующее:
cmp al, 'P'
je print_it
Если мы в командной строке вводим букву P, то управление переходит к процедуре, которая запрашивает у пользователя параметры, в которых мы нуждаемся, и собственно печатает текст на экране. Естественно, процедуры под названием print_it у нас еще нет. Напишем ее. Маленькое отступление. Для того, чтобы наша ОС еще и радовала глаз пользователя, я написал малюсенькую процедуру, выводящую на экран в режиме TTY пробел. Конечно, это не самое важное здесь, однако пока что пускай будет. Вот ее код:
print_space:
mov al, ' '
call writechar
ret
Теперь перейдем непосредственно к нашей процедуре. Для начала выполним вызов процедуры print_space, что, как вы понимаете, абсолютно необязательно. Затем три раза вызываем процедуру чтения номера (readnum) и каждый раз помещаем результат в переменные cylinder, stsector и sectorsc соответственно. Все параметры есть, теперь вызываем функцию read_fdd. В соответствии с параметрами она читает дискету и выводит содержимое на экран. Для проверки в самом конце кода введем одну переменную с произвольным текстом, допустим, так: db "Welcome to the OS!". Внимание: если хотите, отложите газету и подумайте, почему мой совет в предыдущем предложении был концептуально неверным (а он действительно неверен). Если не нашли ошибки (и если нашли тоже:)), читайте дальше. Текст в командной строке ОС должен быть следующим: > P 0 2 1. То есть это обозначает номер цилиндра 0, номер первого сектора 2 и количество секторов 1. Запустили? Я не слышу оваций. А их и не должно быть. Ошибка в объявлении текста была следующей. Как вы поняли, содержимое дискеты читается напрямую в буфер экрана. А буфер сконструирован так, что он забивается не только символами, но и их атрибутами. При этом байт символа идет первым, а байт атрибута — вторым. И так по очереди: символ, атрибут, символ, атрибут… Так что неудивительно, что на экран выводились только нечетные символы, да и то в самом экзотическом оформлении. Исправлять это придется путем добавления пробела после каждого символа. Впрочем, это нам мало поможет, т.к. код пробела имеет сканкод 0x20. Если его представить в двоичном счислении, то мы получим следующее: 00100000, что свидетельствует о зеленом цвете фона и черном цвете текста. Нормальной записи текста на дискету для нашей ОС я попытаюсь посвятить целую статью. А пока что разбирайтесь с этим кодом, присылайте свои идеи и комментарии.
Небольшое послесловие. Код, приведенный в прошлой статье, можно оптимизировать следующим образом:
1. В процедуре смены графического режима заменить команды xor ah, ah и mov al, 3 на одну: mov ax, 3.
2. В процедуре writestr заменить операцию dec bl на dec bx и заменить cmp bl, 0 на or bl, bl.
Таким образом мы сэкономим 3 байта. Спасибо за это замечание Сергею Павлюковичу. Кстати, немного позже я сделаю нормальный исходник (текущий у меня ужасно заляпан комментариями и разным хламом) и выложу его на сайт.
На этом позвольте попрощаться. Попробуйте своими силами сделать возможной загрузку программ с дискеты. Здесь все аналогично, только надо будет сделать переход на загруженный код. Присылайте свои варианты реализации.
Влад Маслаков, Vladislav_1988@mail.ru
Здравствуйте, уважаемые читатели! На дворе творятся абсолютные аномалии: с неба все время что-то капает, термометр на автозаправке, через которую я хожу каждый день, показывает три градуса ниже нуля, когда на самом деле плюс пять… Но энтузиасты всегда видят в чем-то свои преимущества, не так ли? Допустим, нынешнее потепление может свидетельствовать о скором приближении долгожданной весны. Вот поэтому мы сейчас продолжим делать нашу мини-ОС, оснастив ее некоторыми дополнительными функциями.
Прежде всего, разберем почту. Первое, что хотелось бы заметить: не надо в исходном коде записывать процедуры выше основного кода. Наш FASM транслирует команды по принципу "что вижу, то и транслирую". Поэтому, если вы объявите процедуры перед основным кодом, исполняться начнут процедуры, и после первого же исполнения команды RET наша система улетит неизвестно куда. Когда вы получили бинарный файл с кодом в 512 байт, необходимо закинуть его на дискету с помощью программы rawwrite или подобных ей. Кстати, прошу прощения за небольшую погрешность в прошлой статье. BIOS записывает загрузочный сектор по адресу 0x7C00:0x0000, но не в адрес 0x0000:0x7C00, как я написал в предыдущий раз. Что же мы сегодня сделаем? Мы попытаемся вывести некоторый текст, записанный на дискету, на экран. Естественно, сильно разогнаться в плане комфортабельности мы не сможем, а посему поместим текст сразу после 512 байт нашего загрузочного сектора. Нам понадобится прерывание BIOS под номером 0x13 для оперирования диском. Номер функции равен 0x2. Какие параметры нам понадобятся для выполнения этой функции? Вот их полное перечисление в соответствии с регистрами:
AH — 0x2 (номер функции);
AL — количество секторов для чтения;
CH — номер цилиндра;
CL — номер первого сектора;
DH — номер головки;
DL — номер диска;
ES:BX — адрес назначения.
Определим некоторые термины, применяющиеся в данном случае. Сектор — это некоторая область диска фиксированного размера. Она может быть равна 128, 256, 512 или 1024 байта. Цилиндр — это, скажем так, плоскость, на которой расположены дорожки диска. Головка (или магнитная головка) — небольшое устройство, непосредственно производящее чтение с диска. В данном случае этот параметр должен быть всегда равен единице, т.к. в дисководе есть только одна головка, производящая чтение и запись на диск. Номер диска определяется следующим образом: если мы обращаемся к дисководу, то номер диска должен находиться в диапазоне 0x0-0x3. Если же мы обращаемся к жесткому диску, то нумерация дисков начинается с 0x80. Попробуем написать процедуру, выводящую текст на экран. Сперва решим, как нам надо передавать параметры. Т.к. параметров нам придется передавать целых три штуки, то реализуем для них отдельные переменные, находящиеся вне 512 байт загрузчика. Тут нам на помощь придут возможности FASM'а. Для начала спустимся в самый нижний участок кода, где у нас определена сигнатура 0x55AA, и под ней добавим метку virtual_space. После этого напишем следующую строку: virtual at virtual_space. Данная функция позволит нам не мучиться с адресами, а организовывать доступ к переменным, которые на самом деле находятся вне 512 байт загрузчика. Далее все стандартно. Определяем три переменные для передачи параметров функции:
sectorsc db ?
cylinder db ?
stsector db ?
Теперь надо закончить блок следующей строкой: end virtual. Все. Теперь начиная с адреса 0x7C00:0x0200 находятся наши три переменные. Пишем функцию для чтения дискеты. Сперва лучше определить адрес, в который нам надо поместить прочтенный код. Начало видеобуфера в данной ситуации расположено по адресу 0xB800:0x0000. Занесем в регистр AX значение 0xB8000, а затем командой MOV скопируем это значение в регистр ES, т.к. напрямую поместить значение не получится. Регистр BX обнуляем. Теперь определяем в EH номер функции, заносим в регистры AL, CH и CL значения sectorsc, cylinder и stsector соответственно. Теперь обнулим регистр DX. Почему? Потому, что головка в дисководе только одна, и она имеет номер 0, и дисковод, чей номер отражается в DL, имеет номер 0. Теперь просто вызываем прерывание 0x13. Не забудьте добавить в конце процедуры команду RET, чтобы управление перешло к коду, вызвавшему эту процедуру. Вот мой код:
read_fdd:
mov ax, 0xB800
mov es, ax
mov bx, 0x0000
mov ah, 0x2
mov al, byte [sectorsc]
mov ch, byte [cylinder]
mov cl, byte [stsector]
mov dh, byte [head]
xor dx, dx
int 0x13
ret
И в конце кода:
virtual_space:
virtual at virtual_space
sectorsc db ?
cylinder db ?
stsector db ?
end virtual
Осталось только дать понять операционной системе, что мы от нее хотим. Для этого заходим в блок main_os_loop и после строки je os_reboot пишем следующее:
cmp al, 'P'
je print_it
Если мы в командной строке вводим букву P, то управление переходит к процедуре, которая запрашивает у пользователя параметры, в которых мы нуждаемся, и собственно печатает текст на экране. Естественно, процедуры под названием print_it у нас еще нет. Напишем ее. Маленькое отступление. Для того, чтобы наша ОС еще и радовала глаз пользователя, я написал малюсенькую процедуру, выводящую на экран в режиме TTY пробел. Конечно, это не самое важное здесь, однако пока что пускай будет. Вот ее код:
print_space:
mov al, ' '
call writechar
ret
Теперь перейдем непосредственно к нашей процедуре. Для начала выполним вызов процедуры print_space, что, как вы понимаете, абсолютно необязательно. Затем три раза вызываем процедуру чтения номера (readnum) и каждый раз помещаем результат в переменные cylinder, stsector и sectorsc соответственно. Все параметры есть, теперь вызываем функцию read_fdd. В соответствии с параметрами она читает дискету и выводит содержимое на экран. Для проверки в самом конце кода введем одну переменную с произвольным текстом, допустим, так: db "Welcome to the OS!". Внимание: если хотите, отложите газету и подумайте, почему мой совет в предыдущем предложении был концептуально неверным (а он действительно неверен). Если не нашли ошибки (и если нашли тоже:)), читайте дальше. Текст в командной строке ОС должен быть следующим: > P 0 2 1. То есть это обозначает номер цилиндра 0, номер первого сектора 2 и количество секторов 1. Запустили? Я не слышу оваций. А их и не должно быть. Ошибка в объявлении текста была следующей. Как вы поняли, содержимое дискеты читается напрямую в буфер экрана. А буфер сконструирован так, что он забивается не только символами, но и их атрибутами. При этом байт символа идет первым, а байт атрибута — вторым. И так по очереди: символ, атрибут, символ, атрибут… Так что неудивительно, что на экран выводились только нечетные символы, да и то в самом экзотическом оформлении. Исправлять это придется путем добавления пробела после каждого символа. Впрочем, это нам мало поможет, т.к. код пробела имеет сканкод 0x20. Если его представить в двоичном счислении, то мы получим следующее: 00100000, что свидетельствует о зеленом цвете фона и черном цвете текста. Нормальной записи текста на дискету для нашей ОС я попытаюсь посвятить целую статью. А пока что разбирайтесь с этим кодом, присылайте свои идеи и комментарии.
Небольшое послесловие. Код, приведенный в прошлой статье, можно оптимизировать следующим образом:
1. В процедуре смены графического режима заменить команды xor ah, ah и mov al, 3 на одну: mov ax, 3.
2. В процедуре writestr заменить операцию dec bl на dec bx и заменить cmp bl, 0 на or bl, bl.
Таким образом мы сэкономим 3 байта. Спасибо за это замечание Сергею Павлюковичу. Кстати, немного позже я сделаю нормальный исходник (текущий у меня ужасно заляпан комментариями и разным хламом) и выложу его на сайт.
На этом позвольте попрощаться. Попробуйте своими силами сделать возможной загрузку программ с дискеты. Здесь все аналогично, только надо будет сделать переход на загруженный код. Присылайте свои варианты реализации.
Влад Маслаков, Vladislav_1988@mail.ru
Компьютерная газета. Статья была опубликована в номере 12 за 2005 год в рубрике soft :: ос