`
jjchen_lian
  • 浏览: 86385 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

画面显示

阅读更多

今天尝试将操作系统的开发移到linux平台过来,因为感觉在win下面做的很不爽。参与了米油的一些代码,总算成功了。那么下面开始今天的内容。

1,用C语言实现内存写入

往naskfunc.nas里添加的一个新的函数

 

_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(0x1234,0x56);"语句,动作上相当于"MOV BYTE [0x1234], 0x56"。

 

在C语言中如果用到了write_mem8函数,就会跳转到_write_mem8。此时参数指定的数字就存放在内存里,分别是:

第一个数字的内存地址: [ESP + 4]

第二个数字的内存地址: [ESP + 8]

第三个数字的内存地址: [ESP + 12]

第四个数字的内存地址: [ESP + 16]

这里我们来看看栈的使用,当然我们这里不说栈的一些概念,就是先进先出,我们这里只说一些重点的东西。我们知道函数调用参数都是会被压入栈中的。

第一:这里为什么每次都是以4再进行增长?原因很简单,因为传进来的参数都是整形的,因为我们这里的整形占4个字节,所以按4增长。

第二:为什么我们这里MOV EXC,[ESP+4],这里为什么是4,而不是3,2或者是1,这里就要理解压栈的过程了,以后每次压栈时,SP都要依次减2。很明显,不

同于代码段,代码段在处理器上执行时,是由低地址端向高地址端推进的,而压栈操作则正好相反,是从高地址端向低地址端推进的。下面可以通过这个图来



 

了解它的工作原理。

第三:[ESP + 4]是不是等于EDS*16+(ESP+4)?? 不是,而是等于ESS*16+(ESP+4)

 

那么这段代码要被C调用,还需做一些操作才行,需要创建一个naskfunc.nas才行,内容如下

 

;naskfunc
;TAB = 4
[FORMAT "WCOFF"]  ;制作目标文件的模式
[BITS  32]        ;制作32位模式用的机械语言

;制作目标文件的信息
[FILE "naskfunc.nas"]   ;源文件名信息
	GLOBAL _write_mem8   ;程序中包含的函数名

;以下是实际的函数
[SECTION .text]   ;目标文件中写了这些之后再写程序
_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

 OK,完工,有了上面这个文件,就可以在C中轻松的调用汇编代码了。在后面,你会看到用gcc内嵌gas汇编更加的方便。

 

**如果与C语言联合使用的话,有的寄存器能自由使用,有的寄存器不能自由使用,能自由使用的只有EAX,ECX,EDX这三个,至于其他寄存器,只能使用其值,而不能改变其值。

因为这些寄存器在C语言编译后生成的机器语言中,用于记忆非常重要的值,因此这次我们只用EAX和ECX。

 

2,条纹图案

for (i = 0xa0000; i <= 0xaffff; i++) {

write_mem8(i, i & 0x0f);

}

这里为什么能形成条纹图案呢?

其实很简单, 0xa0000 & 0x0000f = 0x00; 0xa0001 & 0x0000f = 0x01;...... 0xa000f & 0x0000f = 0x0f,后面重新循环,又接着从0x00开始到0x0f结束,这样就每隔

16个像素,色号就反复一次。这样就出现了条纹图案。

 

3,挑战指针:

(1)如果我们将write_mem8(i, i & 0x0f)改成 *i = i & 0x0f;那么make run之后就会出现 invalid type argument of 'unary *'错误

我们可以将*i = i & 0x0f用汇编的形式来代替,MOV [i], (i & 0x0f),用我们以前的知识来判断MOV [0x1234], 0x56是否正确,当然这是错误的,为什么呢?这是因为指定

内存事,不知道到底是BYTE,还是WORD,还是DWORD,只是在另一方也是寄存器的时候才能省略,其他情况都不能省略。那么对于MOV [i], (i & 0x0f)来说,同样不知到[i]是BYTE,还是WORD,还是DWORD。这也是这段不用指定BYTE的原因

_write_mem8: ;void write_mem8(int addr,int data);

MOVECX, [ESP+4]  ;[ESP + 4]中存放的是地址,将其读入ECX

MOVAL,[ESP+8]    ;[ESP + 8]中存放的是数据,将其读入AL

MOV[ECX],AL

RET

(2)

char *p /*用于BYTE类地址*/

short *p /*用于WORD类地址*/

int *p /*用于DWORD类地址*/

char i 是类似AL的1字节变量,short i 是类似AX的2字节变量,int i是类似EAX的4字节变量

而不管是char *p, short *p, int *p,变量p都是4个字节,这是因为p是用于记录地址的变量,在汇编语言中,地址就像ECX一样,用4个字节的寄存器来指定,所以也是4个字节。

 

4,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		15

void 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;
}
我们只重点来看看set_palette函数,首先我们先来分析
void set_palette(int start, int end, unsigned char *rgb)
{
	int i;					
	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;
	}	
	return;
}

 我们只重点来看看set_palette函数,首先我们先来分析

void set_palette(int start, int end, unsigned char *rgb)
{
	int i;					
	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;
	}	
	return;
}

 我们前面说过,CPU的管脚与内存相连,如果仅仅是于内存相连,CPU就只能完成计算和存储的功能,但实际上,CPU还要对键盘的输入有响应,要通过网卡从网络取得信息,通过

声卡发送音乐数据,向软盘写入信息,这些都是设备,他们当然要连接到CPU上。

既然CPU与设备相连,那么就有向这些设备发送电信号,或者从这些设备取得信息的指令,向设备发送电信号的是OUT指令,从设备取得电气信号的是IN指令,正如位了区别不同的

内存要使用内存地址一样,在OUT指令和IN指令中,为了区别不同的设备,也要使用设备号码,设备号码在英文中称为port(端口)。port原意是“港口”,这里形象地将CPU与各个设备交换CPU与各个设备交换电信号的行为比作了船舶的出港和进港。

所以,我们执行OUT指令时,出港信号就要挥泪告别CPU了。在C语言中,没有于IN或OUT指令相当的语句,所以我们只好拿汇编语言来做了,这时候,汇编就显示出它的优势了。

调色版的访问步骤:

1,首先在一连串的访问中屏蔽中断(比如CLI)。

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

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

的设定,按RGB的顺序读出。

4,如果最初执行CLI,那么最后要执行STI

 

我们再来看这段代码剩余的部分:

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags(); /*记录中断许可标志的值*/	
	io_cli(); 		 /*将许可标志设置为0,禁止中断*/			
	io_out8(0x03c8, start);
	已经说明的部分
	io_store_eflags(eflags);	/*恢复许可标志的值*/
	return;
}

 首先是CLI和STI,所谓CLI,就是将中断标志设置位0的指令,STI是要将这个中断标志设置为1的指令。而标志,是指像以前曾出现过的进位标志一样的各种标志,也就是说CPU中有多种多样的标志。更改中断标志油什么好处呢?正如其名所示,它与CPU的中断处理有关系,当CPU遇到中断请求时,是立即处理中断请求(中断标志为1),还是忽略中断请求(中断标志为0),就由这个中断标志位来设定。

来了,剩下的我们来看看C调用汇编的代码

; 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

_io_in8:	; int io_in8(int port);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,0
		IN		AL,DX
		RET

_io_in16:	; int io_in16(int port);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,0
		IN		AX,DX
		RET

_io_in32:	; int io_in32(int port);
		MOV		EDX,[ESP+4]		; port
		IN		EAX,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_out16:	; void io_out16(int port, int data);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,[ESP+8]		; data
		OUT		DX,AX
		RET

_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		
		POP		EAX
		RET

_io_store_eflags:	; void io_store_eflags(int eflags);
		MOV		EAX,[ESP+4]
		PUSH	EAX
		POPFD		
		RET

 

我们重点来看看PUSHFD和POPFD指令:
因为汇编中没有MOV EAX,EFLAGS的指令,所以只能通过PUSHFD和POPFD来间接的实现这个功能
PUSHFD是"push flags double-word"的缩写,意思是将标志位的值按双字长压入栈。其实它所做的无非就是"PUSH EFLAGS".POPFD是"pop flags double-word"的缩写,意思是
案双字节长将标志位从栈弹出,它所做的就是"POP EFLAGS"。
也就是说,"PUSHFD POP EAX",是指首先将EFLAGS压入栈,再将弹出的值代入EAX。所以说它代替了"MOV EAX,EFLAGS"。另一方面,PUSH EAX POPFD正好与此相反,它相当于"MOV EFLAGS,EAX"。

最后一个就是绘制矩形的公式:像素坐标(x,y)对应的VRAM地址公式:0xa0000+x+y*320。后面再来看看linux平台上面的实现。

 

 

分享到:
评论

相关推荐

    C#,海康威视摄像头,多路视频监控画面显示,窗体显示切换

    总的来说,实现"C#,海康威视摄像头,多路视频监控画面显示,窗体显示切换"的功能,需要掌握C#窗体应用开发、事件处理、海康威视SDK的使用,以及可能的性能优化和额外功能扩展。通过以上讨论,我们可以看到这个项目...

    vc实现的一个视频多画面显示例子

    在本文中,我们将深入探讨如何使用Visual C++(简称VC)来实现一个视频多画面显示的应用程序。这个项目的核心目标是创建一个窗口,该窗口能够将视频源分割成多个部分,以便同时显示多个视频流。这在监控系统、视频...

    LCD画面显示程序,有字库,有图形

    LCD画面显示程序是嵌入式系统中常见的技术应用,它涉及到硬件接口设计、软件编程以及图形用户界面(GUI)的构建。在这个程序中,"字库"和"图形"是两个核心概念,它们使得LCD能够展示丰富的信息和视觉效果。 首先,...

    LCD画面显示程序,有字库,有图形的文件

    LCD画面显示程序是一种在液晶显示屏(LCD)上呈现文本、图像和图形的应用程序。这个程序包含字库和图形文件,使得它可以高效地处理各种显示需求。以下是对这些关键概念的详细解释: 1. **LCD(液晶显示屏)**:LCD...

    Win10系统玩《星际争霸1》时画面显示不全怎么办.docx

    Win10 系统玩《星际争霸 1》时画面显示不全的解决方法 Win10 系统玩《星际争霸 1》时画面显示不全的问题是一个非常常见的问题,该问题的出现可能是由于《星际争霸 1》游戏的兼容性问题所引起的。在 Win10 系统中,...

    电信设备-电视信源画面显示方法及装置.zip

    标题中的“电信设备-电视信源画面显示方法及装置”主要涉及到的是通信技术和电视信号处理技术,这在现代家庭娱乐系统和智能电视中是至关重要的。这个主题涵盖了多个相关知识点,包括: 1. **电信设备**:电信设备是...

    行业分类-设备装置-一种用于嵌入式媒体播放器多画面显示和控制的方法和系统及应用.zip

    本文件"一种用于嵌入式媒体播放器多画面显示和控制的方法和系统及应用"主要探讨了如何在嵌入式环境中实现高效的多画面显示与控制功能,这对于监控系统、智能电视、车载娱乐系统等场景尤为重要。 一、多画面显示技术...

    Win10玩星际争霸画面显示不全怎么办.docx

    Win10玩星际争霸画面显示不全解决方法 在 Windows 10 正式版中,许多用户报告了《星际争霸 1》等经典老游戏的显示不正常问题,具体表现为画面显示不完全。其实,这个问题可以通过调整游戏文件的兼容性选项来解决。 ...

    GP时钟画面显示时间和日期期限

    在IT行业中,GP时钟画面显示时间和日期期限通常是指一种特定的应用程序或系统功能,用于在用户界面上呈现当前的时间和日期,并可能包括对这些时间信息的特定限制或管理。这个程序可能是为了满足某些特定业务需求,...

    行业资料-电子功用-具有双触控显示装置的电子装置及其画面显示方法的介绍分析.rar

    标题中的“行业资料-电子功用-具有双触控显示装置的电子装置及其画面显示方法”表明,这份资料主要探讨的是电子设备中双触控显示技术的应用及其在屏幕显示上的创新方法。双触控显示装置是现代电子产品中的一种先进...

    如何调整TIA博途WINCC画面的分辨率?.docx

    在工业自动化领域,西门子的TIA博途( Totally Integrated Automation Portal)是广泛使用的集成工程软件,其中的WINCC(Windows Control Center)...通过以上步骤,你可以灵活地改变画面显示效果,提升人机交互体验。

    s5pv210摄像头画面显示

    标题"**s5pv210摄像头画面显示**"意味着我们关注的重点在于如何在S5PV210平台上正确地显示来自摄像头的图像。这涉及到摄像头驱动的配置、图像处理单元(如ISP:Image Signal Processor)的设置,以及显示框架的建立...

    RK 7.1 图库预览视频时,视频画面显示倾斜

    在IT行业中,尤其是在移动设备和智能系统的开发与优化过程中,可能会遇到各种各样的问题,其中“RK 7.1 图库预览视频时,视频画面显示倾斜”是一个典型的图像显示故障。这个问题涉及到图像处理、视频播放以及用户界面...

    登陆画面显示管理员用户帐号

    启用登陆画面显示管理员用户帐号,在XP系统启动后显示管理员Administartor用户名称.

    关于电子功用-便携式电子装置及其画面显示方向的切换方法的说明分析.rar

    本话题将深入探讨便携式电子装置的画面显示方向切换方法,以提升用户使用体验。 在便携式电子装置上,画面显示方向的切换通常是通过设备内置的传感器和操作系统共同实现的。这些传感器包括陀螺仪、加速度计和磁力计...

    qt5_V4L2_Camera 实现摄像头实时画面显示和拍照功能

    qt5_V4L2_Camera 实现摄像头实时画面显示和拍照功能。 ubuntu14.04系统下,qt5.7用V4L2读取摄像头/dev/video*,实现摄像头实时画面显示和拍照功能,照片保存在debug(或其他创建的构建目录中)。 qt5,V4L2

    swing入门样例,配置文件读取,画面显示

    总的来说,这个"swing入门样例,配置文件读取,画面显示"涵盖了Swing GUI开发的基础和进阶内容,对于初学者来说是一份很好的学习资源。通过分析和理解这个样例,你可以学习到如何构建复杂的桌面应用程序,以及如何...

    网络游戏-游戏画面显示方法及装置、存储介质、终端.zip

    标题中的“网络游戏-游戏画面显示方法及装置、存储介质、终端”揭示了这个压缩包包含的是关于网络游戏技术中,特别是游戏画面显示、相关设备、存储方案以及终端应用的专业资料。这个主题涵盖了多个IT领域的知识,...

    西门子画面显示

    西门子画面显示是工业自动化领域中一个关键的组成部分,主要涉及的是西门子PLC(可编程逻辑控制器)与HMI(人机界面)之间的交互。在这一主题下,我们将深入探讨西门子的画面组态、基本I/O配置以及程序与画面之间的...

Global site tag (gtag.js) - Google Analytics