Отправляет email-рассылки с помощью сервиса Sendsay

Linux Gazette на русском

  Все выпуски  

Linux Gazette на русском


Информационный Канал Subscribe.Ru

Здравствуйте!

Очередной перевод, посвященный основам "осестроения". Кроме того, обращаю всеобщее внимание на то, что на нашем сайте появился выполненный Ивано Песиным перевод "Bandwidth Limiting HOWTO", посвященный (как следует из названия:) вопросам ограничения пропускной способности канала доступа в Internet для некоторых особо жадных до траффика пользователей локальной сети:) Читайте: http://gazette.linux.ru.net/rus/articles/bandwidth_limiting_HOWTO.htlm

Как всегда, жду вопросов и замечаний по адресу suralis-s@mtu-net.ru. Если не лень, то сразу пишите, можно ли использовать Ваши письма в рассылке.

Сергей Скороходов

Пишем игрушечную ОС (часть II)

Автор: (C) Krishnakumar R.
Перевод: (C) Александр Куприн


Часть I (http://gazette.linux.ru.net/lg77/articles/rus-krishnakumar.html) опубликована в апрельском номере журнала.

Следующее, что необходимо разобрать после изучения процесса создания загрузочного сектора и до того, как мы перейдём к описанию приёмов переключения в защищённый режим, это использование прерываний BIOS[1]. Прерывания BIOS представляют собой подпрограммы низкого уровня, облегчающие работу создателю операционной системы. В этой части статьи мы будем иметь дело с ними.

1. Теория

1.1 Почему BIOS ?

BIOS копирует загрузочный сектор в оперативную память и передаёт на него управление. Кроме этого, существует ещё несколько функций, которые могут выполняться BIOS'ом. В момент загрузки операционная система ещё не располагает драйверами для поддержки оборудования. Размещение такого драйвера в загрузочном секторе -- задача практически невыполнимая. Должен существовать другой способ решить эту проблему и в этом нам поможет BIOS. Он содержит множество подпрограмм, служащих для различных целей и которыми мы можем воспользоваться. Например, существуют подпрограммы проверки установленного оборудования, подпрограммы управления выводом на печать, определения размера оперативной памяти и пр. Эти подпрограммы называются подпрограммами или обработчиками прерываний BIOS.

1.2 Как же вызывать прерывания BIOS ?

В "обычных" языках программирования вызов подпрограммы реализуется через обращение к её имени. К примеру, если в программе на C у нас есть подпрограмма display, получающая в качестве параметров атрибуты (attr) и количество отображаемых символов на экране (noofchar), то обратиться к ней мы можем просто указав её имя и необходимые параметры. Однако, здесь мы будем использовать прерывания, вызов которых осуществляется посредством инструкции ассемблера int.

Например, для вывода символов на экран в C мы используем функцию похожую на:

display(noofchar, attr);

Эквивалентно этому, оперируя средствами ассемблера и BIOS, мы напишем:

int 0x10

1.3 Ладно, а как мы передаём параметры ?

Перед тем как вызвать прерывание BIOS, нам нужно загрузить данные в заранее определённом формате в регистры процессора. Предположим, мы используем прерывание 0x13, предназначенное для чтения/записи с дискеты. Прежде чем вызвать его, мы должны определить адрес в оперативной памяти, куда будут загружены данные. Также мы должны передать информацию о номере устройства (fd0 - 0x00, fd1 - 0x01, hda - 0x80, hdb - 0x81 и т.д.), цилиндре, секторе и количестве копируемых секторов. Эти данные должны быть загружены в определённые регистры. Всё это вам станет понятно после того, как вы прочтёте описание работы кода загрузочного сектора, который мы разработаем чуть позже.

Есть одна очень важная деталь, о которой вы должны знать -- одно и тоже прерывание может использоваться для различных целей. Всё это зависит от номера функции, который указывается в регистре ah (иногда ax). К примеру, прерывание 0x10 может быть использовано как для вывода на экран строки, так и для получения координат курсора. Если мы запишем в регистр ah значение 0x03, то тем самым при вызове прерывания 0x10 мы выберем функцию, используемую для получения координат курсора. Для вывода строки на экран мы записываем в регистр ah значение 0x13, которое является номером функции вывода строки на экран.

2. Что мы будем делать теперь ?

На этот раз наш исходный код состоит из двух программ на ассемблере и одной на C. Первый файл (на ассемблере) -- это код загрузочного сектора. В нём хранятся инструкции, копирующие второй сектор флоппи-диска в сегмент памяти 0x500 (абсолютный адрес 0x5000)[2]. Операция выполняется при помощи прерывания BIOS 0x13. После этого код загрузчика передаёт управление по адресу 0x500:0 (сегмент -- 0x500, смещение -- 0). Код второго файла на ассемблере выводит на экран сообщение, используя прерывание BIOS 0x10. Программа на C предназначена для записи исполняемого кода программ на ассемблере в 1-й и во 2-й сектора дискеты.

3. Загрузочный сектор

Используя прерывание 0x13, код загрузочного сектора читает и копирует второй сектор флоппи-диска в память по адресу 0x5000 (сегментный адрес 0x500). Пример этого показан ниже. Сохраните его в файле bsect.s.

LOC1=0x500

entry start
start:
        mov ax,#LOC1
        mov es,ax
        mov bx,#0

        mov dl,#0
        mov dh,#0
        mov ch,#0
        mov cl,#2
        mov al,#1

        mov ah,#2

        int 0x13

        jmpi 0,#LOC1

Первая строка -- это макро-определение. Следующие две инструкции уже знакомы вам по предыдущей статье. Записать данные непосредственно в регистры сегментов нельзя, поэтому следующие две строки используются для загрузки в регистр es значения 0x500. Регистр bx содержит значение смещения в сегменте, по которому будут загружены данные.

Далее мы записываем...

  • номер устройства в регистр dl (принцип кодирования описан в пункте 1.3)
  • номер головки чтения/записи в регистр dh
  • номер цилиндра в регистр ch
  • номер сектора в регистр cl
  • количество загружаемых секторов в регистр al

Итак, мы загружаем второй сектор нулевой дорожки устройства номер 0 (что соответствует приводу флоппи-дисков на 1.44Мб) по адресу 0x500:0.

Значение 2 в регистре ah указывает на номер функции прерывания. Мы выбираем функцию номер 2, которая используется для чтения данных с диска (жёсткого или гибкого).

Теперь мы вызываем прерывание 0x13 и последней командой передаём управление коду, загруженному по адресу 0x500:0.

4. Второй сектор

Второй сектор будет содержать следующий код :

entry start
start:
        mov     ah,#0x03
        xor     bh,bh
        int     0x10

        mov     cx,#26
        mov     bx,#0x0007
        mov     bp,#mymsg
        mov     ax,#0x1301
        int     0x10

loop1:  jmp     loop1

mymsg:
        .byte  13,10
        .ascii "Handling BIOS interrupts"

Этот код загружается и выполняется в сегменте 0x500. Он использует прерывание 0x10 для получения текущей позиции курсора и вывода на экран сообщения.

Первые три строки кода (отсчёт начинается с третьей строки, пропуская инструкции определения точки входа) используются для получения текущей позиции курсора. Для этого используется функция 0x03 прерывания 0x10. Перед её вызовом мы обнуляем значение регистра bh[3]. После выполнения прерывания, интересующий нас результат будет хранится в регистрах dh и dl (номер строки и колонки соответственно). Переходим ко второй части программы. В регистр...

  • cx записываем количество символов в строке, выводимой на экран[4]
  • bx заносим значение номера видео страницы и код атрибута выводимых на экран символов (0x00 и 0x07). Мы планируем использовать белый цвет символов (0x7) на чёрном фоне (0x0).
  • bp пишем адрес строки [5]
  • ax записываем номер функции для вывода на экран строки и код подфункции, определяющий, что атрибут показываемой строки будет взят из регистра bl

Начало сообщения содержит два байта со значениями 13 и 10, что соответствует нажатию клавиши enter. Эти два кода идут вместе, 13 -- код возврата каретки (Carriage Return, CR), 10 -- код перевода строки (Line Feed, LF). Сама строка содержит 24 символа. Символы CR и LF трактуются функцией 0x13 прерывания 0x10 как управляющие и поэтому не высвечиваются. Теперь вызываем прерывание. И последней инструкцией "вешаем" компьютер.

5. Программа на C

Код программы на C, являющийся "ракетоносителем" (точнее "программоносителем" 8-), приведен ниже. Сохраните его в файле write.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("./bsect", 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_SET);
                write(floppy_desc, boot_buf, 512);

                file_desc = open("./sect2", O_RDONLY);
                read(file_desc, boot_buf, 510);
                close(file_desc);

                lseek(floppy_desc, 512, SEEK_SET);
                write(floppy_desc, boot_buf, 512);

                close(floppy_desc);
}

