概述
文章目录
- 01、本章目标和内容提要
- 02、内核结构和加载前的准备工作
- 03、创建安装内核中各段的描述符
- 04、段描述符的创建和BSWAP指令
- 05、进入内核执行
- 06、进入内核之后显示文本
- 07、用CPUID指令显示处理器品牌信息
- 08、准备加载用户程序
- 09、预读用户程序并得到它的大小
- 10、条件传送簇CMOVcc
- 11、计算以512字节为单位的用户程序总长度
- 12、内存分配的基本策略和方法
- 13、内存分配的简易实现过程
- 14、加载用户程序
- 15、准备安装用户程序的段描述符
- 16、用SGDT和MOVZX指令确定GDT的位置
- 17、安装新描述符并生成选择子
- 18、安装用户程序的段描述符并回填选择子
- 19、用户程序的执行和退出
- 20、虚拟机观察内核的加载和用户程序的执行与退出
上一节: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
...
...
;-------------------------------------------------------------------------------
make_gdt_descriptor: ;构造描述符
;输入:EAX=线性基地址
; EBX=段界限
; ECX=属性(各属性位都在原始
; 位置,其它没用到的位置0)
;返回:EDX:EAX=完整的描述符
mov edx,eax
shl eax,16
or ax,bx ;描述符前32位(EAX)构造完毕
and edx,0xffff0000 ;清除基地址中无关的位
rol edx,8
bswap edx ;装配基址的31~24和23~16 (80486+)
xor bx,bx
or edx,ebx ;装配段界限的高4位
or edx,ecx ;装配属性
ret
...
内存布局如下,使用的是4G字节的数据段从0开始,那么公共例程段的逻辑地址就是EDI
指定的地址。
使用make_gdt_descriptor
子程序构造描述符的低32位:
高32位的基地址
部分:
数据交换指令:
以字节为单位进行交换:
高32位的段界限
部分:
05、进入内核执行
本节代码:
。。。
;建立核心数据段描述符
mov eax,[edi+0x08] ;核心数据段起始汇编地址
mov ebx,[edi+0x0c] ;核心代码段汇编地址
sub ebx,eax
dec ebx ;核心数据段界限
add eax,edi ;核心数据段基地址
mov ecx,0x00409200 ;字节粒度的数据段描述符
call make_gdt_descriptor
mov [esi+0x30],eax
mov [esi+0x34],edx
;建立核心代码段描述符
mov eax,[edi+0x0c] ;核心代码段起始汇编地址
mov ebx,[edi+0x00] ;程序总长度
sub ebx,eax
dec ebx ;核心代码段界限
add eax,edi ;核心代码段基地址
mov ecx,0x00409800 ;字节粒度的代码段描述符
call make_gdt_descriptor
mov [esi+0x38],eax
mov [esi+0x3c],edx
mov word [0x7c00+pgdt],63 ;描述符表的界限
lgdt [0x7c00+pgdt]
jmp far [edi+0x10]
。。。
内核加载之后的GDT
布局:
4G字节段内存布局:
执行jmp far [edi+0x10]
指令时:处理器使用DS描述符高速缓存器中的基地址0
加上 指令中的有效偏移地址
生成线性地址去访问内存取出32位段内偏移
和16位段选择子
,接着用16位段选择子
到GDT
中取出段描述符
,经过检查之后加载到CS描述符高速缓存器
,同时将32位段内偏移
传送到指令指针寄存器EIP
,之后处理器就是使用CS描述符高速缓存器
中的基地址
加上 指令指针寄存器EIP
的偏移作为逻辑地址去执行指令,即到内核入口点
取指令并执行指令。
06、进入内核之后显示文本
进入内核之后显示文本信息,本节代码如下:
...
...
SECTION sys_routine vstart=0 ;系统公共例程代码段
;-------------------------------------------------------------------------------
;字符串显示例程
put_string: ;显示0终止的字符串并移动光标
;输入:DS:EBX=串地址
;DS保存字符串所在段的选择子
;EBX保存字符串在段内的偏移量
push ecx
.getc:
mov cl,[ebx]
or cl,cl
jz .exit
call put_char
inc ebx
jmp .getc
.exit:
pop ecx
retf ;段间返回
;-------------------------------------------------------------------------------
put_char: ;在当前光标处显示一个字符,并推进
;光标。仅用于段内调用
;输入:CL=字符ASCII码
pushad
;以下取当前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
in al,dx ;高字
mov ah,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
in al,dx ;低字
mov bx,ax ;BX=代表光标位置的16位数
cmp cl,0x0d ;回车符?
jnz .put_0a
mov ax,bx
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other
add bx,80
jmp .roll_screen
.put_other: ;正常显示字符
push es
mov eax,video_ram_seg_sel ;0xb8000段的选择子
mov es,eax
shl bx,1
mov [es:bx],cl
pop es
;以下将光标位置推进一个字符
shr bx,1
inc bx
.roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor
push ds
push es
mov eax,video_ram_seg_sel
mov ds,eax
mov es,eax
cld
mov esi,0xa0 ;小心!32位模式下movsb/w/d
mov edi,0x00 ;使用的是esi/edi/ecx
mov ecx,1920
rep movsd
mov bx,3840 ;清除屏幕最底一行
mov ecx,80 ;32位程序应该使用ECX
.cls:
mov word[es:bx],0x0720
add bx,2
loop .cls
pop es
pop ds
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
inc dx ;0x3d5
mov al,bh
out dx,al
dec dx ;0x3d4
mov al,0x0f
out dx,al
inc dx ;0x3d5
mov al,bl
out dx,al
popad
ret
...
...
...
start:
mov ecx,core_data_seg_sel ;使ds指向核心数据段
mov ds,ecx
mov ebx,message_1
call sys_routine_seg_sel:put_string ;直接绝对远调用
...
进入strart
标号之后,先将内核自己的数据段选择子core_data_seg_sel
传送给ds
,之后将文本信息的标号message_1
传送给ebx
,之后使用call
指令进行过程调用,处理器将段寄存器CS
和指令指针寄存器EIP(下一条指令的有效地址)
压栈保存,使用指令中的系统公共例程代码段的选择子sys_routine_seg_sel
取得描述符,经过检查之后加载到CS描述符高速缓存器
,同时使用指令中的偏移量put_string
改变指令指针寄存器EIP
,这样就转到目标过程内部执行。
其中movsd
指令的操作和当前默认操作尺寸相关:
1、当前操作尺寸是16位,源操作数由DS : SI
指定、目的操作数由ES : DI
指定;
2、当前操作尺寸是32位,源操作数由DS : ESI
指定、目的操作数由ES : EDI
指定;
且由于当前是在32位操作尺寸下,所以重复传送的次数由ECX
指定。
07、用CPUID指令显示处理器品牌信息
cpuid(CPU identification)
指令用于返回处理器的标识和特性信息,需要使用EAX
指定要返回什么样的信息,指定功能号。
cpuid
指令是在80486
后期版本引入,在执行cpuid
指令之前,要检测处理器是否支持该指令:
EFLAGS
的位21
是ID
位,为0
表示不支持cpuid
指令,为1
表示支持cpuid
指令。
本节代码如下:
。。。
。。。
cpu_brnd0 db 0x0d,0x0a,' ',0
cpu_brand times 52 db 0
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
。。。
。。。
;显示处理器品牌信息
mov eax,0x80000002
cpuid
mov [cpu_brand + 0x00],eax
mov [cpu_brand + 0x04],ebx
mov [cpu_brand + 0x08],ecx
mov [cpu_brand + 0x0c],edx
mov eax,0x80000003
cpuid
mov [cpu_brand + 0x10],eax
mov [cpu_brand + 0x14],ebx
mov [cpu_brand + 0x18],ecx
mov [cpu_brand + 0x1c],edx
mov eax,0x80000004
cpuid
mov [cpu_brand + 0x20],eax
mov [cpu_brand + 0x24],ebx
mov [cpu_brand + 0x28],ecx
mov [cpu_brand + 0x2c],edx
mov ebx,cpu_brnd0
call sys_routine_seg_sel:put_string
mov ebx,cpu_brand
call sys_routine_seg_sel:put_string
mov ebx,cpu_brnd1
call sys_routine_seg_sel:put_string
。。。
。。。
08、准备加载用户程序
接上一节,加载用户程序:
。。。
。。。
message_5 db ' Loading user program...',0
do_status db 'Done.',0x0d,0x0a,0
。。。
。。。
mov ebx,message_5
call sys_routine_seg_sel:put_string
mov esi,50 ;用户程序位于逻辑50扇区
call load_relocate_program
。。。
。。。
接下来主要讲解了用户程序的构造,由什么段组成,具体有什么等等,看代码c13.asm
即可。
09、预读用户程序并得到它的大小
具体看代码c13.asm
即可,接上一节,本节主要加载了用户程序的第一个扇区,代码如下:
。。。
;===============================================================================
SECTION core_code vstart=0
;-------------------------------------------------------------------------------
load_relocate_program: ;加载并重定位用户程序
;输入:ESI=起始逻辑扇区号
;返回:AX=指向用户程序头部的选择子
push ebx
push ecx
push edx
push esi
push edi
push ds
push es
mov eax,core_data_seg_sel
mov ds,eax ;切换DS到内核数据段
mov eax,esi ;读取程序头部数据
;将ESI中的逻辑扇区号传送给EAX
mov ebx,core_buf
call sys_routine_seg_sel:read_hard_disk_0
。。。
10、条件传送簇CMOVcc
mov
指令传统方式:会影响流水线效率
使用CMOVcc
指令简化传送指令:ne
表示Not Equal
Intel
中CMOVcc
指令相关文档:
条件传送指令是从P6
家族引入的,使用下列指令查看处理器是否支持:
信息将返回到EBX、ECX、EDX
,在EDX
的位15为0
表示不支持条件传送指令、为1
表示支持。
11、计算以512字节为单位的用户程序总长度
在二进制中,512整数倍的数,低9位均为0。
具体看代码c13.asm
即可,接上一节,代码如下:
。。。
;以下判断整个程序有多大
mov eax,[core_buf] ;程序尺寸
mov ebx,eax
and ebx,0xfffffe00 ;使之512字节对齐(能被512整除的数,
add ebx,512 ;低9位都为0
test eax,0x000001ff ;程序的大小正好是512的倍数吗?
cmovnz eax,ebx ;不是。使用凑整的结果
。。。
12、内存分配的基本策略和方法
操作系统必须记录所有可以分配的物理内存,当一个程序要求分配内存时,内存管理程序从可分配的内存中切割出一部分将其标记为已使用。已分配的内存在使用之后还需要负责回收它们,将其标记为空闲以便再次分配。
当内存空间紧张时,内存管理程序还需要查找那些很久未使用的程序,将其移出到硬盘上,腾出空间给需要使用内存的程序使用。下次用到这些内存时再将其加载到内存中。这就是虚拟内存管理
。
现在的内存布局:
若一次分配512
字节内存,从0x00100000
处开始,则下一次从0x00100200
处开始分配。
13、内存分配的简易实现过程
具体代码看c13_core0.asm
,本节代码如下:
。。。
。。。
;-------------------------------------------------------------------------------
allocate_memory: ;分配内存
;输入:ECX=希望分配的字节数
;输出:ECX=起始线性地址
push ds
push eax
push ebx
mov eax,core_data_seg_sel
mov ds,eax
mov eax,[ram_alloc]
add eax,ecx ;下一次分配时的起始地址
;这里应当有检测可用内存数量的指令
mov ecx,[ram_alloc] ;返回分配的起始地址
mov ebx,eax ;下面这些指令使内存按照4字节对齐
and ebx,0xfffffffc
add ebx,4 ;强制对齐
test eax,0x00000003 ;下次分配的起始地址最好是4字节对齐
cmovnz eax,ebx ;如果没有对齐,则强制对齐
mov [ram_alloc],eax ;下次从该地址分配内存
;cmovcc指令可以避免控制转移
pop ebx
pop eax
pop ds
retf
。。。
。。。
14、加载用户程序
具体代码看c13_core0.asm
,本节代码如下,主要时申请内存之后使用loop
指令循环读取用户程序。
。。。
。。。
mov ecx,eax ;实际需要申请的内存数量
call sys_routine_seg_sel:allocate_memory
mov ebx,ecx ;ebx -> 申请到的内存首地址
push ebx ;保存该首地址
xor edx,edx
mov ecx,512
div ecx
mov ecx,eax ;总扇区数
mov eax,mem_0_4_gb_seg_sel ;切换DS到0-4GB的段
mov ds,eax
mov eax,esi ;起始扇区号
.b1:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b1 ;循环读,直到读完整个用户程序
。。。
。。。
15、准备安装用户程序的段描述符
本节代码如下:
。。。
;建立程序头部段描述符
pop edi ;恢复程序装载的首地址
mov eax,edi ;程序头部起始线性地址
mov ebx,[edi+0x04] ;段长度
dec ebx ;段界限
mov ecx,0x00409200 ;字节粒度的数据段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+0x04],cx
。。。
使用make_seg_descriptor
创建描述符、使用set_up_gdt_descriptor
安装描述符。
其中头部段描述符:
- G位:为0表示段界限以字节为单位
- P位:为1表示段存在
- S位:为表示这是一个存储器的段描述符
- X位:为0表示这是一个
数据段
- E位:为0表示向上扩展
- W位:为0表示可读可写
16、用SGDT和MOVZX指令确定GDT的位置
sgdt m
指令:将GDTR
的内容保存到内存地址m
处
movzx
指令,扩展指令:使用0进行扩展。
movsx
:使用符号位进行扩展:
本节代码如下:
。。。
。。。
;-------------------------------------------------------------------------------
set_up_gdt_descriptor: ;在GDT内安装一个新的描述符
;输入:EDX:EAX=描述符
;输出:CX=描述符的选择子
push eax
push ebx
push edx
push ds
push es
mov ebx,core_data_seg_sel ;切换到核心数据段
mov ds,ebx
sgdt [pgdt] ;以便开始处理GDT
mov ebx,mem_0_4_gb_seg_sel
mov es,ebx
movzx ebx,word [pgdt] ;GDT界限
inc bx ;GDT总字节数,也是下一个描述符偏移
add ebx,[pgdt+2] ;下一个描述符的线性地址
。。。
。。。
17、安装新描述符并生成选择子
接上一节:
mov [es:ebx],eax
mov [es:ebx+4],edx
add word [pgdt],8 ;增加一个描述符的大小
lgdt [pgdt] ;对GDT的更改生效
mov ax,[pgdt] ;得到GDT界限值
xor dx,dx
mov bx,8
div bx ;除以8,去掉余数,就是描述符的选择子
mov cx,ax
shl cx,3 ;将索引号移到正确位置
pop es
pop ds
pop edx
pop ebx
pop eax
retf
。。。
。。。
安装了新的描述符之后使用lgdt [pgdt]
指令使得新描述符生效,还需要生成它的选择子。
使用段的界线值 / 8
就是对应的索引号。
18、安装用户程序的段描述符并回填选择子
代码如下:
;建立程序头部段描述符
pop edi ;恢复程序装载的首地址
mov eax,edi ;程序头部起始线性地址
mov ebx,[edi+0x04] ;段长度
dec ebx ;段界限
mov ecx,0x00409200 ;字节粒度的数据段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
;这条指令就是回填选择子
mov [edi+0x04],cx
回填选择子:
之后用户程序头部的0x04
地址处就是选择子
,而且只用到了低32位,高32位还是原来的值。
接着创建代码段描述符:
;建立程序代码段描述符
mov eax,edi
add eax,[edi+0x0c] ;代码起始线性地址
mov ebx,[edi+0x10] ;段长度
dec ebx ;段界限
mov ecx,0x00409800 ;字节粒度的代码段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+0x0c],cx
之后就进行安装和加载描述符,后面还有数据段和栈段描述符:
。。。
。。。
;建立程序数据段描述符
mov eax,edi
add eax,[edi+0x14] ;数据段起始线性地址
mov ebx,[edi+0x18] ;段长度
dec ebx ;段界限
mov ecx,0x00409200 ;字节粒度的数据段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+0x14],cx
;建立程序堆栈段描述符
mov eax,edi
add eax,[edi+0x1c] ;数据段起始线性地址
mov ebx,[edi+0x20] ;段长度
dec ebx ;段界限
mov ecx,0x00409200 ;字节粒度的数据段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+0x1c],cx
。。。
。。。
19、用户程序的执行和退出
从load_relocate_program
程序返回之前将用户程序头部段
的选择子
保存在AX
中。
;===============================================================================
SECTION core_code vstart=0
;-------------------------------------------------------------------------------
load_relocate_program: ;加载并重定位用户程序
;输入:ESI=起始逻辑扇区号
;返回:AX=指向用户程序头部的选择子
push ebx
push ecx
push edx
push esi
push edi
push ds
push es
。。。
。。。
。。。
mov ax,[edi+0x04]
pop es ;恢复到调用此过程前的es段
pop ds ;恢复到调用此过程前的ds段
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret
从load_relocate_program
程序返回内核程序:
...
...
call load_relocate_program
mov ebx,do_status
call sys_routine_seg_sel:put_string
mov [esp_pointer],esp ;临时保存堆栈指针
mov ds,ax ;从load_relocate_program程序返回时,用户程序头部段选择子存在AX中
call far [0x08] ;控制权交给用户程序(入口点)
;堆栈可能切换
...
...
这条call far [0x08]
指令执行时,处理器用段寄存器DS
访问用户程序头部段,从偏移为8的地方取出32位偏移量传送到指令指针EIP
,再取出16位段选择子传送到段寄存器K,这样处理器进入用户程序内部执行。下面是用户程序start
代码:
。。。
。。。
SECTION code vstart=0
start:
mov eax,ds ;此时DS指向用户程序头部段
mov fs,eax ;但是我们不能失去对DS的追踪,所以使用其他FS段寄存器重新设置DS
;mov eax,[stack_seg]
;mov ss,eax
;mov esp,stack_end
;mov eax,[data_seg]
;mov ds,eax
;用户程序要做的事情(省略)
retf ;将控制权返回到系统
code_end:
。。。
。。。
从用户程序返回到内核代码段:
。。。
。。。
call far [0x08] ;控制权交给用户程序(入口点)
;堆栈可能切换
return_point: ;用户程序返回点
mov eax,core_data_seg_sel ;使ds指向核心数据段
mov ds,eax
mov eax,core_stack_seg_sel ;切换回内核自己的堆栈
mov ss,eax
mov esp,[esp_pointer]
mov ebx,message_6
call sys_routine_seg_sel:put_string
;这里可以放置清除用户程序各种描述符的指令
;也可以加载并启动其它程序
hlt ;在主引导程序进入保护模式之前使用cli指令清除了中断,
;所以在是哟个hlt指令停机之后处理器不在会被中断唤醒
。。。
。。。
20、虚拟机观察内核的加载和用户程序的执行与退出
主引导程序:0、内核程序:1、用户程序:50
Virtual Box
虚拟机允运行:
Bochs
虚拟机运行:
可以观察EFLAGS
、GDT
、内核程序头部和用户程序头部内容、栈的变化。
上一节:24、存储器的保护
下一节:26、用户程序编程接口及其实现
最后
以上就是干净魔镜为你收集整理的25、保护模式程序的动态加载和执行01、本章目标和内容提要02、内核结构和加载前的准备工作03、创建安装内核中各段的描述符04、段描述符的创建和BSWAP指令05、进入内核执行06、进入内核之后显示文本07、用CPUID指令显示处理器品牌信息08、准备加载用户程序09、预读用户程序并得到它的大小10、条件传送簇CMOVcc11、计算以512字节为单位的用户程序总长度12、内存分配的基本策略和方法13、内存分配的简易实现过程14、加载用户程序15、准备安装用户程序的段描述符16、用SGDT和MOV的全部内容,希望文章能够帮你解决25、保护模式程序的动态加载和执行01、本章目标和内容提要02、内核结构和加载前的准备工作03、创建安装内核中各段的描述符04、段描述符的创建和BSWAP指令05、进入内核执行06、进入内核之后显示文本07、用CPUID指令显示处理器品牌信息08、准备加载用户程序09、预读用户程序并得到它的大小10、条件传送簇CMOVcc11、计算以512字节为单位的用户程序总长度12、内存分配的基本策略和方法13、内存分配的简易实现过程14、加载用户程序15、准备安装用户程序的段描述符16、用SGDT和MOV所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复