《30天自制操作系统》读书笔记(3) 引入C语言

这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧.

作者提供的源码的注释在中文系统下是乱码, 而且代码的分隔用了两个Tab, 在这里要处理一下:

:%s/;.*//g 删除所有的注释;

:%s/\t\t/\t 把两个Tab替换为一个Tab;

要让作者的nas文件和asm文件拥有相同的语法规则, 在_vimrc文件的最后一行添加

au BufNewFile,BufRead *.nas set filetype=asm

  • 真正的IPL

    之前我们写的只是软盘启动区代码, 所有代码只能放在512字节里, 根本不够用,

所以我们要利用这一段代码读入软盘的其他内容, 并将控制权移交给它, 所以叫做IPL(Initial Program Loader).

  • 需要的知识点:

int 0x13 磁盘中断

AH = 0x02    ; 读盘

AH = 0x03    ; 写盘

AH = 0x04    ; 校验

AH = 0x0c    ; 寻道

AH = 0x00    ;大概是重置磁盘, 配合DH = 0x00 使用

AL = 连续处理的扇区数

CH = 柱面号

CL = 扇区号

DH = 磁头号

DL = 驱动器号

ES:BX = 缓冲地址, 寻道及校验不使用

C = 0 没有错误, AH = 0;

C = 1 错误号码存入AH.

这里只会用到0x02和0x00.

软盘的结构:

    用柱面(C)-磁头(H)-扇区(S) 来标识软盘上的扇区, 我们的启动扇区的标号是C0-H0-S1, 值得注意的是下一个扇区是C0-H0-S2, 这个例子里作者要读入10个柱面, 从C0-H0-S1 读到 C9-H1-S18, 这样算来应该是18*2*10 = 360 个扇区, 大小是360*512/1024 = 180 KB, 从我们现在小的可怜的需求看来, 180KB绝对够了.(按这样算的话不是一个柱面有36个扇区吗? 此处不解).

以下的代码能将软盘除启动区外开始的180KB的空间读入内存中, 起始地址是0x8200, 占用的空间是0x2d000.

  • 执行软盘中的内容

    编写Haribote.nas文件如下, 结构很简单:

    1 fin:
    2     HLT
    3     JMP    fin 

    问题是如何把编译好的程序和IPL结合在一起?

    我们可以先编译好IPL, 和之前一样做成一个img, 然后把程序写入img.

    最直观的方法是把img文件写入U盘, 把程序写入U盘, 再把U盘打包成img,

虽然有些迂回, 但是我们可以借助WinHex来直接写入, 用WinHex直接打开作者做好的Haribote.img ,可以发现它的内部格式如下:

发现Haribote.SYS处在0x4200的位置处 , 这是不是真的Haribote.sys?

也从Winhex里面打开Hraibote.sys:

只有一句, F4 EB FD, 和img文件0x4200处的完全一样, 和作者的总结一样:

向一个空软盘保存文件的时候,

1.文件名会写在0x2600以后的地方(这里我没有去验证);

2.文件的内容会写入到0x4200以后的地方.

非空软盘的情况没有考虑(这里开始我们就真正意义上使用了FAT12的文件系统了, 而不是自己设计一个文件系统).

知道了这个我们就可以知道怎么调用这个程序了, 使用作者提供的edimg.exe 可以方便地将程序写入到img中, 所以Makefile也要做相应的改动, 具体看Project文件夹day3里面的e文件夹.

这个Haribote.nas 所做的只是不断HLT, 我们并不知道这段代码是否真的执行, 作者这里使用了0x10来切换画面模式, 使得光标消失,

参数是AL = 0x13, AH = 0x00, 效果是屏幕全黑, 连光标也不能看到.

我这里采用的是0x10中断的另一个功能, 能够显示带颜色的字符串, 代码如下:

 1 ; Program
 2     ORG    0xc200    ; 加载到 0x8200 + 0x4200 – 0x200(启动区没有被读入)
 3     MOV    AX,0
 4     MOV    ES,AX
 5     MOV    AX,loaded
 6     MOV    BP,AX    ; ES:BP = 串地址
 7     MOV    CX,10        ; 串长度
 8     MOV    AX,0x1301    ;AH = 0x13,AL = 0x01
 9     MOV    BX,0x000b    ; 页号BH = 0 黑底蓝字 BL = 0x0b
