日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

《30天自制操作系统》 day8 小结

發(fā)布時間:2023/12/20 windows 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《30天自制操作系统》 day8 小结 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

鼠標(biāo)控制與32位模式切換

在編寫代碼的時候可以對照書中的代碼,以便及時找出自己的錯誤。

  • 鼠標(biāo)控制與32位模式切換
    • 鼠標(biāo)解讀1
    • 整理
    • 鼠標(biāo)解讀2
    • 移動鼠標(biāo)指針
    • 通往32位模式之路

1. 鼠標(biāo)解讀(1)

已經(jīng)能從鼠標(biāo)取得數(shù)據(jù)了,緊接著的問題是要解讀這些數(shù)據(jù),調(diào)查鼠標(biāo)是怎么移動的,然后結(jié)合鼠標(biāo)的動作,讓鼠標(biāo)指針相應(yīng)地動起來。

首先對bootpack.c中的HariMain進行一些修改:

for (;;) {io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {io_stihlt();} else {if (fifo8_status(&keyfifo) != 0) {i = fifo8_get(&keyfifo);io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_phase == 0) /*等待鼠標(biāo)的0xfa的狀態(tài)*/{if (i == 0xfa){mouse_phase = 1;}} else if (mouse_phase == 1) {/*等待鼠標(biāo)的第一字節(jié)*/mouse_dbuf[0] = i;mouse_phase = 2;} else if (mouse_phase == 2) {/* 等待鼠標(biāo)的第二字節(jié) */mouse_dbuf[1] = i;mouse_phase = 3;} else if (mouse_phase == 3) {mouse_dbuf[2] = i;mouse_phase = 1;/*鼠標(biāo)的三個字節(jié)都齊了,顯示出來*/sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}}}

實際上是將HariMain中for循環(huán)部分進行修改,首先把最初讀到的0xfa舍棄掉。之后,每次從鼠標(biāo)那里送過來的數(shù)據(jù)都應(yīng)該是3個字節(jié)一組的,所以每當(dāng)數(shù)據(jù)累積到3個字節(jié),就把他顯示在屏幕上。

變量mouse_phase用來記住接受鼠標(biāo)數(shù)據(jù)的工作進展到了什么階段(phase)。接受到的數(shù)據(jù)放在mouse_dbuf[0~2]內(nèi)。

if (mouse_phase == 0) /*等待鼠標(biāo)的0xfa的狀態(tài)*/{各種處理; } else if (mouse_phase == 1) {/*等待鼠標(biāo)的第一字節(jié)*/各種處理;} else if (mouse_phase == 2) {/* 等待鼠標(biāo)的第二字節(jié) */各種處理;} else if (mouse_phase == 3) {/*鼠標(biāo)的三個字節(jié)都齊了,顯示出來*/各種處理;}

這部分就是對于不同的mouse_phase值,相應(yīng)地做各種不同的處理。顯示結(jié)果如下(鼠標(biāo)移動過):

屏幕上除了括號內(nèi)的還有三字節(jié)數(shù)字,即mouse_dbuf[0],mouse_dbuf[1],mouse_dbuf[2]里的數(shù)據(jù)。”08”部分0會在0~3的范圍內(nèi)變化,”8”只有在點擊鼠標(biāo)時才會有變化,值在8~F之間。第二個字節(jié)與鼠標(biāo)的左右移動有關(guān),第三個字節(jié)與鼠標(biāo)的上下移動有關(guān)。

2. 整理

在HariMain函數(shù)中出現(xiàn)的unsigned char mouse_dbuf[3], mouse_phase;聲明可以放到函數(shù)前的結(jié)構(gòu)體里:

struct MOUSE_DEC {unsigned char buf[3], phase; };

并在這句話修改為:

struct MOUSE_DEC mdec;

創(chuàng)建的這個結(jié)構(gòu)體MOUSE_DEC,DEC是decode的縮寫,用這個結(jié)構(gòu)日把鼠標(biāo)所需要的變量都?xì)w總到一塊兒。
然后將鼠標(biāo)的解讀從函數(shù)HariMain的接受信息處理中剝離出來,放到了mouse_decode函數(shù)。

3. 鼠標(biāo)解讀(2)

