C言語でOSを書く

ブート - 計算機メモGRUBでブートするところまで書きましたので
次に簡単にC言語カーネルを書いてみようと思います(GRUBのところを読んでいなくてもできます)
「GRUB」の検索結果 - OSのようなもの(「OSのようなもの」さん)を参考に致しました
まず僕が作ったOSで実験しようと思います。

セットアップ

そこでまずcid-ba66e6bd45624584.skydrive.live.com/browse.aspx/.Public?uc=1cid-ba66e6bd45624584.skydrive.live.com/browse.aspx/.Public?uc=1をダウンロードしてください(/tmp以下に展開)
イメージファイルを操作する必要があり、マウントをユーザから行えるようにします
/etc/fstabに以下の一行を追加してください

/dev/mapper/VolGroup-lv_root / ext4 defaults 1 1
UUID=c5af66a8-5410-4b0b-9fdc-978fbcc762b1 /boot ext4 defaults 1 2
/dev/mapper/VolGroup-lv_swap swap swap defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
/tmp/hdd.img /mnt/hdd ext2 user,loop=/dev/loop1,offset=32256,sync 0 0<-これ
まずダウンロードしたファイルを展開します

cd /tmp
tar xzf hatena.tar.gz
mkdir ~/ownOS
mv hatena ~/ownOS/hatena
cd ~/ownOS/hatena
マウント先のディレクトリを作成します

#mkdir /mnt/hdd
それでmount.shをユーザで実行してみてください(注 mount.shの中身は非常に単純ですので確認してください)

sh mount.sh
そしてマウント先のファイルとディレクトリの所有者を現在のユーザに変更してください

chown -R ユーザ:ユーザ /mnt/hdd
これで準備が整いました
次にQemu上で実行してみましょう

make

説明

ではファイルの説明をしたいとおもいます
ファイルの構成

hatena
├── Makefile
├── hdd.img
├── kernel
│ ├── Makefile
│ ├── kmain.c*
│ ├── linker.ld*
│ ├── loader.nas*
│ └── multiboot.h
├── mount.sh
└── umount.sh
kernelディレクトリ内にカーネルを構成するファイルが置かれていています
Makefileについては解説を省き、kmain.c linker.ld loader.nasについて説明します
カーネルであるkernel.binはkmain.cおよびloader.nasコンパイルしリンクすることで生成されます
kernel.binは先頭にloader.o(loader.nasコンパイルしてできるオブジェクトファイル)が配置されます
その後にkmain.oが配置されます
GRUBは処理の最後にloader.oの先頭にジャンプするのでまず処理はloader.oから始まり、loader.oが処理の最後にkmain.cの中のkmain関数を呼び出します
ややこしい説明になってしまいましたが要するにGRUB->loader->kmainという流れで実行されるということです
そしてlinker.ldはリンカスクリプトです
リンカスクリプトとはldコマンドに渡してリンク方法を詳細に制御するためのスクリプトです
次にこれらについて詳しく見ていこうと思います
loader.nas
GRUBから呼び出されるカーネルのエントリポイントです
nasmというアセンブラで記述されています
loader.nasに記述されているのは以下の4つです

  • マジックナンバー
  • スタックの設定
  • kmain関数のコール
  • kmainから帰ってきた場合の処理(HALTして実行を停止)

マジックナンバーGRUBの規程で先頭にマジックナンバーという数値を設定する必要があります(おまじない)
loader.nasはkmainを呼び出す前準備です

linker.ld
kmainとloaderをリンクしカーネルイメージkernel.binを作るためのリンク方法を記述したスクリプトです

ENTRY (loader)
SECTIONS{
. = 0x00100000;
.text :{
*(.text)
}
.rodata ALIGN (0x1000) : {
*(.rodata)
}
.data ALIGN (0x1000) : {
*(.data)
}
.bss : {
sbss = .;
*(COMMON)
*(.bss)
ebss = .;
}
}
kernel.binはELF形式で出力されます(GRUBはELFも取り扱うことができるのです)
一行目の"ENTRY (loader)"でGRUBがkernel.binの中から呼び出す関数を指定します(エントリポイントの指定)
三行目の". = 0x00100000;"はコードやデータがメモリの0x0100000以降に読み込まれるという指定です(データの番地などのアドレスが調整されます)
この記述がないとGRUBが実際にコードやデータを配置するアドレスとカーネルがアクセスするアドレスに齟齬(そご)が生じるでしょう
リンクして最終的に生成されるファイルはコードとデータからなります。それぞれが".text .rodata .data .bss"という名前のセクションに配置されます
.textにはコードが、.rodataと.dataと.bssセクションにはデータが配置されます
このリンカスクリプトではコードセクションを先頭に配置し、他をその後にアライメントしつつ配置しています
試しにobjdumpでkernel.binの中身を確認してください
シンボルがどのように配置されているか確認できます
リンカスクリプトの詳細についてはhttp://www.sra.co.jp/wingnut/ld/ld-ja_3.htmlを参照してください

kmain.c
カーネルのメイン関数という意味でこの名前をつけました
やっていることは画面を真っ白に初期化し"KMAIN"と表示させる事だけです

#include "multiboot.h"
typedef unsigned short u16_t;//unsigned 16bit data type
void putc(u16_t *point,char a){
*point|=a;
}
void kmain(unsigned long addr,unsigned long magic)
{
int i,j;
u16_t *CharMonitor=(u16_t *)0xb8000;
char message[]="****KMAIN****";
//clear screen
for(j=0;j<25;j++)//25行
for(i=0;i<80;i++)//80列
CharMonitor[j*80+i]=0xf020;
for(i=0;i
画面の表示はキャラディスプレーを使っています(ピクセル毎に描画するのは面倒くさいです)
キャラディスプレーは2バイトで一文字分の表示を行います
画面への表示はメモリへ書き込むことによって行われます
そのメモリは0xb8000にマッピングされています
1バイト目が文字の色で2バイト目が文字です。(リトルエンディアンなので実際は逆なんですが)
実行結果
[f:id:KYudy:20100314231910j:image]
以上です
コメントお願い致しますm(__)m