龙空技术网

30天自制操作系统day04:用C来控制画面显示

老师明明可以靠颜值 885

前言:

而今小伙伴们对“c语言建立坐标系绘图”大体比较关切,你们都想要知道一些“c语言建立坐标系绘图”的相关文章。那么小编同时在网摘上网罗了一些关于“c语言建立坐标系绘图””的相关文章,希望同学们能喜欢,看官们一起来学习一下吧!

第三天,我们完成了一些基本的设定,实现了操作系统代码bootpack.c的编写。

第三天教程的地址:

以及一些说明:

但是,第三天的bootpack.c内部什么事也没有做,只是个无限等待的循环,同时操作系统的画面是一片黑色,就是黑屏。

结果展示

那么今天,我们就在这黑屏上画一个窗口出来,画这个窗口如下:

这个窗口的所有代码在:

那么这个窗口具体的代码为:

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		15void 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();	}}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);	return;	/* 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;}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;}

可以看到,28行的HariMain函数内,基本上一堆的boxfill8函数,

也就是说,就是这些boxfill8函数构成了最终的窗口画面。

因为今天的目标就是写这样一个画面出来,所以,只要详细解析boxfill8函数,今天的任务就完成了。

那么boxfill8函数实现了怎样的功能?它是如何控制显示屏来显示出了一个窗口的?

控制显示屏

实际上,在昨天的代码中,我们已经对显示屏做了设置:

这里设置了显示屏按照像素的方式来显示画面,画面的大小为320x200,

特别注意最后一行:MOV DWORD [VRAM],0x000a0000;,设置缓冲区的地址为0x000a0000,这意味着,将内存的0x000a 0000出写的值,就是显示屏上每个像素的值。

那我们在c语言中实现这样一个函数就可以了,如下bootpack.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 */	}	for (;;) {		io_hlt();	}}

在第8行,我们将15这个值放在了0xa0000到0xaffff地址里,用的是write_mem8函数实现的。write_mem8函数在第2行有声明,注意到它和io_hlt一样,而io_htl是汇编语言写的,所以,write_mem8函数也是汇编语言写的,在naskfunc.nas里,它的实现是这样的:

; naskfunc; TAB=4[FORMAT "WCOFF"]				; 文件格式设定	[INSTRSET "i486p"]				; 486模式[BITS 32]						; 32模式[FILE "naskfunc.nas"]			;		GLOBAL	_io_hlt,_write_mem8 ; 声明函数[SECTION .text]_io_hlt:	; void io_hlt(void);		HLT		RET_write_mem8:	; void write_mem8(int addr, int data);		MOV		ECX,[ESP+4]		; [ESP+4] 就是int addr, 		MOV		AL,[ESP+8]		; [ESP+8] 就是int data		MOV		[ECX],AL      ; 把data 放在addr处		RET

也就是说:write_mem8函数将 data存放到地址addr出。

那么在HariMain函数中,就实现了把15放到了内存地址0xa0000至0xaffff处。

这就实现了改变显示屏内容的作用,把原来的黑屏,变成了白屏,如下:

既然画成白屏了,不如再画个其他的,比如条纹,代码如下:

void io_hlt(void);void write_mem8(int addr, int data);void HariMain(void){	int i; /* 変数宣言。iという変数は、32ビットの整数型 */	for (i = 0xa0000; i <= 0xaffff; i++) {		write_mem8(i, i & 0x0f);	}	for (;;) {		io_hlt();	}}

只有第10行有改动: write_mem8(i, 15)改为 write_mem8(I,i&0x0f);

I&0x0f是只取i的最小的四位,由于i本身是从0xa0000增加到0xaffff的,所以,i*0x0f会是一个周期变化的值,不断的从0变化到f=15,然后又从0变化到f=15。

我们设置为15的时候,画面是白色的,现在是0--15之间变化,那么画面会发生怎样的变化呢?

以上程序运行结果如下:

可以看到,这里是一个黑白相间的条纹。

不过这个还是黑白的,那么能否显示彩色呢?

我们知道显示屏一般都是彩色的,肯定是可以显示彩色的,那要如何设定呢?

这涉及到一个概念,调色板。

调色板是啥?

其实我们现在的程序:15对应白色,0对应黑色,就是一个调色板。这种数字和颜色的对应关系,就是调色板。

所以我们只要改变一下数字和颜色的对应关系就行了。在这个代码中实现:

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;	char *p;	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,	/*  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;	}void set_palette(int start, int end, unsigned char *rgb){	int i, eflags;	eflags = io_load_eflags();	/* 记录运行到此代码处时的中断标志到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);	/* 用 eflags恢复中断标志 */	return;}

set_palette(start=0,end=15,table_rgb=颜色表)

这个函数中的start,end就是往数据缓冲区中写的数字的范围。

我们给0,1,2,3,,,,15,每个数字对应一种颜色,一共16种颜色,写在table_rgb中。通过往端口0x03c9写入颜色的红色分量,绿色分量,蓝色分量就可以完成调色板palette的设置,把这些颜色和0,15对应起来。

代码中io_out8是往指定端口写入8位数字。

这里设定显示屏的调色板,要先把start写到0x03c8,然后把具体的颜色写到0x03c9就行了,这都是VGA显示器规定的标准设定方法。

运行一下就可以看到彩色了,如下:

