Orange‘s 一个操作系统的实现 chap5 扩充内核记录

扩充内核

1.在引导过程中在屏幕上画一个图案

  系统引导指的是将操作系统内核装入内存并启动系统的过程。在我们的操作系统中,引导由Boot.bin和Loader.bin程序负责。Boot.bin位于主引导扇区,而主引导扇区空间有限。Boot.bin的修改空间有限,不宜被修改。因此,我们修改Loader.asm,以实现在Loader加载内核完毕前,在屏幕上画一个ASCII图案。

  首先,在Loader.asm增加一个数据区,用以存储ASCII图案。ASCII图案为一个由“#”构成的心形。图案尺寸为6 * 9(6行9列)。变量PatternWidth和PatternHeight分别为字符矩阵的列数和行数。
  接着,在Loader.asm中添加一个名为DispPattern的函数。该函数将根据ASCII图案的尺寸将图案输出到显存中。DispPattern利用10h中断,将字符图案逐行显示。

DispPattern:  
push    ax	  
 push    cx    
mov cx, PatternHeight  
mov ax, PatternData  
add dh, 3     ; 将第一个字符串输出到屏幕第4行
mov bp, ax  ;  
mov ax, ds  ;  
mov es, ax  ; es:bp -> 第一个字符串  
  
PatternLoop:  
push    cx  ; 将待输出字符串的行数入栈保存
mov cx, PatternWidth  ;设置cx为当前字符串宽
mov ax, 01301h  
mov bx, 0004h  
mov dl, 20  
int 10h	; 调用中断,显示字符串
pop cx
add bp, PatternWidth	; es:bp -> 下一个字符串 
add dh, 1	; 行数加一
loop    PatternLoop  
  
pop cx  
pop ax  
ret  

kernel.asm
  在找到内核kernel.bin后,调用图案显示函数,并显示字符串“Ready”。
  在引导过程和内核初始化过程中,屏幕还会显示其它信息。最后,为了避免图案掩盖住这些信息,我们需要修改这些信息的输出位置。在进入实模式后,系统会调用DispMemInfo来输出内存信息。其中,字符串的显示是通过调用函数DispStr完成的。

DispStr部分代码

  查看DispStr,我们可以确定字符串的输出位置是由变量dwDispPos决定的,所以我们需要对dwDispPos修改,使输出位置下移,为ASCII图案腾出空间。

修改dwDispPos
  dwDispPos是_dwDispPos映射得到的,修改_dwDispPos即可。
  另外,在kernel运行的过程中,会调用cstart函数完成GDT和堆栈切换。而cstart执行开始和结束时会输出两行提示信息。这两行提示信息也需要向下移动。
start.c中的cstart函数调用disp_str()函数完成提示信息的输出。

cstart()中的disp_str()
kernel.asm中对disp_pos的初始化

  查看disp_str()的源码,我们可以得知:disp_str()会以变量disp_pos的值为字符出输出地址。在kernel.asm中,disp_pos最初被设置为0。cstart输出提示信息前,先输出了若干个换行,以避免其提示信息覆盖住之前输出的内存信息。因此,我们只需要再增加输出几个换行即可。

运行结果

2.将内存管理模块添加进kernel

  首先,创建一个新的汇编代码文件(memop.asm)并将之前实现的alloc_pages函数和free_pages函数加入其中。

跳入保护模式后段寄存设置
  接着,由于系统在跳入保护模式后,已经将段寄存器设置好了,所以之前实现的函数中的段寄存器设置部分可以省去。另外,在没有引入pm.inc的情况对memop.asm编译时,编译会出错:PG_U、PG_USU、PG_RWW未定义。我们直接用具体值代替对三者的引用。

; memory operation module



; import function



; import global variables



; export function

global	alloc_pages

global  free_pages



[SECTION .data]

BitMap:

		times	1	db	0xff

		times	63	db	0x0



BitMapLen			equ	$ - $$

StartLinearAddress	dd	0x60000000



[SECTION .text]

alloc_pages:



	mov 		ecx, eax

	mov		ebx, 4096

	mul		ebx

	mov		ebx, [es:StartLinearAddress]

	add		[es:StartLinearAddress], eax

	push		ebx

	mov		eax, ebx

	

	mov		ebx, cr3

	and		ebx, 0xfffff000

	and		eax, 0xffc00000

	shr		eax, 20

	add		ebx, eax

	mov		edx, ebx

	mov		ebx, [ebx]



	test		ebx, 0x00000001

	jnz		.pde_exist

	

	mov		ebx, cr3

	mov		ebx, [ebx]

	and		ebx, 0xfffff000

	shl		eax, 10

	add		ebx, eax

	or		ebx, 0x00000007

	mov		[edx], ebx



.pde_exist:

	mov		eax, [esp]

	and		ebx, 0xfffff000

	and		eax, 0x003ff000

	shr		eax, 10

	add		ebx, eax



