30天自制操作系统——第4天实验总结

实验日期实验项目
2020.10.22第4天 C语言和画面显示的练习

一、实验主要内容

1、 内容1 用C语言实现内存写入

(1).内容概要

  • 实验内容:用C语言实现内存写入,即向指定地址中写入指定的数据。
  • 实验重点:理解写入的原理和函数参数传入内存地址中的位置。

使用汇编语言编写一个write_mem8函数来实现对指定地址中数据的写入,wirte_mem8函数的参数1是地址,参数2是写入该地址的数据值。调用函数时,会在内存中开辟函数栈空间,函数参数从栈顶指针esp+4的位置开始存储,有如下对应关系:

第一个参数的存放地址ESP+4
第二个参数的存放地址ESP+8
第三个参数的存放地址ESP+12

另外,指定内存地址的地方,可以自由使用寄存器EAX,ECX,EDX,其他寄存器因为在C语言编译生成的机器语言中用于记忆非常重要的值,因此只能使用其值,不能改变其值。

(2).关键代码分析

naskfunc.nas代码分析
[FORMAT "WCOFF"]
[INSTRSET "i486p"]	;告诉nask,程序是给486使用的				
[BITS 32]						
[FILE "naskfunc.nas"]		
		GLOBAL	_io_hlt	,_write_mem8;		
[SECTION .text]	
_write_mem8:  ;void write_mem8(int addr,int data)
		MOV ECX,[ESP+4]   ;[ESP+4]存放的是地址
		MOV AL ,[ESP+8]   ;[ESP+8]存放的是数据
		MOV [ECX],AL
		RET

wirte_mem8函数是使用汇编语言写的,将地址和写入数据存放到对应的寄存器中,再利用相应的间接寻址的方式把数据写入指定地址中去。之和的实验中会使用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++)//0xa0000-0xaffff64k大小的内存中用来设定颜色
	{
		write_mem8(i,15);//将第2个参数的值写入第1个参数表示的地址中
	}
	for(;;)
	{
		io_hlt();
	}
}

这部分代码在for循环中调用write_mem8函数将64k大小的图形缓冲区中每个像素换成15,即白色所对应的像素数值。替换完成后,运行代码,显示屏就会变成白色。

2、 内容2 条纹图案

(1).内容概要

  • 实验内容:绘制条纹图案

为了改变显示在屏幕上的图案,绘制出不同的图案效果。可以使用C语言中的逻辑运算符去改变颜色的色号。图形数据处理方式总结如下:

和1相或(OR)使特定位变1
和0相与(AND)使特定位变0
和1相异或(XOR)使特定位反转

(2).关键代码分析

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,i & 0x0f);//i和0x0f相与,得到16种不同的色号
	}
	for(;;)
	{
		io_hlt();
	}
}

这部分代码主要是用来绘制条纹图案。将i和0x0f相与,将会是i的值从0—f开始循环,对应在画面上的效果就是0-f对应 16种颜色,按照列依次排列形成条纹图案。
代码实现的效果如下:
在这里插入图片描述

3、 内容3 挑战指针和指针的应用

(1).内容概要

  • 实验内容: 了解指针的概念以及用法;学会使用指针将数据写入内存,并能够完成相应图形的绘制。
  • 实验重点:掌握几种使用指针将数据写入内存的方法,并能够熟练地使用指针。

在汇编语言中,可以省略BYTE等的情况:源和目的操作数位数相同;源和目的操作数均是寄存器。当我们使用指针(地址的专用变量)时,可以相应用于BYTE,WORD类地址。对应关系如下:

char *p用于BYTE类地址,1个字节
short *p用于WORD类地址,2个字节
int *p用于DWORD类地址,4个字节

引入指针后,对于MOV ECX,I;MOV BYTE [ECX], (I &0x0f)两句汇编语言可以用C语言替代,声明一个指针变量p,指针变量指针地址ECX,*p表示该地址中存放的值,使用赋值语句即可改变地址p中存放的数值。
下表是对将数据写入内存的几种方式的总结:

方式用法(使用的语句)
汇编语言MOV指令MOV ECX,地址;MOV AL,数据;MOV [ECX],AL
类型转换*((char *) i)=数据,i为写入的位置
指针i[p]=数据;p[i]=数据; *(p+i)=数据;这3种方式中p为写入的首地址,需要提前指定,i为相对于首地址的偏移量p=(char *)i , *p=数据

(2).关键代码分析

bootpack.c代码分析
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
    int i;
	char *p;
	for(i=0xa0000;i<=0xaffff;i++)
	{
		p=(char *)i;//这样写是为了消除警告
		*p=i & 0x0f;
	}
	for(;;)
	{
		io_hlt();
	}
}

这部分代码是使用指针向内存写入数据,实现之前的图形绘制。其中p=(char )i用到了类型转换。类型转换是改变数值类型的命令,一般不必每次都注意类型转换,但是这里的给指针变量赋值时需要先强制类型转换为对应类型,不然会存在警告,因为不是每个数值都可以用来作为内存地址。在for循环中内容也可以相应使用类型转换将数据写入内存的语句替换,如((char *) i)=i & 0x0f,或者使用指针的其他写法,如i[p]=i &0x0f,p[i]=i &0x0f, *(p+i)=i & 0x0f。

4、 内容4 色号设定

(1).内容概要

  • 实验内容: 设定相应的颜色,在8位彩色模式下,初始化调色板。

本次实验中使用320*200的8位颜色模式,色号使用8位(二进制)数表示,但是这种方式可以指定的颜色很少。一般指定颜色是采用#ffffff的方式,使用6位16进制的数,用2位16进制的数表示R,2位16进制的数表示G,2位16进制的数表示B,也就是RGB的色彩表示方法。实验中用到的0-15号码的颜色如下:
在这里插入图片描述
调色板的访问步骤:

	a.设置中断屏蔽
	b.将设定的调色板号码写入0x03c8,将颜色按照RGB的顺序写入0x03c9。当设定多个调
	色板号码时,可以省略调色板号码,直接写入颜色。
	c.如果想要读出调色板的状态,则首先将调色板号码写入0x03c7,在从0x03c9中依次读
	出RGB。
	d.取消中断屏蔽。

汇编指令:IN指令是从设备取得电气信号,OUT指令是向设备发送电信号。IN和OUT指令可以实现CPU和设备之间的端口通信。PUSHFD指令是将标志位按照双字节压入栈,即PUSH EFLAGS,POPFD指令是将标志位按照双字节弹出栈,即POP EFLAGS。

EFLAGS寄存器的介绍:EFLAGS 是一组用来存放进位标志和中断标志等标志的存储器。进位标志可以用JC或者JNC来判断进位标志是否为0.。对于中断标志,需要读入EFLAGS,检查第9位的情况,第9位为0,忽视中断请求,否则则是立即处理中断请求。
在这里插入图片描述

(2).关键代码分析

  • 和之前内容1,2,3部分相同的代码不做说明
  • 汇编代码部分
_io_cli:	; void io_cli(void);//设置中断位为0
		CLI
		RET
_io_sti:	; void io_sti(void);//设置中断位为1
		STI
		RET
  • init_palette代码分析
void init_palette(void)
{
	static unsigned char table_rgb[16 * 3] = {//定义16种颜色的数组
		0x00, 0x00, 0x00,	/*  0:黒 */
		0xff, 0x00, 0x00,	/*  1:亮红 */
		
		0x84, 0x84, 0x84	/* 15:暗灰 */
	};
	set_palette(0, 15, table_rgb);//设置调色板
	return;
	/* C语言中static char 语句只能用于数据,相当于汇编中的DB指令*/
}

这部分代码是初始化调色板,首先定义一个16*3的二维数据,每行表示一种颜色,将0-15号颜色按照RGB的顺序调用set_palette写入调色板

  • set_palette代码分析
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;
}

这部分代码是设定的颜色写入对应的设备号码中。首先使用io_load_sflags()函数记录中断许可标志,接着调用io_cli()函数设置中断屏蔽,将设定的调色板号码写入0x03c8,使用for循环从start到end按照RGB的顺序分别将end-start+1钟颜色写入0x03c9。最后使用io_store_aflags()取消中断屏蔽。

