Hatena::ブログ(Diary)

へにゃぺんて@日々勉強のまとめ

2017-12-01

自作OS(OS5)のUEFI+x86_64対応でやったこと/やっていること(そして-fPIEの謎挙動)

少し早いですが、この記事は「自作OS Advent Calendar 2017」の12/3(日)の記事です。


自作OS(OS5GitHub)の「UEFI + x86_64」対応で

  • やったこと(masterブランチへマージ済の内容)
  • やっていること(作業ブランチで作業中の内容)、そして-fPIEの謎挙動

を紹介します。


OS5について

OS5はx86_32のQEMU(qemu-system-i386)を想定したフルスクラッチの自作OSです。CUIのみですが、ブートローダー・カーネルユーザーランド(アプリケーション群)を備え、すべて含めて3000行程度のOSです。

自作OS Advent Calendar 2016」の12/13(火)の記事でOS5を紹介しました


OS5については、今年、"Ohgami's Commentary on OS5"という「全コード + コメンタリー」の同人誌を作り、技術書典やオープンソースカンファレンス等で頒布しました。

PDF版は公開していますので、興味があれば見てもらえるとうれしいです。


その後、"UEFI + x86_64"の自機で動作させたく、「まずは下回りから」ということで、「UEFI」の勉強をしていました。

OS5で十分なブートローダーを作る程度であれば理解できてきたので、11月頃からOS5の「UEFI + x86_64対応」を行っています。


やったこと(masterブランチへマージ済の内容)

主にブートローダーのUEFI+x86_64対応です。


3f0b05dがHEADである現時点で、カーネルユーザーランド(アプリケーション群)をRAMへロードし、カーネルの先頭アドレスへジャンプするといった基本的な事はできます。


Makefileも修正済なので、OS5のソースディレクトリ直下で

$ make fs

を実行すると、UEFI+x86_64対応のブートローダーとブートローダーの動作確認程度のカーネル、x86_32のユーザーランド(x86_64対応未着手)の各バイナリが生成されます。


そして、

$ make run64

を実行すると、QEMU上で実行します。QEMUは"qemu-system-x86_64"コマンドを使用します。

また、QEMUにはUEFIファームウェアが入っていないので、加えてOVMFというオープンソースUEFIファームウェアを使用します。DebianAPTが使用できる環境ならば、"qemu-system-x86"と"ovmf"パッケージをインストールしておいてください。


"make fs"ではfsディレクトリへ各バイナリを生成します。fsディレクトリEFIシステムパーティションの構成になっているため、USBフラッシュメモリの第1パーティション(FATフォーマット済)へ

$ sudo cp -r fs/* /mnt/to/usb/

のようにコピーすれば、USBフラッシュメモリから起動することで実機で動作させることもできます。(ただし、Lenovo Thinkpad以外未確認)


なお、fsディレクトリの構成は以下の通りです。

fs
├── EFI
│   └── BOOT
│       └── BOOTX64.EFI  ← ブートローダー
├── kernel.bin  ← カーネル
└── apps.img    ← ユーザーランド

ブートローダーはユーザーランド(apps.img)が無い場合はロードをスキップする様に作っています。

そのため、kernel.binという名前で置いておけば任意のファイルをブートローダーにロード・実行させることができます。

ただし、ブートローダーはロードしたカーネルの先頭アドレスへジャンプするので、リンク時にはテキストセクションをバイナリ先頭へ配置し、なおかつエントリ関数をテキストセクション先頭に配置する必要があります。


現在のOS5カーネルでは以下のリンカスクリプトを使用しています。

■ os5/kernel/sys_64.ld

OUTPUT_FORMAT("binary");

SECTIONS
{
	.text	: {*(.text)}
	.rodata	: {
		*(.strings)
		*(.rodata)
		*(.rodata.*)
	}
	.data	: {*(.data)}
	.bss	: {*(.bss)}
}

そして、Makefileではオブジェクトファイルのリンク順でカーネル初期化関数がテキストファイル先頭に配置されるようにしています。

■ os5/kernel/Makefile

・・・省略・・・
ifneq ($(x86_64),true)
CFLAGS += -m32
else
CFLAGS += -fPIE    ← (*1)
endif
LDFLAGS = -Map System.map -s -x
ifneq ($(x86_64),true)
LDFLAGS += -m elf_i386 -T sys.ld
OBJS = sys.o cpu.o intr.o excp.o memory.o sched.o fs.o task.o	\
		syscall.o lock.o timer.o console_io.o queue.o	\
		common.o debug.o init.o kern_task_init.o
else
LDFLAGS += -T sys_64.ld
OBJS = init_64.o fb.o    ← "init_64.o"を先頭に
endif

kernel_64.bin: $(OBJS)
	ld $(LDFLAGS) -o $@ $+

kernel.bin: $(OBJS)
	ld $(LDFLAGS) -o $@ $+

%.o: %.S
	gcc $(CFLAGS) -o $@ $<
%.o: %.c
	gcc $(CFLAGS) -o $@ $<

・・・省略・・・

(*1)について、PIEはPosition-Independent Executableの略で位置独立実行形式という意味です。

ブートローダーは、カーネルユーザーランドを配置可能なメモリ領域を検索し、見つけた場所へ配置します。

カーネルの実行バイナリがメモリ領域のどこに配置されても良いように、このオプションをつけています。


その後、ブートローダーは以下のようにカーネルへのジャンプを行います。


現状、カーネルのエントリ関数は、ブートローダーの動作確認程度で、以下の通りです。

■ os5/kernel/init_64.c

#include <efi.h>
#include <fb.h>

int kern_init(struct EFI_SYSTEM_TABLE *st __attribute__ ((unused)),
	      struct fb *_fb)
{
	unsigned int x, y;

	fb_init(_fb);

	for (y = 0; y < fb.vr; y++)
		for (x = 0; x < fb.hr; x++)
			draw_px(x, y, (x + y) % 256, y % 256, x % 256);

	while (1);

	return 0;
}

実行すると↓の絵が出ます。

f:id:cupnes:20171201210742p:image


やっていること(作業ブランチで作業中の内容)

作業ブランチはwip/support_uefi_x86_64_2です。(ブランチは移動するのでタグを打っておきました。)


masterブランチでせっかく対応したPIEなのですが、謎の挙動があり、作業ブランチではPIE対応を外しています。


問題は、-fPIEオプションをつけた場合に生成されるアセンブラが間違っているように見受けられる、というものです。

加えて、-fPIEオプションを付けずともPC(RIP)相対なアセンブラが生成され、実行にも今の所、問題無い、ということもあります。


"-fPIE"オプションをつけた場合の問題(謎の挙動)を以降で説明します。

(ただし、正直、コンパイラが間違うなんて事は考えにくく、自分の認識がどこか間違っているのではないかと思ってはいます。)


問題

別ソースファイル(別オブジェクトファイル)で定義されているグローバル変数へ、extern宣言でアクセスする際、-fPIEを付けてコンパイルすると間違ったアセンブラを生成しているように見える。


確認コード
  • main.c
extern unsigned int foo;
int main(void)
{
	volatile unsigned int bar = foo;
	while (1);
	return 0;
}

  • foo.c
unsigned int foo = 0xbeefcafe;

確認手順

1. main.cとfoo.cをkernel/ディレクトリへ追加

2. 追加したファイルをビルドする様、kernel/Makefileを変更

kernel/Makefile(13〜16行目)

else
LDFLAGS += -T sys_64.ld
OBJS = main.o foo.o  # 変更
endif

3. ビルド、実行

$ make run64

4. QEMUモニター[*2]で実行中のアセンブラレジスタ値等を確認


kernel/MakefileのCFLAGSの-fPIEあり/なしそれぞれで3.と4.を行いました。


[*2] 2.6 QEMU Monitor - QEMU version 2.10.92 User Documentation


確認結果

【-fPIEあり】

.data           0x0000000000000050        0x4
 *(.data)
 .data          0x0000000000000050        0x0 main.o
 .data          0x0000000000000050        0x4 foo.o
                0x0000000000000050                foo

  • QEMUモニターでの確認結果
(qemu) xp/6i 0x100000
0x0000000000100000:  push   %rbp
0x0000000000100001:  mov    %rsp,%rbp
0x0000000000100004:  mov    0x45(%rip),%rax        # 0x100050
0x000000000010000b:  mov    (%rax),%eax
0x000000000010000d:  mov    %eax,-0x4(%rbp)
0x0000000000100010:  jmp    0x100010
(qemu) info registers  <= (*1)
RAX=0000000000000000 RBX=0000000007a43f18 RCX=0000000000400000
RDX=0000000007a43f18
...
(qemu) xp/gx 0x100050  <= (*2)
0000000000100050: 0x00000003beefcafe
(qemu) xp/wx 0x3beefcafe  <= (*3)
00000003beefcafe: 0x00000000

  • 説明

0x100000はmain.cとfoo.cから生成した実行バイナリ(kernel.bin)をブートローダーがロードしたアドレスです。


まず、main.cの

volatile unsigned int bar = foo;

に該当するQEMUモニターで確認したアセンブラは、

0x000000000010000d:  mov    %eax,-0x4(%rbp)

ですので、グローバル変数fooの内容0xbeefcafeは、%eaxへ格納されていなければならないです。


しかし、レジスタ値を見てみると(*1)、RAXは全て0になっています。(EAXはRAXの下位32ビット)


%eaxへ値を格納している処理は以下の箇所です。

0x0000000000100004:  mov    0x45(%rip),%rax        # 0x100050
0x000000000010000b:  mov    (%rax),%eax

ここで、

0x0000000000100004:  mov    0x45(%rip),%rax        # 0x100050

は、"0x45(%rip)"のPC(%rip)相対アドレス計算結果のアドレス(0x100050)ではなく、アドレスが指す先を%raxへ格納します。


0x100050の内容を64ビット幅で確認すると(*2)、0x00000003beefcafe という値が格納されており、この値が %rax へ格納されます。


この後、

0x000000000010000b:  mov    (%rax),%eax

で、%rax(0x00000003beefcafe)が指す先の内容を%eaxへ格納しています。


0x3beefcafe の内容を見てみると(*3)、0が書かれており、

その結果、%eaxへは0が格納されてしまいます。


謎に思うのが、

0x0000000000100004:  mov    0x45(%rip),%rax        # 0x100050

の箇所です。

0x100050はグローバル変数fooのアドレスなので、

lea    0x45(%rip),%rax

であれば%raxに0x100050が格納され、次の

0x000000000010000b:  mov    (%rax),%eax

で、アドレス"0x100050(foo)"に格納されている内容が%eaxへ格納されるため、

無事変数fooの内容を%eaxへロードできるのですが、

なぜコンパイラは"lea"命令ではなく"mov"命令をしようしているのか、謎です。


【-fPIEなし】

.data           0x0000000000000048        0x4
 *(.data)
 .data          0x0000000000000048        0x0 main.o
 .data          0x0000000000000048        0x4 foo.o
                0x0000000000000048                foo

  • QEMUモニターでの確認結果
(qemu) xp/5i 0x100000
0x0000000000100000:  push   %rbp
0x0000000000100001:  mov    %rsp,%rbp
0x0000000000100004:  mov    0x3e(%rip),%eax        # 0x100048
0x000000000010000a:  mov    %eax,-0x4(%rbp)
0x000000000010000d:  jmp    0x10000d
(qemu) info registers
RAX=00000000beefcafe RBX=0000000007a43f18 RCX=0000000000400000
RDX=0000000007a43f18
(qemu) xp/wx 0x100048
0000000000100048: 0xbeefcafe

  • 説明

"-fPIE"を外していますが、

> 0x0000000000100004: mov 0x3e(%rip),%eax # 0x100048

のようにPC相対で値をロードしています。(こういうものなのか。。?)


この場合、"0x3e(%rip)"のアドレス計算結果0x100048が指す先を%eaxへ

格納しており、%eaxへfooの値 0xbeefcafe が格納されます。


補足

kernel/Makefileではld時に-pieの指定が無いですが、

gccの-fPIEとldの-pieを共に指定した場合も上記の"【-fPIEあり】"の結果と同じでした。

2017-08-01

夏コミ(C92)でフルスクラッチのUEFIベアメタルプログラミング本出します!

f:id:cupnes:20170801202423j:image:h350

次の夏コミ(C92)の1日目(8/11)に"東た27b"で↑の本を出します!

(PC画面の写真が表紙です。)


EDK2やgnu-efiといった開発環境やツールチェインを使わず、

エディタコンパイラのみのフルスクラッチUEFIファームウェアを叩く方法を紹介し、

OSっぽいものを作り上げる本です。


既刊の"Ohgami's Commentary on OS5"も持っていきます!

コミケットへお越しの際、興味があればぜひお立ち寄りください!

(既刊同様に、PDF版は頒布日以降に http://yuma.ohgami.jp で公開します。)


目次と表紙については↓の"続きを読む"から


目次

  1. はじめに
    1. poiOSについて
    2. 本書に関する情報の公開場所
  2. Hello UEFI!
    1. ベアメタルプログラミングの流れ
    2. UEFIの仕様に沿ったプログラムを書く
      1. UEFI仕様書の読み方
      2. ソースコードを書く
    3. UEFIファームウェアが実行できる形式へクロスコンパイル
      1. [column] Makefile自動化
    4. UEFIファームウェアが見つけられるように起動ディスクを作成
      1. [column] QEMUで実行する
      2. [column] 日本語表示
  3. キー入力を取得する
    1. EFI_SIMPLE_TEXT_INPUT_PROTOCOL
    2. エコーバックプログラムを作ってみる
      1. 補足: キー入力を待つ(WaitForKey)
    3. シェルっぽいものを作ってみる
      1. [column] 5分のウォッチドッグタイマを解除する
  4. 画面に絵を描く
    1. EFI_GRAPHICS_OUTPUT_PROTOCOL
    2. 矩形を描くサンプルを実装
    3. GUIモードを追加する
  5. マウス入力を取得する
    1. EFI_SIMPLE_POINTER_PROTOCOL
      1. マウスの状態を見てみる(pstatコマンド)
    2. マウスカーソルを追加する
  6. ファイル読み書き
    1. EFI_SIMPLE_FILE_SYSTEM_PROTOCOLとEFI_FILE_PROTOCOL
    2. ルートディレクトリ直下のファイル/ディレクトリを一覧表示(ls)
    3. GUIモードでファイル/ディレクトリ一覧を表示する
    4. ファイルを読んでみる(cat)
    5. GUIモードへテキストファイル閲覧機能追加
      1. [column] getcでスキャンコードも返せるよう修正(オガム文字の領域を使う)
    6. ファイルへ書き込んでみる(edit)
    7. GUIモードへテキストファイル上書き機能追加
  7. poiOSの機能拡張
    1. 画像を表示してみる
      1. blt関数を追加
      2. シェルへ画像閲覧コマンド追加(view)
        1. [column] BGRA32ビットへの画像変換方法
      3. GUIモードへ画像ファイル表示機能追加
      4. [column] blt関数UEFIの仕様に存在します
    2. GUIモードとシェルの終了機能を追加する
      1. シェル終了機能追加
      2. GUIモード終了機能追加
    3. マウスを少し大きくする
    4. 機能拡張版poiOS実行の様子
  8. おわりに
  9. 参考情報
    1. 参考にさせてもらった情報
    2. 本書の他にUEFIベアメタルプログラミングについて公開している情報

表紙について

本の中でも書いていますが、表紙は、UEFIファームウェアを呼び出して画面へ文字を表示した写真です。

実は、裏表紙も合わせると↓の様になっていて、裏表紙と表紙でベアメタルプログラミングのやり方を一通り説明しています。

f:id:cupnes:20170801202417j:image:w800


UEFIは文字をUnicodeで扱うので、日本語を指定することもできます。しかし、UEFIファームウェアUnicodeで指定できるすべての文字のフォントを持っているわけではないので、表紙画像のように表示できない文字もあります。


UEFIの仕様書には色々な機能が書かれており、この本でも仕様書を参照しながらソースコードを書く方法を説明しています。

ただし、UEFIはあくまでも仕様で、PCに搭載されているファームウェアは仕様に従ってはいますが、すべての仕様を実装しているわけでは無い様です。

# 表紙は、「仕様のすべてがそのまま動くとは限らないよ」という事を暗に示していたりします(後付け)。

2017-04-06

技術書典2で同人誌を出します!

お知らせです。


4/9(日)に秋葉原UDX 2Fのアキバ・スクエアで開催される

技術書典2」という技術書限定の同人誌即売会で、

OS5の同人誌を出します!

f:id:cupnes:20170406203528p:image:h240

配置は「え-15」で、

サークル紹介ページはこちらです。


OSを作っていて、「ソースコードを印刷して本にしてみたい」という想いがありまして、

「Lions' Commentary on UNIX」をまねて、

「Ohgami's Commentary on OS5」という「ソースコード + コメンタリー」の本にしてみました。


OS5はQEMU上で動きますので、

当日はRaspberry Pi 3のRaspbian上のQEMUで動かすデモも設置します。

技術書典2へ来ることがあれば、立ち寄ってみてもらえると嬉しいです。

# とはいえ、CUIなので、見た目としてはそれほど面白いものでも無いのですが。。


なお、紙の本ではなくPDFデータに関しては、4/9(日)以降、

http://yuma.ohgami.jp

からダウンロードできるようにしますので、

こちらも、興味があれば見てみてください。

 


加えて、

実は、UEFIの勉強として、UEFIファームウェアが持つ機能を呼び出す形でOSを作ってみています。

(現状はUEFI Shellみたいなものです。)

こちらも、当日はデモとしてお見せできると思います。(ノートPCのバッテリーが保つ限り)


# まだお見苦しいですが、ソースコードこちらです。

2017-01-23

ファイルシステムでマルチブロックに対応しました

自作OS(OS5)の紹介とユーザーランド周りの機能追加について - へにゃぺんて@日々勉強のまとめ

こちらの記事の続きで、自作OS(OS5)のアップデートについてです。


今回はファイルシステムでマルチブロックに対応しました。

これまで、ファイルシステムの仕様上、各ファイルには「ブロックサイズ(4KB)以下」の制限がありました。

マルチブロックに対応したので、今後、この制限は無くなります。


コミット数としては11コミットと、いつものアップデートに比べるとコミット数は少ないですが、

カーネル周りについては一段落ついたかなというとことで、この段階でリリースします。


タグを付けてGitHubへpushしています。

今回のリリースのソースコードtar ballは以下からダウンロードできます。

また、OS5については以下のページにまとめています。


シェル上での操作や見た目に変化はないので、

今回、GIFアニメはありません。


今回のアップデートのコミットについては以下のとおりです。


boot,kernel,app: ファイルシステムのマルチブロック対応

  • 810c266 2017-01-09 boot: extend load size 43KB
  • 4df3d0b 2017-01-09 kernel: fs: support multi block file at fs_init
  • 4a90fdc 2017-01-09 kernel: mem: move stack to 0xffff e000 in VA
  • dbc6d96 2017-01-09 kernel: task: support multi block file at task_init
  • 68fdd01 2017-01-09 kernel: fs: change app entry point to 0x20000030
  • 414de77 2017-01-09 kernel: task: update task_exit
  • 035c0ed 2017-01-09 kernel: task: extend user stack 4KB
  • 99c7ddd 2017-01-09 Revert "Revert "tool: fs: support the file size exceeds block size""

ブートローダーでRAMへのロードサイズを増やし(810c266)、

カーネルでマルチブロック対応の修正を行いました。(4df3d0b..035c0ed)


その上で、前回リリース時にrevertしていたユーザーランドファイルシステム生成スクリプトへの修正をrevertしました(ユーザーランド生成スクリプトへの修正を反映させました、99c7ddd)。


app: libkernelへの機能追加

  • b32cfd2 2017-01-09 app: libkernel: add get_global_counter()

ファイルシステムの仕様上、アプリケーションバイナリサイズが4KBを超えられないために見合わせていたライブラリへの機能追加を行いました。


kernel,app: warning抑制

  • d98bd8d 2017-01-12 kernel: fs: add unused attribute at f parameter in fs_close()
  • 758bdac 2017-01-12 app: add unused attribute at argc and argv parameters

未使用の仮引数についてのwarningを抑制しました。

2016-12-13

自作OS(OS5)の紹介とユーザーランド周りの機能追加について

本記事は、「自作OS Advent Calendar 2016 - Adventar」の12/13(火)の記事です。


自作OS(OS5)について改めての紹介と、今回のリリースで追加した機能の紹介です。


はじめに

OS5は、自分の理解の確認や勉強のために、フルスクラッチで作成しているOSです。

動いている様子はこんな感じです。

f:id:cupnes:20161212064534g:image


今回のリリースを含め、現在の主なスペックは以下のとおり。

  • x86QEMU(qemu-system-i386)で動作
  • 一番作りこめているのはカーネルで、主な機能を一通り備えている
    • 時間管理
    • タスク管理(スケジューラ、タスクローダ)
    • メモリ管理(ページング、ヒープアロケータ)
    • デバイスドライバ
    • システムコール
    • ファイルシステム
  • ユーザーランド(アプリケーション郡)は、まだカーネルの動作確認程度
    • CUIのみ
    • (NEW)コマンドライン引数対応
    • (NEW)静的ライブラリ対応(libkernel、libcommon、libstring、libconsole)
  • RAM上で動作
    • ファイルシステムもRAM上
    • コンベンショナルメモリ(640KB)に収まっている
  • ビルド環境はMakeとGCC
  • スモールスタートとして、シンプルに(割りきって)実装している
    • 一通りの機能を備えたカーネルが2000行程度

作成状況の節目ごとに当ブログで記事にしており、そのタイミングをリリースとしています。

前回のリリースは以下の記事です。


ソースコードはGitで管理しており、GitHub上にリポジトリがあります。

リリースの際は"blog-YYYYMMDD"というタグをプッシュしています。

今回のリリースは"blog-20161213"というタグをプッシュしています。


また、これまでのリリース状況や、GitHubへのリンク、勉強会スライド等を「自作OS(OS5)のまとめ」にまとめています。


今回のリリースでソースコード行数(アセンブラとCの行数の総和)は以下の様になりました。

ブートローダ253行(変化なし)
カーネル1966行 → 2073行
ユーザーランド437行 → 535行

以降では、OS5の各実装について、簡単に説明します。(今回追加した機能についても、併せて説明します。)


ビルドシステム

OS5のビルドを確認しているのはDebian上で、ツールはMakeとGCCを使用しています。


今回のリリースでGCCオプション変更とドキュメントビルド対応を行いました。


GCCオプション変更

開発当初使用していたPCが32ビットであったために、OS5はこれまで、32ビットのDebian上でビルドしていました。PCが64ビットに変わったあとも、特に何も考えず、QEMU上に32ビットのDebian環境を構築し、ビルドしていました。


そんな中、今年の4月に勉強会でOS5を紹介させて頂いた所、GCCオプションを変更するパッチを書いて下さった方が居たので、マージさせていただきました。そのため、今は、amd64のDebian上でビルドしています。


ドキュメントビルド対応

OS5のソースディレクトリ直下のdocディレクトリには、emacsのorg-modeの形式でドキュメントを配置しています。org-mode自体プレーンテキストであり、読み難いものでは無いですが、org-modeは整形されたテキストへの変換が可能なので、make docでこの変換を行うようにしました(要emacs)。


ブートローダ

カーネルユーザーランドのRAMへのロード(BIOS割り込みルーチンを使用)と、CPU設定を行っています。CPU設定では割り込みや、各種ディスクリプタテーブルの設定などを行っています。特に重要なのが16ビットのリアルモードから32ビットのプロテクトモードへの移行です。CPUのモードをプロテクトモードへ移行させた後、カーネルの先頭アドレスへジャンプします。


現状、MBR(512バイト)に収まっており、ブートローダーの多段ブートの様な事はしていません。ソースコードは、単一のアセンブラファイルです。


後述の「カーネル」でも書きますが、「ひとまずタスクは2つまで」と想定した作りこみがあり、今回のリリースで、RAMへロードするセクタ数とGDT(グローバルディスクリプタテーブル)を増やしました。


カーネル

ブートローダーからジャンプしてくると、カーネル初期化処理が実行されます。カーネルの各機能の初期化を行った後は、カーネルは割り込み駆動で動作し、何もしない時はhlt命令でCPUを寝させるようにしています。


カーネルの構成を図にしてみると、以下のとおりです。

f:id:cupnes:20161213054530p:image


カーネルの各機能の実装については、今年の4月に発表させていただいた勉強会のスライドにまとめています。各機能の実装について、図を使って説明していますので、興味があれば見てみてください。


今回のリリースでは、カーネルが起動してから3つ目のタスク(*1)を使用するための修正、schedule()の整理、exitシステムコールとタスク終了イベントの追加を行いました。

(*1)カーネルではx86 CPUのデータシートの言い回しに合わせて「タスク」と呼ぶようにしていますが、現状の実態としては、「アプリケーション」と同一です。


カーネルが起動してから3つ目のタスクを使用するための修正

「ひとまずタスクは2つまで」と想定した作り込みが2つあり、修正しています。


1つ目は、ヒープ領域のサイズです。物理アドレス空間内の配置場所の問題で、これまでヒープ領域は44KBでした。メモリのアロケーション(OS5ではmem_alloc()、4KBずつ確保)を解放無しで11回行うと使いきってしまうサイズです。現状、mem_alloc()はタスクに関連する箇所でのみ、タスク1つ当たりに5回呼ばれます。ファイルシステムとタスクのエントリ追加で2回、ページディレクトリ・ページテーブル・スタック領域の確保で3回です。タスクを2つ起動させるとmem_alloc()が10回呼ばれることになり、3つ目のタスクを起動させるためのメモリを確保できなくなります。ヒープ領域の配置場所を変え、252KBまで拡張しました。


2つ目は、GDT内のCS(コードセグメント)とDS(データセグメント)のディスクリプタの配置場所の変更です。上記のスライドでも説明していますが、OS5ではx86 CPUの持つタスク管理機能を使っています。CPUのタスク管理機能を使うためには、TSS(タスクステートセグメント)を定義する必要があり、そのためのディスクリプタをGDTに配置します。これまでは、GDT内のディスクリプタの並びが

0使用禁止
1CS(カーネル権限用)
2DS(カーネル権限用)
3TSS(カーネルタスク)
4TSS(1つ目のユーザタスク用)
5TSS(2つ目のユーザタスク用)
6CS(ユーザ権限用)
7DS(ユーザ権限用)

となっていたのですが、タスク生成時にTSSをGDTへの配置する場所はインクリメントしていく実装なので、3つ目のタスクのTSSは「CS(ユーザ権限用)」を上書きしてしまいます。そこで、以下の配置へ変更しました。(今思えば、ユーザ権限用のCS・DSを追加する際にTSSの配置換えを行うべきでした。。。)

0使用禁止
1CS(カーネル権限用)
2DS(カーネル権限用)
3CS(ユーザ権限用)
4DS(ユーザ権限用)
5TSS(カーネルタスク)
6TSS(1つ目のユーザタスク用)
7TSS(2つ目のユーザタスク用)

schedule()の整理

sched.cのschedule()がスパゲッティになっていたので整理しました。schedule()を汎用的にしようと思うあまり、schedule()にcause_idという引数を渡すようにしてどこから呼ばれたかを判別し、呼び出し元に応じた処理を行っていました。しかし、これがschedule()をスパゲッティにしてしまう要因であり、呼び出し元に依存する処理は呼び出し元で行うほうがシンプルと考え、schedule()のcause_id引数は廃止し、呼び出し元へ処理を移動しました。


exitシステムコールとタスク終了イベントの追加

exitシステムコールを追加しました。このシステムコールが実行されると、タスクをランキューから外し、タスクのために確保していたメモリを解放します。


また、OS5には、あるイベントが発生するまでタスクを待機させる(ランキューから外す)機能があります。今回のリリースで、タスク終了イベントを追加したので、他のタスクの終了を待つことができるようになりました。なお、現状、このイベントを使用するのはシェルのみで、複数のタスクの終了を待つという状況はないので、「どのタスクの終了を待つか」という実装はありません。


ユーザーランド

アプリケーションとしては、shellとuptime、whoareyouという3つです。現状、カーネルの動作確認程度のものでしかないです。


shellはその名の通りシェルで、CUIを提供します。shellの組み込みコマンドとしては、echoとメモリ/IOへの直接read/writeのコマンド(readb,readw,readl,ioreadb,writeも同様のコマンド名)、そしてbgというコマンドがあります。bgは今回のリリースで追加したコマンドで、引数で指定したコマンドをバックグラウンド実行します。(これまでは、実行したコマンドの終了を待つことができなかったので、常にバックグラウンド実行でした。)uptime・whoareyouもshellから起動します。これらのコマンド名をshell上で入力すると、shellはexecシステムコール(OS5ではexecをシステムコールとしています)を使用して実行します。


uptimeはマルチタスクの動作確認をするためのコマンドです。コンソール画面の右上で、16進数で起動時間をカウントし続けます。自ら終了することがないコマンドなので、バックグラウンド実行しないとプロンプトが帰ってこなくなります。


whoareyouは今回のリリースで新たに追加したコマンドです。argcとargvによりコマンドライン引数を受け取れることを確認するためのコマンドです。


また、今回のリリースでは、main()をエントリポイントとする変更や、静的ライブラリの仕組みも導入しており、よく見るCのソースコードのようにアプリケーションを書けるようになりました。

例えば、whoareyouのソースコードは以下のとおりです。

#include <app.h>
#include <kernel.h>
#include <string.h>
#include <console.h>

int main(int argc, char *argv[])
{
	if ((argc >= 2) && !str_compare(argv[1], "-v"))
		put_str("Operating System 5\r\n");
	else
		put_str("OS5\r\n");
	exit();

	return 0;
}

話は変わって、ユーザーランドのファイルシステムイメージは、makeの過程で、シェルスクリプトで作成します。ファイルシステムは、簡単に、ファイル名とバイナリのみを管理するだけのもので、シェルスクリプトでバイナリを並べて連結しています。ファイルシステムについても詳しくは上述のスライドをご覧ください。


これまでをまとめると、アプリケーションが実行されるまでの流れは以下のとおりです。

  1. ファイルシステムイメージをブートローダーがRAM上の決まったアドレスへロード
  2. カーネルは、初期化の過程でファイルシステムが配置されているRAM上の領域をチェック
  3. カーネルは、ファイルシステム上の1つ目のファイルを、カーネル起動後に実行する最初のアプリとして実行する(ここでshellが実行される)

おわりに

OS5と今回のリリースについて、紹介してみました。


まだまだソースコード行数も大したことなくGUIも無いようなOSですが、自分の興味のままに、作っていきたいなと思っています。


# 実は、GUIという程ではないですが、グラフィックモードで動作させるパッチはあります。

# (ブートローダーでビデオモードをグラフィックのモードへ変更し、カーネルでVRAM空間をアプリケーションのメモリ空間へマップするだけです。)

# - https://github.com/cupnes/os5/tree/test_gui