java开发系统内核:实现进程优先级队列
2017-01-19 11:06
585 查看
更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核
前几节,我们实现了进程的调度,并给每个进程赋予相应优先级,优先级高的进程,能够得到更多的CPU时间。但在演示时,我们发现一个问题,就是,当进程发送切换时,鼠标键盘的响应会被卡死,这是因为响应鼠标键盘事件的进程被调到后台,无法获得CPU运行时间,从而不能处理鼠标或键盘事件。
这种情况对用户来说,是不可接受的。对用户输入给予及时回应,直接关系到用户体验。所以负责相应用户输入的进程,其重要性要比那些只需要在后台运行,不用和用户直接交互的进程高很多。由此,我们需要实现的是,只要用户有输入,那么负责处理用户输入的进程就不能被打断,为了实现这个目的,我们需要实现进程的优先级队列。
如上图,我们把所有进程根据level分成三个队列,进程越重要,它对应的level值越低,这样,当进行进程调度时,进程调度器先查看level等于0的队列是否还有进程,有的话,只执行该队列的进程,如果该队列不止一个进程,那么每个进程根据他们的优先级获得相应的CPU时间。
如果levelw为0的队列没有可调度的进程,那么level为1的队列中的进程才有获得调度的机会,以此类推。
由此,我们看看相关数据结构的定义,首先是multi_task.h:
TASK结构体增加了一个level变量,用于表明进程的重要性。TASKLEVEL对应于上图的进程队列,相同重要性的进程,都存储在对应TASKLEVEL的tasks数组中。
TASKCTL 也就是进程管理器,其存储的不再是进程,而是进程的优先级队列,它要找到重要性最高的进程队列,从中取出进程进行调度。
multi_task.c 需要进行相应改动:
init_task_level 对进程优先级队列进行初始化,running表示改对了中有几个进程,now表示队列中,哪个进程正在被调度到前台进行运行。
上面代码用于初始化运行CMain函数的进程,它把CMain进程的level设置为0,也就是该进程的重要性最高,只要它不被挂起,那么它始终拥有被调度的权利。task_add会根据进程的重要性,将其加入对应的优先级队列,一旦有新进程加入,task_switchsub 则修改相应调度信息,以便进程调度器做出适当调动。
task_run 的作用是修改进程重要性或优先级,或者把新的进程根据其重要性添加到相应队列。如果进程的重要性有改动,那么通过task_remove把进程从原有的优先级队列中去除,然后再通过task_add将进程添加到对应的队列。
task_switch 被时钟中断调用,它的逻辑发送不小变化。taskctl->now_lv表示当前正则被调度的优先级队列,例如,如果now_lv的值是0,那么表示当前level等于0的队列中的进程才可以获得被调度的机会。task_switch 通过taskctl->now_lv得知哪个进程队列应该被调度,然后从该队列中,取出task对象进行执行。如果有新的进程加入,或有进程的重要性被改变了,那么taskctl->lv_change的值就不等于0。假设当前获得调度机会的是level值为1的队列中的进程,但是有level等于0的进程对象添加到了level等于0的队列中,那么此时,调度算法就会停止从level为1的队列中去调度进程,而是切换到level为0的队列,从中获取要调度的进程。
task_sleep 用于删除进程,如果需要杀掉某个进程,那么可以使用该函数剥夺指定进程对象获得调度的权利。task_remove负责把进程从它所在的队列中删除,如果当前被挂起的进程是正在运行的进程,那么task_sleep会选择下一个合适的进程进行调度执行。
task_now 用于返回当前正在被调用的进程对象,task_add把给定进程加入对应的优先级队列。
task_remove主要负责把进程对象从队列中删除,并修改相应的队列数据。一旦有进程删除或添加,那么进程调度需要作出对应的调整,task_switchsub的作用是根据进程的添加或删除,修改进程调度器的相应信息,进而改变进程调度器的调度行为。
最后是主入口函数CMain也做相应改动:
主要到,我们把运行两个窗体的进程其重要性设置成1,也就是只要运行主入口函数的进程不挂起,那么运行两个窗体的进程就不能得到执行。
本节代码改动虽多,但逻辑简单,理解起来应该不难,经过上面的改动后,系统运行的情况如下:
从上图看出,如果CMain进程运行时,上面两个窗体原来的计数字符串没有显示,这是因为两个窗体的进程得不到调度的机会。由于CMain进程负责相应鼠标键盘,因此,此时我们移动鼠标就不再出现卡顿的情形。
当CMain进程被挂起后,两个窗体的进程获得执行机会,因而窗体中的计算字符串就出现了:
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
Linux kernel Hacker, 从零构建自己的内核
前几节,我们实现了进程的调度,并给每个进程赋予相应优先级,优先级高的进程,能够得到更多的CPU时间。但在演示时,我们发现一个问题,就是,当进程发送切换时,鼠标键盘的响应会被卡死,这是因为响应鼠标键盘事件的进程被调到后台,无法获得CPU运行时间,从而不能处理鼠标或键盘事件。
这种情况对用户来说,是不可接受的。对用户输入给予及时回应,直接关系到用户体验。所以负责相应用户输入的进程,其重要性要比那些只需要在后台运行,不用和用户直接交互的进程高很多。由此,我们需要实现的是,只要用户有输入,那么负责处理用户输入的进程就不能被打断,为了实现这个目的,我们需要实现进程的优先级队列。
如上图,我们把所有进程根据level分成三个队列,进程越重要,它对应的level值越低,这样,当进行进程调度时,进程调度器先查看level等于0的队列是否还有进程,有的话,只执行该队列的进程,如果该队列不止一个进程,那么每个进程根据他们的优先级获得相应的CPU时间。
如果levelw为0的队列没有可调度的进程,那么level为1的队列中的进程才有获得调度的机会,以此类推。
由此,我们看看相关数据结构的定义,首先是multi_task.h:
struct TASK { int sel, flags; int priority; int level; struct TSS32 tss; }; #define MAX_TASKS 5 #define MAX_TASKS_LV 3 #define MAX_TASKLEVELS 3 #define TASK_GDT0 7 #define SIZE_OF_TASK 120 struct TASKLEVEL { int running; int now; struct TASK *tasks[MAX_TASKS_LV]; }; #define SIZE_OF_TASKLEVEL (8+ 4*MAX_TASKS_LV) struct TASKCTL { int now_lv; int lv_change; struct TASKLEVEL level[MAX_TASKLEVELS]; struct TASK tasks0[MAX_TASKS]; };
TASK结构体增加了一个level变量,用于表明进程的重要性。TASKLEVEL对应于上图的进程队列,相同重要性的进程,都存储在对应TASKLEVEL的tasks数组中。
TASKCTL 也就是进程管理器,其存储的不再是进程,而是进程的优先级队列,它要找到重要性最高的进程队列,从中取出进程进行调度。
multi_task.c 需要进行相应改动:
struct TASKCTL *get_taskctl() { return taskctl; } void init_task_level(int level) { taskctl->level[level].running = 0; taskctl->level[level].now = 0; int i; for (i = 0; i < MAX_TASKS_LV; i++) { taskctl->level[i].tasks[i] = 0; } }
init_task_level 对进程优先级队列进行初始化,running表示改对了中有几个进程,now表示队列中,哪个进程正在被调度到前台进行运行。
struct TASK *task_init(struct MEMMAN *memman) { int i; .... task = task_alloc(); task->flags = 2; //active task->priority = 100; task->level = 0; task_add(task); task_switchsub(); .... }
上面代码用于初始化运行CMain函数的进程,它把CMain进程的level设置为0,也就是该进程的重要性最高,只要它不被挂起,那么它始终拥有被调度的权利。task_add会根据进程的重要性,将其加入对应的优先级队列,一旦有新进程加入,task_switchsub 则修改相应调度信息,以便进程调度器做出适当调动。
void task_run(struct TASK *task,int level, int priority) { if (level < 0) { level = task->level; } if (priority > 0) { task->priority = priority; } if (task->flags == 2 && task->level != level) { task_remove(task); //change task flags } if (task->flags != 2) { task->level = level; task_add(task); } taskctl->lv_change = 1; return; }
task_run 的作用是修改进程重要性或优先级,或者把新的进程根据其重要性添加到相应队列。如果进程的重要性有改动,那么通过task_remove把进程从原有的优先级队列中去除,然后再通过task_add将进程添加到对应的队列。
void task_switch(void) { struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv]; struct TASK *new_task, *now_task = tl->tasks[tl->now]; tl->now++; if (tl->now == tl->running) { tl->now = 0; } if (taskctl->lv_change != 0) { task_switchsub(); tl = &taskctl->level[taskctl->now_lv]; } new_task = tl->tasks[tl->now]; timer_settime(task_timer, new_task->priority); if (new_task != now_task && new_task != 0) { farjmp(0, new_task->sel); } return; }
task_switch 被时钟中断调用,它的逻辑发送不小变化。taskctl->now_lv表示当前正则被调度的优先级队列,例如,如果now_lv的值是0,那么表示当前level等于0的队列中的进程才可以获得被调度的机会。task_switch 通过taskctl->now_lv得知哪个进程队列应该被调度,然后从该队列中,取出task对象进行执行。如果有新的进程加入,或有进程的重要性被改变了,那么taskctl->lv_change的值就不等于0。假设当前获得调度机会的是level值为1的队列中的进程,但是有level等于0的进程对象添加到了level等于0的队列中,那么此时,调度算法就会停止从level为1的队列中去调度进程,而是切换到level为0的队列,从中获取要调度的进程。
int task_sleep(struct TASK *task) { struct TASK *cur_task = 0; if (task->flags == 2) { cur_task = task_now(); task_remove(task); if (task == cur_task) { task_switchsub(); cur_task = task_now(); if (cur_task != 0) { farjmp(0, cur_task->sel); } } } return 0; } struct TASK *task_now(void) { struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv]; return tl->tasks[tl->now]; } void task_add(struct TASK *task) { struct TASKLEVEL *tl = &taskctl->level[task->level]; tl->tasks[tl->running] = task; tl->running++; task->flags = 2; return; }
task_sleep 用于删除进程,如果需要杀掉某个进程,那么可以使用该函数剥夺指定进程对象获得调度的权利。task_remove负责把进程从它所在的队列中删除,如果当前被挂起的进程是正在运行的进程,那么task_sleep会选择下一个合适的进程进行调度执行。
task_now 用于返回当前正在被调用的进程对象,task_add把给定进程加入对应的优先级队列。
void task_remove(struct TASK *task) { int i ; struct TASKLEVEL *tl = &taskctl->level[task->level]; for (i = 0; i< tl->running; i++) { if (tl->tasks[i] == task) { tl->tasks[i] = 0; break; } } tl->running--; if (i < tl->now) { tl->now--; } if (tl->now >= tl->running) { tl->now = 0; } task->flags = 1; for (; i < tl->running; i++) { tl->tasks[i] = tl->tasks[i+1]; } return; } void task_switchsub(void) { int i; for (i = 0; i < MAX_TASKLEVELS; i++) { if (taskctl->level[i].running > 0) { break; } } taskctl->now_lv = i; taskctl->lv_change = 0; }
task_remove主要负责把进程对象从队列中删除,并修改相应的队列数据。一旦有进程删除或添加,那么进程调度需要作出对应的调整,task_switchsub的作用是根据进程的添加或删除,修改进程调度器的相应信息,进而改变进程调度器的调度行为。
最后是主入口函数CMain也做相应改动:
void CMain(void) { .... for (i = 0; i < 2; i++) { .... .... task_run(task_b[i], 1, (i+1)*5); } .... }
主要到,我们把运行两个窗体的进程其重要性设置成1,也就是只要运行主入口函数的进程不挂起,那么运行两个窗体的进程就不能得到执行。
本节代码改动虽多,但逻辑简单,理解起来应该不难,经过上面的改动后,系统运行的情况如下:
从上图看出,如果CMain进程运行时,上面两个窗体原来的计数字符串没有显示,这是因为两个窗体的进程得不到调度的机会。由于CMain进程负责相应鼠标键盘,因此,此时我们移动鼠标就不再出现卡顿的情形。
当CMain进程被挂起后,两个窗体的进程获得执行机会,因而窗体中的计算字符串就出现了:
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
相关文章推荐
- java开发系统内核:实现进程优先级
- java开发系统内核:进程初体验及代码其实现1
- java开发系统内核:实现进程自动切换,再现Linus当年辉煌一刻
- java开发系统内核:实现回车键的处理
- java开发系统内核:实现应用程序和内核间的控制权切换
- java开发系统内核:实现窗口图形绘制API
- java开发系统内核:实现窗口创建API,由DOS进入windows
- java开发系统内核:cls 屏幕清空命令的实现
- java开发系统内核:自动化进程切换
- java开发系统内核:实现基于FAT12文件系统的dir命令
- java开发系统内核:使用一个中断实现多个API调用
- java开发系统内核:实现系统API调用
- java开发系统内核:像Linux一样使用中断实现内核API
- java开发系统内核:实现type命令读取文件内容
- java开发系统内核:实现鼠标与窗口的互操作
- java开发系统内核:使用LDT保护进程数据和代码
- java开发操作系统内核:实现进程的优先级切换
- java开发系统内核:实现shift按键效果
- java开发系统内核:进程的挂起和恢复
- java开发系统内核:依靠多任务实现多窗口