[ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ]



Ответов: 0
25-02-12 07:01







   Web - программирование
PHP


ASP






XML



CSS

SSI





   Программирование под ОС











   Web - технологии








   Базы Данных









   Графика






Данные




Программирование под ОС / Assembler /

Настоящий Hello World на Assembler

С чего начинается изучение нового языка (или среды) программирования? С написания простенькой программы, выводящей на экран краткое приветствие типа "Hello World!". Например, для C это будет выглядеть приблизительно так:

main() 
{
printf("Hello World!\n"); 
} 

Показательно, но совершенно неинтересно. Программа, конечно, работает, приветствие свое пишет; но ведь для этого требуется целая операционная система! А что если хочется написать программку, для которой ничего не надо? Вставляем дискетку в компьютер, загружаемся с нее и ..."Hello World"! Можно даже прокричать это приветствие из защищенного режима…
Сказано - сделано. С чего бы начать?.. Набраться знаний, конечно. Для этого очень хорошо полазить в исходниках Linux и Thix. Первая система всем хорошо знакома, вторая менее известна, но не менее полезна.

Подучились? Теперь займемся. Понятно, что первым делом надо написать загрузочный сектор для нашей мини-операционки (а ведь это будет именно мини-операционка!). Поскольку процессор грузится в 16-разрядном режиме, то для создания загрузочного сектора используется ассемблер и линковщик из пакета bin86. Можно, конечно, поискать еще что-нибудь, но оба наших примера используют именно его; и мы тоже пойдем по стопам учителей. Синтаксис этого ассемблера немного странноватый, совмещающий черты, характерные и для Intel и для AT&T, но после пары недель мучений можно привыкнуть.

Загрузочный сектор (boot.S)

Сознательно не буду приводить полных листингов программ. Так станут понятней основные идеи, да и вам будет намного приятней, если все напишете своими руками.

Для начала определимся с основными константами.
START_HEAD = 0 - Головка привода, которою будем использовать.
START_TRACK = 0 - Дорожка, откуда начнем чтение.
START_SECTOR = 2 - Сектор, начиная с которого будем считывать наше ядрышко.
SYSSIZE = 10 - Размер ядра в секторах (каждый сектор содержит 512 байт)
FLOPPY_ID = 0 - Идентификатор привода. 0 - для первого, 1 - для второго
HEADS = 2 - Количество головок привода.
SECTORS = 18 - Количество дорожек на дискете. Для формата 1.44 МБ это количество равно 18.

В процессе загрузки будет происходить следующее. Загрузчик BIOS считает первый сектор дискеты, положит его по адресу 0000:0x7c00 и передаст туда управление. Мы его получим и - для начала - переместим себя пониже по адресу 0000:0x600, перейдем туда и спокойно продолжим работу. Собственно вся наша работа будет состоять из загрузки ядра (сектора 2 - 12 первой дорожки дискеты) по адресу 0x100:0000, переходу в защищенный режим и скачку на первые строки ядра. В связи с этим еще несколько констант:
BOOTSEG = 0x7c00 - Сюда поместит загрузочный сектор BIOS.
INITSEG = 0x600 - Сюда его переместим мы.
SYSSEG = 0x100 - А здесь приятно расположится наше ядро.
DATA_ARB = 0x92 - Определитель сегмента данных для дескриптора
CODE_ARB = 0x9A - Определитель сегмента кода для дескриптора.

Первым делом произведем перемещение самих себя в более приемлемое место.

cli 
xor ax, ax 
mov ss, ax 
mov sp, #BOOTSEG 
mov si, sp 
mov ds, ax 
mov es, ax 
sti 
cld 
mov di, #INITSEG 
mov cx, #0x100 
repnz 
movsw 
jmpi go, #0 
;прыжок в новое местоположение загрузочного сектора на метку go

Теперь необходимо настроить как следует сегменты для данных (es, ds) и для стека. Неприятно, конечно, что все приходится делать вручную, но что поделаешь - ведь кроме нас и BIOS в памяти компьютера никого нет.

