30天自制操作系统-第05天-结构体、文字显示、GDT/IDT此初始化

1.使用结构体处理启动信息(/05_day/harib02b)

1)启动信息定义在asmhead.nas里,定义的都是内存地址,启动信息的首地址在0x0ff0,片段如下:

; BOOT_INFO
CYLS EQU 0x0ff0 ;设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色数目的信息。颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率X
SCRNY EQU 0x0ff6 ; 分辨率Y
VRAM EQU 0x0ff8 ; 图形缓冲区的开始地址

2)结构体定义:

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

3)HariMain函数代码:

void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;//结构体变量指向对应的地址,参加前面介绍的asmhead.nas定义的首地址,这里将整数0xff0转换为指向结构变量的指针
init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);//这里使用箭头对应起图形缓冲区的写入地址,图形显示模式的X,Y
for (;;) {
io_hlt();
}
}

2.显示字符A(/05_day/harib02c)

在保护模式下,以及不能再使用bios显示字符,因此必须字节画字符。假定采用8 X16像素点阵显示字符,像素为置为1显示,0不显示,因此要按照行定义点阵数组,需要16个字节表示一个字符,比如A字符的8X16点阵数据表示为:

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

  1) 画字符的函数:

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

/*

//d为一个字节,每个bit对应一个像素,其最高位对应p[0],次高位对应p[1],以后依此类推,

0x80对应二进制为1000 0000,在此做位与运算,判断最高位是否为1,如果为1,p[0]设置颜色值(c)

0x40对应二进制为0100 0000,在此做位与运算,判断次高位是否为1,如果为1,p[1]设置颜色值(c)

后面与此同理,不过这里的代码有点啰嗦,不优雅,固定写死八行处理8个字节

*/

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

  2) make run  

3.增加字体/05_day/harib02e)

前一节明白了字符点阵显示的原理,本节就增加一个OSASK字库文件,有英文大小写字母和十个数字以及其他字符,汉子的显示以后再说

1) 字库文件hankaku.txt

使用makefont.exe 生成hankaku.bin文件,再用bin2obj转换成hankaku.obj,此时可与bootpack.obj链接,此obj就是简单的数据定义:

    _hankaku:

                  db...

                  .....

 

c 语言中引用字库: extern char hankaku[4096];

c语言中获取字符A的点阵数据: hankaku + 'A' * 16;

字库示例字符A:

char 0x41
........
...**...
...**...
...**...
...**...
..*..*..
..*..*..
..*..*..
..*..*..
.******.
.*....*.
.*....*.
.*....*.
***..***
........
........

4.显示字符串(/05_day/harib02f)

 1)显示字符串函数  

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

2) HariMain函数

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

3) make run

5.显示变量值(/05_day/harib02g)

  1)调试系统的需要,打印程序中某些变量的值,先#include <stdio.h>,引入sprintf函数,在作者的精心设计下,此函数与操作系统无关,功能如下说明:

sprintf函数将输出内容作为字符串写在内存中,接着使用之前编写的字符串显示函数就可以将写在内存中的字符串打印出来,下表是对sprintf的格式总结:

                       sprintf(地址,格式,值,值,值,…)
%d                                十进制数
%5d                              5位十进制数,不足会加空格补全5位
%05d                            5位十进制数,不足会加前导0补全5位
%x                                十六进制数
%5x                              5位十六进制数, 不足会加空格补全5位
%05x                            5位十六进制数,不足会将前导0补全5位
%s                                字符串格式

 2)Harimain函数中增加:

void HariMain(void){

.....

char s[40];     

.....

sprintf(s, "scrnx = %d", binfo->scrnx); //将屏幕宽度转为字符串s,即整数320转换为字符串"320"
putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);

....

}

3) make run

6.显示鼠标(/05_day/harib02h)

1)核心代码:

void init_mouse_cursor8(char *mouse, char bc)
{
static char cursor[16][16] = { //鼠标16 X16点阵模拟数据
"**************..",
"*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') {//为o设置背景色
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.') {
mouse[y * 16 + x] = bc;
}
}
}
return;
}

 

/*

其中函数中vram和vxsize是关于VRAM的信息。他们的值分别是Oxa0000和320。pxsize和pysize是想要显示的图形的大小,鼠标指针的大小是16×16,所以这两个值都是16。px0和py0指定图形在画面上的显示位置。最后的buf和bxsize分别指定图形的存放地址和每一行含有的像素数。bxsize和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;
}

void HariMain(void){ //片段

{

......

char s[40], mcursor[256];
int mx, my;

......

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);
sprintf(s, "(%d, %d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

......

}

2) make run

7.GDT与IDT的初始化(/06_day/Harib0i)

1)内存分段信息(8字节)

     a.段大小

     b.段的起始地址

     c.段的管理属性(禁止写入、禁止执行、系统专用等)

 2)GDT全局段记录表 

     32b下,段寄存器仍然只有16位,存放段号(segment selector),低三位不能使用,只能用高13位,因此0-8191共8192个段,每个段都有前述的内存分段信息,因此需要8192 X 8 =65536=64k,因此只能放到内存中,叫GDT,全局段号记录表

 3)GDTR 寄存器

      存放:上述GDT的 内存起始地址和有效设定个数等信息

4)   IDT 中断记录表 

      访问外设的高效处理方式,鼠标、键盘、硬盘、网卡等,该表记录的是每个外设中断的处理函数地址

5) 关键代码:

struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
}; //GDT 8字节内容

struct GATE_DESCRIPTOR {
short offset_low, selector;
char dw_count, access_right;
short offset_high;
};//LDT 8字节内容

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);//执行lgdt, 设置段大小和起始地址

/* IDT初始化 */
for (i = 0; i < 256; i++) {
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(0x7ff, 0x0026f800);;//执行lgdt, 设置段大小和起始地址

return;
}

这部分代码是对GDT的初始化。gdt是定义的SEGMENT_DESCRIPTOR结构体变量,gdt的起始的地址为0x00270000,一直到0x0027ffff的区域都用来设置gdt。set_segmdesc函数的第2个参数表示段的大小,第3个参数表示段的基址,第4个参数表示段的管理属性。for循环一开始对所有段的相关信息都初始化为0。接下来两条语句是对段为1,2的两个段的设定,段号为1的段,上限值为Oxffffffff即大小正好是4GB),地址是0,它表示的是CPU所能管理的全部内存本身。段的属性设为0x4092;段号为2的段,它的大小是512KB,地址是0x280000,这是为bootpack.hrb而准备的。最后一句是使用汇编语言对寄存器GDTR赋值。

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

 8.问题:

  1) 保护模式难度很大,多花时间

  2)本节没讲 GDTR

posted @ 2022-04-23 10:35  煮酒熬码  阅读(174)  评论(0)    收藏  举报