前言
这一章没啥可讲的,主要就是实现一个类似putchar()
的函数,当然是使用纯汇编的,书上已经讲的很详细了,我主要加上了一些我自己的注释
代码
print.S
;--------步骤---------------------------
;1.备份寄存器现场
;2.获取光标坐标值,光标坐标值时下一个可打印字符的位置
;3.获取待打印的字符
;4.判断字符是否为控制字符,若是回车符,换行符,退格符三种控制字符之一,则进入
;相应流程,否则,其余字符否被粗暴的认为是可见字符,进入输出流程处理
;5.判断是否需要滚屏
;6.更新光标坐标值,使其指向下一个打印字符的位置
;7.恢复寄存器的现场,退出
TI_GDT equ 0
TI_GDT_RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT_RPL0
[bits 32]
section .text
put_int_buffer dq 0
;---------put_str--------------
;打印字符串
;------------------------------
global put_str
put_str:
push ebx
push ecx
xor ecx,ecx
mov ebx,[esp+12]
.goon:
mov cl,[ebx]
cmp cl,0
jz .str_over
push ecx
call put_char
add esp,4
inc ebx
jmp .goon
.str_over:
pop ecx
pop ebx
ret
;------------put_char-----------
;把栈中一个字符写入光标所在处
;------------------------------
global put_char
put_char:
pushad ;备份
mov ax,SELECTOR_VIDEO
mov gs,ax
mov dx,0x03d4 ;端口为0x03d4
mov al,0x0e ;得到光标的高8位地址
out dx,al ;写入端口,得到索引
mov dx,0x03d5 ;端口为0x03d5
in al,dx ;读到光标的高8位
mov ah,al
mov dx,0x03d4
mov al,0x0f ;光标的低8位
out dx,al
mov dx,0x03d5
in al,dx
mov bx,ax ;将光标存入bx中
;下面这行获取待打印的字符
mov ecx,[esp+36] ;pushad 占用了32字节,加上主调函数4字节的返回地址,36字节
cmp cl,0xd ;回车 ascii码
jz .is_carriage_return
cmp cl,0xa ;换行
jz .is_line_feed
cmp cl,0x8 ;空格
jz .is_backspace
jmp .put_other
.is_backspace:
dec bx ;bx存储着光标的地址,减一就是前一个,
shl bx,1 ;左移是因为显存中是用两个字节来存储字符的
mov byte [gs:bx],0x20 ;删除的字符复盖为0或者空格
inc bx
mov byte [gs:bx],0x07
shr bx,1
jmp .set_cursor
.put_other:
shl bx,1
mov [gs:bx],cl ;将字符输入到显存中
inc bx
mov byte [gs:bx],0x07 ;背景色
shr bx,1 ;右移动,下一个位置
inc bx
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 ;减去除以80的余数,再加上80就是下一行
.is_carriage_return_end:
add bx,80
cmp bx,2000
.is_line_feed_end:
jl .set_cursor ;小于2000个字符就设置光标,否则往下执行
.roll_screen:
cld ;清除标志位
mov ecx,960 ;每次复制4字节,一共3840字节,(2000-80)*2,所以有960次,
mov esi,0xc00b80a0 ;第1行的起始位置
mov edi,0xc00b8000 ;第0行的起始位置
rep movsd ;目的地址 edi,源地址 esi,大数据复制,由源地址复制到目的地址
mov ebx,3840 ;指向最后一行
mov ecx,80 ;循环次数80次,因为要把最后一行的字符都清除,每次清除1个字符
.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 ;恢复32字节
ret ;返回是弹出4字节,所以正好弹出了36字节
;-------整数打印--------------------
;put_int
;-----------------------------------
global put_int
put_int:
pushad
mov ebp,esp
mov eax,[ebp+4*9] ;把第一个参数给eax
mov edx,eax ;edx存储参数,也就是数字
mov edi,7 ;put_int_buffer的最后一个字节索引,从0开始
mov ecx,8 ;重复次数
mov ebx,put_int_buffer
.16based_4bits:
and edx,0x0000000f ;数字的末尾4位
cmp edx,9 ;进行转化
jg .is_A2F
add edx,'0'
jmp .store
.is_A2F:
sub edx,10
add edx,'A'
.store:
mov [ebx+edi],dl ;将转化后的字符存储在put_buffer_int中,此时是大端序
dec edi
shr eax,4
mov edx,eax
loop .16based_4bits
.ready_to_print:
inc edi
.skip_prefix_0:
cmp edi,8
je .full0
.go_on_skip:
mov cl,[put_int_buffer+edi]
inc edi
cmp cl,'0'
je .skip_prefix_0 ;消除掉前缀0
dec edi
jmp .put_each_num
.full0:
mov cl,'0'
.put_each_num:
push ecx
call put_char ;进行打印
add esp,4
inc edi
mov cl,[put_int_buffer+edi]
cmp edi,8
jl .put_each_num
popad
ret
main.c
;;主要测试一下会不会回滚
#include "print.h"
int main(void){
put_char('r');
put_char('e');
put_char('t');
put_char('u');
put_char('r');
put_char('n');
put_char('\n');
put_char('1');
put_char('2');
put_char('3');
put_char('4');
put_char('5');
put_char('\b');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_str("hello kernel!\n");
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_char('\n');
put_int(0x01);
put_char('\n');
put_int(0x00);
while(1);
return 0;
}
相关命令
nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
gcc -m32 -I lib/kernel/ -c -o lib/kernel/main.o lib/kernel/main.c
ld -m elf_i386 -Ttext 0xc0001500 -e main -o lib/kernel/kernel.bin \
lib/kernel/main.o lib/kernel/print.o