`
jjchen_lian
  • 浏览: 84287 次
  • 性别: 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平台上面的实现。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics