《操作系统真象还原》 5.2启用内存分页机制代码

这儿因为书上代码写的不完整,花了很长时间才完整运行代码,所以这里贴出代码部分

代码部分

  • boot.inc
;----------------- loader 和 kernel -----------------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

PAGE_DIR_TABLE_POS equ 0x100000

;------------------ 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位 DPL Descriptor 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 e=0 w=0 a=0
DESC_TYPE_DATA    equ  0010_00000000b            ;第9-11位type段 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字节初始化
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

;--------------------页表相关属性---------------------------
PG_P     equ    1b
PG_RW_R  equ	00b
PG_RW_W  equ	10b
PG_US_S  equ	000b
PG_US_U  equ	100b



  • mbr.S
;主引导程序
;------------------------------------------------------------------------------
%include "boot.inc"		;让编译器在编译之前,把boot.inc文件包含进来
SECTION MBR vstart=0x7c00
	mov ax,cs
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov fs,ax
	mov sp,0x7c00
	mov ax,0xb800
	mov gs,ax

;清屏
;利用0x06功能,上卷所有行,则可清屏
;-------------------------------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;-------------------------------------------------------------------------------
;输入;
;AH 功能号:0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值
	mov ax,0600h
	mov bx,0700h
	mov cx,0	;左上角:(0,0)
	mov dx,184fh	;右下角:(80,25)
;因为VGA文本模式中,一行只能容纳80个字符,共25行
	; 下标从0开始,所有0x18=24,0x4f=79
	int 10h		;int 10h

	;输出字符串MBR
	mov byte [gs:0x00],'1'
	mov byte [gs:0x01],0xA4

	mov byte [gs:0x02],' '
	mov byte [gs:0x03],0xA4

	mov byte [gs:0x04],'M'
	mov byte [gs:0x05],0xA4		;A表示绿色背景闪烁,4表示前景颜色为红色

	mov byte [gs:0x06],'B'
	mov byte [gs:0x07],0xA4

	mov byte [gs:0x08],'R'
	mov byte [gs:0x09],0xA4

	mov eax,LOADER_START_SECTOR	;起始扇区lba地址,0x2
	mov bx,LOADER_BASE_ADDR		;写入的地址,0x900
	mov cx,4			;待写入的扇区数,由于loader.bin超过了512个字节,可能是多个扇区
	call rd_disk_m_16		;以下读取程序的起始部分(一个扇区)

	jmp LOADER_BASE_ADDR+0x300

;-------------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;-------------------------------------------------------------------------------------
					;eax=LBA扇区号
					;bx=将数据写入的内存地址
					;cx=读入的扇区数
	mov esi,eax			;备份eax,因为al在out命令中会使用,会影响到eax的低8位
	mov di,cx			;备份cx,cx在读数据的时候会使用到
;读写硬盘
;第一步:设置要读取的扇区数
	mov dx,0x1f2			;虚拟硬盘属于ata0,是Primary通道,所以sector count 是由0x1f2访问
	mov al,cl			;cl是cx的低8位,就读一个扇区,这样就能传过去了
	out dx,al			;读取的扇区数,sector count 记录要读取的扇盘数量

	mov eax,esi			;恢复eax,现在eax存的是其实扇区lba的地址,0x2,第二个扇区

;第二步:将LBA地址存入 0x1f3 ~ 0x1f6

	;LBA地址 7~0 位写入端口 0x1f3
	mov dx,0x1f3			;LBA low
	out dx,al			;eax的第8位,就是al

	;LBA地址 15~8 位写入端口 0x1f4
	mov cl,8
	shr eax,cl			;eax右移8位,让al的数,变为eax中8位
	mov dx,0x1f4			;LBA mid
	out dx,al

	;LBA地址 23~16 位写入端口 0x1f5
	shr eax,cl			;再右移8位
	mov dx,0x1f5			;LBA high
	out dx,al

	shr eax,cl			;这样al为0000
	and al,0x0f			;lba第24~27位
	or al,0xe0			;设置7~4位为1110,表示lba模式
	mov dx,0x1f6			;就是拼凑出device寄存器的值
	out dx,al

;第3步:向0x1f7端口写入读命令,0x20
	mov dx,0x1f7
	mov al,0x20
	out dx,al			;command:0x1f7,写入命令,写入的命令是读命令

;第四步:检测硬盘状态
 .not_ready:
	;同一端口,写时表示写入命令字,读时表示写入硬盘的状态,所以不需要更换dx的值
	nop				;减少打扰硬盘的工作
	in al,dx			;将Status的寄存器的值读入到al中
	and al,0x88			;第四位为1表示硬盘控制器已准备好数据传输,第七位为1表示硬盘忙,保存第4位和第7位
	cmp al,0x08			;若第4位为1,表示数据已经准备好了,若第7位为1,表示硬盘处于忙
	jnz .not_ready			;若未准备好,继续等,判断结果是否为0

;第5步,从0x1f0端口读数据
	mov ax,di			;这个时候di存的是上面备份的cx,及时要读取的扇区的数量
	mov dx,256			;每次in操作只读取两个字节,根据读入的数据总量(扇区数*512字节)
	mul dx				;dx*ax就是总数量/2,然后将值送到cx中,cx就是要in的次数
	mov cx,ax			;di为要读取的扇区数,一个扇区有512个字节,每次读入一个字,共需要di*512/2次,所以di*256

	mov dx,0x1f0
 .go_on_read:
	in ax,dx			;读入到ax中
	mov [bx],ax			;读入到bx指向的内存
	add bx,2			;每次读入2个字节
	loop .go_on_read		;cx是循环的次数
	ret

	times 510-($-$$) db 0
	db 0x55,0xaa

  • loader.S:注意这里与书上不一致,需要仔细对比
	%include "boot.inc"
	section loader vstart=LOADER_BASE_ADDR
	LOADER_STACK_TOP equ LOADER_BASE_ADDR	;保护模式下的栈

;构建 gdt 及其内部的描述符
	GDT_BASE: dd 0x00000000		;GDT的第0个段不可用
		  dd 0x00000000
	; 定义了三个有用的段描述符
	CODE_DESC: dd 0x0000FFFF	;段描述符的低4字节,其中的低2字节是段长度 FFFF,高2字节是段基址 0000,
		  dd DESC_CODE_HIGH4	;代码段的高四节,已经定义好了

	DATA_STACK_DESC: dd 0x0000FFFF	;数据段和栈段的段描述符
			 dd DESC_DATA_HIGH4
;显存段描述符,0xb8000~0xbffff是用于文本模式的显示内存,段基址:0x8000 长度 0007
VIDEO_DESC:	  dd 0x80000007		;limit=(0xbffff-0xb8000)/4k = 0x7	4k是段粒度
		  dd DESC_VIDEO_HIGH4	;此时dpl为0

	GDT_SIZE equ $ - GDT_BASE	;19~20 获取GDT的大小,为加载GDT做准备
	GDT_LIMIT equ GDT_SIZE - 1
	times 59 dq 0			;
	times 5  db 0
	SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0	;相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0,构建代码段的段选择子,段描述符+TI+PRL
	SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	;同上
	SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	;同上
	;total_mem_bytes用于保存内存容量,以字节为单位,此位置比较好记
	;当前偏移 loader.bin 文件头0x200字节 loader.bin的加载地址是0x900	4个段描述符的定义 32 dq了60个 480 = 512 = 0x200
	;故total_mem_bytes内存中的地址是0xb00 将来在内核咱们会引用此地址
	total_mem_bytes dd 0		;存储获取到的内存容量
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	;以下是gdt的指针,前2字节是gdt界限,后四字节是gdt起始地址
	gdt_ptr dw GDT_LIMIT		;定义GDT的指针.前16位,是GDT以字节为单位的长度,也就是GDT大小减1
		dd GDT_BASE		;GDT的起始位置

