[Rx86OS-V] 用C语言实现画面(颜色)

平台

处理器:Intel Celeron(R) Dual-Core CPU

操作系统:Windows7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.03.17 – 03.18]

将《30天自制操作系统》简称为“书”。

工具:../tolset/


1画面显示基础

1.1 调色板

在“[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 调色板的访问步骤

为了更好的访问调色板,访问调色板的一般步骤如下。

[1] 访问调色板前屏蔽中断。

[2] 将想要设定的调色板号写入0x03c8,然后按照R,G,B的顺序写入0x03c9。如果还想继续设定下一个调色板,则省略调色板号,再按照RGB的顺序写入0x03c9就行了。

[3] 想要读出当前调色板的状态,首先要将调色板的号码写入0x03c7,再从0x03c9读取3次。读出的顺序就是R,G,B。如果要继续读出下一个调色板,同样也是省略调色板号码的设定,按RGB的顺序读出。

[4] 如果设置调色板之前执行了CLI,则设置完调色板后要执行STI。


2 绘制矩形

2.1 设置调色板号的颜色

选择调色板的调色板号来与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;
}

(1) 相关的汇编函数

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代表的端口中。


(2) 设置调色板号颜色

组织几种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	//暗灰
};

2.2 绘制矩阵函数

在进入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;
}


2.3 主函数

在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. 显示矩形的操作系统程序运行结果

3 显示任务栏

显示任务栏也是通过往显存内写调色板号(调色板号对应一个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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值