操作系统真象还原——第6章 完善内核

什么是函数调用约定

一言概之:函数调用约定就是调用函数时,如何传参入栈,调用后如何恢复栈的协定。我们实现的过程中使用的是cdecl协定,基本的原则是:

  1. 参数按从右到左的顺序入栈
  2. 由调用者清理栈空间

其中第二点带来了一个很明显的好处,那就是可以传入不定长的参数。

实现打印函数

复习下汇编的知识:

in [num], [port]	; 从port端口读出num,in的主体是内存
out [port], [num]	; 向port端口写入num,out的主体是内存

实现字符打印

然后开始写打印函数:

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	; 备份所有寄存器
	mov ax, SELECTOR_VIDEO
	mov gs, ax
; 获取光标位置
	mov dx, 0x03d4	; 索引号端口
	mov al, 0x0e	;8位寄存器
	out dx, al
	mov dx, 0x03d5	; 数据端口
	in al, dx	; 读取高八位到al
	mov ah, al
	
	; 低八位同理
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	in al, dx

	mov bx, ax	; 将光标存入bx
	mov ecx, [esp + 36]	; 获取待打印的字符,其中0-4位是返回地址,4-36是寄存器
	cmp cl, 0xd
	jz .is_carriage_return
	cmp cl, 0xa
	jz .is_line_feed	; 二者都看成换行+回车
	cmp cl, 0x8	; 退格
	jz .is_backspace
	jmp .put_other

.is_backspace:	; 退格并将退后的字符设置为空
	cmp bx, 0
	je .set_cursor	
	
	dec bx
	shl bx, 1	; 每次读写光标必须乘2,因为每个位置用2字节表示,1字节代表ascii,1字节代表状态
	
	mov word [gs:bx], 0x0720
	shr bx, 1
	jmp .set_cursor

.put_other:
	shl bx, 1
	mov [gs:bx], cl
	inc bx
	mov byte [gs:bx], 0x07
	inc bx
	shr bx, 1
	cmp bx, 2000
	jl .set_cursor

.is_line_feed:
.is_carriage_return:
	xor dx, dx
	mov ax, bx
	mov si, 80
	div si
	sub bx, dx

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

; 1.1-24行搬到0-23.roll_screen:
	cld
	mov ecx, 960	; 960 = (2000 - 80) * 2 / 4
	mov esi, 0xc00b80a0
	mov edi, 0xc00b8000
	rep movsd
; 2. 将最后一行填充为空
mov ebx, 3840
mov ecx, 80

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

; 将当前光标写回显存
.set_cursor:
	; 高八位
	mov dx, 0x03d4
	mov al, 0x0e
	out dx, al
	mov dx, 0x03d5
	mov al, bh
	out dx, al
	
	; 低八位
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	mov al, bl
	out dx, al
.put_char_done:
	popad
	ret

文章看似字数不多,但我在这里卡了快一天了,终于通过一次次调试得出了理想的结果:
在这里插入图片描述
哈哈不容易啊不容易……

打印字符串

接下来写打印字符串,尝试自己手撸:

;------------ put str -----------------
global put_str
put_str:
	push ebx
	push ecx
	mov ebx, [esp + 12]
.print_loop:
	mov ecx, [ebx]
	cmp cl, 0
	je .print_end
	push ecx
	call put_char
	pop ecx
	add ebx, 1
	jmp .print_loop

.print_end:
	pop ecx
	pop ebx
	ret

检查一下,成功!
在这里插入图片描述

打印整数

这个是我们大二上的考试题哈,继续手撸(代码后面有汇总):
在这里插入图片描述
到此为止,所有三个打印函数已经全部实现!
给出完美版本的print.S:

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

section .data
buffer dq 0

[bits 32]
section .text
;------------ put str -----------------
global put_str
put_str:
	push ebx
	push ecx
	mov ebx, [esp + 12]
.print_loop:
	mov ecx, [ebx]
	cmp cl, 0
	je .print_end
	push ecx
	call put_char
	pop ecx
	add ebx, 1
	jmp .print_loop

.print_end:
	pop ecx
	pop ebx
	ret
	
;------------ put char ----------------
global put_char
put_char:
	pushad	; 备份所有寄存器
	mov ax, SELECTOR_VIDEO
	mov gs, ax
; 获取光标位置
	mov dx, 0x03d4	; 索引号端口
	mov al, 0x0e	;8位寄存器
	out dx, al
	mov dx, 0x03d5	; 数据端口
	in al, dx	; 读取高八位到al
	mov ah, al
	
	; 低八位同理
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	in al, dx

	mov bx, ax	; 将光标存入bx
	mov ecx, [esp + 36]	; 获取待打印的字符,其中0-4位是返回地址,4-36是寄存器
	cmp cl, 0xd
	jz .is_carriage_return
	cmp cl, 0xa
	jz .is_line_feed	; 二者都看成换行+回车
	cmp cl, 0x8	; 退格
	jz .is_backspace
	jmp .put_other

.is_backspace:	; 退格并将退后的字符设置为空
	cmp bx, 0
	je .set_cursor	
	
	dec bx
	shl bx, 1	; 每次读写光标必须乘2,因为每个位置用2字节表示,1字节代表ascii,1字节代表状态
	
	mov word [gs:bx], 0x0720
	shr bx, 1
	jmp .set_cursor

.put_other:
	shl bx, 1
	mov [gs:bx], cl
	inc bx
	mov byte [gs:bx], 0x07
	inc bx
	shr bx, 1
	cmp bx, 2000
	jl .set_cursor

.is_line_feed:
.is_carriage_return:
	xor dx, dx
	mov ax, bx
	mov si, 80
	div si
	sub bx, dx

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

; 1.1-24行搬到0-23.roll_screen:
	cld
	mov ecx, 960	; 960 = (2000 - 80) * 2 / 4
	mov esi, 0xc00b80a0
	mov edi, 0xc00b8000
	rep movsd
; 2. 将最后一行填充为空
mov ebx, 3840
mov ecx, 80

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

; 将当前光标写回显存
.set_cursor:
	; 高八位
	mov dx, 0x03d4
	mov al, 0x0e
	out dx, al
	mov dx, 0x03d5
	mov al, bh
	out dx, al
	
	; 低八位
	mov dx, 0x03d4
	mov al, 0x0f
	out dx, al
	mov dx, 0x03d5
	mov al, bl
	out dx, al
.put_char_done:
	popad
	ret

;------------put_int------------
global put_int
put_int:
	pushad
	mov ebp, esp
	add ebp, 36
	mov eax, [ebp]	; point to the num
	mov edi, buffer
	add edi, 7 ; move to the last location
	mov ecx, 8	; num of loop
deal_with_num:
	mov ebx, eax
	and ebx, 0x0000000f
	shr eax, 4
	cmp bl, 0x9	; judge num
	jg A2F	; if greater than 9, go to A2F
	add bl, '0'	; else add '0'
	jmp store	; cross A2F
A2F:
	sub bl, 10
	add bl, 'A'
store:
	mov byte [edi], bl	; store it
	dec edi	; point to the next location
	loop deal_with_num
	mov ecx, 8
	xor edx, edx
	mov edi, buffer
check_if_all_0:
	mov bl, [edi + edx]
	cmp bl, '0'
	jne do_print_num
	inc edx	; count num of '0'
	loop check_if_all_0

	cmp edx, 8
	jne do_print_num
print_all_0:
	mov ecx, '0'
	push ecx
	call put_char
	pop ecx
	jmp put_int_end
do_print_num:
	cmp edx, 8
	je put_int_end
	mov ecx, [edi + edx]
	push ecx
	call put_char
	pop ecx
	inc edx
	jmp do_print_num
put_int_end:
	popad
	ret

什么是内联汇编

一句话,在C中嵌入汇编,提升代码的“战斗力”。
总共花了30分钟左右把最后一章过了一遍,第6章正式结束。

结语

原来第五章埋的雷在这一章爆了,导致调试调了快一整天,最后看到bochs中如我心意地打出字符和数字时,真的很开心,原来最简单的一句printf(不完全的),如果全部手写,都需要我花一整天啊。
总的来说,感觉自己对于汇编的知识提升又高了一些,但是还有很长的路要走啊,预计接下来两天把第六章——中断给吃完吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值