首先啊,今天的内容比较多,但大家可以直接看书最后面,一想到做完后就能看到这个界面上有我们画上去的东西就开心纳,朝着这个尽头,开始吧。
首先就是用C语言写内存,如果用汇编写的话,就是用[]这种来进行内存读写,其实我们就是要在汇编里封装一些函数让C去调用。这里突然想起了,做手游,为了热更新,也是这个套路。
_write_mem8: ; void write_mem8(int addr, int data);
MOV ECX,[ESP+4] ; [ESP+4]‚中存放的是地址,将其读入ECX
MOV AL,[ESP+8] ; [ESP+8]‚中存放的是数据,将其读入AL
MOV [ECX],AL
RET
首先,写了个函数叫write_mem8,其意是向内存地址中写入8个字节,所以用的是AL。
函数本身也很简单,作者在这里做了说明,只能用EAX,ECX,EDX,因为用于记忆C语言编译后的机器语言中。
[INSTRSET "i486p"] ; 486‚指定用于486,当然也不是说其他的就不能用。
int i;
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, i & 0x0f);
}
for (;;) {
io_hlt();
}
注释也省略了,因为我觉得到了C语言,应该没有什么语法看不懂的了,主要是思维。昨天已经介绍过0xa00000-0xaffff是使用这个模式,对这部分内存操作就是对最终屏幕绘制出来的内容密切相关。
条纹图案
这里巧妙的运用了AND运算符,由于每次遍历出来一个字节,8位,与0x0f与运算,也就是0x0f有低四位但是高四位是0。由于i、每隔16进一次位,所以每隔16次反复进行此运算,而这16此每一次的计算,保留低4位,因为f的存在,高四位变成0因为0x0f中0的存在。
指针
理所当然C语言的指针就是拿来玩儿内存的,可能是读者为了让我们更了解原理,然后写了之前的read_mem8这个函数。如果直接对i进行取地址,那他当然不知道这个地址是个什么类型,当然也就不知道赋值多少位过去,所以写了个char *p
指针在专栏里作者讲了一大堆,如果能看懂,哦,作者讲的确实对。但我觉得对于初学者,去学一些C语言的书,多看些打印的值,更直观些。说个我也会忘的知识吧 a[1] 1[a]是一样的意思。
void io_hlt(void);
void io_cli(void);
void io_out8(int port, int data);
int io_load_eflags(void);
void io_store_eflags(int eflags);
// 就算写在同一个源文件里,在使用之前必须声明一下,如果函数在前面则不用
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void HariMain(void)
{
int i; // 声明变量,这里的变量i是32位整型
char *p; // 变量p是Byte[...]用的地址
init_palette(); // 设定调色板
p = (char *) 0xa0000; // 指定地址
for (i = 0; i <= 0xffff; i++) {
p[i] = i & 0x0f;
}
for (;;) {
io_hlt();
}
}
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
0x00, 0x00, 0x00,
0xff, 0x00, 0x00,
0x00, 0xff, 0x00,
0xff, 0xff, 0x00,
0x00, 0x00, 0xff,
0xff, 0x00, 0xff,
0x00, 0xff, 0xff,
0xff, 0xff, 0xff,
0xc6, 0xc6, 0xc6,
0x84, 0x00, 0x00,
0x00, 0x84, 0x00,
0x84, 0x84, 0x00,
0x00, 0x00, 0x84,
0x84, 0x00, 0x84,
0x00, 0x84, 0x84,
0x84, 0x84, 0x84
};
set_palette(0, 15, table_rgb);
return;
// C语言中的static char语句只能用于数据,相当于汇编中的DB指令
}
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); // 记录中断许可标志的值
io_cli(); // 将中断许可标志置为0,禁止中断
io_out8(0x03c8, start);
for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); // 复原中断许可标志
return;
}
代码有点长,不过声明就占了好些行数。记得不管以前是学C还是其他高级语言对于静态变量的说法都是,静态存储区,特有的存储区。具有全局性等,但现在如果从汇编的角度来看,好像会对他产生全新的认识。记住static就相当于在汇编使用DB一样。作者说这个方式a[0] = 1;占三个字节的原因可能是赋值语句转换位汇编是mov ....可能是这样导致的,所以改成static就能避免很多这样的操作。
我们初始我们想要的颜色后,现在要设定到调色板里去。虽然作者说让我们先不理解io_out这个函数,但我觉得有点奇怪,一直往一个地址里面写值,不久把之前的覆盖了吗?还是我写一个,就会有相应的东西把值取走,然后保存下来?
有两个函数CLI与STL这两个配合起来意思是我在做这段代码的过程中不许任何人打搅,感觉有点像高级语言的线程锁。
前面几个函数相对简单,我直接来搞后两个新函数。如果之前知道push和pop的来理解也就不难,PUSHFD先把eflags的值存在栈里面,然后又放到EAX里面去(不知道为什么作者没讲,要想返回函数的值,放到EAX里面就好了,所以在C语言我们可以接收到一个返回值)。由于这个值到C那边跑了一圈,又传回来了,所以我们再入栈,在POPFD又回到了EFLAGS里边儿。
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}
看到这里更带劲儿了,因为画出了个正方形,来看看这个函数,思想也比较简答, 就是块数组,指定区域用指定值,然后对应的就是指定屏幕变成指定颜色。像这种常值的值,定义成define就好。
void HariMain(void)
{
char *vram;
int xsize, ysize;
init_palette();
vram = (char *) 0xa0000;
xsize = 320;
ysize = 200;
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);
boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);
for (;;) {
io_hlt();
}
}
这段代码运行后,简直是太棒啦,感觉有点想我们的桌面了,但其实还是前面的知识,只是巧妙的利用了boxfill8这个函数。
大概解释一下吧,第一个boxfill8是绘制桌面的感觉,一张蓝绿色的低,从左上角到离右下角y29,x1的位置,至于为什么作者在x方向少了一个1不太理解,可能是留出边缘吧。第二个是绘制一个桌面与状态栏的灰色分割线。然后是蓝色分割线,以此。。。还有一个办法看每一行到底做了哪些东西,可以一句代码一句代码的注释,然后看效果。实话说这个操作系统是真的小,像我做exe,apk,ipa什么的随便弄个空项目出来就好几百K,甚至用游戏引擎的话,随随便便就是几MB,简直不敢想象,那个在我们看来是个空项目,内部到底有多少代码。
写在最后我记得我在编译这一天代码的时候出现过一个问题, 不过最后也找到了,就是路径问题,请大家注意一下,tolsetz_toolsharibote这个路径下的haribote.rul文件内容的路径一定也要修改,不然make run会报错。