30天自制操作系统-第五天

来源:互联网 发布:java打五角星 编辑:程序博客网 时间:2024/05/17 06:59
结构体、文字显示与GDT/IDT初始化
      学习目标:1、接收启动信息(使用结构体);2、显示字符;3、增加字体;4、显示字符串;5、显示变量值;6、显示鼠标指针;7、GDT与IDT的初始化。
         1、接收启动信息
         以前都是将启动信息的参数直接写入程序,但是其实这些值应该从asmhead.nas先保存下来,再从中取值(否则,画面改变时不能正确运行)。
         将这些要传入的参数写在一起,即写成结构体的形式。
         2、显示字符
         字符可以使用8*16的长方形像素点阵表示(如果感觉太小的话,可以将像素点阵调成大点):
          
         这种描画文字形状的数据陈伟字体(font)数据(将上述的0和1写成十六进制而已):     
static char font_A[16] = {    0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,0x24, 0x7e,0x42, 0x42, 0x42, 0xe7, 0x00, 0x00};
         为了显示该字符(即对这个8*16的矩形中有1的填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];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;}
          3、增加字体
         26个英文字母+10个数字+汉字+其他字符->处理起来麻烦
        这里不亲自编字体,借助文中提供的hankaku.txt这个文本文件加入到我们的源程序
        当然,这既不是C语言,也不是汇编语言,所以需要专用的编译器,这里使用makefont.exe工具,说是编译器,其实就是将上面的文本文件读进来,256个字符,然后输出16*256=4096字节的文件而已。
        编译后生成hankaku.bin文件,但仅有这个文件还不能和bootpack.obj连接,因为它不是目标文件。所以还需要加上连接所必需的接口信息,将它变成目标文件,这个工作由bin2obj.exe来完成。

        如果在C语言中使用这种字体数据,只需要写上以下代码:  
extern char hankaku[4096];
      A的字体数据,放在自“hankaku+0x41*16”开始的16字节里
void HariMain(void){struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;extern char hankaku[4096];init_palette();init_screen(binfo->vram, binfo->scrnx, binfo->scrny);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);for (;;) {io_hlt();}}
           4、显示字符串
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;}
           5、显示变量值
           能不能显示字符串对于操作系统的开发影响很大,这是因为程序运行于想象中不一致时,可将变量的值显示出来是最好的方法。
          怎么显示变量呢?可以使用sprintf函数,它是printf函数的同类。区别是:在自制操作系统中,printf函数不能随便使用,但sprintf可以使用,因为sprintf不是按指定的格式输出,只是将输出内容存在字符串写在内存中。
           printf函数不管如何精心设计,都不可避免使用操作系统的功能(如何显示字符),而sprintf不同,它只是对内存进行操作,所以可应用于任何操作系统。
sprintf(s,"scrnx=%d",binfo->scrnx);putfonts8_asc(binfo->vram,binfo->scrnx,16,64,COL8_FFFFFF,s);
              6、显示鼠标指针
         将鼠标指针的大小定义为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;}}}return;}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;}init_mouse_cursor8(mcursor, COL8_008484);putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);

        7、GDT与IDT的初始化
        在上面的基础上,鼠标指针显示出来了,我们想做的第一件事就是去移动它,但鼠标指针却一动不动。
        要怎么样才能使它们动起来呢?首先要将GDT和IDT初始化。
        GDT也好,IDT也好,它们都是与CPU有关的设定。为了让操作系统能够使用32位模式,需要对CPU做各种设定。不过,asmhead.nas里写的程序有点偷工减料,只是随意进行了一些设定。这里没有做出使用鼠标指针所需要的一些设定(原先的asmhead.nas只做了运行bootpack.c所必需的一些设定)。
        先了解2个概念:GDT、IDT
        分段的作用:能够解决不同应用程序之间由于内存冲突,不能执行的情况。
        所谓分段,打个比方说,就是按照自己喜欢的方式,将合计4GB的内存分成很多块(block),每一块的起始地址都看作0来处理(在程序里可以写上一句ORG 0来实现)。
       这里的分段,就是使用这个段寄存器。但是在16位的时候,如果计算地址,只要将地址乘以16就可以了。但现在已经是32位了,不能再这么用了。如果写成“MOV AL,[DS:EBX]”,CPU会往EBX里加上某个值来计算地址,这个值不是DS的16倍,而是DS所表示的段的起始地址。即使是省略了段寄存器的地址,也会自动认为是指定了DS。这个规则不管是16位模式还是32位模式,都是一样的。
       为了表示一个段,需要以下信息:1、段的大小是多少;2、段的起始地址在哪里;3、段的管理属性(禁止写入,禁止执行,系统专用等)。
       CPU用8个字节(=64位)的数据来表示这些信息,但事实上段寄存器只有16位。
       这个是如何实现的呢?模仿图像调色板的做法。也就是说,现有一个段号,存放在段寄存器里,然后预先设定好段号与段的对应关系。段号可以用0-8191的数(因为段寄存器是16位,本来能够处理0-65535范围的数,但由于CPU设计上的原因,段寄存器的低3位不能使用)。
       段号如何设定呢?对于CPU的设定,不需要像调色板那样使用in_out(由于不是外部设备,当然没有必要)。但因为能够使用0-8191的范围,即可以定义8192个段,所以设定那么多段需要8192*8=65536字节(64KB)。CPU没有那么大的存储能力,所以这部分数据应该写入内存中去,这部分数据叫做GDT。
      GDT是“global (segment) descriptor table”的缩写,意思是全局段号记录表。将这些数据整齐地排列在内存的某个地方,然后将内存的其实地址和有效设定个数放在CPU内被称作GDTR的特殊寄存器,设定就完成了。
      IDT是“interrupt descriptor table”的缩写,为“中断记录表”。
       如果段的设定还没顺利完成就设定IDT的话,会比较麻烦,所以必须先进行GDT的设定。
struct SEGMENT_DESCRIPTOR {short limit_low, base_low;char base_mid, access_right;char limit_high, base_high;};struct GATE_DESCRIPTOR {short offset_low, selector;char dw_count, access_right;short offset_high;};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);/* IDT偺弶婜壔 */for (i = 0; i < 256; i++) {set_gatedesc(idt + i, 0, 0, 0);}load_idtr(0x7ff, 0x0026f800);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_SESCRIPTOR中存放GDT的8字节的内容,它无非是以CPU的资料为基础,写成了结构体的形式。同样,GATE_DESCRIPTOR中存放IDT的8字节的内容,也是以CPU的资料为基础的。

set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);//段号为1的段,上限值是0xffffffff,地址是0,它表示的是CPU所能管理的全部内存本身set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a); //段号为2的段,它的大小是512KB,地址是0x280000,这正好是为bootpack.hrb而准备的。

load_gdtr(0xffff, 0x00270000); //对于这个函数的实现,C语言不能给GDTR赋值,所以要借助汇编的力量,仅此而已。

    变量gdt被赋值0x00270000,就是说要将0x270000-0x27ffff设为GDT。至于为什么选择这个是笔者随便决定的。
  变量idt也是一样的,IDT被设为0x26f800-0x26ffff。




原创粉丝点击