#前言#
>_<" 这里主要是多任务的初探~比较简单,主要是尝试保存当前任务、任务切换、恢复当前任务,而真正的多任务要比这个复杂的多,因为包含互不干扰,甚至是高度优化的并行技术!
一、保存当前任务:
>_<" 当向CPU发出切换命令的时候,CPU会先把寄存器中的值全部写入内存中,这样当切换回来时,可以从中断的地方继续执行。接下来,为了运行下一个程序,CPU会把所有的寄存器中的值从内存中读取出来,这就完成了一次切换。
>_<" 这里的结构TSS32是task status segment,是32位任务状态段,其也是内存段的一种,需要再GDT中进行定以后使用。
1 struct TSS32 {//task status segment 任务状态段 2 int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;//保存的不是寄存器的数据,而是与任务设置相关的信息,在执行任务切换的时候这些成员不会被写入(backlink除外,某些情况下会被写入) 3 int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;//32位寄存器 4 int es, cs, ss, ds, fs, gs;//16位寄存器 5 int ldtr, iomap;//有关任务设置部分 6 };
二、任务切换:从任务A到任务B[12a]
>_<" 接下来要在main函数中创建两个TSS:任务A的TSS和任务B的TSS,然后对其初始化,接着再在GDT中定义...见下面的main函数(有删减,这里只展示核心部分):
18 void HariMain(void) 19 { 25 int mx, my, i ,cursor_x, cursor_c, task_b_esp;//cursor_x是记录光标位置的变量,cursor_c表示光标现在的颜色,每隔0.5秒变化一次,任务B的栈 41 struct TSS32 tss_a, tss_b;//首先建立2个TSS,任务a的TSS和任务b的TSS
110 tss_a.ldtr = 0;//先这样设置好 111 tss_a.iomap = 0x40000000; 112 tss_b.ldtr = 0; 113 tss_b.iomap = 0x40000000; 114 set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);//定义在gdt的3号,段长限制为103字节 115 set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32); 116 //向TR寄存器写入这个值,因为刚才把当前运行任务的GDT定义为3号,TR寄存器是让CPU记住当前正在运行哪一个任务 117 //每当进行任务切换时,TR寄存器的值也会自动变换,task register 118 //每次给TR赋值的时候,必须把GDT的编号乘以8 119 load_tr(3 * 8); 120 task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;//要为任务B专门分配栈,直接用任务A的栈就会乱成一团糟 121 tss_b.eip = (int) &task_b_main;//任务b的开始地址 122 tss_b.eflags = 0x00000202; /* IF = 1; */ 123 tss_b.eax = 0; 124 tss_b.ecx = 0; 125 tss_b.edx = 0; 126 tss_b.ebx = 0; 127 tss_b.esp = task_b_esp; 128 tss_b.ebp = 0; 129 tss_b.esi = 0; 130 tss_b.edi = 0; 131 tss_b.es = 1 * 8; 132 tss_b.cs = 2 * 8; 133 tss_b.ss = 1 * 8; 134 tss_b.ds = 1 * 8; 135 tss_b.fs = 1 * 8; 136 tss_b.gs = 1 * 8; 137 138 for (;;) {140 if (fifo32_status(&fifo) == 0) {142 } else {145 if (256 <= i && i <= 511) {//键盘数据165 }else if (512 <= i && i <= 767) {//鼠标数据202 }else if(i==10){//10s定时 204 taskswitch4();//程序10s以后进行任务切换 205 }else if (i == 3){}219 } 220 } 221 }300 ///////////////////////////////////////////////////////////////////////////////////// 301 //功能:任务b的函数 302 //参数: 303 void task_b_main(void) 304 { 305 for (;;) { io_hlt(); } 306 }

第41行是建立2个TSS,分别是任务A和任务B的
第110~113是对A和B的TSS任务的成员变量的初始化,这里不能乱赋值否则不能正常切换
第114和第115两行是定义两个TSS的GDT,分别是段3和段4
第119行load_tr(3 * 8);是用汇编写的函数,目的是给TR寄存器赋值,因为刚才设置任务a的GDT为3,而TR寄存器是让CPU记住当前执行那一条任务,所以,要给TR重新设置值,这里*8是和硬件有关~
第120行是给任务B分配栈,否则和任务A混在一起就会出现混乱
第121~136是对B的TSS的初始化设置,其中第一行指定任务B的入口地址,当任务切换到任务B时,就会转到第303行任务B的函数执行~
第204行是任务切换,同样这里的函数也是用汇编写的,里面就是一个JMP语句:JMP 4*8:0 用来转到任务B所对应的段地址,我们可以看到这是在10s定时器中写的,所以每隔10s就进行任务切换了
@总的来说,这段代码的任务就是做一个10s后的任务切换工作,先运行A任务,然后任务切换进入B,一直处于进行hlt()~
三、任务切换:任务A和B的互相切换[12b]
>_<" 上面只是实现了从任务A切换到任务B,然后就一直处于hlt()了~这里我们只要简单的改一下任务B的函数就能实现任务的切回了:
1 void task_b_main(void) 2 { 3 struct FIFO32 fifo; 4 struct TIMER *timer; 5 int i,fifobuf[128]; 6 7 fifo32_init(&fifo,128,fifobuf); 8 timer=timer_alloc(); 9 timer_init(timer,&fifo,1); 10 timer_settime(timer,500); 11 12 for(;;){ 13 io_cli(); 14 if(fifo32_status(&fifo)==0){ 15 io_stihlt(); 16 }else{ 17 i=fifo32_get(&fifo); 18 io_sti(); 19 if(i==1){//超时时间为5s 20 taskswitch3();//返回任务A 21 } 22 } 23 } 24 }
PS: 这里在任务B的函数里写了和任务A中相同的变量名,其实是互不影响的~
四、多任务:每隔0.02s切换任务 & 变量传递[这里是窗口sht_back][12e]
>_<" 这里把汇编写的taskswitch3();和taskswitch4();合并为一个可以带参数的任务切换函数farjmp(),并声明一个定时器0.02s中断一次用于任务切换,注意这里每次切换后要重新设置定时器,以便下次仍然是0.02s切换一次~,对于参数传递采用的思路是:把sht_back在任务A的时候找一个内存存进去(*((int*)0x0fec)=(int) sht_back;),然后在任务B的时候再从这个内存中读出sht_back的值(sht_back=(struct SHEET *)*((int*)0x0fec);)
1 if (i==2){//每隔0.02s执行一次任务切换 2 farjmp(0,4*8); 3 timer_settime(timer_ts,2); 4 }

五、任务自动切换[12g]
>_<" 真正的多任务是程序本身不知道的情况下进行任务切换!这里直接创建一个mtask.c专门用来处理多任务
1 /* 任务管理相关程序 */ 2 3 #include "bootpack.h" 4 5 struct TIMER *mt_timer; 6 int mt_tr; 7 8 ///////////////////////////////////////////////////////////////////////////////////// 9 //功能:初始化mt_timer和mt_tr的值,并将计数器设定为0.02s,仅此而已 10 //参数: 11 //附加:变量mt_tr实际上代表了TR寄存器,而不需要timer_init是因为在超时的时候不需要向FIFO缓冲区写入数据 12 //接下来,mt_taskswitch函数的功能是按照当前的mt_tr变量的值计算下一个mt_tr的值,将计时器重新设定为0.02s之后并进行任务切换 13 void mt_init(void) 14 { 15 mt_timer = timer_alloc(); 16 /* 这里没有必要使用timer_init */ 17 timer_settime(mt_timer, 2); 18 mt_tr = 3 * 8; 19 return; 20 } 21 ///////////////////////////////////////////////////////////////////////////////////// 22 //功能:按照当前的mt_tr变量的值计算下一个mt_tr的值,将计时器重新设定为0.02s之后并进行任务切换 23 //参数: 24 void mt_taskswitch(void) 25 { 26 if (mt_tr == 3 * 8) { 27 mt_tr = 4 * 8; 28 } else { 29 mt_tr = 3 * 8; 30 } 31 timer_settime(mt_timer, 2); 32 farjmp(0, mt_tr); 33 return; 34 }
mt_init函数是初始化mt_timer和mt_tr的值,并将计数器设置为0.02s之后,mt_tr代表TR寄存器,而不需要使用timer_init()是因为发生超时时不需要向FIFO写数据~
mt_taskswitch是按照当前的mt_tr变量的值计算出下一个mt_tr的值,并设置计时器为0.02s,然后任务切换~
>_<" 这里就必须对timer.c里的定时器中断响应函数inthandler20进行响应的修改了:
1 void inthandler20(int *esp) 2 { 3 char ts=0;//标记mt_timer任务切换计时器是否超时,如果直接在else语句直接调用任务切换会出现错误,因为这个中断处理还没完成 4 struct TIMER *timer; 5 io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接受完了的信息通知给PIC */ 6 timerctl.count++; 7 if (timerctl.next > timerctl.count) {//如果下一个还没计数完毕就直接返回 8 return; 9 } 10 timer = timerctl.t0; //把最前面的地址赋址给timer 11 for (;;) { 12 if (timer->timeout > timerctl.count) { 13 break; 14 }//从前往后遍历,一旦发现有计时未完成的计时器就跳出循环 15 /*除了上面的情况,都是定时已达的定时器*/ 16 timer->flags = TIMER_FLAGS_ALLOC; 17 if(timer!=mt_timer){ 18 fifo32_put(timer->fifo, timer->data); 19 }else{ 20 ts=1;/* mt_timer超时 */ 21 } 22 timer = timer->next;//下一个定时器的地址赋址给timer 23 } 24 timerctl.t0 = timer;//新移位 25 timerctl.next = timer->timeout;//timectl.next设定 26 if(ts!=0){ 27 mt_taskswitch(); 28 } 29 return; 30 }
如果发生超时的是mt_timer的话,不向FIFO写数据,而是置ts为1,最后根据这个标志看是否要进行任务切换~(这里一定不能直接在第20行进行任务切换,因为中断还没进行完毕~)
六、效果展示:
>_<" 这里每隔1s就显示一下累计的数据:7370035,下面一行是总的数据~
七、代码链接:
- 任务切换A->B链接12a:http://pan.baidu.com/s/1eQlDQ2u
- 任务切换AB互转链接12b:http://pan.baidu.com/s/1qWlZ4Qg
- 简单的多任务B数数链接12d:http://pan.baidu.com/s/1Gongy
- 简单的多任务优化提速链接12e:http://pan.baidu.com/s/1pJFhI8j
- 任务自动切换链接12g:http://pan.baidu.com/s/1ntHTGsp