文章目录
- 1. harib02b用例(使用结构体)
- 2. harib02c用例
- 3. harib02d用例(显示字符图案)
- 3. harib02e用例(增加字符图案)
- 4. harib02g用例
- 4.1 显示字符串
- 4.2 显示变量值
- 5. harib02h用例(显示鼠标)
- 6. harib02i用例(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个字节。例如:
类似这种描绘文字图案的数据称为字体(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();
}
}
显示结果为:
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);
显示效果为:
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."字符串中显示出了阴影的立体效果。
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();
}
}
显示效果为:
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);
显示效果如图所示,可以显示鼠标为白色黑框,并且在左上角显示了鼠标的坐标:
6. harib02i用例(GDT与IDT的初始化)
GDT与IDT都是与CPU有关的设定。
为了解决多个进程访问的内存发生重叠,就需要对内存分段。将4GB的内存分成很多块(block),每一段的起始地址看起来都是0,此时任何程序都可以先写上一句ORG 0
,像这样分割出来的块就称作段(segment)
。也可以用分页(paging)
解决问题,但是当前过多讨论。Day3中引入的段寄存器,目的就是用于分段。
为了表示一个段,需要明确一下信息:
- 段的大小。
- 段的起始地址。
- 段的管理属性(禁止写入,禁止执行,系统专用)
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_DESCRIPTOR
和GATE_DESCRIPTOR
都是以CPU资料为基础定义的结构体,结构体size为8字节。
SEGMENT_DESCRIPTOR的指针变量初始化为0x00270000,本意是将0x00270000 ~ 0x0027ffff
共64KB空间作为GDT,同样只是因为这一块区域在内存分布图中显示没有被使用,所以就直接用作GDT。同理,IDT的起始地址空间为0x0026f800 ~ 0x0026ffff
。