30日でできるOS自作入門_DAY3

harib00a

; ディスクを読む

        MOV     AX,0x0820
        MOV     ES,AX
        MOV     CH,0            ; シリンダ0
        MOV     DH,0            ; ヘッド0
        MOV     CL,2            ; セクタ2

        MOV     AH,0x02         ; AH=0x02 : ディスク読み込み
        MOV     AL,1            ; 1セクタ
        MOV     BX,0
        MOV     DL,0x00         ; ドライブ番号0x00 = Aドライブ
        INT     0x13            ; ディスクBIOS呼び出し
        JC      error
  • JC命令=「jump if carry」
  • キャリーフラグ?が1(フラグが立っている)時は、jumpを実行する
  • INT命令の戻り値を見て、エラーハンドルしている?
  • 恐らく、INT命令は失敗っすると戻り値として1を返す */harib

AHレジスタの値で、ディスクに対する処理を指定する

AH = 0x02; 書き込み
AH = 0x03; 良い込み
AH = 0x04; ベリファイ?
AH = 0x0c; シーク
  • キャリーフラグとは、1bitしか記憶できないレジスタ。このようなレジスタはflagと呼ばれる。
  • CH, CL, DH, DLにはそれぞれ、シリンダ番号、セクタ番号、ヘッド番号、ドライブ番号を代入する

      CH シリンダ番号
      CL セクタ番号
      DH ヘッド番号
      DL ドライブ番号
    
  • シリンダ番号は外側⇒内側に番号がインクリメントされていく

  • シリンダ=cylinder=円筒
  • シリンダは片面に80本(#0 - #79)存在する セクタは各シリンダ内に18個(#1 - #18)存在する

*linuxのセクタサイズは以下で確認可能

[root@centos7 ~]# fdisk -l /dev/sda1 | grep 'Sector size'
Sector size (logical/physical): 512 bytes / 4096 bytes

バッファアドレス

  • 引用されているホームページに以下のような記述がある
  • ES:BX = バッファアドレス; ベリファイ時、シーク時にはこれを参照しない。
  • バッファアドレスとは、メモリのどこに読み込むかを表す番地
  • BXレジスタのみだと、0~0xffff(65535=63.99kb)までしか表せないが、ESレジスタを合わせて使うと、(EX×16)+BXまで表現できるようになる
  • ES=0x0820、BX=0の場合 0x0820(2080) × 16 = 0x8200(33280) / ESレジスタの値を16倍したものが起点になる /

セグメントとは

  • メモリをセグメントと呼ばれる一定サイズの大きさに区切ることをSegmentationと呼ぶ(用例としては、Segmentation Faultとか)
  • セグメントレジスタとはセグメントレジスタはセグメントの開始位置を決めるもので、具体的には以下が存在する。(いずれも16bit=2byteまで格納可能?)

    • CS
    • DS
    • SS
    • ES
    • FS
    • GS

セグメントレジスタにおけるセグメントの開始位置

  • セグメントの開始位置は、以下の計算式で計算される。
  • セグメントの開始位置 = セグメントレジスタに入れた値×16(16進数では0x10)

セグメントレジスタにおけるセグメントの大きさ

  • セグメントレジスタによって確保されるメモリ空間の大きさは 0xFFFF(65535)+1となる。 / 0始まりのため。時刻のHHの最大値は23だが、時間の合計値は24となるのと同じ理屈 /

  • DSレジスタの値が0xFFFF(DEC 65535)の時、 セグメントの開始位置 = セグメントレジスタに入れた値×16(16進数では0x10) なので、 セグメントの開始位置 = 0xFFFF × 16 = 0xFFFF0(DEC 1,048,560)となる。 / 16進数で末尾に0をつけると16倍したことになる /
  • また、 セグメントの大きさは 0xFFFF(DEX 65535)+1 = 0x10000(DEC 65536) となる。 そのためアドレスの末番号(アクセスできる最大アドレス)は、 0xFFFF0(DEX 1,048,560) + 0x10000(DEC 65536) = 0x10FFF(DEC 1,114,096)となる。
  • Ref: 0から作るOS開発 ブートローダその6 セグメント

余談

bitとbyteと2進/16進数

- bitとは'2進数で表した'数字の並び ( 0 or 1)
 - byteとは8bitを一つグループとして表した概念

 - 1byte = 8bitの最大値は、1111 1111(10進255)なので、
   0000 0000 から 1111 1111までの256個の数字を表すことができる。
   つまり、1byteで256通りのパターンを表現できる。

   2進数の'1111'は0xFとあらわすことができるため、
   1byte = 0xFFとなる。つまり、0xFFで256通りのデータを表現可能

英語での〇進数の呼び方

2進   binary(bin)
 10進  decimal(dex)
 16進  hexadecimal(hex)

同様に、本の例、つまり ES = 0x0820 BX = 0 の時は、 開始位置:0x08200(DEC 33,280) アクセス可能な最大アドレス = 0x08200(DEC 33,280) + 0x10000(DEC 65536) = 0x18200(DEC 98816) …と思ったが、本を読むと 0x08200(DEC 33,280)から0x83ff(DEC 33,791)までとなっている。 DEC 33,791 - DEC 33,280 = DEC 511(0x1FF) メモリ1区画で1byte(8bit)までの値が格納可能なので、 合計512byte分の領域があることになる。 納得いかないが、読み進めよう。。。

後は、MAKEFILEがだいぶ読みやすくなっていた。GJ

TOOLPATH = ../z_tools/
MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

# デフォルト動作

default :
    $(MAKE) img

# ファイル生成規則

ipl.bin : ipl.nas Makefile
    $(NASK) ipl.nas ipl.bin ipl.lst

haribote.img : ipl.bin Makefile
    $(EDIMG)   imgin:../z_tools/fdimg0at.tek \
        wbinimg src:ipl.bin len:512 from:0 to:0   imgout:haribote.img

# コマンド

asm :
    $(MAKE) ipl.bin

img :
    $(MAKE) haribote.img

run :
    $(MAKE) img
    $(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
    $(MAKE) -C ../z_tools/qemu

install :
    $(MAKE) img
    $(IMGTOL) w a: haribote.img

clean :
    -$(DEL) ipl.bin
    -$(DEL) ipl.lst

src_only :
    $(MAKE) clean
    -$(DEL) haribote.img

harib00b

新たな命令として、JNC命令が紹介される。要するに直前の命令値を見て、戻り値(というかキャリーフラグ)が0(正常終了)であれば go to するとのこと

harib00c

突然、C0-H0-S18セクタと言われてわからなかったけど、シリンダ/ヘッダ/セクタの略称なんだろう。 AHレジスタの値が0x02の状態で、「INT 0x13」を実行するとESアドレスで指定のメモリ領域にシリンダの情報が書き込まれる。これを18回ループさせている。

harib00d

ディスクの裏面は、head0⇒head1に切り替えることで読み込み可能になる様子。 JB = Jump if below、 冒頭の「CYLS EQU 10 ; どこまで読み込むか」はC言語の#defineのようなものらしい。(CYLS=10、となる)

harib00e

ようやく、ブートセクタ読み取り用プログラム(ipl.nas)とは別にkernelっぽいものが現れた!(haribote.nas) ディスクイメージファイルに、OS本体を書き込むと、ファイル名とファイルの中身が所定の場所に保存されるらしい。

harib00g

なので、ファイルの中身のある場所(この場合0x8000 + 0x4200 = 0xc200)にJMP命令を発行すると、OSが実行できるという理屈。 ここでは、・OSファイル(haribote.nas)に「ORG 0xc200」を記載し、 読み込まれる目折り番地を固定・ブートローダ?(ipl10.nasの処理の最後にJMP命令を記載)することでOSを起動させるコードとなっている。

; 読み終わったのでharibote.sysを実行だ!
MOV [0x0ff0],CH ; IPLがどこまで読んだのかをメモ JMP 0xc200

OSの内容は以下

; haribote-os
; TAB=4

        ORG     0xc200          ; このプログラムがどこに読み込まれるのか

        MOV     AL,0x13         ; VGAグラフィックス、320x200x8bitカラー
        MOV     AH,0x00         ; AH = 0x00; でビデオモードの設定モードに
        INT     0x10            ; ビデオドライバへの割り込み処理発行
fin:
        HLT
        JMP     fin

harib00h

CPUには16bit modeと32bit modeが存在するBIOSは16bit modeでしか動作しないため、32bit modeに変更するとBIOSはもう使えなくなる

harib00i

ようやくC言語の世界へ!(長かった。。)今までharibote.nasだったファイルがasmhead.nasにかわった、 起動の順番としては、 ipl10.nas->asmhead.nas->bootpack.c となる。 bootpack.cの起動は、asmhead.nasによって行われているようだが詳細な解説は後程とのこと。後は、 16bit mode=特権モード 32bit mode=プロテクトモードでよかったっけ?

harib00j

最後に、C言語のファイル bootpack.c から、nasmの関数を記述したファイル naskfunc.nas を起動する方法を書いておわり。 nasm側で関数名の前に、アンダーバーをつけるのはLinuxカーネルでよく見る気がするのだけど、どういう技術的な制約なんだろう?

/* https://sunrise033.com/entry/hatena-blog-how-to-hierarchicalize-categories */