第26天 爲窗口移動提速
2020.5.2
1. 提高窗口移動速度(1)(harib23a)
-
本着優化用戶體驗的宗旨,修改代碼以提高窗口移動速度。
-
導致窗口移動相對緩慢的原因其中之一是:sheet_refreshmap的速度太慢。sheet_refreshmap在sheet_slide中被調用了2次,如果能夠提高sheet_refreshmap的速度,那麼就能顯著提高窗口移動的速度。
-
舊版本的sheet_refreshmap:
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, sid, *map = ctl->map; struct SHEET *sht; if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = h0; h <= ctl->top; h++) { sht = ctl->sheets[h]; sid = sht - ctl->sheets0; buf = sht->buf; bx0 = vx0 - sht->vx0; by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; if (bx0 < 0) { bx0 = 0; } if (by0 < 0) { by0 = 0; } if (bx1 > sht->bxsize) { bx1 = sht->bxsize; } if (by1 > sht->bysize) { by1 = sht->bysize; } for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; if (buf[by * sht->bxsize + bx] != sht->col_inv) { map[vy * ctl->xsize + vx] = sid; } } } } return; }
- 相關變量的解釋:
- (vx0,vy0)~(vx1,vy1)表示的是刷新範圍相對於屏幕的範圍。
- (bx0,by0)~(bx1,by1)表示的是刷新範圍相對於圖層的範圍。
- (sht->vx0,sht->vy0)表示的是圖層相對於屏幕的左上角座標。
- 代碼
if (buf[by * sht->bxsize + bx] != sht->col_inv) {
位於第三層for循環中,要被執行成千上萬次。這句代碼的意思是判斷圖層是否爲透明部分,如果直接去掉,那麼圖層的透明部分就沒有了,鼠標指針就會變成一個方塊。但是,窗口基本上都是矩形的,沒有透明部分,如果僅去掉窗口部分的if語句是沒有影響的。 - 因此,在進入bx和by的for循環之前先判斷這個圖層是否有透明部分,如果有透明部分,按照舊版本的sheet_refreshmap執行,否則就執行一個沒有if語句的兩層循環(bx和by)。
- 相關變量的解釋:
-
修改sheet_refreshmap函數:
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { …… for (h = h0; h <= ctl->top; h++) { …… if (sht->col_inv == -1) { /* 無透明色圖層專用的高速版 */ for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; map[vy * ctl->xsize + vx] = sid; } } } else { /* 有透明色的普通版 */ for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; if (buf[by * sht->bxsize + bx] != sht->col_inv) { map[vy * ctl->xsize + vx] = sid; } } } } } return; }
-
make
後用VMware運行:
- 貌似快了那麼一點兒(笑)。
2. 提高窗口移動速度(2)(harib23b)
-
sheet_refreshmap中的代碼:
map[vy * ctl->xsize + vx] = sid;
- 這句代碼被執行的次數最多。這句代碼的作用是向內存的某個地址寫入sid的值。
- 這句代碼在for循環中,會被反覆執行,而且這個地址的後面以及再後面的地址也要寫入sid值。【符合空間侷限性】
- 在彙編語言中,如果用16位的寄存器代替8位寄存器來執行MOV指令的話,相鄰地址中也會同時寫入數據,而如果是32位寄存器,僅1條指令就可以同時向相鄰的4個地址寫入值。
- 更重要的是,即便試試同時寫入4個字節的值,只要指定地址是4的整數倍,指令的執行速度就和1個字節的MOV是相同的。
- sid是char類型,佔1個字節,顯然,如果根據上述方式修改代碼,速度會提升將近四倍!
-
修改sheet_refreshmap:
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { …… for (h = h0; h <= ctl->top; h++) { …… if (sht->col_inv == -1) { if ((sht->vx0 & 3) == 0 && (bx0 & 3) == 0 && (bx1 & 3) == 0) { /* 無透明色圖層專用的高速版(4字節型) */ bx1 = (bx1 - bx0) / 4; /* MOV次數 */ sid4 = sid | sid << 8 | sid << 16 | sid << 24; for (by = by0; by < by1; by++) { vy = sht->vy0 + by; vx = sht->vx0 + bx0; p = (int *) &map[vy * ctl->xsize + vx]; /*注意這裏的轉化!*/ for (bx = 0; bx < bx1; bx++) { p[bx] = sid4; /*4個字節寫入*/ } } } else { /* 無透明色圖層專用的高速版(1字節型) */ for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; map[vy * ctl->xsize + vx] = sid; } } } } else { /* 有透明色圖層用的普通版 */ …… } } return; }
- 其實有透明色的普通版也可以改成4字節型。但是現在只有鼠標圖層有透明色,且修改的代碼較多,因此暫時不修改。
- 爲了儘可能地讓圖層使用*無透明色圖層專用的高速版(4字節型)*的代碼,因此,需要儘可能地保證:
- 窗口在X方向上的大小爲4的整數倍
- 窗口的x座標也要是4的整數倍
- 目前,所有的窗口的大小都是4的倍數。而窗口的座標,需要使用AND運算來取整,使打開窗口時顯示位置是4的整數倍。
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 5) { sht = sheet_alloc(shtctl); sht->task = task; sht->flags |= 0x10; sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax); make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0); sheet_slide(sht, ((shtctl->xsize - esi) / 2) & ~3, (shtctl->ysize - edi) / 2); /*新生成的圖層的x座標需要調整爲4的倍數*/ sheet_updown(sht, shtctl->top); reg[7] = (int) sht; } else if (edx == 6) { …… }
~3
代表將數字3按位取反,即:~3
=0xfffffffc
。- 注意:當用鼠標拖動窗口時,如果目的座標不是4的倍數,那麼這次修改也就沒有意義了,因此,還要保證目的座標x是4的整數倍。
-
修改bootpack.c中的HariMain:
void HariMain(void) { …… int j, x, y, mmx = -1, mmy = -1, mmx2 = 0; /*變量mmx2保存移動前的sht->vx0*/ …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { /* 按下左鍵 */ if (mmx < 0) { for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { mmx = mx; /* 切換到窗口移動模式 */ mmy = my; mmx2 = sht->vx0; } …… } } } } else { /* 如果處於窗口移動模式 */ x = mx - mmx; /* 計算鼠標指針的移動量 */ y = my - mmy; sheet_slide(sht, (mmx2 + x + 2) & ~3, sht->vy0 + y); mmy = my; /* 更新移動後的座標 */ } } else { mmx = -1; } } } } } }
- 注意,這裏沒有更新mmx的原因:
- 圖層從
最初的狀態
移動到最終的狀態
,實際上的過程是(1)(2),但是結果彷彿是(3),因爲二者的效果是一樣的。mmx記錄的是圖層處於最初的狀態
的鼠標x位置。mmy記錄的是圖層處於最初的狀態
的鼠標y位置。mmx2記錄的是圖層處於最初的狀態
的sht->vx0。 - 當鼠標一直按住左鍵,即圖層處於移動狀態的時候,x,y計算的是實時的鼠標移動距離。sheet_slide函數的第二個參數,使用的是(3)這種方式計算,而第三個參數使用的是(1)(2)這種方式。因爲mmx2不變,x是變化的,因此不需要更新mmx。sht->vy0是實時變化的,也沒有mmy2變量記錄其初始值,因此只能使用(1)(2)這種方式,因而mmy需要實時更新。
- 之所以採用這種方式,是因爲需要保證移動後的圖層的x座標需要是4的倍數。
- 圖層從
(mmx2 + x + 2) & ~3
,+2後AND運算的原因是,如果直接AND的話可能會造成窗口左移,+2相當於做了四捨五入,左右移的機率相同。
- 注意,這裏沒有更新mmx的原因:
-
make
後用VMware運行,感覺快了不少呢(笑)。
3. 提高窗口移動速度(3)(harib23c)
-
sheet_refreshsub是不是也可以一次性寫入4個字節呢?
-
修改sheet_refreshsub:
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1) { int h, bx, by, vx, vy, bx0, by0, bx1, by1, bx2, sid4, i, i1, *p, *q, *r; …… for (h = h0; h <= h1; h++) { …… if ((sht->vx0 & 3) == 0) { /*窗口相對於屏幕的位置x是4的倍數*/ /* 4字節型 */ i = (bx0 + 3) / 4; /* bx0除以4,小數進位 */ i1 = bx1 / 4; /* bx1除以4,小數捨去 */ i1 = i1 - i; sid4 = sid | sid << 8 | sid << 16 | sid << 24; for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1 && (bx & 3) != 0; bx++) { /* 前面被4除多餘的部分逐個字節寫入 */ vx = sht->vx0 + bx; if (map[vy * ctl->xsize + vx] == sid) { vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } vx = sht->vx0 + bx; p = (int *) &map[vy * ctl->xsize + vx]; q = (int *) &vram[vy * ctl->xsize + vx]; r = (int *) &buf[by * sht->bxsize + bx]; for (i = 0; i < i1; i++) { /* 4的倍數部分 */ if (p[i] == sid4) { q[i] = r[i]; /*估計大多數情況都是這種*/ } else { bx2 = bx + i * 4; vx = sht->vx0 + bx2; if (map[vy * ctl->xsize + vx + 0] == sid) { vram[vy * ctl->xsize + vx + 0] = buf[by * sht->bxsize + bx2 + 0]; } if (map[vy * ctl->xsize + vx + 1] == sid) { vram[vy * ctl->xsize + vx + 1] = buf[by * sht->bxsize + bx2 + 1]; } if (map[vy * ctl->xsize + vx + 2] == sid) { vram[vy * ctl->xsize + vx + 2] = buf[by * sht->bxsize + bx2 + 2]; } if (map[vy * ctl->xsize + vx + 3] == sid) { vram[vy * ctl->xsize + vx + 3] = buf[by * sht->bxsize + bx2 + 3]; } } } for (bx += i1 * 4; bx < bx1; bx++) { /* 後面被4除多餘的部分逐字節寫入 */ vx = sht->vx0 + bx; if (map[vy * ctl->xsize + vx] == sid) { vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } } } else { /* 1字節型 */ for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; if (map[vy * ctl->xsize + vx] == sid) { vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } } } } return; }
- 這樣寫的話,當窗口顯示位置是4的倍數時速度就會很快。重新繪製畫面時,重繪範圍不一定是4的整數倍,因此,當前面有餘數時,將餘數部分逐個字節處理,同理後面有餘數一樣。不過,即使窗口本身沒有透明色,當它和別的窗口或者鼠標重疊時,此時不能往相鄰的內存地址寫入同一個sid,因此if語句不能省略。
-
make
後用VMware運行:彷彿又快了一點兒呢(笑)。
4. 提高窗口的移動速度(4)(harib23d)
-
伴隨圖層移動所進行的繪圖操作非常耗費時間,導致系統來不及處理FIFO緩衝區中的鼠標移動數據。這樣就會產生圖層跟不上鼠標的情況。
-
因此,在接收到鼠標移動數據後不利己進行繪圖操作,但如果一直不繪圖的話窗口和鼠標就靜止不動了,因而,等到FIFO緩衝區爲空時再進行繪圖操作。
-
修改bootpack.c中的HariMain:
void HariMain(void) { …… int mx, my, i, new_mx = -1, new_my = 0, new_wx = 0x7fffffff, new_wy = 0; …… for (;;) { …… if (fifo32_status(&fifo) == 0) { /* FIFO爲空,當存在擱置的繪圖操作時立即執行 */ if (new_mx >= 0) { io_sti(); sheet_slide(sht_mouse, new_mx, new_my); new_mx = -1; } else if (new_wx != 0x7fffffff) { io_sti(); sheet_slide(sht, new_wx, new_wy); new_wx = 0x7fffffff; } else { task_sleep(task_a); io_sti(); } } else { …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ if (mouse_decode(&mdec, i - 512) != 0) { …… new_mx = mx; new_my = my; if ((mdec.btn & 0x01) != 0) { if (mmx < 0) { for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { mmx = mx; mmy = my; mmx2 = sht->vx0; new_wy = sht->vy0; } …… } } } } else { /* 當窗口處於移動模式 */ x = mx - mmx; y = my - mmy; new_wx = (mmx2 + x + 2) & ~3; new_wy = new_wy + y; mmy = my; } } else { mmx = -1; if (new_wx != 0x7fffffff) { sheet_slide(sht, new_wx, new_wy); /* 固定圖層位置 */ new_wx = 0x7fffffff; } } } } } } }
- 增加了new_mx和new_my變量,並將sheet_slide(sht_mouse, mx, my)改成了new_mx=mx和new_my=my。也就是說,並不真正地移動鼠標圖層的位置,而是將移動後的座標暫時保存下下來,當FIFO爲空時,再執行sheet_slide(sht_mouse, new_mx, new_my)。
- 窗口的移動,採用相同的辦法,只不過有一個小小的區別:當緩衝區空了且不需要執行sheet_slide的值是0x7fffffff。因爲鼠標的位置不可能出現負數,因此可以用-1判斷。而圖層的位置可能是負數,因此用一個不可能出現的值0x7fffffff來判斷。
- 當放開鼠標左鍵退出窗口移動模式時,即便FIFO不爲空也需要立即更新窗口的位置,這是因爲用戶可能馬上回移動別的窗口,那樣的話sht的值就會變化,因此必須在sht值發生變化之前將當前窗口移動到指定的位置。
-
make
後用VMware運行:感覺又變快了(笑)。 -
可能讓人失望的是:對於本機的CPU來講,harib23a~harib23d所做的努力彷彿沒什麼明顯的效果,因爲CPU的處理速度太快了,幾乎沒有差別。但是做的這種努力是必要的!
- 本機的CPU型號是i5-8265U。
- 如果換一個低性能的CPU會明顯吧。
5. 啓動時只打開一個命令行窗口(harib23e)
-
將啓動時顯示的命令行窗口數量改成1個,並且實現可以隨意啓動新命令行窗口的功能。
-
打開命令行的方式有如下幾種:
- 在命令行中輸入特定指令,比如在Windows的命令行中輸入指令
start cmd.exe
,就會出現一個新的命令行窗口。 - 通過開始菜單,比如Windows中通過開始菜單可以打開命令行。
- 通過快捷鍵,比如在Ubuntu中通過Ctrl+Alt+T可以打開命令行。
- 在命令行中輸入特定指令,比如在Windows的命令行中輸入指令
-
在harib23e中,先採用快捷鍵的方式打開命令行。規定:
Shift+F2
打開新的命令行。 -
修改HariMain(bootpack.c):
void HariMain(void) { …… /* sht_cons */ sht_cons[0] = open_console(shtctl, memtotal); sht_cons[1] = 0; /* 未打開狀態 */ …… sheet_slide(sht_back, 0, 0); sheet_slide(sht_cons[0], 32, 4); sheet_slide(sht_mouse, mx, my); sheet_updown(sht_back, 0); sheet_updown(sht_cons[0], 1); sheet_updown(sht_mouse, 2); key_win = sht_cons[0]; keywin_on(key_win); …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… if (i == 256 + 0x3c && key_shift != 0 && sht_cons[1] == 0) { /* Shift+F2 */ sht_cons[1] = open_console(shtctl, memtotal); sheet_slide(sht_cons[1], 32, 4); sheet_updown(sht_cons[1], shtctl->top); /* 自動將輸入焦點切換到新打開的命令行窗口 */ keywin_off(key_win); key_win = sht_cons[1]; keywin_on(key_win); } …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ …… } } } }
- 將打開命令行窗口的程序封裝成了一個單獨的函數(open_console),返回值是生成的命令行的圖層。
- 將沒有打開的命令行的sht_cons[]置爲0。當按下Shift+F2且第二個命令行窗口未打開時,將其打開。
-
make
後用VMware運行:
- 注意,現在只有兩個命令行。只能按一次Shift+F2,按多了也沒啥用(準確地說,會造成內存的浪費)。
6. 增加更多的命令行窗口(harib23f)
-
harib23e中命令行窗口至多打開兩個。這是不合實際的。因此需要更多的命令行窗口。
-
最容易想到的方式是將sht_cons[]數組開闢更多的元素。
-
查看harib23e中的HariMain中有段sht_cons的代碼:
/* sht_cons */ sht_cons[0] = open_console(shtctl, memtotal); /*(1)*/ …… key_win = sht_cons[0]; /*(2)*/ keywin_on(key_win);
- 顯然,(1)和(2)可以寫成
key_win = open_console(shtctl, memtotal)
。 - 那麼,sht_cons數組沒啥用了啊。因此,刪掉sht_cons。
- 顯然,(1)和(2)可以寫成
-
修改HariMain:
void HariMain(void) { …… struct SHEET *sht_back, *sht_mouse; /*刪掉了shtc_cons[2]*/ …… /* sht_cons */ key_win = open_console(shtctl, memtotal); …… sheet_slide(sht_back, 0, 0); sheet_slide(key_win, 32, 4); sheet_slide(sht_mouse, mx, my); sheet_updown(sht_back, 0); sheet_updown(key_win, 1); sheet_updown(sht_mouse, 2); keywin_on(key_win); …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… if (i == 256 + 0x3c && key_shift != 0) { /* Shift+F2 */ keywin_off(key_win); key_win = open_console(shtctl, memtotal); sheet_slide(key_win, 32, 4); sheet_updown(key_win, shtctl->top); keywin_on(key_win); } …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ …… } } } }
- 不再使用sht_cons,因而命令行窗口數量不再有限制,只要內存允許,想開多少開多少。
-
make
後用VMware運行試試:
7. 關閉命令行窗口(1)(harib23g)
-
當命令行打開太多,而現在又不需要這麼多命令行時,便會對內存造成浪費。因爲,需要關掉命令行窗口。
- 注意,關閉命令行,不僅要關閉命令行圖層,還要關閉命令行任務。
-
規定:增加一個命令,當在命令行窗口中輸入
exit
時,關閉命令行。- 關閉一個命令行的具體步驟:
- 首先,將創建該窗口時所佔用的內存空間全部釋放出來
- 然後,釋放窗口的圖層和任務結構
- 關閉一個命令行的具體步驟:
-
在創建命令行時,爲命令行窗口準備了專用的棧,卻沒有將這個棧保存起來,這樣的話就無法釋放內存空間了。
-
因此,修改TASK結構體:
struct TASK { int sel, flags; int level, priority; struct FIFO32 fifo; struct TSS32 tss; struct CONSOLE *cons; int ds_base, cons_stack; };
- 新增成員變量cons_stack,用來保存棧的地址。
-
修改open_console,增加個函數close_constask和close_console(bootpack.c):
struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal) { …… task->cons_stack = memman_alloc_4k(memman, 64 * 1024); …… return sht; } void close_constask(struct TASK *task) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; task_sleep(task); memman_free_4k(memman, task->cons_stack, 64 * 1024); memman_free_4k(memman, (int) task->fifo.buf, 128 * 4); task->flags = 0; /* 用來代替task_free(task); */ return; } void close_console(struct SHEET *sht) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = sht->task; memman_free_4k(memman, (int) sht->buf, 256 * 165); sheet_free(sht); close_constask(task); return; }
- close_console函數用於關閉命令行。close_constask用於關閉命令行任務。
- 在close_constask中,先讓任務進入休眠狀態,這樣是爲了將任務從等待切換列表中安全剝離出來,這樣就可以安全地釋放棧和FIFO緩衝區空間。當內存空間全部釋放完畢以後,爲了能夠重新利用這些內存空間,還需要將flags置爲0。
-
修改cons_runcmd:
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal) { …… } else if (strcmp(cmdline, "exit") == 0) { cmd_exit(cons, fat); } else if (cmdline[0] != 0) { …… }
- 函數cmd_exit:
void cmd_exit(struct CONSOLE *cons, int *fat) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = task_now(); struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4); struct FIFO32 *fifo = (struct FIFO32 *) *((int *) 0x0fec); timer_cancel(cons->timer); memman_free_4k(memman, (int) fat, 4 * 2880); io_cli(); fifo32_put(fifo, cons->sht - shtctl->sheets0 + 768); /* 768~1023 */ io_sti(); for (;;) { task_sleep(task); } }
- exit命令執行的過程應該是:先取消控制光標的定時器,然後將FAT用的內存空間釋放,然後偶調用close_console關閉命令行窗口和自身的任務。
- 顯然,有問題。如果再cmd_exit執行close_console的話,就相當於close_constask中的task_sleep對自己任務本身執行了休眠,因此,task_sleep後面的代碼就不會被執行。
- 因此,需要task_a幫助完成執行close_console的任務:向task_a任務的FIFO發送一個數據,當task_a收到這個數據後就幫助該命令行執行close_console。
- 需要知道task_a的FIFO緩衝區,因此將task_a的緩衝區地址寫在內存地址0xfec中。
- 在harib22f中,修改了TASK結構體。到現在,內存地址0xfec和0xfe8都是沒用存放數據的,0xfe4存放着shtctl。
- 對緩衝區讀寫過程中,需要禁止中斷。
- 發送完成後,結束命令行的任務交給了task_a,因此,該命令行接下來休眠即可,等待task_a結束其。
- 發送的數據是
cons->sht - shtctl->sheets0 + 768
。
- 函數cmd_exit:
-
修改HariMain:
void HariMain(void) { …… *((int *) 0x0fec) = (int) &fifo; /將task_a的FIFO緩衝區地址寫入內存地址0x0fec**/ …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (key_win != 0 && key_win->flags == 0) { /* 窗口被關閉 */ if (shtctl->top == 1) { /* 當畫面上只剩下鼠標和背景時 */ key_win = 0; } else { key_win = shtctl->sheets[shtctl->top - 1]; keywin_on(key_win); } } if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… if (s[0] != 0 && key_win != 0) { /* 一般字符、退格鍵和Enter */ fifo32_put(&key_win->task->fifo, s[0] + 256); } if (i == 256 + 0x0f && key_win != 0) { /* Tab */ …… } …… if (i == 256 + 0x3b && key_shift != 0 && key_win != 0) { /* Shift+F1 */ task = key_win->task; if (task != 0 && task->tss.ss0 != 0) { cons_putstr0(task->cons, "\nBreak(key) :\n"); io_cli(); task->tss.eax = (int) &(task->tss.esp0); task->tss.eip = (int) asm_end_app; io_sti(); } } if (i == 256 + 0x3c && key_shift != 0) { /* Shift+F2 */ if (key_win != 0) { keywin_off(key_win); } key_win = open_console(shtctl, memtotal); sheet_slide(key_win, 32, 4); sheet_updown(key_win, shtctl->top); keywin_on(key_win); } …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ …… } else if (768 <= i && i <= 1023) { /* 命令行窗口關閉處理 */ close_console(shtctl->sheets0 + (i - 768)); } } } }
- 當task_a的FIFO接收到的數據介於768和1023之間,就執行命令行關閉處理。
- 發送的數據是
cons->sht - shtctl->sheets0 + 768
,因此i=cons->sht - shtctl->sheets0 + 768,shtctl->sheets0 + (i - 768)
=cons->sht,即該命令行的圖層。
- 發送的數據是
- 代碼中加入了判斷key_win是否爲0的條件,這是因爲關閉命令行以後,可能導致屏幕上一個窗口都沒有了。
- 當key_win=0時,此時只有背景和鼠標。
- 當key_win=0時,字符輸入和Shift+F1是沒有任何作用的。
- 當task_a的FIFO接收到的數據介於768和1023之間,就執行命令行關閉處理。
-
make
後在VMware運行試試:- 剛打開時的畫面:
- 使用Shift+F2新打開幾個命令行並輸入exit:
- 執行exit命令:
- 關閉所有窗口:
- 這個畫面既熟悉又陌生(笑)。
- 仍然可以使用Shift+F2打開新的命令行。
- 剛打開時的畫面:
8. 關閉命令行窗口(2)(harib23h)
-
實現用鼠標關閉命令行的功能。
- 當用鼠標點擊命令行窗口的“X”按鈕時,向命令行任務發送4這個數據,命令行接收到這個數據後就開始執行exit命令的程序。
- 鼠標點擊是task_a處理的。之所以不直接在task_a中調用close_console,是因爲如果這樣做的話,由命令行窗口自身管理的釋放定時器以及FAT內存空間的部分不好實現。
- 因此,點擊命令行的“X”按鈕->task_a給命令行發送數據4->命令行收到數據4->命令行執行cmd_exit->發送數據給task_a->task_a調用close_console->結束命令行。
-
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { if (mmx < 0) { for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { if ((sht->flags & 0x10) != 0) { /*是否是應用程序的窗口*/ …… } else { /* 命令行窗口 */ task = sht->task; io_cli(); fifo32_put(&task->fifo, 4); /*向命令行的FIFO緩衝區寫入4*/ io_sti(); } } break; } } } } else { …… } } else { …… } } } else if (768 <= i && i <= 1023) { close_console(shtctl->sheets0 + (i - 768)); } } } }
-
修改console_task:
void console_task(struct SHEET *sheet, int memtotal) { …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (i == 4) { cmd_exit(&cons, fat); } …… } } }
-
make
後用VMware運行:- 點擊命令行窗口的“X”按鈕:
- 命令行關閉
- 使用Shift+F2打開命令行,打開color.hrb應用程序:
- 此時點擊命令行窗口的“X”按鈕,不能關閉命令行窗口。
- 此時的確不能關閉命令行,可是爲什麼啊?明明向命令行任務的FIFO緩衝區寫入了4,可是卻沒收到?真是奇怪啊!
- 點擊命令行窗口的“X”按鈕:
9. start命令(harib23i)
-
編寫一個跟Windows中差不多的start命令。
-
修改cons_runcmd:
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal) { …… } else if (strncmp(cmdline, "start ", 6) == 0) { cmd_start(cons, cmdline, memtotal); } else if (cmdline[0] != 0) { …… }
- 編寫cmd_start函數:
void cmd_start(struct CONSOLE *cons, char *cmdline, int memtotal) { struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4); struct SHEET *sht = open_console(shtctl, memtotal); /*新打開一個命令行*/ struct FIFO32 *fifo = &sht->task->fifo; int i; sheet_slide(sht, 32, 4); sheet_updown(sht, shtctl->top); /* 將命令行輸入的字符串複製到新的命令行窗口中 */ for (i = 6; cmdline[i] != 0; i++) { fifo32_put(fifo, cmdline[i] + 256); } fifo32_put(fifo, 10 + 256); /* Enter */ cons_newline(cons); return; }
- 編寫cmd_start函數:
-
make
後用VMware運行,輸入start color
:
10. ncst命令(harib23j)
-
ncst是no console start的縮寫。start命令開起來不錯,但是如果運行color這種應用程序時,有些用戶不希望真的打開一個新的命令行窗口。即能在原來的命令行中繼續輸入指令。
-
這樣,可以根據需求選擇使用哪個命令運行應用程序:
- 當希望運行應用程序的同時打開新的命令行窗口,就使用start命令。
- 當不需要打開新的命令行窗口時,就使用ncst命令。
-
其實,ncst和start一樣,只不過沒有命令行圖層而已,除此之外,還有一些細節需要處理。
-
修改cons_runcmd,添加ncst命令:
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, int memtotal) { if (strcmp(cmdline, "mem") == 0 && cons->sht != 0) { cmd_mem(cons, memtotal); } else if (strcmp(cmdline, "cls") == 0 && cons->sht != 0) { cmd_cls(cons); } else if (strcmp(cmdline, "dir") == 0 && cons->sht != 0) { cmd_dir(cons); } else if (strncmp(cmdline, "type ", 5) == 0 && cons->sht != 0) { cmd_type(cons, fat, cmdline); } else if (strcmp(cmdline, "exit") == 0) { cmd_exit(cons, fat); } else if (strncmp(cmdline, "start ", 6) == 0) { cmd_start(cons, cmdline, memtotal); } else if (strncmp(cmdline, "ncst ", 5) == 0) { cmd_ncst(cons, cmdline, memtotal); } else if (cmdline[0] != 0) { …… } void cmd_ncst(struct CONSOLE *cons, char *cmdline, int memtotal) { struct TASK *task = open_constask(0, memtotal); struct FIFO32 *fifo = &task->fifo; int i; /* 將命令行輸入的字符串逐字複製到新的命令行窗口中 */ for (i = 5; cmdline[i] != 0; i++) { fifo32_put(fifo, cmdline[i] + 256); } fifo32_put(fifo, 10 + 256); /* Enter */ cons_newline(cons); return; }
- 規定沒有窗口的命令行任務的cons->sht=0。
- 在沒有窗口的情況下,執行mem、cls等命令沒有用(因爲沒有窗口輸出)。
- 函數open_constask會在bootpack.c中編寫。
-
當cons->sht=0時,要禁用命令行窗口的字符顯示等所有的操作,修改相關函數(console.c):
void cons_putchar(struct CONSOLE *cons, int chr, char move) { …… if (s[0] == 0x09) { /* 製表符 */ for (;;) { if (cons->sht != 0) { /*只有當cons->sht不爲0時,才能向命令行窗口輸出字符*/ putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, " ", 1); } cons->cur_x += 8; …… } } else if (s[0] == 0x0a) { /* 換行 */ cons_newline(cons); } else if (s[0] == 0x0d) { /* 回車 */ /* 暫時不做任何操作 */ } else { /* 一般字符 */ if (cons->sht != 0) { /*只有當cons->sht不爲0時,才能向命令行窗口輸出字符*/ putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 1); } if (move != 0) { …… } } return; } void cons_newline(struct CONSOLE *cons) { int x, y; struct SHEET *sheet = cons->sht; if (cons->cur_y < 28 + 112) { cons->cur_y += 16; /* 到下一行 */ } else { /* 滾動 */ if (sheet != 0) { /*只有當cons->sht不爲0時,才能向命令行窗口輸出字符*/ for (y = 28; y < 28 + 112; y++) { for (x = 8; x < 8 + 240; x++) { sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize]; } } for (y = 28 + 112; y < 28 + 128; y++) { for (x = 8; x < 8 + 240; x++) { sheet->buf[x + y * sheet->bxsize] = COL8_000000; } } sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128); } } cons->cur_x = 8; return; }
-
修改console_task:
void console_task(struct SHEET *sheet, int memtotal) { …… if (sheet != 0) { /*沒有圖層的時候不需要光標定時器*/ cons.timer = timer_alloc(); timer_init(cons.timer, &task->fifo, 1); timer_settime(cons.timer, 50); } …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ if (i == 8 + 256) { …… } else if (i == 10 + 256) { /* Enter */ cons_putchar(&cons, ' ', 0); cmdline[cons.cur_x / 8 - 2] = 0; cons_newline(&cons); cons_runcmd(cmdline, &cons, fat, memtotal); /* 執行命令 */ if (sheet == 0) { /*自動關閉沒有圖層的命令行任務*/ cmd_exit(&cons, fat); } cons_putchar(&cons, '>', 1); } else { …… } } /* 重新顯示光標 */ if (sheet != 0) { /*沒有圖層的時候不需要重新顯示光標*/ if (cons.cur_c >= 0) { boxfill8(sheet->buf, sheet->bxsize, cons.cur_c, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15); } sheet_refresh(sheet, cons.cur_x, cons.cur_y, cons.cur_x + 8, cons.cur_y + 16); } } } }
- 修改的要點是:當不顯示命令行窗口時,禁用一些不必要的處理,並且當命令執行完畢時,立即結束命令行窗口任務(應用程序運行完畢後,這個命令行窗口任務就派不上什麼用場了。因爲畫面上不顯示命令行窗口,也就無法輸入其他命令,也不能執行關閉操作,因此需要設置其在命令執行完畢後自動終止任務。)
-
修改cmd_exit:
void cmd_exit(struct CONSOLE *cons, int *fat) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = task_now(); struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4); struct FIFO32 *fifo = (struct FIFO32 *) *((int *) 0x0fec); if (cons->sht != 0) { timer_cancel(cons->timer); } memman_free_4k(memman, (int) fat, 4 * 2880); io_cli(); if (cons->sht != 0) { fifo32_put(fifo, cons->sht - shtctl->sheets0 + 768); /* 768~1023 */ } else { fifo32_put(fifo, task - taskctl->tasks0 + 1024); /* 1024~2023 */ } io_sti(); for (;;) { task_sleep(task); } }
- 當命令行任務有圖層時,可以通過圖層得到是哪個命令行任務,因此可以傳遞給task_a圖層地址。
- 當命令行任務沒有圖層時,cons->sht=0,因此將TASK結構的地址告訴task_a。
-
修改bootpack.c,添加函數open_constask:
struct TASK *open_constask(struct SHEET *sht, unsigned int memtotal) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = task_alloc(); int *cons_fifo = (int *) memman_alloc_4k(memman, 128 * 4); task->cons_stack = memman_alloc_4k(memman, 64 * 1024); task->tss.esp = task->cons_stack + 64 * 1024 - 12; task->tss.eip = (int) &console_task; task->tss.es = 1 * 8; task->tss.cs = 2 * 8; task->tss.ss = 1 * 8; task->tss.ds = 1 * 8; task->tss.fs = 1 * 8; task->tss.gs = 1 * 8; *((int *) (task->tss.esp + 4)) = (int) sht; *((int *) (task->tss.esp + 8)) = memtotal; task_run(task, 2, 2); /* level=2, priority=2 */ fifo32_init(&task->fifo, 128, cons_fifo, task); return task; }
-
修改open_console函數:
struct SHEET *open_console(struct SHTCTL *shtctl, unsigned int memtotal) { struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct SHEET *sht = sheet_alloc(shtctl); unsigned char *buf = (unsigned char *) memman_alloc_4k(memman, 256 * 165); sheet_setbuf(sht, buf, 256, 165, -1); make_window8(buf, 256, 165, "console", 0); make_textbox8(sht, 8, 28, 240, 128, COL8_000000); sht->task = open_constask(sht, memtotal); sht->flags |= 0x20; return sht; }
- 其實就是將原來的open_console函數中的一部分代碼分離出來組成新的函數open_constask。
- 跟close_console中分離一部分到close_constask一樣。
-
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 鍵盤數據 */ …… } else if (512 <= i && i <= 767) { /* 鼠標數據 */ …… } else if (768 <= i && i <= 1023) { /* 命令行關閉處理 */ close_console(shtctl->sheets0 + (i - 768)); } else if (1024 <= i && i <= 2023) { /*只需要關閉任務即可*/ close_constask(taskctl->tasks0 + (i - 1024)); } } } }
-
make
後用VMware運行:
- 成功了!
- 不過,好像有BUG,使用“X”按鈕或者Shift+F1無法關閉應用程序的窗口,而使用回車倒是可以。
- 明天再解決這個bug吧。
11. 寫在五四青年節
- 現在是2020.5.4 21:56。
- 這篇博客寫了兩天,從markdown文檔的行數也可以看出,第26天工作量巨大。
- 這篇文檔寫了近1100行,因此,一不小心又打破了記錄了呢。
- 看了bilibili獻給新一代的演講《後浪》https://www.bilibili.com/video/BV1FV411d7u7
弱小的人,才習慣嘲諷和否定;內心強大的人,從不吝嗇讚美和鼓勵。
- 願你和我,心裏有火,眼裏有光!