第4天 C语言与画面显示的练习
1.用C语言实现内存写入
现在想要在屏幕上显示点什么东西,需要向VRAM中数据,虽然C语言可以写内存,但我们还是先用汇编来完成这个工作吧。现在在naskfunc.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
在第二天的博客中有一张CPU内存部分寄存器的表,SP表示16位的栈指针寄存器,而此处ESP表示32位的栈指针寄存器。函数的参数会按地址从低到高的顺序存放在栈中,可以通过ESP取出参数。
这段代码的含义就是把data数据存入内存的addr地址中。另外,要与C语言联合使用的话,能自由使用的寄存器就只有EAX、ECX和EDX,因此不能随意更改代码中的寄存器。
naskfunc.c中还添加了一个INSTRSET指令,由于书中有介绍就不多说了。
汇编部分完成了,就开始写C语言代码了。
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void)
{
int i;
for (i = 0xa0000; i <= 0xaffff; i++)
{
write_mem8(i, 15); /* MOV BYTE [i], 15 */
}
while (1)
{
io_hlt();
}
}
不会C语言语法的读者就先去学学C语言吧,相信我,那会事半功倍的。
这段代码是将地址0xa0000~0xaffff的内存全部写入15,然后一直死循环。
来运行看看效果吧。
一片白色,正是我们想要的效果。
2.条纹图案
改成条纹图案的操作倒是很简单,只需要改一行代码:
for (i = 0xa0000; i <= 0xaffff; i++)
{
write_mem8(i, i & 0x0f); /* MOV BYTE [i], 15 */
}
这里用到了与运算,如之前所说,不介绍C语言的基础语法,相信大家也能看懂。
这段代码就是把调色板的16种颜色全部显示出来,并循环往复,我们设置的屏幕宽度是320像素,是16的整数倍,所以相同的颜色会在同一列出现,形成条纹。
最后的运行效果如下:
这图看久了眼睛都觉得有点花。
3.挑战指针
指针作为C语言的核心,自然是重中之重。什么?你没学过指针?那你学的什么C语言。什么?感觉指针太难了,学不会?那我只能说C语言不适合你,写操作系统也不适合你,去学Java、Python或Php吧。
来看看使用指针后的代码吧。
int i; /* int为32位 */
char *p; /* BYTE型地址 */
for (i = 0xa0000; i <= 0xaffff; i++)
{
p = (char*) i;
*p = i & 0x0f; /* 代替了write_mem8(i, i & 0x0f); */
}
p = (char*) i;这句代码中对i进行了类型转换,虽然在这里字符指针和整型数据都占4字节,但编译器它不听啊,你不按规矩来,他就报警告,虽然警告没什么影响,但眼不见心不烦嘛,0 error 0 warning听起来多舒服。
运行一下看结果。
4.指针的应用(1)
对代码做点小小的修改:
p = (char *) 0xa0000; /* 给地址变量赋值 */
for (i = 0; i <= 0xffff; i++)
{
*(p + i) = i & 0x0f;
}
图我就不放了,反正效果看上面的就行了。
5.指针的应用(2)
对代码做点小小的修改:
p = (char *) 0xa0000; /* 给地址变量赋值 */
for (i = 0; i <= 0xffff; i++)
{
p[i] = i & 0x0f;
}
这和数组操作差不多。
图也不放了,偷懒。
6.色号设定
既然都能显示颜色了,那自定义要显示的颜色?当然可以!但是我们只能设置最多256种颜色,至于为什么只能设置256位,还记得我们在asmhead.nas中设置的8位彩色模式吧。这个8位彩色模式,是由程序员随意指定0~255的数字所对应的颜色的。1个数字对应一种颜色,这种方式被称为调色板。
来看看代码吧。
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] =
{
0x00, 0x00, 0x00, /* 0:黑 */
0xff, 0x00, 0x00, /* 1:亮红 */
0x00, 0xff, 0x00, /* 2:亮绿 */
0xff, 0xff, 0x00, /* 3:亮黄 */
0x00, 0x00, 0xff, /* 4:亮蓝 */
0xff, 0x00, 0xff, /* 5:亮紫 */
0x00, 0xff, 0xff, /* 6:浅亮蓝 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:亮灰 */
0x84, 0x00, 0x00, /* 9:暗红 */
0x00, 0x84, 0x00, /* 10:暗绿 */
0x84, 0x84, 0x00, /* 11:暗黄 */
0x00, 0x00, 0x84, /* 12:暗蓝 */
0x84, 0x00, 0x84, /* 13:暗紫 */
0x00, 0x84, 0x84, /* 14:浅暗蓝 */
0x84, 0x84, 0x84 /* 15:暗灰 */
};
set_palette(0, 15, table_rgb);
}
这个函数的作用就是定义颜色数据,然后将数据传入下一个函数中进行处理。
这里我们得先讲讲一些汇编代码,因为下面的C语言程序需要用到汇编语言编写的函数。作者搞事啊,一下子蹦出这么多函数,我就挑几个来讲讲好了。
_io_in8: ; int io_in8(int port)
MOV EDX, [ESP + 4] ; port
MOV EAX, 0
IN AL, DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX, [ESP + 4] ; port
MOV AL, [ESP + 8] ; data
OUT DX, AL
RET
_io_in8用于从端口读入一个字节,_io_out8用于向端口输出一个字节。其中,IN和OUT都是汇编中的I/O操作指令,IN用于从端口读取数据,OUT用于向端口写入数据。
如:IN AL, 21H表示从21H端口读取一个字节数据到AL。
IN AX, 21H表示从21H端口读取两个字节数据到AX(21H端口的数据放入AL,22端口的数据放入AH)
OUT 21H, AL表示将AL的值写入21H端口
OUT 21H, AX表示将AX的值写入端口地址21H开始的两个字节(AL写入21H端口,AH写入22H端口)
注:当I/O地址大于FFH时,地址需放入DX中,而且数据传输只能通过使用EAX,AX或AL。
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
CLI指令用于禁止中断发生,STI指令用于允许中断发生。所以这两函数的功能也显而易见。
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; 将标志寄存器数据压入栈中
POP EAX
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX, [ESP + 4]
PUSH EAX
POPFD ; 将数据写入标志寄存器中
RET
这两函数的主要内容都已经在注释写好了,而且关于栈操作书上也写得很清楚,我就不多说了。接下来继续介绍C语言文件的函数。
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); /* 复原中断许可标志 */
}
首先用io_load_eflags保存状态寄存器的值,然后用io_cli将中断标志位置0(中断标志位在状态寄存器上),禁止中断,最后复原状态寄存器的值,被修改的中断标志就会被修改回来。
代码中分别对0x3C8和0x3C9端口进行了操作,这两个端口的操作步骤如下:
- 屏蔽中断
- 将想要设定的调色板号码写入0x3C8,紧接着,按R、G、B的顺序写入0x3C9。如果还想继续设定下一个调色板,则省略调色板号码,再按照RGB的顺序写入0x3C9就行了。
- 恢复中断
程序的结果就是把调色板的数字0对应黑色,1对应红色,……,15对应暗灰。其中有个奇怪的地方就是,io_out8(0x03c9, rgb[0] / 4);这段代码中,当rgb[0]=0xff时,代码中为rgb[0]或rgb[0] / 4的效果是一样的;但当rgb[0]=0x84时,效果也不一样了。而且,当rgb[0]=0xff时,代码为io_out8(0x03c9, rgb[0] / 4);和代码为io_out8(0x03c9, 0xcf);的效果不一样,也不知道是为什么。如果有人知道请务必在下方评论告诉我。
主程序并没有什么变化。让我们看看修改后的效果。
可以看到从左到右依次是黑、红、绿、黄……,和我们设定的一样。
7.绘制矩阵
绘制矩阵是完成靠C语言程序逻辑实现的,而且并没有什么难度,相信读者花一点时间也能琢磨出一个实现绘制矩阵的算法。
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;
}
}
}
如下图所示:
首先从(x0, y0)点开始填充像素点,直到(x1, y0),然后换行继续填充,直到(x1, y1)。很简单的程序逻辑。
说来惭愧,之前竟然把调色板的工作方式给忘了,所以面对COL8_000000这个定义是数字时还想了一会儿是怎么回事。因为调色板的工作方式,一个数字代表一个颜色,只要往VRAM中写入数字,屏幕上会显示数字相应的颜色。
主函数中添加了绘制矩阵的代码,没什么好讲的,就直接看效果吧。
也不知道五彩斑斓的黑的颜色怎么定义,我想画出来。
8.今天的成果
大家期待的界面它来了!
先看看代码。
void HariMain(void)
{
char *vram; /* BYTE型地址 */
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);
while (1)
{
io_hlt();
}
}
老实说,代码没什么好讲的。我就是来水字数的。下面是界面效果。
这界面应该能和85年的Windows界面有的一拼。
今天的内容又结束了。本以为出完第3天的博客之后要等半个月才能写出这篇来,结果只花了3天时间。其中,2天学第7章的内容,1天时间写这篇博客。。。虽然博客简陋,内容也不多,也是花了我3小时才写出来的。愿你能继续看第5天的博客!