怎么把表格存在桌面上,把表格放到桌面的方法

首页 > 经验 > 作者:YD1662022-10-29 08:26:14

那么,既然可以绘制字符了,为何不绘制一个字符串呢?

绘制字符串

我们绘制一个字符,主要做了:

  1. 根据字符的形状写出font数据,这个数组中每个char能够表明一行像素的颜色。16个char 就能够表明16行像素的颜色,也就是说16个char 就能够表示一个字符了。
  2. 把font中定义的16个char 用putfont8函数绘制到指定位置x,y就行了

那么,如何绘制一个字符串,也就是说,如何绘制很多个字符串?

一个一个绘制就行了呗。重复使用putfont8函数,使用是,更改一下x,y的取值就行了。

不过使用putfont8前,还需要准备好font数组。

刚才我们指示准备了字符A的font数组。要显示字符串,字符串里面必然包含26个字母,以及26个字母的大小写,不仅仅包含字母,还有各种标点符号等。

所以,要想显示字符串,首先就要构造出每个字符,标点符号的font数组。

我们使用一个现成的font数组,别人已经构建好的数组。

这个数组比较好用。

假设这个数组的首地址hankaku,那么hankaku 0x41*16就得到了字符‘A’首地址,

因为字符A的字符编码是0x41,所以,我们可以这样写:hankaku 'A'*16就得到了字符'A'的首地址。使用putfont8(,,,,, hankaku 'A'*16)就可以绘制字符A了。

也就是说:hankaku 'B'*16就是字符'B'

hankaku 'a'*16就是小写字符a.

所以,如果要绘制字符串:”ABC123“,可以使用如下代码:

oid 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行,extern char hankaku[4096],表示hankaku这个数组不在本代码中编译,在其他代码中编译了。这个hankaku就是我们使用的字符库。

怎么把表格存在桌面上,把表格放到桌面的方法(5)

新的问题又来了,这个显示程序使用其他不太方便,显示字符串还需要把每个字符单独拆开去写,并且自己还要设定每个字符的坐标x,y, 能不能只指定一个字符串" mingminglashi"就可以了?

能,所以有如下函数putfonts8_asc:

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

这个函数第4行的for循环,便利出字符串中的每个字符。

第5行,调用putfont8函数显示字符

第6行,设定下一个字符的显示位置。

现在可以显示任意字符串就比较方便了。

那么如果我要显示一个变量的值呢? 比如“ a=5",该如何操作?

这样操作就行:

sprintf(s, "a = %d", 5); putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);

先用sprint将a=5做成字符串,然后再用putfonts8_asc将字符串显示。

因为我们是用c在写程序,所以,可以使用sprintf函数。

字符串的显示就到这里。自己写的操作系统可以显示字符了

鼠标的显示

既然可以显示字符串了,下一步不就是键盘打字,然后显示了嘛

这样,表面看起来,就是一个真正的操作系统了:可以用键盘来控制显示屏来显示任意字符,任意图像了。

操作系统本来就是为了让人方便地使用键盘,鼠标来控制cpu来帮人工作的嘛。

因为键盘的控制和鼠标的控制基本相同,并且,鼠标的使用更加直观,所以我们就先讲鼠标如何显示,以及如何操作,再讲键盘的操作,他们两个非常的相似。

那么如何显示鼠标?

鼠标就是一个箭头的形状嘛,如下:

怎么把表格存在桌面上,把表格放到桌面的方法(6)

我们可以把鼠标绘制到一个16x16的长方形中,在长方形中,有的位置需要显示的颜色与背景不同。

所以,显示鼠标与显示一个字符还是非常相似的。不同的是,字符所在的长方形是16x8的,而鼠标是16x16的。

显示鼠标,也是分两步:

  1. 构建表明鼠标颜色的数组
  2. 将数组用for循环赋值到显示缓冲区里。

关于构建表明鼠标每个像素颜色的数组,有如下代码:

#define COL8_000000 0 #define COL8_FF0000 1 #define COL8_00FF00 2 #define COL8_FFFF00 3 #define COL8_0000FF 4 #define COL8_FF00FF 5 #define COL8_00FFFF 6 #define COL8_FFFFFF 7 #define COL8_C6C6C6 8 #define COL8_840000 9 #define COL8_008400 10 #define COL8_848400 11 #define COL8_000084 12 #define COL8_840084 13 #define COL8_008484 14 #define COL8_848484 15 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; }

使用init_mouse_cursor8函数,就可以设字符数组mouse的值,mouse就可以表示鼠标所在长方形中每个像素的颜色。

关于把mouse用for循环绘制到显示缓冲区里:

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