struct MOUSE_DEC {unsigned char buf[3], phase;int x, y ,btn; };int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) {if (mdec->phase == 0) {/* 等待鼠標(biāo)的0xfa的階段 */if (dat == 0xfa) {mdec->phase = 1;}return 0;}if (mdec->phase == 1) {/* 等待鼠標(biāo)第一字節(jié)的階段 */if ((dat & 0xc8) == 0x08) {/*如果第一字節(jié)正確*/mdec->buf[0] = dat;mdec->phase = 2;}return 0;}if (mdec->phase == 2) {/* 等待鼠標(biāo)第二字節(jié)的階段 */mdec->buf[1] = dat;mdec->phase = 3;return 0;}if (mdec->phase == 3) {/* 等待鼠標(biāo)第三字節(jié)的階段 */mdec->buf[2] = dat;mdec->phase = 1;mdec->btn = mdec->buf[0] & 0x07;mdec->x = mdec->buf[1];mdec->y = mdec->buf[2];if ((mdec->buf[0] & 0x10) != 0){mdec->x |= 0xffffff00;}if ((mdec->buf[0] & 0x20) != 0){mdec->y |= 0xffffff00;}mdec->y = - mdec->y; /*鼠標(biāo)的y方向與畫面符號相反*/return 1;}return -1; /* 應(yīng)該不可能到這里來 */ }

結(jié)構(gòu)體里增加的幾個變量用于存放解讀結(jié)果,這幾個變量是x、y和btn,分別用于存放移動信息和鼠標(biāo)按鍵狀態(tài)。

if (mdec->phase == 1)這個語句用于判斷第一字節(jié)對移動有反應(yīng)的部分是否在0~3的范圍內(nèi);同時還要判斷第一字節(jié)對點擊有反應(yīng)的部分是否在8~F的范圍內(nèi),如果不在以上數(shù)據(jù)范圍內(nèi)就被舍去。這樣做是因為鼠標(biāo)連線可能會由接觸不良,這樣產(chǎn)生的數(shù)據(jù)就有錯位,不能順利解讀。

if (mdec->phase == 3)語句是解讀處理的核心。鼠標(biāo)鍵的狀態(tài)放在buf[0]的低3位,我們只取出這3位。十六進制的0x07相當(dāng)于二進制的0000 0111,通過與運算(&)取出低3位。

x,y基本上直接使用buf[1]和buf[2],但是需要使用第一字節(jié)中對鼠標(biāo)移動有反應(yīng)的幾位,將x和y的第8位及第8位以后全部都設(shè)成1,或全部都保留為0,就能正確解讀x和y。

解讀最后對y符號進行了取反操作是因為鼠標(biāo)與屏幕的y方向正好相反,為了配合畫面方向,就對y符號進行了取反操作。

鼠標(biāo)數(shù)據(jù)解讀完成之后接下來修改顯示部分:

else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) /*3字節(jié)都湊齊了,所以把它們顯示出來*/{sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);if ((mdec.btn & 0x01) != 0) {s[1] = 'L';}if ((mdec.btn & 0x02) != 0) {s[3] = 'R';}if ((mdec.btn & 0x04) != 0) {s[2] = 'C';}boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}

如果mdec.btn的最低位是1,就把s的第2個字符(第一個字符是s[0])換成‘L’。執(zhí)行結(jié)果如下:

最左邊的字符是因為有按鍵。

4. 移動鼠標(biāo)指針

現(xiàn)在就是讓鼠標(biāo)指針在屏幕上動起來啦,感覺好激動,終于能動了。

else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) /*3字節(jié)都湊齊了,所以把它們顯示出來*/{sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);if ((mdec.btn & 0x01) != 0) {s[1] = 'L';}if ((mdec.btn & 0x02) != 0) {s[3] = 'R';}if ((mdec.btn & 0x04) != 0) {s[2] = 'C';}boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);/*鼠標(biāo)指針的移動*/boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15 * 8 - 1, my + 15); /*隱藏鼠標(biāo)*/mx += mdec.x;my += mdec.y;if (mx < x){mx = 0;}if (my < 0){my = 0;}if (mx > binfo->scrnx - 16){mx = binfo->scrnx - 16;}if (my > binfo->scrny - 16){my = binfo->scrny - 16;}sprintf(s, "(%sd, %3d)", mx, my);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /*隱藏坐標(biāo)*/putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /*顯示坐標(biāo)*/putfonts8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /*描畫鼠標(biāo)*/}

