专注于操作系统21之分页进阶

        在上一篇文章中,我们虽然启动的分页,但我们没感受到分页带来的好处。这里,让我们来体会一下分页带来的其中一点好处,用同一个线性地址去访问不同的物理地址处的程序。再说一下,线性地址到物理地址的转换,先用32位线性地址的高20位去找到一个页表项,再把这个页表项的内容(物理页的地址)加上线性地址的低12位(页内偏移),便得到了物理地址。那怎样用同一个线性地址来访问不同的物理地址呢?我们只需把该线性地址所对应的页表项(用线性地址的高20位找到的)中的内容改为我们需要访问的物理页的地址即可。

      在这里,我们建立了两个页目录表,以及每个页目录表所对应的页表。第一次访问线性地址用的是第一个页目录表,第二次访问线性线性地址用的是第二个页目录表(涉及页目录的变换,所以要改变CR3的内容)。这里,我们要先建立两个程序,程序1和程序2. 第一次访问线性地址,调用程序1,第二次访问同一个线性地址,调用程序2. 还要把程序1和程序2的代码复制到相应的物理地址处。代码中有详细的注释。

    下面给出代码,该代码是看了《自己动手写操作系统》的代码后写的,把与分页无关的代码去掉,也改了部分的代码

;;nasm 2.04 
;;nasm fenyeup.asm -o fenyeup.com
org 0100H
	jmp	LABEL_BEGIN  
%macro Descriptor 3             ;定义Descriptor结构体宏
	dw	%2 & 0FFFFh				; 段界限 1				(2 字节)
	dw	%1 & 0FFFFh				; 段基址 1				(2 字节)
	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 字节)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性 1 + 段界限 2 + 属性 2		(2 字节)
	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字节)
%endmacro ; 共 8 字节

DA_C		EQU	98h	; 只读代码段的属性值
DA_DRW		EQU	92h	; 允许读写的数据段的属性值
DA_32		EQU	4000h	;32位段的属性值
DA_CR		EQU	9Ah	; 存在的可执行可读代码段属性值
DA_DRWA		EQU	93h	; 存在的已访问可读写数据段类型值
DA_LIMIT_4K	EQU	8000h	; 段界限粒度为 4K 字节
PG_P		EQU	1	; 页存在属性位
PG_RWR		EQU	0	; R/W 属性位值, 读/执行
PG_RWW		EQU	2	; R/W 属性位值, 读/写/执行
PG_USS		EQU	0	; U/S 属性位值, 系统级
PG_USU		EQU	4	; U/S 属性位值, 用户级




CatalogPageTableBase0 equ 200000h  ; 页目录表的基址:2M
PageTableBase0    equ    201000h    ;页表的基址 :2M+4k
CatalogPageTableBase1 equ 601000h  ; 页目录表的基址:2M+4k+4M
PageTableBase1    equ    602000h    ;页表的基址 :2M+4k+4M+4K


FirstProgramBase equ  00b01000h ;32位表示第一个程序的物理地址
SecondProgramBase equ 00c01000h ;32位表示第二个程序的物理地址
LineAddressOffset equ 00b01000h ;32位,是48位线性地址中的后32位,表示段内偏移 

       
	               ;现在进行第一步:建立GDT表
[SECTION .gdt]     ;用于放GDT表的段
;GDT
LABEL_GDT: Descriptor 0,0,0 ;空描述符,当一个任务没有LDT时,会把LDTR清空,这时,LDTR便指向空描述符。

LABEL_CODE32: Descriptor 0,SegCode32Len-1,DA_CR | DA_32 ; 代码段的描述符

LABEL_VIDEO: Descriptor 0B8000h,0ffffh,DA_DRW    ;显存的描述符,0b8000h是显存的首地址
LABEL_GSTACK: Descriptor 0, GStackLen , DA_32 | DA_DRWA      ;全局堆栈段的描述符

LABEL_GENERAL_RW:    Descriptor 0, 0fffffh ,DA_DRW | DA_LIMIT_4K  ;总段的描述符1,读写该段的时候用
LABEL_GENERAL_C:     Descriptor 0, 0fffffh ,DA_CR | DA_32 | DA_LIMIT_4K ;总段的描述符2,执行该段代码的时候用

;GDT 结束
GdtLenth equ $ - LABEL_GDT   ;计算GDT表的长度

GdtPtr dw GdtLenth - 1 ; 准备存入GDTR的信息
       dd 0    ;    用于存放GDT表的物理地址,后面会计算


;GDT选择子