.update_pte:

	call		alloc_a_4k_page

	or		eax, 0x00000007

	mov		[ebx], eax

	add		ebx, 4

	loop		.update_pte

	pop		ebx

	ret



alloc_a_4k_page:

	xor		eax, eax



.search:

	bts		[BitMap], eax

	jnc		.find

	inc		eax

	cmp		eax, BitMapLen * 8

	jl		.search

	hlt



.find:

	shl		eax, 12

	ret



free_pages:

	mov ecx, eax		; 将页的个数存入 ecx 循环计数

	mov eax, ebx		; 获取传入的线性地址



	mov edx, eax  		;edx暂存线性地址

	mov ebx, cr3 

	and ebx, 0xfffff000 ; 取 cr3 前 20 位

	and eax, 0xffc00000 ; 取线性地址前 10 位

	shr eax, 20 		; shr 22 + shl 2

	add ebx, eax 		; 计算 PDE 地址

	mov ebx, [ebx]		; 取 PDE 值



	test ebx, 0x00000001; 相与

	jnz .pde_exist 		; 不为 0,说明页表存在

    ret

	; 页表不存在,无需释放页面



.pde_exist:

	mov eax, edx 		; 恢复线性地址到 eax

    and ebx, 0xfffff000 ; 取 PDE值 前 20 位

	and eax, 0x003ff000 ; 取线性地址中间 10 位

	shr eax, 10 		; shr 12 + shl 2

	add ebx, eax 		; 计算 PTE 地址

	

.update_pte:

	call free_a_4k_page ; 调用该函数,用于释放一个 4KB 的页面

   ;更新页表项,将对应的页表项设置为无效

	mov eax, 0

    mov [ebx], eax		; 将页表项内容设置为0,标记为无效

	add  ebx, 4

	loop .update_pte 	; 继续循环,直到所有页面都释放完毕

	ret



free_a_4k_page:

	push ebx

    mov ebx,[ebx]

    and ebx, 0xfffff000

	mov eax, edx

    and eax, 0xfff	

	add ebx, eax  ;现在ebx为物理地址



	; 将传入的物理地址右移 12 位,得到页号

	shr ebx, 12

	; 将位图中对应的位设置为 0,表示该物理页未被占用

	btr [BitMap], ebx 

    pop ebx

	ret

  然后,将alloc_pages和free_pages设置为引出函数并将memop.asm加入kernel文件夹中。同时,kernel.asm中增加了对这两个函数的引用和一段测试代码。

kernel导入函数:
kernel导入函数
测试代码:先分配两个页,再释放两个页:
测试代码
调试结果:
调试结果

  因为我们分配了2个页,所以建立了从0x60000000到0x60001fff的映射关系(红色框标注处):其中0x60001fff对应的物理地址是0x8000,即我们使用BitMap规定的第一处空闲物理地址(0x8000-0x1fffff为空闲区域)。接着,调用free_pages函数,释放2个页。(蓝色框标注处)可见,再调用free_pages后,映射关系已恢复为调用alloc_pages前的状态了。

3. 将自己设计的中断添加进kernel

  类似加入内存管理模块的步骤,将自己设计的中断单独装入一个文件并放入kernel文件夹。同时,将自定义的函数设置为导出函数,并在kernel.asm中导入。导入的中断模块代码如下:

;inserted keyboard interrupt

;import functions

;import global variables

;export functions
global	change_color


change_color:

        in al,60h
        mov ah,0
        sub al,1
        je ESC

	push cx
	push edi

	mov cx, 6
	mov edi, (80 * 4 + 20) * 2 + 1
lines:
	push cx
	push edi
	mov cx, 9
oneLine:
	cmp	byte [gs:edi - 1], '#'
	jne	next
        inc     byte [gs:edi]
next:
	inc	edi
	inc	edi
	loop	oneLine
	
	pop	edi
	pop 	cx
	add 	edi, 160
	loop	lines
	
	pop	edi
	pop	cx
      
	mov al,20h
        out 20h,al
        ret

ESC:
        mov al,20h
        out 20h,al
        mov al,11111111b
        out 21h,al
        call io_delay
        ret

io_delay:
	nop
	nop
	nop
	nop
	ret

  该模块的功能为:每当由键盘按下时,改变屏幕上之前输出的ASCII图案颜色。按下ESC后,退出中断。

调用自定义的函数:
调用自定义的函数

  需要注意的是,之前实现的中断函数中,返回使用iretd。而此处change_color中使用ret返回,在change_color执行后,再使用iretd将处理器状态恢复到中断或异常发生时的状态并跳转回用户模式。

运行结果:
运行结果1
运行结果2

4.修改makefile,重新编译内核
  前面导入内存管理和自定义中断函数都需要对makefile进行修改,但修改幅度较小:只需要增加编译指令,并将编译得到的文件的路径加入链接目标变量(OBJS)即可。

添加编译指令:
添加编译指令
增加链接目标:
增加链接目标

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值