1.接收启动信息 & 2.试用结构体 & 3.试用箭头符号
在第五天之前,我们都是把vram、xsize、ysize这些值直接写在了bootpack.c文件中。而这些值应从asmhead.nas中获取,因为在asmhead.nas里面我们调用显卡函数并选择了模式,配置好了SCRNX,SCRNY,VRAM参数,如果不从asmhead.nas中获取,当画面模式改变时,系统就不能正常运行。
asmhead.nas中的相关代码:
;有关BOOT_INFO
CYLS EQU 0x0ff0 ;设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ;关于颜色数目的信息。颜色的位数
SCRNX EQU 0x0ff4 ;分辨率x(screen x)
SCRNY EQU 0x0ff6 ;分辨率y(screen y)
VRAM EQU 0x0ff8 ;图像缓冲区的开始地址
ORG 0xc200 ;这个程序要装载到内存中的地方
;VGA显卡 320*200*8位彩色
MOV AL,0x13
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ;记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
;用BIOS取得键盘上各种LED指示灯的状态
MOV AH,0x02
INT 0x16 ;keyboard BIOS
MOV [LEDS],AL
因为要传递的变量不是一个两个,为了使代码简洁,利用结构体将这些相关的变量一股脑的打包传递过去。
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
for (;;) {
io_hlt();
}
}
4.显示字符
之前要显示字符是通过BIOS函数,但这次是32位模式,不能再依赖BIOS了,只能自力更生。
字符可以用8*16的长方形像素点阵来表示。8“位”是一个字节,一个字符是16个字节。
写入内存的字体数据:
static char font_A[16] = {
0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};
这仅仅是将刚才的0和1的排列,重新成16进制数而已。因为C语言无法用二进制数记录数据,只能写成十六进制或八进制。
数据备齐之后,只要描画到画面上就好了。用for语句将8个像素的程序循环16遍就可以了。
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;
}
5.增加字体
如果还想要显示其他的字符,那就需要一个字符一个字符的添加,太费时了。所以拿来一个现成的字体文件(hankaku.txt)来用,这是OSASK的字体。内容如下:
…
如果在C语言中使用这种字体数据,只需写上以下语句:
ertern char hankaku[4096];
像这种在源程序以外准备的数据,都需要加上extern属性,这样C编译器就能够知道它是外部数据,并在编译时做出相应调整。
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
extern char hankaku[4096];
init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
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);
for (;;) {
io_hlt();
}
}
6.显示字符串
上面都是输出字符的函数,现在来写一个显示字符串的函数。
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;
}
所谓字符串是指按顺序排列在内存里,末尾加上0x00而组成的字符编码。所以s是指字符串的首地址,而使用*s就可以读取字符编码。
7.显示变量值
使用sprintf函数可以显示变量的值,它是printf的同类,我们不能随便使用printf函数,但是sprintf函数是可以的。因为sprintf函数不是按指定格式输出,只是将输出内容作为字符串写在内存中。
这个sprintf函数,是名为GO的C编译器附带的函数,它在制作者的精心设计下能够不适用操作系统的任何功能。
要在sprintf函数,需要添加C语言的头文件#include
接下来在HariMain中使用此函数:
sprintf(s, "scrnx = %d", binfo->scrnx);
putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);
8.显示鼠标指针
比较简单,直接上代码:
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
char s[40], mcursor[256];
int mx, my;
init_palette();
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
mx = (binfo->scrnx - 16) / 2; /* 计算画面中央位置坐标 */
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
for (;;) {
io_hlt();
}
}
void init_mouse_cursor8(char *mouse, char bc) //bc指背景颜色
{
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;
}
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
9.GDT与IDT的初始化
GDT:全局段号记录表
IDT:中断记录表
所谓分段:打个比方说,就是按照自己喜欢的方式,将合计4GB的内存分成很多块了,每一块的起始地址都看作0来处理。
CPU用8个字节的数据来表示这些信息。但是,用于指定段的寄存器只有16位。模仿图像调色板的做法:现有一个段号,存放在段寄存器里,然后预先设定好段号与段的对应关系。
段寄存器低三位不能用,能够处理的就只有位于0~8191的区域。
要使用鼠标,就必须要使用中断,所以我们必须设定IDT,IDT记录了0~255的中断号码与调用函数的对应关系。其设定方法与GDT相似(或许是因为使用同样的方法能简化CPU电路)。
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
struct GATE_DESCRIPTOR {
short offset_low, selector;
char dw_count, access_right;
short offset_high;
};
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_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
gd->offset_low = offset & 0xffff;
gd->selector = selector;
gd->dw_count = (ar >> 8) & 0xff;
gd->access_right = ar & 0xff;
gd->offset_high = (offset >> 16) & 0xffff;
return;
}
变量idt也是一样,IDT被设为了0x26f800~0x26ffff。
for (i = 0; i < 8192; i++) {
set_segmdesc(gdt + i, 0, 0, 0);
}
内存地址只是一个编号,代表一个内存空间。那么这个空间是多大呢?原来在计算机中存储器的容量是以字节为基本单位的。也就是说一个内存地址代表一个字节(8bit)的存储空间。