我记得我以前学习的时候卡在了,这一章,有些时间。我也会尽量在作者之外描述出来,尽量易懂些。
还记得以前保存的屏幕分辨率信息,我们是存在了内存里面,而在昨天我们直接写320这样的数字,万一等到后来调分辨率,就尴尬了。所以我们来读取我们之前写的吧。
binfo_scrnx = (short *) 0x0ff4;
binfo_scrny = (short *) 0x0ff6;
binfo_vram = (int *) 0x0ff8;
xsize = *binfo_scrnx;
ysize = *binfo_scrny;
vram = (char *) *binfo_vram;
由于之前就是保存在这些地址的,主要是注意一下类型,是short类型的也就是一个word
结构体的导入
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
struct BOOTINFO *binfo;
init_palette();
binfo = (struct BOOTINFO *) 0x0ff0;
xsize = (*binfo).scrnx;
ysize = (*binfo).scrny;
vram = (*binfo).vram;
注意看一下我们定义的结构体,都是由一定顺序的,是因为我们在对这个结构体进行初始化的时候是按照0x0ff0来的,会依次向后读取内存数据来填充结构体变量值。
指针玩儿法
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
因为会常常(*binfo).scrnx 这里给了一个语法糖,这样的情况下用->就好啦。
显示一个字符
之前显示过字符,但是是调用BIOS自带的功能来调用的。既然能填写像素的方式画出一个巨型,举一反三嘛,我们想画啥都可以,当然也可以画一个字出来。
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /* data */;
for (i = 0; i < 16; i++) {
p = vram + (y + i) * xsize + 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;
}
来看下代码,首先遍历16此,是因为十六行。 然后d分别与0x80,0x40,0x20..等分别进行与操作,这些数这样看,看不出来,如果你写出二进制你就看的比较明白了 0x80->1000 000 0x40->0100 0000 可以发现其实是二进制挨着往右移动后的值。然后来说说怎么控制的坐标,首先vram是显卡地址开始,然后(y + i)控制的是每行的位置, * xsize的原因是一行又xsize宽度,x上之后则定位到具体某一行上,最后加x的原因就是,在该行,以x的位置开始显示字符。
读取字符库
当然像上面那样写,成功的显示了字符,通常讲,字符库会有很多很多,那么多艺术设计,那么多种类的语言,需要别人帮助我们去设计,所以我们从外部来读取最好。所以接下来读取一个字符库会。
首先的是需要把这个txt文件编译到我们的操作系统中去,作者使用的是makefont工具,他解释道,其实就是定义了一个tag的感觉,叫做_hankanku 这个,然后这个TAG下定义了那4096个字节。来细看一下,makefile里面的文件,
可以看到会先把我们的hankaku.txt制作成kankaku.obj,并且在48行,传入了一个参数_hankaku(这个参数就是拿来给我们在C语言中用的标记,当然你可以改成其他的名字,但是一定不能把 ‘_’ 去掉,因为这是链接的意思),有了这个hankaku.obj,我们会把他做成bootpack.bim。然后是bootpack.hrb,最后是haribote.sys,再写入待haribote.img中去。
所以我们在C语言中可以名正言顺的来写这个变量啦。
extern char hankaku[4096];
putfont8(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
putfont8(binfo->vram, binfo->scrnx, 16, 8, COL8_FFFFFF, hankaku + 'B' * 16);
putfont8(binfo->vram, binfo->scrnx, 24, 8, COL8_FFFFFF, hankaku + 'C' * 16);
putfont8(binfo->vram, binfo->scrnx, 40, 8, COL8_FFFFFF, hankaku + '1' * 16);
putfont8(binfo->vram, binfo->scrnx, 48, 8, COL8_FFFFFF, hankaku + '2' * 16);
putfont8(binfo->vram, binfo->scrnx, 56, 8, COL8_FFFFFF, hankaku + '3' * 16);
拿到这个字符库变量后就好办啦,接下来就是对他进行读取,由于C语言中会把'A'解释位0x41所以作者直接就写成了 hankaku + 'A',最后X16的原因就是一个字符16个字节,第N个字符就是从 X16开始。
由于刚才那个还是有点麻烦,又继续简化了一下
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for (; *s != 0x00; s++) {
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}
其实比较好理解,原理就是在之前的putfont8函数之上封装了一个函数,负责解析成类似与刚才写了那么多重复的代码的功能。
打印调试
写程序过程中,最重要的就是看到某个值的变化情况,所以这里我们要做这样一个功能。sprintf就是往指定内存中写数据,然后我们再利用我们刚写的putfonts8_asc来输出这个地址的值,就达到了输出的作用啦。
显示鼠标
既然能显示出来一个字符,那么按照原理来显示一个鼠标箭头也就是理所应当啦。
void init_mouse_cursor8(char *mouse, char bc)
/* ƒ}ƒEƒXƒJ[ƒ\ƒ‹‚ð€”õi16x16j */
{
static char cursor[16][16] = {
"**************..",
"*OOOOOOOOOOO*...",
"*OOOOOOOOOO*....",
"*OOOOOOOOO*.....",
"*OOOOOOOO*......",
"*OOOOOOO*.......",
"*OOOOOOO*.......",
"*OOOOOOOO*......",
"*OOOO**OOO*.....",
"*OOO*..*OOO*....",
"*OO*....*OOO*...",
"*O*......*OOO*..",
"**........*OOO*.",
"*..........*OOO*",
"............*OO*",
".............***"
};
int x, y;
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
if (cursor[y][x] == '*') {
mouse[y * 16 + x] = COL8_000000;
}
if (cursor[y][x] == 'O') {
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.') {
mouse[y * 16 + x] = bc;
}
}
}
return;
}
我们定义了一个数组,让这个数组内部的数据看起来像鼠标指针,然后利用这些数据填入不同的颜色,绘制最终的鼠标。
void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize)
{
int x, y;
for (y = 0; y < pysize; y++) {
for (x = 0; x < pxsize; x++) {
vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];
}
}
return;
}
再根据刚才填好的颜色,放到显卡中去。
三四参数决定,这个鼠标的宽高,五六决定所在位置,mcursor:鼠标颜色形状。bxsize 鼠标宽度。
void init_gdtidt(void)
{
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;
int i;
// GDT的初始化
for (i = 0; i < 8192; i++) {
set_segmdesc(gdt + i, 0, 0, 0);
}
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
load_gdtr(0xffff, 0x00270000);
// IDT的初始化
for (i = 0; i < 256; i++) {
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(0x7ff, 0x0026f800);
return;
}
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff;
sd->base_low = base & 0xffff;
sd->base_mid = (base >> 16) & 0xff;
sd->access_right = ar & 0xff;
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
sd->base_high = (base >> 24) & 0xff;
return;
}
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */ // 0x8000 => 1000 0000 0000 0000 让ar的第一个一定是1
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff; // 只要limit低16位
sd->base_low = base & 0xffff; // 只要base低16位
sd->base_mid = (base >> 16) & 0xff; // 首先右移16位,然后与ff按位于,得到接下来8位
sd->access_right = ar & 0xff; // 取后8位
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); // ((limit >> 16) & 0x0f) 只要第五位(十六进制), 要ar中第四位
sd->base_high = (base >> 24) & 0xff; // 取第七八位(十六进制)
return;
}
首先是初始化8192所有地址,然后让 1,2分别变为ffffffff和7ffff,然后就没啦。。。。位运算相关的都写在注释里了