go: 
mov ax, #0xF0 
mov ss, ax 
mov sp, ax ; Стек разместим как 0xF0:0xF0 = 0xFF0 
mov ax, #0x60 ; Сегменты для данных ES и DS зададим в 0x60 
mov ds, ax 
mov es, ax  

Наконец, можно вывести победное приветствие. Пусть мир узнает, что мы смогли загрузиться! Поскольку у нас есть все-таки целый BIOS, воспользуемся готовой функцией 0x13 прерывания 0x10. Можно, конечно, его презреть и написать напрямую в видеопамять, но у нас каждый байт команды на счету, а байт таких всего 512. Потратим их лучше на что-нибудь более полезное.

mov cx,#18 
mov bp,#boot_msg 
call write_message 
Функция write_message выглядит следующим образом   
write_message: 
push bx 
push ax 
push cx 
push dx 
push cx 
mov ah,#0x03 ; прочитаем текущее положение курсора, 
             ; дабы не выводить сообщения где попало. 
xor bh,bh 
int 0x10 
pop cx 
mov bx,#0x0007 ; Параметры выводимых символов : видеостраница 0, 
               ; атрибут 7 (серый на черном) 
mov ax,#0x1301 ; Выводим строку и сдвигаем курсор. 
int 0x10 
pop dx 
pop cx 
pop ax 
pop bx 
ret 

А сообщение так

boot_msg: 
.byte 13,10 
.ascii "Booting data ..." 
.byte 0  

К этому времени на дисплее компьютера появится скромное "Booting data ...". Это в принципе не хуже, чем "Hello World", но давайте добьемся чуть большего. Перейдем в защищенный режим и выведем этот "Hello" уже из программы, написанной на C.

Ядро 32-разрядное. Оно будет у нас размещаться отдельно от загрузочного сектора и собираться уже с помощью gcc и gas. Синтаксис ассемблера gas соответствует требованиям AT&T, так что тут все будет попроще. Но для начала нам нужно прочитать ядро. Опять воспользуемся готовой функцией 0x2 прерывания 0x13.

recalibrate: 
mov ah, #0 
mov dl, #FLOPPY_ID 
int 0x13 ; проведем реинициализацию дисковода. 
jc recalibrate 
call read_track ; вызов функции чтения ядра 
jnc next_work ; если во время чтения не произошло
              ; ничего плохого, то работаем дальше 

bad_read: 
; если чтение произошло неудачно - выводим сообщение об ошибке 
mov bp,#error_read_msg 
mov cx,7 
call write_message 
inf1: jmp inf1 ; и уходим в бесконечный цикл. 
               ; Теперь нас спасет только ручная перезагрузка 

Сама функция чтения предельно простая: долго и нудно заполняем параметры, а затем одним махом считываем ядро. Сложности начнутся, когда ядро перестанет помещаться в 17 секторах (то есть 8.5КБ); но это пока в будущем, а сейчас вполне достаточно такого молниеносного чтения

read_track: 
pusha 
push es 
push ds 
mov di, #SYSSEG ; Определяем 
mov es, di ; адрес буфера для данных 
xor bx, bx 
mov ch, #START_TRACK ;дорожка 0 
mov cl, #START_SECTOR ;начиная с сектора 2 
mov dl, #FLOPPY_ID 
mov dh, #START_HEAD 
mov ah, #2 
mov al, #SYSSIZE ;считать 10 секторов 
int 0x13 
pop ds 
pop es 
popa 
ret

Вот и все. Ядро успешно прочитано, и можно вывести еще одно радостное сообщение на экран.

next_work: 
call kill_motor ; останавливаем привод дисковода 
mov bp,#load_msg ; выводим сообщение 
mov cx,#4 
call write_message 
Вот содержимое сообщения 
load_msg: 
.ascii "done" 
.byte 0 
А вот функция остановки двигателя привода. 
kill_motor: 
push dx 
push ax 
mov dx,#0x3f2 
xor al,al 
out dx,al 
pop ax 
pop dx 
ret  

