十五天的内容开始啦!
本书的进度大概已经到一半,今天要开始讲一个非常重要同时也挺难的东西,那就是多任务!
多任务我们都很清楚,但一步步实现多任务的过程实在不容易,请跟紧思路哦!
首先,我们考虑电脑是如何完成多任务这样的功能呢?对了,就是把时间分成一片片,再分给一个个任务,让其执行。而分时间片的工作交给之前一直在研究的timer来实现。就像这样:
好的,现在我们有了时间片,那接下来呢?如何在一个程序的timer超时后,切换到另一个程序呢?
讲切换任务的时候,我们引入TSS“任务状态段”,即Task Status Segment。TSS中的内容包括两部分,第一部分是与任务设置相关的信息,第二部分是各种段寄存器,通用寄存器。为什么要保存寄存器的值呢?在任务切换的时候,我们应该像调用子程序时一样,应该先保存断点。而这里,因为是任务之间的切换,所以大部分寄存器都要重新保存。
那么有了TSS之后呢?TSS不过是记录了一些设置信息和断点信息,如何记性任务切换呢?
要进行任务切换,我们执行JMP 指令。如果JMP后面的地址是far地址,并指向TSS的话,CPU就不会执行通常的改写EIP和CS的操作,而是将此指令理解为任务切换。而为了实现这样的功能CPU在每次执行带有段地址的指令时,都回去确认一下GDT中的设置来判断是正常的JMP,还是任务切换。
这里还要提到一个寄存器,TR,“task register”,专门来存当前执行的task的TSS段址,它能让CPU记住现在正在运行哪一个任务。而TR里面的值是TSS在GDT中的编号*8。
TR寄存器的内容书上介绍的不是很仔细,这里引用CSDN上一个博主的文章:
任务寄存器tr保存 16 位的段选择子、32 位基地址、16 位段界限和当前任务的 TSS属性。它引用 GDT 中的 TSS 描述符。基地址指明 TSS 的第一个字节(字节 0)的线性地址,段界限确定 TSS 的字节个数。TR寄存器包含了当前正在CPU运行的进程的TSSD(任务段描述符)选择符。也包含了两个隐藏的非编程域:TSSD的base 和limit域。通过这种方式处理器就能直接对TSS寻址,而不用从GDT中索引TSS的地址
TR寄存器---->GDT中的TSS描述符---->硬件上下文的具体数据。任务切换中
cpu会把当前寄存器的数据保存到当前(旧的)tr寄存器所指向的tss数据结构里,然后把新的tss数据复制到当前寄存器里。这些操作是通过cpu的硬件实现的
引用结束。好了,现在我们知道了TS的作用,继续回到我们的多任务的内容中来。
接下来,作者尝试编写第一个多任务的程序。这里只有两个任务,A是主线的,负责鼠标,键盘输入等,B基本什么都不做。关键代码如下:
void HariMain(void) { struct TSS32 tss_a, tss_b; for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_stihlt(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 2) { farjmp(0, 4 * 8); timer_settime(timer_ts, 2); } } } } 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; fifo32_init(&fifo, 128, fifobuf); timer_ts = timer_alloc(); timer_init(timer_ts, &fifo, 1); timer_settime(timer_ts, 2); 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); io_cli(); if (fifo32_status(&fifo) == 0) { io_sti(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 1) { /* タスクスイッチ */ farjmp(0, 3 * 8); timer_settime(timer_ts, 2); } } } }
具体的源代码就不附上了。明天的多任务会引进更多内容,准备好接受挑战吧!