SelectorCode32 equ LABEL_CODE32 - LABEL_GDT ;代码段的选择子
SelectorVideo  equ LABEL_VIDEO - LABEL_GDT  ;显存所在段的选择子
SelectorGeneralRW equ LABEL_GENERAL_RW - LABEL_GDT ;总段的描述符1的选择子
SelectorGeneralC  equ LABEL_GENERAL_C - LABEL_GDT ;总段的描述符2的选择子
SelectorGStack equ	LABEL_GSTACK - LABEL_GDT       ;全局堆栈段的选择子


;[SECTION .gdt]结束

;全局堆栈段  
[SECTION .gstack]
ALIGN	32
[BITS	32]
LABEL_SEG_GSTACK:
times 512  db 0
GStackLen equ $ - LABEL_SEG_GSTACK - 1
;[SECTION .gstack]结束


[SECTION .s16]
[BITS 16]	;目标处理器模式
LABEL_BEGIN:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h

;初始化全区堆栈段的描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_GSTACK
mov	word [LABEL_GSTACK + 2], ax
shr	eax, 16
mov	byte [LABEL_GSTACK + 4], al
mov	byte [LABEL_GSTACK + 7], ah



;初始化32位段的描述符

xor eax,eax   ; 同或运算,这里是将eax清零

mov ax,cs
shl eax,4     ;左移四位,相当于乘以16,实模式下计算物理地址
add eax,LABEL_SEG_CODE32    ;eax中为32位段的物理地址

mov word [LABEL_CODE32 + 2],ax   ;将32位段的物理地址存入段的描述符中

shr eax,16
mov byte [LABEL_CODE32 + 4],al
mov byte [LABEL_CODE32 + 7],ah

;计算GDT表的物理地址,将GDT表的物理地址信息存入GdtPtr中
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr + 2],eax

;现在进行第二步:将GDT表的信息加载到GDTR中
lgdt [GdtPtr]  ;装载GDTR

;关中断
cli

;现在进行第三步:打开地址线
in al,92h
or al,00000010b
out 92h,al

;现在进行第四步:置PE为1,表示CPU处于保护模式下
mov eax,cr0
or eax,1
mov cr0,eax
;现在进入第五步:跳到保护模式的代码

jmp dword SelectorCode32:0  ;这里将SelectorCode32存入CS中作为选择子
;[SECTION .s16] 结束

;保护模式下的代码,32位代码
[SECTION .s32]
[BITS 32]  

LABEL_SEG_CODE32:

mov ax,SelectorGStack
mov ss,ax
mov esp,GStackLen
mov ax,SelectorVideo ;视频选择子,用于找到显存段的描述符
mov gs,ax 

mov edi,(80 * 8 + 0)*2  ;屏幕的第8行,第0列
mov ah,0ch
mov al,'p'
mov [gs:edi],ax ;将字符‘p’输出到屏幕



    mov	ax, cs     ;将程序1复制到相应的物理地址处
	mov	ds, ax      
	mov	ax, SelectorGeneralRW
	mov	es, ax

	push	FirstLen
	push	OffsetFirst
	push	FirstProgramBase
	call	MemCpy
	add	esp, 12

;将程序2复制到相应的物理地址处

	push	SecondLen
	push	OffsetSecond
	push	SecondProgramBase
	call	MemCpy
	add	esp, 12

call SetupPaging   ;启动分页

call SelectorGeneralC:LineAddressOffset ;调用线性地址(SelectorGeneralC:LineAddressOffset对应的)
                                        ;对应的物理地址处的程序(此处调用程序1)

call change          ;改变线性地址与物理地址的对应关系

call SelectorGeneralC:LineAddressOffset ;再次调用相同的线性地址,由于线性地址与物理地址的对应关系
  jmp $                                      ;已改变,所以会调用不同物理地址处的程序(此处调用程序2)


SetupPaging:
;为简单,这里让线性地址等于物理地址

;初始化页目录表0
mov ax,SelectorGeneralRW  ;将页目录表的选择子放到es中
mov es,ax
mov ecx,1024   ;1024送到cx,表示循环1024次
xor edi,edi     ;edi清零
mov edi,CatalogPageTableBase0  ;将页目录表0地址送edi
             
xor eax,eax        ;ax清零

mov eax,PageTableBase0 | PG_P | PG_USU | PG_RWW  ;将第一个页表的信息(页目录表中的一个项)放到eax中,准备放到页目录表中
                                         
s1:
stosd
add eax,4096           ;eax加4096表示下一个页表的地址(一个页表有4096的大小)
loop s1


;初始化页表的信息
mov ax,SelectorGeneralRW  ;将第一个页表的选择符放到es
mov es,ax
mov ecx,1024*1024   ;循环1024*1024次,因为一个目录表有1024个项,每一个项对应一个页表,而每个页表有1024个项,每一个项
xor edi,edi          ;对应一个物理页
mov edi,PageTableBase0 
xor eax,eax

mov eax,PG_P | PG_USU | PG_RWW  


s2:
stosd
add eax,4096  ;每一页有4k的大小
loop s2

mov eax,CatalogPageTableBase0  ;将页目录表0的地址存入cr3中
mov cr3,eax

mov eax,cr0         ;将cr0的最高位置1,表示启动分页
or eax,80000000h
mov cr0,eax

ret
;改变线性地址与物理地址的对应关系
change:
;初始化页目录表1
mov ax,SelectorGeneralRW  ;将页目录表的选择子放到es中
mov es,ax
mov ecx,1024   ;1024送到cx,表示循环1024次
xor edi,edi ;edi清零
mov edi,CatalogPageTableBase1  ;将页目录表1地址送edi
             
xor eax,eax        ;ax清零

mov eax,PageTableBase1 | PG_P | PG_USU | PG_RWW  ;将第一个页表的信息(页目录表中的一个项)放到eax中,准备放到页目录表中
                                                 
s3:
stosd
add eax,4096           ;eax加4096表示下一个页表的地址(一个页表有4096的大小)
loop s3

;初始化页表的信息
mov ax,SelectorGeneralRW  ;将第一个页表的选择符放到es
mov es,ax
mov ecx,1024*1024   ;循环1024*1024次,因为一个目录表有1024个项,每一个项对应一个页表,而每个页表有1024个项,每一个项
xor edi,edi          ;对应一个物理页
mov edi,PageTableBase1 
xor eax,eax


mov eax,PG_P | PG_USU | PG_RWW  
s4:
stosd
add eax,4096  ;每一页有4k的大小
loop s4

                                 
								 ;注意SelectorGeneralC所对应的段的基址为0,所以LineAddressOffset
								 ;就是线性地址。

                                 ;先找到线性地址(SelectorGeneralC:LineAddressOffset)所对应的页表项
    mov	eax, LineAddressOffset   ;然后,改变线性地址(SelectorGeneralC:LineAddressOffset)与物理地址的对应关系,
	                             ;由原来对应程序1的物理地址,改为对应程序2的物理地址。
	shr	eax, 22                  ;(这里只改变了,一个页表中的一项,其它页表中的项的对应关系不变)
	mov	ebx, 4096                
	mul	ebx
	mov	ecx, eax
	mov	eax, LineAddressOffset
	shr	eax, 12
	and	eax, 03FFh	; 1111111111b (10 bits)
	mov	ebx, 4
	mul	ebx 
	add	eax, ecx
	add	eax, PageTableBase1   ;这里[es:eax]对应的就是所要找的页表项,将该页表项的内容改为
	                          ;程序2的物理地址
	mov	dword [es:eax], SecondProgramBase | PG_P | PG_USU | PG_RWW

mov eax,CatalogPageTableBase1  ;将页目录表1的地址存入cr3中
mov cr3,eax

	ret



;第一个程序的开始
first:
OffsetFirst equ first - $$  ;标签(first:)在该段([SECTION .s32])中的偏移

mov ah,0ch  ;0000:黑底 1100:红字
mov al,'1'  ;表示输出的字符是‘1’

mov [gs:((80*10+2)*2)],ax  ;在第10行第二列输出字符

retf
FirstLen equ $ - first
;第一个程序的结束

;第二个程序的开始
second:

OffsetSecond equ second - $$
mov ah,0ch  ;0000:黑底 1100:红字
mov al,'2'  ;表示输出的字符是‘2’
mov [gs:((80*11+2)*2)],ax  ;在第11行第二列输出字符

retf
SecondLen equ $ - second
;第二个程序的结束



; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------;用于把程序1和程序2
MemCpy:                                                                  ;复制到相应的物理地址处
	push	ebp
	mov	ebp, esp

	push	esi
	push	edi
	push	ecx

	mov	edi, [ebp + 8]	; Destination
	mov	esi, [ebp + 12]	; Source
	mov	ecx, [ebp + 16]	; Counter
.1:
	cmp	ecx, 0		; 判断计数器
	jz	.2		; 计数器为零时跳出

	mov	al, [ds:esi]		; ┓
	inc	esi			;         ┃
					;         ┣ 逐字节移动
	mov	byte [es:edi], al	; ┃
	inc	edi			;         ┛

	dec	ecx		; 计数器减一
	jmp	.1		; 循环
.2:
	mov	eax, [ebp + 8]	; 返回值

	pop	ecx
	pop	edi
	pop	esi
	mov	esp, ebp
	pop	ebp

	ret			; 函数结束,返回
; MemCpy 结束-------------------------------------------------------------



SegCode32Len equ $ - LABEL_SEG_CODE32  ;计算32位代码段的长度


编译完成后,是在虚拟机中的MS-DOS上运行的,如果没有虚拟机和MS-DOS,请看第16篇文章,搭建dos环境

下面是运行的效果图



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值