30天自制操作系统(第15天)

15天 多任务(1

15.1 挑战任务切换

任务切换,就是从某一任务切换成另一任务,如果切换的频率很快,就会有一种同时运行多任务的错觉。下面先介绍一下任务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;//任务设置相关的信息
};
其中,EIP是CPU用来记录下一条需要执行的指令位于内存中哪个地址的寄存器,因此它才被称为“指令指针”。( JMP 指令实际上是一个向 EIP 寄存器赋值的指令
JMP 指令分为两种,只改写 EIP 的称为 near模式,同时改写 EIP CS 的称为 far模式。
JMP DWORD 2*8:0x0000001b
这条指令在向 EIP 存入 0x1b 的同时,将 CS 置为 2*8 =16 )。像这样在 JMP目标地址 中带冒号( : )的,就是 far 模式的 JMP 指令。
首先创建两个任务tss_a和tss_b,并将任务写入GDT中,加载第一任务tss_a,并对后一任务进行的元素进行赋值tss_b,在系统运行10s后进行任务切换。
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;
};

void task_b_main(void);

void HariMain(void)
{
	(中略)
	int cursor_x, cursor_c, task_b_esp;
	(中略)	
	/* 创建两个任务 */
	struct TSS32 tss_a, tss_b;
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
    (中略)
    /* 装载任务 */
	tss_a.ldtr = 0;
	tss_a.iomap = 0x40000000;
	tss_b.ldtr=0;
	tss_b.iomap = 0x40000000;
	//tss_a定义在gdt的3号,tss_b定义在gdt的4号
	set_segmdesc(gdt+3, 103, (int)&tss_a, AR_TSS32);
	set_segmdesc(gdt+4, 103, (int)&tss_b, AR_TSS32);
	load_tr(3*8);//向TR寄存器存入3*8这个值,首先加载任务3

    /* 设置任务4的相关信息 */
	task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;//task_b_esp栈底的内存地址
	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;

	for (;;) {
		(中略)
			} else if (i == 10) {/* 10s计时器 */
				putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
				taskswitch4(); /*这里! */
			} else if (i == 3) {/* 3s计时器 */
				(中略)
		}
	}
}

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

15.2 任务切换进阶

为实现从任务tss_b切换成tss_a,只需要修改task_b_main函数即可。在该函数中增加了一个计数器,实现在5s后切换到任务tss_a。
//只需要修改函数task_b_main
void task_b_main(void){
	struct FIFO32 fifo;
	struct TIMER *timer;
	int i, fifobuf[128];
	
	fifo32_init(&fifo, 128, fifobuf);// FIFO结构体初始化
	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 */
		}
	}
}

15.3 做个简单的多任务(1

在15.2节中,实现了2个任务的切换。如果有100个任务需要进行切换,总不能写taskswitch1()~taskswitch100()个汇编语言。所以将任务切换的程序进行统一编写成
_farjmp:                                         ; void farjmp(int eip, int cs);
        JMP         FAR [ESP+4]         ; eip, cs
        RET
farjmp函数的功能就是按照输入参数,切换到对应的任务。并完善对应的task_b_main和HariMain函数,即可每0.02s实现两任务的快速切换。
void HariMain(void)
{
	(中略)
	timer_ts = timer_alloc();
	timer_init(timer_ts, &fifo, 2);
	timer_settime(timer_ts, 2);
    (中略)
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			io_stihlt();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (i == 2){
				farjmp(0, 4*8);/*返回任务A */
				timer_settime(timer_ts, 2);
			} else if (256 <= i && i <= 511) {/* 键盘数据 */
				(中略)
			} else if (512 <= i && i <= 767) {/* 鼠标数据 */
				(中略)
			} else if (i == 10) {/* 10s计时器 */
				(中略)
			} else if (i == 3) {/* 3s计时器 */
				(中略)
			} else if (i <= 1) {/* 光标用计时器 */
				(中略)
			} 
		}
	}
}

