オペレーティングシステム

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
このページ「オペレーティングシステム」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。
予備知識

本書を読むには予備知識としてアセンブラの知識と、X86系CPUのレジスタなどのアーキテクチャの知識が必要である。

一般的にプログラミングにおいて、ハードウェアの制御は、OSの準備していない命令では、アセンブラを使う慣習になっているので、各種のアセンブラで、ハード制御の方法が用意されている(もし そうでないと、そもそも技術者がOSやファームウェアなどを開発できない)。

もし知らなければwikibooks記事『X86アセンブラ』などで解説してある。特に『X86アセンブラ/GASでの文法』『X86アセンブラ/x86アーキテクチャ』『X86アセンブラ/x86アセンブラ』では初心者むけに説明してある。

本書では触れてないが「カーネル」とか「ユーザランド」とかの用語についてはwikibooks『高等学校工業/ソフトウェア技術』などで触れてある。

本書はタイトルが今のところは「オペレーティングシステム」だが、実際はコンピュータアーキテクチャ理論やデジタル回路理論など低レイヤーの理論や実務的知識が混在したものになっている(あとで整理する)。大学の科目の分類が縦割りのタコツボ・時代おくれで、全体像が分かりづらいので、予定では、今後の構成でも、意識的にコンピュータアーキテクチャなど関連分野の説明を、整理後にも、ある程度は残しておく予定。

なのでOSと言うタイトルなのにマイコンCPU(Z80など)にも触れているという状況である。

総論[編集]

学習の方向性[編集]

概要[編集]

オペレーティングシステムは一般に、ユーザーにハードウェアレベルのプログラミングをさせる手間を減らし(ほぼ無くして)、パソコンを使いやすくするためのものです。

なので、オペレーティングシステムの中身は、そういったものになっているハズです。


具体的に言うと、市販のOSなどは、まず起動時にハードディスクとBIOSを作動させ(いわゆる「ブートローダー」 Boot Loader)、つづいてデバイスドライバなどを起動させる仕組みになっています。

私たちが自分でOSを作る場合、BIOSの機能の呼出し命令などは自分で書く必要がありますが、CPUメーカーなどが必要な命令を(int命令など)用意してくれているので、それを使うことで、手間を減らせます。

デバイスドライバなどは、OS制作では、自分で書く必要があります。


さて、ブートの仕組みは、もとになった規格である、ハードディスクの規格が古いため、まずMBR(マスター ブートレコード, Master Boot Record)またはMBRを経由してPBR(パーティション ブートレコード)というのを起動してから、 そのあとにハードディスクの指定された第1セクタをコピーして、そのコピーをメモリ番地の0x7C00 番地に配置して実行する。

ただし、当面は、この番地(0x7C00 )は知らなくても、アセンブラなどを用いて機械語に翻訳する際に、アセンブラが自動的に適切な番地に割り当ててくれます(※ のちの節で後述するコード例でも、当面がこの番地を入力する必要が無い)。


なので、このメモリ上へとコピーされるプログラムが、BIOS以降に最初にOSの起動するプログラムです。

もし「プログラム・カウンタ」という用語を知っていれば(高校の『情報』教科で習う用語です)、プログラムカウンタが最初に指し示すアドレスは、メモリ番地の0x7C00 番地ということになります。


ブートの概要[編集]

MBR(Master Boot Record)には、どのPBRを起動するのかの情報や、また、そのPBRはハード上のどの位置に存在しているかの ありか の場所を記述することになります。

このようなブートの経緯の仕組み(ブートプロセス boot process)のため、内蔵ハードディスクのようなブート用の記録メディアの先端○○バイトはどう書くのかが決まっています(ブートセクタ)。このブートセクタの先端領域がMBRの占有する領域であり、そのMBRのなかに情報としてPBRを起動するためのブート情報を書きます。


しかし、私たちが最終的に作りたいのは、あくまでもOSです。ブートローダーではないのです。

このため、OSをもし自分で作ろうとするなら、最終的には、ある程度はBIOSや内蔵機器のシステムなど、ハードウェア側のシステムの仕組みを知る必要があるでしょう。

しかし、ある程度は規格で決まっています。たとえばディスプレイについては『VESA』という団体が規格化を行っており、このためBIOSでのディスプレイの扱いも、ある程度は規格的に整理されています。


一般的なBIOSには、起動時にハードディスクに書かれたプログラムを実行する仕組みがあります(でないと、ブートローダーとして役立たない)。

また、一般的なCPUやアセンブラには、メモリの最初から何番目のアドレスに、どの機械語命令を書き込むか、という、メモリアドレスを指定してメモリに書き込む機能(org疑似命令)があります。


つまり、これらの機能を合わせて、(ブートデバイスの)メモリへとコピーされる領域に、パソコン電源投入直後の初期状態のメモリをどう変更したいかを書けばいいわけです。


それぞれのBIOSメーカーやCPUメーカーなどがデータシートなどを公開しているハズなので、余裕があればネットで確認しましょう。

(しかし市販の『BIOSの設定が分かる!』みたいなタイトルの書籍は、OSの仕組みの勉強には、まったく使い物になりません。)
(また、「Windows10の使い方が分かる」みたいな書籍も、使い物になりません。)


一般にどのパソコンも、電源ボタンを入れた起動時に、内蔵ハードディスク(または内臓SSD)とCPUとメモリとディスプレイが起動するようになっています。

そして、ディスプレイの表示内容は、(いわゆるビデオチップなどの)VRAM(ビデオRAM)または、メモリ(差し込める奴)から読み読み取るようになっています。

しかし、一般的なBIOSは、アセンブラのINT 0x10命令でディスプレイに割り込みをかけられるようになっていますので、あまりハードの内情を知る必要はありません(アセンブラのINT とは、割り込み interrupt のことです。C言語の整数型変数宣言 integer とは違うので、混同しないように。)。

※ なお、割り込み信号の発生のさいのチャタリングの除去は、たとえばシュミット・トリガ回路で除去できる[1]

また、BIOSあたりのレベルで、ディスプレイが、メモリにASCIIコードにもとづくデーターバイトを入れるだけで、その文字を順番に表示するサポートなどをしています。(ディスプレイ用のメモリに機械語のdb命令で文字を書き込むだけで、その文字をアセンブラが自動的にバイト文字に変換し、さらにBIOSとハードが連携して自動的に該当する文字を画面に表示するプログラムにしてくれる。)

実際、日本の書店で市販の「OS自作」などを謳っている書籍を読むと、「メモリマップ」 memory map というハードウェア業者の用意した仕様にしたがって、該当するメモリの書き換えをする作業を機械語などで書いて、それでGUIを作っています。

けっしてOS自作界隈だけが言ってる用語ではなく、電子工作の界隈でも『メモリマップド I/O』という用語が、同じような意味であります[2]

原理的には、VRAMのメモリマップを書きかえる事と、(C言語でいうif文のような)条件分岐などの制御構造などの実装により、GUI画面が作れます(ディスプレイの画像はピクセル単位の点の画像の集積なので)。

メモリマップ[編集]

さて、一般的に、メモリの一部の領域は、メーカーごとに使い方が決まっており、「最初の何バイトはハードディスクの起動用に使う」、「△△バイト目から□□バイト目からは、ディスプレイとのやり取りの制御に使う」とか、ハードウェア(基盤やBIOSなど)のメーカーごとにメモリの使い方が決まっています。

一般的なパソコンではグラフィック描画用のVRAMアドレスは 0xa0000 から 0xaffffの領域が割り当てられています。


なお、このようなメモリの用途の割り当てのことを「メモリマップ」memory map といいます。 (MBRなどとは違います。MBRはハードディスクの話題です。メモリマップはDRAMなどの話題です。)

もし、一般的でない特殊なハードウェアの場合は、メモリマップを調べる必要があります。


なので、OSを作るには、世間に流通しているハードウェアのメモリマップを探りあて、C言語やアセンブラなどで、メモリ上のデータを書き換えるようにプログラムを書いて、それをブート時にブートローダーから起動させる用に命令すれば、デバイスなどを操作できると考えられます。


現在の市販に流通しているパソコンのアーキテクチャの多くは、1900年代の昔のIBMやインテルなどの発表したアーキテクチャである 「X86」系というのが元になっているので、まずはそれに詳しくなる必要があります。

CPUメーカーのAMD系のハードウェアであっても、パソコンのアーキテクチャの多くはX86系ですので、まずはX86系を勉強する必要があります(しかし、組み込み系とか、スーパーコンピュータ、その他の携帯モバイル機器などは、アーキエクチャの違う場合があるので、別途、アーキを調べる必要がある)。


なお、「メモリマップ」という概念があるのは、パソコン用CPUアーキテクチャだけでなく、組み込みマイコンCPU(Z80やH8など)などでも同様です(とはいえ、組み込みマイコンにブートローダやディスプレイなどは一般に無いのが普通なので、そこは誤解しないように)。


割り込み[編集]

キーボードや内蔵タイマなどの周辺ハードは、「割り込みハンドラ」(PIC)というものを経由して、CPUやメモリは、これらの周辺ハード(キーボードや内蔵タイマなど)を制御しています。

※ 割り込みとは、何が何に割り込んでいるかというと、おおむね、CPUと内蔵DRAMメモリとの通信とのあいだに、それ以外のデバイスの通信が割り込むイメージである。内蔵ハードディスクですら内蔵DRAMではないので、内蔵ハードディスクはその他のデバイスに含まれる。
なので、下記の割り込みハンドラの話題に、内蔵DRAMメモリの話題が無い。内蔵DRAMは、割り込まれる側だからである。
CPUとメモリ間のバス
※ なお、CPUとメモリの間の通信方法は、CPUにアドレスピンというピンが何本かあって[3]、その組み合わせで、メモリにどのアドレスのデータを出力/入力するかを送受信している。
たとえば 36本のアドレスピンのあるCPUなら、236 =24 ×232 =24 × 4GB = 64 GB(ギガバイト)までのアドレスを指定できる。

ここでいう、メモリとはもちろん、物理的に基盤として実在している(いわゆる)「実メモリ」、「物理メモリ」のことである。けっして仮想メモリのことではない。

なお、CPUのピン数とビット数とは、まったく一致しない、別の数字である。

たとえば、インテルの市販の昔の32ビットCPUや64ビットCPUは、ピン数が数百本はある。

X86系CPUにかぎらず、一般にCPUは、この節で述べるような仕組みである。パソコン用CPUにかぎらず、制御用マイコンCPUなどでも同様であり(たとえばZ80やH8など)る[4]、「アドレスバス」や「データバス」の意味も同様である。

たとえば、H8マイコンは古いものは8ビット、新しいものには16ビットや32ビットもあるが、ピン数は100本ある。Z80はピン数が40本。


そしてメモリはアドレスピンの指示どおりのデータをCPUに送信または(cpuから送られたとおりのデータを)受信するという仕組みである

アドレスピンによる設定情報が通る経路をアドレスバスという。また、アドレスピンなどの指定を受けて、メモリなどデバイスの送り出すデータが通る経路のことを「データバス」という。アドレスバスやデータバスは、(おそらく)単なる経路であり、割り込みのような機能は無いと思われる。

