第23天
操作系统的可执行文件中0x0000存放数据段的大小,0x0020位置存放malloc空间的起始地址这两个我们这一节中要用起来。为了能让应用程序自己分配空间而不是一开始就定义一个很大的数组。我们在目标文件进行链接的时候指定操作系统应该为应用程序分配多少空间,这个空间会与应用程序栈的大小相加写入可执行文件的0x0000位置。
我们目前malloc的策略是这样的,生成目标文件的时候指定malloc的大小,并且把起始位置存放在可执行文件的0x0020位置。如果应用程序需要malloc一段内存空间那么调用api_malloc函数,操作系统从本来就分配给应用程序使用的空间中分一部分给应用程序使用。
接下来我们要开始做一系列的画图API了。
我们设计一个在窗口中画点的API
- edx = 11
- ebx = 窗口句柄
- esi = 显示位置的x坐标
- edi = 显示位置的y坐标
- eax = 色号
else if (edx == 11) {
sht = (struct SHEET *) ebx;
sht->buf[sht->bxsize * edi + esi] = eax;
sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
}
画点的api有一个问题,就是每次画一个点都会刷新画面,导致执行效率低下,如果要画很多点,还不如一次把点都画好了,然后刷新一次窗口。我们把所有关于窗口绘图命令设置一个不自动刷新的选项,然后再编写新的用来刷新的API。
刷新窗口这么设计:
- edx = 12
- ebx = 窗口句柄
- eax = x0
- ecx = y0
- esi = x1
- edi = y1
如何设置刷新不刷新的选项呢?之前说过所谓的窗口句柄实际上就是SHEET结构体的指针,肯定是一个偶数也就是说我们可以对窗口句柄与1取与,如果为0则刷新,如果不为0则不刷新。
在窗口上画线API
- edx = 13
- ebx = 窗口句柄
- eax = x0
- ecx = y0
- esi = x1
- edi = y1
- ebp = 色号
现在我们还无法关闭打开的窗口,比如刚才画线的应用程序,当应用程序结束的时候,调用了api_end函数,然后返回了命令行窗口,但是窗口还是显示在屏幕上,并没有消失。我们为留在画面上的窗口分配了应用程序的空间作为存放图层的内存空间,但是当应用程序结束之后,其数据段的内存空间就被释放,拱操作系统及其他应用程序来使用,因此我们在结束应用程序之前应该先关闭窗口。
设计一下关闭窗口的api
- edx = 14
- ebx = 窗口句柄
else if (edx == 14) {
sheet_free((struct SHEET *) ebx);
}
API是很简单,只要在api_end之前调用api_closewin窗口就会关闭。
但是如果不让应用程序接受键盘输入,那么应用程序的窗口只会有屏幕上一闪而过。接下来写键盘输入的API
- edx = 15
- eax = 0 没有键盘输入时返回-1, 不休眠;1休眠直到发生键盘输入
- eax = 输入的字符编码
这个API接收一个参数,如果参数为0,表示马上查询现在是否有键盘输入,如果没有则返回-1,不休眠。如果参数为1,则表示如果没有键盘输入那么任务将体眠,直到有键盘输入后返回键盘输入值。
else if (edx == 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;
}
}
}
在窗口还没有关闭的时候,命令行窗口的光标不需要闪烁。实现这个功能的思路是这样:我们用定时器控制光标的闪烁,定时器超时产生中断如果消息队列接收的消息是0,则将光标置为白色,再设置定时器,接收到的消息为1.当接收到消息1时,则将光标置为黑色,再设置定时器,接收到的消息为0。当暂时不需要光标闪烁时,可以将定时器接收到的数据全部置为1。
顺便看下应用程序如何写:
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);
void api_closewin(int win);
int api_getkey(int mode);
void api_end(void);
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();
}
之前我们写了按下shift + f1会强会关闭应用程序的功能。这段功能是在task_a任务中实现的的
if (i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0) { /* Shift+F1 */
cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nBreak(key) :\n");
io_cli();
task_cons->tss.eax = (int) &(task_cons->tss.esp0);
task_cons->tss.eip = (int) asm_end_app;
io_sti();
}
也就是说强制强束应用程序的时候只是把task_console任务重新回到应用程序结束之前的寄存器状态。内存空间已经被收回,但是窗口却还在屏幕上。
我们可以在SHEET结构体中添加一个用来存放task的成员变量,当应用程序结束时,查询所有的图层,如果图层的task为将要结束的应用程序任务时,则关闭该图层。
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);
}
}