void task_b_main(void){
	struct FIFO32 fifo;
	struct TIMER *timer_ts;
	(中略)
	timer_settime(timer_ts, 2);// 这里
	
	for(;;){
		io_cli();
		if(fifo32_status(&fifo) == 0)
			io_stihlt();
		else{
			i = fifo32_get(&fifo);
			io_sti();
			if(i == 1){/*超时时间为5秒    修改位置 */
				farjmp(0, 3*8);/*返回任务A */
				timer_settime(timer_ts, 2);
			}
		}
	}
}

15.4 做个简单的多任务(2

功能是让task_b_main在背景图层中的(0, 144)位置显示计数,就需要在task_b_main调用函数putfonts8_asc_sht刷新图层。在函数中task_b_main中怎么知道背景图层呢?就需要在主函数中指定背景图层的存放位置,并task_b_main中调用该位置的数据,即可实现。
void task_b_main(void){
	struct FIFO32 fifo;
	struct TIMER *timer_ts;
	int i, fifobuf[128], count = 0;
	char s[11];
	struct SHEET *sht_back;
	sht_back = (struct SHEET *) *((int *) 0x0fec);
	(中略)
	for(;;){
		count++;
		sprintf(s, "%10d", count);
		putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_848484, s, 10);
		io_cli();
		if(fifo32_status(&fifo) == 0)
			io_sti();
		else{
			(中略)
		}
	}
}

15.5 提高运行速度

在15.4中,实时显示count值。由于每0.02s任务切换一次,然后计数增加,这个速度人眼根本无法分辨出来,所以将其改成每0.01s刷新一次计数。task_b_esp分配了64k内存,可以将sht_back的图层地址放在该块内存中,所以sht_back图层地址的信息传递也修改成:
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
*((int *) (task_b_esp + 4)) = (int) sht_back;
为何计算task_b_esp时,是-8,而不是-4呢?因为memman_alloc_4k(memman, 64 * 1024)返回的是申请的内存首地址,在此基础上加上64*1024,已经超过申请的内存范围,所以是-8。
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);// FIFO结构体初始化
	(中略)
	timer_put = timer_alloc();// 分配一个计时器
	timer_init(timer_put, &fifo, 1);// 计时器初始化
	timer_settime(timer_put, 1);// 设置计时器中断时长
	
	for(;;){
		count++;
		io_cli();
		if(fifo32_status(&fifo) == 0)
			io_sti();
		else{
			i = fifo32_get(&fifo);
			io_sti();
			if(i == 2){/*超时时间为5秒 */
				farjmp(0, 3*8);/*返回任务A */
				timer_settime(timer_ts, 2);/* 重新设定计时器 */
			} else if(i == 1){
				sprintf(s, "%11d", count);
				putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_848484, s, 11);
				timer_settime(timer_put, 1);/* 重新设定计时器 */
			}
		}
	}
}

15.6 多任务进阶

由于每次任务切换都是靠 task_b_main函数代码实现的,下面将创建一个mtask.c文件实现任务切换的功能。该文件中有两个函数,第一个为初始化函数,另一个是任务切换函数。
#include"bootpack.h"

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;
}

并在timer.c的中断函数inthandler20做如下改变:在计时器遍历时,检查是mt_timer多任务计时器还是其他计时器,如果是mt_timer则需要执行任务变换,否则将data输入写入fifo中,用于显示。(不直接切换的原因是:调用mt_taskswitch进行任务切换的时候,即便中断处理还没完成,IF(中断允许标志)的值也可能会被重设回1(因为任务切换的时候会同时切换EFLAGS)。)

void task_b_main(struct SHEET *sht_back){
	struct FIFO32 fifo;
	struct TIMER *timer_ls, *timer_put;
	int i, fifobuf[128], count = 0, count0 = 0;
	(中略)
	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_848484, s, 11);
				timer_settime(timer_put, 1);/* 重新设定计时器 */
			} else if(i == 100){/* 修改位置 */
				sprintf(s, "%11d", count - count0);
				putfonts8_asc_sht(sht_back, 0, 128, COL8_FFFFFF, COL8_848484, s, 11);
				count0 = count;
				timer_settime(timer_ls, 100);/* 重新设定计时器 */
			}
		}
	}
}

你可能感兴趣的:(其他)