三十天自制作業系統(13)

weixin_33895657發表於2016-07-27

第25天

這本書的這一章一開始就講如果控制主機板上的蜂鳴發專聲器發聲,看到這個我很興奮。因為到目前為止我還沒有用windows api或者自己寫程式讓電腦發出聲音,終於可以嘗試一下了,雖然不是音效卡發聲,只是最低階的蜂鳴發聲器,但是還是很興奮。

我們控制主機板上的發聲器也和控制中斷處理器是一樣的,也是要使用in和out指令進行操作。

蜂鳴發聲器的開啟和關閉:

  • 使用埠0x61控制
  • 開啟:IN(AL, 0X61); AL |= 0X03; AL &= 0X0F; OUT(0X61, AL);
  • 關閉:IN(AL, 0X61); AL &= 0X0D; AL &= 0X0D; OUT(0X61, AL);

如果開啟之後控制發聲器的發聲頻率:

  1. AL = 0XB6; OUT(0X43, AL);
  2. AL = 設定值的低8位; OUT(0X42, AL);
  3. AL = 設定值的高8位; OUT(0X42, AL);
  4. 當設定值為0時當作65536來處理
  5. 發聲的頻率為時鐘除以設定值,也就是說設定值為1000時相當於發也1.19318KHZ的聲音

api設計

  • edx = 20
  • eax = 聲音訊率(單位是mHz, 即毫HZ),當頻率為0時表示停止發聲
    else if (edx == 20) {
        if (eax == 0) {
            i = io_in8(0x61);
            io_out8(0x61, i & 0x0d);
    }   else {
            i = 1193180000 / eax;
            io_out8(0x43, 0xb6);
            io_out8(0x42, i & 0xff);
            io_out8(0x42, i >> 8);
            i = io_in8(0x61);
            io_out8(0x61, (i | 0x03) & 0x0f);
        }
    }


    _api_beep:          ; void api_beep(int tone);
        MOV     EDX,20
        MOV     EAX,[ESP+4]         ; tone
        INT     0x40
        RET

看一下應用程式如何寫:

    void api_end(void);
    int api_getkey(int mode);
    int api_alloctimer(void);
    void api_inittimer(int timer, int data);
    void api_settimer(int timer, int time);
    void api_beep(int tone);

    void HariMain(void)
    {
        int i, timer;
        timer = api_alloctimer();
        api_inittimer(timer, 128);
        for (i = 20000000; i >= 20000; i -= i / 100) {
            /* 20KHz�~20Hz :人類可以聽到的聲音範圍 */
            /* i以1%的速度遞減 */
            api_beep(i);
            api_settimer(timer, 1);     /* 0.01秒 */
            if (api_getkey(1) != 128) {
                break;
            }
        }
        api_beep(0);
        api_end();
    }

關於聲音就到這裡,接下來看看如何增加更多的顏色。

到目前為止我們只用了16程模式。我們使用的是顯示卡的調色盤模式,理論上有256種顏色,我們就想辦法讓作業系統支援更多的顏色。

剩下的240個顏色我們設定麼顏色呢?調色盤對應的是RGB,一共24位,我們給每個三原色中的一種分6個,那麼一共就可以定義6 * 6 * 6 = 216種顏色

    unsigned char table2[216 * 3];
    int r, g, b;
    set_palette(0, 15, table_rgb);
    for (b = 0; b < 6; b++) {
        for (g = 0; g < 6; g++) {
            for (r = 0; r < 6; r++) {
                table2[(r + g * 6 + b * 36) * 3 + 0] = r * 51;
                table2[(r + g * 6 + b * 36) * 3 + 1] = g * 51;
                table2[(r + g * 6 + b * 36) * 3 + 2] = b * 51;
            }
        }
    }
    set_palette(16, 231, table2);

RGB取值分別為0, 51, 102, 153, 204, 255。這樣調色盤設定好了之後如果要取(51, 102, 153)這個顏色的話就是137號。