このほか、タイミングの動機などの制御を行うためのコントロールバスがある。
CPUとメモリとの間のバスは、一般的に、アドレスバス、データバス、コントロールバスの3種類だけである。
なお、(おそらく)データバスはCPUのデータの入力/出力とも同じデータバスを兼用であると思われている。
※ インテルのCPUはどうか知らないが、マイコンCPUとかだと(たとえばZ80マイコンCPUとか)、実際にアドレスバス用ピンとデータバス用ピンとが別々のピンとして割り当てられているのが、肉眼で容易に確認できる。



ともかく、X86系における割り込みハンドラとは、イメージ的には、下図のような感じです。


CPU ━━ 割り込みハンドラ━━━━┳━キーボード
                  ┃
                  ┣━タイマ
                  ┃
                  ┣━(※ 以下略)
                  ┃
                                 (※ 以下略)


なお、マイコンボードのPICマイコンとは「割り込みハンドラ」(PIC)は名前が似ていますが、まったくの別物ですので、混同しないように。


キーボードの制御は、PICから、さらにキーボードコントローラー(KBC)というのを経由して、通信方式は一般的にシリアル通信で、制御が行われます。

上図はつまり、さらに下図のようにキーボードの部分が修正されます。


CPU ━━ 割り込みハンドラ━━━━┳━キーボードコントローラー ━━キーボード
                  ┃
                  ┣━タイマ
                  ┃
                  ┣━(※ 以下略)
                  ┃
                                 (※ 以下略)


余談

当面は覚える必要は無いですが、

一般的なパソコン/BIOSでは、割り込みに使える番号の数は、0番から255番までの合計256個です[5] [6]。このうち、0番~31番まではインテル(CPUメーカー)仕様などの規格などにより用途が既に決められております。なので、ユーザーが自由に定義できるのは32番~255番の領域だけです。


また、割り込みが発生する直前のCPUの状態の保存の方法は、単にスタックにプッシュすることで保存しているだけです。割り込みが発生すると、スタックにフラグレジスタやPCレジスタなど(主に CSレジスタ(コードセグメント)とIPレジスタ(命令ポインタ)とFLAGSレジスタ)の値が(スタックに)プッシュされて保存され、そしてハンドラに制御が移ります [7] [8]

割り込みが終わるなどして戻るときに、スタックに保存しておいたフラグなどのデータを取り出します。

Linuxのコマンド

LinuxのFedora31ではターミナル端末のコマンドで cat /proc/interrupts とすれば、そのハードウェアでの割り込みコントローラの割り当ての構成を見られる。 「proc」の前にスラッシュ記号(/)をつけるのをコマンド入力時には忘れないように。

なお、表示結果は、コンピュータのハードウェア構成によって異なる。

一例として、富士通の2010年頃のノートパソコン FMV-BIBLO NF/B70 を Fedora31 上で cat /proc/interrupts してみると、下記のように表示される。

[ユーザー名@localhost ~]$ cat /proc/interrupts
           CPU0       CPU1       
  0:     143681          0   IO-APIC   2-edge      timer
  1:        110          0   IO-APIC   1-edge      i8042
  8:          0          1   IO-APIC   8-edge      rtc0
  9:          0         76   IO-APIC   9-fasteoi   acpi
 12:          0        728   IO-APIC  12-edge      i8042
 16:      18138          0   IO-APIC  16-fasteoi   uhci_hcd:usb3, i915
 17:          0          9   IO-APIC  17-fasteoi   uhci_hcd:usb4, firewire_ohci, yenta, mmc0
 18:      17109          0   IO-APIC  18-fasteoi   ehci_hcd:usb1, uhci_hcd:usb5, uhci_hcd:usb8, ath
 19:          0          0   IO-APIC  19-fasteoi   uhci_hcd:usb7, i801_smbus
 23:          0         30   IO-APIC  23-fasteoi   ehci_hcd:usb2, uhci_hcd:usb6
 27:          0      68728   PCI-MSI 512000-edge      ahci[0000:00:1f.2]
 28:      16891          0   PCI-MSI 4194304-edge      enp8s0
 29:          0        922   PCI-MSI 442368-edge      snd_hda_intel:card0
NMI:        133        125   Non-maskable interrupts
LOC:     154414     216397   Local timer interrupts
SPU:          0          0   Spurious interrupts
PMI:        133        125   Performance monitoring interrupts
IWI:      28932      26837   IRQ work interrupts
RTR:          0          0   APIC ICR read retries
RES:      41828      40626   Rescheduling interrupts
CAL:      18459      15141   Function call interrupts
TLB:      38258      37058   TLB shootdowns
TRM:          0          0   Thermal event interrupts
THR:          0          0   Threshold APIC interrupts
DFR:          0          0   Deferred Error APIC interrupts
MCE:          0          0   Machine check exceptions
MCP:          2          2   Machine check polls
HYP:          0          0   Hypervisor callback interrupts
HRE:          0          0   Hyper-V reenlightenment interrupts
HVS:          0          0   Hyper-V stimer0 interrupts
ERR:          0
MIS:          0
PIN:          0          0   Posted-interrupt notification event
NPI:          0          0   Nested posted-interrupt event
PIW:          0          0   Posted-interrupt wakeup event
[ユーザー名@localhost ~]$ 


なお、cat コマンドは単に、引数で指定されたファイルを表示するだけである。また、「 /proc/interrupts 」とは、単に proc フォルダの中にある interrupts ファイルを指定しているだけである。なお、マウスで探すときは、ホームフォルダの中には proc フォルダはなく、「コンピュータ」アイコンから、たどって行くと見つかる。

なので、別にコマンド端末からの捜査でなくとも構わず、マウス操作で直接、procフォルダを探して interruptsを見ても、かまわない。

なお、番号の呼び方で

「 0: 143681 0 IO-APIC 2-edge timer」 の端子は「 IRQ 0 」と呼ぶ。

「 1: 110 0 IO-APIC 1-edge i8042」 は「IRG 1」 と呼ぶ。

「 8: 0 1 IO-APIC 8-edge rtc0」 は「IRQ 8」と呼ぶ。


「IRQ」とは、割り込み要求 Interrupts ReQuest のことである。


「APIC」というのは、単に、インテル製の割り込みコントローラーの製品名で、そういう製品がある。インテルの古い割り込みコントローラで「8259A」というのがあって、それを(Windows2000やペンティアムが出始めた)1990年代〜2001年頃に後継したのが「APIC」である。

さらに2010年代の原題では、その「APIC」を拡張した「xAPIC」になっているが、便宜的に「APIC」と呼ばれているママのことも多い。


なお、メーカーなどによって異なる可能性はあるが、だいたい、どこのメーカーのパソコンでも、割り込み IRQ のハードウェア構成は

0番(IRQ 0)はタイマー、
1番(IRQ 1)はキーボード、 i8042 とはインテル製のキーボードコントローラの型番
12番(IRQ 12)はマウス、 i8042 とキーボードコントローラの型番がある理由は、マウスのセンシングでもキーボードコントローラを流用しているから、

などの共通的な傾向がある。

から8番あたりまでの構成は、表示結果からも

「 CPU0 CPU1 」と2つCPUなんとかが表示されるのは、単にこのパソコンのCPUが2コアだから(インテル Core2Duo 採用品)。


          CPU0       CPU1       
 0:     143681          0   IO-APIC   2-edge      timer

などの「143681」は、単に合計の割り込み回数だと言われている(要 確認)。Fedora では、この /proc/interrupts ファイル内に、割り込みの統計が保管されている。

ちなみに proc フォルダおよびproc配下のほとんどのファイルは、実は(DRAMなどの)メモリ上に置かれた設定情報である。実は物理的にはハードディスクには proc フォルダ/ファイルは無い。ユーザーがアクセスしやすいように、わざと、あたかも proc 関連ファイルが(まるで)ハードディスクにあるかのように擬態されて表示される仕組みに Linux などが なっている。そもそも proc 関連の項目が「ファイル」かどうかも本来は疑わしい。


なお、ポートアドレスで指定したIOポートにデータを送信できます[9]

out 0x21, ax

のように書きます。

つまり、

out ポートアドレス, ax

の書式になります。

ポートアドレスの一覧については下の表を参照。」

PIC1
(マスター)
PIC2
(スレーブ)
コマンド 0x20 0xA0
データ 0x21 0xA1


0x11 の部分は、コマンドで、この場合は初期化コマンド。


メーカーによって違う可能性はありますが、よくあるポートアドレスとして、

0x20 から 0x3F   割り込みコントローラ(PIC)
0x40 あたり            タイマ 
0x60 あたり      キーボード関係 

の割り当てがあります。(※ 参考文献『作って理解するOS』、276ページ。 )

その他、割り込みコントローラー(PIC)について、

0x20 マスターPICのコマンドレジスタ。
0x21 マスタPICの割り込みマスク(interruput mask)レジスタ。
0xA0 はスレーブPICのコマンドレジスタ。
0xA1 はスレーブPICの割り込みマスク(interruput mask)レジスタ。

などに割り当てられている場合もある。


あらかじめ直前に move 命令で、axレジスタに送信したいデータを入れておく必要があります。

つまり、

mov ax, 送信したいデータ
out ポートアドレス, ax

の書式になります。


ポートアドレスをレジスタに代入する場合は、慣用的に

out dx, ax

のように dx をポートアドレスの指定に使うようです[10]


さらに、データサイズが何ビットかにしたがってmovやoutの末尾に b (1バイトの場合)または w (2バイトの場合)がつくので、

  movb ax  送信したいデータ
  outb ポートアドレス  ax

のようになります。

なおC言語の outb() 関数は、上述のようなアセンブリ言語のoutb命令に相当するものです[11]


シリアル通信とパラレル通信

なお、一般に電子機器ケーブルが信号を送受信をするとき、複数本の信号線で送信する方式をパラレル通信といい、いっぽう、1本だけの信号線で送信する方式をシリアル通信という。

ただし、パラレル通信は、速度が遅い。

一見すると、パラレル通信のほうが、線路が多いぶん多くの情報を遅れそうだが、実際には、銅線どうしがコンデンやアンテナとして干渉しあったりしてノイズが発生したりするので、パラレル通信では、あまり周波数を高められない。

また、パラレル通信では微妙に線路の長さが違う影響も出て、信号の同期のタイミング調整があるので、高速化は困難である。


だとすると、CPUとメモリとの通信方式はいったい、なんなんだろうか?(インテルやAMDなどが公開していないっぽいので仕様が不明。)(ググっても、ぜんぜん出てこない。「シリアルバス」や「パラレルバス」という用語はあるが、これはマウスケーブルやらUSB端子ケーブルなどでの外部デバイスとの通信方式の用語なので、メモリCPU間の通信方式とは意味が違う。)

アドレスピンが30本以上もあったりすると、一見するとパラレル通信のように見える。だが、上述のようにパラレル通信には、高速化が困難という欠点がある。

だとすると、そのアドレスピン30本以上を使って、シリアル通信のようなデジタル波形をCPUで生成していることになるだろう。

だとすると、これは符号化の技術になる。

だとすると、ビット数が多くなるほど符号化が困難・大容量になるので、そう簡単には(パソコンCPUの)128ビット化や256ビット化が進まないのも納得である(かつてソニーとIBMは128ビットCPUのCELLを生産していたが)。


