万州网站推广搜索引擎优化教材答案
文章目录
- 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); // 汇编实现定义,赋值IDTRreturn;
}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
。