回顾一下C语言的代码
void HariMain(void){ char *vram; int xsize, ysize; init_palette(); vram = (char *) 0xa0000; xsize = 320; ysize = 200; ....此处省略 }像这种结构,直接把0xa0000,320,200写入C语言的方式有一个问题
当我们想改变画面模式时,系统不能正确运行,所以最好写入asmhead.nas文件中
然后从nas文件读取这些数据
更新:asmhead.nas
;-----------------------------------增加内容MOVBYTE [VMODE],8MOVWORD [SCRNX],320MOVWORD [SCRNY],200MOVDWORD [VRAM],0x000a0000更多内容,大家看一下源文件:05_day\harib02a\asmhead.nas
接收启动信息
在nashead文件设置后,我们要在C语言获取分辨率相关信息
更新:bootpack.c
void HariMain(void){ short *binfo_scrnx, *binfo_scrny; int *binfo_vram; int xsize, ysize; char *vram; init_palette(); binfo_scrnx = (short *) 0x0ff4; binfo_scrny = (short *) 0x0ff6; binfo_vram = (int *) 0x0ff8; xsize = *binfo_scrnx; ysize = *binfo_scrny; vram = (char *) *binfo_vram; init_screen(vram, xsize, ysize); for (;;) { io_hlt(); }}我们把显示画面背景的形状,独立出来做成一个函数 init_screen。
binfo_scrnx = (short *) 0x0ff4;xsize = *binfo_scrnx; 上面两行可以写成下面一行xsize = (short *) 0x0ff4;;
试用结构体
我们来看看普通结构体长啥样
void HariMain(void) { struct BOOTINFO abc; abc.scrnx = 320; abc.scrny = 200; abc.vram = 0xa0000; (以下略) } 结构体的好处是,可以将各种东西都一股脑儿 地传递过来。
func(abc);
如果没有结构体,就只能将各个参数一个一个地传递过来了。
func(scrnx, scrny, vram, ...);
现在,我们来升级普通结构体
更新:bootpack.c
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(); }}*binfo表示指针(内存地址)变量。所以应该首先给指针赋值,否则就不知道要往哪里读写了。可以写成下面这样: binfo = (struct BOOTINFO *)0x0ff0;
试用箭头记号
xsize = (*binfo).scrnx;等同 xsize = binfo— >scrnx;等同 binfo—>scrnx因为用箭头符号所以叫箭头标记
用箭头符号,升级C语言
更新:bootpack.c
struct BOOTINFO { char cyls, leds, vmode, reserve; short scrnx, scrny; char *vram;};void HariMain(void){ struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; init_palette(); init_screen(binfo->vram, binfo->scrnx, binfo->scrny); for (;;) { io_hlt(); }}
显示字符
二进制:0000 0000 0000 0001
二进制:16个数字表示16个bit,1个数字=1个bit
十六进制:01
十六进制:2个数字表示16个bit,1个数字=8个bit
同样是数字,差距咋这么大呢?
1byte=8bit,
1char=16byte
用16 *8的像素点阵来表示1个字符(char)
像这种描画文字形状的数据称为字体(font)数据
怎样写到程序里的呢?
很遗憾,C语言无法用二进制数记录数据,只能写成十六进制或八进制。
看看16进制如何表示font数据
static char font_A[16] = { 0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24, 0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00 };
来看看在C语言中怎么写
更新:bootpack.c
....此处代码脑补一下void putfont8(char *vram, int xsize, int x, int y, char c, char *font);void HariMain(void){ static char font_A[16] = { 0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24, 0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00 }; ....此处代码脑补一下} ....此处代码脑补一下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;}
测试:打开cmd,输入make run
(文件夹:harib02d)
增加字体
英文字母就有26个, 分别有大写和小写,还有10个数字,再加上各种符号肯定超过30个了。 太多了,所以用OSASK的字体数据
看看OSASK字体长啥样
char 0x41 ........ ...**... ...**... ...**... ...**... ..*..*.. ..*..*.. ..*..*.. ..*..*.. .******. .*....*. .*....*. .*....*. ***..*** ........ ........
完整版OSASK字体数据(05_day\harib02e\hankaku.txt)
基于hankaku.txt生成hankaku.bin
基于hankaku.bin生成hankaku.obj
更新:Makefile
#基于hankaku.txt生成hankaku.binhankaku.bin : hankaku.txt Makefile $(MAKEFONT) hankaku.txt hankaku.bin#基于hankaku.bin生成hankaku.objhankaku.obj : hankaku.bin Makefile $(BIN2OBJ) hankaku.bin hankaku.obj _hankaku接着在c语言中引用hankaku内容
更新:bootpack.c
void HariMain(void){ extern char hankaku[4096]; 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); .......此处脑补代码}OSASK的字体数据,依照一般的ASCII字符编码,含有256个字符
测试:打开cmd.bat,输入make run
(文件夹:harib02e)
显示字符串
之前是显示单个字符,这次改成字符串(像羊肉串一样,串起来!)
因此,增加新功能
更新:bootpack.c
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);void HariMain(void){ putfonts8_asc(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, "hello world"); putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Haribote OS."); putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Haribote OS."); ......自行脑补代码}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;}C语言中字符串都是以0x00结尾的,比如“helloworld”本质上是“helloworld0x00”,为了方便阅读0x00隐藏了.
所谓字符串是指按顺序排列在内存里,末尾加上 0x00而组成的字符编码。
测试:打开cmd.bat,输入make run
字体有阴影,本质是绘制了2次,白色在顶部,黑色在底部。
所以游戏中关掉阴影提升性能!
显示变量值
自制操作系统中不能随便使用printf函数,那怎么样显示变量的值呢?
可以使用sprintf函数,只是将输出内容作为字符串写在内存中, 它只对内存进行操作,所以适用于不同操作系统,要在C语言中使用sprintf函数
必须在源程序的开头写上#include
更新:bootpack.c
#include <stdio.h>void HariMain(void){ ...代码脑补 sprintf(s, "scrnx = %d", binfo->scrnx); putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);}%d
单纯的十进制数(0123456789)
%5d
5位十进制数。(12345)
如果是123,则在前面加上两个空格,变成" 123",强制达到5位
%05d
5位十进制数。
如果是123,则在前面加上0,变成"00123",强制达到5位
%x
单纯的十六进制数,字母部分使用小写abcdef(0123456789abcdef)
%X
单纯的十六进制数,字母部分使用大写ABCDEF(0123456789ABCDEF)
%5x
5位十六进制数。
如果是456(十进制),则在前面加上两个空格,变成" 1c8",强制达到5位
%5X
5位十六进制数。
如果是456(十进制),则在前面加上两个空格,变成" 1C8",强制达到5位
%05x
5位十六进制数。
如果是456(十进制),则在前面加上两个0,变成"001c8",强制达到5位
%05X
5位十六进制数。
如果是456(十进制),则在前面加上两个0,变成"001C8",强制达到5位
测试:打开cmd,输入make run
变量显示成功了!
显示鼠标指针
鼠标图标要用十六进制绘制,跟造字类似
static char cursor[16][16] = { "**************..", "*OOOOOOOOOOO*...", "*OOOOOOOOOO*....", "*OOOOOOOOO*.....", "*OOOOOOOO*......", "*OOOOOOO*.......", "*OOOOOOO*.......", "*OOOOOOOO*......", "*OOOO**OOO*.....", "*OOO*..*OOO*....", "*OO*....*OOO*...", "*O*......*OOO*..", "**........*OOO*.", "*..........*OOO*", "............*OO*", ".............***" };
在C语言中增加鼠标功能
更新:bootpack.c
void init_mouse_cursor8(char *mouse, char bc);void HariMain(void){ char mcursor[256]; init_mouse_cursor8(mcursor, COL8_008484); .....代码脑补}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;}
鼠标由三个部分构成
黑色轮廓白色填充背景色最后一行变量bc是指back-color,也就是背景色。
要将非鼠标区填充背景色,起到透明作用,还需要添加下面这个函数
更新:bootpack.c
void putblock8_8(char *vram, int vxsize, int pxsize, int pysize, int px0, int py0, char *buf, int bxsize);void HariMain(void){ ... init_mouse_cursor8(mcursor, COL8_008484); putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); ...}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;}pxsize和pysize是想要显示的图形(picture)的大小。鼠标指针的大小是16×16,所以这两个值都是16,
px0和py0指定图形在画面上的显示位置。
buf指定图形的存放地址。
测试:打开cmd 输入make run
(文件夹harib02h)
GDT与IDT的初始化
想要移动鼠标,涉及到设备状态的检测
鼠标、 软驱、硬盘、光驱、网卡、声卡等很多需要定期查看状态的设备。其中,网卡还需要CPU快速响应。响应不及时的话,数据就可能接受失败,而不得不再传送一次。如果因为害怕处理不及时而靠查询的方法轮流查看各个设备状态的话,CPU就会穷于应付,不能完成正常的处理。
正是为解决以上问题,才有了中断机制。各个设备有变化时就产生中 断,中断发生后,CPU暂时停止正在处理的任务,并做好接下来能够继 续处理的准备,转而执行中断程序。中断程序执行完以后,再调用事先 设定好的函数,返回处理中的任务。正是得益于中断机制,CPU可以不 用一直查询键盘,鼠标,网卡等设备的状态,将精力集中在处理任务 上。
GDT是“global(segment)descriptor table”的缩写,意思是全局段号记录表。
将这些数据整齐地排列在内存的某个地方,然后将内存的起始地址和有效设定个数放在CPU内被称作GDTR的特殊寄存器中,设定就完成 了。
IDT是“interrupt descriptor table”的缩写,直译过来就是“中断记录表”。
当CPU遇到外部状况变化,或者是内部偶然发生某些错误时,会临时切换过去处理这种突发事件。这就是中断功能。
新增了一些功能
更新:bootpack.c
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;}这里涉及到的内容太多,建议大家看看书
测试:打开cmd,输入make run
今天到此,洗洗睡了