1.主引导程序
一开始是实模式,这里切换到保护模式
; 职责:
; 1.完成实模式到保护模式切换
; 2.安装保护模式下4个基础段描述符
; 3.将指定位置物理磁盘程序加载到指定物理内存位置
; 4.为加载的程序构造&安装3个段描述符
; 5.将执行流程跳转到加载的程序
core_base_address equ 0x00040000
core_start_sector equ 0x00000001
; 刚刚进入主引导程序时候
; 进程位于物理内存0x0000:0x7c00处,cs的内容是0x0000
mov ax,cs
mov ss,ax
mov sp,0x7c00
; gdt区域起始地址
mov eax,[cs:pgdt+0x7c00+0x02]
xor edx,edx
mov ebx,16
div ebx
; ds:ebx就是gdt区域起始位置
mov ds,eax
mov ebx,edx
; gdt中索引1
; 段基地址为0x0000 0000
; 段内的数据是32位的
; 4GB尺寸
; 可读,可写的数据段
mov dword [ebx+0x08],0x0000ffff
mov dword [ebx+0x0c],0x00cf9200
; gdt中索引2
; 段基地址0x0000 7c00
; 段尺寸512
; 段是只执行的代码段
; 引导程序作为一个任务需要在gdt中为自己安装代码段
mov dword [ebx+0x10],0x7c0001ff
mov dword [ebx+0x14],0x00409800
; gdt中索引3
; 段基地址0x0000 7c00
; 段尺寸4KB
; 段是可读,可写的栈段
; 引导程序作为一个任务需要在gdt中为自己安装栈段
mov dword [ebx+0x18],0x7c00fffe
mov dword [ebx+0x1c],0x00cf9600
; gdt中索引4
; 段基地址为0x000b 8000
; 段尺寸0x8000
; 可读,可写的数据段
; 需要在gdt中为显存区域安装一个显存段
mov dword [ebx+0x20],0x80007fff
mov dword [ebx+0x24],0x0040920b
; gdt界限=gdt尺寸-1
mov word [cs: pgdt+0x7c00],39
; gdt界限,基地址传递到处理器
lgdt [cs: pgdt+0x7c00]
in al,0x92
or al,0000_0010B
out 0x92,al
cli
mov eax,cr0
or eax,1
mov cr0,eax
; 保护模式下,0x0010:flush解释为
; 0x0010得到RPL为0,位于gdt中,索引为2的8字节描述符描述的段
; 保护模式下,段选择子开始起作用
jmp dword 0x0010:flush
[bits 32]
flush:
mov eax,0x0008
; 保护模式下,段选择子起作用,4GB数据段选择子
mov ds,eax
mov eax,0x0018
; 栈段选择子
mov ss,eax
xor esp,esp
; ss:esp构成初始时刻栈指针位置(位于栈区域地址高位置)
mov esp, 0xffffffff
mov edi,core_base_address
mov eax,core_start_sector
mov ebx,edi
; 从磁盘扇区读取一个扇区到物理内存
call read_hard_disk_0
; 内核任务头4字节是内核整体尺寸
mov eax,[edi]
xor edx,edx
mov ecx,512
div ecx
or edx,edx
jnz @1
dec eax
@1:
; eax为内核任务剩余扇区数
or eax,eax
jz setup
mov ecx,eax
mov eax,core_start_sector
inc eax
@2:
call read_hard_disk_0
inc eax
loop @2
; 此时内核任务【操作系统】已经全部拷贝到了物理内存
setup:
mov esi,[0x7c00+pgdt+0x02]
; 操作系统的
; 内核任务例程段偏移
mov eax,[edi+0x04]
mov ebx,[edi+0x08]
sub ebx,eax
; 历程段界限
dec ebx
; 这样得到内核例程段在内存起始位置
add eax,edi
mov ecx,0x00409800
; 构造段描述符
call make_gdt_descriptor
; 安装到gdt区域
mov [esi+0x28],eax
mov [esi+0x2c],edx
; 内核任务数据段
mov eax,[edi+0x08]
mov ebx,[edi+0x0c]
sub ebx,eax
dec ebx
add eax,edi
mov ecx,0x00409200
; 构造段描述符,安装到gdt
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]
; 借助gdt中的代码段选择子,段内偏移实现控制跳转
jmp far [edi+0x10]
; 传入:eax是磁盘起始逻辑扇区
; 传入:ds:ebx构成存放磁盘扇区起始物理内存位置
; 传出:ds:ebx磁盘扇区物理内存尾后位置
read_hard_disk_0:
push eax
push ecx
push edx
push eax
mov dx,0x1f2
mov al,1
out dx,al
inc dx
pop eax
out dx,al
inc dx
mov cl,8
shr eax,cl
out dx,al
inc dx
shr eax,cl
out dx,al
inc dx
shr eax,cl
or al,0xe0
out dx,al
inc dx
mov al,0x20
out dx,al
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits
mov ecx,256
mov dx,0x1f0
.readw:
in ax,dx
mov [ebx],ax
add ebx,2
loop .readw
pop edx
pop ecx
pop eax
ret
; 依据传入构造8位edx:eax描述符
make_gdt_descriptor:
mov edx,eax
shl eax,16
or ax,bx
and edx,0xffff0000
rol edx,8
bswap edx
xor bx,bx
or edx,ebx
or edx,ecx
ret
pgdt dw 0
dd 0x00007e00
times 510-($-$$) db 0
db 0x55,0xaa
2.内核
保护模式下,开启分页机制(虚拟内存管理),多任务机制,基于调用门调用例程,特权级
core_code_seg_sel equ 0x38
core_data_seg_sel equ 0x30
sys_routine_seg_sel equ 0x28
video_ram_seg_sel equ 0x20
core_stack_seg_sel equ 0x18
mem_0_4_gb_seg_sel equ 0x08
core_length dd core_end
; 这里的偏移信息将帮助引导程序为这些段构建描述符
; 并将描述符安装到gdt中
sys_routine_seg dd section.sys_routine.start
core_data_seg dd section.core_data.start
core_code_seg dd section.core_code.start
core_entry dd start
dw core_code_seg_sel
[bits 32]
SECTION sys_routine vstart=0
; ds:ebx待显示字符串起始位置
put_string:
push ecx
.getc:
mov cl,[ebx]
or cl,cl
jz .exit
call put_char
inc ebx
jmp .getc
.exit:
pop ecx
retf
put_char:
pushad
mov dx,0x3d4
mov al,0x0e
; 向0x3d4写入0x0e
out dx,al
inc dx
; 从0x3d5读出1字节到al
in al,dx
; 这是16位光标位置高8位
mov ah,al
dec dx
mov al,0x0f
; 向0x3d4写入0x0f
out dx,al
inc dx
; 从0x3d5读出1字节到al
in al,dx
; 16位光标位置存储到bx
mov bx,ax
; 如果c1是0x0d
cmp cl,0x0d
jnz .put_0a
mov ax,bx
mov bl,80
div bl
mul bl
; 将bx设置为光标所在行行首的位置
mov bx,ax
; 设置光标位置实现回车
jmp .set_cursor
.put_0a:
; c1是否为0x0a
cmp cl,0x0a
jnz .put_other
; 将光标位置设置为下一行对应位置
add bx,80
; 每次光标位置前进时,都要经过滚屏处理。
; 滚屏处理是先判断光标位置是否超出一屏范围。
; 超出时,滚屏,然后将光标设置为最后一行行首。
; 没超出,则什么都不做。
jmp .roll_screen
.put_other:
; 寄存器入栈
push es
mov eax,video_ram_seg_sel
; 显存段
mov es,eax
; 光标位置*2=对应位置字符在显存位置
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
mov edi,0x00
mov ecx,1920
rep movsd
mov bx,3840
mov ecx,80
.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
; 向0x3d4写入0x0e
out dx,al
inc dx
mov al,bh
; 向0x3d5写入1字节,这是16位光标位置高8位
out dx,al
dec dx
mov al,0x0f
out dx,al
inc dx
mov al,bl
; 向0x3d5写入1字节,这是16位光标位置低8位
out dx,al
; 一系列寄存器恢复
popad
; 流程恢复
ret
; eax存储28位逻辑扇区
; ds:ebx用于接受磁盘扇区物理内存位置
; 过程结束后ds:ebx指向区域尾后位置
read_hard_disk_0:
push eax
push ecx
push edx
push eax
mov dx,0x1f2
mov al,1
; 向0x1f2写入1,这时本次磁盘传输的块个数
; 磁盘块是512字节
out dx,al
inc dx
pop eax
; 向0x1f3写入1字节,这是28位逻辑扇区最低8位
out dx,al
inc dx
mov cl,8
shr eax,cl
; 向0x1f4写入1字节,这是28位逻辑扇区次低8位
out dx,al
inc dx
shr eax,cl
; 向0x1f5写入1字节,这是28位逻辑扇区次次低8位
out dx,al
inc dx
shr eax,cl
or al,0xe0
; 向0x1f6写入1字节。低4位是28位逻辑扇区高4位。高4位1110
; 用于解释逻辑扇区号,写入对象是主盘
out dx,al
inc dx
mov al,0x20
; 向0x1f7写入0x20
; 这样磁盘开始数据传输工作
out dx,al
.waits:
; 从0x1f7读取1字节
in al,dx
and al,0x88
cmp al,0x08
; 持续等,直到磁盘不忙且准备好了数据传输
jnz .waits
mov ecx,256
mov dx,0x1f0
.readw:
; 从0x1f0读取2字节
; 这实际上是顺序读取2字节内容。两字节来自磁盘已经准备好的数据。
in ax,dx
; 将数据顺序写入物理内存ds:ebx
mov [ebx],ax
add ebx,2
loop .readw
; 寄存器恢复
pop edx
pop ecx
pop eax
; 过程返回
retf
; 格式化显示
; 将edx中存储的32位数值按16进制输出显示
put_hex_dword:
; 一系列寄存器入栈
pushad
; 寄存器入栈
push ds
mov ax,core_data_seg_sel
; 内核数据段
mov ds,ax
mov ebx,bin_hex
mov ecx,8
.xlt:
rol edx,4
mov eax,edx
; 仅仅保留最低4位
and eax,0x0000000f
xlat
push ecx
; al是对应的字符
mov cl,al
call put_char
pop ecx
loop .xlt
pop ds
popad
retf
; 将构造好的edx:eax写入gdt。
; cx带回新写入描述符的选择子
set_up_gdt_descriptor:
; 寄存器入栈
push eax
push ebx
push edx
push ds
push es
mov ebx,core_data_seg_sel
mov ds,ebx
; 得到处理器在用的gdt的界限,基地址
sgdt [pgdt]
mov ebx,mem_0_4_gb_seg_sel
mov es,ebx
; 这是gdt界限
movzx ebx,word [pgdt]
; 这是gdt尺寸
inc bx
; 这是gdt区域尾后位置
add ebx,[pgdt+2]
; 向gdt尾部写入8字节,就是新的描述符
mov [es:ebx],eax
mov [es:ebx+4],edx
add word [pgdt],8
; 再次通知处理器新的gdt界限,基地址
lgdt [pgdt]
mov ax,[pgdt]
xor dx,dx
mov bx,8
div bx
mov cx,ax
; 这是新写入描述符的选择子
shl cx,3
pop es
pop ds
pop edx
pop ebx
pop eax
retf
; 段基地址31~24 G D/B L AVL 段界限19~16 P DPL S TYPE 段基地址23~16
; 段基地址15~0 段界限15~0
; 传入:eax段基地址,ebx段界限,ecx属性信息
; 传出:edx:eax 8位描述符 段基地址15~0 段界限15~0
make_seg_descriptor:
; edx
mov edx,eax
; 高16位是原来低16位
shl eax,16
; 低16位是ebx低16位
or ax,bx
; eax已经全部设置完毕
; edx高16位保存--段基地址31~24
and edx,0xffff0000
; 高8位是高16位低8位,低8位是高16位高8位。其余是0
rol edx,8
; 高8位是高16位高8位,低8位是高16位低8位
bswap edx
xor bx,bx
; ebx本身只有低20位有值,上面又把低16位置0,所以这里ebx只有低20位的高4位有效
; 这个or把这个高4位设置到edx
or edx,ebx
; 把edx剩余部分用属性位填充
or edx,ecx
retf
; 通过调用门执行控制转移相比通过段选择子:段内偏移方式
; 可以在描述符中额外指定特权级,参数个数等信息
; 构造门描述符
; 段内偏移31~16 P DPL 0 TYPE(1100) 000 参数个数(5位)
; 例程所在代码段选择子 段内偏移15~0
; edx:eax
; eax:32位段偏移
; ebx:16位例程所在代码段选择子
; cx:16位属性位
make_gate_descriptor:
push ebx
push ecx
mov edx,eax
; edx保留eax高16位
and edx,0xffff0000
; edx低16位完全按照cx来
or dx,cx
; eax保留低16位
and eax,0x0000ffff
; ebx用高16位保存原来低16位内容
shl ebx,16
; 高16位存储代码段选择子,低16位存储段内偏移低16位
or eax,ebx
pop ecx
pop ebx
retf
; 物理页分配
; 分配的物理页基地址应该传送出来
; eax返回时包含了刚刚分配的物理页的起始物理地址
allocate_a_4k_page:
push ebx
push ecx
push edx
push ds
mov eax,core_data_seg_sel
mov ds,eax
xor eax,eax
.b1:
; ds:page_bit_map提供区域起始位置
; 从该位置开始第eax位内容传入CF,eax位被设置为1
bts [page_bit_map],eax
; cf为0,则跳转
jnc .b2
; 前进1位
inc eax
; 比较
cmp eax,page_map_len*8
; 小于,跳转到b1
jl .b1
; 这样表示搜索了所有可用物理页,找不到空闲页
mov ebx,message_3
call sys_routine_seg_sel:put_string
hlt
.b2:
; 这意味着完成了一个4k页的分配
; eax返回时包含了刚刚分配的物理页的起始物理地址
shl eax,12
pop ds
pop edx
pop ecx
pop ebx
ret
; ds:ebx是要划分虚拟内存区域起始位置
; 为4kb虚拟区域分配了物理页,且在页目录表-页表中进行了虚拟页-物理页的登记&注册
; 依据ds:ebx线性地址找到页目录表中对应项
; 1.对应项存在,找到对应项指向的页表
; 寻找并分配物理页,在页表对应项登记物理页
; 2.对应项不存在,寻找并分配物理页,物理页初始化
; 在页目录表对应项登记页表。
; 寻找并分配物理页,在页表对应项登记物理页
alloc_inst_a_page:
; 寄存器入栈
push eax
push ebx
push esi
push ds
mov eax,mem_0_4_gb_seg_sel
mov ds,eax
; ds:ebx是要划分虚拟内存区域起始位置
mov esi,ebx
; 高10位保留,其余位设置为0
and esi,0xffc00000
; 20位全0 高10位 00
shr esi,20
; 20位全1 高10位 00
or esi,0xfffff000
; ds:esi
; 高10位 0b 1111 1111 11,作为索引访问页目录表,得到物理地址指向页目录表,但被看成页表
; 中间10位 0b 1111 1111 11,作为索引访问页表,得到物理地址指向页目录表,但被看成物理页
; 第12位作为页内偏移。取出的4字节是以esi原始高10位为索引得到的页目录表里的项
; esi原始高10位代表了这个线性地址关联的页目录表的项。
test dword [esi],0x00000001
; 项最低位不是1,跳转到b1
jnz .b1
; 项最低位是0,表示原始esi线性地址关联的页表不存在于物理内存。
; 准备4kb物理页用作传入esi对应线性地址关联的页表
; eax返回时将包含刚刚分配的物理页的起始物理地址
call allocate_a_4k_page
; 低3位设置为111,其余位保持不变
or eax,0x00000007
; ds:esi在页目录表里找到传入esi对应线性地址关联索引项
; 设置索引项的内容,以便索引项可以指向关联页表
mov [esi],eax
.b1:
mov esi,ebx
; 高10位全0 高22位
shr esi,10
; 高10位全0 高10位 低12位全0
and esi,0x003ff000
; 高10位全1 高10位 低12位全0
or esi,0xffc00000
; ebx是要分配虚拟内存区域起始地址(虚拟地址)
; 高10位全0 中间10位 低12位全0
and ebx,0x003ff000
; 高10位全0 10位全0 中间10位 00
shr ebx,10
; 高10位全1 高10位 中间10位 00
or esi,ebx
; 分配4kb物理页,物理页起始地址存在于eax
call allocate_a_4k_page
or eax,0x00000007
; ds:esi
; 高10位全1作为索引项访问"页目录表",得到物理地址指向页目录表(当作页表)
; 次高10位是线性地址高10位作为索引项,访问"页表",得到物理地址指向线性地址关联页表(当作物理页)
; 低12位是线性地址中间10位+00,访问"物理页",得到4字节,这4字节是线性地址在页表里面的项
; 项的内容是线性地址所在的物理页起始位置(含属性位)
; 这样就为4kb虚拟区域分配了物理页,且在页目录表-页表中进行了虚拟页-物理页的登记&注册
; 这样此后,分配的虚拟区域就可被使用和访问了
mov [esi],eax
pop ds
pop esi
pop ebx
pop eax
retf
; 分配一个物理页
; 将页目录表内容拷贝到新的物理页
; 返回新物理页起始地址eax
create_copy_cur_pdir:
; 寄存器入栈
push ds
push es
push esi
push edi
push ebx
push ecx
mov ebx,mem_0_4_gb_seg_sel
mov ds,ebx
mov es,ebx
; 分配一个物理页,以便作为新的页目录表
call allocate_a_4k_page
mov ebx,eax
or ebx,0x00000007
; 0xffff fff8
; 0x1111 1111 11线性地址对应页目录表中最后一项,该项中物理地址指向是页目录表
; 0x1111 1111 11线性地址对应页表中最后一项,该项中物理地址指向的是页目录表
; 0xff8,线性地址对应物理页中偏移为0xff8处,0xff8/4=0x1111 1111 10=2^10 - 1 - 1=1022
; 如果物理页都是4字节的项,这个偏移指向倒数第2项
; 倒数第二项设置为指向刚刚分配的4kb物理页
mov [0xfffffff8],ebx
mov esi,0xfffff000
mov edi,0xffffe000
mov ecx,1024
cld
; 每次拷贝4字节
; ds:esi--->es:edi
; 0xfffff000是在用的页目录表起始线性地址
; 0xffffe000是刚刚新分配4kb物理页的起始线性地址
repe movsd
; 上述完成了将在用的页目录表1024项逐个拷贝到新的4kb物理页
; 寄存器恢复
pop ecx
pop ebx
pop edi
pop esi
pop es
pop ds
retf
; 将控制从用户任务返回到内核任务
terminate_current_task:
mov eax,core_data_seg_sel
mov ds,eax
pushfd
pop edx
test dx,0100_0000_0000_0000B
jnz .b1
; 通过jmp进入用户任务的,通过jmp返回到内核任务
jmp far [program_man_tss]
.b1:
; 通过call 用户任务tss基地址 进入用户任务的,通过iretd返回到内核任务
iretd
sys_routine_end:
SECTION core_data vstart=0
pgdt dw 0
dd 0
page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
page_map_len equ $-page_bit_map
salt:
salt_1 db '@PrintString'
times 256-($-salt_1) db 0
dd put_string
dw sys_routine_seg_sel
salt_2 db '@ReadDiskData'
times 256-($-salt_2) db 0
dd read_hard_disk_0
dw sys_routine_seg_sel
salt_3 db '@PrintDwordAsHexString'
times 256-($-salt_3) db 0
dd put_hex_dword
dw sys_routine_seg_sel
salt_4 db '@TerminateProgram'
times 256-($-salt_4) db 0
dd terminate_current_task
dw sys_routine_seg_sel
salt_item_len equ $-salt_4
salt_items equ ($-salt)/salt_item_len
message_0 db ' Working in system core,protect mode.'
db 0x0d,0x0a,0
message_1 db ' Paging is enabled.System core is mapped to'
db ' address 0x80000000.',0x0d,0x0a,0
message_2 db 0x0d,0x0a
db ' System wide CALL-GATE mounted.',0x0d,0x0a,0
message_3 db '********No more pages********',0
message_4 db 0x0d,0x0a,' Task switching...@_@',0x0d,0x0a,0
message_5 db 0x0d,0x0a,' Processor HALT.',0
bin_hex db '0123456789ABCDEF'
core_buf times 512 db 0
cpu_brnd0 db 0x0d,0x0a,' ',0
cpu_brand times 52 db 0
cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
; 指向tcb链表首个元素线性地址
tcb_chain dd 0
core_next_laddr dd 0x80100000
program_man_tss dd 0
; 存储内核tss段选择子
dw 0
core_data_end:
SECTION core_code vstart=0
; 在用户任务ldt区域安装新的描述符
; cx是安装描述符的选择子
fill_descriptor_in_ldt:
push eax
push edx
push edi
push ds
mov ecx,mem_0_4_gb_seg_sel
mov ds,ecx
; ldt基地址
mov edi,[ebx+0x0c]
xor ecx,ecx
; ldt界限
mov cx,[ebx+0x0a]
inc cx
mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx
add cx,8
dec cx
mov [ebx+0x0a],cx
mov ax,cx
xor dx,dx
mov cx,8
div cx
mov cx,ax
shl cx,3
or cx,0000_0000_0000_0100B
pop ds
pop edi
pop edx
pop eax
ret
; 为用户任务划分虚拟空间
; 将用户任务从磁盘加载到物理内存
; 为用户任务构建段安装到用户任务的ldt
; 为用户任务完成符号重定位
; 为用户任务分配tss,构建段,安装到gdt
; 为用户任务tss各个字段填充有意义的值
; 填充tss中还涉及3个特权级栈的空间分配,描述符构建,ldt中安装
; 为用户任务分配新的物理页作为用户任务的页目录表
load_relocate_program:
; EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI
pushad
push ds
push es
mov ebp,esp
mov ecx,mem_0_4_gb_seg_sel
mov es,ecx
; 内核页目录表前512项设置为0x0000 0000
mov ebx,0xfffff000
xor esi,esi
.b1:
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,512
jl .b1
mov eax,core_data_seg_sel
mov ds,eax
mov eax,[ebp+12*4]
mov ebx,core_buf
call sys_routine_seg_sel:read_hard_disk_0
; 用户任务尺寸
mov eax,[core_buf]
mov ebx,eax
and ebx,0xfffff000
add ebx,0x1000
test eax,0x00000fff
; 保证eax中的尺寸是4kb的倍数
mov eax,ebx
mov ecx,eax
; 双层循环
; 外层循环控制用户任务虚拟空间4kb可用区域的分配
; 内存循环控制连续拷贝磁盘扇区内容到4kb虚拟区域
shr ecx,12
mov eax,mem_0_4_gb_seg_sel
mov ds,eax
mov eax,[ebp+12*4]
mov esi,[ebp+11*4]
.b2:
; 从用户任务虚拟区域分配可用4kb
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
; 连续读取8个扇区,共计4kb
push ecx
mov ecx,8
.b3:
call sys_routine_seg_sel:read_hard_disk_0
inc eax
loop .b3
pop ecx
loop .b2
; 现在用户任务位于内存---占据物理页
; 访问通过用户任务线性地址访问
mov eax,core_data_seg_sel
mov ds,eax
mov ebx,[core_next_laddr]
call sys_routine_seg_sel:alloc_inst_a_page
; 在内核空间分配用户任务tss
add dword [core_next_laddr],4096
; TCB对象结构
; 0x44 2字节头部选择子
; 0x40 2特权级栈的初始ESP
; 0x3e 2特权级栈选择子
; 0x3a 2特权级栈基地址
; 0x36 2特权级栈以4KB为单位的长度
; 0x32 1特权级栈的初始ESP
; 0x30 1特权级栈选择子
; 0x2c 1特权级栈基地址
; 0x28 1特权级栈以4kb为单位的长度
; 0x24 0特权级栈的初始ESP
; 0x22 0特权级栈选择子
; 0x1e 0特权级栈基地址
; 0x1a 0特权级栈以4kb为单位的长度
; 0x18 tss选择子
; 0x14 tss基地址
; 0x12 tss界限值
; 0x10 ldt选择子
; 0x0c ldt基地址
; 0x0a ldt当前界限值
; 0x06 程序加载基地址
; 0x04 任务状态
; 0x00 下一个tcb基地址
; 在用户任务tcb中记录任务tss基地址
mov [es:esi+0x14],ebx
; 在用户任务tcb中记录任务tss界限值
mov word [es:esi+0x12],103
; 在用户任务局部空间分配任务的ldt
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
; 在用户任务tcb中记录任务ldt基地址
mov [es:esi+0x0c],ebx
; TSS格式
; I/OMapAddr Reserved 100
; Reserved LDT_Sector 96
; Reserved GS 92
; Reserved FS 88
; Reserved DS 84
; Reserved SS 80
; Reserved CS 76
; Reserved ES 72
; EDI 68
; ESI 64
; EBP 60
; ESP 56
; EBX 52
; EDX 48
; ECX 44
; EAX 40
; EFLAGS 36
; EIP 32
; CR3(PDBR) 28
; Reserved SS2 24
; ESP2 20
; Reserved SS1 16
; ESP1 12
; Reserved SS0 8
; ESP0 4
; Reserved Pre_Task_Tss 0
; 在用户任务tss对象里设置cs
; 为用户任务代码段构造描述符
; 在用户任务tss中记录代码段选择子
mov eax,0x00000000
mov ebx,0x000fffff
; 4kb粒度 32位尺寸 段在内存 特权级3 非系统段 代码段
mov ecx,0x00c0f800
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B
mov ebx,[es:esi+0x14]
mov [es:ebx+76],cx
; 为用户任务数据段构造描述符,在用户任务tss中记录数据段选择子
mov eax,0x00000000
mov ebx,0x000fffff
; 4kb粒度 32位尺寸 段在内存 特权级3 非系统段 可读可写数据段
mov ecx,0x00c0f200
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0011B
mov ebx,[es:esi+0x14]
mov [es:ebx+84],cx
mov [es:ebx+72],cx
mov [es:ebx+88],cx
mov [es:ebx+92],cx
; 在用户任务虚拟区域分配4kb用于栈段
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
; 在用户任务tss中记录栈段选择子,栈的esp
mov ebx,[es:esi+0x14]
mov [es:ebx+80],cx
mov edx,[es:esi+0x06]
mov [es:ebx+56],edx
; 在用户任务虚拟空间分配4kb可用空间用于特权级0栈段
; 为特权级0栈段构建描述符,安装于用户任务ldt
; 在用户任务tss中记录特权级0栈段选择子,栈的esp
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
; 4kb计量 32位 段位于内存 特权级0 非系统段 可读可写数据段
mov ecx,0x00c09200
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0000B
mov ebx,[es:esi+0x14]
mov [es:ebx+8],cx
mov edx,[es:esi+0x06]
mov [es:ebx+4],edx
; 在用户任务虚拟空间分配4kb可用空间用于特权级1栈段
; 为特权级1栈段构建描述符,安装于用户任务ldt
; 在用户任务tss中记录特权级1栈段选择子,栈的esp
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
; 4kb计量 32位 段在内存 特权级1 非系统段 可读可写数据段
mov ecx,0x00c0b200
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0001B
mov ebx,[es:esi+0x14]
mov [es:ebx+16],cx
mov edx,[es:esi+0x06]
mov [es:ebx+12],edx
; 在用户任务虚拟空间分配4kb可用空间用于特权级1栈段
; 为特权级1栈段构建描述符,安装于用户任务ldt
; 在用户任务tss中记录特权级1栈段选择子,栈的esp
mov ebx,[es:esi+0x06]
add dword [es:esi+0x06],0x1000
call sys_routine_seg_sel:alloc_inst_a_page
mov eax,0x00000000
mov ebx,0x000fffff
; 4kb计量 32位 段在内存 特权级2 非系统段 可读可写数据段
mov ecx,0x00c0d200
call sys_routine_seg_sel:make_seg_descriptor
mov ebx,esi
call fill_descriptor_in_ldt
or cx,0000_0000_0000_0010B
mov ebx,[es:esi+0x14]
mov [es:ebx+24],cx
mov edx,[es:esi+0x06]
mov [es:ebx+20],edx
mov eax,mem_0_4_gb_seg_sel
mov es,eax
mov eax,core_data_seg_sel
mov ds,eax
; 双层循环,完成用户任务内每个符号的重新定位
; 每个符号定位后,门选择子里请求特权级是3
cld
mov ecx,[es:0x0c]
mov edi,[es:0x08]
.b4:
push ecx
push edi
mov ecx,salt_items
mov esi,salt
.b5:
push edi
push esi
push ecx
mov ecx,64
repe cmpsd
jnz .b6
; 这里是匹配了
; ds:esi指向内核符号4字节段内偏移
mov eax,[esi]
; 设置用户任务符号头4字节
mov [es:edi-256],eax
; 取得内核符号的门选择子
mov ax,[esi+4]
or ax,0000000000000011B
; 用户任务中符号里门选择子的请求特权级是3
mov [es:edi-252],ax
.b6:
pop ecx
pop esi
add esi,salt_item_len
pop edi
loop .b5
pop edi
add edi,256
pop ecx
loop .b4
mov esi,[ebp+11*4]
; ldt基地址
mov eax,[es:esi+0x0c]
; ldt当前界限
movzx ebx,word [es:esi+0x0a]
; 字节计量 32位 段在内存 0特权级 系统段 可读可写数据段
mov ecx,0x00408200
; 将ldt段安装到gdt
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
; tcb对象中设置ldt选择子
mov [es:esi+0x10],cx
; tss基地址
mov ebx,[es:esi+0x14]
; 在用户任务tss对象里设置用户任务ldt选择子
mov [es:ebx+96],cx
; 在用户任务tss对象里设置前一任务选择子为0x0000
mov word [es:ebx+0],0
; tss界限值
mov dx,[es:esi+0x12]
; 用户任务tss中设置i/o map信息
mov [es:ebx+102],dx
mov word [es:ebx+100],0
; 用户任务入口点偏移
mov eax,[es:0x04]
; 在用户任务tss中设置eip---入口点偏移
mov [es:ebx+32],eax
; pushfd:然后将32位标志寄存器EFLAGS压入堆栈
pushfd
; 出栈32位标志寄存器内容存储到edx
pop edx
; 在用户任务tss中记录eflags
mov [es:ebx+36],edx
; 为用户任务tss构建段描述符,安装到gdt
mov eax,[es:esi+0x14]
movzx ebx,word [es:esi+0x12]
; 字节计量 32位 段在内存 0特权级 系统段 代码段且已经访问
mov ecx,0x00408900
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
; 在用户任务tcb对象记录用户任务tss选择子
mov [es:esi+0x18],cx
; 分配4kb物理页
; 将页目录表拷贝到新的物理页
call sys_routine_seg_sel:create_copy_cur_pdir
mov ebx,[es:esi+0x14]
; 在用户任务tss中记录cr3---用户任务页目录表起始物理地址
mov dword [es:ebx+28],eax
pop es
pop ds
popad
ret 8
; es:ecx是tcb对象起始线性地址
; 链表尾部插入,先找到链表尾部tcb,然后将传入tcb插入到尾部
append_to_tcb_link:
push eax
push edx
push ds
push es
mov eax,core_data_seg_sel
mov ds,eax
mov eax,mem_0_4_gb_seg_sel
mov es,eax
; tcb偏移0x00处设置
mov dword [es: ecx+0x00],0
mov eax,[tcb_chain]
or eax,eax
jz .notcb
.searc:
mov edx,eax
mov eax,[es: edx+0x00]
or eax,eax
; 不是0,继续搜索下一个tcb对象
jnz .searc
mov [es: edx+0x00],ecx
jmp .retpc
.notcb:
mov [tcb_chain],ecx
.retpc:
pop es
pop ds
pop edx
pop eax
ret
start:
mov ecx,core_data_seg_sel
mov ds,ecx
mov ecx,mem_0_4_gb_seg_sel
mov es,ecx
mov ebx,message_0
call sys_routine_seg_sel:put_string
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
; 页目录表,页表中每项内的地址是物理地址信息。其他的地址在分页下都是虚拟地址。
mov ecx,1024
; 划出物理地址0x00020000开始的4kb区域用作页目录表
mov ebx,0x00020000
xor esi,esi
.b1:
mov dword [es:ebx+esi],0x00000000
add esi,4
loop .b1
; 这里将索引为1023的项赋值。其中的物理地址指向页目录表自身。
mov dword [es:ebx+4092],0x00020003
; 这里将索引为0的项复制。其中的物理地址指向页表。
; 一个页表含1024项。每项指向一个4kb物理页。一个页表指向物理页尺寸之和为4mb。
mov dword [es:ebx+0],0x00021003
; 设置索引0页表前256项的物理地址
; 也即设置了虚拟地址前1mb内线性地址的物理地址。
; 特点是线性地址和映射后物理地址数值一致
mov ebx,0x00021000
xor eax,eax
xor esi,esi
.b2:
mov edx,eax
or edx,0x00000003
mov [es:ebx+esi*4],edx
add eax,0x1000
inc esi
cmp esi,256
jl .b2
; 虚拟地址32位---》物理地址32位
; 高10位 中间10位 低12位
; 高10位作为索引从页目录表得到页表物理地址
; 中间10位作为索引从页表得到物理地址A
; 物理地址A+低12位得到最终物理地址
.b3:
mov dword [es:ebx+esi*4],0x00000000
inc esi
cmp esi,1024
jl .b3
; 通知处理器页目录表物理地址
mov eax,0x00020000
mov cr3,eax
mov eax,cr0
or eax,0x80000000
; 开启分页机制
mov cr0,eax
; 在为内核任务构建了合适的页目录表,页表后即可开启分页机制
; 每个任务有自己的代码和数据,同时需要依赖内核例程,依赖内核存放任务控制对象
; 所以设计上,
; 任务低2GB虚拟内存映射到任务的页表,以便存储任务局部代码和数据。
; 任务高2GB虚拟内存映射到内核的页表,以便存储内核全局代码和数据。
; 下面,把内核各个段本来虚拟地址位于低2GB的,
; 通过修改页目标表来达到将其线性地址映射调整到高2GB的作用
mov ebx,0xfffff000
mov esi,0x80000000
; 0b0000 0000 0000 0000 0000 00 1000 0000 00
shr esi,22
; 0b 0000 0000 0000 0000 0000 1000 0000 0000
shl esi,2
; 这样页目录表第0项和第512项指向同一个页表
mov dword [es:ebx+esi],0x00021003
; 从处理器得到gdt的界限和基地址
sgdt [pgdt]
mov ebx,[pgdt+2]
; gdt索引2的描述符(64位)最高位设置为1
or dword [es:ebx+0x10+4],0x80000000
; gdt索引3的描述符(64位)最高位设置为1
or dword [es:ebx+0x18+4],0x80000000
; gdt索引4的描述符(64位)最高位设置为1
or dword [es:ebx+0x20+4],0x80000000
; gdt索引5的描述符(64位)最高位设置为1
or dword [es:ebx+0x28+4],0x80000000
; gdt索引6的描述符(64位)最高位设置为1
or dword [es:ebx+0x30+4],0x80000000
; gdt索引7的描述符(64位)最高位设置为1
or dword [es:ebx+0x38+4],0x80000000
add dword [pgdt+2],0x80000000
; 这样原来的gdt基地址及安装在gdt内各个段的线性地址相比原来增加了0x8000 0000
; 成功将低2GB内线性地址在原有基础上增加了2GB
lgdt [pgdt]
; 跳转,可以更新cs
jmp core_code_seg_sel:flush
flush:
mov eax,core_stack_seg_sel
mov ss,eax
mov eax,core_data_seg_sel
mov ds,eax
mov ebx,message_1
call sys_routine_seg_sel:put_string
mov edi,salt
mov ecx,salt_items
.b4:
push ecx
mov eax,[edi+256]
mov bx,[edi+260]
mov cx,1_11_0_1100_000_00000B
; 内核每个符号,在gdt中安装了一个对应的描述符---门描述符
call sys_routine_seg_sel:make_gate_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
; 符号本来存储段选择子的,现在调整为存储门选择子
mov [edi+260],cx
add edi,salt_item_len
pop ecx
loop .b4
mov ebx,message_2
call far [salt_1+256]
mov ebx,[core_next_laddr]
; 要划分一块4kb可用虚拟区域需要
; 找到页目目录表对应项,
; 若项指向了页表,则寻找并分配物理页,在页表对应项注册物理页
; 若项未指向页表,则寻找并分配物理页作为页表,页表初始化,在页目录表对应项注册页表
; 再寻找并分配物理页,在页表对应项注册物理页
call sys_routine_seg_sel:alloc_inst_a_page
add dword [core_next_laddr],4096
; 刚刚分配的区域用于存放内核任务的tss
; TSS格式
; I/OMapAddr Reserved 100
; Reserved LDT_Sector 96
; Reserved GS 92
; Reserved FS 88
; Reserved DS 84
; Reserved SS 80
; Reserved CS 76
; Reserved ES 72
; EDI 68
; ESI 64
; EBP 60
; ESP 56
; EBX 52
; EDX 48
; ECX 44
; EAX 40
; EFLAGS 36
; EIP 32
; CR3(PDBR) 28
; Reserved SS2 24
; ESP2 20
; Reserved SS1 16
; ESP1 12
; Reserved SS0 8
; ESP0 4
; Reserved Pre_Task_Tss 0
; 设置迁移任务的tss选择子,这里为0x0000
mov word [es:ebx+0],0
mov eax,cr3
; 任务tss里设置cr3---任务页目录表
mov dword [es:ebx+28],eax
; ldt选择子
mov word [es:ebx+96],0
; 预留
mov word [es:ebx+100],0
; 意思就是I/O Map区域不存在
mov word [es:ebx+102],103
; 内核tss是一个段
mov eax,ebx
mov ebx,103
; 字节计量 32位 段在内存 特权级0 系统段 代码段被访问过
mov ecx,0x00408900
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [program_man_tss+4],cx
ltr cx
mov ebx,[core_next_laddr]
call sys_routine_seg_sel:alloc_inst_a_page
add dword [core_next_laddr],4096
; TCB对象结构
; 0x44 2字节头部选择子
; 0x40 2特权级栈的初始ESP
; 0x3e 2特权级栈选择子
; 0x3a 2特权级栈基地址
; 0x36 2特权级栈以4KB为单位的长度
; 0x32 1特权级栈的初始ESP
; 0x30 1特权级栈选择子
; 0x2c 1特权级栈基地址
; 0x28 1特权级栈以4kb为单位的长度
; 0x24 0特权级栈的初始ESP
; 0x22 0特权级栈选择子
; 0x1e 0特权级栈基地址
; 0x1a 0特权级栈以4kb为单位的长度
; 0x18 tss选择子
; 0x14 tss基地址
; 0x12 tss界限值
; 0x10 ldt选择子
; 0x0c ldt基地址
; 0x0a ldt当前界限值
; 0x06 程序加载基地址
; 0x04 任务状态
; 0x00 下一个tcb基地址
; tcb中任务加载基地址
mov dword [es:ebx+0x06],0
; 任务ldt界限值
mov word [es:ebx+0x0a],0xffff
; 加入到tcb链表
mov ecx,ebx
call append_to_tcb_link
push dword 50
push ecx
; 用户任务重新定位
call load_relocate_program
mov ebx,message_4
call sys_routine_seg_sel:put_string
; 通过用户任务tss基地址 tss选择子实现任务切换
call far [es:ecx+0x14]
mov ebx,message_5
call sys_routine_seg_sel:put_string
hlt
core_code_end:
SECTION core_trail
core_end:
; 基于页表,页目录表实现分页,分页和虚拟内存机制是一个东西
; 基于tss实现多任务切换,任务管理
; 这里的tcb对象尚未被处理器使用
3.用户任务
program_length dd program_end
entry_point dd start
salt_position dd salt_begin
salt_items dd (salt_end-salt_begin)/256
salt_begin:
PrintString db '@PrintString'
times 256-($-PrintString) db 0
TerminateProgram db '@TerminateProgram'
times 256-($-TerminateProgram) db 0
reserved times 256*500 db 0
ReadDiskData db '@ReadDiskData'
times 256-($-ReadDiskData) db 0
PrintDwordAsHex db '@PrintDwordAsHexString'
times 256-($-PrintDwordAsHex) db 0
salt_end:
message_0 db 0x0d,0x0a,
db ' ............User task is running with '
db 'paging enabled!............',0x0d,0x0a,0
space db 0x20,0x20,0
[bits 32]
start:
mov ebx,message_0
; 基于段内偏移+门选择子进行例程调用
call far [PrintString]
xor esi,esi
mov ecx,88
.b1:
mov ebx,space
; 基于段内偏移+门选择子进行例程调用
call far [PrintString]
; 用户任务虚拟区域起始一段内容按16进制打印
mov edx,[esi*4]
call far [PrintDwordAsHex]
inc esi
loop .b1
call far [TerminateProgram]
program_end: