自制操作系统日志——第四天

本文详细记录了自制操作系统中利用C语言和汇编实现内存写入,显示条纹图案,创建调色板以及绘制矩形框的过程。作者通过解析内存地址与屏幕像素的关系,编写了内存写入函数,并介绍了调色板的设置方法,最终展示了如何通过编程在屏幕上绘制彩色矩形框。
摘要由CSDN通过智能技术生成

自制操作系统日志——第四天

今天是该系列的第四天。今天的任务就是利用指针以及汇编,实现本操作系统的画面显示。



一、利用c直接写入内存

从之前的划分内存那块可以知道,我们要想让系统的画面进行显示就需要向VARM所在的内存中写入数据。
⇒ VARM 是320x200,且于内存中是由0xa0000开始的。因此,我们可以认为屏幕的右上角是(0,0)左下角就是(319,199)。这里假设一个内存地址在屏幕上就算一个点,则内存地址的增加就是按着屏幕从左到右,按行递增的顺序进行的。故有以下的内存地址计算方法:

0xa0000+x+y*320

好了,在了解这个以后,我们接下来正式开始进行编写吧!

这里啊,由于c没有直接向内存地址输入的语句(当然你可以用指针写,后面我也会写用指针的方法),这里我们写一个汇编程序形成c的库函数,以此来实现写入指定的内存地址空间,修改naskfunc.nas:

naskfunc.nas:(新增了以下内容)

[INSTRSET "i486p"]  ;告诉汇编编译器,这个是给486使用的,而不是8086 即可以识别到寄存器EAX
        GLOBAL         _write_mem8   
        
[SECTION .text]        
_write_mem8: ; void wirte_mem8(int addr, int data);
         mov ECX,[esp+4]   ;[ESP+4]中存放的是地址,将其读入搭配ecx中
         mov al,[esp+8]    ;[esp+8]中存放的是数据,将其放入al中
         mov [ECX],al
         RET

然后,我们就可以在bootpack.c,新增以下内容:

void write_mem8(int addr, int data);  //向VARM内存显卡地址写入

// 这里如果看过编写的汇编代码的话,那么可能会疑惑,这里传入的参数貌似没有再汇编中使用到?
// 这里,我做出自己的理解:
// 1、函数的参数是通过栈进行传递的,而且是从右往左依次入栈,所以地址才会在栈指针指向的低地址空间!!
// 2、调用函数的前后堆栈要保持一致,即函数返回后,栈指针要恢复到进入函数前
// 3、函数接受的形参都是从栈中取的
// 这里VRAM的显存空间位于0xa0000~0xaffff
void HariMain(void)
{
	int i ; 
for(i=0xa000; i <= 0xaffff ; i++)
{		write_mem8(i, 15);//mov byte [i],15;  15代表这将像素15全部写入,即白色
}
	for(;;)
	{
	     io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}
} 

然后,make run即可:
在这里插入图片描述看!!此时画面变白了!

这里,解释一下上面的函数传参在汇编里的情况:
在这里插入图片描述

二、制作条纹,以及利用c语言制作

为了显得有成就一点,这里我们继续进一步的修改一下,值得页面能显示条纹状:
修改bootpack.c:

