30天自制操作系统 笔记 01

前言

本系列是星雨学习 川合秀实 的《30天自制操作系统》一书的笔记

0x00 从计算机结构到汇编程序入门

计算机原理

电脑的处理中心是CPU(Central Process Unit, 中央处理单元),CPU只能与别的电路进行 电信号交换,而且对于电信号,只能理解开(ON)和关(OFF)两种状态。

CPU无法理解电信号携带的信息,也不在乎他们,只是按照指令进行相应的处理。

能用CPU处理的不仅仅只有数据,我们还可以用电信号向CPU发出指令。

汇编语言编译器

作者自己开发了“NASK”

nask与nasm代码差异

nasmnask
JMP entryJMP SHORT entry
RESB <填充字节数>TIMES <填充字节数> DB <填充数据>
RESB 0x7dfe-$TIMES 0x1fe-(-$) DB 0
ALIGNB 16ALIGN 16, DB 0

修改后的汇编版本系统 (helloos2)

注释根据微软 FAT Specification 修改

; hello-os
; TAB=4

; 以下是标准FAT12格式软盘专用的代码
        DB      0xEB, 0x4E, 0x90    ; BS_jmpBoot        0xEB 0x?? 0x90 INTEL指令集的无条件跳转,跳转到操作系统的bootstrap代码
        DB      "HELLOIPL"          ; BS_OEMName        OEM名称标识符,可随意设置,一般用来指出这个分区由哪种系统格式化
        DW      512                 ; BPB_BytsPerSec    每扇区字节数
        DB      1                   ; BPB_SecPerClus    每个分配单元的扇区数
        DW      1                   ; BPB_RsvdSecCnt    保留区域中的保留扇区数,用于对齐数据区到簇大小的整数倍,可以为任何非0数
        DB      2                   ; BPB_NumFATs       分卷中文件分配表的数量
        DW      224                 ; BPB_RootEntCnt    对于FAT12,此字段包含根目录中32字节目录条目的数量
        DW      2880                ; BPB_TotSec16      扇区数
        DB      0xF0                ; BPB_Media         应为: 0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF中的一个,0xF8代表不可移动设备, 可移动设备常用0xF0
        DW      9                   ; BPB_FATSz16       FAT12中,这是一个FAT占据的扇区数量
        DW      18                  ; BPB_SecPerTrk     每磁道扇区数量(中断0x13)
        DW      2                   ; BPB_NumHeads      磁头数量(中断0x13)
        DD      0                   ; BPB_HiddSec       分区前的隐藏扇区数量(中断0x13)
        DD      2880                ; BPB_TotSec32      32位的扇区数量
        DD      0xFFFFFFFF          ;                   这个东西和Spec对不上,但是4bytes貌似对应BS_VolID分卷序列号
        DB      "HELLO-OS   "       ; BS_VolLab         卷标,11位
        DB      "FAT12   "          ; BS_FilSysType     "FAT12   " "FAT16   " 或者 “FAT32   ”, 仅提供信息,不决定FAT类型
        RESB    18                  ;                   空出18字节

; 程序主体
        DB      0xB8, 0x00, 0x00, 0x8E, 0xD0, 0xBC, 0x00, 0x7C
        DB      0x8E, 0xD8, 0x8E, 0xC0, 0xBE, 0x74, 0x7C, 0x8A
        DB      0x04, 0x83, 0xC6, 0x01, 0x3C, 0x00, 0x74, 0x09
        DB      0xB4, 0x0E, 0xBB, 0x0F, 0x00, 0xCD, 0x10, 0xEB
        DB      0xEE, 0xF4, 0xEB, 0xFD

; 信息显示部分
        DB      0x0A, 0x0A          ; 换行 * 2
        DB      "hello, world"
        DB      0x0A
        DB      0

        RESB    0x1fe-$
        DB      0x55, 0xAA          ; Signature_word    设置为0x55 0xAA

; 启动区以外部分的输出

        DB      0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00
        RESB    4600
        DB      0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        RESB    1469432

0x01 汇编语言学习与Makefile入门

; hello-os
; TAB=4

        ORG     0x7C00              ; 指明程序的装载地址

; FAT12
        JMP     entry               ; 跳转至操作系统的bootstrap代码
        DB      0x90

--- (略 FAT12分区格式) ---

; 程序核心

entry:
        MOV     AX,0                ; 
        MOV     SS,AX
        MOV     SP,0x7c00
        MOV     DS,AX
        MOV     ES,AX

        MOV     SI,msg
putloop:
        MOV     AL,[SI]
        ADD     SI,1
        CMP     AL,0

        JE      fin
        MOV     AH,0x0E
        MOV     BX,15
        INT     0x10
        JMP     putloop
fin:
        HLT
        JMP     fin
