Mikan OSを自作してみる #7
今日やった内容
※アセンブリはコードのこと、アセンブラはそのコードを翻訳するプログラムやソフトウェアのこと
学習記録
<レジスタとは>
レジスタはCPUに内蔵された記憶領域である。CPUの外部にあるメインメモリとは役割が似ているが、メインメモリに比べてレジスタは容量が小さく読み書きが速いという特徴がある。
レジスタには汎用レジスタと特殊レジスタがある。
汎用レジスタ: CPUの演算対象に指定できる。 x86-64の汎用レジスタは、RAX, PBX, RCX, RDX, RBP, RSI, RDI, RSP, R8~R15の16個がある。
(使用例1)
アセンブリで加算命令addの場合
add rax, rbx ; オペコード(add) オペランド1(引数), オペランド2 (;はコメント)
これは rax += rbx; と一緒である。
特殊レジスタ値の記憶に加えて色々な役割があり、代表例は以下。
・RIP:CPUが次に実行する命令のメモリアドレスを保持するレジスタ
・RFLAGS:命令の実行結果によって変化するフラグを集めたレジスタ
・CR0:CPUの重要な設定を集めたレジスタ
(例2)
loop: dec rax ; decは減算命令でレジスタの値を1減らし、RAXがちょうど0になるとZFが1になる。rax-= 1 jnz loop ; jnzはJump if Not ZeroでZFが0の場合ジャンプする。 今回はRAXから1を引いた結果が0でない間、ループする。
<アセンブリの理解>
(1)
Z3foov: ; c++の関数foo push rbp ; push命令は、RSPレジスタの値を8だけ減らして RSPレジスタが指すメモリ領域にrbpレジスタの値をスタックの末尾に書き込む。 mov rbp, rsp ; mov命令は左を右にコピーする。rbp = rsp ; mov dword ptr [rbp -4] , 42 ; int i = 42 ; ; dwordは4バイトであることを意味する ;[xxx]をアドレスxxxへのメモリアクセスと認識する ;メモリアドレスがRBP-4へのメモリアクセスとなり、ここに42が書き込まれる
(2)
; intは4バイト(dword)でポインタは8バイト(qword) ; int* p = &i と同じで以下の(1),(2)の処理はポインタ*p に iのアドレスをコピーする ;lea命令は変数の実行アドレスを取得する ; rax = rbp - 4で変数iのアドレス取得、つまり&iと同じ lea rax, [rbp - 4] ;(1) rax にrbp-4のアドレスを格納 ; rbp-16を先頭とする8バイトのメモリ領域が変数pに対応する ;qwordは8バイトでptrはデータの型を指定する演算子でqword ptrで[rbp -16]が8バイトであることを表す mov qword ptr [rbp - 16], rax ;(2)アドレス値raxをrbp-16から8バイトのメモリ領域に書き込む ; int r1 = *p; mov rax, qword ptr [rbp - 16] ; raxにrbp-16から8バイトの値を覚えさせる (変数iのメモリアドレス) mov ecx, dword ptr [rax] ; ecxにraxが指すメモリ領域から4バイトを読む mov dword ptr [rbp-20], ecx ; ecxをrbp-20から4バイトのメモリ領域に書き込む ; *p + 1; mov rax, qword ptr [rbp - 16] ; raxにrbp-16から8バイトを覚えさせる mov dword ptr [rax], 1 ; 1をraxから4バイトのメモリ領域に入れる ; int r2 = i; mov ecx, dword ptr [rbp - 4] ; ecxにrbp-4から4バイトを覚えさせる mov dword ptr [rbp - 24], ecx ; ecxをrbp-24から4バイトのメモリ領域に入れる ; uintptr_t addr = reinterret_cast<uintptr_t>(p); mov rax, qword ptr [rbp - 16] ; raxにrbp-16から8バイトを覚えさせる mov qword ptr [rbp - 32], rax ; raxをrbp-32から8バイトのメモリ領域に入れる ; int* q = reinterpret_cast<int*>(addr); mov rax, qword ptr [rbp - 32] ; raxにrbp-32から8バイトを覚えさせる mov qword ptr [rbp - 40], rax ; raxをrbp-40から8バイトのメモリ領域に入れる pop rbp ret
Mikan OSを自作してみる #6
今回やった内容
- メモリマップについて学んだ
- ポインタについて学んだ
メモリマップとは
メモリマップとはメインメモリのどの部分がどの用途で行われているかが載っている地図のこと
(メインメモリは揮発性のRAMであり、作業領域のイメージ )
フィールド名 | 型 | 説明 |
---|---|---|
Type | UNIT32 | メモリ領域の種別 |
PhisicalStart | EFI_PHYSICAL_ADDRESS | メモリ領域先頭の物理メモリアドレス |
VisualStart | EFI_VIRTUAL_ADDRESS | メモリ領域先頭の仮想メモリアドレス |
NumberOfPages | UNIT64 | メモリ領域の大きさ(4KB ページ単位) |
Attribute | UNIT64 | メモリ領域が使える用途を示すビット集合 |
アドレスとポインタ
ポインタは、指すという意味があり、C言語では変数のアドレスという整数値を格納する変数である。(ポインタ変数)
int x; /* xはint型の変数 */ x = 5; /* 変数xに5を代入 */ printf(" x:%d\n", x); /* xの値を表示 */ printf(" &x:%p\n", &x); /* xのアドレスを表示 */ int *p_x; /* p_xはポインタ変数 */ p_x = &x; /* ポインタ変数p_xにxのアドレスを代入 */ printf(" p_x:%p\n", p_x); /* p_xの値を表示 */ printf("*p_x:%d\n", *p_x); /* *p_xの値を表示 */ 結果 x:5 &x:0x7ffeed0dca38 (ランダム) p_x:0x7ffeed0dca38 *p_x:5
演算子 | 名前 | 説明 |
---|---|---|
& | アドレス演算子 | オブジェクトのアドレスを取り出す |
* | 関接演算子 | それが指し示すものの中身(オブジェクト(アドレス)に格納されている値) |
ポインタのポインタ
ポインタ変数にアドレス演算子&を適用すること 、UEFIプログラミングでは頻繁に使うらしい。
void f(int* p){ *p = 42; } int g() { int x = 1; int* p = &x; f(p); return x; } g()を実行すると戻り値は42になる int xの初期値は1だが、f(p)の中で42に書き換わった。
Mikan OSを自作してみる #5
今回やった内容
- メモリマップについて学んだ
- ポインタについて学んだ
メモリマップとは
メモリマップとはメインメモリのどの部分がどの用途で行われているかが載っている地図のこと
(メインメモリは揮発性のRAMであり、作業領域のイメージ )
フィールド名 | 型 | 説明 |
---|---|---|
Type | UNIT32 | メモリ領域の種別 |
PhisicalStart | EFI_PHYSICAL_ADDRESS | メモリ領域先頭の物理メモリアドレス |
VisualStart | EFI_VIRTUAL_ADDRESS | メモリ領域先頭の仮想メモリアドレス |
NumberOfPages | UNIT64 | メモリ領域の大きさ(4KB ページ単位) |
Attribute | UNIT64 | メモリ領域が使える用途を示すビット集合 |
アドレスとポインタ
ポインタは、指すという意味があり、C言語では変数のアドレスという整数値を格納する変数である。(ポインタ変数)
int x; /* xはint型の変数 */ x = 5; /* 変数xに5を代入 */ printf(" x:%d\n", x); /* xの値を表示 */ printf(" &x:%p\n", &x); /* xのアドレスを表示 */ int *p_x; /* p_xはポインタ変数 */ p_x = &x; /* ポインタ変数p_xにxのアドレスを代入 */ printf(" p_x:%p\n", p_x); /* p_xの値を表示 */ printf("*p_x:%d\n", *p_x); /* *p_xの値を表示 */ 結果 x:5 &x:0x7ffeed0dca38 (ランダム) p_x:0x7ffeed0dca38 *p_x:5
演算子 | 名前 | 説明 |
---|---|---|
& | アドレス演算子 | オブジェクトのアドレスを取り出す |
* | 関接演算子 | それが指し示すものの中身(オブジェクト(アドレス)に格納されている値) |
ポインタのポインタ
ポインタ変数にアドレス演算子&を適用すること 、UEFIプログラミングでは頻繁に使うらしい。 ''' void f(int p){ p = 42; } int g() { int x = 1; int* p = &x; f(p); return x; }
g()を実行すると戻り値は42になる int xの初期値は1だが、f(p)の中で42に書き換わった。 '''
Mikan OSを自作してみる #4
今回やった内容
- EDK2について学んだ
- ビルド環境の構築を行なった
学習記録
EDK2
<EDK2とは>
はUEFI BIOS自体の開発とUEFI BIOS 上で動くアプリケーションの開発にも使うことができる開発キットである。
<EDK2のファイル構造>
edk2/ edksetup.sh 環境変数設定用スクリプト Build/ ビルドの成果物が出色されるディレクトリ Conf/ target.txt ビルド設定(何をビルドするかを設定) tools_def.txt ツールチェーンの設定 Mdge/ EDKの中心的ライブラリのパッケージディレクトリ ...Pkg/ その他のパッケージディレクトリ ConfとBuild以外のディレクトリはパッケージごとに別れている。 <パッケージ構造> AppPkg/ AppPkg.dec パッケージ宣言(declaration)ファイル AppPkg.dsc パッケージ記述(description)ファイル Applications/ UEFIアプリケーションを格納するディレクトリ Hello/ Hello モジュールディレクトリ Hello.c Hello.inf モジュール定義ファイル decファイルはパッケージ名やパッケージ内のソースコードから利用する定数を定義する。 dscファイルは出力ディレクトリ名やサポートされるアーキテクチャ、サポートするビルドターゲットなどのビルドに関する設定を書く。
Buildディレクトリはbuildコマンドによって成果物が出力される。
AppPkgをビルドすると
Build/AppPkg/DEBUG_GCC5/X64/Hello.efi などに目的の EFI アプリが出力されるらしい。edksetup.sh
EDK2のビルドコマンドが動くようにするための準備用スクリプト
target.txt
(設定項目)
・ACTIVATE_PLATFORM : ~/ *.dsc
ビルド対象のパッケージの .dscファイルを指定する。
DSCファイルとはパッケージ記述ファイルのことであり、主にパッケージのビルド設定を書くファイルである。
・TARGET : DEBUG
DEBUG、RELEASE、NOOPT、UserDefinedのいずれかを設定する。
・TARGET_ARCH:X64
どのアーキテクチャ向けのバイナリを作るか。(例)X64 、ARM
・TOOL_CHAIN_TAG:
ビルドに用いるツールチェインとtools_def.txtの Supported Tool Chainsから選ぶ。
ビルド環境の構築
ビルド環境の構築はほぼ以下のリンクの通りに行いました。 github.com
10月14日
詰まった、、、、 どうやらubuntuのアーキテクチャはx86_64でMac_M1のUTM上で動くubuntuはarm64のものしかなさそうなのでDokerを使わなければならないかも、、、、10月15日
AMD64のubuntu (ubuntu-18.04.6-desktop-amd64.iso) をUTM上に実装してみたがM1とは相性が悪く動作が重すぎたため使い道にならない 次にintel core i5のMac bookでvirtual box を使って ubuntuを実装すると動作はスムーズに進んだ10月16日
ブートローダーのビルドができない、、、、、
これ以降1ヶ月近く放置していました。
- 現在(11月11日)
よくわからないけどbuildできた!!! 原因はよくわからないが edk2ディレクトリの生成を失敗していたのからなのか、 それかsudo apt update
で保留の項目が出ていたのをsudo apt-get dist-upgrade
でアップデートさせたらからなのだろうか、、、 多分後者だと思う。
Mikan OSを自作してみる #3
今回やった内容
- OSを使わずに起動して「Hello World!」を表示させた
ブートローダーについて
<ブートローダーとは>
OSをメインメモリに読み込み起動させるプログラムのことである
<必要な理由>
パソコンには実行するプログラムはメインメモリに配置しなければならないという制約があり、メインメモリは電源を落とすと内容が消えてしまう仕組みになっている。そのため、ストレージにOSを記録し、ブートローダーを用いてストレージからメインメモリに読み出すようにする
<開発内容>
UEFIではストレージを読み書きする機能を持っているため、ストレージ装置のプログラムであるデバイスドライバを作る必要がない
OSを使わずにHello worldさせる
OSを使わずに起動して「Hello World!」を表示させるプログラムを作る
Ubuntuでoktetaというバイナリエディタを使ってバイナリで書かれたプログラムのEFIファイルを作った
今回は試験用PCでの実行が上手くいかなかったのでこのプログラムをパソコンの代わりに、ソフトウェアで仮想的なパソコンを再現する「QEMU」というエミュレータで実行する
UFI BIOSでUSBに保存したファイルを実行する場合の流れ
- 作成したefiファイルをUSBに保存し、PC起動時に「Hello World」させる場合
起動後CPUはBIOSの実行を開始する
BIOSはコンピュータを初期化した後、接続されているストレージを探索するようになっており、ストレージの中に実行ファイルを見つけると、BIOSはそのファイルをメインメモリへ読み出す。そして、COUはBIOSの実行を中断し、読み出したファイルの実行を開始する
次やる内容
- 次回はEDK ⅡというUEFI用の開発キットを使い、Hello world を再実装する
Mikan OSを自作してみる #2
OS自作ではlixnuコマンドを多用するので今回はコマンドの復習をします。
ファイルの基本操作
ディレクトリの作成
- ディレクトリを作成する
mkdirを使う
mkdir 作成するディレクトリのパス
~/Desktopにtestディレクトリを作成する
$ mkdir ~/Desktop/test
ディレクトリの削除
- ディレクトリを削除する
rmdirを使う
rmdir 削除するディレクトリのパス
しかし、実行するには対象ディレクトリの中身が空でないと実行できない
- ディレクトリを丸ごと削除する
rm -r 削除するディレクトリのパス
- 削除するかどうかを確認する
$ rm -ri 削除するディレクトリのパス
これを実行することで一つ一つ確認しながら削除することができ、削除するファイルと削除しないファイルを選ぶことができる
コマンドの結果をファイルに書き出す
コマンドの結果をファイルに書き出すことをリダイレクトと呼び、記号「 > 」を使う
コマンド > ファイルのパス
- calコマンドの出力をリダイレクトする
$ cal > cal.txt
実行するとカレントディレクトリに「cal.txt」がない場合は作成し、ある場合は上書きをする
- 作成したファイルの中を見る
$ cat cal.txt
- catコマンドとリダイレクションを使用してテキストファイルを作成する
$ cat > mail.txt recursivd@example.com makoto@ecample.com Saori@ecample.com (Ctrl + D で終了)
$ cat mail.txt recursivd@example.com makoto@ecample.com Saori@ecample.com
- リダイレクションを使用したファイルのコピー
「mail.txt」を「myMail.txt」にコピーする
$cat mail.txt > myMail.txt
$ cat Mymail.txt recursivd@example.com makoto@ecample.com Saori@ecample.com
(余談) 普段実行しているコマンド入力は標準入力をファイルにリダイレクトして、データをファイルから読み込んでいる。
$ cat < cal.txt = $ cat cal.txt
- ファイルに追加する 「>>」
「>>」を使うことで上書きではなくファイルに出力を追加することができる
$ cat >> mail.txt yamagata@example.com aaaaaaaa@example.com [ Ctrl + D]
$ cat mail.txt recursivd@example.com makoto@ecample.com Saori@ecample.com yamagata@example.com aaaaaaaa@example.com
コマンドをパイプでつないで実行する
パイプはコマンドの実行結果を別のコマンドに渡してさらに処理を行う機能
2つのコマンドをパイプで結びつける
コマンド1 | コマンド2
- calコマンドの実行結果を「cal.txt」に追加する
$ cal 2021 | cat >> cal.txt # cal 2021を実行し、その出力を cat.txt に追加する
$ cat cal.txt
cal.txtには2021年10月のデータだけだったが2021年のカレンダーも追加された
ファイルのコピー
cp -R コピー元のディレクトリ コピー先のディレクトリ
「~/Desktop/test_2/」の中に「test」ディレクトリをコピーする
$ cp -R ~/Desktop/test ~/Desktop/test_2 $ ls ~/Desktop/test_2/ test
$ ls test example.txt $ cp -R ~/Desktop/test ~/Documents/ $ ls ~ /Documentst/test example.txt
- ディレクトリを丸ごと別の名前でコピーする
$ cp -R ~/Desktop/test/ ~ /Documents/test_sample # testディレクトリをtest_sampleという名前でコピーする
既に「test_sample」ディレクトリが存在するなら下に「test」ディレクトリが作成される
- 特定の複数のファイルをコピーする
$ cp *.txt ~/Documents/text_files/ # カレントディレクトリにある拡張子がtxtのファイル全てをコピーする $ cp ~/Desktop/*.txt ~/Documents/text_files/ # ~/Desktopにある拡張子がtxtのファイル全てをコピーする
*は0文字以上で?は1文字
ファイルの移動
- ファイルの移動
mv 元のファイルのパス 移動先のディレクトリ
「~/mail.txt 」を 「~/Documents/」に移動させる
$ mv ~/mail.txt ~/Documents/ $ls ~/Documents/ mail.txt
mvコマンドは名前の変更もできる
「~/Documents」ディレクトリの下の「mail.txt」を「oldMail.txt」という名前に変更する
$ mv ~/Documents/mail.txt ~/Documents/oldMail.txt
「~/Desktop」ディレクトリ下の「test」ディレクトリを「~/Documents」ディレクトリ下に「Mytest」ディレクトリとして移動
$ mv ~/Desktop/test/ ~/Documents/Mytest/ # ディレクトリの名前を変えて移動 # 同じ名前のディレクトリが存在していた場合は「Mytest」の下に「test」を移動
- 複数のファイルを移動する 「test.txt」と「example.py」を「sample」ディレクトリに移動する
$ mv test.txt example.py sample/ $ ls sample test.txt example.py
シンボリックリンク
シンボリックリンクはショートカットのようなもの
ln -s 元のファイルのパス シンボリックリンクのパス # 元のファイルのパスはシンボリックリンクからの相対パスにするか絶対パスにする
- ファイルのシンボリックリンクの作成
カレントディレクトリのファイル「test.txt」のシンボリックリンクを、カレントディレクトリに「sample.txt」として作成する
$ ln ~s test.txt sample.txt
これで、「test.txt」は「sample.txt」という別名でアクセスできるようになる
カレントディレクトリの中にMikanLoaderPkgという名前で$HOME/workspace/mikanos/MikanLoaderPkgを指すシンボリックリンクを作成する。
$ ln -s ~/workspace/mikanos/MikanLoaderPkg ./ # カレントディレクトリは 「.」
- シンボリックリンクの削除
unlink シンボリックリンクのパス
Mikan OSを自作してみる #1
「ゼロからの OS 自作入門」を読んで分かったことや進捗を記事に書きます。
環境
・開発PC : MacBook Pro((13-inch, 2017, Two Thunderbolt 3 ports)上でUbuntu 20.04.1LST
・検証PC: Windows10の予定
参考本: ゼロからの OS 自作入門 | ゼロからのOS自作入門
ビルド環境の構築 : GitHub - uchan-nos/mikanos-build: Build and run scripts for MikanOS