for(i=0xa000; i <= 0xaffff ; i++)
	{		write_mem8(i, i & 0x0f);//mov byte [i],15;  15代表这将像素15全部写入,即白色
	   

这里,让地址值全部与15进行与操作。解释以下为什么这样子就能显示条纹了:这是因为0x0f的二进制值为 0000 1111 而由于与的特性因此,只要是超过四位以上的将全部都变为了0,只有低四位会随着地址的增加呈现周期性的变化。而一行有320 ,320/4=80 因此,一行会重复80次,当到达下一行时变化也是和第一行一样的,因此就会出现条纹状。

解释完了后,让我们make run以下:
在这里插入图片描述
好的,接下来我们使用c语言的指针完成上述的操作:

void HariMain(void)
{
	int i ;
	char *p; 
for(i=0xa000; i <= 0xaffff ; i++)
{		
       p = (char *) i; //由于c中将数据和内存地址进行了区分,因此在赋值的过程中将数据进行类型转化是最好的!!
     *p = i & 0x0f;
      //代替了write_mem8(i, i & 0x0f);
}
	for(;;)
	{
	     io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}
} 

这里,我们可以以汇编角度来解释一下指针,即p代表着一个地址,而*p则在汇编中就相当于[p]的意思,即代表着一个内存单元的地址。因此,p可以说是内存地址的变量,而char p这个声明,其实就只有一个变量,即p变量。因为放到汇编中,至始至终都只有一个p可变而已,毕竟p = [p] 。

除此之外,再来说明一下声明的含义:

char *P; //用于BYTE类的地址,指向的内存单元数据为1字节,al
short  *P; //用于word类的地址,指向的内存单元数据为2字节,ax
int  *P; //用于用于Dword类的地址,指向的内存单元数据为4字节,eax
//p本身因为代表的是地址,因此p本身是4字节的

上述的指针还可以改成:

void HariMain(void)
{
	int i ;
	char *p; 
	p = (char *) 0xa0000;
for(i=0; i <= 0xffff ; i++)
{		
       p[i] = i $ 0x0f;
}
	for(;;)
	{
	     io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}
}

这里的p[i]就与*(p+i)是一个意思,即p的地址+i的地址后,再指向该地址的内存空间!! 由于加法是可互换的因此其实i[p]也是可以的!!!!

make run一下后,也会出现上述的彩色条纹!

三、制作调色板

我们使用的是VGA模式的320x200x8,至于320x200就是代表着屏幕的矩形框的像素之类的,这里不再赘述了(上面有)。然后,我们来解释一下8: 这里的8其实是指8位的颜色模式,即我们采用色号为8位2进制的数来表示整个系统!

这里熟悉色彩的人可能就会疑惑,因为我们的电脑一般采用RGB(红绿蓝)的方式,即用6位16进制数表示 #ffffff ,也就是24位2进制数,那么我们这个8是不是有点太少了?

再前面我们调用bios时,那里有介绍说我们这个是调色板模式。也就是说,我们可以利用这8位二进制数随意的进行指定RGB的色彩,即我们可以设定15号颜色对应的是#ffffff ,26号对应的是#8c8c8c 号颜色。

再了解这些后,我们去修改一下我们的bootpack.c文件,增加调色板模式(以下代码可能含有未知的函数声明,暂时不用管,讲到时候再说):

void io_hlt(void); //暂停
void io_cli(void); //设置IF标志寄存器为0
void io_out8( int port, int data ); //向端口写入数据,按rgb格式写入
int io_load_eflags(void);
void io_store_eflags(int eflags ) ;
void write_mem8(int addr, int data);  //向VARM内存显卡地址写入

void init_palette(void); //设定调色板
void set_palette(int start , int end, unsigned char *rgb); //向bios提供的调色板写入


void HariMain(void)
{
	int i ; //i是一个32位的整数
	char *p ;//在汇编中变量p是一个地址本身是四字节的,但是其指向的内存单元的byte类型(因为char就是1字节)

    init_palette();//设定调色板

    p = (char *) 0xa0000 ;
    for(i=0; i<= 0xffff; i++)
    {
    	p[i] = i & 0x0f ;
    }

	for(;;)
	{
	     io_hlt();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有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); //进行设置
}

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 = rgb+3;
	}
	io_store_eflags(eflags);   //恢复中断许可标志
}

这里,解释一部分:static 这一句话,对应于汇编其实就是db指令(占1字节)。而如果不用static的话,直接char a[3]这种,就相当于赋值了,在汇编中可以看出resb 3 (占3字节) 。我们上面定义了48个数据,如果用赋值的话大概是150字节左右了,而db则至于48 字节!

除此之外,这里面使用的io_in 于io_out 其实调用的是汇编里的in指令与out指令,方便向连接的设备输送信息的。

然后还有一个0x03c8、0x03c9这是什么呢? 这其实是,VGA显示设备的设备号码,其具体功能如下:

调色板设备的饭问步骤:
1、首先在一连串的访问中屏蔽中断,(使用CLI将IF设置为0)禁止中断请求;
2、将想要设定调色板的号码写入0x03c8 , 然后接着按R、G、B的顺序写入0x03c9,如果想要继续设定下一个调色板,直接继续写入即可。
3、想要读取当前调色板状态,则先将调色板号码写入0x03c7 ,然后就会按R、G、B的顺序从0x03c9中读出即可,如果想要继续读下一个调色板,直接继续读;
4、执行sti

至此,上述代码中大部分已经解读完,下面看一下naskfunc.nas:

[FORMAT "WCOFF"]    ;制作的目标文件的模式
[INSTRSET "i486p"]  ;告诉汇编编译器,这个是给486使用的,而不是8086 即可以识别到寄存器EAX
[BITS 32]           ;制作32位的机器语言模式
[FILE "naskfunc.nas"]   ;源文件名信息

;制作具体的函数库
         
         GLOBAL         _write_mem8    ;程序中包含的函数名,一定要以_开头,为了衔接c语言的函数库
         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]          ;目标文件中写了这些之后再写程序

_write_mem8: ; void wirte_mem8(int addr, int data);
         mov ECX,[esp+4]   ;[ESP+4]中存放的是地址,将其读入搭配ecx中
         mov al,[esp+8]    ;[esp+8]中存放的是数据,将其放入al中
         mov [ECX],al
         RET

_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_stihld(void)
         STI
         HLT
         RET

_io_in8: ;int io_in8(int port)
         mov edx,[esp+4]
         mov eax,0
         in  al,dx  ;将dx端口的数据读8字节到al中,下面的同理
         RET

_io_in16: ;int io_in16(int port)
         mov edx,[esp+4]
         mov eax,0
         in  ax,dx
         RET

_io_in32: ;int io_in32(int port)
         mov edx,[esp+4]
         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       ;将al的数据写到dx这个端口
         RET    

_io_out16:  ;void io_out16(int port ,int data)
         mov edx,[esp+4] ;port
         mov ax,[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   ;指push flag寄存器
         pop eax   ;这里解释一下:函数的返回值,在汇编中是由:char型 AL ; int型 AX
         RET

_io_store_eflags: ; void io_store_flags(int flags)
         mov eax,[esp+4]
         push eax
         popfd
         RET

然后,make run:
在这里插入图片描述

三、制作矩形框,以及今天最终成果的框

由于在第一部分已经讲了,VRAM显卡在内存中像素地址是如何对应到屏幕的,因此我们可以直接利用这个公式制作矩形框:
0xa0000+x+y*320

bootpack.c:(新增一些内容,主要是boxfill8这个函数)
//按着下面的init_palette 定义的颜色
#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 io_hlt(void); //暂停
void io_cli(void); //设置IF标志寄存器为0
void io_out8( int port, int data ); //向端口写入数据,按rgb格式写入
int io_load_eflags(void);
void io_store_eflags(int eflags ) ;
void write_mem8(int addr, int data);  //向VARM内存显卡地址写入

void init_palette(void); //设定调色板
void set_palette(int start , int end, unsigned char *rgb);

void HariMain(void)
{
	char *p ;//在汇编中变量p是一个地址本身是四字节的,但是其指向的内存单元的byte类型(因为char就是1字节)

    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();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}

}

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;  //按着屏幕从左往右一行行读,则需要填入的vram = 0xa0000+x+y*320 。因为本身vram大小是320*200
	}
	return;
}

make run:
在这里插入图片描述
很好,最后来来看一下最终成果吧:(就更改以下主函数)


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();
	    //自建的hlt函数,源目标程序在naskfunc.nas中,c语言本身没有hlt这个命
	}

}

make run:

在这里插入图片描述


总结

:以上就是第四天的内容,感觉内容还是很多的,已经能够显示框框了,兴奋!!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值