《操作系统真象还原》第六章——完善内核之实现单字符打印

由于目前的操作系统是64位的,因此使用默认的编译器编译出来的结果都是64位的

因此在最后的编译阶段需要降低gcc版本,同时使用-m32选项使编译出来的结果是32位,否则便会报错

gcc版本降低

1.打开apt-get源,增加内容。

 vim /etc/apt/sources.list
deb http://dk.archive.ubuntu.com/ubuntu/ trusty main universe
deb http://dk.archive.ubuntu.com/ubuntu/ trusty-updates main universe

2.更新apt源

sudo apt-get update

3.安装gcc4.4

sudo apt-get install g++-4.4

4.如果最后仍旧报错

sudo apt-get install libc6-dev-i386

stdint.h

#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int int64_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
#endif

print.S

处理流程

  • 备份寄存器现场。
  • 获取光标坐标值,光标坐标值是下一个可打印字符的位置
  • 获取待打印的字符。
  • 判断字符是否为控制字符,若是回车符、换行符、退格符三种控制字符之一,则进入相应的处理流程。否则,其余字符都被粗暴地认为是可见字符,进入输出流程处理。
  • 判断是否需要滚屏。
  • 更新光标坐标值,使其指向下一个打印字符的位置
  • 恢复寄存器现场,退出。
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0;定义显存段选择子

[bits 32]
section .text
global put_char                     ;通过global关键字将put_char函数定义为全局符号
                                    ;使其对外部文件可见
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
put_char:
    pushad                          ;push all double,压入所有双字长的寄存器
                                    ;入栈顺序为eax->ecx->edx->ebx->esp->ebp->esi->edi

    mov ax,SELECTOR_VIDEO
    mov gs,ax                       ;为gs寄存器赋予显存段的选择子

;以下代码用于获取光标的坐标位置,光标的坐标位置存放在光标坐标寄存器中
;其中索引为0eh的寄存器和索引为0fh的寄存器分别存放光标高8位和低8位
;访问CRT controller寄存器组的寄存器,需要先往端口地址为0x03d4的寄存器写入索引
;从端口地址为0x03d5的数据寄存器中读写数据
    mov dx,0x03d4                   ;将0x03d4的端口写入dx寄存器中
    mov al,0x0e                     ;将需要的索引值写入al寄存器中
    out dx,al                       ;向0x03d4端口写入0x0e索引
    mov dx,0x03d5                   
    in al,dx                        ;从0x03d5端口处获取光标高8位
    mov ah,al                       ;ax寄存器用于存放光标坐标,
                                    ;因此将光标坐标的高8位数据存放到ah中

;同上,以下代码获取光标坐标的低8位
    mov dx,0x03d4
    mov al,0x0f
    out dx,al
    mov dx,0x03d5
    in al,dx                        ;此时ax中就存放着读取到的光标坐标值
    mov bx,ax                       ;bx寄存器不仅是光标坐标值,同时也是下一个可打印字符的位置
                                    ;而我们习惯于bx作为基址寄存器,以后的处理都要基于bx寄存器
                                    ;因此才将获取到的光标坐标值赋值为bx
                                    

    mov ecx,[esp+36]                ;前边已经压入了8个双字(2个字节)的寄存器,
                                    ;加上put_char函数的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                  ;正常字符处理

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;退格键处理
    ;处理思路:
        ;1.将光标位置减一
        ;2.将待删除的字符使用空格字符(ASCII:0x20)代替
.is_backspace:
    dec bx                          ;bx中存储光标的坐标位置,将光标坐标位置减去一,即模拟退格
    shl bx,1                        ;由于文本模式下一个字符占用两个字节(第一个字节表示字符的ASCII码,第二个字节表示字符的属性),
                                    ;故光标位置乘以2就是光标处字符的第一个字节的偏移量
    mov byte[gs:bx],0x20            ;将空格键存入待删除字符处
    inc bx                          ;此时bx中存储的是字待删除字符的第一个字节位置,
                                    ;使用inc指令将bx加1后就是该字符的第二个字节的位置
    mov byte[gs:bx],0x07            ;将黑底白字(0x07)属性加入到该字符处
    shr bx,1                        ;bx除以2,恢复光标坐标位置
    jmp .set_cursor                 ;去设置光标位置, 这样光标位置才能真正在视觉上更新

;将cx指向的字符放入到光标处
.put_other:
    shl bx,1                        ;将光标坐标转换为内存偏移量
    mov byte[gs:bx],cl              ;将cx指向的字符放入到光标处
    inc bx                          ;bx指向当前字符的下一个字节处,存放当前字符属性
    mov byte[gs:bx],0x07            ;存放字符属性
    shr bx,1                        ;将内存偏移量恢复为光标坐标值
    inc bx                          ;bx指向下一个待写入字符位置
    cmp bx,2000                     ;80*25=2000,判断是否字符已经写满屏了
    jl .set_cursor                  ;更新光标坐标值


;换行处理
    ;思路:首先将光标移动到本行行首,之后再将光标移动到下一行行首
.is_line_feed:
.is_carriage_return:
    xor dx,dx
    ;将光标移动到本行行首
    mov ax,bx
    mov si,80
    div si                          ;除法操作,ax/si,结果ax存储商,dx存储余数
    sub bx,dx

    .is_carriage_return_end:
        add bx,80                   ;将光标移动到下一行行首
        cmp bx,2000
    .is_line_feed_end:
        jl .set_cursor

;滚屏处理
    ;思路:屏幕行范围是0~24,滚屏的原理是将屏幕的1~24行搬运到0~23行,再将第24行用空格填充
.roll_screen:
    cld                             ;清除方向标志位,字符串的内存移动地址从低地址向高地址移动
                                    ;若方向标志位被设置,则字符串的内存移动地址从高地址向低地址移动
    mov ecx,960                     ;共移动2000-80=192个字符,每个字符占2个字节,故共需移动1920*2=3840个字节
                                    ;movsd指令每次移动4个字节,故共需执行该指令3840/4=960次数
    mov esi,0xb80a0                 ;第一行行首地址,要复制的起始地址
    mov edi,0xb8000                 ;第0行行首地址,要复制的目的地址
    rep movsd                       ;rep(repeat)指令,重复执行movsd指令,执行的次数在ecx寄存器中

    ;将最后一行填充为空白
    mov ebx,3840
    mov ecx,80
    .cls:
        mov word[gs:ebx],0x0720
        add ebx,2
        loop .cls
        mov bx,1920                 ;将光标值重置为1920,最后一行的首字符.


.set_cursor:
    					                                    ;将光标设为bx值
    ;;;;;;; 1 先设置高8位 ;;;;;;;;
    mov dx, 0x03d4			                                ;索引寄存器
    mov al, 0x0e				                            ;用于提供光标位置的高8位
    out dx, al
    mov dx, 0x03d5			                                ;通过读写数据端口0x3d5来获得或设置光标位置 
    mov al, bh
    out dx, al

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

.put_char_done:
    popad
    ret

print.h

#ifndef __LIB_KERNEL_PRINT_H
#define __LIB_KERNEL_PRINT_H
#include "stdint.h"                 //我们的stdint.h中定义了数据类型,包含进来
void put_char(uint8_t char_asci);   //在stdint.h中uint8_t得到了定义,就是unsigned char
#endif

main.c 

#include"print.h"
int main()
// int _start(void)
{
    put_char('k');
    put_char('e');
    put_char('r');
    put_char('n');
    put_char('e');
    put_char('l');
    put_char('\n');
    put_char('1');
    put_char('2');
    put_char('\b');
    put_char('3');
    while (1);
    return 0;
}

目录结构

运行命令

#编译mbr
nasm -o ./build/mbr -I ./include/ ./src/mbr.S
dd if=./build/mbr of=~/bochs/hd60M.img bs=512 count=1 conv=notrunc

#编译loader
nasm -o ./build/loader -I ./include/ ./src/loader.S
dd if=./build/loader of=~/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc

#编译print函数
nasm -f elf -o ./build/print.o ./lib/kernel/print.S

#sudo apt-get install libc6-dev-i386

#编译main函数
gcc-4.4 -o build/main.o -c -m32 -I lib/kernel/ kernel/main.c
#将main函数与print函数进行链接
ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o build/print.o

#将内核文件写入磁盘,loader程序会将其加载到内存运行
dd if=./build/kernel.bin of=~/bochs/hd60M.img bs=512 count=200 conv=notrunc seek=9

#rm -rf build/*

运行结果

./bin/bochs -f boot.disk

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值