はじめに
やはり、プログラミングをやるのにあたって基礎は必要不可欠だと思い、それを解消するために自作OSを作ってみることにしました。
ARMとX86のCPUアーキテクチャの違いとは?
そもそもARMとX86は命令セットの構造の設計概念が全く違います。
実際には他にもLoongArchなどがありますが、今回は省略させていただきます。(実際のところライセンス的にARMとかx86はお金がかかるからはLoongArchを使った方が便利だからいずれそっちに移行するかも)
| 比較項目 | RISC (ARM) | CISC (x86) |
|---|---|---|
| 代表例 | ARM | x86 |
| 得意分野 | 単純な命令 | 複雑な命令 |
| 命令の傾向 | 単純な命令が多く、構造がシンプル | 複雑な命令を実行可能 |
| 特徴・背景 | 設計がシンプルで効率的 | 歴史的遺産(互換性)を引き継ぐため高機能だが「重い」 |
そもそも、なぜ今回ARMを採用したのかというと、x86は互換性を高めるためにBootのプロセスが複雑で難しいため、今回はARM(RISC)を採用しました。
x86の場合
[ 16-bit: リアルモード ] ──( CR0レジスタ変更 )──> [ 32-bit: プロテクトモード ] ──( EFERレジスタ変更 )──> [ 64-bit: ロングモード ]
└─ 1MBの壁 / BIOS動作 └─ 4GBの壁 / メモリ保護ON └─ 現代のOS / フルパワー
ARMの中にも種類がある
x86が16bit版、32bit版、64it版とあるようにARMにもいくつかの種類が存在します。
そしてx86とARMのようにアセンブリ言語ごとにそもそも互換性がありません。
| 項目 | ARM32 (AArch32) | ARM64 (AArch64) | ARM32 Thumb (Thumb / Thumb-2) |
|---|---|---|---|
| 命令コード長 | 固定 32-bit (4 Byte) | 固定 32-bit (4 Byte) | 16-bit 主体(Thumb-2は16bit/32bit混在) |
| 汎用レジスタ数 | 16個 (R0〜R15) | 31個 (X0〜X30) | 16個(16bit命令でアクセスしやすいのは主に R0〜R7) |
| アドレス空間 | 最大 4 GB (32-bit) | 理論上 16 EB (実質48〜52-bit幅) | 最大 4 GB (32-bit) |
アセンブリ言語とは?
アセンブリ言語とは、機械語に近いプログラミング言語であり、低水準言語と呼ばれている。
そしてアセンブラはコンパイラのことを指す。
そして機械語に強く依存するためx86,ARM,ARM32,ARM64とあるものに互換性が全くないです。
しかも、CPUのレジスタ数によってもpushの区切りを変えたりしなくてはなりません。
CPUに依存すること
基本中の基本の命令一覧
| 命令 | 役割 | 概要 |
|---|---|---|
push |
スタック退避 | レジスタの値をスタック最上段へ保存する(4/8byte推奨) |
pop |
スタック復元 | スタック最上段の値を順次汎用レジスタへ書き戻す |
movs |
データ転送 | 即値やレジスタの値を転送先レジスタに格納する |
adds |
加算 | レジスタ値を加算して結果を格納(フラグ更新あり) |
ldr |
メモリ読込 | 指定アドレスのメモリ(1ワード)からレジスタへデータを入れる |
blx |
関数呼び出し | 戻り先を記録しながら指定アドレス/関数へジャンプする |
push
pushはレジスタ(CPU内部にあるデータを一時的に保管するところ)中に、格納されているデータをメモリ上に一時的に退避させるものです。
特にbyte単位での大きさ制限はないがもし、データをはみ出してしまうと、2つのレジスタから取り出し、連結する必要が出てくるため、4byteか8byte単位で区切ることが推奨されています。
| ビット数 | 名称 | データ記述 | メモリ確保記述 |
|---|---|---|---|
| 8 bit | byte | .byte |
.ds.b |
| 16 bit | halfword | .hword |
.ds.w |
| 32 bit | word | .word |
.ds.l |
push {r14}
この命令はレジスタ(r14、通称Link Register)をpush(メモリ上に一時的に退避)するコードです。
movs
即値という変化しない値(命令に書き込まれている値のこと)を転送先のレジスタに書き込みます。
movs r14, pc
この命令はレジスタ(r14、通称Link Register)に4byte先(次の命令)の命令アドレスが入ります。
adds
レジスタ値(レジスタに保存されている値)を加算して書き込み先に結果を書く命令
adds r14, 0x06
この命令はr14に入っている値(0x06)に0x06(10進数の6)を加算するコードです。
ldr
メモリ上のデータをロードしてレジスタに書き込む命令です。
ldr r2, [r2]
この命令はメモリの[r2]からレジスタのr2に書き込む命令です。
blx
関数を呼び出すための、ジャンプ命令
blx r2
r2に入っている関数にジャンプする命令です。
pop
スタックしていた、汎用レジスタを1つ1つ汎用レジスタに戻し、pushしたアドレスにジャンプする
push {r14}
pop
スタックしていた汎用レジスタを元に戻しr14に入っているアドレスにジャンプする命令文です。
ARMの関数の規則
- 引数1〜4はr0〜r3
- 5個目からスタック経由となる
しかし、アセンブリには関数が存在しないです。
def (a=10,b=20,c=30):
C言語でインラインアセンブリ
# include <stdio.h>
int main() {
asm volatile(
"push {lr}"
"adds r2, 0x06"
);
}
asm volatileを関数のように呼ぶことで、アセンブラのニーモニック(0 1で書いたものを英語(mov)で置き換えたもの)を書くことができます。
まとめ
実際にARMv7-M thumbで自作OSを作ってみたので、次回はx86やARMの自作OSを作ってみたりもしようと思いました。
複雑な実際のコード
参考記事