OSの超簡単なメモリ管理を実装するおはなし

この記事は Aizu Advent Calendar 2013 6日目の記事です。
前日は「Aizu Advent Calender 5日目(ダイエット成功したかも知れません)」でした。

おはようございます。
もぷりです。
最近すっかり寒いですが、皆様いかがお過ごしでしょうか。
私は今、胸部に痛みを感じつつキーを叩いております。

さてさて、今年もAdventな季節がやってまいりました。

今回は自分の作ってるOSでとりあえず実装した簡単なメモリ管理について書きます。
間違いなどの指摘があれば@Mopp_jpでもissuesでもなんでもよいのでぜひ言ってください。

リポジトリはこれです。

まず、メモリの管理とは
「OS内でハードウェアからのメモリ情報を使って
動的なメモリ確保を行えるようにする。」
ということです。

もっと簡単に言うと使っているのか使っていないのかを自動で判断してメモリ割り当てをしてくれるって意味です。


順を追って説明をします。

まず、OSはブートローダというOSを起動するだけ+αのソフトウェアから起動されます。
具体的にはgrubとかLILOですね。
で、このブートローダはBIOSが起動します。

BIOS → ブートローダ → OS → ζ*'ヮ')ζ<うっうー

このように起動していきます。
ちなみに、もぷりんOSでは色々とめんどかったのでgrubから起動されること前提で作っています。

先に言ったように、ブートローダはOSを起動するだけ+αです。
ですが、とても大切ですので簡単に説明します。

OSの実行ファイルをデバイスから探す
OSの実行ファイルをメモリに読み込む
ハードウェア情報、BIOSコールの戻り値などをメモリに置いておく
読み込んだOSの実行ファイル先頭部分へジャンプ

この4ステップです。
シンプルですねー(※1ステップ目以外)

このOSが起動した時点で、メモリについて考えてみます。
はい、なにもないですね。
ですので、とりあえずOSはセグメントというものを設定します。
セグメントもメモリに関係するものでメモリの使用用途や権限を定めるものです。
(※セグメントも大事なことなのですが今回は説明しないですすいませんです。)

他にも割り込みなどいろいろと設定するものがあります。
どれもこれもコンピュータ上のデータであるからにはメモリ上に存在していなければなりません。
しかし、まだ、動的メモリ確保は行えませんので以下のようにします。


メモリを直書きしてキャストです。
変数をグローバルにおいても別にいいのですが、配列などの大きなデータを入れてしまうのはサイズ的に気が引けます。
出来れば、ポインタだけで済ませたいです。
場合によってサイズが変化する場合などなおさら動的に確保して無駄を減らしたいです。

そもそも、こういうことをたくさんやるのは良くないですし
いちいちアドレスを気にするのも面倒です。

そこで、やっと本題のメモリ管理です。
メモリ管理の初期化は2ステップです。
メモリ情報取得
それを元にメモリ空間をリスト構造化

使うときには普通のC言語と同様にmallocとfreeで扱えます。

1ステップ目
BIOSコールを使って、コンピュータのメモリ情報を取得します。
grubであれば取得データを渡してくれます。
この情報にはメモリの領域が使えるか使えないかという部分が入っています。

使えない部分というのはブートローダに処理が渡った際、既にデータが置いてあって、書き込みされると困る部分です。
そこには割り込みベクタなどがあります。

これは特に問題ないですね。

2ステップ目
メモリ管理には有名なものでビットマップを用いるものと連結リストを用いるものがあるようです。
簡単に説明をします。

ビットマップ方式
    固定長(4KBくらい)でメモリを区切って、その一つを1bitに割り当てて、使っている、使っていないを判断します。
リスト方式
    可変長でメモリを区切って、ここからここまで使ってる、ここからここまで使っていない、という風に判断します。

後々便利そうだったのでもぷりんOSでは連結リストを使用しました。
ファイルはこいつです。
https://github.com/mopp/Axel/blob/advent/src/doubly_linked_list.c

さて、コードをさらーっと見ていきます。

ここでOS起動時の初期化で呼ばれる関数はinit_memory()です。
引数にはgrubのくれるメモリ情報、とそのサイズが渡されます。

L57init_memory_list()で、メモリ管理に使用するデータとリストのノードを初期化します。
> dlst_nodes = (struct dlinked_list_node*)(get_kernel_end_addr() + sizeof(Memory_manager));
> mem_info = (Memory_info*)(dlst_nodes + sizeof(struct dlinked_list_node) * MAX_MEM_NODE_NUM);

> mem_manager = (Memory_manager*)get_kernel_end_addr();
これはカーネル(OS)の読み込まれた末尾のメモリを使用するためにアドレスの計算をしています。
get_kernel_*関数の中身を見ればわかりますが、メモリ直書きと大差ないです。

次にL60-L80でメモリ情報を元にリスト構造化します。
set_meminfo()関数を使って、ベースアドレスとそこからの長さをリストのデータに持たせます。

その後にL82-L117でカーネル部分のメモリを使用中にしておきます。
ここを忘れては元も個もありません(TODOは見なかったことにしてください)

初期化はこれだけです。

あとは使うだけですのでmallocとfreeを用意します。


mallocでは受け取ったサイズが入る領域を持つノードを探して行って、見つかったらその先頭部分から新たにノードを作り、使用中にします。
これをfirst-fit方式といいます。
手っ取り早くていいですね。

freeは確保時に返したアドレスを探していき、見つかったら空き領域とし
可能ならば前後のノードとまとめる、ということを行います。


はい、以上です。
OSのメモリ管理なんてもっと難しいのかと思っていたら案外そんなことはありません。
わかりにくかったらそれはきっとこの記事が悪いのでしょう。

といっても、はりぼてにすらならない穴だらけOSなのできちんと作っていきたいです。

明日、Aizu Advent Calendar 2013、7日目はmotiさんです。

コメント

このブログの人気の投稿

カーソルキーさん@つかわない インサートモード編

Android で MIME Type 判別

Elixir に入門したいので雑な 分散KVS を自作した