30天自制操作系统第四天

来源:互联网 发布:网络控制手机发短信 编辑:程序博客网 时间:2024/05/16 12:36

1:用C语言实现内存的写入
上篇成功的实现了黑屏,今天我们要争取把画面上显示一些东西。想要画些东西的话,只要往VRAM里写点东西就可以了,这里我们先使用汇编语言写入内存,
_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
在C语言中如果使用write_mem8函数,就会跳转到_write_mem8,此时参数指定的数字存放在内存里,分别是
第一个数字的存放地址:[ESP+4]
第二个数字的存放地址:[ESP+8]
第三个数字的存放地址:[ESP+12]
第四个数字的存放地址:[ESP+16]
以下略
其中ECX为32位寄存器。如果与C语言联合使用的话,有的寄存器可以自由使用,有的寄存器不能自由使用,能自由使用的只有EAX、ECX、EDX这三个。其它只能使用其值,不能改变其值。
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, 15); /* MOV BYTE [i],15 */
}
在C文件中在每个显卡内存中写入15,第15中颜色是白色,所以我们运行出来,是白板。
2.条纹图案
接着我们再此稍微修改一下,就可以显示条纹图案了。
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, i & 0x0f); /* MOV BYTE [i],15 */
}
让特定位变为1的功能,可以用或;
让特定位变为0的功能,可以用与;
让特定为翻转,可以用异或;
运行后可以发现,显现为条纹状。
3.挑战指针
C语言中没有直接向内存地址写入的语句,实际上不是C语言的缺陷,在C语言中我们可以使用指针来向内存地址写入。
修改程序如下:
char p; / p用于BYTE型地址
for (i = 0xa0000; i <= 0xaffff; i++) {
p = i; /* 代入地址
*p = i & 0x0f;
}
程序运行结果和上面调用汇编程序显示成功一样。
注意:p代表的是内存地址,如0x123,而*p代表的是改地址存储的东西,如[0x123]
4.色彩的设定
在显示完条带图形后,我们现在想要做个操作系统的界面,在之前,我们首先要做的就是色号的设定。由于我们采用了8位颜色模式,也就是0~255的数,这个非常的小,一般指定颜色都是用#ffffff一类的数,用六位十六进制数,就是24位来指定延时,8位数完全不够,因此我们在这里就采用了调色板,我们可以随意指定0~255的数字所对应的颜色的。如果默认设定,则0号颜色代表#000000,15号颜色就是#ffffff。这里作者随便设定了几个操作系统需要用到的颜色号。
注意:关于调色板,我在博客中发现了一些详细的解释。调色板只有图片的颜色小于等于256色的时候才有,16位高彩和24位32位真彩是没有调色板的。调色板的存在的意义只是在当初486以前为了节省空间的一种采用索引的压缩算法,现在没有人这种东西。调色板是为了节约空简所用的,相当于一个索引。只有16位以下的才用调色板,真彩色不用调色板。
让我们来看看下面的例子。
有一个长宽各为200个象素,颜色数为16色的彩色图,每一个象素都用R、G、B三个分量表示。因为每个分量有256个级别,要用8位(bit),即一个字节(byte)来表示,所以每个象素需要用3个字节。整个图象要用200×200×3,约120k字节,可不是一个小数目呀!如果我们用下面的方法,就能省的多。
因为是一个16色图,也就是说这幅图中最多只有16种颜色,我们可以用一个表:表中的每一行记录一种颜色的R、G、B值。这样当我们表示一个象素的颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值。举个例子,如果表的第0行为255,0,0(红色),那么当某个象素为红色时,只需要标明0即可。
让我们再来计算一下:16种状态可以用4位(bit)表示,所以一个象素要用半个字节。整个图象要用200×200×0.5,约20k字节,再加上表占用的字节为3×16=48字节.整个占用的字节数约为前面的1/6,省很多吧?
这张R、G、B的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表LUT(Look Up Table),似乎更确切一些。Windows位图中便用到了调色板技术。其实不光是Windows位图,许多图象文件格式如pcx、tif、gif等都用到了。所以很好地掌握调色板的概念是十分有用的。
有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的R、G、B颜色表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色图时,每个象素直接用R、G、B三个分量字节表示,而不采用调色板技术。原因很明显:如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24位(因为总共有224种颜色,即调色板有224行),和直接用R,G,B三个分量表示用的字节数一样,不但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用R、G、B三个分量表示,它又叫做24位色图。来自:http://blog.csdn.net/applenob/article/details/19134911
上面的话是我直接摘自该博客的文字;
这次我们给bootpack.c加入了很多的代码,我们下面来一点点的分析。
首先,我们加入了两个函数
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
第一个是初始化调色板,第二个是设置调色板;
初始化调色板的具体内容如下:
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);
return;
}
程序开头定义了一个静态数组,之所以用静态数组的原因是静态数组很大的精简了机器码,因为char a[3]={1,2,3},相当于char a[3] ,a[1] =1;a[2]=2;a[3]=3;因为每个赋值语句就消耗了三个字节,而使用静态赋值,相当于使用DB只需要3个字节就可以了。
程序接下来调用了,set_palette函数,我们来看看它是如何定义的:
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 记录中断许可标志的值
io_cli(); /* 关闭中断
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;
}
程序的核心部分是调用io_out8函数,这个函数是干什么用的,我们具体解释一下:
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET
io_out8 是一个汇编代码。它是往指定设备发送数据的函数;
为什么不用C语言来完成呢?
我们的CPU通过管脚与内存相连,完成计算和存储功能呢,但计算机还有很多外设,CPU还需要处理外设的响应,从外设获得信息,如通过声卡发出声音,通过网卡获得网络信息等。因此CPU与设备相连,那么CPU就有与这些设备交流的指令,向设备发送电信号的是OUT指令,获得设备电信号的是IN指令。为了区分不同的设备,我们也要使用设备号码,port。
其中设置调色板的访问步骤如下:
1.首先在一连串的访问中屏蔽中断;
2.将想要设定的调色板号码写入0x03c8,紧接着,按RGB的顺序写书0x03c9。如果设定下一个调色板,则省略调色板号码,在按照顺序写入0x03c9就行了。
3.如果想要读出当前调色板状态,首先将调色板号码写入0x03c7,再从0x03c9中读取3次,读出的顺序就是RGB,如果要继续读出下一个调色板,同样也是省略调色板号码,按RGB的顺序读出。
4.如果执行了CLI,那么最后要执行STI。
现在,我们来看看程序,程序首先向0x03c8写入0,代表要写入0号调色板,然后在循环中,不断向0x03c9写入RGB数据,直到写完16个调色板。
下面我们来看一些还没有解决的代码:eflags = io_load_eflags()这个函数时什么呢,我们来看一看:
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; PUSH EFLAGS
POP EAX
RET
该段程序的意思简明来写就是让EAX=EFLAGS;在EAX中存储中断许可标志值。这是第一个有返回值的函数,根据C语言规约,EAX的值会被看做函数的返回值。
_io_cli: ; void io_cli(void);
CLI
RET
禁止中断,将中断许可标志置零。
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; POP EFLAGS
RET
程序的意思是EFLAGS = EAX。还原中断许可标志。
那么EFLAGS这个寄存器是什么呢,它是由FALGS的16为寄存器扩展而来的32位寄存器,FLAGS是存储进位标志等标志的寄存器。
5.绘制矩形
颜色备齐了,下面我们就可以画画了,画面上有320*200=64000个像素,右下点的坐标为(319,199)那么像素坐标(x,y)对应的VRAM地址计算为:0xa0000+x+y*320;
本程序就多了一个新的函数:
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声明是为了便于理解。
接下来作者就接着绘制了简单的系统界面。今天的内容就到此结束了。

0 0
原创粉丝点击