На данный момент на экране выведено "Booting data ...done" и лампочка привода флоппи-дисков погашена. Все затихли и готовы к смертельному номеру - прыжку в защищенный режим.

Для начала надо включить адресную линию A20. Это в точности означает, что мы будем использовать 32-разрядную адресацию к данным.

mov al, #0xD1 ; команда записи для 8042 
out #0x64, al 
mov al, #0xDF ; включить A20 
out #0x60, al  

Выведем предупреждающее сообщение - о том, что переходим в защищенный режим. Пусть все знают, какие мы важные.

protected_mode: 
mov bp,#loadp_msg 
mov cx,#25 
call write_message 

Сообщение:

loadp_msg: 
.byte 13,10 
.ascii "Go to protected mode..." 
.byte 0 

Пока у нас еще жив BIOS, запомним позицию курсора и сохраним ее в известном месте (0000:0x8000 ). Ядро позже заберет все данные и будет их использовать для вывода на экран победного сообщения.

save_cursor: 
mov ah,#0x03 ; читаем текущую позицию курсора 
xor bh,bh 
int 0x10 
seg cs 
mov [0x8000],dx ;сохраняем в специальном тайнике

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

cli 
lgdt GDT_DESCRIPTOR ; загружаем описатель таблицы дескрипторов. 

У нас таблица дескрипторов состоит из трех описателей: нулевой (всегда должен присутствовать), сегмента кода и сегмента данных .

align 4 
.word 0 
GDT_DESCRIPTOR: .word 3 * 8 - 1 ; размер таблицы дескрипторов 
.long 0x600 + GDT ; местоположение таблицы дескрипторов 
.align 2 
GDT: 
.long 0, 0 ; Номер 0: пустой дескриптор 
.word 0xFFFF, 0 ; Номер 8: дескриптор кода 
.byte 0, CODE_ARB, 0xC0, 0 
.word 0xFFFF, 0 ; Номер 0x10: дескриптор данных 
.byte 0, DATA_ARB, 0xCF, 0  

Переход в защищенный режим может происходить минимум двумя способами, но обе ОС, выбранные нами для примера (Linux и Thix) используют для совместимости с 286 процессором команду lmsw. Мы будем действовать тем же способом

mov ax, #1 
lmsw ax ; прощай реальный режим. 
        ; Мы теперь находимся в защищенном режиме. 
jmpi 0x1000, 8 ; Затяжной прыжок на 32-разрядное ядро. 

Вот и вся работа загрузочного сектора – не мало, но и не много. Теперь с ним мы попрощаемся и направимся к ядру.

В конце ассемблерного файла полезно добавить следующую инструкцию.

org 511 
end_boot: .byte 0 

В результате скомпилированный код будет занимать ровно 512 байт, что очень удобно для подготовки образа загрузочного диска.

Первые вздохи ядра (head.S)

Ядро, к сожалению, опять начнется с ассемблерного кода. Но теперь его будет совсем немного.

Мы собственно зададим правильные значения сегментов для данных (ES, DS, FS, GS). Записав туда значение соответствующего дескриптора данных.

cld 
cli 
movl $(__KERNEL_DS),%eax 
movl %ax,%ds 
movl %ax,%es 
movl %ax,%fs 
movl %ax,%gs 

Проверим, нормально ли включилась адресная линия A20 - простым тестом записи. Обнулим для чистоты эксперимента регистр флагов.

xorl %eax,%eax 
1: incl %eax 
movl %eax,0x000000 
cmpl %eax,0x100000 
je 1b 
pushl $0 
popfl 

Вызовем долгожданную функцию, уже написанную на С.

call SYMBOL_NAME(start_my_kernel) 

И больше нам тут делать нечего.

inf: jmp inf 

Поговорим на языке высокого уровня (start.c)

