25、保护模式程序的动态加载和执行


上一节:24、存储器的保护
下一节:26、用户程序编程接口及其实现

01、本章目标和内容提要

在这里插入图片描述

02、内核结构和加载前的准备工作

内核的加载包含c13_mbr.asm全部和c13_core.asm一小部分。

c13_mbr.asm用于:

  • 从BIOS中接管处理器以及计算机硬件的控制权;
  • 安装最基本的段描述符;
  • 初始化最初的执行环境;
  • 然后从硬盘上读取和加载内核的剩余部分;
  • 创建组成内核的每一个内存段。

c13_core.asm中定义和内核的分段信息:

  • 第一个分段,内核的头部,用于记录内核总长度,每个段的相对位置、以及入口点信息,以上统计信息用于高速初始化代码如何加载内核;
  • 第二个分段,公共例程段,本质上是一个代码段,包括一些可以反复使用的子过程,比如显示字符串例程、硬盘读写例程、内存分配例程、描述符创建和安装例程。这些子过程可以为内核自己使用也可提供给用户程序使用;
  • 第三个分段,数据段,包括系统核心数据,供内核自己使用;
  • 第四个分段,代码段,包含进入内核之后首席那要执行的代码、以及用于内存分配、读取和加载用户程序、控制用户程序的代码。

程序从c13_mbr.asm中一步步执行即可,具体看视频和程序。此小节代码如下:

	;代码清单13-1
	;文件名:c13_mbr.asm
	;文件说明:硬盘主引导扇区代码 
	;创建日期:2011-10-28 22:35        ;设置堆栈段和栈指针 
	
	core_base_address equ 0x00040000   ;常数,内核加载的起始内存地址 
	core_start_sector equ 0x00000001   ;常数,内核的起始逻辑扇区号 
	
	mov ax,cs      
	mov ss,ax
	mov sp,0x7c00
	
	;计算GDT所在的逻辑段地址
	mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位物理地址 
	xor edx,edx
	mov ebx,16
	div ebx                            ;分解成16位逻辑地址 
	
	mov ds,eax                         ;令DS指向该段以进行操作
	mov ebx,edx                        ;段内起始偏移地址 
	
	;跳过0#号描述符的槽位 
	;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
	mov dword [ebx+0x08],0x0000ffff    ;基地址为0,段界限为0xFFFFF
	mov dword [ebx+0x0c],0x00cf9200    ;粒度为4KB,存储器段描述符 
	
	;创建保护模式下初始代码段描述符
	mov dword [ebx+0x10],0x7c0001ff    ;基地址为0x00007c00,界限0x1FF 
	mov dword [ebx+0x14],0x00409800    ;粒度为1个字节,代码段描述符 
	
	;建立保护模式下的堆栈段描述符         ;基地址为0x00007C00,界限0xFFFFE 
	mov dword [ebx+0x18],0x7c00fffe    ;粒度为4KB 
	mov dword [ebx+0x1c],0x00cf9600
	
	;建立保护模式下的显示缓冲区描述符   
	mov dword [ebx+0x20],0x80007fff    ;基地址为0x000B8000,界限0x07FFF 
	mov dword [ebx+0x24],0x0040920b    ;粒度为字节
	
	;初始化描述符表寄存器GDTR
	mov word [cs: pgdt+0x7c00],39      ;描述符表的界限   
	
	lgdt [cs: pgdt+0x7c00]
	
	in al,0x92                         ;南桥芯片内的端口 
	or al,0000_0010B
	out 0x92,al                        ;打开A20
	
	cli                                ;中断机制尚未工作
	
	mov eax,cr0
	or eax,1
	mov cr0,eax                        ;设置PE位
	
	;以下进入保护模式... ...
	jmp dword 0x0010:flush             ;16位的描述符选择子:32位偏移
	                                   ;清流水线并串行化处理器
	[bits 32]               
flush:                                  
	mov eax,0x0008                     ;加载数据段(0..4GB)选择子
	mov ds,eax
	
	mov eax,0x0018                     ;加载堆栈段选择子 
	mov ss,eax
	xor esp,esp                        ;堆栈指针 <- 0 
									   ;加载的是2个字节,此时ESP位置为:
									   ;    0 - 2 = 0xFFFFFFFE
									   ;加载的是4个字节,此时ESP位置为:
									   ;    0 - 4 = 0xFFFFFFFC
	
	;以下加载系统核心程序 
	mov edi,core_base_address 
	
	mov eax,core_start_sector
	mov ebx,edi                        ;起始地址 
	call read_hard_disk_0              ;以下读取程序的起始部分(一个扇区) 
	
	;以下判断整个程序有多大
	mov eax,[edi]                      ;核心程序尺寸
	xor edx,edx 
	mov ecx,512                        ;512字节每扇区
	div ecx
	
	or edx,edx
	jnz @1                             ;未除尽,因此结果比实际扇区数少1 
	dec eax                            ;已经读了一个扇区,扇区总数减1 
@1:
	or eax,eax                         ;考虑实际长度≤512个字节的情况 
	jz setup                           ;EAX=0 ?
	
	;读取剩余的扇区
	mov ecx,eax                        ;32位模式下的LOOP使用ECX
	mov eax,core_start_sector
	inc eax                            ;从下一个逻辑扇区接着读
@2:
	call read_hard_disk_0
	inc eax
	loop @2                            ;循环读,直到读完整个内核 
。。。
。。。

03、创建安装内核中各段的描述符

上一节中已经将内核全部读入内存,这一节是找到内核的每一个段为它们创建并安装描述符。在保护模式下,内核访问自己的段也需要通过描述符。

内核的段描述符安装在GDT中,之前已经使用lgdt指令加载了GDTR,在这里我们就需要为GDT安装新的描述符。那我们的任务就是从标号pgdt处取得GDT的基地址,为其添加新的描述符,并修改新的界线值,之后使用lgdt指令再次加载GDTR使其生效。

但是现在程序正处于保护模式,对代码段保护的意思就是在代码段中不能通过代码段描述符修改代码段中的内容。但是可以使用哪个指向全部4G字节空间的段进行修改。
在这里插入图片描述
如上图,左边大括号就是4G字节的段、左边小括号就是现在主引导程序所在的段,有重叠。

问:内核代码段的描述符还没有创建和安装,如何知道其选择子是什么?
答:内核是常驻内存的不会改变,内核在加载自己时会提前规划好每个段描述符的类型,以及他们在描述符表中的位置,这样一来就知道每个段选择子的具体数值,因此为了方便可以将每个段的选择子定义为常数。此时内存布局:
在这里插入图片描述

04、段描述符的创建和BSWAP指令

本节代码如下:

...
 setup:
         mov esi,[0x7c00+pgdt+0x02]         ;不可以在代码段内寻址pgdt,但可以
                                            ;通过4GB的段来访问
											;在编译期间计算,处理器默认使用
											;DS描述符高速缓存器中的32位线性基地址0 + 偏移
											;来访问内存
         ;建立公用例程段描述符
         mov eax,[edi+0x04]                 ;公用例程代码段起始汇编地址
         mov ebx,[edi+0x08]                 ;核心数据段汇编地址
         sub ebx,eax
         dec ebx                            ;公用例程段界限 
         add eax,edi                        ;公用例程段基地址
         mov ecx,0x00409800                 ;字节粒度的代码段描述符
         call make_gdt_descriptor
         mov [esi+0x28],eax
         mov [esi+0x2c],edx
...
...
;-------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值