由于目前的操作系统是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