過程が大事

学んだことを適当にアウトプットします

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であり、作業領域のイメージ )

f:id:recursive1414:20211112141505p:plain
メモリマップの中身

フィールド名 説明
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であり、作業領域のイメージ )

f:id:recursive1414:20211112141505p:plain
メモリマップの中身

フィールド名 説明
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日
    AMD64ubuntu (ubuntu-18.04.6-desktop-amd64.iso) をUTM上に実装してみたがM1とは相性が悪く動作が重すぎたため使い道にならない 次にintel core i5Mac 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 BIOSで動くブートローダーを作る

UEFIではストレージを読み書きする機能を持っているため、ストレージ装置のプログラムであるデバイスドライバを作る必要がない

OSを使わずにHello worldさせる

OSを使わずに起動して「Hello World!」を表示させるプログラムを作る

Ubuntuでoktetaというバイナリエディタを使ってバイナリで書かれたプログラムのEFIファイルを作った

今回は試験用PCでの実行が上手くいかなかったのでこのプログラムをパソコンの代わりに、ソフトウェアで仮想的なパソコンを再現する「QEMU」というエミュレータで実行する

f:id:recursive1414:20211018201701p:plain

UFI BIOSでUSBに保存したファイルを実行する場合の流れ

  • 作成したefiファイルをUSBに保存し、PC起動時に「Hello World」させる場合

起動後CPUはBIOSの実行を開始する

BIOSはコンピュータを初期化した後、接続されているストレージを探索するようになっており、ストレージの中に実行ファイルを見つけると、BIOSはそのファイルをメインメモリへ読み出す。そして、COUはBIOSの実行を中断し、読み出したファイルの実行を開始する

例) EFIファイル : ブートローダーの実行ファイル

次やる内容

  • 次回は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

f:id:recursive1414:20211018165718p:plain

  • 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

f:id:recursive1414:20211018172850p:plain

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

大まかな作成手順

  1. 開発環境でOSのコードを書きコンパイルする (本開発では仮想環境のubuntuを使う)
  2. 生成された実行ファイルをUSBメモリに書き込む
  3. 試作用パソコンにUSBメモリを接続し、実行する

詳しい作成手順

  1. ブートローダーの作成
  2. 画面作成
  3. マウスを使えるようにする
  4. メモリ管理の仕組み作成
  5. ウィンドウ表示
  6. タイマに対応
  7. マルチタスク
  8. ターミナルとコマンド作成
  9. システムコールの仕組み作成
  10. ターミナルの複数起動
  11. アプリが大量のメモリを獲得できるようにする
  12. 日本語表示
  13. アプリ間の通信の確立