文章目录
环境
这篇开始换用CentOS,不知道是不是因为MinGW,总之在Windows下编译链接一直各种各样的问题,bochs连OS内核都加载不进去。
代码目录说明
boot/mbr.S
:mbr代码
boot/loader.S
:内核加载器代码
boot/include/boot.inc
:mbr与loader两份汇编代码的公用宏定义
kernel/main.c
:内核代码主文件
lib/kernel
:内核程序用到的库文件
lib/user
:用户程序用到的库文件
编译顺序:
mbr单独编译为bin文件,写入硬盘第一扇区
loader单独编译为bin文件,写入硬盘第三扇区
print.S单独编译生成.o文件(elf格式)
main.c单独编译生成.o文件
链接main.o和print.o生成kernel.bin,写入硬盘第十扇区
VGA寄存器
寄存器子类分为Address Register
和Data Register
的原因:一个寄存器分组下会有多个寄存器,要想对其中的某一个寄存器进行操作,就使用Address Register
的内容指出此寄存器在寄存器数组中的下标,将Data Register
作为此寄存器的替身,对Data Register
进行的读写操作可间接作用于此寄存器上。
CRT Controller Registers
寄存器组中的AR
和DR
的端口地址比较特殊,其端口地址不是固定的,而是含有x,x的值取决于Miscellaneous Output Register
寄存器中的Input/Output Address Select
字段,即I/OAS
。
当此位为0时,x取值为B
;当此位为1时,x取D
。
默认为1。
CRT Controller Registers寄存器组中的各索引及其对应寄存器如下:
打印单个字符
数据类型
对一些数据类型的宏定义。
总体流程
总体处理流程如下:
- 备份寄存器现场。
- 获取光标坐标值,作为下一个可打印字符的位置。
- 获取待打印的字符。
- 判断字符是否为控制字符,若为回车符、换行符、退格符之一,则进入相应的处理流程;否则按可见字符处理,进行输出。
- 判断是否需要滚屏显示。
- 更新光标的坐标值,使其指向下一个打印字符的位置。
- 恢复寄存器现场,退出。
以下所有的函数都在print.S
中实现,拆分开来讲解。
put_char
打印原理是直接写显存。对内存中的视频段进行操作,需要有视频段选择子和段内偏移。
- 第一到三行:定义了视频段的选择子。
- 第十二行:使用
pushad
指令备份全部的32位寄存器,压栈顺序为EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI。 - 第十四、十五行:段寄存器GS赋值,保证在写显存前,GS中的值是正确的段选择子。
- 第十七到三十四行:获取光标位置。光标位置是一维的,如80*25的窗口尺寸,共可容纳2000个字符,所以光标位置的取值为
0~1999
。光标位置的低8位和高8位分别放在CRT Controller Registers中索引为0EH
和0FH
的两个寄存器中。以取高8位为例:先往端口地址为0x03D4
的Address Register写入索引0x0EH
,然后读端口地址为0x03D5
的Data Register,获得光标的高8位,并将其存至ah寄存器的高8位。低8位同理,再将光标位置复制到bx寄存器中。 - 第三十六行:获取待打印字符的ASCII码。调用规则是压栈一个字符的ASCII码后,再调用
put_char
函数,调用时会将其4B的返回地址压栈,且函数中使用了pushad
指令压入了8个4B的寄存器数据,共32B,故待打印的字符ASCII码在栈顶偏移36B的位置。 - 第三十七到四十四行:对字符ASCII进行判断(回车?换行?退格?可见字符?)并跳转至相应处理函数。
is_backspace
退格键的处理原理:将光标向回移动一个位置,并将该处的字符用空格覆盖。
put_other
处理可见字符。
处理LF与CR
roll_screen
处理滚屏
set_cursor
设置光标
put_char_done
测试
内核主代码如下