本章开始多任务的设计。

一 多任务的说明

多任务(multitask),指的是操作系统中,多个应用程序同时运行的状态。然而,对于单核CPU来说,同一个瞬间只能处理一个事情,不能做到左右互搏、一心二用的效果,那只能通过快速切换运行任务,来实现这种所谓的多任务状态:

在一般的操作系统中,这个切换动作每0.01-0.03秒进行一次(这样CPU大概只有1%的处理能力消耗在任务切换上,可以忽略不计)。这个切换时间不能太慢(会让人感觉到程序卡顿)、也不能太快(消耗CPU的处理能力,功夫都花在切换上面,没时间处理正事了) 。

二 实现任务切换

实现任务的切换,有两个步骤:

(1)将TASK1有关寄存器的值写入到内存中;

(2)将运行TASK2需要的值从内存中读出到寄存器中;

每个任务包含的状态可以归纳为“任务状态段”(Task Status Segment,简称TSS)结构体中:

struct TSS32 {int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;         /* 与任务设置有关的信息 */int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi; /* 32位寄存器 */int es, cs, ss, ds, fs, gs;                                /* 16位寄存器 */int ldtr, iomap;                                        /* 与任务设置有关的信息 */
};

TSS总计包含26个int成员(104字节),成员的内容主要是一些寄存器,具体可以参考【操作系统】CPU寄存器详解,这边主要关注一下eip指令寄存器( 指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址,CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令,如此不断重复,这就是CPU工作的基本日常)

对指令寄存器赋值(MOV EIP,0x1234),就相当于0x1234地址的指令,也就类似于汇编中的JMP 0x1234实现的效果。对任务实现切换,本质上就是从一个指令执行地址跳转到另一个指令的执行地址,这边还得用JMP指令,JMP指令又分为两种模式(near模式、far模式):
        (1)near 模式,只改写 EIP(指令寄存器)
        (2)far 模式,同时改写 EIP(指令寄存器) 和 CS(代码段寄存器)

如果一条 JMP 指令所指定的目标地段不是可执行的代码,而是 TSS 的话,CPU 就不会执行通常的改写 EIP 和 CS 操作,而是将这条指令理解为任务切换,换句话说,TSS 里面就是用来执行任务切换的代码。

根据上面的思路,按照下面的步骤就可以进行任务切换了:

【1】首先创建两个TSS:任务A的TSS和任务B的TSS。

struct TSS32 tss_a, tss_b;

【2】向他们的ldtr和iomap赋值:

tss_a.ldtr = 0;
tss_a.iomap = 0x40000000;
tss_b.ldtr = 0;
tss_b.iomap = 0x40000000;

【3】将他们两个在GDT中注册(这边可以参考一下【操作系统】30天自制操作系统--(4)显示字体(汉字)以及GDT/IDT中对于段1和段2的GDT注册):

struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
#define AR_TSS32        0x0089set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);//段号3,基地址&tss_a,段长104-1
set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);//段号4,基地址&tss_b,段长104-1

【4】赋值TR寄存器(任务寄存器,存储当前正在运行哪一个任务),对它的赋值必须把GDT的编号乘以8,且对TR寄存器的操作必须在汇编的层面完成:

_load_tr:        ; void load_tr(int tr)LTR       [ESP+4]RET

【5】光改变TR的值还不能任务切换,必须执行far模式的跳转指令才行:

; 跳转到段3(任务b-->任务a)
_taskswitch3:   ; void taskswitch3(void);JMP        3*8:0RET; 跳转到段4(任务a-->任务b)
_taskswitch4:   ; void taskswitch4(void);JMP        4*8:0RET

【6】上面完成了从tss_a、tss_b的注册和跳转,这边还需要准备一下tss_b的初始化和执行程序,不然跳转到一个空地址上也不顶用:

初始化如下:

int task_b_esp;task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;  //为任务b定义新栈//初始化任务b的寄存器
tss_b.eip = (int) &task_b_main;
tss_b.eflags = 0x00000202; //IF = 1;
tss_b.eax = 0;
tss_b.ecx = 0;
tss_b.edx = 0;
tss_b.ebx = 0;
tss_b.esp = task_b_esp;  //栈指针寄存器
tss_b.ebp = 0;
tss_b.esi = 0;
tss_b.edi = 0;
tss_b.es = 1 * 8;
tss_b.cs = 2 * 8;        //代码段寄存器
tss_b.ss = 1 * 8;
tss_b.ds = 1 * 8;
tss_b.fs = 1 * 8;
tss_b.gs = 1 * 8;

任务b的执行操作如下(定时5s之后,返回任务a):

void task_b_main(void) {struct FIFO32 fifo;struct TIMER *timer;int i, fifobuf[128];fifo_init(&fifo, 128, fifobuf);timer = timer_alloc();timer_init(timer, &fifo, 1);// 设置超时时间为 5 秒timer_settime(timer, 500);for(;;) {io_hlt();if (fifo32_status(&fifo) == 0) {io_sti();io_hlt();} else {i = fifo32_get(&fifo);io_sti();if (i == 1) {// 返回任务 3taskswitch3();}}}
}

【7】主函数中10s超时缓存处理中执行跳转(任务a---->任务b),实现10s之后跳转任务b:

/* 中略 */
else if (i == 10) { putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);taskswitch4();  //切换段4(任务a-->任务b)
}
/* 中略 */

综上,便实现了一个简单的多任务操作,程序运行10s之后光标停止闪烁、鼠标键盘也失效—(任务a---->任务b成功!)。再隔了5s之后,光标恢复正常,刚鼠标键盘没反应的时候存进缓存的数据也一股脑全部冒了出来(任务b---->任务a成功!)。

三 多任务操作优化

【1】优化点一:重写一个farjump函数来替代原来的taskswitch3、taskswitch4:

_farjump:        ; void farjump(int eip, int cs);JMP     FAR [ESP + 4]      ; eip, csRET

同时在任务a和任务b中,分别准备一个timer变量叫做timer_ts(task switch),以便每隔 0.02s 切换一次:

/* HariMain */
if (i == 2) {farjmp(0, 4 * 8);// 任务返回之后,都将计时器设定为 0.02s 之后timer_settime(timer_ts, 2);
}/* task_b_main */
if (i == 1) { // task switchfarjmp(0, 3 * 8);timer_settime(timer_ts, 2);
}

【2】任务b中增加打印来显示确实任务切换了过来:

/* HariMain 中使用 0x0fec 这个地址将 sht_back 这个变量传给任务 B */
*((int *) 0x0fec) = (int) sht_back;/* task_b_main */
sht_back = (struct SHEET *) *((int *) 0x0fec);
// ...
for (;;) {count++;sprintf(s, "%10d", count);putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 10);// ...
}

【3】上面任务b的操作,每计一个数都会打印,这个伴随有操作内存的操作,过于频繁地执行会是程序变慢,处理办法是设置定时器,达到超时才会执行打印:

void task_b_main(struct SHEET *sht_back) {struct FIFO32 fifo;struct TIMER *timer_ts, *timer_put;int i, fifobuf[128], count = 0;char s[12];fifo32_init(&fifo, 128, fifobuf);timer_ts = timer_alloc();timer_init(timer_ts, &fifo, 2);timer_settime(timer_ts, 2);timer_put = timer_alloc();timer_init(timer_put, &fifo, 1);// every 0.01stimer_settime(timer_put, 1);for (;;) {count++;io_cli();if (fifo32_status(&fifo) == 0) {io_sti();} else {i = fifo32_get(&fifo);io_sti();if (i == 1) { sprintf(s, "%11d", count);putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);timer_settime(timer_put, 1);} else {if (i == 2) {farjmp(0, 3 * 8);timer_settime(timer_ts, 2);}}}}
}

四 多任务操作进阶

截至目前,多任务的一些基本的切换操作已经具备,但是设想一下这个场景,比如说程序切换到任务b,但在任务b运行的过程中程序挂死,那么任务b就不会切换回其他的任务,这样单个任务异常,会使得整个程序挂死,这样会影响到程序的稳定性。

解决办法是,将跳转的逻辑封装之后从HariMain与task_b_main中抽离出来,放到定时器中断inthandler20中(关于该中断的描述参考【操作系统】30天自制操作系统--(11)定时器1),定时器中断中检查是否是多任务定时器,如果是,则直接进行切换,这样就避免了各个子任务异常对于程序整体的影响:

【1】封装子任务切换逻辑 mt_taskswitch:

#include"bootpack.h"struct TIMER *mt_timer;
int mt_tr;void mt_init(void){mt_timer = timer_alloc();// 没必要加上 timer_init() 是因为超时时不需要向 FIFO 写数据timer_settime(mt_timer, 2);mt_tr = 3 * 8;                //初始化为子任务areturn;
}void mt_taskswitch(void){if (mt_tr == 3 * 8) {mt_tr = 4 * 8;} else {mt_tr = 3 * 8;}timer_settime(mt_timer, 2);farjmp(0, mt_tr);               //切换到另一个子任务中return;
}

【2】定时器中断 inthandler20 中调用 mt_taskswitch(这边在中断的尾巴调用的原因在于:调用 mt_taskswitch() 进行任务切换的时候,即便中断处理还没有完成,IF(interrupt flag) 也有可能被重置为 1 (因为任务切换时同时也会切换 EFLAGS),而中断处理还没完成的时候产生中断显然是不允许的):

void inthandler20(int *esp){// 把IRQ-00接收信号结束的信息通知给PIC io_out8(PIC0_OCW2, 0x60);  timerctl.count++;if (timerctl.next > timerctl.count) {return;}struct TIMER *timer = timerctl.t0;char ts = 0;while (1) {// timers的计时器全部在工作中,因此不用确认flags if (timer->timeout > timerctl.count) {break;}timer->flags = TIMER_FLAGS_ALLOC;if (timer != mt_timer) {fifo32_put(timer->fifo, timer->data);  //(1)不是mt_timer就往缓存中写数据} else {ts = 1;                                //(2)是mt_timer就是达了切换的时候}timer = timer->next;} timerctl.t0 = timer;// fucking irritating ...timerctl.next = timer->timeout;if (ts != 0) {mt_taskswitch();    //子任务切换}return;
}

