OS开发(3) - 丰富我们的OS

为OS添加基础库

正如之前所说,我们的OS最终会在另一个机器平台上运行,所以我们不能使用当前开发环境的libc库,只能使用编译器提供的库。如果想使用C语言的标准库内容,就需要自己去编写(自己造轮子)。这里依旧参考 osdev 的项目结构,分析每个路径的作用,但由于篇幅问题就不列出代码了,可以参考我的Github:https://github.com/hyt658/SteinsOS。

  • kernel/: OS的内核
    • kernel/arch: architecture,里面有多个文件夹代表了OS在不同架构上机器的引导和启动方式。此时我们只有i386,里面有在上一章提到的boot.S,linker.ld等来让OS可以在i386架构上正常运行
    • kernel/include: 这里是所有kernel功能的header文件,其中kernel/include/kernel中的header文件是kernel的核心内容,kernel/include中其余的header文件是扩充kernel的功能。
    • kernel/startup: 这里是启动kernel的函数文件,里面目前只有kmain。boot.S最终会引导从kmain开始运行kernel。
    • 其余文件夹都会是kernel功能的实现。目前还没什么,之后会有类似于thread,process,lock之类的内容。
  • libc/: 这里存放着OS的libc库
    • libc/include: libc库的头文件的文件夹(暴露给OS用户去调用)
    • 其余文件夹对应着C语言的标准库(stdio,stdlib等)

制作更舒适的输出界面

我们在libc库中完成了printf等函数,可以用C语言的风格来输出内容。但此时的输出界面多少还是有些简陋,他不会像一般终端一样有光标闪烁提示下一个输入位置在哪,也不会在输出内容很多的时候向上滚动,只会在整个界面占满后回到左上角重新开始覆盖书写。这里我们来给他升个级(具体代码都在kernel/arch/i386中)。

我们当前使用的是VGA文本模式来作为输出界面,这里先介绍一下如何与其互动。在电脑运行的过程中,CPU只能访问内存中的数据,想要对其他硬件设备控制和交换数据,就要用到I/O端口。电脑的部分物理内存会被划分成不同区来映射给不同的硬件,这些物理内存地址就是不同的I/O端口。访问这些I/O端口就可以和它们所映射的硬件设备沟通。VGA有很多I/O端口,我们这里只会用到两个:

  1. 0x3D4:寄存器地址端口
  2. 0x3D5:寄存器数据端口

VGA硬件自身有很多寄存器,更改这些寄存器的值就可以更改VGA的行为。但我们不可能把所有寄存器都映射到电脑物理内存上,所以CPU想要更改VGA的行为,需要先访问端口来选择要更改的寄存器,在更改其中的内容。这里在介绍几个我们用得到的VGA寄存器的地址:

  1. 0x0A:光标起始寄存器
  2. 0x0B:光标结束寄存器
  3. 0x0E:光标位置寄存器(高8位)
  4. 0x0F:光标位置寄存器(低8位)

最后,需要介绍一些x86汇编指令:outbinb。它们是x86和I/O端口互动的汇编指令,有许多变种,如outwinw。结尾的bw是代表我们传输的数据是byte(1字节)还是word(2字节)。这里我们使用byte版本就行。gcc默认使用AT&T语法。

  1. outb的用法是:outb %al %dl,用到了aldl两个cpu寄存器,指把al中的值输出到dl中地址所在的端口。
  2. inb的用法是:inb %al %dl,类似的,从dl中地址的端口获取输入,并把输入值存入al中。

由于我真的不喜欢直接写汇编文件再做链接,于是用了C语言的内联汇编__asm__来实现outbinb。具体为:

/* 把val输出到port端口 */
static inline void 
outb(uint16_t port, uint8_t val) 
{
    __asm__ volatile ("outb %0, %1\n\t"
                        : /* no output */
                        : "a"(val), "d"(port)
                        : /* no registers modified */);
}

/* 从port端口获取输入存在result中 */
static inline void
inb(uint16_t port, uint8_t* result)
{
    __asm__ volatile ("inb %1, %0"
                        : "=a"(*result)
                        : "d"(port)
                        : /* no registers modified */);
}

内联汇编语法可以参考 这篇文章 ,对我帮助蛮大,内容也挺全。

首先开始实现光标,VGA寄存器的规格标准可以在 这里 查到。想要启用光标,就需要设置他的起始和结束扫描线位置。VGA文本模式默认是80x25的窗口大小,可以看成他把这个窗口划分成80x25个小格子,每个格子是一个字符,其中每个字符是由9x16的像素组成。扫描线就是每个字符格中的每一行,所以每个字符有16行扫描线。终端上的光标一般是一个下划线或一整个字符块,而不是文档中的竖线。如果我们设置起始扫描线是14结束扫描线是15,那么光标就是下划线的样子。若是0到15,那就是整个字符块。也就是说,起始和结束扫描线决定了光标的厚度。接着以0x0A寄存器举例:

image-20230304164405469

第5位是cursor disable,为0启用光标,为1禁用。0-4位是起始扫描线的位置。我们想让第5位为0,0-4为0,则:

outb(0x3D4, 0x0A);						// 输出0x0A到端口0x3D4来表示我们接下来要对0x0A寄存器进行操作
inb(0x3D5, &result);					// 从数据端口获取0x0A寄存器原本的值
result = (result & 0xC0) | 0			// 0xC0是二进制11000000,和result做and操作可以保留前两位并把0-5位清零
    									//	再和0做or操作可以set0-4位为0来设置起始扫描线
outb(0x3D5, result)						// 最后将result输出回去更新VGA行为

其他寄存器(结束扫描线、光标位置等)操作类似,具体可以参考我的gihub。

最后就是滚动终端。这个其实很好实现,只需要每行去copy它下一行的内容来更新自己,然后清空最后一行就可以实现滚动了。代码实现也就是两层loop,没什么难点,不再叙述。

编译代码

在平常写C/C++代码的时候,gcc默认会在本地开发环境的/usr/include或/include路径下寻找我们的#include <...>头文件。由于我们开发OS不应该依赖于本地库,所以在之前在构建编译器gcc的时候增加了--without-headers参数,它就不会再去本地/usr/include或/include下寻找头文件。当我们在代码中搭建好libc后,编译后的libc.a(libk.a)就是我们目标OS的库,我们就可以引用这个库去做后续开发。想要引用这个库的对应头文件,需要重新构建gcc并加上--with-sysroot参数来允许gcc自动在${sysroot}/usr/include下寻找头文件(注意--with-sysroot--without-headers并不冲突,前者是在本地根目录下的/usr/inlcude,后者是指定${sysroot}下的/usr/include)。或者,暂时的,在编译的时候加上--sysroot=${sysroot}来告诉gcc系统根在哪并加上-isystem=/usr/include来告诉gcc要在${sysroot}的哪里寻找头文件。

${sysroot}是一个空文件夹,当我们构建我们的OS的时候,${sysroot}就是最终系统的根目录,我们需要把kernel,程序,头文件等依次装入其中,然后再引导启动的时候告诉BIOS我们的kernel在哪个路径中。预设是kernel在${sysroot}/boot,二进制程序文件在${sysroot}/lib(或者libc),头文件在$sysroot/usr/include中。

至于编译代码的方法,我选择了两层Makefile:最外层一个Makefile控制编译顺序,每个子文件夹各带一个Makefile来编译自己的内容。osdev的那个bash脚本编译个人感觉有点怪…很简单的东西他非要设置一堆变量然后绕好几圈,可能以后扩展方便吧。如果看不太懂osdev的编译过程在干什么,可以参考我的Github库。这里需要注意的是,由于我们编译的时候指定gcc去${sysroot}/usr/include寻找头文件,所以在真正编译.c文件前,一定要先把对应的头文件都拷贝到${sysroot}/usr/include中,不然找不到。最后的构建结果就在${sysroot}中。


请我喝杯咖啡吧 ヾ(^▽^*)))
hyt658 - 支付宝 支付宝
hyt658 - 微信 微信
  • 文章标题: OS开发(3) - 丰富我们的OS
  • 本文作者: hyt658
  • 本文链接: /3.os-meaty-skeleton.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!