第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);
如果打開之后控制發聲器的發聲頻率:
- AL = 0XB6; OUT(0X43, AL);
- AL = 設定值的低8位; OUT(0X42, AL);
- AL = 設定值的高8位; OUT(0X42, AL);
- 當設定值為0時當作65536來處理
- 發聲的頻率為時鐘除以設定值,也就是說設定值為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運行完之前就向命令行窗口發送光標閃爍的消息時候,命令行窗口連消息隊列都還沒有建立,所以出錯了。解決方法也很簡單,只要把命令行窗口建立消息隊列的程序語句放在發送消息之前就可以了。