5、 内容5 绘制矩形和系统界面

(1).内容概要

  • 实验内容: 通过像素坐标绘制不同颜色,不同大小,不同位置的矩形。
  • 实验重点:掌握像素坐标和绘制图形之间的关系,即图像的坐标系;学会利用坐标关系画画。

(2).关键代码分析

#define COL8_000000		0

#define COL8_848484		15
采用宏定义不同的色号
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;
}

这部分代码是绘制矩形的代码。输入4个坐标参数,分别表示矩形左上角的坐标和矩形右下角的坐标,从而确定整个矩形。y*xsize+x表示按照列的方式进行访问,vram[y * xsize + x] = c表示给坐标为(x,y)的像素设置值为c。

void HariMain(void)
{
	char *p; 
	init_palette(); /*设定调色板*/
	p = (char *) 0xa0000; /* 颜色设定的首地址*/
	boxfill8(p, 320, COL8_FF0000,  20,  20, 120, 120);//绘制矩形左上角坐标为(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();
	}
}

这部分代码是绘制三个不同颜色矩形的代码。绘制系统界面的代码类型,关键在于去确定矩形的位置。坐标系中y轴正方向水平向下,x轴正方向水平向右。以下是对系统界面的绘制的分析。

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);
//右下角方框

两份代码绘制效果如下:
在这里插入图片描述
在这里插入图片描述

二、遇到的问题及解决方法

1、 描述问题1

  • 问题描述

教材第77页,set_palette函数中rgb[0]/4、rgb[1]/4、rgb[2]/4为什么要除以4?

  • 解决方法

起初原本的想法是除以4之后,是为了提高颜色的灰度。经过试验发现确实如此,当增大除数的时候,颜色显示会变得越来越暗,除以一个数相当于是进行右移操作,颜色的像素值会变小,对应的颜色也会逐渐加深,但是对于除以4的解释还是不够全面。后来通过同学的启示,从图形显示模式入手,查阅资料得到如下原因。

本次实验绘制图形是在3202008的八位彩色图形模式下进行的。通过查阅资料可以知道VGA指定调色板颜色时,一个颜色频道只有6位,而我们使用的24位颜色模式中8位表示R,8位表示G,8位表示B,已经超出了颜色频道的表示位数,所以需要舍弃2位。考虑到颜色偏差不能太大,选择右移操作舍弃低2位,对应的操作就是除以4。

三、程序设计创新点

1、 描述创新点1,关键代码及结果截图

  • 创新点1

运用本次所学的图形显示技术,绘制了一个黄色的小人(以下简称小黄人),通过循环控制坐标的变化,可以使小黄人的双臂摆动,向屏幕前的你打招呼。背景颜色更换为亮灰色。

  • 关键代码

矩形绘制代码和原代码一致
圆的绘制代码
在这里插入图片描述
圆弧的绘制代码和圆的绘制代码基本一致,不过因为是绘制圆弧,需要对x,y两个坐标进行位置的限制,使其绘制出的结果是一个弧形。

主体函数部分
在这里插入图片描述
使用ly和ry控制手上下挥动,m用来控制挥手速度,k用来控制抬手的高度

  • 结果截图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    小黄人挥手的动画效果通过3张图片展示。

四、实验心得体会

  • 本次实验是自制操作系统的第4天,实验主要涉及的内容C语言的画面显示练习。笔者循序渐进从汇编开始实现内存写入,再到后面的指针使用,一步步进阶,设定调色板,熟悉图形的坐标系,最终可以使用C语言来绘制出不同的图形,甚至是动画的效果。第一次感觉自制操作系统有点样子了,可以实现画面显示,动画效果。
  • 实验中涉及的知识点基本上都是之前学习过的,如函数调用的参数地址,返回值存放的寄存器,指针的使用等等,因此实验过程中比较顺利。本次实验的重点就是画面显示,对于一个图形画面,设计的关键就是去给对应位置的像素设置不同的颜色,从而达到画图的效果,但对于一些比较复杂的图像,需要编程的程序代码也会是很长很复杂的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值