Day25

今天做的是添加多种颜色、多个命令行(其实是填坑)。

一些感想

蜂鸣器发声

在windows.h就有类似的函数,不过之前的电脑好像不支持,就没有再研究过这个了。这次在QEMU模拟器环境下貌似也不起作用。那就看看代码好了。

增加更多的颜色

并没有用到VESA的全彩色模式,作者在这里只是用了一些技巧。如果要支持全彩色的话,应该是要自己实现吧。

  1. 扩展成6 * 6 * 6个颜色
    并不是6位,而是6个。。考虑到本来这个显卡也支持不了太多颜色吧。。按照6进制来分别记录RGB的色阶。
  2. 增加更多的颜色
    适用于分辨率比较高的情况。把相邻的像素用两种颜色交替排列,看起来就像之间的中间色一样。

增加命令行窗口

以前的有些设计只能适用于一个命令行窗口,所以现在又要逐渐填坑了。

  1. 各自窗口的初始化
    这个就不用说了,算是多个窗口的必须步骤吧)也不算是之前的坑
  2. 增加任务指定的命令行
    之前是在内存的0x0fec位置指定的控制台地址,遇到多个命令行的时候就会被覆盖了。于是在TASK结构体里增加了命令行的指针,表明是由哪个命令行打开的。
  3. 给不同的应用程序分配不同的段
    之前的数据段貌似是分配了一个大段,分给不同的应用程序。但是这样造成的问题是一个新应用程序进来的时候,会把之前的覆盖掉。因此需要修改之前的分配方式,给应用程序预留1000个数据段,分配给不同的应用程序。
  4. 关闭不同命令行打开的应用程序
    因为TASK已经绑定了命令行,在关闭窗口的时候,判断是哪个命令行打开的,把关闭消息发给对应的命令行

删掉task_a

task_a这个窗口是在开始的时候进行测试用的。现在看起来已经没什么用了,就把它删掉好了。但是会导致FIFO初始化的时候出问题(被删掉之后没有进行初始化就有可能向FIFO传数据了),解决方案是把命令行对FIFO的初始化代码放到HariMain里。

蜂鸣器发声

harib22a

蜂鸣器是由PIT来控制的。控制方法:

  • 音高操作
    AL=0xb6; OUT(0x43, AL);
    AL=设定值的低8位bit; OUT(0x42, AL);
    AL=设定值的高8位bit; OUT(0x42, AL);
    设定值为0时当作65536来处理
    1000 = 1.19318KHz
  • 蜂鸣器ON/OFF
    使用I/O端口 0x61 控制
    ON: IN(AL, 0x61); AL |= 0x03; AL &= 0x0f; OUT(0x61, AL);
    OFF: IN(AL, 0x61); AL &= 0xd; OUT(0x61, AL);

API:

  • 蜂鸣器发声
    EDX=20
    EAX=声音频率(单位是mHz,即毫赫兹)
    频率设置为0则表示停止发声

添加API:

case 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);
    }
    break;

汇编调用:

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

测试程序:

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();
}

当程序执行完,或者按下回车之后,程序结束。

因为是模拟器,并不能听到蜂鸣器的声音。(电脑本身支持Beep的)

增加更多的颜色(1)

harib22b

首先是扩展成每种都有6个色阶

void init_palette(void)
{
    static unsigned char table_rgb[16 * 3] =
    {
        0x00, 0x00, 0x00, /*0:黑*/
        0xff, 0x00, 0x00, /*1:亮红*/
        0x00, 0xff, 0x00, /*2:亮绿*/
        0xff, 0xff, 0x00, /*3:亮黄*/
        0x00, 0x00, 0xff, /*4:亮蓝*/
        0xff, 0x00, 0xff, /*5:亮紫*/
        0x00, 0xff, 0xff, /*6:浅亮蓝*/
        0xff, 0xff, 0xff, /*7:白*/
        0xc6, 0xc6, 0xc6, /*8:亮灰*/
        0x84, 0x00, 0x00, /*9:暗红*/
        0x00, 0x84, 0x00, /*10:暗绿*/
        0x84, 0x84, 0x00, /*11:暗黄*/
        0x00, 0x00, 0x84, /*12:暗青*/
        0x84, 0x00, 0x84, /*13:暗紫*/
        0x00, 0x84, 0x84, /*14:浅暗蓝*/
        0x84, 0x84, 0x84  /*15:暗灰*/
    };
    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);
    return;
}

RGB每个都为8位(256)的时候,每51个设置一个色阶。

然后是测试程序:

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 * 6的调色板。

增加更多的颜色(2)

harib22c

混合像素的应用程序:

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;
}

通过奇偶判断以及预设一个奇偶对应的偏移表,将一个正方形的四个格子分别染成
+3 +0
+1 +2
这样类似的效果。
这样的渐变更加平滑

窗口初始位置

harib22d

更改了API申请窗口时的动作,把新申请的窗口放到最上层并放到画面中央。

case 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;
    break;

没问题

增加命令行窗口(1)

harib22e

这一步先更改各种初始化的地方。放一些举例

for(i = 0; i < 2; ++i)
{
    sht_cons[i] = sheet_alloc(shtctl);
    buf_cons[i] = (unsigned char *)memman_alloc_4k(memman, 256 * 165);
    sheet_setbuf(sht_cons[i], buf_cons[i], 256, 165, -1); // 没有透明色
    make_window8(buf_cons[i], 256, 165, "console", 0);
    make_textbox8(sht_cons[i], 8, 28, 240, 128, COL8_000000);
    task_cons[i] = task_alloc();
    task_cons[i]->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;
    task_cons[i]->tss.eip = (int)&console_task;
    task_cons[i]->tss.es = 1 * 8;
    task_cons[i]->tss.cs = 2 * 8;
    task_cons[i]->tss.ss = 1 * 8;
    task_cons[i]->tss.ds = 1 * 8;
    task_cons[i]->tss.fs = 1 * 8;
    task_cons[i]->tss.gs = 1 * 8;
    *((int *)(task_cons[i]->tss.esp + 4)) = (int)sht_cons[i];
    *((int *)(task_cons[i]->tss.esp + 8)) = memtotal;
    task_run(task_cons[i], 2, 2); // level=2, priority=2
    sht_cons[i]->task = task_cons[i];
    sht_cons[i]->flags |= 0x20;    // 有光标
}

这一部分是通过一个循环来简单地申请两个命令行,其它地方的话,没有太多需要看的)
但是光这样申请是不行的,还需要改其它的地方

增加命令行窗口(2)

harib22f

指定打开应用程序的任务

task->cons = &cons;

指定应用程序数据段基址

task->ds_base = (int)q;

总之之前涉及的指定cons的地方都要更改一下。
但是还是有问题。

增加命令行窗口(3)

harib22g

增加多个数据段

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);

这样的话,段号1003~2002为应用程序代码段编号,2003~3002为数据段编号。每个应用程序分配不同的sel,就不会出现问题了。

增加命令行窗口(4)

harib22h

修改应用程序关闭的逻辑
键盘关闭:

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();
    }
}

把task改变为当前正在输入的窗口的应用程序。
鼠标关闭:

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();
    }
}

把task改变为当前点击的窗口的应用程序
这样两个命令行下的问题就解决了。

变得更像真正的操作系统(1)

harib22i

删掉了各种task_a相关的部分。但是删除没法用代码展示。。就略过不提好了
然后删完就出bug了。。

变得更像真正的操作系统(2)

harib22j

console的task还没被初始化就使用了,于是出现了bug
所以把初始化的代码放到了HariMain里

cons_fifo[i] = (int *)memman_alloc_4k(memman, 128 * 4);
fifo32_init(&task_cons[i]->fifo, 128, cons_fifo[i], task_cons[i]);

之后再把之前写的初始化删掉,就完成了。

今天虽然好多个小节都在填坑,但是也可以看看之前设计的不够周全之处。还有有时候从一到多需要改变很多东西。