先隱藏到鼠標(biāo)指針,然后根據(jù)取得的鼠標(biāo)數(shù)據(jù)解讀得到的位移量,讓鼠標(biāo)顯示在屏幕上。

mx += mdec.x; my += mdec.y;

是為了防止鼠標(biāo)指針跑到屏幕外進行的調(diào)整。運行看看,鼠標(biāo)能動啦!

5. 通往32位模式之路

這里講解了asmhead.nas中的程序。

; PIC關(guān)閉一切中斷 ; 根據(jù)AT兼容機的規(guī)格,如果要初始化PIC, ; 必須在CLI之前進行,否則有時會掛起, ; 隨后進行PIC的初始化MOV AL,0xffOUT 0x21,ALNOP ; 如果連續(xù)執(zhí)行OUT指令,有些機種會無法正常運行OUT 0xa1,ALCLI ; 禁止CPU級別的中斷

這段程序等同于一下內(nèi)容的C程序。

io_out8(PIC0_IMR, 0xff ); /* 禁止主PIC的全部中斷 */ io_out8(PIC1_IMR, 0xff ); /* 禁止從PIC的全部中斷 */ Io_cli(); /*禁止CPU級別的中斷*/

為了讓CPU能夠訪問1MB以上的內(nèi)存空間,設(shè)定A20GATE

CALL waitkbdoutMOV AL,0xd1OUT 0x64,ALCALL waitkbdoutMOV AL,0xdf ; enable A20OUT 0x60,ALCALL waitkbdout

這里的waitbdout等同于wait_KBC_sendread,等同于C語言中的:

#define KEYCMD_WRITE_OUTPORT 0xd1 #define KBC_OUTPORT_A20G_ENABLE 0xdf/* A20GATE的設(shè)定*/Wait_KBC_sendready();Io_out8(PORT_KEYCMD, KEYCMD_WRITE_OUTPORT);Waite_KBC_sendready();Io_out8(PORT_KEYDATA, KBC_OUTPORT_A20G_ENABLE);Waite_KBC_sendready(); /*這句話是為了等待完成執(zhí)行指令*/

程序的基本結(jié)構(gòu)與init_keyboard完全相同,功能僅僅是往鍵盤控制電路發(fā)送指令。這里發(fā)送的指令,是指令鍵盤控制電路的附屬端口輸出0xdf。這個附屬端口,連接著主板上的很多地方,通過這個端口發(fā)送不同的指令,就可以實現(xiàn)各種各樣的控制功能。

這次輸出0xdf所要完成的功能,是讓A20GATE信號線變成ON的狀態(tài)。這條信號線的作用是使內(nèi)存的1MB以上的部分變成可使用狀態(tài)。Waite_KBC_sendready();是多余的,在此之后,雖然不會往鍵盤送命令,但仍然要等到下一個命令能夠送來為止。這是為了等待A20GATE的處理切實完成。

; 切換到保護模式[INSTRSET "i486p"] ; “想要使用486指令”的敘述LGDT [GDTR0] ; 設(shè)定臨時GDTMOV EAX,CR0AND EAX,0x7fffffff ; 設(shè)bit31為0(為了禁止頒)OR EAX,0x00000001 ; 設(shè)bit0為1(為了切換到保護模式)MOV CR0,EAXJMP pipelineflush pipelineflush:MOV AX,1*8 ; 可讀寫的段 32bitMOV DS,AXMOV ES,AXMOV FS,AXMOV GS,AXMOV SS,AX

INSTRSET指令,是為了能夠使用386以后的LGDT,EAX, CR0等關(guān)鍵字。LGDT指令將暫定的GDT讀進來。然后將CR0這一特殊的32寄存器的值帶入EAX,并將最高位置為0,最低位置為1,再將這個值返回給CR0寄存器。這樣就完成了模式轉(zhuǎn)換,進入到不用頒的保護模式。CR0,也就是control register 0,是一個非常重要的寄存器,只有操作系統(tǒng)才能操作它。

保護模式是指操作系統(tǒng)受到CPU的保護,應(yīng)用程序既不能隨便改變段的設(shè)定,又不能使用操作系統(tǒng)專用的段。

通過帶入CR0而切換到保護模式時,要馬上執(zhí)行JMP指令。因為變成保護模式后,機器語言的解釋要發(fā)生變化。CPU為了加快指令的執(zhí)行速度而使用了管道(piprline)這一機制。也就是說,前一條指令還在執(zhí)行的時候就開始解釋下一條甚至再下一條指令。因為模式變了,就要重新解釋一遍,所以加入了JMP指令。

并且在進入保濕模式以后,段寄存器的意思也變了(不再是乘以16后再加算的意思了),除了CS以外所有段寄存器的值都從0x0000變成了0x0008.CS保持原狀是因為如果CS也變了,會造成混亂,所以只有CS要放到后面再處理。0x0008,相當(dāng)于“gdt+1”的段。

; bootpack的傳送MOV ESI,bootpack ; 傳送源MOV EDI,BOTPAK ; 傳送目的地MOV ECX,512*1024/4CALL memcpy; 磁盤數(shù)據(jù)最終轉(zhuǎn)送到它本來的位置去; 首先從啟動扇區(qū)開始MOV ESI,0x7c00 ; 傳送源MOV EDI,DSKCAC ; 傳送目的地MOV ECX,512/4CALL memcpy; 所有剩下的MOV ESI,DSKCAC0+512 ; 傳送源MOV EDI,DSKCAC+512 ; 傳送目的地MOV ECX,0MOV CL,BYTE [CYLS]IMUL ECX,512*18*2/4 ; 從柱面數(shù)變換為字節(jié)數(shù)/4SUB ECX,512/4 ; 減去IPLCALL memcpy

這部分程序只是在調(diào)用memcpy函數(shù),同樣可以理解成C語言形式(寫法可能不正確,但中心思想是類似的):

memcpy(bootpack, BOTPAK, 512*1024/4); memcpy(0x7c00, DSKCAC, 512/4 ); memcpy(DSKCAC0+512, DSKCAC+512, cyls*512*18*2/4 - 512/4);

函數(shù)mencpy是賦值內(nèi)存的函數(shù),語法如下:
memcpy(轉(zhuǎn)送源地址, 轉(zhuǎn)送目的地址, 轉(zhuǎn)送數(shù)據(jù)的大小);
轉(zhuǎn)送數(shù)據(jù)大小是以雙字節(jié)位單位的,所以數(shù)據(jù)大小用字節(jié)數(shù)除以4來指定。
memcpy(0x7c00, DSKCAC, 512/4 );
DSKCAC是0x00100000,所以上面這句話就是從0x7c00復(fù)制512字節(jié)到0x00100000。這正好是將啟動扇區(qū)復(fù)制到1MB以后的內(nèi)存去。
memcpy(DSKCAC0+512, DSKCAC+512, cyls*512*18*2/4 - 512/4);
它的意思是將始于0x00008200的磁盤內(nèi)容,復(fù)制到0x00100200那里。

上文中“轉(zhuǎn)送數(shù)據(jù)大小”的計算有點復(fù)雜,因為它是以柱面數(shù)來計算的,所以需要減去啟動區(qū)的那一部分長蘇。IMUL是乘法運算,SUB是減法運算。

bootpack是asmhead,nas的最后一個標(biāo)簽,haribote.hrb連接起來而生成的,所以asmhead結(jié)束的地方,緊接著串聯(lián)著bootpack.hrb最前面的部分。

memcpy(bootpack, BOTPAK, 512*1024/4);
→從bootpack的地址開始的512KB內(nèi)容復(fù)制到0x00280000號地址去

; 必須有asmhead來完成的工作,至此全部完畢 ; 以后就變由bootpack來完成; bootpack的啟動MOV EBX,BOTPAKMOV ECX,[EBX+16]ADD ECX,3 ; ECX += 3;SHR ECX,2 ; ECX /= 4;JZ skip ; 沒有要轉(zhuǎn)送的東西時MOV ESI,[EBX+20] ; 轉(zhuǎn)送源ADD ESI,EBXMOV EDI,[EBX+12] ; 轉(zhuǎn)送目的地CALL memcpy skip:MOV ESP,[EBX+12] ; 棧初始值JMP DWORD 2*8:0x0000001b

這里仍然是在做memcpy,它對bootpack.hrb的header進行解析,將執(zhí)行必需的數(shù)據(jù)傳送過去。EBX里帶入的是BOTPAK,所以值如下:

[EBX + 16]……bootpack.hrb之后的第16號地址。值是0x11a8 [EBX + 20]……bootpack.hrb之后的第20號地址。值是0x10c8 [EBX + 12]……bootpack.hrb之后的第12號地址。值是0x00310000