;人工对齐:total_mem_bytes 4+gdt_ptr 6+ards_buf 244+ards_nr2,共256字节
	ards_buf times 244 db 0
	ards_nr dw 0			;用于记录ARDS结构体数量,每执行一次中断就会获得一个ards结构的数据

	loader_start:

;int 15h eax = 0000E820h,edx = 534D4150h('SMAP')获取内存布局

	xor ebx,ebx			;第一次调用ebx要为0
	mov edx,0x543d4150		;edx只赋值一次,循环体中不改变
	mov di,ards_buf			;ards结构缓冲区,es:di,BIOS将获取到的内存信息写到此寄存器指向的内存,每次都以ARDS格式返回
.e820_mem_get_loop:			;循环获取每个ARDS内存范围描述结构
	mov eax,0x0000e820		;子功能号,eax用来指定子功能号,每次执行 int 0x15后,eax值会变为0x534d4150
	mov ecx,20			;ARDS结构的字节大小,ARDS地址范围描述符是20字节
	int 0x15
	jc .e820_failed_so_try_e801	;若cf位为1啧有错误发生,尝试0xe801子功能
	add di,cx			;使di增加20字节指向缓冲区中新ARDS结构位置
	inc word [ards_nr]		;记录ARDS数量
	cmp ebx,0			;ebx存放后续值,下一个ARDS的地址,在cf为0的情况下,若ebx为0,表示这是最后一个ARDS结构
	jnz .e820_mem_get_loop

;在只有ards结构中,找出(base_add_low + length_low)的最大值,即内存大小
	mov cx,[ards_nr]		;遍历每一个ARDS结构体,循环次数是ARDS的数量
	mov ebx,ards_buf
	xor edx,edx			;edx为最大的内存容量,在此先清0
.find_max_mem_area:			;无需判断type是否为1,最大内存块一定是可被使用的
	mov eax,[ebx]			;base_add_low
	add eax,[ebx+8]			;length_low
	add ebx,20			;指向缓冲区中下一个ARDS结构
	cmp edx,eax			;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
	jge .next_ards
	mov edx,eax
.next_ards:
	loop .find_max_mem_area
	jmp .mem_get_ok
	
;-----------	int 15h ax = E801h 获取内存大小,最大支持4G -------------------
; 返回后,ax cx 值一样,以KB为单位,bx dx 值都一样,以64KB为单位
; 在ax和cx寄存器中为低16MB,在bx和dx寄存器中为16Mb到4GB
.e820_failed_so_try_e801:
	mov ax,0xe801			;功能号
	int 0x15
	jc .e801_failed_so_try88	;若当前e801方法失败,就尝试0x88方法

;1. 先算出低15MB的内存,ax和cx中是以KB为单位的内存数量,将其转化成以byte为单位
	mov cx,0x400			;cx和ax一样,cx用作乘数
	mul cx
	shl edx,16
	and eax,0x0000FFFF
	or edx,eax
	add edx,0x100000		;ax只是15MB,故要加1MB
	mov esi,edx			;先把低15MB的内存容量存入esi寄存器备份

;2. 再将16MB以上的内存转化为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
	xor eax,eax
	mov ax,bx
	mov ecx,0x10000			;0x10000 十进制为 64KB
	mul ecx				;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax
	add esi,eax			;由于此方法只能测出4GB以内的内存,故32位eax足够了 edx肯定为0
	mov edx,esi			;edx为总内存大小
	jmp .mem_get_ok

;-----------	int 15h	ah = 0x88 获取内存大小,只能获取64MB之内	-----------------------
.e801_failed_so_try88:
	;int 15后,ax存入的是以KB为单位的内存容量
	mov ah,0x88
	int 0x15
	;jc .error_hlr
	and eax,0x0000FFFF

	;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中
	mov cx,0x400			;0x400等于1024,将ax中的内存容量换为byte为三维
	mul cx
	shl edx,16			;把dx移到高16位
	or edx,eax			;把积的低16位组合到edx,为32位的积
	add edx,0x100000		;0x88子功能只会返回1MB以上的内存,故实际内存要加上1MB

.mem_get_ok:
	mov [total_mem_bytes],edx	;将内存换成byte单位后存入total_mem_bytes处


;----------------------进入保护模式--------------------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1


	;----------------    打开A20	----------------------------------
	in al,0x92
	or al,0000_0010B
	out 0x92,al

	;----------------     加载GDT	----------------------------------
	lgdt [gdt_ptr]


	;----------------     cr0 第0位置1 -------------------------------
	mov eax,cr0
	or eax,0x00000001
	mov cr0,eax

	

; -------------------------------- 已经打开保护模式 ---------------------------------------
	jmp dword SELECTOR_CODE:p_mode_start	;刷新流水线

	.error_hlt:								;出错则挂起
		hlt 								;处理器暂停,直到出现中断或者复位信号才继续
[bits 32]
p_mode_start:
    ; 初始化32位的段寄存器
    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

;创建页目录及页表并初始化页内存位图
   call setup_page
   ;要将描述符表地址及偏移量写入内存gdt_ptr,一会儿用新地址重新加载
   sgdt [gdt_ptr]              ;存储到原来gdt所在的位置

   mov ebx,[gdt_ptr+2]
   or dword [ebx+0x18+4],0xc0000000

   add dword [gdt_ptr+2],0xc0000000
   add esp,0xc0000000

   mov eax,PAGE_DIR_TABLE_POS
   mov cr3,eax
   mov eax,cr0
   or eax,0x80000000
   mov cr0,eax

   lgdt [gdt_ptr]

   ;重新初始化gs寄存器
   mov ax, SELECTOR_VIDEO
   mov gs, ax
   mov byte [gs:160],'V'

   jmp $



;------------------- 建立页表 -------------------
setup_page:
   mov ecx,4096
   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               ;此时的eax为第一个页表的物理地址
   mov ebx,eax                  ;ebx=eax,为后续的.create_pte做准备,ebx为基址

   ;下面将偏移地址0x0(第1个)和0xc00(第768个页目录项)存为第1个页表的地址,每个页表表示4MB内存
   or eax,PG_US_U|PG_RW_W|PG_P        ;最低特权级|可读写|存在
   mov [PAGE_DIR_TABLE_POS+0x0],eax   ;第1个页目录项
   mov [PAGE_DIR_TABLE_POS+0xc00],eax ;第768个页目录项
   sub eax,0x1000
   mov [PAGE_DIR_TABLE_POS+4092],eax  ;最后一个页目录项指向页目录自己

   ;创建页表项(PTE)
   mov ecx,256                  ;对低端内存1MB建页表:1MB/4KB=256(256个页表项,1个页表足矣)
   mov esi,0
   mov edx,PG_US_U|PG_RW_W|PG_P ;最低特权第|可读写|存在
.create_pte:
   mov [ebx+esi*4],edx          ;逐个页表项设置
   add edx,4096                 ;因为1个页表4KB,所以edx的基址+4KB
   inc esi
   loop .create_pte

   ;创建内核其它页表的PDE
   mov eax,PAGE_DIR_TABLE_POS
   add eax,0x2000               ;此时的eax为第二个页表的物理地址
   or eax,PG_US_U|PG_RW_W|PG_P  ;最低特权级|可读写|存在
   mov ebx,PAGE_DIR_TABLE_POS
   mov ecx,254
   mov esi,769
.create_kernel_pde:
   mov [ebx+esi*4],eax          ;将第2个~第256个页表的地址逐个存入页表项
   inc esi
   add eax,0x1000               ;下一个页表的地址
   loop .create_kernel_pde

   ret


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值