写了函数putblock8_8来将buf中的设置的值赋值到vram中。

buf中就是鼠标的像素颜色,vram就是显示缓冲区。

到此:使用init_mouse_cursor8函数和publock8_8函数,就可以完成鼠标的显示了,总体代码如下:

void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; char s[40], mcursor[256]; int mx, my; init_palette(); init_screen8(binfo->vram, binfo->scrnx, binfo->scrny); 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); for (;;) { io_hlt(); } }

11,12行分别使用了init_mouse_cursor8和putblock8_8。

9,10设定了要显示鼠标的位置

显示效果:

怎么把表格存在桌面上,把表格放到桌面的方法(7)

至此,我们在窗口上显示了鼠标,看起来有点操作系统的样子了。

不过这个鼠标还不会动。

如何使这个鼠标随着人的操作动起来?

利用中断函数。

鼠标相对于CPU来说,是外部设备。鼠标一动,cpu就会有响应。

这个响应就是通过中断函数来实现的。

鼠标中断函数前的准备工作

CPU的中断函数用汇编操作的话,会非常的简单。

不过汇编工作在16位的工作模式下,我们这里使用C语言写操作系统了,就必须工作在32位模式下.

那么在32位模式下,在使用中断函数前,必须自己定义中断函数。然后将中断函数的地址放在表中,叫中断函数表:interrupt descriptor table, IDT.

也就是说,我们需要把原来16位模式下,鼠标中断函数调用出来,放在我们自己定义的IDT中断函数表里。

中断函数表放在内存中某个位置,然后把这个位置放在CPU的寄存器里,这个寄存器叫IDTR,中断函数表首地址寄存器。

以上中断函数表,以及相应寄存器准备好后,

在鼠标有动作的时候,触发中断动作,cpu就去查询IDTR寄存器中的值,这个值就是中断函数表在内存中的首地址,然后就可以从中断函数表中找到相应的中断函数去执行了。

总之: 想得到鼠标的响应,就必须使用中断函数,要想使用中断函数,就必须设定IDT。

还有,IDT里的中断函数体放在哪里呢?我们知道中断函数表,只存储了中断函数的地址。中断函数我们需要放在内存中某个位置的,所以,还要指名中断函数所在的内存位置。

注意到,中断函数是属于我们所写的操作系统的函数,只允许操作系统本身来访问的。

为了指名中断函数所在的位置,需要对内存的使用进行管理,这里就使用一个表来管理内存,叫做全局内存段的定义表,或者叫描述表/记录表,golobal segment descriptor table,GDT.

这这个表格中,定义了对整个内存的分段情况,每段内存的权限情况等。

这个表的开始地址页需要放在其对用的寄存器GDTR中,CPU才会依照GDT所定义的方式去访问内存。

所以,想得到鼠标的响应,我们要先设置GDT,在GDT中存放中断函数,再设置IDT,IDT中存放中断函数的地址, 然后才可以在中段函数中获取到鼠标的状态。

设定GDT,使用以下set_segmdesc函数

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

其中,unsigned int limit, 是当前内存段的大小

int base,当前内存段的开始地址

int ar,当前内存段的属性,可读,可写,可执行等属性的设置,是否分页访问内存等

这个函数,将内存段的大小,内存段的开始地址, 以及属性存放到结构体struct SEGMENT_DESCRIPTOR *sd中, sd就是结构体的首地址。

使用set_segmdesc去定义内存的使用规则GDT代码如下:

struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; // 开始地址0x0000 0000, 内存大小:0xffff ffff ,4GB, set_segmdesc(gdt 1, 0xffffffff, 0x00000000, AR_DATA32_RW); set_segmdesc(gdt 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER); load_gdtr(LIMIT_GDT, ADR_GDT);

第3行,limit=0xffff ffff=4GB, base=0x0000 0000, ar = AR_DATA32_RW=0x4092,

第3行定义了一个大小为limit=4GB的内存段,开始地址为0x0000 0000, 并且定义这段内存是操作系统专用的可读可写,但不可执行的。 也就是操作系统用于存放数据的内存。

补充知识:在set_segmdesc内部第3行,当limit>0xfffff时,将ar的最高位设置为1。这是为什么呢?这是指:当内存大小超过0xfffff=1M后,就使用“页”来访问内存了,而定义用“页”的概念来访问内存,需要更改内存段的属性,将其ar的最高位设定为1即可,即:ar |= 0x8000.

