操作系统实验日志5
第5天:结构体、文字显示与GDT/IDT初始化
30天自制操作系统第五天
一、实验主要内容
1、 内容1:接收启动信息
由于我们之前的代码里面的值,例如xsize、ysize的值都是在中断号0x10下的模式,一旦我们切换了模式,代码就不能正常运行,所以现在通过指针来获得画面模式的信息,这样可以避免当画面模式改变时系统无法正常运行的问题。这里的0xff4等对应asmhead.nas中相应参数地址。画面模式的参数保存在那些地址中,画面模式的改变即那些参数的改变,所以我们这里借用指针可以避免这个问题。
2、 内容2:使用结构体
因为我们需要的信息是连续存储在一块的的,利用结构体获得这一块的信息,如下图:
代码中用了一个指向结构体BOOTINFO的指针,这个指针的地址是0x0ff0,即画面模式参数cyls存放的位置,这样接下来就可以通过原点运算符正确读取SCRNY、SCRNX、VRAM的值了。
注释:结构体的大小与内存对齐。结构体的大小不是结构体元素单纯相加就行的,因为我们主流的计算机使用的都是32bit字长的CPU,对这类型的CPU取4个字节的数要比取一个字节要高效,也更方便。所以我们在使用指向结构体指针的时候要注意这个问题,避免出现实际的数据是连续存储的而存放在结构体内的数据是发生内存对齐的。
3、 内容3:使用箭头记号
注释:圆点运算符是比较古老的写法,不能访问结构体指针变量成员,现在都推荐使用箭头运算符,即->。
4、 内容4:显示字符
原理:
①在之前的16位模式下,显示字符主要通过调用BIOS函数。我们通过汇编指令DB来使我们的操作系统打印字符。
②而我们现在处于32位模式下,无法调用BIOS函数。因此我们还是通过上一天的原理,改变显存中的值,对不同的像素进行填色,达到打印字符的目的。
具体实现:
将这些0和1的排列重写成十六进数进行声明:(因为c语言无法用二进制记录数据)
遍历每一个排列,是1的话就将其着色:
if中的语句是通过一个&运算来判断每个排列的第0到第7为是否为1,(x,y+i)是第i行的首位置。可以用这样一个指针来指向第i行的首位置,这样就可以用p[0]、p[1]…p[7]来表示每一行的8个像素的位置的值。
5、 内容5:增加字体
用一个txt文件来储存256个字符的显示方式,例如:
改写C语言代码:
c语言使用字体数据需要加上extern属性:
ps:像这种在源程序以外准备的数据都要加上该属性,这样c程序就能知道他是外部数据
当我们想要打印某一个字符的时候,只需要将代码写成:
我们的txt文件中有256个字符,每个字符的序号都和其ASCII码相同,而每个字符占16个字节,所以我们想得到某个字符的位置只需要计算hankaku+(其ASCII码)*16,ASCII码可以用‘字符’来代替。
编译过程:
注释:@这个符串通常用在“规则”行中,表示不显示命令本身,而只显示它的结果。
6、 内容6:显示字符串
这里其实就是将我们想要打印的字符串的首地址作为参数传进去,使用for循环遍历字符串的每个字符,对每个字符调用putfont8函数。
效果:
作者通过连续调用两个打印相同字符串的函数,仅仅稍微改变了下开始坐标,达到了一种阴影的效果。
7、 内容7:显示变量值
sprintf函数:是名为GO的C编译器附带的函数
功能:将输出的内容作为字符串写在内存中;
格式:sprintf(地址、格式,值,值……);
==ps:sprintf函数只是对内存操作,不使用操作系统的功能,所以可以使用。 ==
8、 内容8:显示鼠标指针
思路:确定鼠标的尺寸->拼出一个鼠标的图形的数组->遍历数组赋上不同的颜色->写入显存中
putblock8_8函数:通过将buf中的数组复制到vram中从而将背景色显示出来
解释:pxsize和pysize是要显示图片(鼠标)的大小,都为16。px0和py0制定图形(鼠标)在画面上显示的位置。buf和bxsize分别指定图形的存放地址和每一行含有的像素数。
9、 内容9:GDT与IDT的初始化(使鼠标移动的前提)
分段:
**目的:**分段是为了防止多个程序运行时,因为内存地址冲突而不能执行,按一定的方式将内存分为很多块。
段的表示:
①段的大小
②段的起始地址
③段的管理属性(禁止写入,禁止执行,系统专用等)
这些数据需要用8个字节(64位表示),但指定的段寄存器只有16位,所以需要先有段号再来对应段(例如调色板的方式)。段寄存器是16位,但段寄存器低3位无法使用,所以段号只有13位,即处理0~8191的区域,可定义8192个段。需要8192x8=65536个字节(64kb)。
关于段寄存器:在16位的机器上,由于地址线是20位,但是只有16位的寄存器,所以在计算地址的时候,使用段寄存器,将段寄存器中的基址×16在加上寄存器中的变址。也就意味着段的起始地址不能是任何一个地方,只是能整除 16 的地方。在32位机器上DS仍然是 16 位的,但是不再是段的起始地址。段的起始地址放在内存的某个地方。这个地方是一个表格,表格中的一项一项是段描述符。这里面才是真正的段的起始地址。而段寄存器里面保存的是在这个表格中的哪一项。
GDT:表示全区段号记录表(用来管理分段中段的信息)。
IDT:表示中断记录表(用来管理中断号和处理函数的)。要使用鼠标,就要进行中断。
GDT和IDT都是与CPU有关的设定,对其进行特定的设置,才能使得鼠标动起来。
GDT和IDT的相关数据是存放在内存中,GDT中内存的起始地址和有效设定个数存放在GDTR的寄存器中,而IDT则放在IDTR这个寄存器中。
初始化代码:
在第二个段中,即地址0x280000~0x2fffff中,其实已经加载了bootpack.h,这是操作系统所要执行的,所以我们的权限应该设置为可执行、刻度不可写。
load_idtr函数:C语言里不能为GDTR、IDTG寄存器赋值,所以要借助汇编代码。
LIDT和SIDT指令用于加载IDTR寄存器的内容。LIDT指令用于把内存中的限长值和基地址操作数加载到IDTR寄存器中。该指令仅能由当前特权级CPL是0的代码执行,通常被用于创建IDT时的操作系统初始化代码中。
(这个还有后面的函数看不太懂,第六天应该就会学习了,下次再将内容补上)
二、遇到的问题及解决方法
1.描述问题1:在第九部分,书上说MOV AL,[DS:EBX],无论是否指定DS,都会向EBX里加上DS所表示段的起始地址,但是以前我们接触的汇编程序都没有指明DS,有什么影响吗?
解决方法:在和聂文倩同学讨论了一下之后,想到之前的ORG指令,其实ORG指令就是标明段寄存器DS中的内容,如果没有ORG指令,则DS中内容默认为0,所以以前就没谈到这一点。
2.描述问题2:haribote/haribote.rul文件的作用是什么,map文件是什么,以及大小3136kb是怎么计算的?
解决方法: 没有查到这个map文件是什么,打开后看到里面内容是记录了可执行文件中各个段以及不同的函数的入口地址。
对于这个rul文件,我打开了它,并通过翻译软件对注释进行了翻译:
于是我推测,这个文件应该是起到在生成可执行文件之前的将动态或者静态链接库链接到程序中,此文件中描述的应该是静态链接库。
至于这个大小为什么是3136kb,那64kb应该是段号记录表的大小,至于另外的3M,我还没有搞明白,希望老师可以帮忙解答一下。
三、程序设计创新点
1、描述创新点1: 改变字体的大小
修改代码:
修改txt文件:
效果图:(左边是修改前的效果,右边是修改后的效果)
四、实验心得体会
本次实验比较轻松,原理还是第四天学过的,就是向VRAM中写入数据,然后给像素着色,不过方法更简便、封装的东西变得越来越多。渐渐的我们也可以和以前学过的知识里连起来,比如说中断、总线、cpu的控制、16位和32位操作系统的区别以及兼容性、保护模式和实模式等等。但是也有一些汇编代码是在很难理解,不过现在应该懂得实现的功能就好了,至于更多的细节,希望在接下来的学习中可以学到。