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

来源:互联网 发布:美国二战军工生产知乎 编辑:程序博客网 时间:2024/05/05 01:48
1.任务切换
慢慢的,总算写到了多任务了。不过貌似要了好长的时间了。
这里说一下多任务:多任务就相当于快速的切换不同的任务,让人感觉几个不同的任务在同时运行。过程是当需要切换任务时,将当前cpu中的数据全部写入内存,然后执行其他任务,需要切换回来时,再重新读取内存中的数据进入cpu即可。
因此在bootpack.c中添加结构体记录cpu信息:
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;};

然后创建两个TTS,接着向他们的ldtr和ipmap存入值并在GDT中定义:
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;set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);
接着准备tss_b:
int task_b_esp;//为任务B定义的栈task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;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;
接着定义task_b_main这个函数,暂时不用添加功能:
void task_b_main(void){for (;;) { io_hlt(); }}
然后向LR寄存器存入3*8。在naskfunc.nas中编写:
_load_tr:;void load_tr(int tr);LTR [ESP+4];trRET
然后还要在汇编文件中创建far模式的跳转指令:
_taskswitch4:; void taskswitch4(void);JMP4*8:0RET
然后在10秒钟后调用跳转函数。
这样程序运行10秒钟后,执行任务切换,task_b_main开始运行,键盘鼠标都失去相应。
成功运行后,考虑再把任务切换回来。只需要在task_b_main中添加代码并且在汇编文件中添加一个函数即可:
_taskswitch3:; void taskswitch3(void);JMP3*8:0RET

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_sti();io_hlt();} else {i = fifo32_get(&fifo);io_sti();if (i == 1) { /* 超时时间为5秒 */taskswitch3(); /* 返回任务A */}}}}
运行即可实现前10秒能够正常输入,然后键盘鼠标失去相应5秒,接着又正常运行。
2.多任务
前面完成了任务的切换,这里让不同的任务自动切换,实现多任务功能。
首先,前面切换任务的方式肯定不能用,因为任务的切换需要在汇编中单独写如函数。
因此创建通用函数:
_farjmp:; void farjmp(int eip, int cs);JMPFAR [ESP+4];eip,csRET
这样即可将原来的调用修改为:
taskswitch3() -----> farjmp(0, 3*8);taskswitch4() -----> farjmp(0, 4*8);
然后添加任务切换计时器:
struct TIMER *timer_ts;timer_ts = timer_alloc();timer_init(timer_ts, &fifo, 2);timer_settime(timer_ts,2);
然后在for循环中:
if (i == 2)//0.02秒计时器数据{farjmp(0,4*8);timer_settime(timer_ts, 2);}
接着修改B任务:
void task_b_main(void){struct FIFO32 fifo;struct TIMER *timer_ts;int i, fifobuf[128];fifo32_init(&fifo, 128, fifobuf);timer_ts = timer_alloc();timer_init(timer_ts, &fifo, 1);timer_settime(timer_ts, 2);for (;;) {io_cli();if (fifo32_status(&fifo) == 0) {io_sti();io_hlt();} else {i = fifo32_get(&fifo);io_sti();if (i == 1) { /* 超时时间为5秒 */farjmp(0, 3*8); /* 返回任务A */timer_settime(timer_ts, 2);}}}}
到这里其实已经可以切换任务了,但是还不能直观反应出来。考虑让B任务显示计数:
void task_b_main(void){struct FIFO32 fifo;struct TIMER *timer_ts;int i, fifobuf[128];int count = 0;char s[11];struct SHEET *sht_back;fifo32_init(&fifo, 128, fifobuf);timer_ts = timer_alloc();timer_init(timer_ts, &fifo, 1);timer_settime(timer_ts, 2);for (;;) {count++;sprintf(s, "%10d", count);putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 10);io_cli();if (fifo32_status(&fifo) == 0) {io_sti();} else {i = fifo32_get(&fifo);io_sti();if (i == 1) { /* 超时时间为5秒 */farjmp(0, 3*8); /* 返回任务A */timer_settime(timer_ts, 2);}}}}
但是sht_back的值需要传给B,因此,将其存入内存:
在A中存入:*((int *) 0x0fec) = (int) sht_back;在B中取出:sht_back = (struct SHEET *) *((int *) 0x0fec);
注:也可以作为函数的参数进行传递。
接下来对这个任务进行优化。B任务每次count自增都在重新写文字,导致速度慢,可以考虑每隔0.01秒甚至更长时间再刷新。
运行即可。
3.多任务进阶
可以发现前面的多任务其实都是内置在B任务中的调用实现的,其实并不算真正的多任务,真正的多任务任务本身是不会知道的。
这次尝试实现这个功能。
添加mtask.c文件,这个文件负责多任务功能。
添加两个函数实现:
void mt_init(void)/* 初始化mt_timer和mt_tr的值,并将计时器设置为0.02秒 */{mt_timer = timer_alloc();timer_settime(mt_timer, 2);mt_tr = 3 * 8;return;}void mt_taskswitch(void)/* 根据当前tr值计算下一个tr值,并设置定时器实现程序切换 */{if (mt_tr == 3 * 8){mt_tr = 4 * 8;}elsemt_tr = 3 * 8;timer_settime(mt_timer, 2);farjmp(0, mt_tr);return;

然后修改timer.c中的中断处理函数,当中断是由于mt_timer造成的话,就不写入队列,直接将ts设置为0,接着判断如果ts不为0则调用mt_taskswtich切换任务。
void inthandler20(int *esp){char ts = 0;int i;struct TIMER *timer;io_out8(PIC0_OCW2, 0x60);//把IRQ-00信号接收完毕的消息通知给PICtimerctl.count++;//每次中断计时器自加if (timerctl.next > timerctl.count){return;//未到下一时刻,结束}timer = timerctl.t0;//将第一个地址赋值给timerfor (i = 0; i < timerctl.using; i++)//timers中的定时器都处于动作中,所以不确认flags{if (timer->timeout > timerctl.count){break;//一旦遇到未超时的定时器就跳出循环}//超时timer->flags = TIMER_FLAGS_ALLOC;if (timer != mt_timer){fifo32_put(timer->fifo, timer->data);}elsets = 1;timer = timer->next;}//移位timerctl.t0 = timer;//timerctl.next设定timerctl.next = timerctl.t0->timeout;//还有活动的定时器if (ts != 0){mt_taskswitch();}return;}
这里使用ts进行标志而不是直接在else中调用mt_taskswitch是因为进行切换时,即使中断未完成,IF的值也可能被重设为1,引起错误。
最后修改a,b任务,删掉任务切换部分即可。