Вот теперь мы вернулись к тому, с чего начинали рассказ. Почти вернулись, потому что printf() теперь надо делать вручную. Поскольку готовых прерываний уже нет, то будем использовать прямую запись в видеопамять. Для любопытных - почти весь код этой части, с незначительными изменениями, позаимствован из части ядра Linux, осуществляющей распаковку (/arch/i386/boot/compressed/*). Для сборки вам потребуется дополнительно определить такие макросы как inb(), outb(), inb_p(), outb_p(). Готовые определения проще всего одолжить из любой версии Linux.

Теперь, дабы не путаться со встроенными в glibc функциями, отменим их определение

#undef memcpy 
Зададим несколько своих:
static void puts(const char *); 
static char *vidmem = (char *)0xb8000; /*адрес видеопамяти*/ 
static int vidport; /*видеопорт*/ 
static int lines, cols; /*количество линий и строк на экран*/ 
static int curr_x,curr_y; /*текущее положение курсора */ 

И начнем, наконец, писать код на языке высокого уровня... правда, с небольшими ассемблерными вставками.

/* функция перевода курсора в положение (x,y). 
 * Работа ведется через ввод/вывод в видеопорт 
 */ 
void gotoxy(int x, int y) 
{ 
int pos; 
pos = (x + cols * y) * 2; 
outb_p(14, vidport); 
outb_p(0xff & (pos >> 9), vidport+1); 
outb_p(15, vidport); 
outb_p(0xff & (pos >> 1), vidport+1); 
} 

/* функция прокручивания экрана. Работает, 
 * используя прямую запись в видеопамять
 */ 
static void scroll() 
{ 
int i; 
memcpy ( vidmem, vidmem + cols * 2, ( lines - 1 ) * cols * 2 ); 
for ( i = ( lines - 1 ) * cols * 2; i < lines * cols * 2; i += 2 ) 
vidmem[i] = ' '; 
} 
/* функция вывода строки на экран */ 
static void puts(const char *s) 
{ 
int x,y; 
char c; 

x = curr_x; 
y = curr_y; 
while ( ( c = *s++ ) != '




Комментарии

ыд321
26-09-2011   
No comments

 Ваш комментарий к данному материалу будет интересен нам и нашим читателям!



Последние статьи: Программирование под ОС / Assembler /

Первая программа на linux
23-05-2010   

Ассемблер, который я буду использовать - NASM (Netwide Assembler, nasm.2y.net). Этот выбор объясняется тем, что: Во первых, он мультиплатформенный, т.е. для портирования программы на разные ОС достаточно только изменить код взаимодействия с системой, а всю программу переписывать не нужно... подробнее

Кол. просмотров: общее - 3647 сегодня - 1

Использование пакета NuMega Driver Studio для написания WDM - драйверов устройств
17-05-2010   

Разработка WDM - драйвера с использованием только DDK является сложной и трудоемкой задачей. При этом приходится выполнять много однотипных операций: создание скелета драйвера, написание inf - файла для его установки, создание приложения для тестирования и т.п... подробнее

Кол. просмотров: общее - 3297 сегодня - 1

Система классов DriverWorks
17-05-2010   

Возможно, идея писать драйвера объектно-ориентированными и кажется на первый взгляд нелогичной. Но при более близком знакомстве с DriverStudio и с драйверами в общем, оказывается, что это не так уж страшно и довольно удобно... подробнее

Кол. просмотров: общее - 3306 сегодня - 1

Объект устройства device object
17-05-2010   

Объекты устройств являются экземплярами класса KDevice или KPnpDevice. Эти классы являются краеугольными камнями архитектуры DriverWorks: они представляют собой как бы программный образ тех устройств, которые присутствуют в системе... подробнее

Кол. просмотров: общее - 3357 сегодня - 1

Объекты для управления оборудованием
17-05-2010   

Как было упомянуто выше, объект устройства управляет работой устройства при помощи специальных объектов, управляющих работой оборудования - портами В/В, прерываниями, памятью, контроллерами ПДП. Драйвер создает эти объекты для представления физических параметров устройства... подробнее

Кол. просмотров: общее - 3533 сегодня - 1



  WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects