Day23

今天的话主要在提供各种窗口相关的API,以及最后对于强制结束程序的完善。

一些感想和问题

应用程序的malloc

这次做的是应用程序需要的malloc,目前的做法是从一个较大的应用程序数据段中拿一部分给应用程序,以后看起来还会改进

强制结束时释放资源

当应用程序通过API申请某些系统资源的时候,将其与任务绑定。若任务结束后,这些资源仍没被释放,则由操作系统进行释放

free去哪里了?

虽然api提供了free,但是却没有看到在后面的函数使用free。。不太明白为什么

今天主要是功能性的更新,所以开始的总结比较少

编写malloc

定义各种API

  • memman初始化
    EDX=8
    EBX=memman的地址
    EAX=memman所管理的内存空间的起始地址
    ECX=memman所管理的内存空间的字节数
  • malloc
    EDX=9
    EBX=memman的地址
    ECX=需要请求的字节数
    EAX=分配到的内存空间地址
  • free
    EDX=10
    EBX=memman的地址
    EAX=需要释放的内存空间地址
    ECX=需要释放的字节数
case 8:
    memman_init((struct MEMMAN *)(ebx + ds_base));
    ecx &= 0xfffffff0;    // 以16字节为单位
    memman_free((struct MEMMAN *)(ebx + ds_base), eax, ecx);
    break;
case 9:
    ecx = (ecx + 0x0f) & 0xfffffff0; // 以16字节为单位进位取整
    reg[7] = memman_alloc((struct MEMMAN *)(ebx + ds_base), ecx);
    break;
case 10:
    ecx = (ecx + 0x0f) & 0xfffffff0; // 以16字节为单位进位取整
    memman_free((struct MEMMAN *)(ebx + ds_base), eax, ecx);
    break;
_api_initmalloc:    ; void api_initmalloc(void);
        PUSH    EBX
        MOV        EDX,8
        MOV        EBX,[CS:0x0020]        ; malloc内存空间的地址
        MOV        EAX,EBX
        ADD        EAX,32*1024            ; 加上32KB
        MOV        ECX,[CS:0x0000]        ; 数据段的大小
        SUB        ECX,EAX
        INT        0x40
        POP        EBX
        RET

_api_malloc:        ; char *api_malloc(int size);
        PUSH    EBX
        MOV        EDX,9
        MOV        EBX,[CS:0x0020]
        MOV        ECX,[ESP+8]            ; size
        INT        0x40
        POP        EBX
        RET

_api_free:            ; void api_free(char *addr, int size);
        PUSH    EBX
        MOV        EDX,10
        MOV        EBX,[CS:0x0020]
        MOV        EAX,[ESP+ 8]        ; addr
        MOV        ECX,[ESP+12]        ; size
        INT        0x40
        POP        EBX
        RET

然后编写一下测试程序试试

void HariMain(void)
{
    char *buf;
    int win;

    api_initmalloc();
    buf = api_malloc(150 * 50);
    win = api_openwin(buf, 150, 50, -1, "hello");
    api_boxfilwin(win,  8, 36, 141, 43, 6 /* 浅蓝色 */);
    api_putstrwin(win, 28, 28, 0 /* 黑色 */, 12, "hello, world");
    api_end();
}

画点

harib20b

画点API
在窗口中画点
EDX=11
EBX=窗口句柄
ESI=显示位置的x坐标
EDI=显示位置的y坐标
EAX=色号

case 11:
    sht = (struct SHEET *)ebx;
    sht->buf[sht->bxsize * edi + esi] = eax;
    sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
    break;
_api_point:        ; void api_point(int win, int x, int y, int col);
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV        EDX,11
        MOV        EBX,[ESP+16]    ; win
        MOV        ESI,[ESP+20]    ; x
        MOV        EDI,[ESP+24]    ; y
        MOV        EAX,[ESP+28]    ; col
        INT        0x40
        POP        EBX
        POP        ESI
        POP        EDI
        RET

测试程序

void HariMain(void)
{
    char *buf;
    int win;
    api_initmalloc();
    buf = api_malloc(150 * 100);
    win = api_openwin(buf, 150, 100, -1, "star1");
    api_boxfilwin(win,  6, 26, 143, 93, 0 /* 黑色 */);
    api_point(win, 75, 59, 3 /* 黄色 */);
    api_end();
}
void HariMain(void)
{
    char *buf;
    int win, i, x, y;
    api_initmalloc();
    buf = api_malloc(150 * 100);
    win = api_openwin(buf, 150, 100, -1, "stars");
    api_boxfilwin(win,  6, 26, 143, 93, 0 /* 黑色 */);
    for(i = 0; i < 50; ++i)
    {
        x = (rand() % 137) + 6;
        y = (rand() % 67) + 26;
        api_point(win, x, y, 3 /* 黄色 */);
    }
    api_end();
}

在屏幕上点了好多个点。。

刷新窗口

harib20c

每次调用点都要重新刷新,太慢了
所以添加一个刷新的API,同时修改之前的API,可以指定是否刷新
刷新窗口
EDX=12
EBX=窗口句柄
EAX=x0
ECX=y0
ESI=x1
EDI=y1

case 6:
    sht = (struct SHEET *)(ebx & 0xfffffffe);
    putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *)ebp + ds_base);
    if((ebx & 1) == 0)
    {
        sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);
    }
    break;
case 7:
    sht = (struct SHEET *)(ebx & 0xfffffffe);
    boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
    if((ebx & 1) == 0)
    {
        sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
    }
    break;
...
case 11:
    sht = (struct SHEET *)(ebx & 0xfffffffe);
    sht->buf[sht->bxsize * edi + esi] = eax;
    if((ebx & 1) == 0)
    {
        sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
    }
    break;
case 12:
    sht = (struct SHEET *)ebx;
    sheet_refresh(sht, eax, ecx, esi, edi);
    break;

通过偏移1位SHT,之后在判断是否需要刷新的时候,如果不是2的倍数,则ebx & 1就为0,不进行刷新。

_api_refreshwin:    ; void api_refreshwin(int win, int x0, int y0, int x1, int y1);
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV        EDX,12
        MOV        EBX,[ESP+16]    ; win
        MOV        EAX,[ESP+20]    ; x0
        MOV        ECX,[ESP+24]    ; y0
        MOV        ESI,[ESP+28]    ; x1
        MOV        EDI,[ESP+32]    ; y1
        INT        0x40
        POP        EBX
        POP        ESI
        POP        EDI
        RET

测试程序

void HariMain(void)
{
    char *buf;
    int win, i, x, y;
    api_initmalloc();
    buf = api_malloc(150 * 100);
    win = api_openwin(buf, 150, 100, -1, "stars2");
    api_boxfilwin(win + 1,  6, 26, 143, 93, 0 /* 黑色 */);
    for(i = 0; i < 50; ++i)
    {
        x = (rand() % 137) +  6;
        y = (rand() %  67) + 26;
        api_point(win + 1, x, y, 3 /* 黄色 */);
    }
    api_refreshwin(win,  6, 26, 144, 94);
    api_end();
}

画直线

harib20d

貌似现在还不能使用浮点数。作者提供的方法是预先放大坐标1024倍(凑个整)之后每次增加dx,dy
在窗口上画直线
EDX=13
EBX=窗口句柄
EAX=x0
ECX=y0
ESI=x1
EDI=y1
EBP=色号

case 13:
    sht = (struct SHEET *)(ebx & 0xfffffffe);
    hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
    if((ebx & 1) == 0)
    {
        sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
    }
    break;
void hrb_api_linewin(struct SHEET *sht, int x0, int y0, int x1, int y1, int col)
{
    int i, x, y, len, dx, dy;

    dx = x1 - x0;
    dy = y1 - y0;
    x = x0 << 10;
    y = y0 << 10;
    if(dx < 0)
    {
        dx = -dx;
    }
    if(dy < 0)
    {
        dy = -dy;
    }
    if(dx >= dy)
    {
        len = dx + 1;
        if(x0 > x1)
        {
            dx = -1024;
        }
        else
        {
            dx = 1024;
        }
        if(y0 <= y1)
        {
            dy = ((y1 - y0 + 1) << 10) / len;
        }
        else
        {
            dy = ((y1 - y0 - 1) << 10) / len;
        }
    }
    else
    {
        len = dy + 1;
        if(y0 > y1)
        {
            dy = -1024;
        }
        else
        {
            dy = 1024;
        }
        if(x0 <= x1)
        {
            dx = ((x1 - x0 + 1) << 10) / len;
        }
        else
        {
            dx = ((x1 - x0 - 1) << 10) / len;
        }
    }

    for(i = 0; i < len; i++)
    {
        sht->buf[(y >> 10) * sht->bxsize + (x >> 10)] = col;
        x += dx;
        y += dy;
    }
    return;
}

总的来说,就是在变化量较小的方向上变化1个像素。其实图形学的书貌似也有这个算法)
然后是汇编调用

_api_linewin:        ; void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
        PUSH    EDI
        PUSH    ESI
        PUSH    EBP
        PUSH    EBX
        MOV        EDX,13
        MOV        EBX,[ESP+20]    ; win
        MOV        EAX,[ESP+24]    ; x0
        MOV        ECX,[ESP+28]    ; y0
        MOV        ESI,[ESP+32]    ; x1
        MOV        EDI,[ESP+36]    ; y1
        MOV        EBP,[ESP+40]    ; col
        INT        0x40
        POP        EBX
        POP        EBP
        POP        ESI
        POP        EDI
        RET

最后是测试程序

void HariMain(void)
{
    char *buf;
    int win, i;
    api_initmalloc();
    buf = api_malloc(160 * 100);
    win = api_openwin(buf, 160, 100, -1, "lines");
    for(i = 0; i < 8; ++i)
    {
        api_linewin(win + 1,  8, 26, 77, i * 9 + 26, i);
        api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
    }
    api_refreshwin(win,  6, 26, 154, 90);
    api_end();
}

没什么问题。。

关闭窗口

harib20e

虽然可以结束应用程序,但是窗口却没有被释放,所以仍然留在屏幕上。
添加一个关闭窗口的API
关闭窗口
EDX=14
EBX=窗口句柄

case 14:
    sheet_free((struct SHEET *)ebx);
    break;

汇编调用

_api_closewin:        ; void api_closewin(int win);
        PUSH    EBX
        MOV        EDX,14
        MOV        EBX,[ESP+8]    ; win
        INT        0x40
        POP        EBX
        RET

测试程序

void HariMain(void)
{
    char *buf;
    int win, i;
    api_initmalloc();
    buf = api_malloc(160 * 100);
    win = api_openwin(buf, 160, 100, -1, "lines");
    for(i = 0; i < 8; ++i)
    {
        api_linewin(win + 1,  8, 26, 77, i * 9 + 26, i);
        api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
    }
    api_refreshwin(win,  6, 26, 154, 90);
    api_closewin(win);
    api_end();
}

多了一个close,但是这样一开始就被关闭了。。更严格地说,应用程序一开始就结束了,留下了没被释放的窗口在屏幕上。所以我们其实应该等待一个。。输入再结束应用程序

键盘输入API

harib20f

当一个程序等待IO的时候,就让他顺便休眠吧。
键盘输入
EDX=15
EAX=0 没有键盘输入时返回-1,不休眠
EAX=1 休眠直到发生键盘输入
EAX=输入的字符编码

case 15:
    for(;;)
    {
        io_cli();
        if(fifo32_status(&task->fifo) == 0)
        {
            if(eax != 0)
            {
                task_sleep(task);    // FIFO为空,休眠并等待
            }
            else
            {
                io_sti();
                reg[7] = -1;
                return 0;
            }
        }
        i = fifo32_get(&task->fifo);
        io_sti();
        if(i <= 1) // 光标用定时器
        {
            // 应用程序运行时不需要显示光标,因此总是将下次显示用的位置置为1
            timer_init(cons->timer, &task->fifo, 1); // 下次置为1
            timer_settime(cons->timer, 50);
        }
        if(i == 2) // 光标ON
        {
            cons->cur_c = COL8_FFFFFF;
        }
        if(i == 3) // 光标OFF
        {
            cons->cur_c = -1;
        }
        if(256 <= i && i <= 511) // 键盘数据(通过任务A)
        {
            reg[7] = i - 256;
            return 0;
        }
    }
    break;

当接收到键盘数据,或者FIFO为空的时候,会结束掉这个循环。

timer_init(cons->timer, &task->fifo, 1);

这句中,timer加入到了cons里面,因此CONSOLE结构做了修改,同时console里的task的timer就去掉了。
然后是汇编接口

_api_getkey:        ; int api_getkey(int mode);
        MOV        EDX,15
        MOV        EAX,[ESP+4]    ; mode
        INT        0x40
        RET

测试程序

void HariMain(void)
{
    char *buf;
    int win, i;
    api_initmalloc();
    buf = api_malloc(160 * 100);
    win = api_openwin(buf, 160, 100, -1, "lines");
    for(i = 0; i < 8; ++i)
    {
        api_linewin(win + 1,  8, 26, 77, i * 9 + 26, i);
        api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
    }
    api_refreshwin(win,  6, 26, 154, 90);
    for(;;)
    {
        if(api_getkey(1) == 0x0a)
        {
            break; // 按下回车键则break
        }
    }
    api_closewin(win);
    api_end();
}

这样在窗口关闭前,会等待一个enter的输入,之后才会关闭、结束

用键盘输入来消遣一下

harib20g

可以移动的*

void HariMain(void)
{
    char *buf;
    int win, i, x, y;
    api_initmalloc();
    buf = api_malloc(160 * 100);
    win = api_openwin(buf, 160, 100, -1, "walk");
    api_boxfilwin(win, 4, 24, 155, 95, 0 /* 黑色 */);
    x = 76;
    y = 56;
    api_putstrwin(win, x, y, 3 /* 黄色 */, 1, "*");
    for(;;)
    {
        i = api_getkey(1);
        api_putstrwin(win, x, y, 0 /* 黑色 */, 1, "*"); // 用黑色擦除
        if(i == '4' && x >   4) { x -= 8; }
        if(i == '6' && x < 148) { x += 8; }
        if(i == '8' && y >  24) { y -= 8; }
        if(i == '2' && y <  80) { y += 8; }
        if(i == 0x0a) { break; } // 按回车键结束
        api_putstrwin(win, x, y, 3 /* 黄色 */, 1, "*");
    }
    api_closewin(win);
    api_end();
}

强制结束并关闭窗口

harib20h

如果进行强制结束,程序会来不及执行关闭窗口的操作。
给出的一种解法是将图层和任务绑定,当有程序被强制结束时,若有图层是该任务的,则一同关闭。
首先在SHEET的结构体里添加task指针。在SHEET初始化的时候,默认task为0
当API申请图层的时候,将其与任务绑定

case 5:
    sht = sheet_alloc(shtctl);
    sht->task = task;
    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, 100, 50);
    sheet_updown(sht, 3);    // 背景高度3位于task_a之上
    reg[7] = (int)sht;
    break;

在控制台start_app结束之后,增加查找SHEET的代码

shtctl = (struct SHTCTL *)*((int *) 0x0fe4);
for(i = 0; i < MAX_SHEETS; ++i)
{
    sht = &(shtctl->sheets0[i]);
    if(sht->flags != 0 && sht->task == task)
    {
        // 找到被应用程序遗留的窗口
        sheet_free(sht);    // 关闭
    }
}

这样在忘记手动释放窗口的时候,也可以关闭窗口了。