なお、Z80系マイコンCPUにはパラレル通信インターフェースの端子が存在する[12]。Z80は最大クロック数が12MHzの製品がある[13]。12メガもの高周波でどうやってパラレル通信をしてるか(そもそも12メガでもパラレルできるのか)、気になるところである。

いっぽう、H8マイコンはシリアル通信を内蔵している[14]

こういう言い方をすると、なんとなくパラレル通信では符号化が不要な感じかもしれないが、そんなことはなくて、単にシリアル通信のほうが符号が長くなりやすいだけで、シリアル通信でもパラレル通信でもともに符号であるし、そもそもデジタル信号は基本的に符号化して情報伝達をするので、シリアル通信もパラレル通信もともにデジタル信号なので符号化が必要である。

よく変換コネクタで、シリアル通信とパラレル通信の変換コネクタなどが業者むけに販売されているが(はたして家電量販店で市販してるかどうかは知らない)、これは、符号を別方式の符号に変換してるのである。けっしてアナログ信号をデジタル信号に変換してるわけではない(つまり、ADコンバーター(アナログ/デジタル変換器のこと)とは違う。)。

なお現代でも、産業用機械などにおいて(装置と別の装置との)送受信の場合などは、(USBシリアル接続でなくて)パラレル通信用の端子ケーブル(たとえば RS232C ケーブルなど )が使われる。

なお、RS232Cケーブル(これはパラレル端子)やPS/2ケーブル(これはシリアル通信端子)は、イメージ的に何となく古いイメージなので、てっきりアナログ通信かと誤解しがちだが、RS232CケーブルとPS/2ケーブルはそれぞれデジタル通信である。

RS232Cは、現代でも産業機器などで使われる。工場など企業の労働現場は、かならずしも空調などが快適とは言えず、また、機械工作の切削クズなどが発生しやすい環境もあったりするので、頑強な過去の時代のケーブルなどが好まれる場合もある。

過去には、古いパソコンで、外部との接続端子にRS232Cがよく使われていた。

PS/2 は、やや古いキーボード接続に使われた端子およびケーブルである。

これらの古いケーブルおよび端子は、接続先の機器がアナログ的な機器である場合も多いので(特に RS232C)、てっきり端子も「アナログ端子だろう」(×)と誤解しがちであるが、しかし、RS232CもPS/2も端子じたいはデジタル端子なので混同しないように。

なお、パソコンのハードディスクの接続は現代では一般にシリアル通信であり、シリアルATAと言われる。ケーブルが太そうに見えるので何となくパラレルっぽく見えるかもしれないが、しかし、シリアルATAはその名のとおりレッキとしたシリアル通信である。

また、拡張カード端子とパソコン内部コントローラなどの接続に使われるPCIバスおよびその発展系の各種規格もシリアル通信である。


さて、シリアル通信では、8B/10Bという符号化がある。(ウィキペディア『w:8b/10b』) もしCPUがシリアル通信なら、とうぜん、これを行ったうえで、さらにそれぞれの端子の通信内容を重ねているのだろう。


この8b/10bは、もし同じビットが長時間連続すると、電子回路の電磁気的な都合により入力ビットが反転しても出力の立ち上がりが不完全になって出力エラーになるという回路特性があるので、同じビットが絶対に連続しないように、8ビットの入力信号を、ビットが4個以上は連続しない10個のビット符号に置き換えるという、符号方式である。

置き換えの符号は、規格で決められているが、数学的な規則性は無いので、覚えなくていい(実務では、規格表で確認する)。


また、4B/5Bなど、同じ原理の符号方式もある(4B/5Bは4ビット入力を連続しない5ビット入力に置き換えている)。


※ 資格本などで「電気通信設備工事担任者」(いわゆる「工事担任者」)などの資格本を読むと、ここら辺の話題が説明されている。

さて、知り合う信号とパラレル信号の変換技術でSerDes(サーデス)という方式がある。原理は単純で、たとえばパラレル入力でA=1, B=0,C=0,D=1なら、

シリアル変換のために1本の送信ケーブルで1秒後に1, 2秒後に0、3秒後に0、4秒後に1を送信というように送信するようにする原理。(もちろん実際には「1秒」という長時間ではなく、もっと短い周期。)

簡単にいうと、このserdesの原理では、入力ピン数が増えると、そのぶん周期が長くなる。

8B/10B などの符号化と、このserdesを組み合わせて、8B/10B SerDes などとして、コンピュータ内部では使われている。


このようにパラレル伝送は現代では評判が悪いが、しかしGPUではパラレル的な伝送が使われている(原理的に、GPUの各プロセッサに伝送するために伝送をどこかでパラレル化せざるを得ない)。

コンピュータアーキテクチャの専門書でもGPUについて『パラレル』という用語が用いられている(しかし『パラレル伝送』とは専門書では言ってないが)。

他にもスーパーコンピュータの並列コンピューティングも同様に、専門書では『パラレル』という用語が用いられている。


このGPUや並列コンピュータのように、チップ1個上のハードウェア内部的にはシリアル伝送だが、そのチップまたはマザーボードをいくつも配置して並列計算するという、新型のパラレル処理のようなものが現代ではスーパーコンピュータなどの大型計算では行われている。


スパコン開発者・GPU開発者などは特にノウハウを公言していないが、パラレル伝送には上記のような同期化のさいの困難性などの問題もあるので、もしも精度の高く要求される計算をする場合には、伝送のズレなどによる誤差のぶんを考慮しなければならないハズなので、対策として例えば計算の確認のための多重化など(同じ計算を複数回計算して、誤差が無いかを確かめるなど)、慎重な処理をしなければならないだろう。

あるいは、多数決モジュールあるいは多数決回路というのがあり、これは複数の回路からの計算結果の一致やズレを相互検証するものだが、これもパラレル伝送の問題があるので、なので対策が必要である。

パラレルはタイミングがズレるが、しかしタイミング以外の波形パターンはまずズレないのだろうから、だったら最初からズレを見込んで、た余計に伝送をすればイイ。

たとえばもし「110」というデータを送りたいなら、その前後に「0000」をつけて「0000 110 0000」と送れば、

もし「0」一個ぶんだけタイミングが遅れても、「 000 110 0000」と、送りたい「110」のデータは残る。

「0000」1個だけなら不安なら、たとえば「0000」を3回繰り返して「0000 0000 0000」とか追加する仕様にすればいいだろう。そうすれば「000 0000 0000 110 0000 0000 0000」となるので、「0000」が2回繰り返すので、それから、「0000」が保護材として追加されたデータだと分かる。

実際には、4b/5b の導入の経緯のように、0がいくつも「0000」のように続くと立ち上がりが悪くなるので、 4b/5bなどの符号を保護材側データを追加する必要があろうが。



原始的なコンピュータのアーキテクチャ

よく、情報科学の入門的な理論では、「コンピュータの基本的な仕組みとしてCPU内にはレジスタがあって、基盤にはメモリがあって、」(以下略)・・・

などと習うが、

それだけでは実は不十分。


まず、CPUに、作業内容の切り替えスイッチが必要である。

なぜなら、アセンブラ言語で色々な命令があるが、その命令にもとづいて、CPUは回路を切り替えなければいけないからだ。

デマルチプレクサの原理

スイッチの実装としては、デジタル回路の技術で「デマルチプレクサ」という切り替えスイッチ回路が有名であるので、単にこれを使えばいい。


右図がデマルチプレクサの原理図であり、右図の場合は2ビット用なので2の2乗ぶんの4個のスイッチを切り替えられる。

マルチプレクサの原理図

なお、(頭文字に「デ」のついていない)マルチプレクサというものは、デマルチプレクサの出力側の終端すべてにOR回路を1個つなげて、まとめただけである。

用語だけ見ると、てっきり、1文字ぶん短いマルチプレクサのほうが(デマルチプレクサよりも)中枢的な部品のように思えるが、しかし実際はデマルチプレクサこそが中枢的であり、デマルチプレクサによる回線切り替えこそが重要技術である。

ダイオードによるOR回路の等価回路の概略図
※ 実際にはパソコン内のOR回路はMOSトランジスタなどによる回路であり、ダイオードではない。

さらにいうなら、そのOR回路は逆流を防止するためにつけられただけである。このように、マルチプレクサでの出力端子のOR回路に、あまり意味は無い。


私たちは、けっして用語に惑わされるのでなくて、回路図と照らし合わせて本質を学ぼう。(※ なお、パターソン&ヘネシー『コンピュータの構成と設計』だと、マルチプレクサのほうを中心に考えている[15]。しかし、彼らの考えは不十分だろう。デマルチプレクサこそが中心的な部品である。もっともパタヘネ本の初版は1996年4月9日であり、この時代で、ここまで調べたあげているパタヘネはすごい。)


なお、理工書の東京電機大学出版局『コンピュータ工学の基礎』を読むと、最初にマルチプレクサとデマルチプレクサを教えてから、あとから加算回路を教えるという書籍の構成である[16]

さらに、電機大の同文献ではマルチプレクサの説の直前で2進-10進デコーダを紹介しており[17]、まるで「きっとマルチプレクサの正体は2進デコーダなんだろうな」とでも言いたげな構成である。

※ パタヘネ本では、論理回路の中身まで、あまり言及していない。


デジタル電子回路でよく、2進-10進デコーダという、4本の入力端子の2進数の入力を、出力10本の回路に切り替える回路がある。(2の4乗は16なので、最大16本の出力に切り替えできる。)

さて、パタヘネ本によると、X86系CPUの命令数は約1000個らしい[18]。なので、CPU用のデマルチプレクサを作りたい私たちは、単に2進-1000進デコーダを作ればいいだけであろう。パタヘネ本によるとX86系の命令数は2012年の時点で900個である。


なお「デコーダ」という用語について、一般に情報科学でも、CPUが命令を解釈して実行させるために各装置に信号を送るための「命令デコーダ」とは、このデマルチプレクサのことであると考えられる(※ パタヘン本ではマルチプレクサの前段階のシステムが「命令デコード」だとしている[19])。

なお、マルチプレクサのことは、「データセレクタ」という[20]。字面だけみると「命令デコーダ」と「データセレクタ」とは別物のように見えるが、ようするにデマルチプレクサによる回路出力の切り替えと、その付属システムにすぎない。私たちは本質を学ぼう。

さて、私たちに必要なスイッチ個数はアセンブラの命令によって切り替えるので、最低でも数百個もの切り替えが必要だろうが(ひょっとしたら数千個や数万個)、

ここで2の16乗は65536なので、16ビット分のデマルチプレクサの配線をすると、回路を切り替えられる。

数万本もの導線というと、人間の手作業ではムリだが、しかし半導体リソグラフィーを使えば可能であろう。というか、たとい困難だろうが、それをやった企業が半導体チップ産業の覇者になっているのだろう。

半導体の製造では紫外線照射や近年の研究開発ではX線照射をするが、照射の回数を、私たちの目標ではなんとか16回~32回程度に抑えたい。


では、何にしたがって回路を切り替えるかというと、(上述したように)アセンブラのプログラムにしたがって回路を切り替えればいいのである。そのため、アセンブリ言語は、スイッチ切り替えに対応可能なような命令体系になってなけれればらない。


さて、そのアセンブラのプログラムの読み取りのためには、現在の命令の位置を示す「プログラムカウンタ」も作る必要がある。 では、そのプログラムカウンタをどうやって作ればいいか? ヒントは、昔の紙テープ式コンピュータで、要するにあれと同じことを電子データで実現すればいいのである。


