zoukankan      html  css  js  c++  java
  • 《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程序加载到内存某处(记作A),然后跳转到A。这样计算机就可以执行OS的程序了。

    在上一篇中的IPL程序只是个hello world式的试验品,本篇通过修改上一篇的IPL,让它真正实现加载OS程序的功能。同时,将IPL程序代码和OS代码放到不同的源代码文件中;用C语言来编写以后的OS代码;用Makefile来编译源代码。

    有了本篇的基础,就算是正式开始编写OS源代码了

    OS开发设计方案

    关于软盘的预备知识

    一张软盘有80个柱面、2个磁头、18个扇区(Cylinder:0~79;Header:0~2;Sector:1~18),1个扇区有512个字节,所以软盘的容量是80*2*18*512=1440KB。

    向一个软盘保存文件时,文件名会从0x2600开始往后存,文件的内容会从0x4200开始往后存。

    我们的OS开发设计方案如下

    1. 把IPL程序作为一个独立的源文件(ipl10.nas)开发,编译后生成二进制文件(ipl10.bin)。

    2. 把OS程序作为若干独立的源文件开发,编译后生成二进制文件(haribote.sys)。haribote.sys就是我们的OS程序。

    3. 用二进制的方式把ipl10.bin写入haribote.img(磁盘映像文件,看作一个软盘即可)的第一个扇区(这样,计算机启动时就会自动加载ipl10.bin程序)。

    4. 把haribote.sys作为一个文件复制到haribote.img。根据上文的预备知识可知,这个文件的内容会从软盘的0x4200位置开始往后存。

    实现一个开发结构完整的OS

    完备的IPL程序

    下面的代码是完备的IPL程序,它读了10个柱面上的代码到内存,所以文件名从helloos.nas改成了ipl10.nas。

      1 ; haribote-ipl
      2 ; TAB=4
      3 
      4 CYLS    EQU        10                ; どこまで読み込むか
      5 
      6         ORG        0x7c00            ; 指明程序的装载地址
      7 
      8 ; 以下这段是标准FAT32格式软盘专用的代码
      9 
     10         JMP        entry
     11         DB        0x90
     12         DB        "HARIBOTE"        ; freeparam 启动区的名称可以是任意的字符串(8字节)
     13         DW        512                ; 每个扇区(sector)的大小(必须为512字节)
     14         DB        1                ; 簇(cluster)的大小(必须为1个扇区)
     15         DW        1                ; FAT的起始位置(一般从第一个扇区开始)
     16         DB        2                ; FAT的个数(必须为2)
     17         DW        224                ; 根目录的大小(一般设成224项)
     18         DW        2880            ; 该磁盘的大小(必须是2880扇区)
     19         DB        0xf0            ; 磁盘的种类(必须是0xf0)
     20         DW        9                ; FAT的长度(必须是9扇区)
     21         DW        18                ; 1个磁道(track)有几个扇区(必须是18)
     22         DW        2                ; 磁头数(必须是2)
     23         DD        0                ; 不使用分区,必须是0
     24         DD        2880            ; 重写一次磁盘大小
     25         DB        0,0,0x29        ; 意义不明,固定
     26         DD        0xffffffff        ; (可能是)卷标号码
     27         DB        "HARIBOTEOS "    ; freeparam 磁盘的名称(11字节)
     28         DB        "FAT12   "        ; 磁盘格式名称(8字节)
     29         RESB    18                ; 先空出18字节
     30 
     31 ; 程序核心
     32 
     33 entry:
     34         MOV        AX,0            ; 初始化寄存器
     35         MOV        SS,AX
     36         MOV        SP,0x7c00
     37         MOV        DS,AX
     38 
     39 ; 读磁盘
     40 
     41         MOV        AX,0x0820
     42         MOV        ES,AX
     43         MOV        CH,0            ; 柱面0
     44         MOV        DH,0            ; 磁头0
     45         MOV        CL,2            ; 扇区2
     46 readloop:
     47         MOV        SI,0            ; 记录失败次数的寄存器
     48 retry:
     49         MOV        AH,0x02            ; AH=0x02 : 读入磁盘
     50         MOV        AL,1            ; 1个扇区
     51         MOV        BX,0
     52         MOV        DL,0x00            ; A驱动器
     53         INT        0x13            ; 调用磁盘BIOS
     54         JNC        next            ; 没出错时跳转到next
     55         ADD        SI,1            ; SI加1
     56         CMP        SI,5            ; 比较SI与5
     57         JAE        error            ; SI >= 5时,跳转到 error
     58         MOV        AH,0x00
     59         MOV        DL,0x00            ; A驱动器
     60         INT        0x13            ; 重置驱动器
     61         JMP        retry
     62 next:
     63         MOV        AX,ES            ; 把内存地址后移0x200(0x200 = 512)
     64         ADD        AX,0x0020        ; ADD    AX, 512 / 16
     65         MOV        ES,AX            ; 因为没有ADD ES,0x020 指令,所以这里稍微绕个弯
     66         ADD        CL,1            ; CL加1
     67         CMP        CL,18            ; 比较CL与18
     68         JBE        readloop        ; 如果CL <= 18,则跳转到readloop
     69         MOV        CL,1
     70         ADD        DH,1
     71         CMP        DH,2
     72         JB        readloop        ; 如果DH < 2,则跳转到readloop
     73         MOV        DH,0
     74         ADD        CH,1
     75         CMP        CH,CYLS
     76         JB        readloop        ; 如果CH < CYLS,则跳转到readloop
     77 
     78 ; 读完所有数据后,调到0x8200位置,即haribote.sys中的指令
     79 
     80         MOV        [0x0ff0],CH        ; 将CYLS的值写到内存地址0x0ff0中。
     81         JMP        0xc200
     82 
     83 error:
     84         MOV        SI,msg
     85 putloop:
     86         MOV        AL,[SI]
     87         ADD        SI,1            ; 给SI加1
     88         CMP        AL,0
     89         JE        fin
     90         MOV        AH,0x0e            ; 显示一个文字
     91         MOV        BX,15            ; 指定字符颜色
     92         INT        0x10            ; 调用显卡BIOS
     93         JMP        putloop
     94 fin:
     95         HLT                        ; 让CPU停止;等待指令
     96         JMP        fin                ; 无限循环
     97 msg:
     98         DB        0x0a, 0x0a        ; 换行2次
     99         DB        "load error"    ; freeparam
    100         DB        0x0a            ; 换行
    101         DB        0
    102 
    103         RESB    0x7dfe-$        ; 填写0x00,直到0x001fe
    104 
    105         DB        0x55, 0xaa
    ipl10.nas

    简单地说,这个ipl10.nas读了软盘(U盘)最开始的10个柱面,即C0-H0-S1到C9-H1-S18。那么从软盘(U盘)读到的这些内容放到哪里了呢?答:放到了内存的0x8000到0x34FFF这一段空间,如下表所示。

    序号

    软盘(U盘)位置

    内存位置

    备注

    1

    C0-H0-S1

    0x8000~0x81FF

    实际上没有读这一扇区,这一扇区存的是IPL程序

    2

    C0-H0-S2

    0x8200~0x83FF

    从软盘(U盘)的512字节到内存的512字节的一一对应。

    3

    C0-H0-S3

    0x8400~0x85FF

    同上

    ……

    ……

    ……

    ……

    360(10*2*18)

    C9-H1-S18

    0x34E00~0x34FFF

    同上

    从刚刚的软盘预备知识中可知,haribote.sys程序会被加载到内存的(0x8000+0x4200=0xc200)处。所以IPL程序中会有"JMP        0xc200"这一行代码。这行代码的意思是:把10个柱面读到内存后,haribote.sys就准备好了,IPL可以功成身退。下一步就从haribote.sys的第一句指令开始运行我们的OS。

    拆分出OS源代码文件

    我们的目的是用C语言写OS,所以当前给出如下几个OS源代码文件。

    源代码文件

    功能

    asmhead.nas

    承接IPL程序,调用bootpack.c中的主函数

    bootpack.c

    OS程序主函数

    naskfunc.nas

    用汇编语言写一些供C语言调用的函数

    下面分别列出这三个源代码文件的内容。

    源代码asmhead.nas中用日语注释的地方是原作者在后续章节中解释的,现在我也不知道是什么意思。我只知道asmhead.nas起了一个承上启下的作用,以后就可以越来越多得用C来干活了。

      1 ; haribote-os boot asm
      2 ; TAB=4
      3 
      4 BOTPAK    EQU        0x00280000        ; bootpackのロード先
      5 DSKCAC    EQU        0x00100000        ; ディスクキャッシュの場所
      6 DSKCAC0    EQU        0x00008000        ; ディスクキャッシュの場所(リアルモード)
      7 
      8 ; 有关BOOT_INFO
      9 CYLS    EQU        0x0ff0            ; 设定启动区
     10 LEDS    EQU        0x0ff1
     11 VMODE    EQU        0x0ff2            ; 关于颜色数目的信息。颜色的位数。
     12 SCRNX    EQU        0x0ff4            ; 分辨率的X(screen x)
     13 SCRNY    EQU        0x0ff6            ; 分辨率的Y(screen y)
     14 VRAM    EQU        0x0ff8            ; 图像缓冲区的开始地址
     15 
     16         ORG        0xc200            ; 这个程序将要被装载到内存的什么地方呢?
     17 
     18 ; 画面モードを設定
     19 
     20         MOV        AL,0x13            ; VGA显卡,320x200x8bit彩色
     21         MOV        AH,0x00
     22         INT        0x10
     23         MOV        BYTE [VMODE],8    ; 记录画面模式
     24         MOV        WORD [SCRNX],320
     25         MOV        WORD [SCRNY],200
     26         MOV        DWORD [VRAM],0x000a0000
     27 
     28 ; 用BIOS取得键盘上各种LED指示灯的状态
     29 
     30         MOV        AH,0x02
     31         INT        0x16             ; keyboard BIOS
     32         MOV        [LEDS],AL
     33 
     34 ; PICが一切の割り込みを受け付けないようにする
     35 ;    AT互換機の仕様では、PICの初期化をするなら、
     36 ;    こいつをCLI前にやっておかないと、たまにハングアップする
     37 ;    PICの初期化はあとでやる
     38 
     39         MOV        AL,0xff
     40         OUT        0x21,AL
     41         NOP                        ; OUT命令を連続させるとうまくいかない機種があるらしいので
     42         OUT        0xa1,AL
     43 
     44         CLI                        ; さらにCPUレベルでも割り込み禁止
     45 
     46 ; CPUから1MB以上のメモリにアクセスできるように、A20GATEを設定
     47 
     48         CALL    waitkbdout
     49         MOV        AL,0xd1
     50         OUT        0x64,AL
     51         CALL    waitkbdout
     52         MOV        AL,0xdf            ; enable A20
     53         OUT        0x60,AL
     54         CALL    waitkbdout
     55 
     56 ; プロテクトモード移行
     57 
     58 [INSTRSET "i486p"]                ; 486の命令まで使いたいという記述
     59 
     60         LGDT    [GDTR0]            ; 暫定GDTを設定
     61         MOV        EAX,CR0
     62         AND        EAX,0x7fffffff    ; bit31を0にする(ページング禁止のため)
     63         OR        EAX,0x00000001    ; bit0を1にする(プロテクトモード移行のため)
     64         MOV        CR0,EAX
     65         JMP        pipelineflush
     66 pipelineflush:
     67         MOV        AX,1*8            ;  読み書き可能セグメント32bit
     68         MOV        DS,AX
     69         MOV        ES,AX
     70         MOV        FS,AX
     71         MOV        GS,AX
     72         MOV        SS,AX
     73 
     74 ; bootpackの転送
     75 
     76         MOV        ESI,bootpack    ; 転送元
     77         MOV        EDI,BOTPAK        ; 転送先
     78         MOV        ECX,512*1024/4
     79         CALL    memcpy
     80 
     81 ; ついでにディスクデータも本来の位置へ転送
     82 
     83 ; まずはブートセクタから
     84 
     85         MOV        ESI,0x7c00        ; 転送元
     86         MOV        EDI,DSKCAC        ; 転送先
     87         MOV        ECX,512/4
     88         CALL    memcpy
     89 
     90 ; 残り全部
     91 
     92         MOV        ESI,DSKCAC0+512    ; 転送元
     93         MOV        EDI,DSKCAC+512    ; 転送先
     94         MOV        ECX,0
     95         MOV        CL,BYTE [CYLS]
     96         IMUL    ECX,512*18*2/4    ; シリンダ数からバイト数/4に変換
     97         SUB        ECX,512/4        ; IPLの分だけ差し引く
     98         CALL    memcpy
     99 
    100 ; asmheadでしなければいけないことは全部し終わったので、
    101 ;    あとはbootpackに任せる
    102 
    103 ; bootpackの起動
    104 
    105         MOV        EBX,BOTPAK
    106         MOV        ECX,[EBX+16]
    107         ADD        ECX,3            ; ECX += 3;
    108         SHR        ECX,2            ; ECX /= 4;
    109         JZ        skip            ; 転送するべきものがない
    110         MOV        ESI,[EBX+20]    ; 転送元
    111         ADD        ESI,EBX
    112         MOV        EDI,[EBX+12]    ; 転送先
    113         CALL    memcpy
    114 skip:
    115         MOV        ESP,[EBX+12]    ; スタック初期値
    116         JMP        DWORD 2*8:0x0000001b
    117 
    118 waitkbdout:
    119         IN         AL,0x64
    120         AND         AL,0x02
    121         JNZ        waitkbdout        ; ANDの結果が0でなければwaitkbdoutへ
    122         RET
    123 
    124 memcpy:
    125         MOV        EAX,[ESI]
    126         ADD        ESI,4
    127         MOV        [EDI],EAX
    128         ADD        EDI,4
    129         SUB        ECX,1
    130         JNZ        memcpy            ; 引き算した結果が0でなければmemcpyへ
    131         RET
    132 ; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける
    133 
    134         ALIGNB    16
    135 GDT0:
    136         RESB    8                ; ヌルセレクタ
    137         DW        0xffff,0x0000,0x9200,0x00cf    ; 読み書き可能セグメント32bit
    138         DW        0xffff,0x0000,0x9a28,0x0047    ; 実行可能セグメント32bit(bootpack用)
    139 
    140         DW        0
    141 GDTR0:
    142         DW        8*3-1
    143         DD        GDT0
    144 
    145         ALIGNB    16
    146 bootpack:
    asmhead.nas

     

    目前的主函数什么都没有做。

     1 /* 告诉C编译器,有一个函数在别的文件里。 */
     2 
     3 void io_hlt(void);
     4 
     5 /* 是函数声明却不用{}。而用;,这表示的意思是:函数是在别的文件中,你自己找一下吧! */
     6 
     7 void HariMain(void)
     8 {
     9 
    10 fin:
    11     io_hlt(); /* 执行naskfunc.nas里的_io_hlt */
    12     goto fin;
    13 
    14 }
    bootpack.c

     

    这个naskfunc.nas可以说是一个封装硬件供C语言调用的函数库。

     1 ; naskfunc
     2 ; TAB=4
     3 
     4 [FORMAT "WCOFF"]                ; 制作目标文件的模式
     5 [BITS 32]                        ; 制作32位模式用的机器语言
     6 
     7 
     8 ; 制作目标文件的信息
     9 
    10 [FILE "naskfunc.nas"]            ; 源文件名信息
    11 
    12         GLOBAL    _io_hlt            ; 程序中包含的函数名
    13 
    14 
    15 ; 以下是实际的函数
    16 
    17 [SECTION .text]        ; 目标文件中写了这些后再写程序
    18 
    19 _io_hlt:    ; void io_hlt(void);
    20         HLT
    21         RET
    naskfunc.nas

     

    从Makefile画出编译和部署流程

    上述的ipl10.nas、asmhead.nas、bootpack.c、naskfunc.nas是如何组合成为一个OS程序的呢?下面的Makefile文件描述了编译流程。

     1 TOOLPATH = ../z_tools/
     2 INCPATH  = ../z_tools/haribote/
     3 
     4 MAKE     = $(TOOLPATH)make.exe -r
     5 NASK     = $(TOOLPATH)nask.exe
     6 CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
     7 GAS2NASK = $(TOOLPATH)gas2nask.exe -a
     8 OBJ2BIM  = $(TOOLPATH)obj2bim.exe
     9 BIM2HRB  = $(TOOLPATH)bim2hrb.exe
    10 RULEFILE = $(TOOLPATH)haribote/haribote.rul
    11 EDIMG    = $(TOOLPATH)edimg.exe
    12 IMGTOL   = $(TOOLPATH)imgtol.com
    13 COPY     = copy
    14 DEL      = del
    15 
    16 # デフォルト動作
    17 
    18 default :
    19     $(MAKE) img
    20 
    21 # ファイル生成規則
    22 
    23 ipl10.bin : ipl10.nas Makefile
    24     $(NASK) ipl10.nas ipl10.bin ipl10.lst
    25 
    26 asmhead.bin : asmhead.nas Makefile
    27     $(NASK) asmhead.nas asmhead.bin asmhead.lst
    28 
    29 bootpack.gas : bootpack.c Makefile
    30     $(CC1) -o bootpack.gas bootpack.c
    31 
    32 bootpack.nas : bootpack.gas Makefile
    33     $(GAS2NASK) bootpack.gas bootpack.nas
    34 
    35 bootpack.obj : bootpack.nas Makefile
    36     $(NASK) bootpack.nas bootpack.obj bootpack.lst
    37 
    38 naskfunc.obj : naskfunc.nas Makefile
    39     $(NASK) naskfunc.nas naskfunc.obj naskfunc.lst
    40 
    41 bootpack.bim : bootpack.obj naskfunc.obj Makefile
    42     $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map 
    43         bootpack.obj naskfunc.obj
    44 # 3MB+64KB=3136KB
    45 
    46 bootpack.hrb : bootpack.bim Makefile
    47     $(BIM2HRB) bootpack.bim bootpack.hrb 0
    48 
    49 haribote.sys : asmhead.bin bootpack.hrb Makefile
    50     copy /B asmhead.bin+bootpack.hrb haribote.sys
    51 
    52 haribote.img : ipl10.bin haribote.sys Makefile
    53     $(EDIMG)   imgin:../z_tools/fdimg0at.tek 
    54         wbinimg src:ipl10.bin len:512 from:0 to:0 
    55         copy from:haribote.sys to:@: 
    56         imgout:haribote.img
    57 
    58 # コマンド
    59 
    60 img :
    61     $(MAKE) haribote.img
    62 
    63 run :
    64     $(MAKE) img
    65     $(COPY) haribote.img ..z_toolsqemufdimage0.bin
    66     $(MAKE) -C ../z_tools/qemu
    67 
    68 install :
    69     $(MAKE) img
    70     $(IMGTOL) w a: haribote.img
    71 
    72 clean :
    73     -$(DEL) *.bin
    74     -$(DEL) *.lst
    75     -$(DEL) *.gas
    76     -$(DEL) *.obj
    77     -$(DEL) bootpack.nas
    78     -$(DEL) bootpack.map
    79     -$(DEL) bootpack.bim
    80     -$(DEL) bootpack.hrb
    81     -$(DEL) haribote.sys
    82 
    83 src_only :
    84     $(MAKE) clean
    85     -$(DEL) haribote.img
    Makefile in haribote00j

     

    通过Makefile,我们可以画出如下所示的编译和部署流程。

    总结

    今后开发OS时,就可以直接在bootpack.c中写代码;当遇到C语言无法完成的情况时,就在naskfunc.nas里用汇编语言写函数,然后用bootpack.c调用这些函数。

    现在的asmhead.nas程序在计算机启动时将显示器置为全黑,如下图所示。

     点此查看下一篇《30天自制操作系统》笔记(03)——使用Vmware

  • 相关阅读:
    do...while(0)的妙用
    用位运算实现求绝对值-有效避开ifelse判断
    经典排序算法的C++ template封装
    DOM学习总结(二) 熊削铁如泥
    标签设计Loop标签
    asp:树型select菜单
    自家用的DataReapter分页代码
    正则表达式(一)
    C#中利用正则表达式实现字符串搜索
    解读C#中的正则表达式
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/OS-in-30-days-02-use-C-in-OS-implement.html
Copyright © 2011-2022 走看看