终于讲到多任务啦~这个学期的课程也要结束了,最后是一个对之前学过的大设计,利用学到的图层知识和字体输入、定时器等,制作一个真正的操作系统!
一、任务切换
- Cpu处理多任务的原理:实际上是通过调度在不同的时间运行不同的任务,通过快速切换任务(CPU越快切换越快,用户体验越好)实现视觉上的多任务同步。
- 任务切换指令:CPU把寄存器的值写入内存,再次切换回来就从中断的地方继续运行。引入“任务状态段”—TSS,我们使用32位版本
其中EIP寄存器记录CPU下一条指令的地址,每执行一次++。其他寄存器的值不太重要,这两个要设置:
Ldtr=0;
Iomap=0x40000000
插入TSS博客知识:
任务寄存器tr保存 16 位的段选择子、32 位基地址、16 位段界限和当前任务的 TSS属性。它引用 GDT 中的 TSS 描述符。基地址指明 TSS 的第一个字节(字节 0)的线性地址,段界限确定 TSS 的字节个数。TR寄存器包含了当前正在CPU运行的进程的TSSD(任务段描述符)选择符。也包含了两个隐藏的非编程域:TSSD的base和limit域。通过这种方式处理器就能直接对TSS寻址,而不用从GDT中索引TSS的地址:
TR寄存器---->GDT中的TSS描述符---->硬件上下文的具体数据。
任务切换中cpu会把当前寄存器的数据保存到当前(旧的)tr寄存器所指向的tss数据结构里,然后把新的tss数据复制到当前寄存器里。这些操作是通过cpu的硬件实现的。
原文:https://blog.csdn.net/applenob/article/details/20151067
注册到GDT(全局段号记录表)
…(设定寄存器为0)
规定向TR寄存器赋值时必须把GDT编号8,存值指令:load_tr(38);(用汇编)
_load_tr: ; void load_tr(int tr);
LTR [ESP+4] ; tr
RET
Far跳转指令
_taskswitch3: ; void taskswitch3(void);
JMP 3*8:0
RET
_taskswitch4: ; void taskswitch4(void);
JMP 4*8:0
RET
定义寄存器的值:tss_b.esp = task_b_esp;任务B的main地址(制作io_hlt())
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
tss_b.eip = (int) &task_b_main;
tss_b.eflags = 0x00000202; /* IF = 1; */
切换任务:10秒之后切换到B任务,5秒后再换A
B-> Main
二、简单的多任务
- 解决任务切换麻烦的问题
_farjmp: ; void farjmp(int eip, int cs);
JMP FAR [ESP+4] ; eip, cs
RET
这个函数实现farjmp跳转(重点部分!!)
Taskswitch3();-> farjmp(0,3*8);
Taskswitch4();-> farjmp(0,4*8);
缩短切换任务时间:timer_ts=0.02s为切换时间
跟光标闪烁原理一样,每次切换之后重新设定为0.02s,切换成果通过B任务的显示查看,利用同一内存地址,共享图层sht_back : *((int *) 0x0fec) = (int) sht_back;
在B的main中定义同一地址:sht_back = (struct SHEET )*((int *) 0x0fec);
三、优化速度
- 由于以上做法每计数1次就刷新一次,速度变慢,所以改成0.01s显示一次
用栈代替地址传递sht_back
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
*((int *) (task_b_esp + 4)) = (int) sht_back;
这个-8了解一下,task_b_esp=0x01243ff8,写入sht_back地址就是task_b_esp+4:
在A,B任务中都没有return,返回的是[ESP]的值,如果里面的值合适就可以返回,如果是一个错误的地址,那程序就乱套了。(木马来源)
2. 测试改进
一秒输出一次比较,性能比之前差3倍,删除每次输出值,优化到2.1倍。
3. 多任务进阶(知识点太多,利用流程图来总结)
四、任务管理、休眠
作者对多任务的想法(引发很多改进):
- 类似timectl一样定义了TASKCTL-(相当于TIMECTL),TASK-(相当于TIMER)
LEVEL与任务优先级有关,第六点讲。
作者创建了mtask.c文件,用来存放各种任务处理函数。- 创建函数:task_init(struct MEMMAN *memman):返回一个内存地址,说明正在运行这个程序变成一个任务了,初始化:flag=2(表示活动中),running=1表示只有一个任务等等设置.
- 初始化函数:task_alloc(void):设置没有在使用的任务的寄存器的值(随意)
运行函数:task_run(struct TASK *task):falg=2,running++,当前运行task,把task放在tasks末尾 - 切换函数:task_switch(void):总任务大于2才切换,使用循环切换。
- Bootpack.c:创建task_b初始化
休眠:把一个任务从tasks中删除,可通过task_run唤醒
Task_sleep(struct TASK* task)
当FIFO写入数据的时候将任务唤醒:
Fifo32_init(…,struct TASK *task)中唤醒任务fifo->task=task;
Fifo32_put()中判断任务是否需要唤醒(fifo->task)如果处于休眠就唤醒
在HariMain中修改:
先禁用唤醒功能,当FIFO为空时,任务A执行task_sleep代替之前的HLT,task_b不用唤醒
Task_B:
五、增加窗口
添加B0-B2窗口(计数)
后面的设置同上,只是在make_window8的时候添加一个act变量,为1时颜色不变,为0时,窗口标题变为灰色
六、任务优先
修改任务切换间隔,可以实现最大10倍差异。在结构体中定义了优先级:priority
- 在task_init中定义最开始的任务切换时间为0.02s
- 在task_run(struct TASK *task ,int priority)通过传参数设定优先级,如果>0就设定
- 在task_switchz设定定时器时,应用priority的值,在farjmp之前判断任务数量是否在2个以上。
- Fifo.c:将任务唤醒不改变其优先级,直接设置为0就可以了。Task_run(fifo->task,0)
- 改写bootpack.c:task_run(task_b[i],i+1);
继续优化:框架是创建几个TASKCTL
只要最上面的level0中,就完全忽略下面两个的任务。当level0中的任务全部休眠或者下降时,才轮到level1。
实际是只在TASKCTL中创建多个tasks[],每个level最多允许100个任务,总共10个level - Task_now:用来返回当前活动的struct TASK
- Task_add:向TASKLEVEL中添加一个任务
- Task_remove:从TASKLEVEL中删除一个任务(类似task_sleep)
- Task_switchsub:决定切换到那个level
- Task_init:定义任务设置,最高级的level=0
- task_run(struct TASK *task, int level, int priority):在参数中指定LEVEL,如果启动了一个比现在活动的更高级的LEVEL,那么下次切换的时候就要切换到该LEVEL的那个任务中
- task_sleep(struct TASK *task):休眠传入的task,如果自己休眠进行任务切换
- void task_switch(void):新增lv_change不为0的处理,如果不为0说明要切换LEVEL
改写FIFO.c在task_run的传参中添加level=0。
Bootpack中把A设为level1,B0-B2设为level2,A忙碌时就不会切换到B任务。
多任务完成之后,就是自己的设计了