30天自制操作系统第4天.docx
- 文档编号:3853434
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:18
- 大小:427.80KB
30天自制操作系统第4天.docx
《30天自制操作系统第4天.docx》由会员分享,可在线阅读,更多相关《30天自制操作系统第4天.docx(18页珍藏版)》请在冰豆网上搜索。
30天自制操作系统第4天
操作系统实验日志
学号
20160810520
姓名
甘昆禄
专业年级班级
智能1601
实验日期
2018.10.17
实验项目
第4天:
C语言与画面显示练习
一、实验主要内容
1、让C语言读写内存
上次实验隐约知道画面显示与vram有关,但具体的对应关系未知。
这次我们用C语言来改写vram看看。
作者首先用了汇编语言辅助函数,用了简单的方法,便于我们理解。
Naskfunc.nas中增加了一个函数可供C语言调用的函数_write_mem8,用于实现直接写入指定内存地址的语句。
如果C语言中write_mem8(0x1234,0x56);语句,则动作上相当于汇编的MOVBYTE[0x1234],0x56。
第一个数字在内存里的存放地址[ESP+4],下一个数字的存放就依次累加4。
这里两个参数,一个地址,一个数据。
汇编与C语言结合时能自由使用的寄存区只有EAX,ECX,EDX3个,其他寄存区用于记忆非常重要的值,只能读不能写。
这段代码中还增加了INSTRSET指令,是用来告诉nask这个程序是给486使用的,不然会被默认解释成8086机器使用(偶尔使用)的标签(label)或者常数。
C语言代码:
voidio_hlt(void);
voidwrite_mem8(intaddr,intdata);
voidHariMain(void)
{
inti;
for(inti=0xa0000;i<=0xaffff;i++)
{
write_mem8(i,15);/*将数据15写入地址i*/
}
for(;;){
io_hlt();
}
}
2、绘制条纹
只需要在bootpack.c中修改写入值”15”为”i&0x0f”:
write_mem8(i,i&0x0f);
对图形来说,0和1并不是作为数字来使用,重点是0和1的排列方式。
对于0和1的互相变化,有位运算”或”(OR)运算、”与”(AND)运算和”异或”(XOR)运算。
这些我们在数字逻辑和离散数学里都学过。
这样,写入的值就高四位变为0,其余不变。
就这样,每隔16个像素,色号就反复一次。
3、直接用C语言写内存
用指针写。
指针符号是”*”,*p中的p是地址,而*p是p指向地址的内容。
使用*i=i*0x0f可直接将i*0x0f写入i指向的内存地址中。
*i=i*0x0f对应汇编的MOV[i],(i*0x0f),但如果直接这样写就不清楚[i]到底是BYTE还是WORD还是DWORD。
由于MOV指令的两个对象必须是相同字节长度,即同类型(BYTE/WORD/DWORD),除非另一方是寄存器才可以省略。
同理,在使用指针时需要事先声明它的类型,即指针所指向内容的类型。
chari是类似AL的1字节变量,shorti是类似于AX的2字节变量,inti是类似于EAX的4字节变量。
charp;/用于BYTE类地址*/
shortp;/用于WORD类地址*/
intp;/用于DWORD类地址*/
以上指针中的p都是4字节,因为p是用于记录地址的变量。
在汇编语言中,地址也像ECX一样,用4字节的寄存器来指定,所以也是4字节。
p=i;/带入地址/
p=i&0x0f;/这可以替代write_mem8(i,i&0x0f)*/
在执行makerun之后出现了“warning:
assignmentmakespointerfromintegerwithoutacast”这句话。
在C语言中,普通数值和表示内存地址的数值被认为是两种不同的东西。
如果将普通整数值赋给内存地址变量就会有警告,可以在赋值的时候使用强制类型转换:
p=(char*)i;/*注意i的类型要和p类型一样*/
指针应用
p=(char*)0xa0000;/*给地址变量赋值*/
for(inti=0;i<=0xffff;++i)
{
*(p+i)=i&0x0f;
}
在声明p的时候给它赋值为写入内存的起始地址,之后i作为地址增量,由p+i来指定写入内存的地址。
C语言中,*(p+i)还可以改写成p[i]这种形式:
p=(char*)0xa0000;/*给地址变量赋值*/
for(inti=0;i<=0xffff;++i)
{
p[i]=i&0x0f;
}
p[i]与*(p+i)意思相同,这两者的差距只有前者4个字符,后者6个字符。
但是p[i]并不能说是数组,只是一个看起来像是数列的使用了地址变量的省略写法而已。
加法运算可以交换顺序,于是(p+i)和(i+p),p[i]和i[p],a[2]和2[a]都是一个意思,这更能说明它们与数组没有关系。
4、调色板
这次我们使用的是320*200的8位颜色模式,只有256种颜色,而计算机表示表示颜色时,都是用#ffffff一类的数,就是RGB表示法,可以表示256*256*256种颜色。
那么我们现在8位数怎么表示颜色呢,其实我们这次只用到了16种。
给每种颜色编上号码,像这样就可以使用了。
再根据作者讲解修改完bootpack.c中的代码后,作者以汇编的角度解说table_rgb的声明部分。
RESB指令是“reservebyte”的略写预约字节,如果想要从当前位置向后空出3个字节来,并且填0,就可以用
RESB3
在RESB3前面加上地址就变成了:
a:
RESB3
与C语言中的chara[3]一个意思。
但是汇编中RESB的内容能够保证是0,但是C语言不能保证,因此需要在这个声明后加上“={…}”,还可以写上数据的初始值。
如chara[3]={1,2,3};
即
Chara[3];
a[0]=1;
a[1]=2;
a[2]=3;
a是表示最初地址的数字,也就是说它被认为是指针。
像上面两种声明方式,汇编时都是要用到赋值语句的,作者说这样很浪费字节。
而在声明前加上static就可以将汇编的RESB指令代替成DB指令,这样它在内存中的存储位置就变了,并且未初始化的全局静态变量会被程序自动初始化为0。
而且在程序运行之前,static变量就会被初始化或者赋值。
Io_out8函数,
CPU如果只与内存相连,则只能完成计算和存储的功能。
但CPU还要对键盘输入有响应,要通过网卡从网络取得信息,等等。
这些设备会和CPU胡同电信号,为了区别这些设备,要使用设备号码(port)。
向设备发送电信号的是OUT指令;从设备取得电信号的是IN指令。
但在C语言中没有与IN和OUT相当的语句,所以需要用汇编语言来做。
而代码中的0x03c8、0x03c9就是设备号。
最后代码如下:
voidset_palette(intstatic,intend,unsignedchar*rgb)
{
inti,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;
}
在调色板的访问步骤中的CLI指将中断标志置为0的指令,STI是将这个终端标志置为1的指令。
本来上一次日志就想写这个EFLAGS的,这次又遇到了,就记下笔记吧。
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),简称flag。
flag和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义,而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
中断处理结束之后需要恢复中断现场,所以需要记住最开始的中断标志,所以io_load_eflags读取最初的eflags值,io_store_eflags恢复原来的值。
这些都需要用汇编语言来实现。
而CPU中并没有MOVEAX,EFLAGS之类的指令,能够用来读写EFLAGS的只有PUSHFD(pushflagsdouble-word,将标志位的值按双字压入栈)和POPFD(popflagsdouble-word,按双字长将标志位从栈弹出)指令
_io_load_eflags:
;intio_load_eflags(void);
PUSHFD;指PUSHEFLAGS
POPEAX
RET
_io_store_eflags:
;voidio_store_eflags(inteflags);
MOVEAX,[ESP+4]
PUSHEAX
POPFD;指POPEFLAGS
RET
5、绘制矩形
调色板弄好以后就可以画画了。
在当前画面模式中有320x200(=64000)个像素。
假设左上点的坐标是(0,0),右下点的坐标是(319,199),那么像素坐标(x,y)对应的VRAM地址应该这样计算:
0xa0000+x+y*320
最终bootpack.c如下:
/*在下面使用函数前需要先声明函数,相当于告诉C编译器,有一個函数在別的文件里*/
//voidwrite_mem8(intaddr,intdata);
voidio_hlt(void);
voidio_cli(void);
voidio_out8(intport,intdata);
intio_load_eflags(void);
voidio_store_eflags(inteflags);
/*就算写在同一个源文件里,如果想在定义前使用,还是必须事先声明一下*/
voidinit_palette(void);
voidset_palette(intstart,intend,unsignedchar*rgb);
/*是函数声明卻不用{},而用;,这表示的意思是:
函数在別的文件中,你自己找一下吧!
*/
#defineCOL8_0000000
#defineCOL8_FF00001
#defineCOL8_00FF002
#defineCOL8_FFFF003
#defineCOL8_0000FF4
#defineCOL8_FF00FF5
#defineCOL8_00FFFF6
#defineCOL8_FFFFFF7
#defineCOL8_C6C6C68
#defineCOL8_8400009
#defineCOL8_00840010
#defineCOL8_84840011
#defineCOL8_00008412
#defineCOL8_84008413
#defineCOL8_00848414
#defineCOL8_84848415
voidHariMain(void)
{
char*vram;
intxsize,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();/*执行naskfunc里的_io_hlt*/
}
}
voidboxfill8(unsignedchar*vram,intxsize,unsignedcharc,intx0,inty0,intx1,inty1)
{
intx,y;
for(y=y0;y<=y1;y++)
{
for(x=x0;x<=x1;x++)
vram[y*xsize+x]=c;
}
return;
}
voidinit_palette(void)
{
/*table_rgb的声明*/
staticunsignedchartable_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;
/*C语言中的staticchar语句只能用于数据,相当于汇编中的DB指令*/
}
voidset_palette(intstart,intend,unsignedchar*rgb)
{
inti,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;
}
运行结果:
这里关键看这个函数
boxfill8(unsignedchar*vram,intxsize,unsignedcharc,intx0,inty0,intx1,inty1)
即是从坐标(x0,y0)到(x1,y1),循环使其改变颜色为c所指代的颜色。
二、遇到的问题及解决方法
1、关于调色板的色调。
老师说要理解
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;
}
这段代码里为什么要除以4,一开始只是想弄明白到底怎么显示的没有去可以了解显示的效果如何。
后来发现不理解它,只是认为它负责把代表颜色的数据传入端口,至于除以4应该是改变了颜色。
后来做完实验后回过头修改这个代码,截图拿出来,分别是不除,/2,/8,/16,/64,/128的结果,/4的在上面了。
不除
/2
/8
/16
/64
/256
看到上面的变化,我想大概清楚了,这不是亮度改变了吗。
于是我想到了关于颜色的一些知识,以前见到过的饱和度、色调啥的,估计就是这些,就到网上去查了。
果然:
电脑屏幕上的所有颜色,都由这红色绿色蓝色三种色光按照不同的比例混合而成的。
一组红色绿色蓝色就是一个最小的显示单位。
屏幕上的任何一个颜色都可以由一组RGB值来记录和表达。
因此这红色绿色蓝色又称为三原色光,用英文表示就是R(red)、G(green)、B(blue)。
在电脑中,RGB的所谓“多少”就是指亮度,并使用整数来表示。
通常情况下,RGB各有256级亮度,用数字表示为从0、1、2...直到255。
注意虽然数字最高是255,但0也是数值之一,因此共256级。
很明显数值每三个确定一个颜色,分别代表红绿蓝的分配比例,分配的越多,代表的颜色就越深。
这样就似乎明白作者为什么说只要16种颜色了。
确实,16*16=256,我们只要修改这个/号除的数,就可以得到不同的颜色。
就不进行除法操作那个来说,就是调色板的原色,而进行了除法操作的都是相应色调降低了的:
色调是各种图像色彩模式下原色的明暗程度,级别范围从0到255,共256级色调。
例如对灰度图像,当色调级别为255时,就是白色,当级别为0时,就是黑色,中间是各种程度不同的灰色。
在RGB模式中,色调代表红、绿、蓝三种原色的明暗程度,对绿色就有淡绿、浅绿、深绿等不同的色调。
色调是指色彩外观的基本倾向。
在明度、纯度、色相这三个要素中,某种因素起主导作有用,可以称之为某种色调。
实验的结果似乎是证明的原色的明暗程度的改变。
下面这些,以后如果要变颜色可能会用:
对比度:
对比度指不同颜色之间的差别。
对比度越大,不同颜色之间的反差越大,即所谓黑白分明,对比度过大,图像就会显得很刺眼。
对比度越小,不同颜色之间的反差就越小。
亮度:
亮度指照射在景物或图像上光线的明暗程度。
图像亮度增加时,就会显得耀眼或刺眼,亮度越小时,图像就会显得灰暗。
色调:
色调是各种图像色彩模式下原色的明暗程度,级别范围从0到255,共256级色调。
例如对灰度图像,当色调级别为255时,就是白色,当级别为0时,就是黑色,中间是各种程度不同的灰色。
在RGB模式中,色调代表红、绿、蓝三种原色的明暗程度,对绿色就有淡绿、浅绿、深绿等不同的色调。
色调是指色彩外观的基本倾向。
在明度、纯度、色相这三个要素中,某种因素起主导作有用,可以称之为某种色调
色相:
色相就是颜色,调整色相就是调整景物的颜色,例如,彩虹由红、橙、黄、绿、青、蓝、紫七色组成,那么它就有七种色相。
顾名思义即各类色彩的相貌称谓,如大红、普蓝、柠檬黄等。
色相是色彩的首要特征,是区别各种不同色彩的最准确的标准。
事实上任何黑白灰以外的颜色都有色相的属性,而色相也就是由原色、间色和复色来构成的
饱和度:
饱和度是指图像颜色的浓度。
饱和度越高,颜色越饱满,即所谓的青翠欲滴的感觉。
饱和度越低,颜色就会显得越陈旧、惨淡,饱和度为0时,图像就为灰度图像。
可以通过调整电视机的饱和度来进一步理解饱和度的概念。
在图像处理中,常见的颜色模型包括HSB(色相、饱和度、亮度)、RGB(红色、绿色、蓝色)、CMYK(青色、品红、黄色、黑色)和CIEL*a*b*等,因此,相应的颜色模式也就有RGB、CMYK、Lab等。
在HSB颜色模型中,色相、饱和度、亮度是对图像属性的基本描述。
色相或者色调(Hue)是从物体反射或透过物体传播的颜色。
在0°到360°的标准色轮上,按位置度量色相。
通常情况下,色相由颜色名称标识,如红色、橙色或绿色。
亮度(Brightness或者intensity)是颜色的相对明暗程度,通常使用从0%(黑色)至100%(白色)的百分比来度量
图像的色调通常是指图像的整体明暗度,例如,如果图像亮部像素较多的话,则图像整体上看起来较为明快。
反之,如果图像中暗部像素较多的话,则图像整体上看起来较为昏暗。
对于彩色图像而言,图像具有多个色调。
通过调整不同颜色通道的色调,可对图像进行细微的调整。
在图像处理的各种颜色模型中,HSB模型以人类对颜色的感觉为基础,描述了颜色的3种基本特性。
三、程序设计创新点
1、创新绘制的图案——动态等待条
程序运行效果如下:
解释一下,就是,这个短蓝条会在这个灰条里左右移动,做一个等待的效果。
关键代码如下:
这里先是将全部像素变为白色(vram[i2]=7;)
然后制作长灰条和短蓝条的初始状态;
再然后到while
(1)死循环,这里是动态的设置了,原理就是让CPU不断执行空指令达到延时的效果,每一次延时让短蓝条前方一个坐标变蓝,后边变黑。
到达顶点就反过来。
当然以后这个延时我们可以用CPU运行速度大小代替,这样似乎更合“等待条”的情理。
然后死循环改为键盘等IO指令或网络信号,这样就更棒了。
四、实验心得体会
这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 30 自制 操作系统
![提示](https://static.bdocx.com/images/bang_tan.gif)