まず、紙テープコンピュータがどういう仕組みになってるか考えよう。


紙テープのうち、一定の読み取り面だけを読み取るわけで、その紙テープにデータに対応する穴が開いていて、その穴の位置や大きさなどの組み合わせで、プログラムデータを区別しているわけである。

そして、紙テープ読み取りコンピュータがプログラムデータを読み取り終わって、そのプログラムを実行し終えたら、テープを一定幅だけ進めるわけである。

そして、また、テープを読み取り、プログラムを実行することの繰り返し。

これを電子回路的に実現すればいい。


まず、プログラム実行時のデータ保存領域が必要だが、これはメモリで代用できる。 ハードディスクは低速なので、ハードディスクからメモリに読み出す仕組みになっている。


現代のパソコンでは、狭義の命令プログラム保管用のDRAMメモリと、プログラム以外の画像データや音声データやテキストデータなどのDRAMメモリは、同じメモリ基盤上にある。だが原理的には、必ずしも同じ基板上にする必要は無く、そういう構造の場合をハーバード・アーキテクチャという。つまり、命令用メモリと、データメモリとが別々の部品であるのが、ハーバード・アーキテクチャである[21]。かつてハーバード・マークワンで採用されていたアーキテクチャが、そういう(命令とその他データの分離された)アーキテクチャだった)。1990年以降でもAtmel社のAVRシリーズの8bitマイコン用CPUが、ハーバード・アーキテクチャを採用している。Arduino という2005年以降に普及したイタリアのマイコンボードで8bit用ボードにAVRシリーズを採用している。ハーバード型(ハーバード・アーキテクチャ)は欠点として回路構造が複雑になるものの、長所として高速化しやすいという利点もある[21]。そのため、一部の制御用マイコンでもハーバード型が活用されているという[21]>。


さて、演算回路などでプログラムの実行が終わったら、なんらかのフィードバックをプログラムカウンタに返して、前に送ったプログラムが終了したことをカウンタに知らせる必要がある。つまり、どんなプログラム命令でも、命令1個につき、かならず終了後に、命令実行を確認するためのフィードバックを返す必要がある(ただし、デジタルのフィードバック信号)。命令実行ずみ検出フィードバックが帰ってくるまで、プログラムカウンタを進めない仕組みにする必要がある。

電子回路の本を読めばフィードバックなんて、どこの本でも書いてある。

演算回路の出力に、フィードバック回路もつければいいだろう。


あとは、プログラムをそもそもどうやって入力するかだが、コレは単にキーボードから入力すればいい。キーボードは、人間が手作業でボタンを押して入力するので、単に、そのボタンの押された内容にもとづいて、メモリに機械語を保存すればいい。

メモリそのものは、SRAMのようにフリップフロップ回路で作ることができる。

電源を切った後にも保存したければ、磁気テープならぬ磁気ハードディスクにでも保存しとけばいい。黎明期のコンピュータだって、紙テープに保存していたわけだ。

コンピュータはこんな仕組みらしい。


IN命令とOUT命令[編集]

上述のように、キーボードやタイマなどのメモリ以外のデバイスとの送受信は、IN命令または OUT命令で、ポート番号を仲介して制御する仕組みである。

INやOUTの方向は、CPUから見た方向である。

CPUからみて、デバイスに送る方向の場合に OUT 命令である。

デバイスからの情報を、CPUのレジスタが受け取る場合に IN 命令である。


IN命令やOUT命令の引数で、引数でI/Oポートアドレスを指定することで、どのアクセスの読み書きをするかを指定する。

CD/DVDドライブやハードディスクは、IDEコントローラーで制御されているので、おそらく、こちらの制御でも読み書きのできる可能性がある。

また、キーボードコントローラーも、ポートアドレスが割り当てられている。


in src, dest GAS文法
in dest, src MASM文法


out src, dest GAS文法
out dest, src MASM文法


実例として一般に、キーボードコントローラーのポートアドレスは 0x60 と 0x64 である。0x60がデータ用、0x64がコマンドやステータス用。[22]。 なので

out  0x60, al

のような命令により、読み書きが可能である[23]


しかし、コンピュータ黎明期からある、いくつかのデバイスの制御では、IN命令やPUT命令を使わずとも、すでに割り込み命令として INT 命令が用意されている(IN 命令とは異なる)。BIOSによって、 INT 命令が用意されている。

具体的に言うと、仕様上は、ディスプレイやフロッピーディスクやハードディスク、キーボードなどは、割り込み int 命令によって制御できる仕様であり、int 0x10 はディスプレイとの割り込み、int 0x13 はディスクアクセスとの割り込み、などの仕様がある。


(※ 要 確認)しかし、近年のデバイス(マザーボード基板上のデバイス。なんらかのコントローラーや専用通信カードなど)には、int命令は対応していない。なので、たとえばUSBコントローラーなどの制御などは、それらの規格に詳しくなる必要がある。

また、対応してない新デバイスをBIOSレベルで制御したい場合は、IN命令やOUT命令でコードを書いていくしかないだろう。(※ 以上、要 確認)

本書では、とりあえず、80年代あたりの古いアーキテクチャを前提とする(資料が入手しやすいので)。そのため、最近のアーキテクチャでは動作しない可能性がある。

実験の手段[編集]

一般に、エミューレーターを使って実験します。

無料のフリーソフトでも、w:QEMUなどのエミュレータを使えるので、アセンブラのw:NASM(無料ソフト)などで(アセンブリコード入力を経由して)機械語を入力していきます。

※ 次の節で、qemuの使い方を大まかに説明する。

原理的には、アセンブラを経由せずともバイナリエディタ(Hex editor)といわれるもので機械語を直接に書いてプログラムするのも、原理的には可能です。そもそもアセンブラ自体どうやって開発されたかを想像すれば、おそらくバイナリエディタを、流通しているCPU用のアセンブラ命令にあわせて特化してプログラミング用に作られたソフトウェア(がアセンブラ)なのでしょう。

※ なお、日本語ではバイナリ(2進数)エディタといいますが、しかし英語では16進数(hex)で「ヘックス エディタ」Hex editor といいます。なので Linux などで対応の機械語エディタを探す場合は、16進エディタような名前のソフトを探すことになります。


原理の理解としてはバイナリエディタの存在に気づくことも重要ですが、しかしバイナリエディタによるプログラミングではコード記述が覚えづらく非現実的ですし、一目では内容が分かりづらいです。なので、一般にアセンブラで入力していくのが、OS製作では現実的でしょう。


さて、NASMは、あくまでエミュレーター上での仮想化なので、完全には仕組みを再現していませんが、しかし個人の学習では、止む(やむ)を得ません。 もしもエミュレータを使わずに、BIOSのブート設定を 毎回 書き換えて実機のパソコンでブートの実験などを毎回したとしたら、とてもメンドウです。なので、エミュレータを使って、手間を省きます。

なお、一般的なLinuxなどでは、リリース直前の開発の後半などで、確認のために最終的に開発者たちはDVDなどに書き込んでブートしてみたりするなどして、実験します(いわゆる「ベータテスト」などで、DVDのISOを無料配布している)。


なお余談ですが、OS自作でなくCPU自作をしたい場合、w:GNU Binutilsなどの無料のクロスアセンブラがありますので、そういうのを活用します。半導体製造工場などを個人は持ってないので、ソフト的にエミュレートするしかありません。

備考: エミュレータの種類[編集]

エミュレータにも種類や方式が色々とあります。

方式のひとつとして、ホストOSのインストールされているパソコンのCPUを間借りする方式があります。

別の方式として、ホスト側のCPUは間借りせずソフトウェア内に仮想のCPUを制作する方式のものなどもあります。


コミュニティ ベース の qemuやBochs というエミュレータは、(ホストCPUをあまり)間借りしないで、ソフト的に仮想のCPUを作る方式のものである[24]

いっぽう、オラクル社のVirtual Box やヴイエムウェア社のVMWare というエミュレータは、ホストPCのCPUを間借りする方式のものである。


私たちの学習の目的には、VirtualBox 的に複数のCPUが混ざり合うと学習的に分かりづらくなるので、qemuのようなソフト的にCPUの機能を再現するほうが分かりやすいと考え、qemuを優先して紹介することにする。

また、qemu はオープンソースである。

なお、Virtual Box は昔は非オープンソースだったが、現在はオープンソース版のVirtual Box がある。

なお、オラクル社のようなホストCPUを間借りする方式にも長所はあり、仮想化の中では処理速度を速くしやすいという長所もある。


本書では、とりあえず qemu を前提として説明する。


qemuの設定方法と使用方法[編集]

設定方法(Windowsの場合)[編集]

前提として、ダウンロードとインストールは既に終わっていると仮定する。

qemuは、どうやって使うかというと、コマンド端末(いわゆるコマンドプロンプト Command prompt、Winodwsでは「DOSプロンプト」ともいう。Linux/Uuix/BSD界隈でいう「ターミナル」terminal のこと)から使うソフトである。


なので、まずパス(Path)というものを通さないといけない。

環境変数にパスを追加する。

C言語/開発環境』に、GCCという別ソフトだが、Windows上の場合での環境変数のパスの追加方法を説明しておいたので、Windowsユーザーなら同様の方法を使えばいい。

Linuxの場合は知らん。


大まかに言うとWindows環境の場合、普通にインストールしたら C:\Program Files\qemu に諸々のファイルがあるハズなので、この C:\Program Files\qemuをパスに追加すればいい。

Windwosのバージョンによって細かい手順は違うので、具体的な操作方法は省略する。


さて、パスが追加し終わったら、はたして本当にパスが通ってるの確認のため、Windwosのコマンドプロンプトを起動して

C:\Users\ユーザー名>qemu-img

というふうに、コマンド「qemu-img」でも入力してみよう。

なお、「qemu」というコマンドは無いので、「qemu」とコマンド入力しても。

'qemu' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

とエラーになるだけである。

さて qemu-imgコマンドは引数を指定して使用するコマンドなので、上記コマンド「qemu-img」を実行すれば、端末上で引数が足りないことをqemuから警告されるハズである。


C:\Users\ユーザー名>qemu-img
qemu-img: Not enough arguments
Try 'qemu-img --help' for more information

のように、qemuから警告されれば、ひとまずqemuのインストールは成功である。


使い方[編集]

あらかじめ、起動したいファイルを機械語で作っておき、たとえば、testos.img とかの名前をつけて、ホームフォルダなどコマンド端末の認識できる場所に保存しておく。

そしてコマンド端末で

qemu-system-i386 testos.img

のようにコマンド qemu-system-i386 を使えばいい。


では、そのOSイメージをどうやって作るか。

原理は、

まず、アセンブラの nasm で書く。(nasm は Netwide Assembler ともいう。)

※ (gccだと難しくなるので、OS制作ではgccは使わないほうがイイ。gccによる出力は、標準設定では、Windowsの実行ファイル(PEフォーマットなど)に自動変換して、OSそのものの自作には余計な情報を負荷してしまう。そうしないためには、引数であれこれと設定する必要がある。gccでアセンブリコードをそのまま機械語にしようとすると、引数がけっこう多くなる。しかも残念なことに、日本のネットには、この操作を詳しく解説した資料が無い。)
なので、 nasm を使おう。