上面這些值是通過二進制編輯器,打開harib05d的bootpack.hrb后確認(rèn)的。這些值因harib的版本不同而有所變化。

SHA指令是向右移位指令,相當(dāng)于“ECX>>=2;”,JZ時條件轉(zhuǎn)移指令,來自英文jump if zero,根據(jù)前一個計算結(jié)果是否為0來決定是否跳轉(zhuǎn)。

最終這個memcpy的作用是將bootpack.hrb第0x10c8字節(jié)開始的0x11a8字節(jié)復(fù)制到0x00310000號地址去。最后將0x310000代入到ESP里,然后用一個特別的JMP指令,將2*8代入到CS里,同時移動到0x1b號。這里的0x1b號地址是指第2個段的0x1b號地址。第2個段的基地址是0x280000,所以實際上是從0x28001b開始執(zhí)行的。也就是bootpack.hrb的0x1b號地址。

然后是這個我們制作的這個“紙娃娃系統(tǒng)”的內(nèi)存分布圖:

0x00000000 - 0x000fffff : 雖然在啟動中會多次使用,但之后就變空。(1MB) 0x00100000 - 0x00267fff : 用于保存軟盤的內(nèi)容。(1440KB) 0x00268000 - 0x0026f7ff : 空(30KB) 0x0026f800 - 0x0026ffff : IDT(64KB) 0x00270000 - 0x0027ffff : GDT(64KB) 0x00280000 - 0x002fffff : bootpack.hrb(512KB) 0x00300000 - 0x003ffff : 棧及其他(1MB) 0x00400000 - : 空 waitkbdout:IN AL,0x64AND AL,0x02IN AL,0x60 ; 空讀(為了清空數(shù)據(jù)接收緩沖區(qū)中的垃圾數(shù)據(jù))JNZ waitkbdout ; AND的結(jié)果如果不是0,就跳到waitkbdoutRET

waitbdout與wait_KBC_sendready相同,但也添加了部分處理,就是從0x60號設(shè)備進行IN的處理。如果控制器里有鍵盤代碼,或者已經(jīng)積累了鼠標(biāo)數(shù)據(jù),就順便把它們讀取出來。JNC與JZ相反,意思是“jump if not zero”

memcpy:MOV EAX,[ESI]ADD ESI,4MOV [EDI],EAXADD EDI,4SUB ECX,1JNZ memcpy ; 減法運算的結(jié)果如果不是0,就跳轉(zhuǎn)到memcpyRET

復(fù)制內(nèi)存的程序。

ALIGNB 16 GDT0: RESB 8 ; NULL selector DW 0xffff,0x0000,0x9200,0x00cf ; 可以讀寫的段(segment)32bit DW 0xffff,0x0000,0x9a28,0x0047 ; 可以執(zhí)行的段(segment)32bit(bootpack用)DW 0 GDTR0: DW 8*3-1 DD GDT0ALIGNB 16 bootpack:

ALIGNB指令的意思是一直添加DBO,直到時機合適的時候為止,即最初的地址能被16整除。如果標(biāo)簽GDT0的地址不是8的整數(shù)倍,想段寄存器復(fù)制的MOV指令就會慢一些。所以插入了ALIGNB指令。

GDT0也是一種特定的GDT,0號是空區(qū)域(null sector),不能夠在那里定義段。1號和2號分別由下式設(shè)定:

Set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); Set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);

GDTR0時LGDT指令,意思是通知GDT0說“有了GDT喲”。在GDT0里,寫入了16位的段上限,和32位的段起始地址。

最初狀態(tài)時,GDT在asmhead.nas里,并不在0x00270000~0x0027ffff的范圍里。IDT連設(shè)定都沒設(shè)定,所以扔處于中斷禁止的狀態(tài)。應(yīng)當(dāng)趁著硬件上積累過多數(shù)據(jù)而產(chǎn)生誤動作之前,盡快開放終端,接收數(shù)據(jù)。因此,在bootpack.c的HariMain里,應(yīng)該在進行調(diào)色板(palette)的初始化以及畫面的準(zhǔn)備之前,先趕緊重新創(chuàng)建GDT和IDT,初始化PIC,并執(zhí)行”io_sti();”。

總結(jié)

以上是生活随笔為你收集整理的《30天自制操作系统》 day8 小结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。