Advertisement

30天自制操作系统(day5)

阅读量:

第5天:结构体、文字显示与GDT/IDT初始化

**一、实验主要内容
1、内容1:**接收启动信息
在原先的bookpack.c中,都是将0xa0000,320,200等数字直接写入程序,但这些值本来应该从asmhead.nas先前保存下来的值取,否则当画面模式改变时,系统就不能正常运行。采用指针去除上述数值:
在这里插入图片描述
这里的0x0ff4之类的地址是为了与asmhead.nas保持一致才出现的。

**2、内容2:**试用结构体
采用结构体来简化程序,减少代码的行数
在这里插入图片描述
结构体命令将一串变量的声明集中起来,统一叫做“struct BOOTINFO”。最初是一字节的变量cyls,接着是1字节的变量leds,最后是vram。所有变量一共是12字节。
info指针指向地址0x0ff0是结构体的起始地址,结构体内的地址则依次按其类型增加。所以可以不用直接使用内存地址,而是使用*binfo来表示这个内存地址上12个字节的结构体。有了上述声明,“struct BOOTINFO”便可以作为一个新的变量类型,可以像int、char一样使用。*binfo便是这种类型的变量,其是一个4字节的变量。为了表示其中的scrnx,可以使用(*binfo).scrnx写法。
在这里定义了一个新结构体变量,然后再给这个结构体变量的各个元素赋值。结构体的好处是可以将各个元素一起传递。如果没有结构体,就只能将各个参数一个一个传递。

3、内容3:试用箭头记号
在这里可以使用一种不是使用括号的省略表现形式,即binfo->scrnx来表示类似于(*binfo).scrnx的表现手法,也就是所谓的箭头标记形式。
在这里插入图片描述
这样可以使代码更加清晰。(实际上就是结构体使用的时候不同的语法形式罢了)

4、内容4:显示字符
实现在画面上写字,之前显示字符主要靠调用bios函数,但这次是32位模式,不能再使用bios。字符可以用8x16的长方形像素点阵来表示。将点阵中的数据置换成0或者1,8位是一个字节,而1个字符是16字节。如图:
在这里插入图片描述
将这些数据称之为字体数据,并将0、1排列重写成十六进制的数并存储下来。用for语句将画8个像素的程序循环16遍,便可以显示一个字符了。
在这里插入图片描述
在这里插入图片描述
以第2行为例,00011000分别与上0x80-0x01,即判断8位上是否有1,如果有1,便将色号存储到该点的内存中。这样便可以显示出相应的字符了。结果如下:
在这里插入图片描述

**5、内容5:**增加字体
如果要显示更多的字符,用上述方法太麻烦,于是便引入OSASK的字体数据。
首先将hankaku.txt添加到源程序中,其的内容如下:
在这里插入图片描述
由于这既不是C语言也不是汇编语言,因此需要一个专用的编辑器。这里基本上是一个256的ASCII码表。其将上面的文本文件(256个字符的字体文件)读进来,然后输出成16x256=4096字节的文件。
编译生成hankaku.bin文件,并为其加上连接所必需的接口信息,将其变成目标文件。像这种在源程序以外准备的数据,需要加上extern属性,这样,c编译器便能够知道它是外部数据,并在编译时做出相应调整。
在这里插入图片描述
在这里插入图片描述
OSASK的字体数据,依照一般的ASCII字符编码,含有256个字符。A的字符编码是0x41,因此A的字体数据,放在自“hankaku+0x41*16”开始的16字节里。C语言中的A字符编码可以用‘A’来表示,所以也可以写成“hankaku+‘A’*16”。
在这里插入图片描述
结果如下:
在这里插入图片描述

**6、内容6:**显示字符串
通过设计显示函数来显示字符串:
在这里插入图片描述
为什么x要加8?
因为每显示一个字符,x方向上需要右移一位来显示接下来的字符。
在这里,所谓的字符串是指按顺序排列在内存中,末尾加上0x00而组成的字符编码,s是指字符串前头的地址,而使用*s就可以读取字符编码。
在这里插入图片描述
实验结果:
在这里插入图片描述

**7、内容7:**显示变量值
当程序运行与想象中不一致时,可以选择将可疑变量的值显示出来以做对比。
那怎样才可以显示变量的值呢?
在这里可以选择使用sprintf函数,其是printf函数的同类,与printf函数的功能很相近。但与printf不同的是,其并不是按指定格式输出,只是将输出内容作为字符串写在内存中。
这个sprintf函数,是本次使用的名为GO的C编译器附带的函数,其只对内存进行操作,但能不适用操作系统的任何功能,因此其可以应用与所有的操作系统。要在C语言中使用sprintf函数,需要在源程序的开头写上# include <stdio.h>。
Sprintf函数的使用方法是:sprintf(地址,格式,值,值,值…)。这里的地址是指定所生成字符串的存放地址。格式基本上是单纯单纯额的字符串,如果有%d这类的符号,就置换成后面的值的内容。%d将数值作为十进制数转化为字符串。
在这里插入图片描述
这里相当于是把scrnx = binfo->scrnx这个字符串放到了s内存地址的位置,格式基本上是单纯的字符串,然后通过调用putfonts8_asc函数,依次将从s地址开始的内存部分的数据以char的形式进行输出,直到地址位置的内容为空。
在这里插入图片描述
实验结果:
在这里插入图片描述

**8、内容8:**显示鼠标指针
首先将鼠标指针的大小定为16x16,准备16x16=256字节的内存,然后往里面写入鼠标指针的数据。
在这里插入图片描述
当读取遇到*号时,向指定内存中填入黑色,当遇到O号时,向指定内存填土白色,当遇到. 号时,向指定内存中填入背景色。要将背景色显示出来,需要制作一个将buf中的数据复制到vram中的函数。
在这里插入图片描述
这里的vram和vxsize是关于VRAM的信息,他们的值分别是0xa0000和320.pxsize和pysize是想要显示的图形的大小,鼠标指针的大小是16x16,因此这两个值都是16.px0和py0指定图形在画面上的显示位置。Buf和bxsize分别指定图形的存放地址和每一行含有的像素数。
在这里插入图片描述
首先进行初始化,然后通过mx和my确定鼠标的位置,再调用init_mouse_cursor8函数将鼠标的显示图形初始化,再通过调用putblock8_8函数将鼠标进行打印,再通过sprintf函数将mx和my这两个坐标写入内存中,通过调用putfonts8_asc函数将坐标在左上角进行打印。
在这里插入图片描述

**9、内容9:**GDT和IDT的初始化
如何才能使设计的鼠标可以移动呢?
首先需要将GDT和IDT初始化,但什么是GDT和IDT呢?
GDT与IDT都是与CPU有关的设定,为了让操作系统能够使用32模式,需要对CPU做各种设定。为了能使操作系统同时运行多个程序,但又要避免内存的使用范围重叠。解决这种问题的方法便是分段。
所谓分段就是将合计4GB的内存分成很多块,每一块的起始地址都看作0来处理。按这种分段方法,为了表示一个段,需要有以下信息。
段的大小
段的起始地址
段的管理属性(禁止写入,禁止执行,系统专用等)
CPU用8个字节(=64)的数据来表示这些信息。但用于指定段的寄存器只有16位,因此模仿图像调色板的做法,先有一个段号,存放在段寄存器里,然后预先设定好段号与段的对应关系。段号可以用0-8191的数。
但为什么段号只可以用0-8191的数呢?
因为段寄存器是16位,本来应该能处理0-65535范围的数,但由于CPU设计上的原因,段寄存器的低3位不能使用,因此能够使用的段号只有13位,能够处理的就只有位于0-8191的区域了。8192*8=64kb,所以占据64kb的空间。
GDT是全局段号记录表,将这些数据整齐地排列排列按在内存的某个地方,然后将内存的起始地址和有效设定个数放在CPU内被称作GDTR的特殊寄存器中。
IDT是中断记录表,当CPU遇到外部状况变化,或者是内部偶然发生某些错误时,会临时切换过去处理这种突发事件,这就是中断。
什么是中断机制?
各个设备有变化时产生中断,中断发生后,CPU暂时停止正在处理的任务,并做好接下来能够继续处理的准备,转而执行中断程序。中断程序执行完以后,再调用事先设定好的函数,返回处理中的任务。IDT记录了0-255的中断号码与调用函数的对应关系。
在这里插入图片描述
变量gdt被赋值为0x00270000,也就是将0x270000-0x27ffff设为GDT。
变量idt也是一样,IDT被设为0x26f800-0x26ffff。
接下来对段号为1和2的两个段进行设定,段号为1的段,上限值为0xffffffff即大小正好是4GB,地址是0,它表示CPU所能管理的全部内存,段属性设为0x4092,为可读可写不可执行。段号为2的段,其大小是512KB,地址是0x280000,其表示的范围为0x280000-0x280000+0x070000。实际上第二段就是我们的type.c,即os,对应的是应该0x409a为可读不可写可执行属性。
Load_gdtr是通过汇编语言来给GDTR赋值。
运行之后,haribote.sys中已经有7632字节了。

二、遇到的问题及解决方法
1、问题1:
CPU是如何查找GDT的?
解决方法:利用了一个叫GDTR的寄存器,GDTR中高32位表示GDT在内存中的基地址,低16位表示表界限,所以就明确的给出了GDT在内存中的存储信息。
2、问题2:
注册IDT时,函数中声明的变量是什么意思?
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
解决方法:struct GATE_DESCRIPTOR _gd–表示要注册的IDT位置
int offset–偏移(这个偏移是在代码段中的偏移)
int selector–对应的段描述符的选择子
如果想要调用该函数,只需要向下述例子一样即可。
set_getdesc(idt+0x21,(int)asm_inthandler21,2_8,AR_INTGATE32)

三、程序设计创新点
能不能控制字号?
能不能控制颜色?
能不能弄成蹦迪???

全部评论 (0)

还没有任何评论哟~