那么以上并不是全部的代码,因为没有给出io_out8的源码,io_store_eflag的源码,下面给出,其实都在nasmfucn里:

; naskfunc; TAB=4[FORMAT "WCOFF"]				[INSTRSET "i486p"]			[BITS 32]						[FILE "naskfunc.nas"]					GLOBAL	_io_hlt, _io_cli, _io_sti, _io_stihlt		GLOBAL	_io_in8,  _io_in16,  _io_in32		GLOBAL	_io_out8, _io_out16, _io_out32		GLOBAL	_io_load_eflags, _io_store_eflags[SECTION .text]_io_hlt:	; void io_hlt(void);		HLT		RET_io_cli:	; void io_cli(void);		CLI		RET_io_sti:	; void io_sti(void);		STI		RET_io_stihlt:	; void io_stihlt(void);		STI		HLT		RET;端口port的值写入AL,AL是8位寄存器_io_in8:	; int io_in8(int port);		MOV		EDX,[ESP+4]		; port		MOV		EAX,0		IN		AL,DX		RET;端口port的值写入AX,AX是8位寄存器_io_in16:	; int io_in16(int port);		MOV		EDX,[ESP+4]		; port		MOV		EAX,0		IN		AX,DX		RET;端口port的值写入EAX,EAX是32位寄存器_io_in32:	; int io_in32(int port);		MOV		EDX,[ESP+4]		; port		IN		EAX,DX		RET;把8位的data写到端口port_io_out8:	; void io_out8(int port, int data);		MOV		EDX,[ESP+4]		; port		MOV		AL,[ESP+8]		; data		OUT		DX,AL		RET;把16位的data写到端口port_io_out16:	; void io_out16(int port, int data);		MOV		EDX,[ESP+4]		; port		MOV		EAX,[ESP+8]		; data		OUT		DX,AX		RET;把32位的data写到端口port_io_out32:	; void io_out32(int port, int data);		MOV		EDX,[ESP+4]		; port		MOV		EAX,[ESP+8]		; data		OUT		DX,EAX		RET;把标志寄存器的值放到栈里_io_load_eflags:	; int io_load_eflags(void);		PUSHFD		; PUSH EFLAGS		POP		EAX		RET;把 eflags放到栈里,然后再放到标志寄存器里_io_store_eflags:	; void io_store_eflags(int eflags);		MOV		EAX,[ESP+4]		PUSH	EAX		POPFD		; POP EFLAGS 		RET

因为设定调色板,需要用到一些汇编代码,所有就又在nasmfunc中加入了一些相关的函数。

绘制矩形

既然可以改变颜色了,说明我们基本上可以比较自由的操作显示屏显示各种颜色了。

那么能否绘制本文开头的窗口呢?

能,不过要先回值一个长方形。

如何绘制呢?

缓冲区从0xa0000开始,一共320x200=64000个值。

如果要绘制举行,单独设置矩形内的像素为特定颜色就行了。

设定一个坐标系,以窗口的左上角为原点,水平向左为x轴正方向,垂直向下为y轴正方向,那么矩阵的左上角点坐标为(x0,y0),右下角点坐标为(x1,y1),

我们只用对横坐标介于[x1,x2],纵坐标介于[y1,y2]的像素设定为特定的值就行了。

函数boxfill8就是对以上思路的实现:

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;}

vram就是缓冲区的首地址,

xsize是显示屏每行的像素数,

c是像素的值

x0,y0是所要绘制的左上角点的坐标

x1,y1是所要绘制的右下角点的坐标

这个函数就可以绘制一个颜色为c的,左上角在x0,y0,右下角在x1,y1的长方体。

那么我们使用这个函数来绘制几个长方体来看看,代码如下:

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		15void HariMain(void){	char *p; 	init_palette(); 	p = (char *) 0xa0000; 	boxfill8(p, 320, COL8_FF0000,  20,  20, 120, 120);	boxfill8(p, 320, COL8_00FF00,  70,  50, 170, 150);	boxfill8(p, 320, COL8_0000FF, 120,  80, 220, 180);	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;	}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;}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;}

在36,37,38绘制了3个矩形。运行后,启动操作系统后,显示如下:

可以看到,显示了3个不同颜色的矩形。

用矩形绘制窗口

直接上代码:

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		15void 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();	}}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;	}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;}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;}

运行结果如下:

到这里,有点操作系统的样子了,但也只是绘制了一个画面而已。

后面还要完成对鼠标,键盘的相应代码,对内存的管理,以及编写应用程序等功能。

到现在为止,编译完成的映像文件一共1216字节,1.2KB。

下次就显示文字,显示鼠标,以及为操作系统的内存管理做一些准备。

内存管理是操作系统中,重要的内容。我们后期学习C++时,经常要防止内存泄漏。这里的内存管理就是C++中的内存泄漏的基础知识。

总结:

今天,第四天,完成了对显示器的显示内容的控制。我们实现了对显示屏上任一像素的颜色的设定。利用了调色板,以及设定调色板时要关闭中断,往相应端口发送颜色表等。

第五天,我们就显示文字,显示鼠标,再后面控制鼠标,控制键盘,打印文字,保存文件,实现多任务,实现用C语言在操作系统上编写程序等等。

标签: #c语言建立坐标系绘图 #c语言operate函数 #c语言屏幕显示程序 #c语言x0 #图的基本操作c语言