结构体
获取启动信息
我们需要获取我们之前使用ashead.asm设置的启动信息,以便于我们之后的开发
这些启动信息我们利用指针,定义指针,根据指针地址就能获取到定义的值,如下所示:
char *Scrnx = 0x0ff4;
char *Scrny = 0xff6;
char *vram = 0x0ff8;
试用结构体
但是这样的变量没有组织的散落在各个地方后期维护起来会有些困难,我们需要利用类似OOP的编程思想,将相同类型的数据统一封装到一个struct结构体中,如下所示:
struct BootInfo
{
// 引导扇区设置
char cyls;
// LED 指示灯状态
char leds;
// 关于颜色的信息
char vmode;
// 分辨率X
short scrnx;
// 分辨率y
short scrny;
// 图像缓冲区的起始地址
char *vram;
};
struct结构体占用内存大小就是struct中所有变量大小的和我们的例子就是12个字节。结构体.结构体变量与(结构体).结构体变量是不一样的。
使用structur的好处是在定义变量的时候如果参数变多的话会成以下这样子:
void pirnt(int x, int y, int a, int b....) {
}
如果函数中的参数变多的话,函数与函数之间调用传递参数的时候会很麻烦,如果使用结构体的话,之间将参数封装到结构体中,按结构体传参即可:
void print(struct BootInfo info) {
info.a
info.b
...
}
使用结构体时可以使用'.'来访问结构体中的变量。
如果使用结构体指针的话则需要使用结构体指针->变量或是*(结构体指针).变量来访问就结构体指针变量。
显示字符
因为从实模式切换到保护模式就没办法使用BIOS中断向量表里的方法了直接在屏幕中输入字符了,所有我们需要构建一个字体库,根据字体库来大于字符串。
如下图所示,下图是一个18*18的字符A,我们可以将有像素的像素点设置成1,没像素的像素点设置为0。在输出字母的时候根据二进制10就可以输出我们想要的字符了。
c语言没办法定义二进制,如果将以上二进制数据整合到c语言中则是以下变量:
static char font_A[16] = {
0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00};
接下来我们以8位为一组,遍历每一位,如果为1则在屏幕上在该地方图上一个像素点。遍历的方法就是对每一位进行与运算,如果运算结果大于 不等于0则表示这一位的等于1。与运算的规则则是两边数值进行比较如果两边的二进制都是1,那么运算结果为1,如果是0,那么运算结果为0。例如我们有一个二进制数: 1011,我们想要判断它的第三位是否为1,我们可以这样进行与运算。
1011
0010
0010
运算结果大于0那么就表示该位数是等于1的。
显存的计算地址和矩形的计算地址一样第几行 y + i是因为上面的那个字符序列共有十六行,每遍历一次需要换一次行,x和之前一样,向左空出多少个像素点来进行展示。一个字符需要空出十六行来进行展示,所以要遍历十六次。
/**
* vram: 显存地址
* xSize: 每行像素数 320 * 200就是320
* c: 颜色下标
* x: 字符串显示x轴 从左到右空出多少个像素
* y: 字符显示y轴
* font: 字符序列
*/
void put_font_8(unsigned char *vram, int x_size, char c, int x, int y, char font[16])
{
int i;
char *p, d /* data */;
for (i = 0; i < 16; i++)
{
// 和矩形计算公式一样 根据行数和列数 计算出字符需要展示的位置
p = vram + (y + i) * x_size + x;
d = font[i];
if ((d & 0x80) != 0)
{
p[0] = c;
}
if ((d & 0x40) != 0)
{
p[1] = c;
}
if ((d & 0x20) != 0)
{
p[2] = c;
}
if ((d & 0x10) != 0)
{
p[3] = c;
}
if ((d & 0x08) != 0)
{
p[4] = c;
}
if ((d & 0x04) != 0)
{
p[5] = c;
}
if ((d & 0x02) != 0)
{
p[6] = c;
}
if ((d & 0x01) != 0)
{
p[7] = c;
}
}
return;
}
在main方法中调用函数,运行可得
put_font_8(boot_info->vram, boot_info->scrx, 0x0E, 8, 8, font_A);
其实别的字符也可使用这种方式来进行定义,但是一个一个定义太麻烦了,我们就直接使用书中案例中定义好的编译到我们程序中使用算法,将文件hankaku.txt保存到我们的项目中,使用
makefont hankaku.txt font.bin 将txt文件转成.bin文件
bin2obj font.bin font.obj _font 在将.bin文件专场obj文件,_font定义的就在汇编中展示的全局遍历的内容,如果是_font的话则是这样展示的
_font:
db 0x00 0x12
之后在于bockpack进行链接即可
obj2bim @D:\os\os\tolset\z_tools\haribote\haribote.rul out:bootpack.bim stack:3136k map:bootpack.map .\bootpack.obj naskfun.obj font.obj
在使用时我们在项目中定义以下font遍历即可,遍历名称就是我们之前定义的_font,长度是4096 / 16 = 256个字符。
extern char font[4096];
它这在hankaku.txt中定义的文件序列似乎与ascill码相同,在使用的时候直接根据ascill码的序列 * 16就能找到我们想要找到字符数据了。
put_font_8(boot_info->vram, boot_info->scrx, 0x0E, 8, 8, font + 'A' * 16);
显示字符串
显示字符串的话也挺简单的,因为在c语言中,字符串的末尾是\0就是0x00,我们只要遍历字符串,如果到底0x00了,那么就表示到字符串末尾了,在遍历的过程中我们将每一位字符串进行大于,字符与字符之间间隔8个像素点就可以了。
void printFont8_ascii(char *vram, int x_size, int x, int y, char color, unsigned char *str)
{
// 末尾字符是0x00
for (; *str != 0x00; str++)
{
put_font_8(vram, x_size, color, x, y, font + *str * 16);
x += 8;
}
}
使用如下:
printFont8_ascii(boot_info->vram, boot_info->scrnx, 10, 10, 0x0e, "Hello lyra OS。");
输出内存地址
如果系统出问题了,在debug的过程中,需要知道内存中的值是什么,由于各个系统之间格式化输出都是不一样的,所以没办法使用printf直接进行输出。但是我们可以将字符串写入到内存中,在由我们之前写好的字符串输出程序进行输出,想要使用这个函数我们需要导入stdio.h,函数的参数为:变量地址、值、值。
如下所示,先获取bootinfo中部存储的scrnx大小,然后保存到变量c中:
sprintf(s, "size_x:%d", boot_info->scrnx);
最后使用我们定义好的字符串对这个变量进行输出就可以了。
printFont8_ascii(boot_info->vram, boot_info->scrnx, 10, 10, 0x0e, s);
由于c语言没有自带这个函数,我们需要引入叫go的c语言编译器的include才能够使用,使用ccl --help可以查询cc1的命令帮助
很明显,这个命令是我们需要的,这个命令可以将.h文件导入到我们项目中。在编译的时候添加这个参数就可以使用了sprintf函数了。
cc1 -I D:\os\30dayMakeOS\tolset\z_tools\haribote -o bootpack.gas .\bootpack.c
运行结果: