OS自作入門 -Advent20-

NO IMAGE

Step6 もう一度、Hello World 前半

帰ってきたHello Worldとのことで、 1step目でやったHello WorldはROMに書き込んで直接実行していたものだった。このstepではブートローダーを使用しRAM上でHello Worldが実行できるようにしていく。

エントリ・ポイント

前回でELF形式の実行ファイルのセグメント情報を読み取れるようになったのでRAM上に展開していく。このときどのアドレスから実行すれば良いのかを考えるが、その際の実行開始アドレスのことをエントリ・ポイントと呼ぶ。

ELF形式の場合は、エントリ・ポイントの情報はELFヘッダの中に領域が割り当てられているためそこの値をみて実行開始するのが良い。このようなエントリ・ポイントのアドレスにジャンプすることを俗に「実行を渡す」「CPUを渡す」とか言う。

確かにreadelfの結果にはエントリ・ポイントを格納する領域があるのが分かる。

"Hello World"の作成

前回はただ読み込んでセグメント情報を出力するだけであったが、RAM上にコピーするように変更を加えていこう。だがその前にブートローダーが実行するプログラムを作成する。Hello WorldがRAM上のプログラムから出力されるようにファイルを追加していく。

  • 今回追加するファイル
    • ブートローダー
      なし
    • OS
      • defines.h(ブートローダーのものと同じ)
      • lib.h, lib.c(ブートローダーのものと同じ)
      • serial.h, serial.c(ブートローダーのものと同じ)
      • startup.s(ブートローダーのものと同じ)
      • main.c(main関数とHello Worldの出力)
      • ld.scr(リンカ・スクリプト)
      • Makefile
  • 今回修正するファイル
    • ブートローダー
      • elf.h, elf.c(メモリ上へのコピーとエントリ・ポイントの対応)
      • main.c(エントリ・ポイントからの起動を追加)
    • OS
      なし

RAM上に展開する

elf.cのエントリ・ポイント対応を行う。セグメント情報に応じてメモリ上にコピーする処理と、エントリ・ポイントを取得するサービスを追加する。elf.cのようになる。ただのセグメントの情報出力だった部分をmemcpyを使ってメモリ上にコピーを行なっている。phdr->file_sizeのサイズがphdr->memory_sizeでのサイズに満たない場合は余った領域をmemset()でゼロクリアするようにしている。これはBSS領域の準備。

多くの場合値のゼロやNULLポインタはメモリ城のバイトパターンもゼロなので、余った部分をゼロクリアしておくことで初期値なしの変数をゼロとして初期化ができるようになる。

エントリ・ポイントの取得

先の変更分にはエントリ・ポイントの取得の修正も入っている。load_elf関数の返り値がエントリ・ポイントとなるようにしている。

エントリ・ポイントからの起動

load_elfの結果がエントリ・ポイントのアドレスとなったので、これをmain.cで取得し実行できるようにしていく。ここでのポイントは関数ポインタかな。このサイトが結構わかりやすかった。関数名ではなく関数のポインタだけでも実行が可能である。リフレクションとかどうやって実装されているのか気になるなぁ。

Hello Worldプログラムの作成

これまで作ったライブラリやシリアル・ドライバなどは使いまわす。ただブートローダとOSのディレクトリを以下のように変えていく。

├── 01
│   └── bootload
├── 02
│   └── bootload
├── 03
│   └── bootload
├── 04
│   └── bootload
├── 05
│   └── bootload
├── 06
│   ├── bootload
│   └── os

main.c, ld.scr, Makefileには修正が必要なので修正していく。もともとブートローダにあったやつをコピーして修正していくが以下のような変更になる。エントリ・ポイントの指定はリンカ・スクリプトのENTRY("_start")という箇所が起点になる。関数ポインタで指定しているf()の結果この_startから開始される。

  • main.c
    initで行なっていた処理はそれぞれ次のような理由で消去

    • データ領域のコピー
      VA≠PAの対策であるが初めからRAM上で動作させるので不要
    • BSS領域のクリア
      プログラムのロード時にBSS領域のクリアを行なってくれる。
    • シリアルデバイスの初期化
      ブートローダ側で行なっているので不要
  • ld.scr
    RAM上の領域のみを記載。
    ram領域はELFヘッダとプログラムヘッダテーブルが作成されるため先頭に空きを用意し、0xffc020とした。
    vector, bufferセクションも不要なので除去。
    あとは配置先がromであるものをramにする。
  • Makefile
    make imagemake writeに関わるところが不要なので削除。
    XMODEM転送も不要なのでその辺りのオブジェクトファイルも削除。
    TARGETをkzload => kozosに変更。

Hello Worldのビルド

makeでビルドができる。出力の中にkozoskozos.elfができていればOK。readelfをしてELFファイルの内容を確認するとresult.txtになっている。

セクションヘッダ:
  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00ffc020 000074 000374 00  AX  0   0  2
  [ 2] .text.startup     PROGBITS        00ffc394 0003e8 000078 00  AX  0   0  2
  [ 3] .rodata           PROGBITS        00ffc40c 000460 000042 00   A  0   0  4
  [ 4] .bss              NOBITS          00ffc44e 0004a2 000020 00  WA  0   0  1
  ...
プログラムヘッダ:
  タイプ       オフセット 仮想Addr   物理Addr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00ffbfac 0x00ffbfac 0x004a2 0x004a2 R E 0x1
  LOAD           0x0004a2 0x00ffc44e 0x00ffc44e 0x00000 0x00020 RW  0x1

セクションヘッダを見ると全てRAM上に配置されているのが確認できる。またプログラムヘッダを見るとVA=PAになっているのも分かる。


今日はこの辺で。PAとVAがちょっと混乱してきたな。なんでmain.cでロードしたやつが全部PAでロードしてるのかとか、それでもVA=PAになっているのもちょっと分からない。読み戻ってみるか。