* HDD系デバイスから起動しよう
 -(by [[K]], 2009.04.01)
 -発展課題のページ(全部読む前にここを読むのは混乱するのでおすすめじゃないです)
 ----
 -原理的にはここで紹介する方法で、内蔵・外付けHDD、USBメモリ、USB接続のカードリーダ、(最近のパソコンによく付いている)メモリカード端子などから起動できます。これらはBIOSからは同じように扱われているためです。ただしこの通りの対応ではない機種も存在し、その場合はこの方法ではうまく行かないかもしれません。
 *** 注意
 -COLOR(#FF0000){このページは未完成で、ツール群も準備できていないままです。このページは参考のために残しますが、質問は一切受け付けません。すみません。}
 *** まえがき
 -筆者は先日、ASUSのEeePC-900-XというネットブックPCを買いました。それでいろいろ遊んでいるうちに、このパソコンでも「はりぼてOS」で遊んでみたいと思いました。もちろんUSB-FDDをつければ起動はしますが、当然ながらかなり時間がかかります。またUSB-CDドライブをつければCDから起動もできますが、そういうかさばるものをつけるのはなんか気乗りがしません。・・・そしてこのPCにはSDカードスロットが標準で付いています。だからここにSDを入れて、そのSDから「はりぼてOS」が起動したらかっこいいだろうなあと思いました。ということで数日ほど試行錯誤したら結構簡単にできるようになったので、紹介しようと思います。
 --ちなみに電源ボタンを押してから11秒で起動するようになりました。WindowsXPの3倍は速いです(笑)。
 *** 方法
 -普通HDD系デバイスにOSをインストールさせるには、以下の手順を踏みます。
 --パーティションを準備して、そこから起動できるように設定する。
 --パーティションのフォーマットを考える。
 --パーティションの中にどうにかしてOSに必要なファイルを書き込む。
 --パーティションがHDD内のどこに確保されるのかを前もって決めておくことはできないので(強引に決めておくこともできるけど、そうするとそのHDDを他のOSと共存させるのは難しくなる)、IPL等のプログラムは複雑になる。もしくはインストーラが複雑になる。
 -はっきりいって、これは全部面倒です。いきなりこういうのをやると理解できなくなるのは間違いないので、最初は簡単な構成を考えようと思います。最近すっかり安くなったUSBメモリやSDカードなどは、実はHDDと同じフォーマットをすることになっています。だからこれらを「容量が小さくて、万一失敗しても大損害にはならずに済んで、しかもパーティションが一つしかない(=パーティションで悩む必要のない)特別なHDD」だと思うことにしましょう。
 -FDからの起動を思い出してほしいのですが、結局CYLSは最大でも33くらいにしかできませんでした。つまりどんなに容量があっても当面は600KBくらいしか利用できないのです(将来的にはこの問題もクリアするとしても)。だから4GBとか32GBとかそんな大容量のものは全くいりません(とりあえず2MBくらいあれば十分です)。アクセススピードもBIOSを経由して起動する今回の方法では、あまり速くはならないみたいです(これも将来的には解決するかもしれないけど)。だからとにかく安物を準備しましょう。いきなりいいものを買ってきて失敗したらどうしますか。まずは練習用・実験用にできるだけ安いものを使いましょう。
 -以下ではUSBメモリとして書きますが、実際はどのデバイスでも同じです。
 -ちなみにSDカードなどを扱う場合にカードリーダを買うことがあるかもしれません。その場合カードリーダは端子がたくさんある30in1みたいなタイプよりは、端子が一つしかなくてせいぜい2〜3種類くらいのメモリカードにしか対応していないようなカードリーダのほうが、今回の目的に限っては扱いやすいです(複数の端子を制御するタイプは、BIOSも扱いに苦労して、うまく行かないものがあったので・・・筆者の経験でしかないですが)。
 ----
 -USBメモリにはたいていFAT16かFAT32のフォーマットがかかっています。そしてここにOSやアプリケーションのファイルをポンポンいれておいて、あとはIPLががんばってそれらのファイルを見つけ出して起動するというのが普通です。しかしこれはとんでもなく大変です。IPLは基本的にはアセンブラで書かないといけないですし、しかも512バイトしかありません。そんな容量でFAT16かFAT32かも分からないフォーマットを自動判別し、クラスタサイズも512とは限らないので自動で判別するようなものを書けるでしょうか。いやまあ普通のOSではそれを何とかしてやっているわけですが、それは「入門」レベルをはるかに超えています。説明だけでやる気がなくなること請け合いです。
 -ということで、今回はもっと簡単な方法を考えました。まず今までどおりの方法でFD用のディスクイメージを作ります(余談ですがFDは容量が1440KBと決まっているので、説明するのがとても簡単だったのです)。そしてその1440KBのファイルを、たとえば64MBのUSBメモリがあったとして、その中にコピーします。これはimgtolとかを使うことなく、普通にコピーしてかまいません。・・・というのは、FDドライブであるA:に対してimgtolを使うことはできるのですが、それ以外のドライブに対してimgtolやその他のディスクイメージツールを使おうとすると、Windowsがエラーを出して妨害してしまうのです。だから結局入門用としては普通のファイルコピーをするしかない、というのが筆者の結論なのです。
 -これでとにかくUSBメモリのどこかにFDのときと同じ1440KBが書き込まれたはずです。あとはIPLでこの1440KBの先頭だけを探し出して、そしてその中を今までと同じ方法で読めばいいわけです。やったー、頭いい!
 --とまあそういう方法なので、この1440KBが連続したセクタに並んでいないと困ります。ファイルを書いたり消去したりを繰り返した状態だと、ファイルの断片化というのがおきて、もしかしたら1440KBが連続したセクタに書かれていないかもしれません(飛び飛びになっていて、どこに続きがあるのかはFATをたどらないと分からない)。そういうのは困ります。これを防ぐ一番簡単な方法は、一度USBメモリをフォーマットしなおすことです。これで全部のファイルが消えるので、ここに1440KBのファイルをコピーすれば、もちろんファイル領域の先頭から連続してきれいに書かれます。
 --とにかくこの1440KBさえ連続していればいいので、このファイルをコピーした後は、残った容量を好きなことに使ってかまいません。64MBのUSBメモリなら60MBくらいは残るでしょうから、そこに好きなファイルを入れてもいいわけです。
 ----
 -さて1440KBの先頭を探し出す方法ですが、これもものすごく簡単な方法を取ります。・・・普通は、MBRのパーティションテーブルがどうのこうので、そこからパーティションを見つけて、そして今度はそのパーティションのフォーマットがどうなっているか調べて、ファイル名情報がどこにあるかを探して・・・みたいな話になるのですが、こんなのができるのなら、今回の入門仕様にする必要が無いじゃないですか。
 -そうじゃなくて、1440KBの中に見つけやすい(そして誤解しにくい)シグネチャを書いておいて、USBメモリの中からそのシグネチャを探すんです。これはもちろんそれなりに時間がかかります。10秒とか20秒とかかかるかもしれません。でもその代わり、将来USBメモリがどんなフォーマットになってもこの手法は有効です。毎回起動するたびに探すのは起動時間がもったいないので、ディスク内でのFDイメージの位置が変わったときにだけやることにしましょう。だから起動時には探しません。探しておいた場所をいきなり読むのです。
 --余談ですが、筆者はかつてこれとは違うアプローチを試みたことがあります。つまりUSBメモリのフォーマットがFAT12だったりFAT16だったりFAT32だったりしたのに腹を立てて、単純で統一的な一種類のフォーマットにそろえようとしたのです(SF16という名前にしました)。そうすればこの中にファイルを置いてもFDのときのように比較的簡単にアクセスできるわけです。・・・これはうまく行きましたが、今回の方法よりは複雑なので、ここではやっていません。
 *** MBRのプログラム
 -USBメモリの場合、シリンダ番号0、ヘッド番号0、セクタ番号1のセクタのことを、ブートセクタとは言いません。IPLとも言いません。MBR(マスターブートレコード)といいます。なぜこんな風に言うのか、それは今は気にしないことにします。そして言い方は違うものの、結局この部分が起動時に0x7c00番地に読み込まれるということに変わりはありません。
 -MBRの場合も0x7dfeに 55 aa があります(これがないと起動しない)。しかもその上、0x7dbeから0x7dfdまでも使用禁止です。ここにはパーティション情報が書かれるためです。その代わり、FAT12とかディスクの名前とかを書かなくていいので、最初にジャンプ命令を書く必要はありません。いきなりプログラムを書けます。
 -また以下のプログラムでは、USBメモリを読み込むときに、シリンダやヘッド番号などを指定してはいません。基本的にFDDと同じようにもできるのですが、それをやるにはBIOSに最大シリンダ番号や最大ヘッド番号などを問い合わせなければいけません(USBメモリにはそもそもシリンダもヘッドもないのですが)。どうしてかというと、容量や製造会社によって、総シリンダ数などが違うせいです(FDDのときは80シリンダ・2ヘッド・18セクタと決まっていたのでとても簡単でした)。そしてどうにかして最大シリンダやなどを取得したとしても、実はその方法でアクセスできるのはUSBメモリの先頭から8GBまでで、残りの領域にはアクセスできません。・・・そこでこのプログラムではFDDの時とは異なるBIOSの機能番号を使ってアクセスしています。これはシリンダやヘッドは全部無視して、先頭から何番目かのセクタなのかだけを指定します。これをLBA(ロジカルブロックアドレス)方式といいます。この方法の場合、8796093022208GBまで指定できます。
 --参考: OS-wikiのINT(0x13);のAH=0x42;
  
  [INSTRSET "i486p"]
  
          ORG     0x7c00
  
          STI     ; BIOSがSTIし忘れていても大丈夫なために
  
  ; MBR全体を0x0600〜0x07ffへコピー
  
          MOV     AX,0 
          MOV     SS,AX
          MOV     SP,0x7c00
          MOV     DS,AX
          MOV     SI,SP
          MOV     DI,0x600
          MOV     CX,512/2
  copyloop:
          MOV     AX,[SI]
          ADD     SI,2
          MOV     [DI],AX
          ADD     DI,2
          SUB     CX,1
          JNZ     copyloop
          JMP     0x0060:0x0027 ; コピーが終わったら、コピー先へジャンプ(0x0629)
  
          ORG     0x0027
  
  ; LBA方式が使えるかどうかの確認
  
          PUSH    CS
          POP     DS
          PUSH    CS
          POP     ES
          MOV     [drv],DL ; 起動ドライブ番号がDLに入っている(BIOSがDLに入れてからMBRを起動するので)
          CMP     DL,0x80
          JB      error    ; HDD系デバイスでなければエラー
          MOV     AH,0x41
          MOV     BX,0x55aa
          INT     0x13
          JC      error
          CMP     BX,0xaa55
          JNE     error
          TEST    CL,0x01
          JZ      error
  
  ; あらかじめ探しておいたIPLを読み込んで実行
  
          MOV     SP,0x0640
          MOV     DI,load_LBA
          MOV     CX,[load_secs]
          MOV     BX,[load_addr]
          CALL    0x60:0x01b0
          JC      error1
          JMP     FAR WORD [farjmp_vector]
  
  error:
          MOV     SI,msg
  putloop:
          MOV     AL,[SI]
          ADD     SI,1
          CMP     AL,0
          JZ      fin
          MOV     AH,0x0e
          MOV     BX,15
          INT     0x10    ; putc
          JMP     putloop
  fin:
          HLT
          JMP     fin
  
  error1:
          MOV     SI,msg1
          JMP     putloop
  
  read_LBA:       ; [ES:DI]からの8バイトに相対LBA, CXにセクタ数, BXにセグメント
  
          PUSH    DS
          PUSHAD
          MOV     AX,0x4200
  rw_LBA:
          PUSH    CS
          POP     DS
          MOV     SI,param
          MOV     [SI+6],BX
          MOV     EBP,[ES:DI+0]
          MOV     EDX,[ES:DI+4]
          ADD     EBP,[LBA0+0]
          ADC     EDX,[LBA0+4] ; ADCは直前の計算の繰り上がりも含めて足す命令
          MOV     [SI+8],EBP
          MOV     [SI+12],EDX
  .readloop:
          MOV     DL,[drv]
          PUSH    CX
          PUSH    AX
          INT     0x13
          POP     AX
          POP     CX
          JC      .error
          ADD     WORD [SI+6],0x20
          ADD     DWORD [SI+8],1
          ADC     DWORD [SI+12],0
          SUB     CX,1         ; CF == 0
          JNZ     .readloop
  .error:
          POPAD
          POP     DS
          RETF
  
  write_LBA:      ; [ES:DI]からの8バイトに相対LBA, CXにセクタ数, BXにセグメント
  
          PUSH   DS
          PUSHAD
          MOV    AX,0x4300
          JMP    rw_LBA
  
  drv:    DB     0x80
  msg:    DB     "ExtINT13H not ready",0x0d,0x0a,0
  msg1:   DB     "Load error",0x0d,0x0a,0
  
          RESB    0x0188-$
  load_secs:
          DW      0 ; IPLのセクタ数(1より大きくてもよい、pcctolで自動設定)
  load_addr:
          DW      0 ; IPLのロード番地(0x07c0以外でもよい、ppctolで自動設定)
  farjmp_vector:
          DW      0,0 ; IPLの実行開始位置(0:0x7c00以外でもよい、pcctolで自動設定)
  param:
          DW      0x10,1,0,0
          DD      0,0
  load_LBA:
          DD      0,0 ; IPLの相対LBA(0以外でもよい、pcctolで自動設定)
  LBA0:
          DD      0,0 ; ディスクイメージ開始のLBA(pcctolで自動設定)
  
          RESB    0x01b0-$
          JMP     read_LBA
          RESB    0x01b4-$
          JMP     write_LBA
          RESB    0x01b8-$
          DB      "KHBH",0,0
  
 -説明:
 -このMBRではIPLがディスクイメージの先頭から始まっているとは決めていません。ディスクイメージのどこから始まっていてもいいのです。またIPLが512バイト(1セクタ)だとも決めていません。5KB(10セクタ)でも50KB(100セクタ)でもいいのです。読み込む番地も0x7c00とは決めていません。どこだっていいのです。
 -なんでこうしたのかといえば、IPLが512バイト以下でなければいけないとか、先頭に書かなくてはいけないとか、0x7c00に読み込まれるとか決まっているせいで、OSを書くのが大変になったからです(みなさんもそう思いますよね?ね?)。IBMのおじさん(おばさん?)たちが勝手に決めた不便なルールを引き継ぐ必要はないわけです。だから自由にできるようにしました。
 -最初にMBRを0x0600〜0x07ffへ転送しているのは、0x00800〜0x9ffffを全部あきにするためです。こうすれば、きっとOSは使いやすいでしょう。
 
 -(つづく:個人的に忙しいので続きは数週間後?)
 *** 関連リンク
 -このページを作るまでの試行錯誤:
 --http://wiki.osask.jp/?KHBIOS.memo001
 --試行錯誤を見たらかえって混乱するかもしれないので、お勧めはしません。
 --とりあえず「はりぼてOS」とOSAkkieの起動には完全に成功しています(起動だけではなく、アプリももちろん使えています。2009.04.01時点で)。
 
 * こめんと欄
 -USB メモリーは起動が可能なことではなければならないし, BIOS 設定を別にすると起動が可能だと聞いたが他の方法でも USB メモリーに起動することができるんですか? -- ''Kor_Lee_Hee_Rak'' SIZE(10){2009-04-03 (金) 23:58:54}
 -できるかどうかは試してみないと分かりません。 -- [[K]] SIZE(10){2009-04-05 (日) 00:16:10}
 -pcctolってなんですか? -- ''イシハラシュウイチ'' SIZE(10){2018-03-07 (水) 02:27:24}
 
 #comment

リロード   新規 編集 差分 添付   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS