配置环境

boch2.6.8

https://sourceforge.net/projects/bochs/files/bochs/2.6.8/bochs-2.6.8.tar.gz/download

chmod +x ./configure
sudo apt-get install libx11-dev xserver-xorg-dev xorg-dev
./configure \
--prefix=/home/robot/bochs \
--enable-debugger \
--enable-gdb-stub \
--enable-disasm \
--enable-iodebug \
--enable-x86-debugger \
--with-x \
--with-x11
make
make install

gcc4.4.7(ubuntu22.04)

sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ 	trusty main'
sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ trusty universe'
sudo apt update

sudo apt-get install lib32gcc1

sudo apt-get install g++-4.4

sudo apt-get install gcc-4.4 gcc-4.4-multilib

sudo apt-get install aptitude

sudo aptitude install gcc-4.4 gcc-4.4-multilib

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.4 40 

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 

update-alternatives --config gcc

boch环境

disk

home\robot\bochs\bochsrc.disk

megs : 512

romimage: file=/home/robot/bochs/share/bochs/BIOS-bochs-latest
vgaromimage: file=/home/robot/bochs/share/bochs/VGABIOS-lgpl-latest

boot: disk

log: bochs.out

mouse:enabled=0
keyboard:keymap=/home/robot/bochs/share/bochs/keymaps/x11-pc-us.map

ata0:enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14

#gdbstub:enabled=1,port=1234,text_base=0,data_base=0,bss_base=0

运行

bin/bochs
bin/bochs -f bochsrc.disk

导入配置文件

2
bochsrc.disk

创建启动盘

bin/bximage -hd -mode="flat" -size=60 -q hd60M.img

disk

home\robot\bochs\bochsrc.disk

#新加入的代码
ata0-master: type=disk, path="hd60M.img", mode=flat,cylinders=121,heads=16,spt=63

MBR

sudo apt-get install nasm

nasm -o ./boot/mbr.bin ./boot/mbr.S

dd if=/home/robot/bochs/boot/mbr.bin of=/home/robot/bochs/hd60M.img bs=512 count=1 conv=notrunc

home\robot\bochs\boot\mbr.S

;主引导程序
;-----------------------------------------
SECTION MBR vstart=0x7c00
;cs初始化其他寄存器
;reg (通用寄存器)
;   四个通用寄存器
;       ax bx cx dx
;   四个专用寄存器
;       SP(堆栈指针寄存器)    指向栈顶,操作系统使用
;       BP(基指针寄存器)      用于访问栈中的数据(局部变量)
;       SI(源变址寄存器)      用于字符串操作、地址计算
;       DI(目的变址寄存器)    用于字符串操作(如 MOVS, LODS, STOS 指令)
;   bx和si  bx和di  bp和si  bp和di
;sreg (段寄存器)
;   cs:代码段
;   ds:数据段
;   ss:栈段
;   es:扩展段
;   fs、gs:这两个寄存器用于提供额外的段支持,通常用于特殊的系统编程(如线程局部存储等)。
    mov ax,cs
    mov ds,ax
    mov ss,ax
    mov es,ax
    mov fs,ax
    mov sp,0x7c00
;-----------------------------------------
;清屏
    mov ah,0x06
    mov al,0x00
    mov bh,0x00
    mov cx,0x0000
    mov dx,0x184f
    int 10h
;-----------------------------------------
;获取光标位置
    mov ah,0x03
    mov bh,0x00
    int 10h
;-----------------------------------------
;打印字符串
    mov ax,message
    mov bp,ax

    mov ah,0x13
    mov al,0x01
    mov bh,0x00
    mov bl,0x02
    mov cx,0x0005
    int 10h
    
    message db "1 MBR"
    
    jmp $
;-----------------------------------------
;标志MBR
    times 510-($-$$) db 0
    db 0x55,0xaa

home\robot\bochs\tool\xxd.sh

#usage: sh xxd.sh 文件起始地址长度 
xxd -u -a -g 1 -s $2 -l $3 $1 
#-u  use upper case hex letters. Default is lower case.
# 
#-a | -autoskIP 
#          toggle autoskIP: A single '*' replaces nul-lines.  Default off. 
# 
#-g bytes | -groupsize bytes 
#     separate the output of every <bytes> bytes (two hex characters or eight bit-digits each) by a whitespace.  Specify -g 0 to 
#     suppress grouping.  <Bytes> defaults to 2 in normal mode and 1 in bits mode.  Grouping does not  apply  to  postscrIPt  or 
#     include style. 
# 
#-c cols | -cols cols 
#                    format <cols> octets per line. Default 16 (-i: 12, -ps: 30, -b: 6). Max 256. 
# 
#-s [+][-]seek 
#     start at <seek> bytes abs. (or rel.) infile offset.  + indicates that the seek is relative to the current stdin file position 
#     (meaningless when not reading from stdin).  - indicates that the seek should be that many characters from the end of 
#     the input (or if combined with +: before the current stdin file position). 
#     Without -s option, xxd starts at  the  current file position. 
sh ./tool/xxd.sh ./boot/mbr.bin 0 512

完善

cd boot
nasm -o mbr.bin mbr.S
nasm -o loader.bin loader.S
cd ..

dd if=/home/robot/bochs/boot/mbr.bin of=/home/robot/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=/home/robot/bochs/boot/loader.bin of=/home/robot/bochs/hd60M.img bs=512 count=2 seek=2 conv=notrunc

bin/bochs -f bochsrc.disk

home\robot\bochs\boot\mbr.S

;主引导程序
;-----------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
;cs初始化其他寄存器
;reg (通用寄存器)
;   四个通用寄存器
;       ax bx cx dx
;   四个专用寄存器
;       SP(堆栈指针寄存器)    指向栈顶,操作系统使用
;       BP(基指针寄存器)      用于访问栈中的数据(局部变量)
;       SI(源变址寄存器)      用于字符串操作、地址计算
;       DI(目的变址寄存器)    用于字符串操作(如 MOVS, LODS, STOS 指令)
;   bx和si  bx和di  bp和si  bp和di
;sreg (段寄存器)
;   cs:代码段
;   ds:数据段
;   ss:栈段
;   es:扩展段
;   fs、gs:这两个寄存器用于提供额外的段支持,通常用于特殊的系统编程(如线程局部存储等)。
    mov ax,cs
    mov ds,ax
    mov ss,ax
    mov es,ax
    mov fs,ax
    mov sp,0x7c00
    mov ax,0xb800
    mov gs,ax
;-----------------------------------------
;清屏
    mov ah,0x06
    mov al,0x00
    mov bh,0x00
    mov cx,0x0000
    mov dx,0x184f
    int 10h
;-----------------------------------------
;打印字符串
    mov byte [gs:0x00],' ' 
    mov byte [gs:0x01],0xA4    
    
    mov byte [gs:0x02],'M' 
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'B' 
    mov byte [gs:0x05],0xA4
    
    mov byte [gs:0x06],'R' 
    mov byte [gs:0x07],0xA4
    
    mov eax,LOADER_START_SECTOR ;规定的就是dx里面存放的是端口号 ax是需要或者输送的信息    
    mov bx,LOADER_BASE_ADDR     ;把要目标内存位置放进去 bx常作地址储存
    mov cx,4                    ;读取磁盘数 cx常作计数
    call rd_disk_m_16
    jmp LOADER_BASE_ADDR
;-----------------------------------------
;读取硬盘
rd_disk_m_16:
;   备份
    mov esi,eax
    mov di,cx
;   端口设置扇区数
    mov dx,0x1f2    ;OUT 指令的端口号存储在dx寄存器中
    mov al,cl       ;OUT 指令的源操作数必须是累加器
    out dx,al
    mov eax,esi
;   往该通道上的三个 LBA 寄存器写入扇区起始地址的低 24 位
    mov cl,0x08     ;如果移位位数大于1,必须将移位位数放在cl中

    mov dx,0x1f3
    out dx,al
    shr eax,cl
    
    mov dx,0x1f4
    out dx,al
    shr eax,cl
    
    mov dx,0x1f5
    out dx,al
    shr eax,cl
;   往 device 寄存器中写入 LBA 地址的 24~27 位,并置高四位为0x1110
    and al,0x0f
    or al,0xe0
    mov dx,0x1f6
    out dx,al
;   往该通道上的 command 寄存器写入操作命令 0x20读命令
    mov al,0x20
    mov dx,0x1f7
    out dx,al
;   读取该通道上的 status 寄存器,判断硬盘工作是否完成。第四位准备,第七位忙碌
    .not_ready:
        nop
        in al,dx
        and al,0x88
        cmp al,0x08
        jnz .not_ready
;   读出数据
;   每个扇区512个字节,256个字
    mov ax,di
    mov dx,256
    mul dx
    mov cx,ax
    mov dx,0x1f0
    .go_on_read:
        in ax,dx
        mov [bx],ax
        add bx,2
        loop .go_on_read
    ret
;-----------------------------------------
;标志MBR
    times 510-($-$$) db 0
    db 0x55,0xaa

home\robot\bochs\boot\loader.S

%include "boot.inc"
SECTION MBR vstart=LOADER_BASE_ADDR
    mov ax,0xb80a
    mov gs,ax
    mov byte [gs:0x00],0x00
    mov byte [gs:0x01],0xA4    
    
    mov byte [gs:0x02],'L' 
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'O' 
    mov byte [gs:0x05],0xA4
    
    mov byte [gs:0x06],'D' 
    mov byte [gs:0x07],0xA4
    
    mov byte [gs:0x08],'E' 
    mov byte [gs:0x09],0xA4
    
    mov byte [gs:0x0A],'R' 
    mov byte [gs:0x0B],0xA4

    jmp $

保护模式

cd boot
make burn
cd ..
bin/bochs -f bochsrc.disk

home\robot\bochs\boot\makefile

.PHONY:build burn clean

mbr_source = mbr.S
mbr_target = mbr.bin

loader_source=loader.S
loader_target=loader.bin

hard_disk=../hd60M.img

build:
	nasm $(mbr_source) -o $(mbr_target)
	nasm $(loader_source) -o $(loader_target)
burn:
	@make build
	dd if=$(mbr_target) of=$(hard_disk) bs=512 count=1 conv=notrunc
	dd if=$(loader_target) of=$(hard_disk) bs=512 count=2 seek=2 conv=notrunc

home\robot\bochs\boot\boot.inc

;-------------loader----------------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

;-------------------- gdt描述符属性 --------------------------------
DESC_G_4K equ 1_00000000000000000000000b ;第23位G 表示4K或者1MB位 段界限的单位值 此时为1则为4k 
DESC_D_32 equ 1_0000000000000000000000b  ;第22位D/B位 表示地址值用32位EIP寄存器 操作数与指令码32位
DESC_L    equ 0_000000000000000000000b   ;第21位 设置成0表示不设置成64位代码段 忽略
DESC_AVL  equ 0_00000000000000000000b    ;第20位 是软件可用的 操作系统额外提供的 可不设置
                                         
DESC_LIMIT_CODE2  equ  1111_0000000000000000b   ;第16-19位 段界限的最后四位 全部初始化为1 因为最大段界限*粒度必须等于0xffffffff
DESC_LIMIT_DATA2  equ  DESC_LIMIT_CODE2         ;相同的值  数据段与代码段段界限相同
DESC_LIMIT_VIDEO2 equ	0000_0000000000000000b	  ;第16-19位 显存区描述符VIDEO2 书上后面的0少打了一位 这里的全是0为高位 低位即可表示段基址
   
DESC_P            equ 	1_000000000000000b	  ;第15位  P present判断段是否存在于内存  
DESC_DPL_0        equ  00_0000000000000b         ;第13-14位 这两位更是重量级 Privilege Level 0-3
DESC_DPL_1        equ  01_0000000000000b	  ;0为操作系统 权力最高 3为用户段 用于保护
DESC_DPL_2        equ  10_0000000000000b
DESC_DPL_3        equ  11_0000000000000b

DESC_S_sys        equ  0_000000000000b           ;第12位为0 则表示系统段 为1则表示数据段
DESC_S_CODE       equ  1_000000000000b           ;第12位与type字段结合 判断是否为系统段还是数据段
DESC_S_DATA       equ  DESC_S_CODE


DESC_TYPE_CODE    equ  1000_00000000b            ;第9-11位表示该段状态 1000 可执行 不允许可读 已访问位0
;x=1 c=0 r=0 a=0
DESC_TYPE_DATA    equ  0010_00000000b            ;第9-11位表示该段状态 0010 可写  
;x=0 e=0 w=1 a=0


;代码段描述符高位4字节初始化 (0x00共8位 <<24 共32位初始化0) 
;4KB为单位 Data段32位操作数 初始化的部分段界限 最高权限操作系统代码段 P存在表示 状态 
DESC_CODE_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0X00

;数据段描述符高位4字节初始化
DESC_DATA_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X00

;显存段描述符高位4字节初始ua
DESC_VIDEO_HIGH4   equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X0B

;-------------------- 选择子属性 --------------------------------
;第0-1位 RPL 特权级比较是否允许访问  第2位TI 0表示GDT 1表示LDT    第3-15位索引值
RPL0    equ 00b
RPL1    equ 01b
RPL2    equ 10b
RPL3    equ 11b
TI_GDT  equ 000b
TI_LDT  equ 100b

home\robot\bochs\boot\loader.S

;引导程序
;-----------------------------------------
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;-----------------------------------------
;GDT描述符
;GDT第一个为空描述符,双字(32位)对齐
GDT_BASE:
    dd 0x00000000
    dd 0x00000000
;代码段描述符
CODE_DESC:
    dd 0x0000FFFF
    dd DESC_CODE_HIGH4
;数据和堆栈段描述符
DATA_STACK_DESC:
    dd 0x0000FFFF
    dd DESC_DATA_HIGH4
;显存段描述符
;0xBFFFF - 0xB8000 +1 = 0x8000(32KB),32KB / 4KB-1 = 7
VIDEO_DESC:
    dd 0x80000007
    dd DESC_VIDEO_HIGH4
;GDT的大小和界限
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
;预留60个段描述符(每个8字节)
times 60 dq 0

total_mem_bytes dd 0

;-----------------------------------------
;定义选择子(代码段、数据段、显存段)
SELECTOR_CODE equ (0x0001<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0x0002<<3)+TI_GDT+RPL0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
;GDT指针
gdt_ptr dw GDT_LIMIT
        dd GDT_BASE
;-----------------------------------------
;loader设置
loader_start:
;   初始化寄存器
    mov sp,LOADER_BASE_ADDR
    mov ax,0
    mov ax,0xb80a
    mov gs,ax
;   打开A20地址线
;   将端口 0x92 的第 1 位置 1
    in al,0x92
    or al,0000_0010B
    out 0x92,al
;   加载GDT到gdtr寄存器
    lgdt [gdt_ptr]
;   控制寄存器cr0的pe位置1,表示保护模式下运行
    mov eax,cr0
    or eax,0x0000_0001
    mov cr0,eax
;   打印
    mov byte [gs:0x00],0x00
    mov byte [gs:0x01],0xA4    
    
    mov byte [gs:0x02],'L' 
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'O' 
    mov byte [gs:0x05],0xA4
    
    mov byte [gs:0x06],'D' 
    mov byte [gs:0x07],0xA4
    
    mov byte [gs:0x08],'E' 
    mov byte [gs:0x09],0xA4
    
    mov byte [gs:0x0A],'R' 
    mov byte [gs:0x0B],0xA4
;   远跳转,为避免流水线和分支预测提前预取16位指令,刷新流水线
    jmp SELECTOR_CODE:p_mode_start
;-----------------------------------------
;已经打开保护模式
[bits 32]
p_mode_start:
;   将各段寄存器(数据段、附加段、堆栈段)初始化为数据段选择子
;   设置栈顶
    mov ax,SELECTOR_DATA
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,LOADER_STACK_TOP
;   将显存段寄存器初始化为显存段选择子
    mov ax,SELECTOR_VIDEO
    mov gs,ax

    jmp $

载入内核

查看内存容量

cd boot
make burn
cd ..
bin/bochs -f bochsrc.disk
xp 0xb00

home\robot\bochs\boot\loader.S

;引导程序
;-----------------------------------------
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;-----------------------------------------
;GDT描述符
;GDT第一个为空描述符,双字(32位)对齐
GDT_BASE:
    dd 0x00000000
    dd 0x00000000
;代码段描述符
CODE_DESC:
    dd 0x0000FFFF
    dd DESC_CODE_HIGH4
;数据和堆栈段描述符
DATA_STACK_DESC:
    dd 0x0000FFFF
    dd DESC_DATA_HIGH4
;显存段描述符
;0xBFFFF - 0xB8000 +1 = 0x8000(32KB),32KB / 4KB-1 = 7
VIDEO_DESC:
    dd 0x80000007
    dd DESC_VIDEO_HIGH4
;GDT的大小和界限
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
;预留59个 define double四字型 8字描述符
times 59 dq 0
times 5 db 0            ;为了凑整数 0x800 导致前面少了三个字节
;地址0xb00
total_mem_bytes dd 0
;ards结构体及其数量
ards_buf times 244 db 0
ards_nr dw 0
;-----------------------------------------
;定义选择子(代码段、数据段、显存段)
SELECTOR_CODE equ (0x0001<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0x0002<<3)+TI_GDT+RPL0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
;GDT指针
gdt_ptr dw GDT_LIMIT
        dd GDT_BASE
;-----------------------------------------
loader_start:
;检测内存容量
;BIOS中断0x15三个字子功能
;1、EAX=0xE820:遍历主机上全部内存
;   循环获取内存信息内容置于ards结构体中
    mov ax,0
    mov es,ax
    mov di,ards_buf
    mov edx,0x534d4150          ;固定数值,签名标记
    xor ebx,ebx
;   循环记录ards,返回值被覆盖的循环初始化(while结构)
    .e820_mem_get_loop:
        mov eax,0x0000e820
        mov ecx,0x00000014
        int 15h
        jc  .e801_mem_get_loop
        add di,cx               ;使指针指向下一个ards结构体
        inc word [ards_nr]      ;inc使ards_nr对应的内存加一,在这里循环统计ards的数量
        cmp ebx,0
        jnz .e820_mem_get_loop
;   找出所有ards中内存容量最大的那一个,即为要获取的内存容量(冒泡排序)
;       初始化
    mov cx,[ards_nr]
    mov ebx,ards_buf
    xor edx,edx
;       循环(do while结构)
    .e820_find_max_mem_area:
;       BaseAddrLow+LengthLow 是一片内存区域的上限,单位为字节
        mov eax,[ebx]
        add eax,[ebx+8]
        cmp edx,eax             ;if结构用于比较edx和eax的大小
        jge .next_ards          ;SF(非负为1)=OF(非0为1)
        mov edx,eax
        .next_ards:
        add ebx,20              ;指向下一个ards
        loop .e820_find_max_mem_area
    jmp .mem_get_ok
;2、AX=0xE801: 分别检测低 15MB 和 16MB~4GB 的内存,最大支持 4GB。
    .e801_mem_get_loop:
        mov ax,0xe801
        int 15h
        jc  .ah88_mem_get_loop
;   16位以字节为单位最多表示64KB范围的内存,32位以字节为单位最多表示4GB范围的内存
;   AX中为0~15MB的内存容量,单位为1KB,可将其分为0~64KB和64KB~15MB。有1MB是缓冲区,
;   BX中为16MB~4GB的内存容量,单位为64KB
;       先算出AX中的内存容量,并转换成以字节为单位
        and eax,0x0000ffff
        mov cx,0x400
;           积为32位,低16位在ax中,高16位在dx中
        mul cx
        shl edx,16
        or edx,eax
        add edx,0x100000
        mov esi,edx             ;乘法覆盖了edx,因此要备份
;       再算出BX中的内存容量,并转换成以字节为单位
        xor eax,eax
        mov ax,bx
        mov ecx,0x10000
;           积为64位,低32位在eax中,高32位在edx中
        mul ecx
        mov edx,esi
        add edx,eax
        jmp .mem_get_ok
;3、AH=0x88:   最多检测出 64MB 内存,实际内存超过此容量也按照 64MB 返回。
    .ah88_mem_get_loop:
        mov ah,0x88
        int 15h
        jc  .mem_get_error
        and eax,0x0000ffff
        mov cx,0x400
        mul cx
        shl edx,16
        or edx,eax
        add edx,0x100000
        jmp .mem_get_ok
    .mem_get_error:
        jmp $
    .mem_get_ok:
        mov [total_mem_bytes],edx
;-----------------------------------------
;打开保护模式
;   初始化寄存器
    mov sp,LOADER_BASE_ADDR
    mov ax,0xb80a
    mov gs,ax
    mov ax,0
;   打开A20地址线
;   将端口 0x92 的第 1 位置 1
    in al,0x92
    or al,0000_0010B
    out 0x92,al
;   加载GDT到gdtr寄存器
    lgdt [gdt_ptr]
;   控制寄存器cr0的pe位置1,表示保护模式下运行
    mov eax,cr0
    or eax,0x0000_0001
    mov cr0,eax
;   打印
    mov byte [gs:0x00],0x00
    mov byte [gs:0x01],0xA4    
    
    mov byte [gs:0x02],'L' 
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'O' 
    mov byte [gs:0x05],0xA4
    
    mov byte [gs:0x06],'D' 
    mov byte [gs:0x07],0xA4
    
    mov byte [gs:0x08],'E' 
    mov byte [gs:0x09],0xA4
    
    mov byte [gs:0x0A],'R' 
    mov byte [gs:0x0B],0xA4
;   远跳转,为避免流水线和分支预测提前预取16位指令,刷新流水线
    jmp SELECTOR_CODE:p_mode_start
;-----------------------------------------
;已经打开保护模式
[bits 32]
p_mode_start:
;   将各段寄存器(数据段、附加段、堆栈段)初始化为数据段选择子
;   设置栈顶
    mov ax,SELECTOR_DATA
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,LOADER_STACK_TOP
;   将显存段寄存器初始化为显存段选择子
    mov ax,SELECTOR_VIDEO
    mov gs,ax

    mov byte [gs:160],'P'
    mov byte [gs:161],0xA4

    jmp $