В первой части статьи я описал, как создать загрузочную дискету. В данном примере есть небольшие отличия. Сперва мы копируем в загрузочный сектор файл bsect, исполняемый код которого генерируется из bsect.s. Затем наступает очередь sect2 -- мы записываем его во второй сектор флоппи-диска. Вот и всё, изменения, делающие дискету загрузочной, выполнены.[6]

6. Готовые примеры

Вы можете взять исходники примеров здесь

  1. bsect.s
  2. sect2.s
  3. write.c
  4. Makefile

Удалите у файлов расширение txt и введите

make

в ответ на приглашение оболочки или вы можете откомпилировать файлы самостоятельно. В этом случаем введите

as86 bsect.s -o bsect.o

ld86 -d bsect.o -o bsect

и повторите тоже самое для sect2.s. Сборку write.c выполните командой :

cc write.c -o write

и вставив дискету в дисковод выполните программу write.

7. Что дальше ?

После загрузки с дискеты вы можете любоваться строкой на экране. И всё это благодаря прерываниям BIOS. В следующей статье из этой серии я надеюсь написать о том как переключать процессор в защищённый режим. А до тех пор, пока!


Krishnakumar R.

Кришнакумар -- студент последнего курса B.Tech в Govt. Engg. College Thrissur, Kerala, Индия. Его путешествие в земли Операционных Систем началось с программирования модулей для Linux. Он создал операционную систему GROS, основная цель которой -- выполнение функций маршрутизатора. (Детали вы можете найти на его домашней странице: www.askus.way.to ) Другие его интересы -- сетевые драйвера, драйвера устройств, портирование компиляторов и встроенные системы.