あらかじめアセンブラコード形式で、OSにしたいファイルをアセンブリコードで作っておいて、testos.asm などの名前で保存しておく必要がある。


nasmなら、testos.asm を(PEフォーマットでない、直訳の)機械語にするコマンドは

nasm testos.asm -o testos.img 

だけで終わる。

そして、こうして作成したブートイメージをqemuで起動する方法は、ホームファイルに先ほど作成した testos.img を置いた上で、

C:\Users\ユーザー名>qemu-system-i386 testos.img

のコマンドだけで終わる。


では、元になるアセンブリコードをどうやって調達するか? とりあえず、ネットで読者の勉強用に(彼らの)自作OSのブートローダーのアセンブリコードを公開してくれている人がチラホラといるので、彼らのコードで実験するのが良いだろう。

※ いちおう、本書でも、初等的なブートローダのコードを記載している(※ 後述)。

Bochs の使い方[編集]

ネット上に Bochs の使い方の入門書がぜんぜん無いので、wikibook で教えることにする。

Bochsでは、アセンブルはできない。

Bochsの用途は、すでに別ツール(たとえば nasm んど)で作成ずみの img ファイルをBochsで起動するだけのものである。

asmファイルからimgファイルへのアセンブルは、あらかじめ nasm などで行っておく。


Bochsの利便性は、コマンドを覚えなくて言いことである。起動コマンドのBochsだけ押せばウィンドウが起動するので、あとはそのウィンドウ側でGUI的に操作してエミュレータの起動をできるという便利ツールなエミュレータが Bochs である。


さて、Boshs をインストールしてから、環境変数(パス)の設定を終えれば、 コマンドプロンプトで、コマンド bochs で起動する。つまり

ユーザー名>bochs

のように「bochs」の部分を入力する。

で、起動するとダイアログ画面が現れる。


さて、これだけでは、そもそも何のファイルを起動するかすらも設定されてない。なので、これから、このダイアログ画面で、それを設定する。


まず、真ん中の項目(中央ペイン)にある「Edit Option」 の「Disk & boot」をダブルクリックすると、画面が遷移して、

オプション画面である「Bochs Disk Options」画面になる。この項目で、何のファイルを起動するかを設定できる。 そのためには、

その「Bochs Disk Options」画面の中で、タブ「ATA Channel 0」> 子タブ「First HD/CD on Channel 0」 をクリックすると出てくる画面に、

上から2段目あたりに

Path or physical device name          Browse

という項目があるので、その          のなかに、起動したいimgファイル名を入れる。

たとえば、「testos.img」ファイルを起動したいなら、

Path or physical device name testos.img      Browse

のようになる。


こうして、(おそらく)あとはこのオプション画面を終了して(ダイアログ・ウィンドウの右上のクローズ用の×ボタンを押せばいい)メインメニュー画面に戻り、右ペインにあるStartボタンを押せばいい。

Panic ウィンドウが表示されて Message 欄に「specified geometry doesn't fit on disk」 と書いてあるが、このまま左下の欄にある Continue をクリックして、OKを押せばいい。


なお、Path or physical device name の設定をしてないで空欄のままにしておくと、Startの際に Message 欄に 「no bootable device」 と出る。


BochsのDtart>OKの後のエミュレート起動後の終了方法は、ウインドウの上部にあるメニューバーの右側のほうに、○印の中にタテ線「|」のある終了ボタン「(|)」があるので、これを押せばいい。(右上の×ボタンは使えない。×ボタンをクリックしても反応しない。)

擬似命令を使ってブートローダを作ろう[編集]

擬似命令とは[編集]

一般的なアセンブラには DB 命令というのがあり、これはORG命令などで指定したメモリに値を書き込む命令であるが、これで機械語も書き込みできる。

備考: なお、DB命令やORG命令は、(CPUに対する命令でなく)アセンブラに対する命令なので「擬似命令」(ぎじ めいれい、pseudo-instruction)という。コンパイラなどにより機械語に翻訳する際、擬似命令は翻訳されない。というか、そもそも翻訳の指示に関する命令なので、翻訳内容ではないので、擬似命令は 機械語にならない のは当然である。
※ 擬似命令とは、たとえるなら(C言語などの)高級言語でいう、いわゆる「マクロ」のようなものである。


もし、機械語を書き込む先を、メモリではなく、ハードディスクやUSBメモリやフロッピーディスクなどのブート可能なメディアにすれば、原理的には、これでブートローダを作れる。

※ たとえばマイナビ出版の『OS自作入門』(川合秀美 著)では、まさにこの方法でブートローダやそれから起動するGUIつきOSを作っている。

なお、DB とは data byte の略だと言われている。


さて、では、OSを作るためには、まず、ブートセクタを書き込めばいいのですが、では、どういう内容のことを書き込めばいいのでしょうか。


ブートローダ[編集]

まず、ブートローダを書き込むわけですが、

規格により、

MBRのサイズは512バイトと決まっており、

また、

ブートローダと認識させるために必要な署名は、512バイト目の最後の2バイトに16進数で「aa55」と書き込まなければならない(※ 511バイト目が「aa」、512バイト目に「55」である)、

と決まっている。


なので、このために

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

をどこかに書き込む必要があります。

times とは、nasm の擬似命令のひとつで、繰り返し命令のことです。


「aa55」の署名を書き込まずにQemu上で機械語を起動しても、いくつかのメッセージのあとに「No Bootable device」などと表示されるだけです。


さて、上記コードの場合は

510 - ($ -$$) 回数だけ、db 0 (つまり「0を書き込め」)を繰り返せ、

という命令です。

$ は、そのtimes命令が出されたときの現在のアドレスです。

$$ は、現在のセクションの最初のアドレスです。

リトルエンディアンの仕組み

間違えて、「55aa」(マチガイ!)を書き込まないようにしてください。詳しくは『w:リトルエンディアン』で調べてください。

MBRのサイズは512バイトと決まっており、その末尾2バイトに「aa55」と書き込むので、times 繰り返し命令 では「510」と2バイトぶん、余らせています。


なので、とりあえず下記のように書き込みましょう。


コード例
(※ 実際に動く。 windows7 上の qemu で動作を確認。)
mov ah, 0x0e           ; 1文字出力
mov al, 'H'
int 0x10
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

成功すれば、 qemu上で、「H」と表示されます。


int 0x10 とは、ディスプレイへの割り込み命令のひとつです。int でBIOSにより割り込み命令を指示しています。intの引数で、どんな割り込みをするかを指示しており、int 0x10 はビデオサービスの割り込みです。

ですが、int 0x10 だけでは、ディスプレイに文字も画像も表示できません。

int 0x10に加えて、さらに、何を割り込ませるかの指定を行う必要があり、 mov ah, 0x0e で文字割り込みを指定しています。(※ 詳しくは『en:w:INT 10H』(英語版ウィキペディア)などを参照してください。)

ah は、アキュムレータ レジスタ の上位ビットの部分です。
al は、アキュムレータ レジスタ の下位ビットの部分です。

int 0x10 の命令は、上記の手本コードのように、 アキュムレータ レジスタで指示しなければなりません。

なお、学校などで、もしかしたら、情報科学・計算機科学の教育では「アキュムレータ」とは「加減乗除などのためのレジスタ」とか習うかもしれませんが、しかし実際のCPUでは、(上記コード例のように)設定などの一時保存にもアキュムレータが流用されています。


(※ 要確認)また、電源の投入直後(いわゆる「ブート」の直後)に使用できるレジスタは、まず16ビットCPU時代のレジスタだけです。32ビットCPUや64ビットCPU時代に追加されたレジスタは、初期状態では使用できないです。つまりraxやeaxは、16ビットCPU時代のモードでは使用できないです。axなどは16ビットCPU時代のモードで使用できます。
16ビットCPU時代のモードを「リアルモード」といいます。32ビットCPU時代以降のモードを「プロテクトモード」といいます。
2010年代の現代のパソコンでもリアルモードは、下位互換性(かい ごかんせい)などのために残されています。下位互換性とは、古いバージョンのプログラムが動くようにするという事です。
一般的名パソコンでは、ブート直後(電源の投入直後)はリアルモードになっています。言い換えれば、ブート直後はプロテクトモードではないです。
なおモードの切り替え方法は、CR0レジスタ(というのがある)の最下位ビットの値が1ならプロテクトモードになります[25]

[26]。CR0の最下位ビットのが0ならリアルモードです。実際には、プロテクトモードの移行のためには、さらに グローバル デスクリプタ テーブル(GDT)というものを作成する必要があるが、初学者には当面は知らなくていいので、もうプロテウトモードの説明は後回しにする(※ 現時点では未記述)。


さて、この場合での int 0x10 は、alレジスタにある文字を表示できます。


「Hello 」と表示させたければ、「H」だけでなく、同様の操作を繰り返し、「e」「l」「l」「o」を追加で表示させればいいだけなので、


コード例 『基本』
(※ 実際に動きます。 windows7 上の qemu で動作を確認。)
mov ah, 0x0e           ; 1文字出力
mov al, 'H'
int 0x10

mov al, 'e'
int 0x10

mov al, 'l'
int 0x10

mov al, 'l'
int 0x10

mov al, 'o'
int 0x10

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

でブート後に「Hello」と表示できます。

発展的な話題[編集]

ラベルやジャンプ命令を使うと、繰り返し命令を実装できる。

上の「Hello」のプログラムを、ラベルなどによる繰り返し処理でプログラムするとするなら、下記のようになるl


コード例 『発展』
(※ 実際に動きます。 windows7 上の qemu で動作を確認。)
org 0x7c00

mov ah, 0x0e
mov bx, aisatu

kurikaesi:
mov al, [bx]
int 0x10

add bx, 1       ; 使い終わったので 1文字ぶん、進める

cmp al, 0       ; 0に等しければ
je owari        ; (0に等しければ)owariにジャンプしろ

jmp kurikaesi   ; (条件に関わらず)kurikaesi を実行しろ

                ; ここまでラベル kurikaesi の内容

aisatu:
db "Hello aisatu", 0x00     ;文字の終わりとして 0x00 を使用した


times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

owari:
org 0x7c00

org 0x7c00 とは、このようなテキスト処理をするのにBIOSに予約されている領域が7c00 なので、そこから書き始める必要がある。 org は擬似命令であり、これからの書き込みのメモリ位置を指定する命令である。

実際、実験してみると、上記コードの冒頭に org 0x7c00 を削除してみて実行しても、文字が出力されない(実験すれば分かる)。qemuの実装はそうなっている。