home\robot\bochs\boot\loader.S

.PHONY:build burn clean

mbr_source = mbr.S
mbr_target = mbr.bin

loader_source=loader.S
loader_target=loader.bin

hard_disk=../hd60M.img

build:
	nasm $(mbr_source) -o $(mbr_target)
	nasm $(loader_source) -o $(loader_target)
burn:
	@make build
	dd if=$(mbr_target) of=$(hard_disk) bs=512 count=1 conv=notrunc
	dd if=$(loader_target) of=$(hard_disk) bs=512 count=3 seek=2 conv=notrunc

内存分页

home\robot\bochs\boot\loader.S

;引导程序
;-----------------------------------------
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;-----------------------------------------
;GDT描述符
;GDT第一个为空描述符,双字(32位)对齐
GDT_BASE:
    dd 0x00000000
    dd 0x00000000
;代码段描述符
CODE_DESC:
    dd 0x0000FFFF
    dd DESC_CODE_HIGH4
;数据和堆栈段描述符
DATA_STACK_DESC:
    dd 0x0000FFFF
    dd DESC_DATA_HIGH4
;显存段描述符
;0xBFFFF - 0xB8000 +1 = 0x8000(32KB),32KB / 4KB-1 = 7
VIDEO_DESC:
    dd 0x80000007
    dd DESC_VIDEO_HIGH4
;GDT的大小和界限
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
;预留59个 define double四字型 8字描述符
times 59 dq 0
times 5 db 0            ;为了凑整数 0x800 导致前面少了三个字节
;地址0xb00
total_mem_bytes dd 0
;ards结构体及其数量
ards_buf times 244 db 0
ards_nr dw 0
;-----------------------------------------
;定义选择子(代码段、数据段、显存段)
SELECTOR_CODE equ (0x0001<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0x0002<<3)+TI_GDT+RPL0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
;GDT指针
gdt_ptr dw GDT_LIMIT
        dd GDT_BASE
;-----------------------------------------
loader_start:
;检测内存容量
;BIOS中断0x15三个字子功能
;1、EAX=0xE820:遍历主机上全部内存
;   循环获取内存信息内容置于ards结构体中
    mov ax,0
    mov es,ax
    mov di,ards_buf
    mov edx,0x534d4150          ;固定数值,签名标记
    xor ebx,ebx
;   循环记录ards,返回值被覆盖的循环初始化(while结构)
    .e820_mem_get_loop:
        mov eax,0x0000e820
        mov ecx,0x00000014
        int 15h
        jc  .e801_mem_get_loop
        add di,cx               ;使指针指向下一个ards结构体
        inc word [ards_nr]      ;inc使ards_nr对应的内存加一,在这里循环统计ards的数量
        cmp ebx,0
        jnz .e820_mem_get_loop
;   找出所有ards中内存容量最大的那一个,即为要获取的内存容量(冒泡排序)
;       初始化
    mov cx,[ards_nr]
    mov ebx,ards_buf
    xor edx,edx
;       循环(do while结构)
    .e820_find_max_mem_area:
;       BaseAddrLow+LengthLow 是一片内存区域的上限,单位为字节
        mov eax,[ebx]
        add eax,[ebx+8]
        cmp edx,eax             ;if结构用于比较edx和eax的大小
        jge .next_ards          ;SF(非负为1)=OF(非0为1)
        mov edx,eax
        .next_ards:
        add ebx,20              ;指向下一个ards
        loop .e820_find_max_mem_area
    jmp .mem_get_ok
;2、AX=0xE801: 分别检测低 15MB 和 16MB~4GB 的内存,最大支持 4GB。
    .e801_mem_get_loop:
        mov ax,0xe801
        int 15h
        jc  .ah88_mem_get_loop
;   16位以字节为单位最多表示64KB范围的内存,32位以字节为单位最多表示4GB范围的内存
;   AX中为0~15MB的内存容量,单位为1KB,可将其分为0~64KB和64KB~15MB。有1MB是缓冲区,
;   BX中为16MB~4GB的内存容量,单位为64KB
;       先算出AX中的内存容量,并转换成以字节为单位
        and eax,0x0000ffff
        mov cx,0x400
;           积为32位,低16位在ax中,高16位在dx中
        mul cx
        shl edx,16
        or edx,eax
        add edx,0x100000
        mov esi,edx             ;乘法覆盖了edx,因此要备份
;       再算出BX中的内存容量,并转换成以字节为单位
        xor eax,eax
        mov ax,bx
        mov ecx,0x10000
;           积为64位,低32位在eax中,高32位在edx中
        mul ecx
        mov edx,esi
        add edx,eax
        jmp .mem_get_ok
;3、AH=0x88:   最多检测出 64MB 内存,实际内存超过此容量也按照 64MB 返回。
    .ah88_mem_get_loop:
        mov ah,0x88
        int 15h
        jc  .mem_get_error
        and eax,0x0000ffff
        mov cx,0x400
        mul cx
        shl edx,16
        or edx,eax
        add edx,0x100000
        jmp .mem_get_ok
    .mem_get_error:
        jmp $
    .mem_get_ok:
        mov [total_mem_bytes],edx
;-----------------------------------------
;打开保护模式
;   初始化寄存器
    mov sp,LOADER_BASE_ADDR
    mov ax,0xb80a
    mov gs,ax
    mov ax,0
;   打开A20地址线
;   将端口 0x92 的第 1 位置 1
    in al,0x92
    or al,0000_0010B
    out 0x92,al
;   加载GDT到gdtr寄存器
    lgdt [gdt_ptr]
;   控制寄存器cr0的pe位置1,表示保护模式下运行
    mov eax,cr0
    or eax,0x0000_0001
    mov cr0,eax
;   打印
    mov byte [gs:0x00],0x00
    mov byte [gs:0x01],0xA4    
    
    mov byte [gs:0x02],'L' 
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'O' 
    mov byte [gs:0x05],0xA4
    
    mov byte [gs:0x06],'D' 
    mov byte [gs:0x07],0xA4
    
    mov byte [gs:0x08],'E' 
    mov byte [gs:0x09],0xA4
    
    mov byte [gs:0x0A],'R' 
    mov byte [gs:0x0B],0xA4
;   远跳转,为避免流水线和分支预测提前预取16位指令,刷新流水线
    jmp SELECTOR_CODE:p_mode_start
;-----------------------------------------
;已经打开保护模式
[bits 32]
p_mode_start:
;   将各段寄存器(数据段、附加段、堆栈段)初始化为数据段选择子
;   设置栈顶
    mov ax,SELECTOR_DATA
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,LOADER_STACK_TOP
;   将显存段寄存器初始化为显存段选择子
    mov ax,SELECTOR_VIDEO
    mov gs,ax

    mov byte [gs:160],'P'
    mov byte [gs:161],0xA4

;-----------------------------------------
;启动分页
    call setup_page
    sgdt [gdt_ptr]                      ;要将描述符表地址及偏移量写入内存gdt_ptr,一会儿用新地址重新加载
    mov ebx, [gdt_ptr + 2]              ;将gdt描述符中视频段描述符中的段基址+0xc0000000 
    or dword [ebx + 0x18 + 4], 0xc0000000   ; 视频段是第 3 个段描述符,每个描述符是8字节,故0x18
    add dword [gdt_ptr + 2], 0xc0000000 ; 将gdt的基址加上0xc0000000使其成为内核所在的高地址 
    add esp, 0xc0000000                 ; 将栈指针同样映射到内核地址 
;将页表地址写入控制寄存器cr3
    mov eax,PAGE_DIR_TABLE_POS
    mov cr3,eax
;寄存器cr0的PG位(31位)置1
    mov eax,cr0
    or eax,0x80000000
    mov cr0,eax

    lgdt [gdt_ptr]                      ;在开启分页后,用gdt新的地址重新加载 

    mov ax,SELECTOR_VIDEO
    add ax,0xA
    mov gs,ax
    mov byte [gs:160],'V'
    mov byte [gs:161],0xA4

    jmp $

;-----------------------------------------
;分页创建函数
setup_page:
;   把页目录对应的空间清空
    mov ecx,0x1000
    mov esi,0
    .clear_page_dir:
        mov byte [PAGE_DIR_TABLE_POS+esi],0
        inc esi
        loop .clear_page_dir
;   创建页目录项pde
    .create_pde:
        mov eax,PAGE_DIR_TABLE_POS
        add eax,0x1000                  ;第一个页表的位置及属性
        mov ebx,eax                     ;备份做基址
        or eax,PG_P|PG_RW_W|PG_US_U     ;用户级,可读写,现在eax高20位为页表物理地址,除了最低3位以外的控制状态位为0

;       将0~0xc00的空间给用户空间,而将0xc00之上的空间给内核
;       0xc00 表示第768(0xc00/4)个页表占用的目录项,一个页表4K大小0x10_0000
;       0x0~0xbfff_ffff共计3G属于用户进程;0xc000_0000~0xffff_ffff共计1G属于内核
        mov [PAGE_DIR_TABLE_POS+0x0],eax
        mov [PAGE_DIR_TABLE_POS+0xc00],eax

        sub eax,0x1000                  ;指向页目录表自身
        mov [PAGE_DIR_TABLE_POS+0x1000],eax ;虚拟内存最后一个目录项 指向页目录表自身 为了动态操纵页表
