第24天 窗口操作
2020.4.29
1. 窗口切换(1)(harib21a)
-
实现能够切换窗口顺序的功能,首先实现从键盘切换的方法:当按下F11时,将最下面的窗口放到最上面。
-
F11的按键编码是
0x57
,F12的按键编码是0x58
。 -
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… if (i == 256 + 0x57 && shtctl->top > 2) { /* F11 */ sheet_updown(shtctl->sheets[1], shtctl->top - 1); } …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ …… } else if (i <= 1) { /* 光标定时器 */ …… } } } }
- 其实就添加了三行代码。
- 核心代码
sheet_updown(shtctl->sheets[1], shtctl->top - 1);
的意思是:将高度为1的图层(从下面数第2个图层,第1个图层高度为0是背景图层)的高度提升到top-1(从上面数第2个图层,第1个数鼠标图层)。
-
make
后用VMware运行:
2. 窗口切换(2)(harib21b)
-
在harib21a中实现了使用键盘切换窗口的功能,现在实现使用鼠标进行窗口图层切换的功能。
-
首先,现需要将鼠标左键按下后移动task_a窗口的代码删除。使用鼠标移动窗口的功能很快就会实现。
-
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { /* 鼠标指针移动 */ …… if ((mdec.btn & 0x01) != 0) { /* 鼠标左键按下 */ /* 按照从上到下的顺序寻找鼠标所指向的图层 */ for (j = shtctl->top - 1; j > 0; j--) { sht = shtctl->sheets[j]; x = mx - sht->vx0; /*x是相对于图层的X方向坐标*/ y = my - sht->vy0; /*y是相对于图层的Y方向坐标*/ if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { /*鼠标位置(mx, my)位于图层内*/ if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { /*鼠标位置所在的图层的像素不是透明*/ sheet_updown(sht, shtctl->top - 1); /*将当前图层高度提升至top-1高度*/ break; /*终止遍历*/ } } } } } } else if (i <= 1) { /* 光标定时器 */ …… } } } }
- 确定鼠标点击的是哪个图层的方法:按照图层高度,从上到下,判断鼠标位置位于哪个图层的范围内并且保证该位置不是透明色。
-
make
后用VMware运行:
- 如果命令行在最上面的话,由于现在还不能移动图层,因此会遮挡别的窗口,可以使用F11将遮住的图层显示出来。
3. 移动窗口(harib21c)
-
实现窗口移动的功能:
- 当鼠标左键点击窗口时,如果点击位置位于窗口的标题栏区域,则进入
窗口移动模式
,使窗口的位置随着鼠标而移动,当松开鼠标左键的时候,退出窗口移动模式
,返回通常模式。
- 当鼠标左键点击窗口时,如果点击位置位于窗口的标题栏区域,则进入
-
修改HariMain:
void HariMain(void) { …… int j, x, y, mmx = -1, mmy = -1; struct SHEET *sht = 0; …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { /* 鼠标移动 */ …… if ((mdec.btn & 0x01) != 0) { /* 按下左键 */ if (mmx < 0) { /* 如果处于通常模式 */ /* 按从上到下的顺序寻找鼠标所指向的图层 */ for (j = shtctl->top - 1; j > 0; j--) { sht = shtctl->sheets[j]; x = mx - sht->vx0; y = my - sht->vy0; if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { sheet_updown(sht, shtctl->top - 1); if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { /*如果鼠标位置位于标题栏*/ mmx = mx; /* 进入窗口移动模式 */ mmy = my; /* 记录当前鼠标位置 */ } break; } } } } else { /* 如果处于窗口移动模式 */ x = mx - mmx; /* 计算鼠标的移动距离 */ y = my - mmy; sheet_slide(sht, sht->vx0 + x, sht->vy0 + y); /*相应地移动图层*/ mmx = mx; /* 更新mmx,记录当前鼠标位置 */ mmy = my; } } else { /* 没有按下左键 */ mmx = -1; /* 返回通常模式 */ } } } else if (i <= 1) { /* 光标定时器 */ …… } } } }
- mmx和mmy中的mm是
move mode
的缩写,这两个坐标记录上一次鼠标的位置,以计算鼠标的位移。规定,当mmx<0时,当前模式不是窗口移动模式。
- mmx和mmy中的mm是
-
make
后用VMware运行:
- 还不错哦。
4. 用鼠标关闭窗口(harib21d)
-
以前结束应用程序(关闭窗口)的方式是应用程序本身接收键盘数据(回车键)或者Shift+F1。
-
现在使用鼠标关闭应用程序窗口,关闭了窗口并关闭应用程序本身。注意,现在还不能关闭任务,比如命令行还不可以关闭。
-
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { /* 鼠标移动 */ …… if ((mdec.btn & 0x01) != 0) { /* 按下左键 */ if (mmx < 0) { /* 如果处于通常模式 */ /* 按照从上到下的顺序寻找鼠标指向的图层 */ for (j = shtctl->top - 1; j > 0; j--) { sht = shtctl->sheets[j]; x = mx - sht->vx0; y = my - sht->vy0; if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { sheet_updown(sht, shtctl->top - 1); if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { mmx = mx; mmy = my; } if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { /* 点击“X”按钮 */ if (sht->task != 0) { /* 判断该窗口是否为应用程序窗口 */ cons = (struct CONSOLE *) *((int *) 0x0fec); cons_putstr0(cons, "\nBreak(mouse) :\n"); io_cli(); /* 强制结束应用程序时禁止中断 */ task_cons->tss.eax = (int) &(task_cons->tss.esp0); task_cons->tss.eip = (int) asm_end_app; io_sti(); } } break; } } } } else { /* 如果处于窗口移动模式 */ …… } } else { /* 没有按下左键 */ mmx = -1; } } } else if (i <= 1) { /* 光标定时器 */ …… } } } }
sht->task != 0
:图层的task成员如果是0,代表这个图层是任务;如果是非0,代表这个图层是由应用程序产生的。- 判断鼠标是否点击“X”的方法和判断鼠标是否点击标题栏是一样的。
- 点击“X”之后的程序结束处理和Shift+F1时的一样:
io_cli(); /* 强制结束应用程序时禁止中断 */ task_cons->tss.eax = (int) &(task_cons->tss.esp0); task_cons->tss.eip = (int) asm_end_app; io_sti();
- 将命令行任务的tts.eax赋值tss.esp0,并将eip赋值asm_end_app,即执行asm_end_app。
- asm_end_app:
_asm_end_app: ; EAX存放着栈顶指针 MOV ESP,[EAX] MOV DWORD [EAX+4],0 ;ss0=0 POPAD RET ; 返回cmd_app
- RET是根据EIP跳转的,由于事先保存了EIP,所以会调回到cmd_app。
-
make
后用VMware运行:
5. 将输入切换到应用程序窗口(harib21e)
-
在harib21d中运行walk.hrb应用程序,让应用程序接收键盘输入,此时处于接收状态的是命令行窗口,而不是应用程序窗口。
- 应该让应用程序窗口处于输入状态,然后再进行
*
的移动。
- 应该让应用程序窗口处于输入状态,然后再进行
-
在这之前,使用Tab键在console和task_a之间切换输入状态,这时只有两个图层。但现在会有三个及以上图层进行输入状态的切换。
- 按下Tab键时,需要判断切换到了哪个窗口。
- 规定:按下Tab键时,将键盘输入切换到当前输入窗口的下一层窗口中,当当前窗口为最下层,切换到最上层窗口。
-
之前是使用key_to这个变量判别当前输入窗口时console还是task_a。显然现在无法使用key_to变量了。定义一个变量key_win,用于存放当前处于输入模式的窗口的地址。
-
注意:当当前处于输入状态的窗口被关闭后,规定让OS将最上层的图层变成输入状态。
-
修改HariMain:
void HariMain(void) { …… struct SHEET *sht = 0, *key_win; …… key_win = sht_win; sht_cons->task = task_cons; sht_cons->flags |= 0x20; /* 有光标 */ …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { i = fifo32_get(&fifo); io_sti(); if (key_win->flags == 0) { /* 输入窗口被关闭 */ key_win = shtctl->sheets[shtctl->top - 1]; /*将输入窗口改成最上层(除鼠标图层)*/ cursor_c = keywin_on(key_win, sht_win, cursor_c); } if (256 <= i && i <= 511) { /* 键盘数据 */ …… if (s[0] != 0) { /* 一般字符 */ if (key_win == sht_win) { /* 发送到任务A */ if (cursor_x < 128) { /* 显示一个字符并将光标后移一位 */ s[1] = 0; putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1); cursor_x += 8; } } else { /* 发送到命令行窗口 */ fifo32_put(&key_win->task->fifo, s[0] + 256); } } if (i == 256 + 0x0e) { /* 退格键 */ if (key_win == sht_win) { /* 发送到任务A */ if (cursor_x > 8) { /* 用空格擦除光标后,光标前移一位 */ putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1); cursor_x -= 8; } } else { /* 发送到命令行窗口 */ fifo32_put(&key_win->task->fifo, 8 + 256); } } if (i == 256 + 0x1c) { /* Enter */ if (key_win != sht_win) { /* 发送到命令行窗口 */ fifo32_put(&key_win->task->fifo, 10 + 256); } } if (i == 256 + 0x0f) { /* Tab */ cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x); j = key_win->height - 1; if (j == 0) { j = shtctl->top - 1; } key_win = shtctl->sheets[j]; cursor_c = keywin_on(key_win, sht_win, cursor_c); } …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { /* 按下左键 */ if (mmx < 0) { /* 如果处于通常模式 */ /* 按照从上到下的顺序寻找鼠标所指向的图层 */ for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { /* 点击“X”按钮 */ if ((sht->flags & 0x10) != 0) { /* 判断窗口是否是应用程序的窗口 */ …… } } break; } } } } else { …… } } else { …… } } } else if (i <= 1) { /* 光标定时器 */ …… } } } }
- 结构体SHEET的成员task已经不再是原先的含义了,而是用来判断数据发送对象的FIFO。比如,如果发送给应用程序,那么应用程序图层的task指向命令行任务task_console;如果发送给命令行,那么命令行图层的task指向命令行任务task_console。注意:如果发送给任务A,那么可以不让任务A图层的task指向任务A,因为任务A的FIFO是直接从中断哪里获取数据,发送给A的数据可以直接在HariMain中使用(因为,如果发送给A,当前输入状态的是图层A,那么直接使用就行了)。
- 图层的成员task的含义已经发生了变化,现在已经不能根据task是否为0来判断图层是否是应用程序产生的了。
- 借助SHEET结构中的flags标志,规定,flags的第5位是1:代表是应用程序的图层;是0代表不是应用程序的图层。
- 关于光标显示问题:比如,task_a和console图层需要光标,而walk应用程序则不需要光标。也就是说,只有命令行窗口需要控制光标ON/OFF,应用程序窗口不需要。
- 同理,借助flags也可以进行判断,规定,flags的第6位是1:代表需要控制光标;是0不需要控制光标。
- SHEET结构体flags详解:
- flags的第1位:
- 0:该图层未被使用
- 1:该图层被使用
- flags的第5位:
- 0:该图层不是应用程序的图层
- 1:该图层是应用程序的图层
- flags的第6位:
- 0:该图层不需要控制光标
- 1:该图层需要控制光标
- flags的第1位:
- keywin_on和keywin_off函数(bootpack.c中)
int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x) { change_wtitle8(key_win, 0); if (key_win == sht_win) { /*当前处于输入状态的图层是task_a的图层*/ cur_c = -1; /* task_a光标OFF */ boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43); } else { if ((key_win->flags & 0x20) != 0) { /*该图层需要控制光标,即是命令行图层*/ fifo32_put(&key_win->task->fifo, 3); /* 命令行光标OFF */ } } return cur_c; } int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c) { change_wtitle8(key_win, 1); if (key_win == sht_win) { cur_c = COL8_000000; /* task_a光标颜色 */ } else { if ((key_win->flags & 0x20) != 0) { fifo32_put(&key_win->task->fifo, 2); /* 命令行光标ON */ } } return cur_c; }
- keywin_on和keywin_off的功能是控制标题栏的颜色和命令行已经task_a的光标显示。
- 控制标题栏颜色的功能是由change_wtitle8函数实现的。
- 编写change_wtitle8函数(window.c):
void change_wtitle8(struct SHEET *sht, char act) { int x, y, xsize = sht->bxsize; char c, tc_new, tbc_new, tc_old, tbc_old, *buf = sht->buf; if (act != 0) { tc_new = COL8_FFFFFF; tbc_new = COL8_000084; tc_old = COL8_C6C6C6; tbc_old = COL8_848484; } else { tc_new = COL8_C6C6C6; tbc_new = COL8_848484; tc_old = COL8_FFFFFF; tbc_old = COL8_000084; } for (y = 3; y <= 20; y++) { for (x = 3; x <= xsize - 4; x++) { c = buf[y * xsize + x]; if (c == tc_old && x <= xsize - 22) { /*c是字体色*/ c = tc_new; } else if (c == tbc_old) { /*c是背景色*/ c = tbc_new; } buf[y * xsize + x] = c; } } sheet_refresh(sht, 3, 3, xsize, 21); return; }
- 其实,make_wtitle8函数也实现了相同的功能,但是change_wtitle8即便不知道窗口的名称也能改变标题栏的颜色(make是制作,change是改变)。
-
修改cmd_app:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… if (finfo != 0) { /* 文件找到 */ …… if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) { …… for (i = 0; i < MAX_SHEETS; i++) { sht = &(shtctl->sheets0[i]); if ((sht->flags & 0x11) == 0x11 && sht->task == task) { /* 找到应用程序遗留的窗口 */ sheet_free(sht); /* 关闭 */ } } memman_free_4k(memman, (int) q, segsiz); } else { cons_putstr0(cons, ".hrb file format error.\n"); } memman_free_4k(memman, (int) p, finfo->size); cons_newline(cons); return 1; } /* 未找到文件 */ return 0; }
- 主要修改的代码:
if ((sht->flags & 0x11) == 0x11 && sht->task == task)
- 0x11,即flags的第1位和第5位都是1,那么代表当前图层是应用程序产生的图层且当前图层正在被使用。
- 主要修改的代码:
-
修改hrb_api的5号API(图层API):
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… struct TASK *task = task_now(); …… } else if (edx == 5) { sht = sheet_alloc(shtctl); sht->task = task; sht->flags |= 0x10; /*第5位是1,标志着这是应用程序的图层*/ 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); reg[7] = (int) sht; } else if (edx == 6) { …… }
-
make
后用VMware运行:
6. 用鼠标切换输入窗口(harib21f)
-
harib21e实现了用Tab键切换输入窗口。现在使用鼠标切换输入窗口。
- 在Windows中,只要鼠标在窗口上点击一下,那个窗口就会被切换到屏幕的最上方,而且键盘输入也会自动切换到该窗口。
- 已经实现了点击窗口该窗口就会切换到最高的图层,那么只要实现鼠标点击窗口,该窗口自动切换到输入状态即可。
-
修改HariMain:
void HariMain(void) { …… struct SHEET *sht = 0, *key_win; …… key_win = sht_win; sht_cons->task = task_cons; sht_cons->flags |= 0x20; /* 有光标 */ …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { /* 按下左键 */ if (mmx < 0) { /* 如果处于通常模式 */ /* 按照从上到下的顺序寻找鼠标所指向的图层 */ for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { sheet_updown(sht, shtctl->top - 1); /*置顶*/ if (sht != key_win) { /*遍历到的图层不是当前处于输入状态的图层*/ cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x); key_win = sht; /*切换*/ cursor_c = keywin_on(key_win, sht_win, cursor_c); } if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { mmx = mx; /* 进入窗口移动模式 */ mmy = my; } if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { /* 点击“X”按钮 */ …… } break; } } } } else { …… } } else { …… } } } else if (i <= 1) { /* 光标定时器 */ …… } } } }
- 鼠标操作切换输入窗口和键盘操作切换输入窗口差不多。
-
make
后VMware运行:
- 完美实现!
7. 定时器API(harib21g)
-
让应用程序也可以使用定时器。因而设计一个定时器APIs:
- 获取定时器(alloc)的API:
- EDX = 16
- 返回值EAX = 定时器句柄(由OS返回给应用程序)
- 设置定时器发送数据(init)的API:
- EDX = 17
- EBX = 定时器句柄
- EAX = 数据
- 定时器时间设置(set)的API:
- EDX = 18
- EBX = 定时器句柄
- EAX = 时间
- 释放定时器(free)的API:
- EDX = 19
- EBX = 定时器句柄
- 获取定时器(alloc)的API:
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 15) { for (;;) { …… if (i >= 256) { /* 键盘数据 */ reg[7] = i - 256; return 0; } } } else if (edx == 16) { reg[7] = (int) timer_alloc(); } else if (edx == 17) { timer_init((struct TIMER *) ebx, &task->fifo, eax + 256); } else if (edx == 18) { timer_settime((struct TIMER *) ebx, eax); } else if (edx == 19) { timer_free((struct TIMER *) ebx); } return 0; }
- 之前的时候,在15号API中(api_getkey),通过任务A获得的数据是
字符编码+256
。因此i-256就是数据的字符编码。现在,通过任务A获得的数据还包括定时器超时的数据,因此i>=256即可。 - 在17号API中,定时器的数据是通过eax传递进来的,将其+256,这样产生定时超时中断发送给缓冲区的数据就是eax+256,再减去256,就变成了原来的数据。
- 17号API中,指定了定时器的缓冲区!
- 之前的时候,在15号API中(api_getkey),通过任务A获得的数据是
-
编写可以供C语言调用API的函数:
_api_alloctimer: ; int api_alloctimer(void); MOV EDX,16 INT 0x40 RET _api_inittimer: ; void api_inittimer(int timer, int data); PUSH EBX MOV EDX,17 MOV EBX,[ESP+ 8] ; timer MOV EAX,[ESP+12] ; data INT 0x40 POP EBX RET _api_settimer: ; void api_settimer(int timer, int time); PUSH EBX MOV EDX,18 MOV EBX,[ESP+ 8] ; timer MOV EAX,[ESP+12] ; time INT 0x40 POP EBX RET _api_freetimer: ; void api_freetimer(int timer); PUSH EBX MOV EDX,19 MOV EBX,[ESP+ 8] ; timer INT 0x40 POP EBX RET
- api_alloctimer是调用16号API的函数。分配定时器。
- api_inittimer是调用17号API的函数。设置当定时器发生超时中断时,向FIFO缓冲区发送的数据。
- api_settimer是调用18号API的函数。设置定时器的时间。
- api_freetimer是调用19号API的函数。删除定时器。
-
编写应用程序noodle.c:
#include <stdio.h> int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title); void api_putstrwin(int win, int x, int y, int col, int len, char *str); void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col); void api_initmalloc(void); char *api_malloc(int size); 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_end(void); void HariMain(void) { char *buf, s[12]; int win, timer, sec = 0, min = 0, hou = 0; api_initmalloc(); buf = api_malloc(150 * 50); win = api_openwin(buf, 150, 50, -1, "noodle"); timer = api_alloctimer(); api_inittimer(timer, 128); for (;;) { sprintf(s, "%5d:%02d:%02d", hou, min, sec); api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色 */); api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s); api_settimer(timer, 100); /* 1s */ if (api_getkey(1) != 128) { /*不是定时器超时发送来的数据,就退出*/ break; } sec++; if (sec == 60) { sec = 0; min++; if (min == 60) { min = 0; hou++; } } } api_end(); }
- 如果从键盘获取的数据不是128,那么一定是用户按了回车键或者其他的键,这时应用程序退出。
-
make
后用VMware运行:
- 时间过得真快啊,转眼间四分钟过去了(笑)。
8. 取消定时器(harib21h)
-
在harib21g中,当结束noodle.hrb后(不论是按Enter或者鼠标点击“X”),命令行会出现一个神秘的字符(不是字符C哦)。这是我们不希望出现的。
- 应用程序设置了一个1s定时器,每个1s就会产生一次超时中断并向缓冲区发送事先设置的数据。如果应用程序结束了,定时器的数据就会发送到命令行窗口。
- 查看console_task中的代码,发现只要数据介于256和511之间,如果数据不是退格键和Enter,那么就往命令行上写入。
- 在harib21g中,数据是128,因此当应用程序结束以后,定时器产生的数据会发送到命令行窗口,命令行接收到128+256,刚好介于范围内,所以就会出现
128
这个什么字符。 - 解决方法有2种:
- 其一,数据设成大于512的数字(治标不治本)。
- 其二,取消定时器(治标治本)。
-
编写用于取消定时器的函数timer_cancel(timer.c):
int timer_cancel(struct TIMER *timer) { int e; struct TIMER *t; e = io_load_eflags(); io_cli(); /* 在设置定时器的过程中禁止中断 */ if (timer->flags == TIMER_FLAGS_USING) { /* 只有定时器正在使用才能被取消 */ if (timer == timerctl.t0) { /* 当要取消的定时器是第一个定时器 */ t = timer->next; timerctl.t0 = t; timerctl.next = t->timeout; } else { /* 非第一个定时器的取消处理 */ /* 找到timer前的定时器 */ t = timerctl.t0; for (;;) { if (t->next == timer) { break; } t = t->next; } t->next = timer->next; /* 更新next */ } timer->flags = TIMER_FLAGS_ALLOC; io_store_eflags(e); return 1; /* 取消处理成功 */ } io_store_eflags(e); return 0; /* 取消处理失败,即定时器不需要取消 */ }
- 回顾:timer们的管理使用了链表数据结构+哨兵。
-
修改TIMER结构体:
struct TIMER { struct TIMER *next; unsigned int timeout; char flags, flags2; struct FIFO32 *fifo; int data; };
- 新增了flags2变量,用于标记该定时器是否需要在应用程序结束后自动取消。
- 这样做是为了不取消光标定时器。
- flags2 =
- 0:不自动取消
- 1:自动取消
- 通常,flags2 = 0。
- 新增了flags2变量,用于标记该定时器是否需要在应用程序结束后自动取消。
-
修改timer_alloc函数:
struct TIMER *timer_alloc(void) { int i; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timers0[i].flags == 0) { timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC; timerctl.timers0[i].flags2 = 0; /*通常情况下,定时器不自动取消*/ return &timerctl.timers0[i]; } } return 0; }
-
将应用程序申请的定时器的flags2标志设置为1,修改hrb_api中的16号API:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 16) { reg[7] = (int) timer_alloc(); ((struct TIMER *) reg[7])->flags2 = 1; /* 自动取消 */ } else if (edx == 17) { …… }
- reg[7]是eax,是返回值,是定时器句柄。
-
修改cmd_app,应用程序结束后取消不需要的定时器:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline) { …… if (finfo != 0) { …… if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) { …… timer_cancelall(&task->fifo); /*取消不需要的定时器*/ memman_free_4k(memman, (int) q, segsiz); } else { cons_putstr0(cons, ".hrb file format error.\n"); } memman_free_4k(memman, (int) p, finfo->size); cons_newline(cons); return 1; } return 0; }
- 编写timer_cancelall函数(timer.c中):
void timer_cancelall(struct FIFO32 *fifo) { int e, i; struct TIMER *t; e = io_load_eflags(); io_cli(); /* 中断禁止 */ for (i = 0; i < MAX_TIMER; i++) { t = &timerctl.timers0[i]; if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) { timer_cancel(t); timer_free(t); } } io_store_eflags(e); return; }
- 参数是缓冲区指针,因为每一个定时器都有缓冲区,根据传进来的参数,判断定时器是否属于该缓冲区。如果是,再判断定时是否正在运行和flags2标志。
- 如果是,取消定时器,并释放之。
- 编写timer_cancelall函数(timer.c中):
-
make
后用VMware运行:- 重新运行noodle.hrb并关闭之:
- 没出现神秘字符!
- 重新运行noodle.hrb并关闭之:
9. 写在14:36
- 现在是2020.4.30 14:36,结束了第24天的内容,超过了500页。
- 现在外面天气很好,阳光明媚,万里无云。
- 写文档写的右手中指关节痛。可能压力太大,今天食欲不振,中午只吃了一个小包子。
- 现在,为了让自己精神贯注,已经开始借助茶叶了。
- 现在开始第25天的内容吧,明天是五一劳动节,还是不给自己放假了吧。
- 兴趣加专注,方能不断进取!