Продовжуємо писати свою ОС. Інтерпретатор у 279 байт
Що таке операційна система?
Це така програма, яка дозволяє створювати та координувати дочірні програми. Розділяти між дочірними процесами пам”ять, ресурси, запускати та знищувати їх. Програма, яка дозволяє дочірним програмам абстрагуватись від HardWare або предоставляє свої інтерфейси для різних типових задач. Саме по цим параметрам і відрізняються існуючі ОСі. Можете переконатись.
Що таке інтерпретатор?
Це програма, яка
- дозволяє користувачу ввести рядок символів,
- потім аналізує його,
- проводить певні дії в зв”язку з результатами аналізу
- і повторює даний алгоритм знову, починаючи з першого пункту.
Конкретно, інтерпретатором є командний рядок. ( Але командний рядок не є операційною системою!)
Ну що ж, тепер я відаючий! Гайда кодити!
Почнемо з чогось простенького. Наприклад, такого:
;============================= ;-- Макроси FASM-у, для більшої структурності ;============================= PUT_CHAR_FUNC = 0x0E BIOS_VIDEO = 0x10 READ_KEY = 0x00 KEYBOARD = 0x16 ;----------------- ;-- Simple routines for basic I/O ;----------------- ; they change AX ;MODIFIES ; AL - input char will be there SimpleGetKey: mov ah, READ_KEY int KEYBOARD ret SimplePutChar: mov ah, PUT_CHAR_FUNC int BIOS_VIDEO ret ;types EOL SimplePutEnd: mov al, 10 call SimplePutChar mov al, 13 call SimplePutChar ret
Тепер, наприклад, викликавши функцію SimpleGetKey (call SimpleGetKey), ми отримаємо в регістрі AL аскіі-код символу. А щодо останньої функції: виявляється, символ №13 виконує ту дію, яку повинна виконувати кнопка “стрілочка вниз”, а №10 – ту дію, яку виконує кнопка “Home”. Тому комбінація цих двох символів і буде являтись переводом каретки. Ось така особливість БІОСа…
Паскаль-рядки
В своїй програмі я буду використовувати Паскаль-рядки, а не ASCIIZ, оскільки недоліки останніх в загальному гірші для структурності та програмування. Паскаль-рядок – це такий масив символів, у якому довжина цього масиву задається у нульовому байті.
Вивід рядка на екран
Отже, ось три еквівалентні функції виводу паскаль-рядку на екран.
; Puts a string with length in zero-index byte to screen and types EOL ; USES ; SI - adress of string ; Мій особистий перший варіант )) PutPASCAL: xor cx, cx ; CH <- 0 lodsb ; AL <- [SI]; SI++ // read first byte - string length mov cl, al ; CL <- AL mov ah, PUT_CHAR_FUNC ; AH <- 0x0E nextpascalchar: lodsb ; AL <- [SI]; SI++ int BIOS_VIDEO ; put char in AL to screen loop nextpascalchar ; loop this actions for CX symbols ret ; Попитав я людей і написав такий оптимізованіший по розміру варіант PutPASCAL_opt: movzx cx, byte[si] inc si mov ah, 0Eh next_pascal_char: lodsb int 10h loop next_pascal_char ret ; МНР-подібний алгоритм. використав тільки MOV, INC, LOOP PutPASCAL_algorithm: ; Вивід рядка, алгоритм. mov ah, 0Eh ; AH <- 0x0E mov al, [si] ; AL <- mem[SI] inc si ; SI++ mov cx, 0 ; CX <- AL mov cl, al ; next_pascal_char_algo: ; mov al, [si] ; AL <- mem[SI] int 10h ; вивести символ в AL на екран inc si ; SI++ loop next_pascal_char_algo ; CX <- CX-1 / goto next_pascal_char_algo if CX=0 ret
Звісно, у своїй програмі я використаю ту, в якій менше коментарів ))) Насправді, бо вона найменша – всього лиш 12 байт в машинних кодах.
Зчитування рядка з екрану
Тут також приведу два алгоритми – один свій, інший чужий оптимізованіший.
;------------------ ;-- GetString ----- ; Gets an #13 ended string from input device (like readln in Pascal) ;------------------ ;USES ; DI - destination, where to write string ;MODFIES ; AX - length of string ; DI - adress of first free memory (adress of string end+1) ; BX - adress of string start GetString_: mov bx, di mov al, 0 stosb loop_read: call SimpleGetKey cmp al, 13 jz finish_input stosb call SimplePutChar jmp loop_read finish_input: mov ax, di sub ax, bx dec ax mov byte[bx], al ret GetString: mov bx, di mov byte[bx],-1 inc di mitka: call SimpleGetKey ;считали клавишу в AL stosb call SimplePutChar ;также нужно вывести ее на екран, чтобы юзерь видел, что вводит inc byte[bx] ;длина строки cmp al, 13 ;пока не нажмут Enter jnz mitka ret
Звісно, чужу функцію використаю, 20 байт (мій варіант – 26 байт). Тепер у нас є функції вводу-виводу, ура! Можна приступати до структурної частини розробки ПЗ.
Алгоритм
1. Зчитали рядок
2. Шукаємо в зв”язному списку введений рядок
3.а Якщо знайшли, то переходимо до коду виконання і запускаємо його
3.б Якщо не знайшли, то запускаємо спеціальну ф-цію NOTFOUND (насправді, не така вона вже й спеціальна, адже знаходиться в нашому зв”язному списку).
4. Починаємо все спочатку
Структура зв”язного списку
Наш список (список зв”язаний з кінця) буде складатись з іменованих процедур, я їх буду називати “словами”. Кожне слово у собі містить:
- Адресу попереднього “слова” (2 байти). Якщо немає попереднього – то нуль.
- Паскаль-рядок – назва слова (у нульовому його байті довжина рядка)
- Повноцінний код на асемблері, який буде асоціюватись з даним словом
- Команда ret – вихід з “слова”
Власне, дивіться:
;--------------------------- ;-- Different data ------- ;--------------------------- ; Дані для ядра. last_word dw HELLO ; last_word вказує на останнє визначене слово ;--- ; NOTFOUND - повинно бути першим. NotFound_code не видаляти. Інше можна змінити NOTFOUND: dw 0 db 8,'NOTFOUND' NotFound_code: nop mov si, nf_help call PutPASCAL_opt ret nf_help db 29,'Sorry Dave, I cannot do that.' ; Далі йдуть юзерські слова.... HELLO: dw NOTFOUND db 12,'Hello world!' mov si, hell_help call PutPASCAL_opt ret hell_help db 8,'Hi dude!' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Всі юзерські функції повинні описуватись вище даного місця. ; Звідси починаються системні змінні, як наприклад ось ця ; Тимчасовий буфер при зчитуванні рядка з клавіатури. InputBuffer db 0
Ядро
Нарешті ви готові подивитись на ядро мого інтерпретатора. Я коментував його, не шкодуючи символів, щоб не дай бог ніхто не сказав про мене, що я пишу незрозумілий код.
;-------------------------- ;-- Console_GetString ----- ; Gets an #13 ended string, but in console-like view. Sets SI to adress of input buffer ;-------------------------- ;USES ; nothing ;MODIFES ; AX - trash ; DI - adress of last free memory ; SI - adress of current word, input buffer Console_GetString: mov al, '>' call SimplePutChar mov al, ' ' call SimplePutChar mov di, InputBuffer ; Ось ядро цієї функції call GetString ; Виділив його коментаріями серед купи декораторів call SimplePutEnd ret ;++++++++++++++++++++ ;-- MAIN ------- ;++++++++++++++++++++ ; Ядро операційної системи (інтерпретатора) ; Мінімізував як міг. Пожертвував зрозумілістю main: ; Точка входу у вічний цикл інтерпретатора. <-------- call Console_GetString ; Зчитуємо, що вводить юзер у InputBuffer | mov bx, [last_word] ; BX <- адресу останнього слова | search_loop: ; Цикл пошуку слова серед інуючих <---------------- | mov si, InputBuffer ; Наступні 5 команд - перевірка на рівність рядків: | | mov di, bx ; записаного у полі "Назва" існуючого слова і | | add di, 2 ; введеного у InputBuffer | | movzx cx, byte[di] ; | | repe cmpsb ; | | jz EQUAL ; Якщо ми знайшли слово, то виконати його! | | mov bx, [bx] ; BX <- попереднє слово | | or bx, bx ; Якщо BX != 0 (тобто, слова ще присутні у списку), | | jnz search_loop ; то продовжити шукати ------------------------ | mov di, NotFound_code ; Ой! Уже кінець, а слово не знайдене | ; ^[брудний хак] )) | EQUAL: ; | inc di ; обчислюємо початок коду... | call di ; ... і запускаємо його | call SimplePutEnd ; перевід рядка | jmp main ; і все спочатку -------------------------------------
Демонстрація роботи
Ну що? Будете дивитись, як воно працює? =)
Якщо чесно, я збрехав у назві теми. Мій інтерпретатор насправді можна зменшити ще на третину (або навіть половину!), але тоді він буде мего-неструктурованим, мего-захаченим, не буде вміти писати “Hi dude!” і, що найважливіше, НЕ БУДЕ ВМІТИ МАЛЮВАТИ ФРАКТАЛ МАНДЕЛЬБРОТА!!!
Дякую, що прочитали мій постик! Як винагорода –
Код повністю
;============================= ;-- Макроси для FASM-у, для більшої структурності ;============================= PUT_CHAR_FUNC = 0x0E BIOS_VIDEO = 0x10 READ_KEY = 0x00 KEYBOARD = 0x16 ;------------------------------------------------- ; MBR clear ;------------------------------------------------- ORG 7C00h use16 ;++++++++++++++++++++ ;-- MAIN ------- ;++++++++++++++++++++ ; Ядро операційної системи (інтерпретатора) ; Мінімізував як міг. Пожертвував зрозумілістю main: ; Точка входу у вічний цикл інтерпретатора. <-------- call Console_GetString ; Зчитуємо, що вводить юзер у InputBuffer | mov bx, [last_word] ; BX <- адресу останнього слова | search_loop: ; Цикл пошуку слова серед інуючих <---------------- | mov si, InputBuffer ; Наступні 5 команд - перевірка на рівність рядків: | | mov di, bx ; записаного у полі "Назва" існуючого слова і | | add di, 2 ; введеного у InputBuffer | | movzx cx, byte[di] ; | | repe cmpsb ; | | jz EQUAL ; Якщо ми знайшли слово, то виконати його! | | mov bx, [bx] ; BX <- попереднє слово | | or bx, bx ; Якщо BX != 0 (тобто, слова ще присутні у списку), | | jnz search_loop ; то продовжити шукати ------------------------ | mov di, NotFound_code ; Ой! Уже кінець, а слово не знайдене | ; ^[брудний хак] )) | EQUAL: ; | inc di ; обчислюємо початок коду... | call di ; ... і запускаємо його | call SimplePutEnd ; перевід рядка | jmp main ; і все спочатку ------------------------------------- ;----------------- ;-- Simple routines for basic I/O ;----------------- ; they change AX ;MODIFIES ; AL - input char will be there SimpleGetKey: mov ah, READ_KEY int KEYBOARD ret SimplePutChar: mov ah, PUT_CHAR_FUNC int BIOS_VIDEO ret ;types EOL SimplePutEnd: mov al, 10 call SimplePutChar mov al, 13 call SimplePutChar ret ;------------------ ;-- GetString ----- ; Gets an #13 ended string from input device (like readln in Pascal) ;------------------ ;USES ; DI - destination, where to write string GetString: mov bx, di mov byte[bx],-1 inc di mitka: call SimpleGetKey ;считали клавишу в AL stosb call SimplePutChar ;также нужно вывести ее на екран, чтобы юзерь видел, что вводит inc byte[bx] ;длина строки cmp al, 13 ;пока не нажмут Enter jnz mitka ret ;-------------------------- ;-- Console_GetString ----- ; Gets an #13 ended string, but in console-like view. Sets SI to adress of input buffer ;-------------------------- ;USES ; nothing ;MODIFES ; AX - trash ; DI - adress of last free memory ; SI - adress of current word, input buffer Console_GetString: mov al, '>' call SimplePutChar mov al, ' ' call SimplePutChar mov di, InputBuffer ; Ось ядро цієї функції call GetString ; Виділив його коментаріями серед купи декораторів call SimplePutEnd ret ;-------------------- ;-- Console_PutPASCAL ------- ; Puts a string with length in zero-index byte to screen and types EOL ;-------------------- ; USES ; SI - adress of string ; MODIFIES ; SI - end of string ; CX - 0 ; AX - trash PutPASCAL_opt: movzx cx, byte[si] inc si mov ah, PUT_CHAR_FUNC next_pascal_char: lodsb int BIOS_VIDEO loop next_pascal_char ret ;--------------------------- ;-- Different data ------- ;--------------------------- ; Дані для ядра. last_word dw MAND ; last_word вказує на останнє визначене слово ;--- ; NOTFOUND - повинно бути першим. NotFound_code не видаляти. Інше можна змінити NOTFOUND: dw 0 db 8,'NOTFOUND' NotFound_code: nop mov si, nf_help call PutPASCAL_opt ret nf_help db 29,'Sorry Dave, I cannot do that.' ; Далі йдуть юзерські слова.... HELLO: dw NOTFOUND db 12,'Hello world!' mov si, hell_help call PutPASCAL_opt ret hell_help db 8,'Hi dude!' ; Mandelbrot plotter, 61 bytes - Tenie Remmel ; Надіюсь, вам сподобається ) MAND: dw HELLO db 4,'MAND' push es mov ax, 0 mov di, 0fffeh push 0A000h pop es mov al, 013h ; установка видеорежима int 10h ; 320х200 256 цветов stosw ; [es:di] <-- ax, di <-- di+2 mov cl, 200 ; cl <-- 200 outer_loop: mov si, 320 ; si <-- 320 inner_loop: mov bp, 79 ; bp <--- макс. число шагов xor bx, bx ; p <-- 0 xor dx, dx ; q <-- 0 complex_loop: push dx ; сохраняем q в стеке mov ax, bx ; ax <-- p sub ax, dx ; ax <-- p - q add dx, bx ; dx <-- q + p imul dx ; dx:ax <-- p^2 - q^2 mov al, ah mov ah, dl ; ax <-- (p^2 - q^2) / 256 pop dx ; dx <-- q из стека xchg bx, ax ; bx <-- (p^2 - q^2)/256, ax <-- p sub bx, si ; bx <-- (p^2 - q^2)/256 + 256x imul dx ; dx:ax <-- p*q shld dx, ax, 9 ; dx <-- p*q / 128 sub dx, cx ; dx <-- p*q / 128 + 256*y test dh, dh jg plot_color ; если q >= 256, ----v dec bp ; bp-- jne complex_loop ; если bp != 0, ----^ plot_color: xchg bp, ax stosb ; экран <-- bp dec si ; si-- jne inner_loop ; если si != 0, ----^ loop outer_loop ; cx раз ----^ pop es call SimpleGetKey mov ax, 2 int 10h ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Всі юзерські функції повинні описуватись вище даного місця. ; Звідси починаються системні змінні, як наприклад ось ця ; Це для того, щоб не створювати межі для вводу символів. InputBuffer db 0 times (512-2-($-7C00h)) db 0 db 055H, 0AAH
Для підсвітки коду використав http://tohtml.com/asm/, стиль black.
Опа. А я вже подумав що ти збацав інтерпретатор Форта на 279 байт. І на ньому ганяєш програмки для фракталу Мандельброта.
Але все одно клас…
bunyk
1 Березня, 2010 at 19:55