第26天 爲窗口移動提速

第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相當於做了四捨五入,左右移的機率相同。
  • 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可以打開命令行。
  • 在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。
  • 修改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時,關閉命令行

    • 關閉一個命令行的具體步驟:
      1. 首先,將創建該窗口時所佔用的內存空間全部釋放出來
      2. 然後,釋放窗口的圖層和任務結構
  • 在創建命令行時,爲命令行窗口準備了專用的棧,卻沒有將這個棧保存起來,這樣的話就無法釋放內存空間了。

  • 因此,修改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
  • 修改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是沒有任何作用的。
  • 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,可是卻沒收到?真是奇怪啊!

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;
      }
      
  • 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
  • 弱小的人,才習慣嘲諷和否定;內心強大的人,從不吝嗇讚美和鼓勵。
  • 願你和我,心裏有火,眼裏有光!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章