「30天自制操作系统」Day25 - 增加命令行窗口

蜂鸣器发声

下面我们添加一个让蜂鸣器发声的功能,编写 API 如下:

寄存器
EDX 20
EAX 声音频率(单位是 mHz)

频率设为 0 表示停止发声

我们编写 API 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
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);
}
}
return 0;
}

然后我们编写用来测试的应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}

增加更多的颜色

我们为光的三原色中每种颜色赋予 6 个色阶,这样就可以定义 216 种颜色了。

修改 graphic.c 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

应用程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
}

运行一下,效果如下:

增加更多的颜色

这里我们可以用一个技巧,让 6 级色阶显示出 21 级色阶。修改应用程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void HariMain(void)
{
char *buf;
int win, x, y;
api_initmalloc();
buf = api_malloc(144 * 164);
win = api_openwin(buf, 144, 164, -1, "color2");
for (y = 0; y < 128; y++) {
for (x = 0; x < 128; x++) {
buf[(x + 8) + (y + 28) * 144] = rgb2pal(x * 2, y * 2, 0, x, y);
}
}
api_refreshwin(win, 8, 28, 136, 156);
api_getkey(1);
api_end();
}

unsigned char rgb2pal(int r, int g, int b, int x, int y)
{
static int table[4] = { 3, 1, 0, 2 };
int i;
x &= 1;
y &= 1;
i = table[x + y * 2]; /* 用来生成中间色的常量 */
r = (r * 21) / 256; /* r 为 0~20 */
g = (g * 21) / 256;
b = (b * 21) / 256;
r = (r + i) / 4; /* r 为 0~5 */
g = (g + i) / 4;
b = (b + i) / 4;
return 16 + r + g * 6 + b * 36;
}

看一下运行效果吧!

窗口初始位置

我们想让窗口总是显示在画面的中央,并且总是自动显示在最上面。修改代码如下即可实现:

1
2
3
4
5
6
7
8
9
10
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, (shtctl->ysize - edi) / 2);
sheet_updown(sht, shtctl->top); /*将窗口图层高度指定为当前鼠标所在图层的高度,鼠标移到上层*/
reg[7] = (int) sht;
}

看看运行效果:

增加命令行窗口

为了能够同时启动两个应用程序,我们首先考虑能同时启动两个命令行窗口。这次要做不少改动了……首先我们需要把相关变量 buf_cons、sht_cons、task_cons、cons 都扩充成数组。然后把原来对一个变量做的操作都用 for 循环对数组做一遍。

直接这样扩充肯定是存在问题的,第一个问题是我们无论在哪个窗口运行应用程序,都会在固定的其中一个窗口中显示字符。

要解决这个问题,我们考虑修改一下 TASK 结构,将 cons 和 ds_base 放在 TASK 结构中(每个人物都拥有各自的 TASK 结构,这样就可以由不同的任务读取出不同的值了)。然后我妈把代码中的 ((int ) 0x0fec) 和 ((int ) 0x0fe8) 都改为用 cons 和 ds_base 代替就可以了。

看一下现在的效果:

此外,我们还要给 color.hrb 和 color2.hrb 分配编号不同的段,代码如下:

1
2
3
4
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);

start_app(0x1b, task->sel + 1000 * 8, esp, task->sel + 2000 * 8, &(task->tss.esp0));

这样就不会发生段被覆盖的问题了。

最后我们再修改一下对 shift+F1 以及 × 按钮的处理就好了,最后的效果如下:

变得更像真正的操作系统

现在我们的操作系统在启动时还会自动弹出来一个 task_a,我们现在考虑把它删掉。

但是删掉后一运行,系统就开始不断重启。要解决这个问题只要将 keywin_on(key_win) 这一句删掉就可以了。可以确定是这个函数里的 fifo32_put(&key_win->task->fifo, 2) 这一句出了问题,这是因为我们这个 FIFO 缓冲区的从初始化在 console_task 最开头,而命令行窗口任务的优先级比较低,而如果它没有被运行的话,FIFO 缓冲区就不会被初始化,从而造成 fifo32_put 混乱而导致重启。

那么我们把初始化的代码移动到 HariMain 里就可以了。最后我们运行一下看看效果:

大功告成了!