目录

Day5-结构体文字显示与GDTIDT初始化

Day5 结构体、文字显示与GDT/IDT初始化

1. harib02b用例(使用结构体)

由于在asmhead.nas中已经定义好的:

# asmhead.nas 节选
CYLS	EQU		0x0ff0			; 设定启动区地址
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 关于颜色的信息的地址,颜色的位数
SCRNX	EQU		0x0ff4			; 分辨率X地址
SCRNY	EQU		0x0ff6			; 分辨率Y地址
VRAM	EQU		0x0ff8			; 图像缓冲区的开始地址

MOV		BYTE [VMODE],8	; 记录画面模式
MOV		WORD [SCRNX],320
MOV		WORD [SCRNY],200
MOV		DWORD [VRAM],0x000a0000

可以在bootpack.c中封装一个struct BOOTINFO,并通过访问结构体成员的方法,直接访问对应地址的内容。这样就可以直接从asmhead.nas访问到已定义好的屏幕显示信息,当屏幕显示信息修改时,显示图案可以随之修改。(BOOTINFO结构体中的字段顺序不可改变,与asmhead.nas定义好的地址强关联。)

struct BOOTINFO {
	char cyls, leds, vmode, reserve;
	short scrnx, scrny;
	char *vram;
};

void HariMain(void)
{
	char *vram;
	int xsize, ysize;
	struct BOOTINFO *binfo;

	init_palette();
	binfo = (struct BOOTINFO *) 0x0ff0;
	xsize = (*binfo).scrnx;
	ysize = (*binfo).scrny;
	vram = (*binfo).vram;

	init_screen(vram, xsize, ysize);

	for (;;) {
		io_hlt();
	}
}

2. harib02c用例

除了使用*解引用结构体指针外,还可以使用->直接访问结构体指针的成员。因此,修改bootpack.c:

	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;;

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);

3. harib02d用例(显示字符图案)

可以通过8*16的长方形像素点阵表示字符图案,8bits是一个字节,显示一个字符图案需要使用16个字节。例如:

https://i-blog.csdnimg.cn/direct/4f7648ead8d94687a39aa34e6938edd7.png

类似这种描绘文字图案的数据称为字体(font)数据。暂时使用这样一个数组表示上图中的字符“A”:

static char font_A[16] = {
	0x00, 0x18, 0x18, 0x18, 0x18, 0x24 0x24, 0x24,
	0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};

将显示字符的功能封装为一个函数,并在HariMain中调用它:

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];
		// 按像素逐个将font表示出来
		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;
}

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	static char font_A[16] = {
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);				// 界面设计
	putfont8(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_A);	// 白色线条显示文字

	for (;;) {
		io_hlt();
	}
}

显示结果为:

https://i-blog.csdnimg.cn/direct/84a051937d2b4920a302d459c554b865.png

3. harib02e用例(增加字符图案)

使用OSASK字体数据(记录在hankaku.txt文件中),并使用专用工具makefont.exe将hankaku.txt文件制作成一个bin文件,接着使用bin2obj.exe工具使其生成目标文件,就可以连接到可执行文件中。最终仅需要在bootpack.c文件中 extern char hankaku[4096]; 即可。

A的字符编码是0x41,因此A的字体图案数据的地址为 hankaku+0x41*16 也可以写成 hankaku+'A'*16 ,在源文件bootpack.c添加显示字符的语句:

	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);

显示效果为:

https://i-blog.csdnimg.cn/direct/26482e418b0f4fa6b9099c168920e27e.png

4. harib02g用例

4.1 显示字符串

由于字符串的结尾会存在一个 \0 ,因此可以封装一个函数,传入字符串的首地址,循环字符读取,当读到0时返回:

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;
}

// 调用
void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfonts8_asc(binfo->vram, binfo->scrnx,  8,  8, COL8_FFFFFF, "ABC 123");
	putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Haribote OS.");
	putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Haribote OS.");

	for (;;) {
		io_hlt();
	}
}

显示效果为如下图,在"Haribote OS.“字符串中显示出了阴影的立体效果。

https://i-blog.csdnimg.cn/direct/20cb1f2c57d1442caa7a9e0ca16f79c9.png

4.2 显示变量值

期望显示变量值,可以使用C库函数sprintf,作用就是将格式化字符串写到一个内存地址中,

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	char s[40];

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfonts8_asc(binfo->vram, binfo->scrnx,  8, 8, COL8_FFFFFF, "ABC 123");
	putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Haribote OS.");
	putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Haribote OS.");
	sprintf(s, "scrnx = %d", binfo->scrnx);		// 格式化字符串写到s中
	putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);

	for (;;) {
		io_hlt();
	}
}

显示效果为:

https://i-blog.csdnimg.cn/direct/235cad91337941e1a037d0a44b988bdb.png

5. harib02h用例(显示鼠标)

定义鼠标的图案为16*16:

// 初始化一个鼠标图案
void init_mouse_cursor8(char *mouse, char 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;		// bc: back-color 背景色
			}
		}
	}
	return;
}

// 将鼠标图案显示在屏幕上
// vram: VRAM address
// vxsize: screen display size
// pxsize: pattern(mouse) x size
// pysize: pattern(mouse) y size
// px0: mouse location x of screen display
// py0: mouse location y of screen display
// buf: pattern address
// bxsize: roughly equal to pxsize
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;
}

对函数的调用:

	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);

显示效果如图所示,可以显示鼠标为白色黑框,并且在左上角显示了鼠标的坐标:

https://i-blog.csdnimg.cn/direct/1c69c818f2524f099f69cce35d7ede69.png

6. harib02i用例(GDT与IDT的初始化)

GDT与IDT都是与CPU有关的设定。

为了解决多个进程访问的内存发生重叠,就需要对内存 分段 。将4GB的内存分成很多块(block),每一段的起始地址看起来都是0,此时任何程序都可以先写上一句 ORG 0 ,像这样分割出来的块就称作 段(segment) 。也可以用 分页(paging) 解决问题,但是当前过多讨论。 中引入的段寄存器,目的就是用于分段。

为了表示一个段,需要明确一下信息:

  • 段的大小。
  • 段的起始地址。
  • 段的管理属性(禁止写入,禁止执行,系统专用)

CPU用64bits的数据表示这些信息,但是用于指定段的寄存器只有16bits。模拟调色板的方法,预先设定好段号和段的对应关系,将段号存放在段寄存器中。

段寄存器的低3bits不允许使用,因此段号可以是0 ~ 8191之间的数字,因此最多可以设置8192个段,存储这么多段的信息总共需要 8192*8 = 65536Byte = 64KB (每个段的信息需要8Byte存储)。由于段号与外设无关,因此不需要使用 io_out 这类函数接口的调用。

这64KB的数据就被称为GDT(global segment describer table),全局段描述符表。这部分内容需要顺序写入到内存中,然后将内存地址和有效的设定个数放在CPU的GDTR(global segment describer table register)的特殊寄存器中,设定就完成了。

IDT(interrupt describer table),中断记录表。当CPU遇到外部状况变化,或内部偶然发生某些错误时,就会临时切换过去处理这种情况,被称为中断功能。

各个设备有变化时就会产生中断,中断发生后,CPU暂时停止正在处理的任务,并做好接下来能够继续处理的准备,转而执行中断程序。中断程序执行完之后,再调用事先设定好的函数,返回处理中的任务。

正式得益于中断机制,CPU可以不用一直查询键盘,鼠标,网卡等设备状态,从而专注于处理任务。

IDT记录了0 ~ 255的中断号码与调用函数的对应关系。设定GDT之前需要设定好IDT。

// GDT结构体,全局段号记录表,占8个字节
struct SEGMENT_DESCRIPTOR {
	short limit_low, base_low;
	char base_mid, access_right;
	char limit_high, base_high;
};

// IDT结构体,中断记录表,占8个字节
struct GATE_DESCRIPTOR {
	short offset_low, selector;
	char dw_count, access_right;
	short offset_high;
};

void init_gdtidt(void)
{
	// 0x27 0000 ~ 0x27ffff 共8192*8 = 65536个Byte
	// 由于段寄存器只有2^11 = 8192个状态
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
	// 0x26 f800 ~ 0x26 ffff 共256*8 = 2048个Byte
	// 由于中断号码共256个
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) 0x0026f800;
	int i;

	/* GDT中所有段初始化为0 */
	for (i = 0; i < 8192; i++) {
		set_segmdesc(gdt + i, 0, 0, 0);
	}
	// 分别对段号为1和2的两个段单独设置属性
	// 1、地址为0,上限为4G,表示全部的内存本身
	set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
	// 2、地址为0x280000,大小为512K,主要用作bootpack.hrb启动程序
	set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
	
	load_gdtr(0xffff, 0x00270000);		// 汇编实现定义,赋值GDTR

	/* IDT的初始化 */
	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(0x7ff, 0x0026f800);		// 汇编实现定义,赋值IDTR

	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;
}

SEGMENT_DESCRIPTORGATE_DESCRIPTOR 都是以CPU资料为基础定义的结构体,结构体size为8字节。

SEGMENT_DESCRIPTOR的指针变量初始化为0x00270000,本意是将 0x00270000 ~ 0x0027ffff 共64KB空间作为GDT,同样只是因为这一块区域在内存分布图中显示没有被使用,所以就直接用作GDT。同理,IDT的起始地址空间为 0x0026f800 ~ 0x0026ffff