《操作系统真象还原》第四篇:完善内核,实现打印函数

第四篇 完善内核,实现打印函数

1.显卡端口解析

显卡中寄存器有很多,为了通过有限的端口进行访问,对他们进行了分组。

下图中前四组寄存器属于分组,他们被分成了两类寄存器,Address Register 和 Data Register。

在这里插入图片描述

Address Register作为数组的索引,Data Register作为寄存器数组中该索引对应的寄存器。

​ 所以对这类分组的寄存器的操作方法是先在Address Register中指定寄存器的索引值,然后在Data Register寄存器中对所索引的寄存器进行读写操作。

​ 以CRT Controller Register为例,默认情况下,Address Register的端口地址为0x03D4,Data Register的端口地址为0x03D5

索引如下:

在这里插入图片描述

2.实现单个字符打印

单个字符打印流程如下:

  • 备份寄存器现场
  • 获取光标的地址
  • 获取待打印的字符
  • 判断字符是否为控制字符
  • 判断是否需要滚屏
  • 更新光标位置
  • 恢复寄存器现场,退出

实现代码如下:

TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0

[bits 32]
section .text
;--------- put_char------
;功能描述:把栈中的一个字符写入光标所在处
;------------------------
global put_char
put_char:
	;保存所有双字寄存器
	pushad
	;保证gs寄存器中是视频段的选择子
	mov ax, SELECTOR_VIDEO
	mov gs, ax

	;获取光标地址
	;获取光标地址的高8位	
	mov dx, 0x03d4
	mov al, 0x0e
	out dx, al
	mov dx, 0x03d5
	in al, dx
	mov ah, al

	;获取光标地址的低8位
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	in al, dx

	;将光标地址保存到ebx
	mov bx, ax
	;获取待打印的字符
	mov ecx, [esp + 36]
	
	;CR是0x0d,LF是0x0a
	cmp cl, 0x0d
	je .is_carriage_return 

	cmp cl, 0x0a
	je .is_line_feed

	;BackSpace 的asc码是0x08
	cmp cl, 0x08
	je .is_backspace
	jmp .put_other

.is_backspace:
;------backspace操作:先将光标往前移动一位,再将该位置改为空格------
	dec bx
	shl bx, 1
	mov word [gs:bx], 0x0720
	shr bx, 1
	jmp .set_cursor	

.put_other:
	;bx*2 等于偏移的字节数
	;写入字符和属性
	shl bx, 1
	mov byte [gs:bx], cl
	inc bx
	mov byte [gs:bx], 0x07
	;恢复光标并指向下一个位置
	shr bx, 1
	inc bx
	;如果超出页面则卷屏
	cmp bx, 2000
	jl .set_cursor	
	jmp .roll_screen

.is_line_feed:
.is_carriage_return:
	xor dx, dx	;dx是被除数的高16位,清0
	mov ax, bx	;ax是被除数的低16位
	mov si, 80	;将除数存入si

	div si

	sub bx, dx	;用bx减去余数,令bx指向当前行的首端

.is_carriage_return_end:
	add bx, 80
	cmp bx, 2000
.is_line_feed_end:
	jl .set_cursor
	jmp .roll_screen

;屏幕范围是0~24行
;滚屏将1~24行移置0~23行,并将第24行清0
.roll_screen:
	cld 
	;共1920个字符,3840字节
	;一次搬运4字节,共需搬运960次
	mov ecx, 960
	
	mov esi, 0xc00b80a0 ;第1行行首
	mov edi, 0xc00b8000	;第0行行首
	rep movsd
	
;----将最后一行清0----

	mov bx, 3840	;最后一行首字符偏移:1920*2 = 3840
	mov ecx, 80		;最后一行共80个字符

.cls:
	mov word [gs:bx], 0x0720
	add bx, 2
	loop .cls
	;将光标移到最后一行行首
	mov bx, 1920

.set_cursor:
;将光标设为bx的值
;-----先设置bx的高8位---
	mov dx, 0x03d4
	mov al, 0x0e
	out dx, al
	mov dx, 0x03d5
	mov al, bh
	out dx, al

;-----再设置bx的低8位---
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	mov al, bl
	out dx, al

.put_char_done:
	popad
	ret

3.实现字符串打印

通过调用put_char实现单个字符的打印,故只需要判断是否到字符串结尾即可

实现如下:

[bits 32]
section .text
;----------------------------------
;---put_str 打印以0为结尾的字符串--
;----------------------------------
;输入:栈中参数为打印的字符串
;输出:无
global put_str
put_str:
	push ebx
	push ecx
	xor ecx, ecx		;用ecx来存储字符
	mov ebx, [esp + 12] ;用ebx存储当前处理的地址
	
.go_on:
	;如果当前字符为0,则跳出循环
	mov cl, [ebx]	
	cmp cl, 0
	je .str_over
	;参数入栈,调用put_char
	push ecx
	call put_char
	;回收栈空间,指向下一个字符
	add esp, 4
	inc ebx
	jmp .go_on

.str_over:
	pop ecx
	pop ebx
	ret

4.实现整数打印

将整数以4bit为单位进行转换保存,最后通过put_char打印即可

实现如下:

section .data
put_int_buffer dq 0	;定义8字节的缓冲区用于字符转换

;------打印32位整数------
;输入:栈中为待打印的整数
;输出:打印数字的16进制格式,没有前置的0x

section .text
global put_int
put_int:
	pushad
	mov ebp, esp
	mov eax, [ebp + 4 * 9]
	mov edx, eax			;将要转换的数字
	mov edi, 7				;buffer中的索引
	mov ecx, 8				;共有8个16进制位
	mov ebx, put_int_buffer
	
.16based_4bits:
	and edx, 0x0000000F		;获取低4位
	cmp edx, 9
	jg .is_A2F				;如果是0-9则+‘0’
	add edx, '0'
	jmp .store

.is_A2F:					;如果是a-f则+‘A’-10
	sub edx, 10
	add edx, 'A'

.store:
	;将当前的字符存储在缓存中
	;并处理下一个字符
	mov [ebx + edi], dl
	dec edi
	shr eax, 4
	mov edx, eax
	loop .16based_4bits

.ready_to_print:
	;令edi为0
	inc edi
.skip_prefix_0:
	;若当前检测到了第8个字符,说明全为0
	cmp edi, 8
	je .full_0

.go_on_skip:
	;比较字符并指向下一个字符
	mov cl, [ebx + edi]
	inc edi
	cmp cl, '0'
	je .skip_prefix_0
	;指向第一个不为0的字符,并跳转
	dec edi
	jmp .put_each_num

.full_0:
	mov cl, '0'
.put_each_num:
	;利用put_char函数依次打印所有的字符
	push ecx
	call put_char
	add esp, 4
	inc edi
	mov cl, [ebx + edi]
	cmp edi, 8
	jl .put_each_num

	popad
	ret
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值