10     MOV    DL,0
11     INT    0x10
12     JMP fin
13 loaded:
14     DB    0x0a, 0x0a
15     DB    "Loaded."
16     DB    0x0a
17     DB    0
18
19 fin:
20     HLT
21     JMP fin 

效果如下, 可以确定我们Haribote.sys里面的代码已经被执行.

  • 进入32位模式&导入C语言*

这里作者开始用我们看不懂的东西了, 给出了一个长长的asmhead.nas, 并且表示"先跳过这一部分", 让我有点不爽, 但是毕竟不能半途而废不是? 这里的Makefile变得越来越复杂, 在这里我第一次碰到了困难, 由于粗心Makefile写少了几行, 导致编译莫名其妙地出错, 纠结了两天才发现, 抄代码都能抄错 = =, 废话略过, 作者还增加了一个Bootpack.c 大概就是我们以后的主战场了, 这里编译过程异常繁琐(作者啊你可不可以不要用你自己写的工具!? (╯‵□′)╯︵┻━┻),

以上所有的工具都是川和先生写的或者改写的(╯‵□′)╯︵┻━┻.

需要强调的是HariMain这个Main函数的函数名已经写死了, 不能够更换, 为了在C

语言里面使用HLT, (事实上现在C语言里面根本就没有函数可用), 增加naskfunc.nas.

所有文件均如下:

IPL.nas:

; hello-os
; TAB=4
; const

CYLS    EQU    10

    ORG    0x7c00    

    JMP    entry
    DB    0x90
    DB    "OS 0.01 "
    DW    512
    DB    1
    DW    1
    DB    2
    DW    224
    DW    2880
    DB    0xf0
    DW    9
    DW    18
    DW    2
    DD    0
    DD    2880
    DB    0,0,0x29
    DD    0xffffffff
    DB    "OS ver 0.01"
    DB    "FAT12 "
    RESB    18        

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

    MOV    AX,0x0820    ; 缓存位置 = ES:BX
    MOV    ES,AX
    MOV    CH,0    ; 柱面0
    MOV    DH,0    ; 磁头0
    MOV    CL,2    ; 扇区2

readloop:
    MOV    SI,0    ; 记录失败次数

retry:
    MOV    AH,0x02 ; 读盘
    MOV    AL,1    ; 一个扇区
    MOV    BX,0
    MOV    DL,0x00 ; 驱动器A:
    INT    0x13    ; 调用磁盘中断
    JNC    next
    ADD    SI,1
    CMP    SI,5    ; 失败5次跳出
    JAE    error
    MOV    AH,0x00
    MOV    DL,0x00    ; 指定驱动器A:
    INT    0x13    ; 重置并重试
    JMP    retry
next:
    MOV    AX,ES
    ADD    AX,0x0020
    MOV    ES,AX    ; 段寄存器往后移0x0020 实际偏移 0x0020*0x0010 = 0x0200 = 512d
    ADD    CL,1
    CMP    CL,18    ; 读到18扇区
    JBE    readloop; <= 则跳
    MOV    CL,1
    ADD    DH,1    ; 换一个磁头
    CMP    DH,2
    JB    readloop; <则跳
    MOV    DH,0
    ADD    CH,1    ; 柱面+1
    CMP    CH,CYLS
    JB    readloop

    MOV    [0x0ff0],CH
    JMP    0xc200

error:            ; 错误提示
    MOV    AX,0
    MOV    ES,AX
    MOV    AX,msg
    MOV    BP,AX    ; ES:BP = 串地址
    MOV    CX,14    ; 串长度
    MOV    AX,0x1301    ;AH = 0x13,AL = 0x01
    MOV    BX,0x000c    ; 页号BH = 0 黑底红字 BL = 0x0c
    MOV    DL,0
    INT    0x10

fin:
    HLT
    JMP    fin

msg:
    DB    0x0a, 0x0a
    DB    "Load error."
    DB    0x0a
    DB    0

    RESB    0x7dfe-$
    DB    0x55, 0xaa 

Asmhead.nas:

BOTPAK    EQU    0x00280000
DSKCAC    EQU    0x00100000
DSKCAC0    EQU    0x00008000    

CYLS    EQU    0x0ff0    ; 设定启动区
LEDS    EQU    0x0ff1
VMODE    EQU    0x0ff2    ; 关于颜色数目的信息,颜色的位数
SCRNX    EQU    0x0ff4    ; 分辨率 X(Screen X)
SCRNY    EQU    0x0ff6    ; 分辨率 Y(Screen Y)
VRAM    EQU    0x0ff8    ; 图像缓冲区的起始地址    

    ORG    0xc200       

    MOV    AL,0x13    ; VGA 显卡
    MOV    AH,0x00
    INT    0x10
    MOV    BYTE [VMODE],8    ; 记录画面模式
    MOV    WORD [SCRNX],320
    MOV    WORD [SCRNY],200
    MOV    DWORD [VRAM],0x000a0000

; 取得键盘上的LED灯的状态
    MOV    AH,0x02
    INT    0x16
    MOV    [LEDS],AL

    MOV    AL,0xff
    OUT    0x21,AL
    NOP
    OUT    0xa1,AL
    CLI            

    CALL    waitkbdout
    MOV    AL,0xd1
    OUT    0x64,AL
    CALL    waitkbdout
    MOV    AL,0xdf
    OUT    0x60,AL
    CALL    waitkbdout

[INSTRSET "i486p"]        

    LGDT    [GDTR0]
    MOV    EAX,CR0
    AND    EAX,0x7fffffff
    OR    EAX,0x00000001
    MOV    CR0,EAX
    JMP    pipelineflush

pipelineflush:
    MOV    AX,1*8
    MOV    DS,AX
    MOV    ES,AX
    MOV    FS,AX
    MOV    GS,AX
    MOV    SS,AX

    MOV    ESI,bootpack
    MOV    EDI,BOTPAK
    MOV    ECX,512*1024/4
    CALL    memcpy

    MOV    ESI,0x7c00
    MOV    EDI,DSKCAC
    MOV    ECX,512/4
    CALL    memcpy

    MOV    ESI,DSKCAC0+512
    MOV    EDI,DSKCAC+512
    MOV    ECX,0
    MOV    CL,BYTE [CYLS]
    IMUL    ECX,512*18*2/4
    SUB    ECX,512/4
    CALL    memcpy

    MOV    EBX,BOTPAK
    MOV    ECX,[EBX+16]
    ADD    ECX,3
    SHR    ECX,2
    JZ    skip
    MOV    ESI,[EBX+20]
    ADD    ESI,EBX
    MOV    EDI,[EBX+12]
    CALL    memcpy

skip:
    MOV    ESP,[EBX+12]
    JMP    DWORD 2*8:0x0000001b

waitkbdout:
    IN     AL,0x64
    AND     AL,0x02
    JNZ    waitkbdout
    RET

memcpy:
    MOV    EAX,[ESI]
    ADD    ESI,4
    MOV    [EDI],EAX
    ADD    EDI,4
    SUB    ECX,1
    JNZ    memcpy
    RET

    ALIGNB    16

GDT0:
    RESB    8
    DW    0xffff,0x0000,0x9200,0x00cf
    DW    0xffff,0x0000,0x9a28,0x0047
    DW    0

GDTR0:
    DW    8*3-1
    DD    GDT0
    ALIGNB    16 

bootpack.c:

void io_hlt(void);

void HariMain(void)
{

fin:
    io_hlt();
    goto fin;
}

Naskkfunc.nas:

;naskfunc
[FORMAT "WCOFF"]        ; 制作目标文件的模式
[BITS 32]            ; 制作32位模式用的机械语言

; 制作目标文件的信息

[FILE "naskfunc.nas"]        ; 源文件名信息
    GLOBAL    _io_hlt        ; 程序中包含的函数名

; 实际的函数
[SECTION .text]    ;
_io_hlt:
    HLT
    RET
void io_hlt(void);
void HariMain(void)
{
fin:
    io_hlt();
    goto fin;
} 

万恶的Makefile:

TOOLPATH = ../z_tools/
INCPATH = ../z_tools/haribote/

MAKE = $(TOOLPATH)make.exe -r
NASK = $(TOOLPATH)nask.exe
CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM = $(TOOLPATH)obj2bim.exe
BIM2HRB = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG = $(TOOLPATH)edimg.exe
DEL = del
SHORTCUT = "D:\Program Files\Oracle\VirtualBox\VirtualBox.exe" --comment "OS1" --startvm "a5c4b0e6-e142-4720-98ee-056911204b29"

default :
    $(MAKE) install
    $(MAKE) run
    $(MAKE) clean

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

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

bootpack.gas : bootpack.c Makefile
    $(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
    $(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
    $(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
    $(NASK) naskfunc.nas naskfunc.obj naskfucn.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
    $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
        bootpack.obj naskfunc.obj
    echo error

# 3MB+64KB=3136KB

bootpack.hrb : bootpack.bim Makefile
    $(BIM2HRB) bootpack.bim bootpack.hrb 0

haribote.sys :asmhead.bin bootpack.hrb Makefile
    copy /B asmhead.bin+bootpack.hrb haribote.sys

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

install:
    $(MAKE) haribote.img

run :
    echo Running...
    $(SHORTCUT)
    echo Finished

clean :
    -$(DEL) *.bin
    -$(DEL) *.lst
    -$(DEL) *.gas
    -$(DEL) *.obj
    -$(DEL) bootpack.nas
    -$(DEL) bootpack.map
    -$(DEL) bootpack.bim
    -$(DEL) bootpack.hrb
    -$(DEL) haribote.sys
    -$(DEL) *.*~
    -$(DEL) *~
    echo Finished. 

这次更新慢了, 下次争取早一点…

《30天自制操作系统》读书笔记(3) 引入C语言

时间: 2024-10-25 10:59:35

《30天自制操作系统》读书笔记(3) 引入C语言的相关文章

《30天自制操作系统》笔记(02)——导入C语言

<30天自制操作系统>笔记(02)--导入C语言 进度回顾 在上一篇,记录了计算机开机时加载IPL程序(initial program loader,一个nas汇编程序)的情况,包括IPL代码(helloos.nas).编译生成helloos.img文件.用虚拟机QEMU加载helloos.img.制作U盘启动盘和用物理机加载helloos.img. 计算机启动时会自动加载和执行IPL程序,但IPL程序只能占用512字节.若直接用IPL写OS,空间不够用.所以IPL程序一般用于将真正的OS程序

多定时器处理1(30天自制操作系统--读书笔记)

自认为写过很多MCU程序,但总是回头想想,我所了解的MCU编程思想大体有两种,其中具体的想法我得再找时间写下来. 总想总结出一个可扩展的,易移植的写法,但能力还没到这个层次.但<30天自制操作系统>这本书确实给我了一个思路,就像我已经写过的两篇读书笔记. 将两个独立的内容--FIFO和内存动态管理做到高度模块化,尤其是其中数据结构模型的设计更是我学习的好例子. 今天要学习的设计内容是多定时器处理.原书对这部分的处理讲的很详细,由浅入深,看得我由衷佩服作者,也可能是因为我水平低,稍稍看出点门道来

内存管理(30天自制操作系统--读书笔记)

今天继续读书笔记,“挑战内存管理”(30天自制操作系统). 为什么对这块内容敢兴趣呢,因为曾经遇到这么一个问题.在STM32程序中想使用队列,可不是上篇讲的FIFO,而是使用了较大的内存空间,又想做队列的顺序存取管理. 在这个队列里用到了malloc,动态申请内存,一开始是直接申请不到内存,后来在启动脚本里更改了设置堆的地址值,可以申请成功,但发现申请几次后,也申请不到内存. 果然MCU级别的程序,内存这块处理起来就没有windows程序那么随心所欲了.讲了这么多,开始正题吧. 1.相关数据结构

单字节的FIFO缓存(30天自制操作系统--读书笔记)

从今天起,写一些读书笔记.最近几个月都在看<30天自制操作系统这本书>,书虽说看的是电子书,但可以花钱买的正版书,既然花费了金钱,就总得有些收获. 任何人都不能总是固步自封,想要进步就得学习别人的知识,对于程序员而言,最简单的方法即是学习别人的代码. 今天的标题是“单字节的FIFO缓存”,其实就是做一个FIFO,看名字就知道了.也就4个函数和1个相关结构体,这样的小代码在嵌入式系统中很常用,也会很好用. 1.相关数据结构体 struct FIFO8 { unsigned char *buf;

多定时器处理2(30天自制操作系统 -- 读书笔记)

今天的内容依然来自<30天自制操作系统>这本书. 1.中断处理程序存在的问题,中断处理程序Timer_Interrupt如下: //定时器中断函数 void Timer_Interrupt(void) { int i=0; timerctl.count ++; for(i=0;i<MAX_TIMER;i++) //扫描所有的定时器 { if(timerctl.timer[i].flag == TIMER_FLAG_USING) { timerctl.count--; if(timerct

30天自制操作系统读书笔记(一)

刚开始作者叫我们用二进制编辑器,手敲01代码写了个IMG(磁盘映像文件) ,并提供了一些文件 : Run.bat, install.bat这些一个批处理文件,其实就是写好了cmd指令保存到文档里面,方便以后执行. 其中run.bat里面的指令是这样的 : copy helloos.img ..\z_tools\qemu\fdimage0.bin ..\z_tools\make.exe         -C ../z_tools/qemu (稍微解释一下:第一行:copy指令把,helloos.i

30天自制操作系统读书笔记(五)

为了让程序灵活点,作者觉得把什么320X200这些数据直接写入程序,不如让程序自己获取. 所以就引入了结构体,指针.用指针直接在内存中获取这些数据 (如程序里的这句: Binfo_scrnx = (short *) 0xff4; ). 对于结构体和指针我就不细写了,有C语言基础的人都知道. 因为已经进入了32位模式,所以不能再用bios写好的中断程序给我们输出字符了,要手动了! 用像素点描出图形. 字符可以使用8X16的长方形像素点来表示,转变为16进制就是这样: static char fon

30天自制操作系统读书笔记(二)

我还以为马大哈的作者忘记解释之前那段好长的汇编代码了,留下这么多疑惑! 第二天作者就交代了:以后再讲,第二天我们先来谈谈程序核心部分的内容吧! 好吧,刚准备欢喜的等着作者解释,没想到他就扔了一大串代码(什么鬼?) ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址,这个指令是和nask编译器说的(伪指令)告诉nask,开始执行的时候,把代码放到0x7c00的地方. ;为什么是0x7c00呢,因为规定了0x7c00-0x7dff是启动区的装载地址. ; 以下用于标准

多定时器处理3(30天自制操作系统 -- 读书笔记)

继续定时器中断处理的改进. 1.定时器中断程序Timer_Interrupt是这样的. //定时器中断函数 void Timer_Interrupt(void) { int i=0; timerctl.count ++; for(i=0;i<MAX_TIMER;i++) //扫描所有的定时器 { if(timerctl.timer[i].flag == TIMER_FLAG_USING) { timerctl.count--; if(timerctl.count == 0) //减得时间到了 {

30天自制操作系统读书笔记(四)

想要在画面里画点什么东西,就需要往VRAM的内存区里写入点什么东西. 但是如何写入呢? 作者先用了汇编: _write_mem8:         ; void write_mem8(int addr, int data); MOV                  ECX,[ESP+4]               ; [ESP + 4]中存放的是地址,将其读入ECX MOV                  AL,[ESP+8]                 ; [ESP + 8]中存放的