处理器:Intel Celeron(R) Dual-Core CPU
操作系统:Windows7 专业版 x86
阅读书籍:《30天自制操作系统》—川合秀实[2015.03.17 – 03.18]
将《30天自制操作系统》简称为“书”。
工具:../tolset/
在“[Rx86OS-III]由实模式切换到保护模式”笔记中设定了图像显示的画跟显存地址的关系,其中VRAM的内存地址空间为0xa0000~0xaffff。往这段内存地址空间写入0~ 255的数值就会让屏幕呈现出颜色。如往0xa0000~0xaffff中的每个字节中写入15,屏幕就会呈现出白色。
为什么往内存地址空间0xa0000~0xaffff中的每个字节内写入15就能让屏幕显示白色呢?因为笔记[OS-III]指定了画面的模式为320x 200的8位颜色模式,这8位颜色模式能对应0~ 255个数字,程序员可随意指定0 ~ 255所对应的RGB颜色(如#ffffff表示白色,参见“RGB颜色查询对照表”)。程序员可以规定8位颜色模式的25号对应颜色#ffffff,26号对应颜色#000000。默认情况下,15号对应的颜色就是#ffffff,白色。
像这种拥有8位颜色模式,但可以被随意指定0~ 255的数字去对应RGB颜色的方式被称为调色板。8位模式对应的0~ 255数值称为调色板号。
为了更好的访问调色板,访问调色板的一般步骤如下。
[1] 访问调色板前屏蔽中断。
[2] 将想要设定的调色板号写入0x03c8,然后按照R,G,B的顺序写入0x03c9。如果还想继续设定下一个调色板,则省略调色板号,再按照RGB的顺序写入0x03c9就行了。
[3] 想要读出当前调色板的状态,首先要将调色板的号码写入0x03c7,再从0x03c9读取3次。读出的顺序就是R,G,B。如果要继续读出下一个调色板,同样也是省略调色板号码的设定,按RGB的顺序读出。
[4] 如果设置调色板之前执行了CLI,则设置完调色板后要执行STI。
选择调色板的调色板号来与RGB中的颜色对应:
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]); io_out8(0x03c9, rgb[1]); io_out8(0x03c9, rgb[2]); rgb += 3; } io_store_eflags(eflags); //恢复屏蔽中断之前标志寄存器的值 return; }
CPU可以直接访问(与CPU相连)的硬件有寄存器、内存、端口号(设备寄存器)。如果计算机的硬件用指令来区分设备和内存的访问,那么CPU就只能通过OUT和IN指令用来访问指定的端口,从而访问设备。而C语言中无与OUT/IN等汇编指令对应的语句,所以只能用汇编程序来实现调色板的设置。将这部分程序写在naskfunc.nas中形成函数,再供C程序调用。
1. ; naskfunc
2. ; TAB=8
3.
4. [FORMAT "WCOFF"] ;制作目标文件的格式
5. [INSTRSET "i486p"] ;让编译器识别32模式下的指令
6. [BITS 32] ;制作32位模式用的机器语言
7. [FILE "naskfunc.nas"] ;源文件名信息
8.
9. GLOBAL _io_hlt, _io_cli, _io_sti
10. GLOBAL _io_out8
11. GLOBAL _io_load_eflags, _io_store_eflags
12.
13. [SECTION.text]
14.
15. _io_hlt: ; void io_hlt(void);
16. HLT
17. RET
18.
19. _io_cli: ; void io_cli(void);
20. CLI
21. RET
22.
23. _io_sti: ; void io_sti(void);
24. STI
25. RET
26.
27. _io_out8: ; void io_out8(int port, int data);
28. MOV EDX,[ESP+4] ;port
29. MOV AL,[ESP+8] ;data
30. OUT DX,AL
31. RET
32.
33. _io_load_eflags: ; int io_load_eflags(void);
34. PUSHFD
35. POP EAX
36. RET
37.
38. _io_store_eflags: ; void io_store_eflags(int eflags);
39. MOV EAX,[ESP+4]
40. PUSH EAX
41. POPFD
42. RET
CLI指令将标志寄存器的中断标志置为0,中断标志位为0时CPU会忽略中断请求;STI指令将中断标志位置为1,中断标志位为1时CPU会立即处理中断请求。
因为我们只需要在访问调色板的过程中屏蔽中断,从而改变标志寄存器。在访问调色板后还需要将中断标志寄存器的值复原。这个过程通过“_io_load_eflags”和“_io_store_eflags”来完成:“_io_load_eflags”将压入栈中的标志寄存器的值出栈给EAX寄存器,再用RET指令返回(EAX寄存器的值就是函数的返回值,《汇编语言》 – 王爽 “使用内存空间”);“_io_store_eflags”函数将参数给EAX,将压栈的EAX值(就是原标志寄存器的值)出栈给标记寄存器。“ESP+4”是“_io_store_eflags”函数的第一个参数的地址(《汇编语言》 – 王爽 “函数如何接收不定数量的参数”内为“BP + 4”)。
“_io_out8”程序首先将port参数(设备号)取到EDX中,将data(写往prot号设备的数据)读入AL中,然后调用OUT指令将AL写往DX代表的端口中。
组织几种RGB颜色到数组中(跟“书”中一样,我不知道RGB值对应的具体颜色)来充当set_palette函数的rgb参数的实参。
static unsigned char table_rgb[16 * 3] = { //R , G, B 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, //<span style="font-family: Arial, Helvetica, sans-serif;">暗</span><span style="font-family: Arial, Helvetica, sans-serif;">青</span> 0x84, 0x00, 0x84, //暗紫 0x00, 0x84, 0x84, //浅暗蓝 0x84, 0x84, 0x84 //暗灰 };
在进入32位模式前做的画面中,图像显示的VRAM内存地址空间为0xa0000~ 0xafffff,显示画面的屏幕上设置为320x200(=6400)个像素点:
{
(0,0), (1,0), …,(319,0 ),
(0,0), (1,1), …,(319,1 ),
……
(0,199),(1,199), …, (319,199 )
}
屏幕上显示画面的位置(x,y)和VRAM地址空间有一个逻辑映射关系:ADD(VRAM)= 0xa0000 + y * 320 + x。按照ADD(VRAM)的计算方式,往ADD(VRAM)内写上某种调色板号,(x,y)位置就会出现相应的颜色,就得到一个点。继续增加x的值,就能得到一条水平直线。再向下循环这条直线,就能画出很多的直线,组成一条有填充色的长方形。
//vram为vram的首地址,xsize为屏幕x方向像素值,c为调色板号 //x0, y0为矩形左上角的坐标;x1, y1为矩形右下角坐标 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; }
在bootpack.c中的HariMain函数中调用“设置调色板程序”和“显示矩形的程序”:
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 boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1); #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 HariMain(void) { char *p; //设置调色板 init_palette(); p = (char *) 0xa0000; //绘制矩形,矩形由线构成,线由点构成 boxfill8(p, 320, COL8_FF00FF, 20, 40, 120, 140); boxfill8(p, 320, COL8_000084, 180, 40, 280, 140); boxfill8(p, 320, COL8_FFFFFF, 100, 0, 200, 100); for (;;) { io_hlt(); } } //函数定义 …..
运行“书”中的“!cons_nt.bat”工具,并在弹出的命令行窗口中运行“makeinstall”命令将这几个文件形成的“.img”文件下载到磁盘中,然后重启计算机运行“显示矩形的操作系统程序”,结果如下:
Figure 1. 显示矩形的操作系统程序运行结果
显示任务栏也是通过往显存内写调色板号(调色板号对应一个RGB颜色)实现。这只需要在主函数HariMain中调用boxfill8函数来实现。
void init_screen(char *vram, int x, int y); void HariMain(void) { char *vram; int xsize, ysize; init_palette(); vram = (char *) 0xa0000; xsize = 320; ysize = 200; init_screen(vram, xsize, ysize); for (;;) io_hlt(); } void init_screen(char *vram, int x, int y) { boxfill8(vram, x, COL8_008484, 0, 0, x - 1, y - 29); boxfill8(vram, x, COL8_C6C6C6, 0, y - 28, x - 1, y - 28); boxfill8(vram, x, COL8_FFFFFF, 0, y - 27, x - 1, y - 27); boxfill8(vram, x, COL8_C6C6C6, 0, y - 26, x - 1, y - 1); boxfill8(vram, x, COL8_FFFFFF, 3, y - 24, 59, y - 24); boxfill8(vram, x, COL8_FFFFFF, 2, y - 24, 2, y - 4); boxfill8(vram, x, COL8_848484, 3, y - 4, 59, y - 4); boxfill8(vram, x, COL8_848484, 59, y - 23, 59, y - 5); boxfill8(vram, x, COL8_000000, 2, y - 3, 59, y - 3); boxfill8(vram, x, COL8_000000, 60, y - 24, 60, y - 3); boxfill8(vram, x, COL8_848484, x - 47, y - 24, x - 4, y - 24); boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y - 4); boxfill8(vram, x, COL8_FFFFFF, x - 47, y - 3, x - 4, y - 3); boxfill8(vram, x, COL8_FFFFFF, x - 3, y - 24, x - 3, y - 3); return; }
修改HariMain后,重新运行的结果:
Figure 2. 显示desktop的操作系统程序的运行结果
界面之上的“凸”、“凹”只是因为因不同颜色而形成的视觉差异。
[1] 使用BIOS程序时查看BIOS手册;访问VGA(调色板)时需要查看VGA手册…….
[2] 想要在屏幕中显示图像,就需要找到显存地址空间和屏幕坐标之间的对应关系。往显存内写的内容(字符,颜色)就会在屏幕上显示出来。显存的颜色可以通过设置调色板用调色板号去对应任意的RGB颜色,从而屏幕上可以灵活的展现五彩之色。
[3] 屏幕之上的“凸”、“凹”(如按钮)只是因为因不同颜色而形成的视觉差异。
[4] 调色板增加更多颜色的设置见p-526-528(RGB色阶,混用颜色)。(兼容VESA标准的电脑)VESA全彩色模式也可使用更多的颜色。
[x86OS] Note Over.
[2015.04.17]