OS自作入門 -Advent20-
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 image
とmake write
に関わるところが不要なので削除。
XMODEM転送も不要なのでその辺りのオブジェクトファイルも削除。
TARGETをkzload => kozosに変更。
Hello Worldのビルド
make
でビルドができる。出力の中にkozos
とkozos.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になっているのもちょっと分からない。読み戻ってみるか。
- 前の記事
英語の勉強「must」「have to」-Advent19- 2017.12.19
- 次の記事
英語の勉強「something like that」-Advent21- 2017.12.21