Примечания переводчика

[1] BIOS -- Basic Input/Output System (Базовая Система Ввода/Вывода), код прошиваемый в ПЗУ и позволяющий работать с оборудованием компьютера.

[2] Чтобы получить абсолютное значение адреса из адреса в формате сегментной адресации, нужно умножить значение сегментного регистра на 0x10 и прибавить величину смещения. В нашем случае это 0x500*0x10=0x5000.

[3] Автор не стал объяснять почему в регистр bh необходимо записать нулевое значение, поэтому это сделаю я 8-). В текстовом режиме существует несколько страниц видеопамяти, которые могут отображаться на экране. Объём каждой из них зависит от режима. Если это режим 80x25, то объём страницы составляет 4000 байт -- (80 * 25 * 2). Два байта, если вы помните из предыдущей статьи, отводятся для хранения кода символа и его атрибутов. Переключение между видео страницами осуществляется при помощи функции 0x05 прерывания 0x10.

[4] На мой взгляд, необходимо немного изменить эту программу -- убрать явное указание количества символов в строке, заменив его вычислением. Т.е. вместо строки

      mov     cx,#26

ставим две других

      mov     cx,#end_mystr
        sub     cx,#mymsg

и добавляем в конце программы метку end_mystr. По крайней мере, теперь у нас не будет болеть голова о том, чтобы рассчитывать длину строки, каждый раз, когда меняется её содержимое.

[5] Если быть точным, то на адрес строки указывает пара регистров -- es:bp. Но в нашем случае инициализация регистра es произошла до этого момента (см. код загрузчика).

[6] Для любителей простоты исполнения, код программы на C может быть заменён скриптом на bash'е из двух строк (точнее трёх -- есть ещё заголовок:)

#!/bin/bash
dd if=bsect of=/dev/fd0 conv=notrunc
dd if=sect2 of=/dev/fd0 seek=1 conv=notrunc

Хотя опция conv=notrunc возможно и не нужна в случае с /dev/fd0.


Copyright (С) 2002, Krishnakumar R.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 79 of Linux Gazette, June 2002

Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн, Андрей Киселев

Со всеми предложениями, идеями и комментариями обращайтесь к Сергею Скороходову (suralis-s@mtu-net.ru). Убедительная просьба: указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в рассылке.

Сайт рассылки: http://gazette.linux.ru.net
Эту статью можно взять здесь: http://gazette.linux.ru.net/lg79/krishnakumar.html



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное