【操作系统】30天自制操作系统--(4)显示字体(汉字)以及GDT/IDT

一 显示字体(汉字)

        字符的显示其实跟上一章矩形的显示类似,也是往指定的像素块中写颜色值,只不过字符的显示回更精细一些,而不是一大块一大块的简单赋值。

         显示8*16字符的函数如下,通过直接向VRAM中的地址赋值来显示字符

/**
 * @brief 通过直接向 VRAM 中的地址赋值来显示字符
 * @param vram 
 * @param xsize 
 * @param x 
 * @param y 
 * @param c 颜色 color
 * @param font 显示的排列位置的描述数组
 */
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;
}

        一般情况下,想显示字母或者数字,8*16也就够了,然而若想显示汉字,需要16*16的像素块,我这边随便选取了一个汉字“弦”(汉字unicode编码为5f26),我用emwin自带的文字处理器fontcvt,转换得到宋体“弦”字的编码如下:

/* Start of unicode area <CJK Unified Ideographs> */
GUI_CONST_STORAGE unsigned char acGUI_Fontsongti16_5F26[ 32] = { /* code 5F26 */
  ________,_X______,
  XXXXX___,__X_____,
  ____X___,__X_____,
  ____X_XX,XXXXXXX_,
  ____X___,_X______,
  _XXXX___,_X______,
  _X______,X___X___,
  _X_____X,____X___,
  _X____XX,XXXX____,
  _XXXX___,__X_____,
  ____X___,_X______,
  ____X___,X___X___,
  ____X__X,_____X__,
  ____X_XX,XXXXXX__,
  _X_X___X,_____X__,
  __X_____,________};

        将其转换为16进制码,并调用,这边注意区别与8*16字符的16*16的实现是有区别的(前者遍历数组长度为16,后者遍历数组长度为32):

//调用
void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
    static char font_xian[32] = {
		0x00,0x40,0xF8,0x20,0x80,0x20,0x0b,0xfe,0x80,0x40,0x78,0x40,0x40,0x88,0x41,0x80,
        0x43,0xf0,0x78,0x20,0x80,0x40,0x80,0x88,0x09,0x04,0x0b,0xfc,0x51,0x04,0x20,0x00
	};

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfont16(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_xian);

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


//显示字符
void putfont16(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d /* data */;
	for (i = 0; i < 32; i++) {
		p = vram + (y + i/2) * xsize + x + 8*(i%2);  //注意这边的区别
		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;
}

        实际效果如下:

         书中作者的字库使用了现成的HANKAKU字库,里面基本上涵盖了常用的ASCII字符(不含中文),如果需要使用中文,有两种方案:

【1】若使用的汉字比较多,需要自行增加新的字库文件(建立新的编译链);

【2】若使用的汉字比较少,在bootpack.c添加几个常用汉字的静态定义即可,或者修改书中作者提供的hankaku.txt文件亦可;

        

二 显示字符串

        基于引入的hankaku字库,构造显示字符串的函数如下:

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

        与此同时,包含c标准输入输出流stdio.h之后(#include <stdio.h>),就可以引用sprintf函数(sprintf只对内存进行操作,而printf函数不可避免地会使用到操作系统的功能,所以这边不用printf函数):

	char s[40];
	
	sprintf(s, "scrny = %d", binfo->scrny);

三 显示符号图形(鼠标指针形状)

        与显示字符类似,我们需要用显示图形来创建一个鼠标的指针形状(这边还只是静态图形,后面做完与鼠标外设的联动之后,就可以自由移动了):

void init_mouse_cursor8(char *mouse, char bc)
/* マウスカーソルを準備(16x16) */
{
	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;
}

        这边附一个正常点的鼠标光标图案:

static char cursor[16][16] = {
         "*...............",
         "**..............",
         "*O*.............",
         "*OO*............",
         "*OOO*...........",
         "*OOOO*..........",
         "*OOOOO*.........",
         "*OOOOOO*........",
         "*OOOOOOO*.......",
         "*OOOO*****......",
         "*OO*O*..........",
         "*O*.*O*.........",
         "**..*O*.........",
         "*....*O*........",
         ".....*O*........",
         "......*........."
    };

四 GDT与IDT初相识

【1】分段机制(GDT:全局段号记录表)

        为了解决内存重叠的问题,需要引入分段机制。32位的CPU使用32条地址线,能区分2^32=4G个内存地址。每个内存地址都有1Byte的内容。

        分段,就是将4GB的内存分成很多块(block),每一块的起始地址都看作0来处理。有了这个功能,任何程序都可以先写上一句"ORG 0",一个应用程序就不会占用别人的内存空间,这样就可以同时运行多个程序。像这样分割出来的块,就称为段(segment)。还有一种"分页"的技术,这里不讨论。

        为了表示一个段,需要记录以下信息:

  • 段的起始地址

  • 段的大小

  • 段的管理属性(禁止写入,执行,系统专用等)

        这些信息需要用8个字节保存。使用段的方式是和调色板神似的:DS是16位,理论上能够表示2^16=65536个段。但由于CPU设计上的原因,低3位不能用,因此DS只能表示2^13=8192个段(即第0个~第8191个)。

        要存储8192个段,就需要占用8192*8=65536Byte=64KB的内存空间。这64KB的数据就称为GDT (Global segment Descriptor Table)即"全局段号记录表"

        将这64K的GDT整齐地排列在内存某处,再将其起始地址和有效设定个数放在CPU内被称作GDTR的48bit寄存器中,GDT的设定就完成了。

        段的起始地址、大小、管理属性这些信息是按bit保存的,十分复杂,暂时不予理会。

【2】中断机制(IDT:中断记录表):

        CPU应对外设的情况变化或者内部错误时,一般有轮询和中断两种机制。

        轮询比较耗时,所以要使用鼠标键盘,就必须使用中断机制,即设置IDT(IDT的设置必须要在GDT设置完成之后)。

        IDT(Interrupt Descriptor Table)即"中断记录表"。IDT记录了0~255的中断号与调用函数之间的对应关系。当发生了123号中断,就会调用对应的函数。其设置方式与GDT是相似的,IDT的每一项也需要8Byte保存,这8Byte里包括中断处理函数名(即C语言中的函数指针)。

void init_gdtidt(void){
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR*) 0x00270000;
	struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR*) 0x0026f8000;
	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_dgtr(0xffff, 0x00270000);
	// IDT 初始化
	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(0x07ff, 0x0026f800);

	return;
}

void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar){
	if (limit > 0x000fffff) {
		ar |= 0x8000;
		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;
}


// void load_dgtr(int limit, int addr);
// void load_idtr(int limit, int addr);

_load_gdtr:		; void load_gdtr(int limit, int addr);
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LGDT	[ESP+6]
		RET

_load_idtr:		; void load_idtr(int limit, int addr);
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LIDT	[ESP+6]
		RET

        这边注意以下四点:

        (1)段号为1的段,上限值为0xffffffff,即大小正好是4GB,地址是0,它表示的是CPU所能管理的全部内存本身,段的属性设为0x4092,属性的含义暂且不表。

        (2)段号为2的段,它的大小是512KB,地址是0x280000 这正好是为bootpack.hrb(C 转化成的与汇编可连接文件)而准备的用这个段,就可以执行bootpack.hrb,因为 bootpack.hrb 是以 ORG 0 为前提翻译成的机器语言。

        (3)LGDT/LIDT分别是汇编中的加载全局段号描述符/加载中断描述符命令。

        (4)这边IDT内存是从0x26f800--0x26ffff的2KB区间GDT内存是从0x270000--0x27ffff的64KB区间,后面C语言段bootpack.hrb(第2段)是从0x280000--0x2fffff的512KB区间:

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值