Перспективы создания ОС 4
Продолжение. Начало в КГ №№ 7-9, 12
Доброго времени суток, уважаемые читатели! В этой статье мы наконец доработаем нашу операционную систему до того уровня, чтобы ее можно было использовать практически, а не только в целях изучения.
В прошлой статье мы добавили небольшую возможность вывода данных на экран. Наряду с этим мы использовали очень неудобный режим указания требуемого сектора. Такой режим называется CHS (от первых букв трех параметров адресации: Cylinder (цилиндр), Head (головка) и Sector (сектор). Но было бы несравнимо удобнее указывать напрямую номер сектора, с которого мы хотим начать чтение. Такой режим адресации называется LBA (Logical Block Addressing). Его реализацией мы сейчас и займемся.
Обратим еще раз внимание на функцию 0x2 прерывания 0x13, а точнее, на два регистра: CH и CL. Как сообщалось ранее, CH содержит номер цилиндра, а CL — номер первого сектора, с которого мы начинаем чтение диска. Но есть одно "но": номер цилиндра является 10-битной величиной, два верхних бита которой записываются в биты 7 и 6 регистра CL. А потому адаптируем код чтения и в этом плане. Сперва мы сотрем начисто процедуры print_me и read_fdd. Сначала начнем заново писать последнюю процедуру. Прежде всего определим входные параметры. В регистре CL будет находиться количество секторов, которые необходимо прочитать, в паре DX:AX — номер первого сектора, а в паре ES:BX — адрес памяти, в который будет производиться чтение. Теперь этот параметр придется передавать (а не как в прежнем варианте — заполнять в теле функции), потому что мы будем использовать эту функцию не только для вывода какой-то надписи на экран. Стоит заметить, что процедура использует некоторые стандарты для дискет. Определим их где-нибудь в конце:
1) std_spt dw 12
2) std_hpc dw 2
В строке (1) мы определили количество секторов на одной дорожке диска, а в строке (2) — количество головок. Вообще эти параметры записываются программой форматирования в заголовок файловой системы FAT12, но так как файловой системой у нас даже и не пахнет (она в данном случае нам просто не нужна), определим параметры отдельно. К слову, эти данные взяты со свежеотформатированной дискеты. Сразу после входа в процедуру read_fdd отправляем значение регистра CX в стек, так как этот регистр нам еще понадобится. Затем делим номер сектора (т.е. DX:AX) на количество секторов на дорожке. Согласно описанию команды DIV, в регистре AX оказывается результат деления, а в регистре DX — остаток от деления. То есть в нашем случае в AX мы имеем количество дорожек, а в DX — количество секторов минус один, т.к. нумерация секторов начинается с единицы. Выполняем инкремент регистра DX и копируем его значение в регистр CX. Далее делим DX:AX на количество головок (std_hpc), предварительно обнулив регистр DX, т.к. нам надо разделить только значение регистра AX. В итоге мы имеем в регистре AX номер цилиндра, а в DX — номер головки.
Теперь надо реализовать ту особенность функции BIOS чтения с диска, которую я описал вначале. Для начала поместим в регистр CH, который несет значение номера цилиндра (а точнее — восьми бит номера) значение регистра AL. А затем мы делаем следующую манипуляцию. Сдвинем влево значение регистра AH на 6 разрядов и выполним операцию "Логическое включающее ИЛИ" (или просто OR) с регистрами CL и AH. Не надо быть великим математиком, чтобы понять, что два верхних бита CL будут равняться нулю. А потому два верхних бита CL будут как раз равняться двум верхним битам номера цилиндра. Вот и все. Нам осталось только извлечь из стека количество секторов и задать регистру AH значение требуемой функции — 0x2. После этого смело вызываем прерывание 0x13. Вот мой код:
read_fdd:
push cx
div word [std_spt]
inc dx
mov cx, dx
xor dx, dx
div word [std_hpc]
mov ch, al
shl ah, 6
or cl, ah
mov dh, dl
mov al, 0x0
pop ax
mov ah, 0x2
int 0x13
ret
А теперь переделаем процедуру print_me. После входа в процедуру вызываем функции чтения числа и отправки значения регистра CX в стек. Затем помещаем в регистр ES значение нужного сегмента — 0xB800 — и обнуляем регистр BX. Теперь в паре ES:BX у нас полный адрес, куда мы будем записывать считанные данные. Достаем из стека значение сектора, заносим в CL значение 1 (нам надо прочитать только один сектор), обнуляем регистр DX и вызываем функцию read_fdd. Вот код:
print_me:
call print_space
call readnum
push cx
mov ax, 0xB800
mov es, ax
xor bx, bx
pop ax
mov cl, 1
xor dx, dx
call read_fdd
jmp main_os_loop
Вроде бы, неплохо. А теперь реализуем самую главную возможность ОС — возможность запускать программы. Делаем мы это так. Сперва напишем метку для программы (допустим, load_program). Вызовем дважды процедуру чтения номера и команду отправки значения регистра CX в стек. В первый раз — для указания номера первого сектора, во второй — для количества секторов. Теперь решим, куда надо записывать код программы. Сегмент 0xB800 явно не подойдет: при первой же попытке со стороны программы записать что-то на экран код самой программы превратится во что-то ужасное. Да и помещать всякие закорючки на экран тоже, так сказать, неэстетично. Так что я выбрал сегмент 0x10. А потому после считывания параметров мы занесем в регистр ES значение 0x10, а регистр BX обнулим. После этого из стека вынимаем значения в регистры AX и CX, а регистр DX обнуляем. Теперь выполняем процедуру read_fdd и делаем межсегментный переход на наш код:
load_program:
call print_space
call readnum
push cx
call readnum
push cx
mov ax, 0x10
mov es, ax
xor bx, bx
pop ax
pop cx
xor dx, dx
call read_fdd
jmp 0x10:0x0000
В качестве кода для отладки я выбрал код перезагрузки компьютера в горячем режиме:
mov ah, 0x40
mov es, ax
mov word [es:0x72],0x1234
jmp 0xFFFF:0x0000
Ну и самый последний штрих: в коде под main_os_loop добавляем следующий код для того, чтобы система поняла наше намерение загрузить программу:
cmp al, 'L'
je load_program
Для тестирования пишем в нашей консоли: "> L 1 1". И еще несколько заметок для потенциальных разработчиков ПО для этой ОС (если такие еще будут):
. если вы работаете с данными, то лучше переопределите регистры CS, DS, SS и SP;
. в начале программы вставляйте org 0x10;
. для возврата обратно в ОС выполняйте межсегментный переход jmp 0x7C00:0x0000. Конечно, ОС будет заново определять регистры, переключать режимы экрана, но так даже лучше.
На сегодня все. Очень скоро (не позже, чем через неделю) я выложу исходный код на свой сайт www.micronasp.com.ru . Ищите в разделе "Download" файл work.zip. На сайте есть форум, так что при наличии вопросов заходите, спрашивайте. Если есть вопросы или идеи — обязательно пишите. Всего хорошего.
Влад Маслаков, Vladislav_1988@mail.ru
Доброго времени суток, уважаемые читатели! В этой статье мы наконец доработаем нашу операционную систему до того уровня, чтобы ее можно было использовать практически, а не только в целях изучения.
В прошлой статье мы добавили небольшую возможность вывода данных на экран. Наряду с этим мы использовали очень неудобный режим указания требуемого сектора. Такой режим называется CHS (от первых букв трех параметров адресации: Cylinder (цилиндр), Head (головка) и Sector (сектор). Но было бы несравнимо удобнее указывать напрямую номер сектора, с которого мы хотим начать чтение. Такой режим адресации называется LBA (Logical Block Addressing). Его реализацией мы сейчас и займемся.
Обратим еще раз внимание на функцию 0x2 прерывания 0x13, а точнее, на два регистра: CH и CL. Как сообщалось ранее, CH содержит номер цилиндра, а CL — номер первого сектора, с которого мы начинаем чтение диска. Но есть одно "но": номер цилиндра является 10-битной величиной, два верхних бита которой записываются в биты 7 и 6 регистра CL. А потому адаптируем код чтения и в этом плане. Сперва мы сотрем начисто процедуры print_me и read_fdd. Сначала начнем заново писать последнюю процедуру. Прежде всего определим входные параметры. В регистре CL будет находиться количество секторов, которые необходимо прочитать, в паре DX:AX — номер первого сектора, а в паре ES:BX — адрес памяти, в который будет производиться чтение. Теперь этот параметр придется передавать (а не как в прежнем варианте — заполнять в теле функции), потому что мы будем использовать эту функцию не только для вывода какой-то надписи на экран. Стоит заметить, что процедура использует некоторые стандарты для дискет. Определим их где-нибудь в конце:
1) std_spt dw 12
2) std_hpc dw 2
В строке (1) мы определили количество секторов на одной дорожке диска, а в строке (2) — количество головок. Вообще эти параметры записываются программой форматирования в заголовок файловой системы FAT12, но так как файловой системой у нас даже и не пахнет (она в данном случае нам просто не нужна), определим параметры отдельно. К слову, эти данные взяты со свежеотформатированной дискеты. Сразу после входа в процедуру read_fdd отправляем значение регистра CX в стек, так как этот регистр нам еще понадобится. Затем делим номер сектора (т.е. DX:AX) на количество секторов на дорожке. Согласно описанию команды DIV, в регистре AX оказывается результат деления, а в регистре DX — остаток от деления. То есть в нашем случае в AX мы имеем количество дорожек, а в DX — количество секторов минус один, т.к. нумерация секторов начинается с единицы. Выполняем инкремент регистра DX и копируем его значение в регистр CX. Далее делим DX:AX на количество головок (std_hpc), предварительно обнулив регистр DX, т.к. нам надо разделить только значение регистра AX. В итоге мы имеем в регистре AX номер цилиндра, а в DX — номер головки.
Теперь надо реализовать ту особенность функции BIOS чтения с диска, которую я описал вначале. Для начала поместим в регистр CH, который несет значение номера цилиндра (а точнее — восьми бит номера) значение регистра AL. А затем мы делаем следующую манипуляцию. Сдвинем влево значение регистра AH на 6 разрядов и выполним операцию "Логическое включающее ИЛИ" (или просто OR) с регистрами CL и AH. Не надо быть великим математиком, чтобы понять, что два верхних бита CL будут равняться нулю. А потому два верхних бита CL будут как раз равняться двум верхним битам номера цилиндра. Вот и все. Нам осталось только извлечь из стека количество секторов и задать регистру AH значение требуемой функции — 0x2. После этого смело вызываем прерывание 0x13. Вот мой код:
read_fdd:
push cx
div word [std_spt]
inc dx
mov cx, dx
xor dx, dx
div word [std_hpc]
mov ch, al
shl ah, 6
or cl, ah
mov dh, dl
mov al, 0x0
pop ax
mov ah, 0x2
int 0x13
ret
А теперь переделаем процедуру print_me. После входа в процедуру вызываем функции чтения числа и отправки значения регистра CX в стек. Затем помещаем в регистр ES значение нужного сегмента — 0xB800 — и обнуляем регистр BX. Теперь в паре ES:BX у нас полный адрес, куда мы будем записывать считанные данные. Достаем из стека значение сектора, заносим в CL значение 1 (нам надо прочитать только один сектор), обнуляем регистр DX и вызываем функцию read_fdd. Вот код:
print_me:
call print_space
call readnum
push cx
mov ax, 0xB800
mov es, ax
xor bx, bx
pop ax
mov cl, 1
xor dx, dx
call read_fdd
jmp main_os_loop
Вроде бы, неплохо. А теперь реализуем самую главную возможность ОС — возможность запускать программы. Делаем мы это так. Сперва напишем метку для программы (допустим, load_program). Вызовем дважды процедуру чтения номера и команду отправки значения регистра CX в стек. В первый раз — для указания номера первого сектора, во второй — для количества секторов. Теперь решим, куда надо записывать код программы. Сегмент 0xB800 явно не подойдет: при первой же попытке со стороны программы записать что-то на экран код самой программы превратится во что-то ужасное. Да и помещать всякие закорючки на экран тоже, так сказать, неэстетично. Так что я выбрал сегмент 0x10. А потому после считывания параметров мы занесем в регистр ES значение 0x10, а регистр BX обнулим. После этого из стека вынимаем значения в регистры AX и CX, а регистр DX обнуляем. Теперь выполняем процедуру read_fdd и делаем межсегментный переход на наш код:
load_program:
call print_space
call readnum
push cx
call readnum
push cx
mov ax, 0x10
mov es, ax
xor bx, bx
pop ax
pop cx
xor dx, dx
call read_fdd
jmp 0x10:0x0000
В качестве кода для отладки я выбрал код перезагрузки компьютера в горячем режиме:
mov ah, 0x40
mov es, ax
mov word [es:0x72],0x1234
jmp 0xFFFF:0x0000
Ну и самый последний штрих: в коде под main_os_loop добавляем следующий код для того, чтобы система поняла наше намерение загрузить программу:
cmp al, 'L'
je load_program
Для тестирования пишем в нашей консоли: "> L 1 1". И еще несколько заметок для потенциальных разработчиков ПО для этой ОС (если такие еще будут):
. если вы работаете с данными, то лучше переопределите регистры CS, DS, SS и SP;
. в начале программы вставляйте org 0x10;
. для возврата обратно в ОС выполняйте межсегментный переход jmp 0x7C00:0x0000. Конечно, ОС будет заново определять регистры, переключать режимы экрана, но так даже лучше.
На сегодня все. Очень скоро (не позже, чем через неделю) я выложу исходный код на свой сайт www.micronasp.com.ru . Ищите в разделе "Download" файл work.zip. На сайте есть форум, так что при наличии вопросов заходите, спрашивайте. Если есть вопросы или идеи — обязательно пишите. Всего хорошего.
Влад Маслаков, Vladislav_1988@mail.ru
Компьютерная газета. Статья была опубликована в номере 13 за 2005 год в рубрике soft :: ос