Yabu.log

色々勉強するブログです。

30日OS自作本20日目

20日目は1文字を表示するAPIを作りました。これでOSに配備したアプリケーションからOSの機能を呼び出すことができます。

f:id:yuyubu:20180613053734p:plain

API

APIを作ります。プログラムからOSの機能を呼ぶ部分です。コマンドラインに書いたコマンドからOSの機能を呼び出します。

C言語で作ったput_charをアプリケーション(OSにコピーした実行ファイル)から呼びます

/**
 * コンソールに指定した文字を1文字書きます
 * @param cons 描画先のコンソール
 * @param chr  描画する文字コード
 * @param move 制御用フラグ 0:カーソル位置を進めない
 */
void cons_putchar(struct CONSOLE *cons, int chr, char move)
MOV      AL,'h'
CALL    2*8:0xbe8 ;
  • ALに文字を入れてセグメント2(OS領域)の0xbe8を呼ぶとconsoleにALに入れた文字がでる

そんなシンプルなAPIになります。

引数を扱う専用の処理を呼び出す

アセンブリからC言語の関数を引数付きで呼べないらしいのでcons_putcharを直接呼ばず、引数を扱う専用の処理(_asm_cons_putchar)を一旦呼びます。

ALに文字コードを入れた状態で、OSのセグメント0xbe8にロードされる_asm_cons_putcharを呼びます。

C言語で定義した関数に引数を渡すにはスタックに引数となる値を積む必要があります。それがC言語ではできないそうです。*1

MOV      AL,'h'
CALL    2*8:0xbe8 ;

引数専用の処理でスタックに引数をつむ

_asm_cons_putchar:
        PUSH    1               ; move:moveフラグ
        AND     EAX,0xff        ; AHやEAXの上位を0にして、EAXに文字コードが入った状態にする。
        PUSH    EAX             ; chr:表示対象の文字コード
        PUSH    DWORD [0x0fec]  ; cons:コンソールの値
        CALL    _cons_putchar
        ADD     ESP,12            ; スタックに積んだデータを捨てる
        RETF

スタックに積んでいる順がpopした際にcons_putcharの引数として適当な順番になっていることが確認できます

※push命令を順序を変えずに抜粋。

PUSH    1               ; move:moveフラグ
PUSH    EAX             ; chr:表示対象の文字コード
PUSH    DWORD [0x0fec]  ; cons:コンソールの値
void cons_putchar(struct CONSOLE *cons, int chr, char move)

_asm_cons_putcharの以下の部分は何をやっているのかよくわかりません。抜いても動作は変わらず、正しく文字表示ができました。

AND      EAX,0xff        ; AHやEAXの上位を0にして、EAXに文字コードが入った状態にする。

consの渡し方だけ意味不明なので別途解説します

void console_task(struct SHEET *sheet, unsigned int memtotal)
{
  //抜粋
    struct CONSOLE cons;
    cons.sht = sheet;
    cons.cur_x =  8;
    cons.cur_y = 28;
    cons.cur_c = -1;
    *((int *) 0x0fec) = (int) &cons;

書き込み対象のコンソールの情報consを0x0fecに書き込んでいます。ここをアセンブリで参照してスタックに積んでいます。

PUSH DWORD [0x0fec]  ; cons:コンソールの値

今のままだと、consの番地がOSとアプリケーションに決めうちになっています。

23bitレジスタ,16bitレジスタ,8bitレジスタ復習(EAX,AL等)

一応補足ですが、EAX,AX,AL,AHは全てEAXの一部分です。

EAX:32bitレジスタ AX:16bitレジスタ AL: 8bitレジスタ AH: 8bitレジスタ

ALはEAXのどこの部分か

レジスタがEAXのどの部分に当たるかを図にしました。

EAX:■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■  
AX :□□□□□□□□□□□□□□□□■■■■■■■■■■■■■■■■  
AL :□□□□□□□□□□□□□□□□□□□□□□□□■■■■■■■■  
AH :□□□□□□□□□□□□□□□□■■■■■■■■□□□□□□□□  

IDT経由で割り込みとしてシステムコールを呼び出す

MOV      AL,'h'
CALL    2*8:0xbe8 ;

この文字出力APIは番地が決めうちになってしまっています。OSの開発が進むにつれこの番地が変わった場合、 新しい番地に書き直す必要があります。このAPIを読んでいる箇所を全て書き直す必要があります。

これは問題なので関数をIDTに登録し、割り込み命令扱いとし、INT命令で呼び出すように変更します。

[BITS 32]
        MOV     AL,'h'
        INT     0x40
        MOV     AL,'e'
        INT     0x40
        MOV     AL,'l'
        INT     0x40
        MOV     AL,'l'
        INT     0x40
        MOV     AL,'o'
        INT     0x40
        RETF

APIを変更し、INT 0x40で呼び出せるようにしました。上記でhelloと表示されます。 また本書ではIDTテーブルに限りがあることから、INT 0x40の処理にswitch文的な機能を持たせてedxで処理を切り替えるように作ってあります。

プログラムのメモリのハードコードを割り込みテーブルを使って解決するというのは、ちょっと強引な気がしました。本来の使い方とはかけ離れていると思うのですが、一般的なやり方なのでしょうか。

追記:Linuxでは0x80で呼び出すそうです。

http://kzlog.picoaccel.com/post-104/

アプリケーションからOSの機能を呼び出すのは、ソフトウェア割り込みが一般的なんですかね。そういえばLinuxもint 0x80だし。その他、コールゲートというのがあるのは知っているけれど、他にはあるのかな。

感想

この本はなんかライブ感?がある感じで(毎回挨拶からスタートしてる)これもしかして草稿は30日で書き上げられたのかな?とふと思いました。

*1:あくまで本書に書いてあることの私の理解です。もしかしたら最近のアセンブリは出来るようになっている気もする。