;   创建页表pte
;   循环256次,每次初始化一个页表项
    mov ecx,0x100
    mov esi,0
    mov edx,PG_P|PG_RW_W|PG_US_U        ;用户级,可读写,高位地址全0
    .create_pte:
        mov [ebx+esi*4],edx
        add edx,0x1000
        inc esi
        loop .create_pte
;   创建内核其他页表的pde
;   0xc01~0xcfe
    mov eax,PAGE_DIR_TABLE_POS
    mov ebx,eax
    add eax,0x2000
    or eax,PG_P|PG_RW_W|PG_US_U
    mov ecx,0xfe
    mov esi,0xc01
    .create_kernel_pde:
        mov [ebx+esi*4],eax
        inc esi
        add eax,0x1000
        loop .create_kernel_pde
    ret

home\robot\bochs\boot\boot.inc

;-------------loader----------------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
PAGE_DIR_TABLE_POS equ 0x100000          ;页目录表的物理地址,1MB的第一个字节

;-------------------- gdt描述符属性 --------------------------------
DESC_G_4K equ 1_00000000000000000000000b            ;第23位G 表示4K或者1MB位 段界限的单位值 此时为1则为4k 
DESC_D_32 equ 1_0000000000000000000000b             ;第22位D/B位 表示地址值用32位EIP寄存器 操作数与指令码32位
DESC_L    equ 0_000000000000000000000b              ;第21位 设置成0表示不设置成64位代码段 忽略
DESC_AVL  equ 0_00000000000000000000b               ;第20位 是软件可用的 操作系统额外提供的 可不设置
                                         
DESC_LIMIT_CODE2  equ  1111_0000000000000000b       ;第16-19位 段界限的最后四位 全部初始化为1 因为最大段界限*粒度必须等于0xffffffff
DESC_LIMIT_DATA2  equ  DESC_LIMIT_CODE2             ;相同的值  数据段与代码段段界限相同
DESC_LIMIT_VIDEO2 equ	0000_0000000000000000b	    ;第16-19位 显存区描述符VIDEO2,这里的全是0为高位 低位即可表示段基址
   
DESC_P            equ 	1_000000000000000b          ;第15位  P present判断段是否存在于内存  
DESC_DPL_0        equ  00_0000000000000b            ;第13-14位 这两位更是重量级 Privilege Level 0-3
DESC_DPL_1        equ  01_0000000000000b	        ;0为操作系统 权力最高 3为用户段 用于保护
DESC_DPL_2        equ  10_0000000000000b
DESC_DPL_3        equ  11_0000000000000b

DESC_S_sys        equ  0_000000000000b              ;第12位为0 则表示系统段 为1则表示数据段
DESC_S_CODE       equ  1_000000000000b              ;第12位与type字段结合 判断是否为系统段还是数据段
DESC_S_DATA       equ  DESC_S_CODE


DESC_TYPE_CODE    equ  1000_00000000b               ;第9-11位表示该段状态 1000 可执行 不允许可读 已访问位0
;x=1 c=0 r=0 a=0
DESC_TYPE_DATA    equ  0010_00000000b               ;第9-11位表示该段状态 0010 可写  
;x=0 e=0 w=1 a=0


;代码段描述符高位4字节初始化 (0x00共8位 <<24 共32位初始化0) 
;4KB为单位 Data段32位操作数 初始化的部分段界限 最高权限操作系统代码段 P存在表示 状态 
DESC_CODE_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0X00

;数据段描述符高位4字节初始化
DESC_DATA_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X00

;显存段描述符高位4字节初始ua
DESC_VIDEO_HIGH4   equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X0B

;-------------------- 选择子属性 --------------------------------
;第0-1位 RPL 特权级比较是否允许访问  第2位TI 0表示GDT 1表示LDT    第3-15位索引值
RPL0    equ 00b
RPL1    equ 01b
RPL2    equ 10b
RPL3    equ 11b
TI_GDT  equ 000b
TI_LDT  equ 100b

;------------------- 页表相关属性 -------------------------------
;第1位,存在位
PG_P  equ   1b 
;第2位,读写位
PG_RW_R  equ  00b 
PG_RW_W equ  10b 
;第3位,普通/超级用户位
PG_US_S  equ  000b 
PG_US_U  equ  100b