【操作系统】30天自制操作系统--(14)多任务1相关推荐

  1. 《30天自制操作系统》笔记(04)——显示器256色

    <30天自制操作系统>笔记(04)--显示器256色 进度回顾 从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题. 本篇做一个小练习:设置显卡显示256色. ...

  2. 索骥馆-DIY操作系统之《30天自制操作系统》扫描版[PDF]

    内容简介: <30天自制操作系统>是一本兼具趣味性.实用性与学习性的操作系统图书.作者从计算机的构造.汇编语言.C语言开始解说,让读者在实践中掌握算法.在这本书的指导下,从零编写所有代码, ...

  3. 为什么《30天自制操作系统》封面中的猫是两只尾巴

    刚刚在一社区,发了一贴,被指出一问题,询一高人,得一答案.这便是我没有关注到的封面上的那只猫,我想这也是很多读者没有关注到的.因为在我微博的200转发贴中,并没有人提到封面中的猫为何有两只尾巴.于是咨 ...

  4. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的 ...

  5. 写在《30天自制操作系统》上市之前

       这本<30天自制操作系统>马上就要在各大书店和网上商城全面上架了,作为本书的4位译者之一,我负责翻译了本书约三分之二的内容.这是我参与翻译的第一本译著,我感到很激动也很紧张,因为我知 ...

  6. 《30天自制操作系统》笔记(09)——绘制窗口

    <30天自制操作系统>笔记(09)--绘制窗口 进度回顾 上一篇中介绍了图层式窗口管理的思路和算法.在此基础上,本篇就解决绘制窗口及其简单的优化问题. 这里稍微吐槽一下<30天自制操 ...

  7. 发布在《30天自制操作系统》之前的帮助阅读贴

    说明:这是8月15日即将上市的一本新书,本文的摘选也可以命名为<30天自制操作系统>上市之前必读.本书幽默,有趣,可以说是技术书里的幽默书,让您读起来绝对不会感到乏味.在本书上市之前,您一 ...

  8. 《30天自制操作系统》学习笔记--第好多天

    之前看<30天自制操作系统>,参考而成,和书中系统并不完全一致,是在原有基础上按照自己的习惯而成,由于水平和工作原因,未完成内存管理和文件系统,有兴趣者可以通过以下网址https://gi ...

  9. 由《30天自制操作系统》引发的漫画创作

    大家可还记得<30天自制操作系统>的封面上的那只猫吗?记得当时,在果壳网有人问,为何这只猫长了两只尾巴呢,延着这条线,我把这本书捧上了展示的舞台.事隔四个多月,我又重提此书. 这本经我手宣 ...

最新文章

  1. elasticsearch-.yml(中文配置详解)
  2. Kraken采用CashAddr地址,BCH地址统一向前一步
  3. 简单CSS3动画制作
  4. Ubuntu 16.04 LTS下编译GPU版tensorflow
  5. springboot 打卡功能_实战:如果让你用SpringBoot实现签到奖励的功能,你会怎么做?...
  6. 2021-9-下旬 数据结构-线性表-队列-java代码实现(复习用)
  7. 如何快速在oracle内生成数据,[Oracle]快速生成大量模拟数据的方法
  8. SQL注入攻击(SQL注入(SQLi)攻击)-报错注入
  9. Mapreduce中maptask过程详解
  10. Debug和Release 老程序啊 调试之前 区分一下啊
  11. 前端学习(2667):退出编辑状态
  12. mysql 慢sql分析_如何分析Mysql慢SQL
  13. jquery on()方法和bind()方法的区别
  14. 又一数据库高危漏洞爆出,数据安全如何有效保障?
  15. 渗透测试流程信息收集
  16. 一些关于虚拟交易的有趣文章
  17. 离散数学笔记整理(个人向)
  18. MATLAB遇到问题:MATLAB2020以上版本代码拷到其他低版本电脑出现中文乱码的解决方案
  19. c语言经典程序100例加注释,C语言经典100例
  20. 触动精灵贝塞尔曲线Bezier Curve

热门文章

  1. word中添加Mathtype公式行间距改变问题
  2. html网页弹幕特效,jquery仿哔哩哔哩弹幕文字动画特效
  3. 计算机控制交通灯实验报告,PLC实验专用周实验报告 交通灯
  4. 蓝月亮做java好吗,“蓝月亮”蓝吗?历史上真正的蓝月亮,你真就不敢看
  5. 杯子、笔、微信发朋友圈测试用例设计
  6. windows域控制器损坏修复过程
  7. 资深项目经理这样做成本控制
  8. 计算机图形学基础学习笔记-其一:向量与线性代数
  9. 数字逻辑结课实验 VHDL语言设计洗衣机控制系统
  10. unity 网络 --NetWorking