OSを自作してみる8 ~簡易libc(printfなど)を自作 & メモリマップ取得~



久しぶりな自作OS回。

 
※過去回の記事とソースコードは下記から入手できる
https://github.com/Shadow5523/osdev/blob/master/README.md
また、ここで使用するコードはGitHubからダウンロードできる。
https://github.com/Shadow5523/osdev/releases/tag/version0.7.0
 
 

目次

 
 

今回やったこと

前回との変更点は以下の通り。

  • ファイルが増えてきたのでフォルダを追加して各ソースファイルを移動した
  • 簡易的なlibcを自作した
  • メモリマップを取得できるようにした
  •  
    libcの自作では簡易的なprintf()の実装やitoa()実装などのCの標準ライブラリに記述されている、よく使いそうな関数を実装した。
    また、メモリマップの取得ではブートローダ(GRUB)がカーネル起動時にメモリマップ情報を格納した構造体へのポインタをEBXレジスタへ格納するのでそれを読みだして表示してみる。

     
     

    簡易libcを自作

    よく使う関数などを記述したlibcを自作してみる。なお、ここで作成するlibcの関数は「sh_libc」フォルダの配下に作成し、関数名の接頭辞に「sh_」を付ける。
    まずはデータ表示などでよく使うprintf()を作ってみることにした。

    今回作製したprintf()書式指定子は以下の変換指定子のみを対応させてみた。

  • %s : 文字列表示
  • %c : 文字を表示
  • %i : 10進数を文字列として表示
  • %d : 10進数を文字列として表示
  • %x : 16進数を文字列として表示
  •  
    また、10進数/16進数に限り長さを指定することのも可能になっている。(あとで文字列とかも対応させる…)書式は以下の通り。
    %0[長さを255以内で指定][変換指定子]
     
    1.自作printf()のソースコードを以下に表示する。ファイル名は「stdio.c」とし、関数名は「sh_printf()」とする。libcのprintf()のソースコードでは受け取った可変長引数を、va_start()でva_list型変数「parameter」を初期化し代入、その値と可変長引数と一緒に受け取った文字列「format」とともにvprintf()へ渡している。なので、実際のprintf()の処理のほとんどはこのvprintf()で行っている。その後、va_end()関数を呼び出し可変長引数にアクセスした後の後処理を行う。(va_end()が詳しく何をやってるのかはわからないが、とりあえずva_start()したらva_end()しなきゃいけないと覚えといたほうがいいっぽい…)

     
     
    2.次にsh_printf()の本体とも言える存在であるsh_printf()のソースは以下の通り。単純に「format」文字列で%の文字が見つかるまでwhileで回して、%の次の文字、つまり変換指定子の種類でswitch文の処理を決定している。

     
     
    3.下の関数達は、実際に画面に表示する為にterminal_write()にデータを渡している。

     
     
    4.次にsh_printf()で使用しているsh_itoa()を自作する。数値を文字列へ変換する関数。(libcのitoa()と同じ) sh_libcフォルダに以下をstdlib.cを作成し記述する。

  • void sh_itoa(unsigned long data, unsigned char* str, unsigned int base)
    …dataで受け取った数値データを文字列に変換しstrへ格納する。baseで基数を指定する。(16進数なら「16」)
  •  
     
    5.文字列操作系の関数を集めたファイルstring.cをsh_libcフォルダに作成する。作る関数は以下の通り。

  • size_t sh_strlen(const uint8_t* str) … strの長さをsize_t型で返す関数(libcのstrlen()と同じ)
  • void sh_strrev(unsigned char* str) … str文字列の並びを反転させる関数(libcのstrrev()と同じ)
  • char* sh_strcpy(char *s1, const char *s2) … s2の内容をs1へコピーする関数(libcのstrcpy()と同じ)
  • char* sh_strcat(char *s1, const char *s2) … s1にs2を連結させる関数(libcのstrcat()と同じ)
  •  
    ソースは以下の通り。

     
     
    6.math.cをsh_libcフォルダに作成する。内容はとりあえず累乗の計算をしてくれる「sh_pow()」のみ。

  • double sh_pow(double x, double y) … xのy乗を求めて返す関数。(libcのpow()と同じ)
  • 7.sh_libcフォルダにincludeフォルダを作成し、中に上記で作成したソースファイルのヘッダファイルをそれぞれ作成する。
    //stdio.h

     
    //stdlib.h

     
    //string.h

     
    //math.h

     
     

    メモリマップを取得

    将来的にはメモリ管理も行う予定なので、OSで認識しているメモリマップ全体を取得する。

    メモリマップの取得には主にBIOSを使用して行われるが、GRUBをブートローダとして使用している場合、GRUBがメモリマップを検出し、「multiboot_info構造体」の形式で保存する。
    その後、保存された構造体へのポインタをEBXレジスタに格納する為、それを読みだせばメモリマップを簡単に取得できる。

    1.まずboot.sを開き、以下のように編集。EAXとEBXに格納されているデータをkernel_main()で受け取る為、pushコマンドでスタックへ格納する。

     
     
    2.EBXレジスタに格納されたメモリマップを取得するために構造体を「multiboot.h」に定義する。(multiboot.hを元に作製した)

     
    実際にメモリマップの情報を格納する「multiboot_memory_t」の内容は以下の通り。

  • size … 構造体のサイズ(このメンバを除いた)
  • base_addr_low … メモリアドレスの下位ビット
  • base_addr_high … メモリアドレスの上位ビット
  • length_low … メモリ領域サイズの下位ビット
  • length_high … メモリ領域サイズの上位ビット
  • type … そのメモリ領域の状態を表す(0x1 ~ 0x5まである)
  •  
     
    3.メモリマップを画面上に表示するための関数「getmmap()」を memoryフォルダを作製し、その配下に「getmmap.c」を作製し記述する。

     
     
    4.kernel.cで呼び出せるようにincludeフォルダ配下に「getmmap.h」を以下のように作製する。

     
     
    5.kernel.hにさっき作ったヘッダファイル2つをインクルードするように記述。またsh_libcの「math.h」もインクルードするように追加させる。

     
     
    6.kernel.cでgetmap()を呼び出す行をkernel_main()内に記述。

     
     

    フォルダ構成の変更

    1.ソースコードのファイルが結構増えてきたのでフォルダを新たに作り分けてみた。作ったフォルダと格納したソースファイルは以下の通り。機能別に分けてみている。

     
     
    2.フォルダの変更に伴い、Makefileも修正していく必要がある。(フォルダごとにMakefileを作って多段makeしたかったけど次回…)
    変数を宣言し、ソースファイルへのパスを書いていく。

     
     

    確認

    1.まず、GRUBで取得しているメモリマップを確認する。GRUBのCUIプロンプトに入り、「lsmmap」を実行する。(メモリは3.5GB割り当てている)

     
     
    2.次にOS本体を起動してみて、表示されているメモリマップをGRUBのCUIプロンプトで取得したものと比較してみる。同じであればOk。

    Leave a Reply

    Your email address will not be published. Required fields are marked *