《30天自制操作系统》笔记----Day15

挑战任务切换

CPU处理多任务的原理:

当向CPU发出任务切换指令时,CPU会先把寄存器的值全部写入内存中,为了运行下一个程序,CPU会把所有寄存器中的值从内存中读出来,这样就完成了一次切换。而任务切换所需要的时间,就是对内存进行写入和读取操作所消耗的时间。

寄存器中的内容写入内存:

补充“任务状态段”(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;
		int es, cs, ss, ds, fs, gs;
		int ldtr, iomap;
}

EIP(extended instruction pointer):是CPU用来记录下一条需要执行的指令位于内存中哪个地址的寄存器。
JMP 0x1234;即“MOV EIP,0x1234”(不推荐汇编语言中编写)

JMP指令

在进行任务切换时,还得勤用JMP指令,而JMP指令分为俩种:
①只改写EIP的为near模式
②同时改写EIP和CS的为far模式

far模式样例:

JMP DWORD 2*8:0x0000001b

这条指令在向EIP存入0x1b的同时,将CS置为2*8(=16)。像这样在JMP目标地址中带冒号(:)的,就是far模式的JMP指令。
JMP指令所指定的目标地址段不是可执行的代码,而是TSS的话,CPU就不会执行通常的改写EIP和CS的操作,而是将这条指令理解为任务切换。也就是说,CPU会切换到目标TSS所指定的任务,说白了,就是JMP到一个任务那里去了。

事例:从任务A切换到任务B
首先创建俩个TSS:

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

然后让其在GDT中进行定义:

struct SEGMENT_DESCRIPTOR *gdt=(struct SEGMENT_DESCRIPTOR *)ADR_GDT;
set_segmedes(gdt+3,103,(int)&tss_a,AR_TSS32);
set_segmedes(gdt+4,103,(int)&tss_a,AR_TSS32);

tss_a定义在gdt的3号,段长限制为103字节(tss_b类似)

TR寄存器(task register)

其作用是让CPU记住当前正在运行哪一个任务。
每次给TR寄存器赋值的时候,必须把GDT的编号乘以8!
而且给TR寄存器赋值需要使用LTR指令!

向TR寄存器存入 3*8这个值,因为刚才我们把当前运行的任务定义为GDT的3号。

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

执行far跳转指令:

_taskswitch4:  ;void taskswitch4(void);
			JMP		4*8:0
			RET

far-JMP指令是用作任务切换的话,地址段(冒号前面的4*8的部分)要指向TSS这一点比较重要,而偏移量(冒号后面的0的部分)并没有什么实际作用,会被忽略掉,一般来说像这样写0就可以了。
初始化tss_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;

给cs置为GDT的2号,其他寄存器都置为GDT的1号.
在eip中,需要定义切换任务时,要从哪里开始运行,在这里先把task_b_main这个函数的内存地址赋予给它。

void task_b_main(void)
{
		for(;;){ io_hlt();}
}

任务切换进阶

上文只是将任务B切换到A,现在尝试再切换回任务A:

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

而对于taskswitch3函数:

_taskswitch3:  ;void taskswitch3(void);
				JMP   3*8:0
				RET

做个简单的多任务

为了能够同时执行多个任务,新创建如下函数:

_farjmp:   ;void farjmp(int eip,int cs);
			JMP   FAR[ESP+4]			;eip,cs
			RET

“JMP FAR”指令的功能是执行far跳转。。在JMP FAR指令中,可以指定一个内存地址,CPU会从指定的内存地址中读取4个字节的数据,并将其存入EIP寄存器,再继续读取2个字节的数据.并将其存入CS寄存器。
farjmp(eip,cs);,在[ESP+4]这个位置就存放了eip的值,而[ESP+8]则存放了cs的值,这样就可以实现far跳转了。
taskswitch3(); → farjmp(0, 3* 8);
taskswitch4(); → farjmp(0, 4 * 8);
接下来缩短切换的间隔,在任务A和任务B中,分别准备一个timer_ts变量,以便每隔0.02s执行一次任务切换。

运行速度改进

提高运行速度

降低页面刷新频率,人眼要求不是很高

多任务进阶

现在准备实现真正的多任务,即在程序本身不知道的情况下进行任务切换。
于是创建如下函数:

struct TIMER *mt_timer;
int mt_tr;
void mt_init(void)
{
		mt_timer = timer_alloc();
		/*这里没有必要使用timer_init */
		timer_settime(mt_timer, 2);
		mt_tr = 3 * 8;
		return;
}
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;
}

mt_init函数的功能是初始化mt_timer和mt_tr的值,并将计时器设置为0.02秒之后。
变量mt_tr实际上代表了TR寄存器,而不需要使用timer_init是因为在发生超时的时候不需要向FIFO缓冲区写入数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不吃人的坤坤坤坤坤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值