第4行定义了一个大小为LIMIT_BOTPAK=0x0007ffff=512KB的内存,它以地址ADR_BOTPAK=0x0028 0000开始,它的属性为AR_CODE32_ER,可执行可读,这个内存段是指存放操作系统程序的内存段。就是我们现在正在写的,bootpack.c这个文件存放的内存段。所以它的开始地址是0x0028 0000,

第5行,load_gdtr,是将GDT的首地址ADR_GDT,以及大小LIMIT_GDT放入寄存器。涉及到寄存器的函数,都是汇编函数,在文件naskfunc.nas中的实现为:

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

注意第1行定义了gdt,它是以ADR_GDT为开始地址的结构体。

整体有关内存分段管理的代码为:

truct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }; #define ADR_GDT 0x00270000 #define LIMIT_GDT 0x0000ffff #define ADR_BOTPAK 0x00280000 #define LIMIT_BOTPAK 0x0007ffff #define AR_DATA32_RW 0x4092 // 操作系统使用的数据断,可读,可写 #define AR_CODE32_ER 0x409a // 操作系统使用的存放程序的内存段,可读,可执行 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; } struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; int i; for (i = 0; i <= LIMIT_GDT / 8; i ) { set_segmdesc(gdt i, 0, 0, 0); } // 开始地址0x0000 0000, 内存大小:0xffff ffff ,4GB, set_segmdesc(gdt 1, 0xffffffff, 0x00000000, AR_DATA32_RW); set_segmdesc(gdt 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER); load_gdtr(LIMIT_GDT, ADR_GDT);

这里,在内存中设定了两个段:一个是数据段,是gdt中的第1个内存段的使用规则,开始地址为0x0000 0000,总内存大小为4GB,它规定了一个操作系统专用的数据存放区域。

一个程序段,是gdt中定义的第2个内存段的使用规则,开始地址为0x0028 0000,大小为512KB,它规定了一个操作系统专用存放代码的存放区域

我们刚才所说的中断函数,就要存放在程序段里。

好了,定义好了内存段了,也就是定义好中断函数所存放的内存区域了,下面就来定义中断函数表IDT了,定义这个表的总体代码为:

struct GATE_DESCRIPTOR { short offset_low, selector; char dw_count, access_right; short offset_high; }; #define ADR_IDT 0x0026f800 // IDT的地址 #define LIMIT_IDT 0x000007ff 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; } struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT; for (i = 0; i <= LIMIT_IDT / 8; i ) { set_gatedesc(idt i, 0, 0, 0); } load_idtr(LIMIT_IDT, ADR_IDT); // 高速cpu,IDT的在内存中的存放地址 set_gatedesc(idt 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);

以上代码就是设置IDT的所有代码,设定完成后,就可以等待中断程序,比如鼠标动作的发生了

第13行,定义了asm_inthandler21函数,作为中断函数表的第0x21号个中断函数,因为把它放在了idt 0x21位置处,一般在什么位置,就称为几号中断函数,所以,这里就称其为0x21号中断函数。

在第14,15行定义了0x27,0x2c号中断函数。

注意到set_gatedesc函数的第3个参数:2*8,指的就是GDT中定义的第2个内存段,操作系统代码bootpack.c所在的内存段。那么为什么要2*8呢?因为完整的描述一段内存的作用,需要如结构体SEGMENT_DESCRIPTOR所示的8个字节的数据,所以,第2个段的开始地址就是2*8了

关于IDT的代码解释完了,IDT所定义的中断函数的意义是什么?

中断函数asm_inthandler21是处理键盘中断过程的函数。

中断函数asm_inthandler2c是关于鼠标的中断处理函数。

也就是说,要处理鼠标,键盘的中断,只需要详细的完成这两个中断处理函数就可以了,这两个函数获取到鼠标和键盘的状态,然后我们再把鼠标的状态改变反馈到我们绘制的鼠标上。比如鼠标位置改变了,那么我们绘制鼠标的位置页要改变。

比如键盘按下某个键了,我们就在屏幕上输入相应的字符。

这些功能,我们就就在day06来讨论吧,毕竟中断函数的编写,以及对中断后的处理也是需要花费一些精力的。

总结:

今天是第5天,我们尝试在屏幕上显示了字符串,并且显示了鼠标。

这让当前这个界面,有点像操作系统的样子了

但是,当前这个鼠标是个静止的鼠标,所以。我们第一步就是先让鼠标可以操作

然后再让键盘可以操作。

这就是今后几天的目标。

在控制鼠标之前,需要先设置寄存器GDTR, 并声称一个表GDT,然后设置IDTR,并生成一个表IDT.

我们可以把获取鼠标按键,移动等代码放在IDT定义的中断函数中去实现,后续再像办法显示到界面上来。

上一页12末页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.