起動するまでの長い道のり IPL編(5) CPU解説の巻
前回まで実際のソースコードを元に不器用に説明したけど、いい加減CPUの説明抜きで話を進めるのが非常に辛いと分かったので、必要最低限解説しておこうと思う。
メモリ・レジスタ関連
メモリ・レジスタ
一般的なCPUには全てメモリとレジスタがある。メモリは言うまでもなくデータやプログラムを置く記憶領域で、レジスタは以前説明したとおり作業用の「手」だ。メモリから取り出した値を持ったり、値を加工するために持ったりする。非常に小さく、ほとんど1〜4バイトしかない。また、計算や処理には使わない実行制御用の特殊なレジスタもたくさん存在する。
プログラムカウンタ
CPUはメモリに置かれたプログラムのコードを順次実行していく。そのために、メモリ上のどの命令を現在実行しているのかを覚えていなければならない。CPUにはそのための専用のレジスタがあり、プログラムカウンタと呼ばれる。486やPentiumなどのいわゆるx86系CPUでは、IPまたはEIPレジスタが該当する。jmp命令やcall命令で処理を別の場所に移す時は、このプログラムカウンタが書き換えられる。
スタック
メモリ上にはスタックという領域(とデータ構造)が割り当てられる。これもメモリの一部で、スタックポインタという専用のレジスタで在り処を指定する。x86系CPUでは、SPまたはESPレジスタが該当する。
スタックはデータ構造の用語として出てくるスタックと同じで、データ値をpush/popできる格納庫だ。pushによりデータを入れ、popにより一番上にあるデータを取り出す。popではスタックのどのデータを取り出すかは指定できず、とにかく今一番上にあるものが取り出される。次回のpopでは、最後に取り出したデータの下にあったものが取り出される。
スタックは、関数の呼び出しを行う時や、ローカル変数の格納場所として用いられる。callを実行した時、その時点でのプログラムカウンタの値がスタックにpushされる。call先でretが実行されると、スタックの一番上から以前のプログラムカウンタの値が取り出され、元の処理に戻る。
IO・割り込み
もしプログラムが単に実行されるだけで、外部入力やディスクのデータを参照しない場合は、上記要素が揃っていれば処理が行える。だが、大抵のプログラムはディスクからデータを読み込んでしかもユーザの入力に反応する。そのための仕組みとして、IOと割り込みがある。
IO
IOはデバイスによる外部入出力のための仕組みで、x86では専用のIO入出力命令か、メモリマップによりアクセスできる。IOもメモリのようにアドレスがあり、1バイト・2バイト・4バイトといった単位でデータの読み書きが行える。メモリマップとは、IOのアドレスをメモリのアドレスにマップして、メモリアクセスすることがそのままIOにアクセスすることになる仕組みだ。
割り込み
割り込みとは、外部デバイスで何かが起きたときに通知される信号のことだ。例えばキーボードでキーが押された時や、マウスが移動した時に発生する。割り込みにはそれぞれ種類ごとに番号が振られていて、発生時には番号に応じた処理が呼び出される。このとき実行される処理のことを割り込みハンドラと呼ぶ。番号と割り込みハンドラの対応付けはプログラムで設定できる。また、プログラム実行中にエラーが起きた場合(0で除算した等)も割り込み(この場合は例外とも呼ばれる)で通知される。
OSの仕事
CPUでの処理は、基本的には、IO操作で外部デバイスを操作し、割り込みハンドラで外部デバイスからの入力を受け取り、メモリとレジスタで処理を実行するという形になる。
しかし実際はもっと複雑で、例えばディスクに書き込もうとしてもデバイスが処理中で書き込めない事がある。その場合、割り込みで後から書き込み可能になったという通知が届く。それでようやくデータをディスクに書き込める。その間、ディスクを操作しようとしたプログラムは停止させておかなければならない。ずっと待っているだけなのは無駄なので、別のプログラムに切り替えてそちらを実行したりする(いわゆるマルチタスク)。そういうデバイスや割り込みの管理・プログラムの切り替え等がOSの仕事になる。
x86でのレジスタ。
x86でのレジスタには以下のようなものがある。なお、これ以外にも特殊な用途のものがまだたくさんある。だが一般的なものは以下で網羅していると思う。
名称 | サイズ | 用途 |
---|---|---|
AH | 1バイト | 汎用 |
AL | 1バイト | 汎用 |
AX | 2バイト | 汎用 |
EAX | 4バイト | 汎用 |
BH | 1バイト | 汎用 |
BL | 1バイト | 汎用 |
BX | 2バイト | 汎用。データ参照用アドレスとして使われる。 |
EBX | 4バイト | 汎用。データ参照用アドレスとして使われる。 |
CH | 1バイト | 汎用 |
CL | 1バイト | 汎用 |
CX | 2バイト | 汎用。主にループカウンタとして使われる。 |
ECX | 4バイト | 汎用。主にループカウンタとして使われる。 |
DH | 1バイト | 汎用 |
DL | 1バイト | 汎用 |
DX | 2バイト | 汎用。主にIOポインタとして使われる。 |
EDX | 4バイト | 汎用。主にIOポインタとして使われる。 |
SI | 2バイト | 汎用。ストリング命令の元アドレスとして使われる。 |
ESI | 4バイト | 汎用。ストリング命令の元アドレスとして使われる。 |
DI | 2バイト | 汎用。ストリング命令の先アドレスとして使われる。 |
EDI | 4バイト | 汎用。ストリング命令の先アドレスとして使われる。 |
SP | 2バイト | スタックポインタ |
ESP | 4バイト | スタックポインタ |
BP | 2バイト | スタック上のアドレスを指すベースポインタ |
EBP | 4バイト | スタック上のアドレスを指すベースポインタ |
IP | 2バイト | プログラムカウンタ |
EIP | 4バイト | プログラムカウンタ |
EFLAGS | 4バイト | フラグレジスタ |
CS | 2バイト | コードセグメントレジスタ |
DS | 2バイト | データセグメントレジスタ |
SS | 2バイト | スタックセグメントレジスタ |
ES | 2バイト | 追加のデータセグメントレジスタ |
FS | 2バイト | 追加のデータセグメントレジスタ |
GS | 2バイト | 追加のデータセグメントレジスタ |
A・B・C・Dの各汎用レジスタにはそれぞれにAH・AL・AX・EAXといった4種類がある。表の通りそれぞれサイズが違う。また、サイズが違うレジスタ同士で領域が重なっていて、ALレジスタに値を代入した場合はAX・EAXの値(最下位バイト)も変わる。逆にEAXに値を設定した場合もAX・AH・ALの値が変わる。2バイトと4バイトの2種類があるESI・EDIも同様だ。
EBP・ESP・EIPに関しては、CPUのモード(リアルモードかプロテクトモードか)に応じて2バイトになるか4バイトになるかが決まる。
命令セット
x86系のCPUが実行する命令について簡単に解説する。
命令の構造
CPUに与えられる命令は、以下のような構造になっている。
オペコード | オペランド1 | オペランド2 | ...オペランドn |
---|---|---|---|
movw・jmpなど命令の種類を表す。 | 命令の対象(レジスタや値)を指定する。 | 同じく命令の対象を指定する。 | オペランドの数は命令により違う。 |
たとえば「movw %ax, %bx」なら、オペコードはmovw、オペランド1はAXレジスタ、オペランド2はBXレジスタになる。movw命令はオペランド1の値をオペランド2にコピーする命令なので、AX→BXの処理が行われる。
ちなみに、movwやjmpといったオペコードの名前のことをニーモニックと呼ぶ。
基本的な命令
詳細はやはりIntelのマニュアルを見て欲しい。本当にごく簡単なものをごく簡単に説明する。カッコつきのニーモニックは「movw」といったようにサイズを表す文字が付加されることを示す。
ニーモニック | 説明 |
---|---|
mov(bwl) | 値をコピーする。 |
lea(wl) | アドレス値をレジスタに読み込む。 |
add(bwl) | 値を加算する。 |
sub(bwl) | 値を減算する。 |
mul(bwl) | EAX(AL・AX)の値をオペランドの値で乗算する。 |
div(bwl) | EAX(AL・AX)の値をオペランドの値で除算する。 |
neg(bwl) | 値の符号を反転させる。 |
sal(bwl) | 値を左ビットシフトする。符号を考慮する。 |
sar(bwl) | 値を右ビットシフトする。符号を考慮する。 |
shl(bwl) | 値を左ビットシフトする。符号は考慮しない。 |
shr(bwl) | 値を右ビットシフトする。符号は考慮しない。 |
or(bwl) | 値をOR演算する。 |
and(bwl) | 値をAND演算する。 |
xor(bwl) | 値をXOR演算する。 |
not(bwl) | 値をビット反転する。 |
inc(bwl) | 値を1加算する。 |
dec(bwl) | 値を1減算する。 |
jmp | 指定アドレスにジャンプする。 |
je | 直前の計算で値が等しかった場合ジャンプする。 |
jne | 直前の計算で値が等しくなかった場合ジャンプする。 |
jz | 直前の計算でゼロになった場合ジャンプする。 |
jnz | 直前の計算でゼロにならなかった場合ジャンプする。 |
jl | 直前の計算で値がより小さい場合ジャンプする。 |
jle | 直前の計算で値が以下の場合ジャンプする。 |
jnl | 直前の計算で値がより小さくない場合ジャンプする。 |
jnle | 直前の計算で値が以下ではない場合ジャンプする。 |
jg | 直前の計算で値がより大きい場合ジャンプする。 |
jge | 直前の計算で値が以上の場合ジャンプする。 |
jng | 直前の計算で値がより大きくない場合ジャンプする。 |
jnge | 直前の計算で値が以上ではない場合ジャンプする。 |
call | 関数呼び出しを行う。 |
ret | 関数内から呼び出し元に戻る。 |
iret | 割り込みハンドラを終了させる。 |
push(wl) | スタックに値を積む。 |
pop(wl) | スタックから値を取り出す。 |
in(bwl) | IOポートから入力を取り出す。 |
out(bwl) | IOポートに出力する。 |
int | 指定した番号の割り込みを発生させる |
cli | 割り込みを禁止する。 |
sti | 割り込み禁止を解除する。 |
cld | ストリング命令を低位アドレス→高位アドレスの方向にする。 |
std | ストリング命令を高位アドレス→低位アドレスの方向にする。 |
nop | 何もしない。 |
次回予告
リアルモードでのセグメンテーションと割り込みについて解説する。
参考資料
- 作者: Daniel P. Bovet,Marco Cesati,高橋浩和,杉田由美子,清水正明,高杉昌督,平松雅巳,安井隆宏
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/02/26
- メディア: 大型本
- 購入: 9人 クリック: 269回
- この商品を含むブログ (73件) を見る
- 作者: 蒲地輝尚
- 出版社/メーカー: アスキー
- 発売日: 1994/09
- メディア: 単行本
- 購入: 20人 クリック: 165回
- この商品を含むブログ (83件) を見る
x86アセンブラ入門―PC/ATなどで使われている80x86のアセンブラを習得 (TECHI―Processor)
- 作者: 大貫広幸
- 出版社/メーカー: CQ出版
- 発売日: 2006/01
- メディア: 単行本
- 購入: 7人 クリック: 195回
- この商品を含むブログ (10件) を見る