コード例『基本』ではorg0x7c00が無くても文字出力できたのに、コード例『発展』では0rg0x7c00が無いと文字出力できないのは奇妙かもしれないが、ともかく qemu の実装がそうなっている(そして、おそらく市販のパソコンのBIOSも同様の仕様だろう。

mov bx, aisatu

また、 mov bx, aisatu は形式的には、あたかもラベルaisatuの内容を代入するかのような表現ですが、

実際は、単にaisatu ラベルの先頭のメモリを代入するだけです。

なお、aisatuラベル先にある db "Hello aisatu", 0x00 も、単に、aisatuラベルの先頭の位置から順番に「H」「e」「l」「l」「o」 (以下略)に対応するアスキーコードを代入しているだけです。


bx とは、単なるbxレジスタ(ベースレジスタ)。siレジスタ(ソースインデックス)で書いてもいい。

というか、ソースシンデックスで書くほうが、お行儀がいいだろう。


本書wikibooksでは単にアルファベット順で「A」の次は「B」なので、ソースインデックスを紹介するのがメンドウなので、BXで代用した。


また、ラベル(たとえば aisatu ラベル)の中身を定義する前に、mov bx, aisatu のように、先にラベルを代入するなどの指示をする必要がある。

C言語になれていると宣言の順序が逆なので奇妙だが、ともかくアセンブラでは、こうである。(おそらくだが、(文法の形式ではコピー・代入だが、)実際はマシン内部では作業用メモリの確保の処理や、それらの複数のメモリ領域どうしの間の関連づけの作業をしているだけなのだろう。(そして、その結果をBIOS作業用メモリのどこかに保存しているのだろう) )


mov al, [bx] について。

仮に、bxに角カッコ[ ] をつけずに mov al, bx と書いても、まずalレジスタとbxレジスタのサイズが合わないのでコンパイル時にエラーになり、「 error: invalid combination of opcode and operands」と表示されるだけである。si(ソースインデックス)レジスタの場合でも同様である。サイズをあわせたいなら「bx」でなく「bl」にする必要がある。

かといってbxからblに書き換えて、 mov bl, aisatu とか mov al, bx とか add bl, 1 とか入力して実行しても、出力結果はワケのわからない文字列のあとに

(※ わけのわからない文字列がここら辺にズラズラと並んでから)0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ[_]^`?@abcdefghijklmnopqrstuvwxyz{ (※ このあと、記号やギリシャ語のアルファベットなどの何かの文字列が並ぶ)

とか、途中にアスキーコード表の順序どおりのような文字列が表示されるだけである。

なぜこうなるかというと、 mov bl, aisatu のあとに mov al, bl をすると、あたかもalに aisatu ラベルを入力するかのように見えますが、そうではなくalに代入されるのは、aisatuラベルの最初のアドレスの番号です。


なので add bx, 1 で1文字ぶんを進めることができるわけです。

私たちは、アドレス番号ではなくて、そのアドレス番号のメモリにあるデータの中身(内容は文字のアスキー)を表示したいわけですので、 [ ] でククる必要があります。

C言語になれていると、ついつい、何の記号もない「変数っぽい?」何かは(勘違い→)「きっとアドレス番号ではない」と思い勝ちですが、しかしアセンブラではむしろ逆で、movm命令の2番目の引数は、記号のついてないほうがアドレス番号の解釈になります。


また、代入される側(上記の例では al )は、代入された数値の由来は不明であり、数値の由来がはたしてアドレス番号に由来なのか、あるいは、電卓みたいな加減算をしたいのか、なんにも知りません。

たとえば、もし bl レジスタに保管されている(メモリの)アドレス番号が200番だとして、 mov al, bl をしたら、alに200が保存されますが、しかしalはその「200」が何なのか知りません。200円の価格の商品の計算なのか、全校生徒が200人の小学校なのか、アドレス200番なのか、alには、知るよしが無いのです。


int 0x10

int 0x10 は、繰り返し命令の中で、毎回、使用する必要がある。


さて、もし上記コード中の int 0x10 の位置を移動して、繰り返しの終了後に移動して「owari」ラベルで int 0x10 をまとめて実行しようとしても、文字出力はされない(実験すれば、そうなる)。


このことから、int 0x10 は、単にディスプレイ出力をするだけの命令ではなく、文字出力に必要なメモリ処理をしていることが分かる(※ もし、単にディスオプレイの画面後進をするだけなら、あとでマトメて処理してもいいハズになる)。おそらく org 0x7c00 で宣言したメモリ領域以降に、情報を書き込んでいるのだろう。

「cmp」とは比較命令で、等しいかどうかを調べて報告する命令で、出力は「真」か「偽」のどちらかであり、等しければ真、等しくなければ偽である。

そして、「je」とは条件つきジャンプで、直前の条件判定が「真」なら、指定したラベルにジャンプする命令である。


その他の例

次のようlodsb 命令とsiレジスタを使っても良。lodsbは1バイトぶんだけレジスタsiの指し示す先のデータを読む込む(ロード load)命令であり、さらに使用後に自動的にsiレジスタの指し示す位置を1バイトぶんだけインクリメントしてくれるので、手間が省ける。

コード例1
org 0x7c00
mov ah, 0x0e           ; 1文字出力を設定

mov si, msg

LOOP:
	lodsb

        cmp al, 0x00
        je Loop_break

	int 0x10

        jmp LOOP

Loop_break:

msg:
    db 'Hello, World!', 13, 10, 0
    
    
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa

解説

lodsb の内容は

mov al, [ai] 
inc si

と同じである。


その他の例2
cmp al, 0x00
je Loop_break

の代わりに

test al, al
jz Loop_break

でも良い。

また test の代わりに

or al, al
jz Loop_break

でも良い。

コード例2
org 0x7c00
mov ah, 0x0e           ; 1文字出力を設定

mov si, msg

LOOP:
	lodsb

        test al, al
        jz Loop_break

	int 0x10

        jmp LOOP

Loop_break:

msg:
    db 'Hello, World!', 13, 10, 0
    
    
times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa


この他、lodsb を他の命令に書き換える方法もあるが、説明がメンドウなので省略。

メモリ直書き[編集]

グラフィックのVRAM直書き[編集]

まず、設定として、

mov al, 0x13
mov ah,0x00
int 0x10

というコードが必要です。

int 0x10 はグラフィック割り込みです。

グラフィック割り込みの場合、ahは0に固定するように規格で決まっています。

alによって、ビデオモードを指定しています。

alが0x13なら、320 x 200ドット x 16色モード(8bitカラー)

という意味です。


一般的なパソコンではグラフィック描画用のVRAMアドレスは 0xa0000 から 0xaffffの領域が割り当てられています。

なので、mov命令で、このアドレスに書き込むと、VRAMに直書きできます。


で、問題は、通常の16ビットCPUモードのmov命令では、 メモリ番号は4ケタまでしかアクセスできない。「a0000」や「affff」は5ケタであることに注目。

で、しかも、ブートローダ起動中のリアルモードでは、この16ビットCPUのモードである(らしい)

なので、ともかく、通常の方法ではアクセスできない。

5ケタのメモリ番号にアクセスするためには、

「セグメント方式」という手法を使う。


簡単に言うと、

物理アドレス番号 = セグメントベース値×16 + オフセット値

なお、上式の数は十進数である。

16進数になおすと、単にセグメントベース×16は、末尾に0をつけたすだけである。

※ セグメントベース値の1の違いはオフセット値に換算すれば たった16 なので、よって、もし2つの物理アドレス番号があって、それらのセグメントベースの値が違っていても、オフセット値でそのぶんの差を補う加減があれば、2つの物理アドレス番号が重なることがあるし、用途によっては重なっていても構わない[27]
また、2つのアドレス番号があって、セグメントベース値とオフセット値がそれぞれ異なっていても、「セグメントベース値×16 + オフセット値」の合計値が同じなら、それは同じアドレスを指し示す処理になる[28]。たとえば 0000:1000 と 0100:0000 は同じアドレスを指し示す(※ 文献『Linuxのブートプロセスをみる』での例)。

これらのセグメントベースを格納するために「セグメントレジスタ」と言う専用のレジスタを使う。

16ビットCPUのセグメントレジスタには

CS (コード セグメント)、
DS (データ セグメント)、
ES (エクストラ セグメント)、
SS (スタック セグメント)、

の4つがある。

なおFSとGSは32ビットCPU以降のセグメントレジスタである。

CSは、CPUが実行するプログラムを格納するためのセグメントとして、CPUに使用させる。

DSは、メモリの読み書きといったデータ関係の手段のセグメントとして、CPUに使用させる。

SSはスタック関係のセグメントで使用。

「エクストラ」とは、「その他」とか「追加の」のような意味。


よく分からなければ「セグメントベース」でググると、詳しく紹介してくれている親切なITブロガー日本人さんが何人かネットにいるので、それらのページを参照してください。

で、書式は、[セグメントベース:オフセット]の書式である。



下記のようなコードで、ブート直後の画面の任意の場所にピクセル単位で色をぬれる。

コード例 1
mov AL, 0x13            ; ビデオモード0x13
mov AH, 0x00
int 0x10

mov BX, 1111            ; ピクセルで書き込みたいアドレスの計算用
mov AX, 0xa000            ; VRAMアクセスのためのセグメント処理で使用
mov DS, AX

mov AL, 0x01        ; 色の設定
mov byte [DS:BX], AL        ; VRAMに書き込み

mov BX, 1112        ; 次に書き込みたいアドレスの計算用
mov byte [DS:BX], AL        ; VRAMに書き込み(以下、同様)

mov BX, 1113
mov byte [DS:BX], AL

mov BX, 1114
mov byte [DS:BX], AL


mov AL, 0x031        ; 色の変更
mov BX, 1115
mov byte [DS:BX], AL

mov BX, 1116
mov byte [DS:BX], AL

mov BX, 1117
mov byte [DS:BX], AL

mov BX, 1118
mov byte [DS:BX], AL

mov BX, 1119
mov byte [DS:BX], AL

mov BX, 1120
mov byte [DS:BX], AL

mov BX, 1121
mov byte [DS:BX], AL

times 510 - ($-$$) db 0
dw 0xAA55


予備知識
mov命令によるメモリへの書き込み 

さて、mov 命令でメモリに書き込むには mov BYTE [123], 0x4567 の書式で書き込みます。

この場合、メモリの123番地に、数値(16進数で)4567を書き込むわけです。

[ ] をつけると、メモリへの指示だとアセンブラなどが認識します。

ここでの「BYTE」 は1バイト長で書き込め、という命令です。

2バイト長なら(BYTE でなく) WORD になります。

4バイト長さなら DWORD になります。

※ なお、WORDやDWORDを使う場合、リトルエンディアンの順序で書き込まれます。


なお

mov al, BYTE [123]

のように書いた場合は、メモリ123番地にある内容をレジスタalに書き込め、という命令になります。

このように、(書き込みだけでなく)メモリからの読み込みにも [ ] は使えます。

つまり、 [ ] をつけると、メモリへの指示だとアセンブラなどが認識します。


コードの解説

0xa0000は画面の左上だが、そこに色を塗っても見づらいので、

上記コードでは 0xa1111 から色を塗り始めることにした。


なお 0xaffff は画面の右下である。


画面の真ん中の上のほうに、なんか緑色っぽい線が5ミリくらいの長さで水平に引かれている結果が表示されると思う。

上記のコードでは、分かりやすさを重視して、あえて繰り返し命令(ラベルやジャンプ命令で実装できる)を使わなかったが、実際に図形を書きたい場合は、ラベルを活用して繰り返し命令で処理するのが効率的だろう。

なお、上記コード例1で

mov AX, 0xa000
mov DS, AX

とあるが、


これを、(下記はエラーになる)

mov DS, 0xa000

とまとめても、なぜかエラーになる。

なので、手本のコードのように、レジスタを経由する必要がある。

テキスト直書き[編集]

※ 調査中

一般的名パソコンでは、英数字などのテキスト出力も、ブート直後の段階では、専用のメモリ領域が用意されるので、このメモリを書き換えることで、テキスト文字を出力できます。(ただし、漢字や平仮名・カタカナなどは無理。)

ブートプログラムの確認作業などに、便利な機能でしょう。


また、 int 命令によって、1文字ずつ書いていく方法は、原理は単純ですが、実装では、処理速度があまり速くないという問題があります。

ラベル命令やジャンプ命令などを使ってコードの行数を減らしても、int命令で書き込みをするよりも、テキスト用メモリを直に書き換えするほうが処理速度が速くなります。

一般的なパソコンでは 0xb800 からの領域がテキスト用メモリに割り当てられています。


なので、まず

mov ax, 0xb800

と指定します。



BIOSに予約されたメモリ領域[編集]

メモリ領域のうち、0xa0000 から0xfffff までの領域は通常のパソコンでは、BIOSがハードウェアを管理するために使用することになっています[29]


このため、それらハード管理以外のソフトウェア的な処理をするためにメモリ使用したい場合は、この領域を避けてメモリを使用する必要があります。

こういった用途には Linuxなどの現代のオープンソースOSでは、 メモリを使用する際には 0x100000 以降の領域を使うのが一般的です。

原理的には 0xa0000 未満の領域も使用可能ですが、使いすぎ等のミスによって0xa0000以降にハミ出る恐れがあるので、なるべく 0x100000 以降の領域だけを使うほうが安全でしょう。


ハードディスクなどへのアクセス[編集]

まず、フロッピーディスクやハードディスクなどに読み書きのできる割り込み命令で、 int 0x13 というのがあります。

2000年代の現代では、これを大容量デバイス用に拡張した拡張 int 0x13 というのがあります。

どちらの int 0x13 とも、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。

このように、int 0x13 では、割り込み時におけるレジスタの役割が決められています。

拡張 int 0x13

さて、拡張 int 0x13 を使って、フロッピーディスクやハードディスクなどの記憶媒体に割り込みをできます。(USB対応については今後の見通しは不明。書き込みできるものもあるようだが、)


int 13h は、レジスタなどの数値で 書き込みの方式や対象を指定します。拡張 int 13h と、非格調 int 13h では、レジスタの解釈が違っております。

格調 int 0x13 のほうの方式を LBA方式といいます。

格調 int 0x13 (LBA方式)の仕様
AH 読み込みは 42h で固定
AL 読み込むセクタ数
DL ドライブ番号
DS:SI Disk Address Packet のアドレス

格調 int 0x13 では、レジスタに収まりきらない様々な情報を、任にの Disk Addres Packet (DAP)という場所に配置しており、その形式も決まっています。

※ 調査中


非拡張の int 0x13

なお、拡張されてない int 0x13 は、ハードディスク容量などの小さい時代の古い規格のものであり、現代では、読み書きに時間が掛かったり、あるいは不可能です。


非拡張の int 0x13 では、ドライブ番号は、DLレジスタ(データレジスタの下位(Low)の部分)で指定します。

AHレジスタが 0x02 なら 読込み、AHレジスタが 0x03 なら 書込み です。

仕様
AH 読み込みは 0x02 で固定
AL 読み込むセクタ数
CH トラック番号(下位8ビット)
CL
DH ヘッド番号
DH ヘッド番号
DL ドライブ番号
ES:BX または ES:EBX 読み込みたい先のアドレス


参考サイト
ディスクBIOS
INT 13h - jou4のブログ

int 13hともいう。

参考文献
林高勲『作って理解するOS』 328ページで、非拡張 int 13h のCHS方式について解説あり。

キーボードサービス[編集]

int による割り込み[編集]

まず、int命令で、キーボードサービスの割りこみがあり、 int 0x16 がキーボードサービスである。

コード例
mov ah, 0x0e           ; 1文字出力
mov al, 'p'            ; 「pushしろ」・・・のつもり
int 0x10

mov al, ' '
int 0x10

mov al, 's'            ; 「space」・・・のつもり
int 0x10

mov al, ' '
int 0x10

mov al, ' '
int 0x10


.LOOP:
	mov ah,0x00       ; キーボード入力待ち. 0x10 でもいい
	int 0x16

	cmp al, ' '         ; 空白なのでスペース
	jne .LOOP           ; 直前の比較cmpの結果が偽(否定)だったらループする


mov ah, 0x0e           ; 1文字出力
mov al, 'f'            ; 「finish 終わったよ」・・・のつもり
int 0x10

times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa


さてint 0x16 を呼び出す際、

mov ah,0x00       ; キーボード入力待ち. 0x10 でもいい

なら、ah = 0x00 は、キーボードのキー入力待ちである。ah = 0x10 だと拡張キーボード対応らしい[1]


in または out命令による処理[編集]

実は一般的なパソコンでは、アセンブリ言語の命令で、いくつかのハードウェアに読み書きのアクセスするための、専用の命令がある。

IN 命令と、 OUT命令である。

そして、キーボードなど、昔のどこのパソコンにも存在したハードウェアは、実はハードウェア番号が決められている(「I/Oポートアドレス」などという)。

たとえば、キーボードはハードウェア番号(I/Oポートアドレス)が十六進数で0x60 番である。(メモリマップとは別。メモリアドレスの0x0060などにdb命令で書き込んでも、目的のハードにはアクセスできない。 )


IN命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に、もうひとつの引数で指定した目的デバイスから送られた値が保存されます。(引数の順序はアセンブラの種類などによって異なるので、説明を除外。)

また、OUT命令で、引数で指定したレジスタ(普通はALレジスタやAXレジスタを指定する)に格納されている値が、もうひとつの引数で目的デバイスに送られます。


たとえば、下記のようなコードで、キーボードの文字「E」または前後のWかRを押すと、1行ぶん下の位置に文字「G」が表示される。

コード例
mov al, 'p'       ; 「push e」と表示の予定
int 0x10

mov al, 'u'
int 0x10

mov al, 's'
int 0x10

mov al, 'h'
int 0x10

mov al, ' '
int 0x10

mov al, 'E'
int 0x10

mov al, 0x0a	;改行の指示
int 0x10


.LOOP:
	in al, 0x60 		; in al, 0x0060 でもよい

	cmp al, 18 		; 値が文字 'E' かどうか判定のつもり
	jne .LOOP 		; 偽なら LOOP 冒頭にジャンプ

mov ah, 0x0e           ; 1文字出力
mov al, 'G'
int 0x10


times 510 - ($ - $$ ) db 0
db 0x55 , 0xaa


解説

in al, 0x60 を使えば、キーボードコントローラーから送られてきたキーも al に入力されます。

in al, 0x60 とは、けっして「レジスタ al に 60 を代入しろ!」(×、マチガイ)という意味ではなく(そもそも定数の代入だけなら mov 命令だけで可能である)、「ポートアドレス 0x60番 のポートから送信されてきたデータを、レジスタalに代入しろ」という意味です。勘違いしないでください。

そして、キーボードコントローラーのポートアドレスが 0x60 なので、めでたく、上記コードでキーボードから押されたボタンの情報を受け取れます。


さて、キーボードを押したとき、押したボタンに対応するスキャンコードが、パソコン内部にあるキーボードコントローラー(KBC)という装置に送信される仕組みになっています。

このスキャンコードは、アスキーコードとは異なります。

上記コード例にある cmp al, 18 の数値「18」とは、スキャンコードでの番号です。だいたいスキャンコードで18番のあたりが文字 W,E,R のどれかのあたりです。


また、アスキーでは「2」と「"」とは別の文字ですが、しかし日本語キーボードの場合、「2」と「"」はボタンが同じなので、スキャンコードでは同一になります。

このように、物理的に同じ位置にあるかどうかで、スキャンコードは決まります(なお、JIS配列キーボードやUS配列キーボードのように言語が違うキーボードでも、位置が同じなら、ほとんどのボタンのスキャンコードの内容も同じ場合が多い)。


また、スキャンコードは、押されている時に送信されるコード(「メイク コード」という)と、離した瞬間に送信されるコード(「ブレイク コード」という)とが、それぞれ違うコードです。


日本語キーボードは OADG という規格にほぼ統一されています。

ですが、世界的にスキャンコードの規格は、古いものでも3種類くらいあり、さらにUSBキーボードの規格は別です。このため、日本語 OADG のスキャンコードも、現在でも3種類くらいあります。

下記のリンクが詳しいです。 [2]

なお、一般的に「メイクコード/ブレイクコード」の書式です。


たとえば、ボタン「1」(「!」と同じ)のスキャンコードが「16 / F0 16」とかかれていれば、メイクコードが「16」であり、ブレイクコードが「FD 16」という意味です。(ある規格では、ブレイクコードは、メイクコードの先頭にF0がついたものになっている。)

なお、キーボードコントローラーもインテルなどが製造していました。かつて Intel i8042 というキーボードコントローラーが有名でした。


より正確な仕組みとしては、シリアル通信(PS 2 信号)などで送られた信号をKBCあたりでスキャンコードに変換しているわけですが、CPUから見ればスキャンコードしか見えないので、あまり気にしなくていいでしょう。


参考サイト

0から作るOS開発 カーネルローダその3 プロテクティッドモードへの移行とA20

※ 本wikibooksの当ページが完成するまでの間、上記の参考サイトが分かりやすくて役立つと思いますので勉強してください。



出力例?[編集]

次のようなコードを使えばledが点滅するらしいのだが、しかしエミュレータでの実験では分からなかった(Windows起動により、すでにLEDが点灯しているので、区別しづらい)。

	mov al, 0xED
	out 0x60, al

0xED というのは、LED点灯に関する命令の番号。


BPB[編集]

一般的なブートローダのいくつかには、ブートセクタに BIOS Parameter Block (BPB)というものが書かれており、これは BIOS への指示や設定を出すブロックです。

パソコンの電源を投入して、まず最初に起動するのは BIOS なのですから、このブロックが必要です。少なくとも Windows系OSのブートローダーでは、そうなっていると言われています。

このように、ブートセクタは、書式がほぼ決まっています。

冒頭でJMP命令で ブートローダ(IPL: Initial Program Loader)へジャンプ
BPB
IPL
位置0x01FE に 0x55AA と書いて「ここまでがブートローダ」だとBIOSに認識させる

という構成になっています。

ジャンプしてしまうので、BPB はプログラムカウンタでは読み取れず、BPBはBIOSしか読み取れないことに注目しましょう。


市販のOS自作本にある、DB命令で書き込む冒頭の 「DB 0xeb」 と言うのも、このJMP命令のことです。

JMP命令はX86系CPUの機械語では eb です。

『IA-32 インテル®アーキテクチャソフトウェア・デベロッパーズ・マニュアル』『中巻A:命令セット・リファレンスA-M』3-411



プロテクトモード[編集]

プロテクトモードとは、CPUの32ビットモード(および32ビット以上のモード)のこと。

32ビットモードには、アクセス権の無い状態からのアクセスを禁止するという、特権レベルによる保護機能などがあるので、プロテクトモードという。


プロテクトモードにいこうするためのコードの一部を抜粋すると、おおむね書きのような感じになる[30]

コード例
    mov eax, cr0    ; 
    or ax, 1    ;  
    mov cr0, eax    ;  最下位ビットに1を設定
    jmp ジャンプ先のラベル

ジャンプ先のラベル:
    mov ax, ここに何か    ; mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
解説

cr0レジスタの最下位ビット(PEビットという)が1だとプロテクトモードである、という仕様である。

    mov eax, cr0   
    or ax, 1    
    mov cr0, eax

の3行の処理で、cr0レジスタの最下位ビットを1に設定している。なお、このcr0の最下位ビットのことをプロテクト・エネーブルド pr0tect enebled という意味でPEビットという。


設定後にわざわざジャンプ命令 jmp を通す理由は、CPUの先読みした命令を破棄するためである[31][32] 。ジャンプ命令には、先読みを破棄する機能がある。

なお、パイプラインという仕組みにより、CPUは先読みしている。このパイプラインの先読みを破棄することをフラッシュという。

実はCPUは、いくつか先の命令をすでに先読みしている(これがパイプライン)。プロテクトモード以降では、それが不具合の原因になるので、いったんフラッシュする(カラにする)必要がある。

なので、CPUのパイプラインをフラッシュするためにジャンプ命令を使っている。



全体像

さて、一般にWindowsやLinuxなどのOSには、パーティションという、インストール時にハードディスクの使用領域を決める機能がある。

実はCPU側に、メモリに関する機能だが、似たような動作を機能がある。


GDT(グローバル ディスクリプタ テーブル Global Descriptor Table)といって、メモリのアドレスのどこからどこまでがそのCPUで使える領域を定義する機能がある。

で、プロテクトモードでは、あらかじめ、このGDTを設定しないと動作しない。そういう仕様で、むかしのインテルあたりの人が決めてしまったので、従うしかない。

で、実はCPUにGDTレジスタ(GDTR)という、GDTの場所を保管する専用レジスタがあるので、このGDTレジスタにGDTのアドレスなどの情報を入れる必要がある。

さらに、このGDTレジスタに書き込むための専用の命令 lgdt (ロードgdt)があるので、これを使う必要がある。(「書き込みだから save では?」という疑問も、わくかもしれないが、こういう名前に決まってしまってるので、従うしかない。)


同様に IDT(Interrupt Descriptor Table)というのがある。

さらに、16ビット時代の昔はCPUのアドレスバスが20本までだったので、リアルモードでは利用するアドレスバスが20本までという制限が掛かっており、A00からA19までを使用している。A20以降はマスクされている。


この制限のことを「A20のマスク」という。プロテクトモードに以降するためには、このA20のマスクを解除しないといけない。


下記の順序で作業しないといけない。そういう仕様である。

  1.  GDT(Global Descriptor Table)の作成
  2.  GDTレジスタの設定
  3.  IDT(Interrupt Descriptor Table)の作成
  4.  IDTレジスタの設定
  5.  A20のマスク解除
  6.  CPUへの割り込み禁止
  7.  cr0レジスタの最下位ビット(PEビット)を1に設定
  8.  CPUの先読み(パイプライン)を除去する(jmp命令で除去できる)
  9.  セグメントレジスタの設定


A20マスクの解除には複数の方法がある。

  • キーボードコントローラーから解除
  • System Control Portから制御
  • BIOSの割り込み命令 int 15 で解除


キーボードコントローラから解除できる理由は、単に昔のインテルかどこかの人が設計したとき、たまたまキーボードコントローラ用のアドレスバスが余ってたからだけと言う理由らしく、あまり技術的な深い意味は無い。


なお作業の順番について、A20のマスク解除の順場は多少前倒しになっても平気なようである。

Linuxのブートローダはkernelには無い[編集]

ある程度、理解が進むと、 Linux など実際に活用されているオープンソースOSのブートローダを調べたいと感じるかもしれません。

まずOSの起動で最初に動くのはブートローダだからです。

しかし Linux を開発している kernel.org のサイトには、ブートローダは無いのです。


Linux で仕様されているブートローダは、Gnu(グニュー)というオープンソース・ソフトウェア団体の作っている GRUB (グラブ)というソフトウェアです。

なのでブートローダをソースコードを探す場合も、Gru Grub のウェブサイトを探す必要があります。

書籍だとアスキー出版『Linuxのブートプロセスを見る』などの題名の書籍で Grub を紹介してるので、ついつい何となく、ソースコードを読むためにリーナスの管理している kernel.org を探しがちですが、しかし、よくよく考えたら、Grub は Linux ではありません(実際、Windows版Grubもある)。

もし『Grubのブートプロセスを見る』だと売れないので、出版社が「Linuxのブートプロセスを見る」という題名にしたのでしょう。

私たちOS開発をしたい読者は、けっして出版社にマインド・コントロールされたままでは、イケません。真実「Grub は Linux ではない」に気がつきましょう。


Grub のソースコードのダウンロードにgitコマンド(git clone などのコマンド)が必要なので、あらかじめインストールしておくか、Gitコマンドが最初から使える Ubuntu か Fedora などのLinuxをパソコンにインスト-ルしておきましょう。

Git コマンドのインストールと、Git Hub などのウェブサイトとは別物ですので、混同しないようにしましょう。

外部リンク: Gitの公式サイト


なお Windows版のGitはVimエディターにしか対応してないとの情報がインストール時に出ますが、しかしわざわざvimを別途インストールしなくても、Win版GitをインストールすればWindwowsコマンドプロンプトでGitコマンドを使えるようになります。

参考文献[編集]

書籍出版物[編集]

  • 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版
  • 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、
  • 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日 初版 第1刷 発行、
  • Igor Zhirkov 著、古川邦夫 監訳『低レベルプログラミング』、翔泳社、2018年01月19日 初版 第1刷 発行、

このほか、マイナビ出版の『OS自作入門』(川合秀美 著)を参考にしたが(同じ出版社の『自作エミュレータで学ぶX86アーキテクチャ』でも川合氏の文献をところどころ引用的に出して技術解説している)、しかし本書 wikibooks では残念ながら技術内容の確認作業には使えなかった(著者の川合氏が技術内容の正確さよりも初心者の取っ付きやすさを優先しているため。また、ところどころ説明が不十分(アセンブリ言語による各論の理解よりも、読者がC言語でコードを作って動かせることや、全体像の把握を重視している、川合氏の方針のため)。)技術内容の確認作業には、上記一覧の別文献を参考にした。あと、川合氏の文献は2005年の出版物という事もあり、古いので、現代の動向の確認には別文献に当たることになった。

なお、上記文献一覧にある林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』(技術評論社)を、川合氏が監修している。

ただし、直接にこそ川合氏の文献を本wikiは参考にしてないものの、しかし川合氏の文献は日本でのOS自作解説書の草分け的な存在なので、間接的には本wikiも影響を大きく受けているだろう。だからこそ、2019年代の現代でも増刷・再版され続けている(絶版になってない)。

他の著者(名誉のため名前は伏せる)のOS自作本の中には、絶版になってしまったものもある。

脚注[編集]

  1. ^ 『H8マイコン入門』、堀桂太郎、東京電機大学出版局、2003年12月20日 第1版 第2刷、162ページ
  2. ^ 『H8マイコン入門』、堀桂太郎、東京電機大学出版局、2003年12月20日 第1版 第2刷、54ページ
  3. ^ ダニエル・P・ボベットおよびマルコ・サセティ著『詳解Linuxカーネル 第3版』、高橋浩和監訳、2011年5月20日 初版 第5刷、40ページ
  4. ^ 堀桂太郎『Z80アセンブラ入門』、東京電機大学出版局、2007年5月20日 第1版 第2刷、20ページ
  5. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、53ページ
  6. ^ 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、160ページ
  7. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、55ページ
  8. ^ 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、160ページ
  9. ^ 白崎博生『新装改訂版 Linuxのブートプロセスをみる』、株式会社 KADOKAWA、2014年10月2日 初版発行、55ページおよび66~67ページ
  10. ^ 『Tips IA32(x86)命令一覧 Oから始まる命令 OUT命令』
  11. ^ ダニエル・P・ボベットおよびマルコ・サセティ著『詳解Linuxカーネル 第3版』、高橋浩和監訳、2011年5月20日 初版 第5刷、244ページ
  12. ^ 堀桂太郎『Z80アセンブラ入門』、東京電機大学出版局、2007年5月20日 第1版 第2刷、143ページ
  13. ^ 堀桂太郎『Z80アセンブラ入門』、東京電機大学出版局、2007年5月20日 第1版 第2刷、134ページ(「143」の誤記ではない)
  14. ^ 白土義男『H8ビギナーズガイド』、東京電機大学出版局、2008年2月20日 第1版 第9刷、16ページ
  15. ^ David A. Ratterson, John L.Hennessy『コンピュータの論理と設計 第5版[上] ~コンピュータのハードウェアとインターフェース~』、2019年9月5日 第5版 第7刷 発行、238ページなど
  16. ^ 淺川毅『コンピュータ工学の基礎』、東京電機大学出版局、2018年9月10日 第1版 第1刷 発行、100ページおよび101ページ
  17. ^ 淺川毅『コンピュータ工学の基礎』、東京電機大学出版局、2018年9月10日 第1版 第1刷 発行、98ページ
  18. ^ David A. Ratterson, John L.Hennessy『コンピュータの論理と設計 第5版[上] ~コンピュータのハードウェアとインターフェース~』、2019年9月5日 第5版 第7刷 発行、157ページの図2.43など
  19. ^ David A. Ratterson, John L.Hennessy『コンピュータの論理と設計 第5版[上] ~コンピュータのハードウェアとインターフェース~』、2019年9月5日 第5版 第7刷 発行、276ページおよび277ページ図4.33など
  20. ^ David A. Ratterson, John L.Hennessy『コンピュータの論理と設計 第5版[上] ~コンピュータのハードウェアとインターフェース~』、2019年9月5日 第5版 第7刷 発行、238
  21. ^ 21.0 21.1 21.2 『H8マイコン入門』、堀桂太郎、東京電機大学出版局、2003年12月20日 第1版 第2刷、6ページ
  22. ^ (※ 参考文献『作って理解するOS』、初版第1刷、447ページ )
  23. ^ (※ 参考文献『作って理解するOS』、初版第1刷、448ページ )
  24. ^ 内田公太・上川大介 著『自作エミュレータで学ぶX86アーキテクチャ』、株式会社マイナビ (※出版社名)、2015年8月30日 初版 第1刷 発行、6ページ
  25. ^ 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日、235ページ
  26. ^ Igor Zhirkov 著、古川邦夫 監訳『低レベルプログラミング』、翔泳社、2018年01月19日 初版 第1刷 発行、53ページ
  27. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、20ページ
  28. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、25ページ
  29. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、70ページ
  30. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
  31. ^ 白崎博生 著『Linuxのブートプロセスをみる』、株式会社KADOKAWA(発行)、アスキー・メディアワークス(プロデュース)、2014年10月2日 初版、98ページ
  32. ^ 林高勲 著『X86系コンピュータを動かす理論と実装 作って理解するOS』、技術評論社、2019年10月9日 初版 第1刷 発行、471ページ

関連項目[編集]

外部リンク[編集]

  1. 『Tips BIOSサービス割り込み ビデオサービス』
  2. 『Expanded Main Page - OSDev Wiki』(英語)
  3. FATファイルシステムのしくみと操作法
  4. パソコンのレガシィI/O活用大全