Помощь в написании студенческих работ
Антистрессовый сервис

Настоящий Hello World

РефератПомощь в написанииУзнать стоимостьмоей работы

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

Настоящий Hello World (реферат, курсовая, диплом, контрольная)

Настоящий «Hello World «

Станислав Иевлев С чего начинается изучение нового языка (или среды) программирования? С написания простенькой программы, выводящей на экран краткое приветствие типа «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:0×600, перейдем туда и спокойно продолжим работу. Собственно вся наша работа будет состоять из загрузки ядра (сектора 2 — 12 первой дорожки дискеты) по адресу 0×100:0000, переходу в защищенный режим и скачку на первые строки ядра. В связи с этим еще несколько констант:

BOOTSEG = 0x7c00 — Сюда поместит загрузочный сектор BIOS.

INITSEG = 0×600 — Сюда его переместим мы.

SYSSEG = 0×100 — А здесь приятно расположится наше ядро.

DATA_ARB = 0×92 — Определитель сегмента данных для дескриптора.

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, #0×100.

repnz.

movsw.

jmpi go, #0.

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

go:

mov ax, #0xF0.

mov ss, ax.

mov sp, ax.

;Стек разместим как 0xF0:0xF0 = 0xFF0.

mov ax, #0×60.

;Сегменты для данных ES и DS зададим в 0×60.

mov ds, ax.

mov es, ax.

Наконец, можно вывести победное приветствие. Пусть мир узнает, что мы смогли загрузиться! Поскольку у нас есть все-таки целый BIOS, воспользуемся готовой функцией 0×13 прерывания 0×10. Можно, конечно, его презреть и написать напрямую в видеопамять, но у нас каждый байт команды на счету, а байт таких всего 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,#0×03.

;прочитаем текущее положение курсора,.

;дабы не выводить сообщения где попало.

xor bh, bh.

int 0×10.

pop cx.

mov bx,#0×0007.

;Параметры выводимых символов:

;видеостраница 0, атрибут 7 (серый на черном).

mov ax,#0×1301.

;Выводим строку и сдвигаем курсор

int 0×10.

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, так что тут все будет попроще. Но для начала нам нужно прочитать ядро. Опять воспользуемся готовой функцией 0×2 прерывания 0×13.

recalibrate:

mov ah, #0.

mov dl, #FLOPPY_ID.

int 0×13.

;проведем реинициализацию дисковода.

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 0×13.

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 #0×64, al.

mov al, #0xDF.

;включить A20.

out #0×60, 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:0×8000). Ядро позже заберет все данные и будет их использовать для вывода на экран победного сообщения.

save_cursor:

mov ah,#0×03.

;читаем текущую позицию курсора.

xor bh, bh.

int 0×10.

seg cs.

mov [0×8000], dx.

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

cli.

lgdt GDT_DESCRIPTOR.

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

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

align 4.

.word 0.

GDT_DESCRIPTOR: .word 3 * 8 — 1 ;

;размер таблицы дескрипторов.

.long 0×600 + GDT.

;местоположение таблицы дескрипторов.

.align 2.

GDT:

.long 0, 0.

;Номер 0: пустой дескриптор

.word 0xFFFF, 0.

;Номер 8: дескриптор кода.

.byte 0, CODE_ARB, 0xC0, 0.

.word 0xFFFF, 0.

;Номер 0×10: дескриптор данных.

.byte 0, DATA_ARB, 0xCF, 0.

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

mov ax, #1.

lmsw ax.

;прощай реальный режим. Мы теперь.

;находимся в защищенном режиме.

jmpi 0×1000, 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, 0×0.

cmpl %eax, 0×100 000.

je 1b.

pushl $ 0.

popfl.

Вызовем долгожданную функцию, уже написанную на С: call SYMBOL_NAME (start_my_kernel). И больше нам тут делать нечего.

Поговорим на языке высокого уровня (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++) ≠ «.

Показать весь текст
Заполнить форму текущей работой