將下來我們寫一個應用程式測試一下。

    int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
    void api_initmalloc(void);
    char *api_malloc(int size);
    void api_refreshwin(int win, int x0, int y0, int x1, int y1);
    void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
    int api_getkey(int mode);
    void api_end(void);

    void HariMain(void)
    {
        char *buf;
        int win, x, y, r, g, b;
        api_initmalloc();
        buf = api_malloc(144 * 164);
        win = api_openwin(buf, 144, 164, -1, "color");
        for (y = 0; y < 128; y++) {
            for (x = 0; x < 128; x++) {
                r = x * 2;
                g = y * 2;
                b = 0;
                buf[(x + 8) + (y + 28) * 144] = 16 + (r / 43) + (g / 43) * 6 + (b / 43) * 36;
            }
        }
        api_refreshwin(win, 8, 28, 136, 156);
        api_getkey(1);
        api_end();
    }

我們現在只能在命令列視窗執行應用程式,但是命令列視窗只有一個,所以我們同時只能執行一個應用程式,那麼多工對於應用程式來說沒有任何意義。我們應用讓作業系統可以同時執行多個命令列以支行多個應用程式。

如果想要支援多個命令列視窗,那麼就需要改造作業系統的程式結構。我們之前把每個命令列視窗的資料段和視窗資料存到作業系統的記憶體中。

    int ds_base = *((int *) 0xfe8);
    struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);

如果有多個命令列視窗的話這樣的方式就不能用了,反正要為每個命令列視窗建立一個任務,而每個任務有會維護一個TASK結構的資料,我們可以改造這個結構將這兩個值儲存到這個變數中。

    struct TASK {
        int sel, flags; /* sel‚ÍGDT‚Ì”Ô�†‚Ì‚±‚Æ */
        int level, priority;
        struct FIFO32 fifo;
        struct TSS32 tss;
        struct CONSOLE *cons;
        int ds_base;
    };

之前我們在執行應用程式的時候,直接給應用程式分配段號,現在由於存在多個命令列視窗,可能會執行多個應用程式就不能用這樣的方式分配段號了。

    set_segmdesc(gdt + task->sel / 8 + 1000, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
    set_segmdesc(gdt + task->sel / 8 + 2000, segsiz - 1,      (int) q, AR_DATA32_RW + 0x60);

接下來處理關閉應用程式時候的問題了。

    if (i == 256 + 0x3b && key_shift != 0) {
        task = key_win->task;
        if (task != 0 && task->tss.ss0 != 0) {  /* Shift+F1 */
            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 (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
        /* �點選關閉按扭 */
        if ((sht->flags & 0x10) != 0) {     /* 是否為應用程式視窗 */
            task = sht->task;
            cons_putstr0(task->cons, "\nBreak(mouse) :\n");
            io_cli();   /* 強制結束時禁止任務切換 */
            task->tss.eax = (int) &(task->tss.esp0);
            task->tss.eip = (int) asm_end_app;
            io_sti();
        }
    }

以上是點選關閉按鈕時關閉應用程式。這樣處理完之後就可以支援多個應用程式視窗了,為了寫程式方便,這本書就使用了作業系統執行時開啟2個命令列視窗,估計接下來還會通過輸入命令執行命令列視窗的功能。

這個作業系統還有一個和其他作業系統不一樣的地方。一開機時候自動行開啟一個視窗也就是task_a視窗。為了使作業系統更像個作業系統應該把這個視窗給取消了。

如果要取消這個視窗其實只要刪除跟執行這個視窗有關的程式碼就行了,可以刪好多東西,刪除之後看看結果怎麼樣結果是作業系統出錯了。原來的作業系統執行流程是這樣的:首先執行主程式,開啟task_a視窗,然後把作業系統的焦點放在這個視窗,游標也在task_a視窗閃爍,直到使用者按下了tab或者用滑鼠切換視窗之後才切換。但是現在不是了,現在我們想要讓游標直接在第一個開啟的命令列視窗閃爍就出問題了。因了task_a的優先順序要高,當task_a執行完之前就向命令列視窗傳送游標閃爍的訊息時候,命令列視窗連訊息佇列都還沒有建立,所以出錯了。解決方法也很簡單,只要把命令列視窗建立訊息佇列的程式語句放在傳送訊息之前就可以了。

相關文章