msg:
        DB      0x0A, 0x0A
        DB      "hello, world"
        DB      0x0A
        DB      0

汇编指令

  • ORG: 指明程序的开始地址,将机器语言指令装载的地址

来自OSDEV Wiki

一般的X86架构电脑启动时会进入实模式,因为在启动过程中IRQ0(时钟)和存储设备等都会产生 中断,所以内存要存储中断向量表,部分内存区域会被BIOS等设备覆写,所以内存映射应该被很好 的设计。以下是实模式的内存表。

大小描述类型
0x000000000x000003FF1KiB实模式中断向量表实模式不可用640KiB内存(低内存)
0x000004000x000004FF256bytesBIOS内存区域
0x000005000x00007BFF将近30KiB常规内存可用内存
0x00007C000x00007DFF512bytes你的操作系统启动扇区
0x00007E000x0007FFFF480.5KiB常规内存
0x000800000x0009FFFF128KiBBIOS数据扩展部分被用于BIOS数据扩展
0x000A00000x000BFFFF128KiB视频显示内存硬件映射384KiB系统/保留(上位内存)
0x000C00000x000C7FFF32KiB(一般)视频BIOS只读内存和硬件映射/影子内存
0x000C80000x000EFFFF160KiB(一般)BIOS扩展
0x000F00000x000FFFFF64KiB主板BIOS

应该注意的是,BootLoader程序代码会被加载到0x7C00到0x7DFF并运行,所以应当使用ORG伪指令规定偏移地址并且在执行第二阶段BootLoader或内核之前,这个区域也无法使用。

  • JMP: 跳转至制定的标签
  • MOV: 赋值,MOV AX,0 相当于 AX=0;

一些寄存器:

名称缩写英文中文
AXAccumulator累加寄存器
CXCounter计数寄存器
DXData数据寄存器
BXBase基址寄存器
SPStack Pointer栈指针寄存器
BPBase Pointer基址指针寄存器
SISource Index源变址寄存器
DIDestination Index目的变址寄存器

以上寄存器均为16位寄存器,虽然所有寄存器都能进行同样的计算,但是使用对应的寄存器可以使程序变得更简洁

  • ADD CX,0x1234 编译后为 81 C1 34 12 (4bytes)
  • ADD AX,0x1234 编译后为 05 34 12 (3bytes)

名称末尾的'X'表示‘extend(扩展)’,扩展的原8位寄存器

8位寄存器:

名称缩写英文中文
ALAccumulator Low累加寄存器低位
CLCounter Low计数寄存器低位
DLData Low数据寄存器低位
BLBase Low基址寄存器低位
AHAccumulator High累加寄存器高位
CHCounter High计数寄存器高位
DHData High数据寄存器高位
BHBase High基址寄存器高位

MOV AI,[SI]

这里的方括号代表内存地址,我们可以用寄存器来指定内存地址,但是只有BX,BP,SI,DI这几个寄存器能用来指定 内存地址。内存对于CPU而言是外部存储器(很重要),所以CPU要通过自己的一部分管脚(引线)向内存发送电信 号来存取数据。如:

MOV 数据大小 [678],123

指令就是要用内存的"678"号地址来保存“123”这个数值。

数据大小 [地址]

这是一个固定的组合,如果我们指定数据大小为"BYTE",那么使用的存储单元就只是地址所指定的字节;如果指定 数据大小为"WORD",那么相邻的一个字节也会成为这个指令的操作对象;如果指定的数据大小为"DWORD",那么与 WORD相邻的两个字节也会成为这个指令的操作对象(共四字节)。(这里的相邻是地址增加方向的相邻)

---------123-----------
| 00000000 | 01111011 |       |       Memory       |
-----------------------       |--------------------|
      |          \----------> |      01111011      |   678   (制定地址)
      \---------------------> |      00000000      |   679   (邻居)
  • ADD: 加法指令,ADD SI,1 --> SI=SI+1;
  • CMP: 比较指令,简单的来说,是if语句的一部分

if(a==3) { 处理; }

要将a与3比较时,应先写CMP a,3,告诉CPU比较的对象,然后再写进一步的条件。JE(Jump if Equal)是条件 跳转指令之一,根据比较结果决定是否跳转。如果相等,则跳转到对应地址;如不相等,则继续执行下方的代码。 因此

CMP AL,0 JE fin

相当于

if (AL == 0) { goto fin; }

  • INT: 软件中断指令,中断机制现暂时可看做函数调用。

来自OSDEV Wiki

BIOS用于提供一些基础的低级服务给初期的系统程序,基本的目的是尽可能的避免机型之间的差别对操作系统和 软件的影响。BIOS还提供了一些函数来让开发更简单。为了调用这些函数,一般需要设置CPU AH寄存器 (或AX, EAX)至特定的值然后执行INT命令。寄存器的值和中断序号共同决定调用的函数。

中断序号描述
INT 0x10视频显示相关
INT 0x11硬件检测
INT 0x13大容量存储的访问(硬盘、软盘)
INT 0x15内存大小
INT 0x16键盘

常见函数

中断序号AH寄存器描述
INT 0x101初始光标
3光标位置
0xE显示字符
0xF获取视频页和模式
0x11设置8*8字体
0x12检测 EGA/VGA
0x13显示字符串
0x1200备用屏幕输出(Alternate Print Screen)
0x1201关闭光标模拟
0x4F00视频内存大小
0x4F01VESA获取模式信息调用
0x4F02选择VESA视频模式
0x4F0AVESA2.0保护模式接口
0x130重置软盘/硬盘
2在CHS模式读取软盘/硬盘
3在CHS模式下写软盘/硬盘
0x15检测第二硬盘
0x41测试中软13插件是否存在
0x42在LBA模式下读取硬盘
0x43在LBA模式下写硬盘

所以在这里显示一个字符就需要一下操作:

  1. AH = 0x0E
  2. AL = character code
  3. BH = 0
  4. BL = color code

更多中断请看Interrupt Jump Table

  • HLT: 使CPU进入待机状态,如果外部变化才会醒来(来自英文halt, 停止)

制作启动区

为了方便后续开发,一般不适用nask制作整个硬盘镜像,而是制作启动区,剩余部分使用 映像管理工具做,更加方便。

首先将helloos.nas的后半部分截掉,只剩启动区的512字节(; 程序核心之前) 将文件名改为ipl.nas,然后制作 asm 脚本:

nasm ipl.nas -o ipl.bin -l ipl.list

(由于nask不支持linux所以使用nasm,代码内相关内容也要改动)

制作生成磁盘镜像文件脚本 makeimg

./tolset/edimg   imgin:../z_tools/fdimg0at.tek   wbinimg src:ipl.bin len:512 from:0 to:0   imgout:helloos.img

由于光盘自带的edimg预编译版本为Windows可执行文件格式,Linux版本应当自己编译, 到 光盘根目录/omake/tolsrc/edimg0j 目录,修改 edimg.c,注释SAR_MODE_WIN32定义,取消注释SAR_MODE_POSIX定义。执行

gcc edimg.c autodec_.c -o edimg
chmod +x ./edimg

将编译得到的edimg可执行文件和fdimg0at.tek复制到 工程目录/tolset

创建 makeimg 脚本

./tolset/edimg.exe   imgin:./tolset/fdimg0at.tek   wbinimg src:ipl.bin len:512 from:0 to:0   imgout:helloos.img

创建虚拟机启动脚本:

安装qemu,将 光盘根目录/tolset/z_toolls/qemu 目录下的 bios.bin 和 vgabios.bin 复制到 工程目录/qemu/fw

创建 run 脚本

qemu-system-x86_64 -L ./qemu/fw -m 32-fda ./helloos.img

之后就能通过 asm -> makeimg -> run 来测试代码了。

Makefile 入门

make 是一个相当方便的构建工具,他可以通过只编译修改过的文件来大量节省时间。 要使用make,首先应该创建make配置文件Makefile,并在Makefile里写入文件生成规则。

ipl.bin : ipl.nas Makefile
    nasm ipl.nas -o ipl.bin -l ipl.lst

helloos.img : ipl.bin Makefile
    ./tolset/edimg   imgin:./tolset/fdimg0at.tek   wbinimg src:ipl.bin len:512 from:0 to:0   imgout:helloos.img

asm :
    make ipl.bin

img :
    make helloos.img

run : helloos.img
    qemu-system-x86_64 -L ./qemu/fw -m 32 -fda ./helloos.img

现在就可以通过简单的

make run

来启动镜像了。

0x03 进入32位模式并导入C语言

制作真正的IPL(Initial Program Loader, 启动程序加载器)

首先通过调用BIOS中断实现读取磁盘最初512字节的启动区。根据中断表,读取硬盘需要进行以下操作:

AH = 02h
AL = 要读取的扇区数
CH = 柱面数低八位
CL = 扇区数1-63  (0-5位)
     柱面数高二位 (6-7位, 仅硬盘)
DH = 磁头号
DL = 驱动器号 (第七位设为硬盘)
ES:BX -> 数据缓存

输出:

出错时CF被设置
如果 AH = 11h (ECC纠正错误), AL = 突发长度
成功时CF被清除
AH = 状态 (see #00234)
AL = 传输的扇区数

对应汇编代码为:

        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         ; A 驱动器
        INT     0x13            ; 调用BIOS
        JC      error
星雨 建站
Valaxy v0.14.36 驱动 | 主题 - Yun v0.14.36