30天自制作業系統——第4天實驗總結

Nuclesome0發表於2020-11-28
實驗日期實驗專案
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語言來繪製出不同的圖形,甚至是動畫的效果。第一次感覺自制作業系統有點樣子了,可以實現畫面顯示,動畫效果。
  • 實驗中涉及的知識點基本上都是之前學習過的,如函式呼叫的引數地址,返回值存放的暫存器,指標的使用等等,因此實驗過程中比較順利。本次實驗的重點就是畫面顯示,對於一個圖形畫面,設計的關鍵就是去給對應位置的畫素設定不同的顏色,從而達到畫圖的效果,但對於一些比較複雜的影像,需要程式設計的程式程式碼也會是很長很複雜的。

相關文章