《30天自制操作系统》15_day_学习笔记

harib12a:
  这一部分我们来尝试两个任务的切换。下面我们一步一步的看:

  1、定义TSS任务状态段(task statuc segment);定义的一种段,需要在GDT中定义使用

//TSS任务状态段(task statuc segment)
struct TSS32 {//26个int成员,104字节
    //与任务设置相关的信息(任务切换时,除backlink,都不会被写入)
    int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
    //32位寄存器;eip任务返回时,找到返回的地址
    int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
    //16位寄存器
    int es, cs, ss, ds, fs, gs;
    //第一行一样,有关任务设置的信息。任务切换时CPU不写
    //ldtr = 0; iomap = 0x4000_0000
    int ldtr, iomap;
};

  2、尝试两个任务的切换。A和B

//(1)、两个任务定义
  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中定义3号、4号
    set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);
    set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);

  3、TR(task  register)寄存器让CPU记住当前运行哪一个任务(GDT中任务号*8)

    //HariMain
    load_tr(3 * 8);
    //naskfunc.nas
    _load_tr:        ; void load_tr(int tr);
        LTR        [ESP+4]            ; tr
        RET
    //任务切换函数
    _taskswitch4:    ; void taskswitch4(void);
        JMP        4*8:0;4*8用来指向TSS。这个是任务段啊!哥哥
        RET        ;从汇编语言返回到C语言执行

  4、程序执行10s后进行任务切换

void HariMain(){
    //.......B的任务栈
    tss_b.esp = task_b_esp;//B的任务栈
    task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
    //.......
    else if (i == 10) { /* 10秒定时器超时 */
        putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
        taskswitch4();  //进行任务切换
    } else if (i == 3) { /* 3昩僞僀儅 */
    //......
}

harib12b:
  上一步,我们让任务B切换到任务A;下面再切回任务A 。改写task_b_main();最后调用taskswitch3()切回任务A:

void task_b_main(void)
{    //.....
    timer = timer_alloc();
    timer_init(timer, &fifo, 1);
    timer_settime(timer, 500);
    //....定时器超时时间为5s,5s后切回任务A。3号
    taskswitch3();
}   //  跳到GDT中3号段的位置
_taskswitch3:    ; void taskswitch3(void);
    JMP        3*8:0
    RET

harib12c:

  上面我们进行了任务A、B之间的切换,方法是为A\B都写一个任务入口的函数,在这个函数中调用teskswitch();然而在真正的系统中,如果我们有100个任务,难道我们要去为这100个任务写100个处理函数?显然这不是我们想要的,接下来我们来实现一个通用函数来进行任务切换。

;函数:farjmp(eip,cs);
;例如:farjmp(0,4*8);表示切换到GDT的4号段的任务
_farjmp:        ; void farjmp(int eip, int cs);
    JMP        FAR    [ESP+4]       ; eip, cs
    RET

  接着准备一个任务切换的计时器。timer_ts,用每0.02s执行一次任务的切换。每次farjmp切换返回的时候,将定时器重新设定到0.02s后,让程序返回0.02s后再次执行任务切换。

//HariMain部分代码:
load_tr(3 * 8)//初始化TR寄存器位3号,任务A运行
for (;;) {
    //...
    farjmp(0, 4 * 8);//跳到4号,任务B
    timer_settime(timer_ts, 2);//延时0.02s
    //...
}
void task_b_main(void)//任务B做的事情。
{    //...
    farjmp(0, 3 * 8);跳到3号,任务A
    timer_settime(timer_ts, 2);//这个好像没有执行啊!!!???
}

harib12d:
  sht_back背景图层实在HariMain中定义的。怎么让void task_b_main(void)知道sht_back的值?我们将将sht_back的地址写到内存0x0fec中。然后再task_b_main(void)中读出来:

//HariMain将sht_back写到内存中。
*((int *) 0x0fec) = (int) sht_back;
//task_b_main将sht_back的值读出来
sht_back = (struct SHEET *) *((int *) 0x0fec);

harib12e:

  我们发现虽然上面实现任务之间的不断切换,但是在真机上的运行时间太慢了。因为count每计数一次就刷新一次。然而我们并不要求刷新这么频繁,人眼是分辨不出来的。我们只需要0.01s刷新一次就行了/。修改task_b_main()
  sht_back从HM中传过来的方法:将sht_back的地址放到任务B段的起始位置,这样任务B在执行task_b_main时,会把开始的4个字节的数据当作参数*sht_back的地址。另外,task_b_main实际上就是任务B执行的内容。没有任何函数调用,不用return来返回调用处。

void task_b_main(struct SHEET *sht_back)
{
    struct FIFO32 fifo;          //32位的FIFO缓冲区
    struct TIMER *timer_ts, *timer_put;//两个定时器
    int i, fifobuf[128], count = 0;
    char s[12];

    fifo32_init(&fifo, 128, fifobuf);//FIFO缓冲区初始化fifobuf[128]
    timer_ts = timer_alloc();     //任务切换定时器,0.02s
    timer_init(timer_ts, &fifo, 2);  //数据为2
    timer_settime(timer_ts, 2);

    timer_put = timer_alloc();    //刷新(输出)定时器,0.01s
    timer_init(timer_put, &fifo, 1); //数据为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 == 1) {           //数据为1,刷新定时器超时
                sprintf(s, "%11d", count);
                putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);
                timer_settime(timer_put, 1);//输出后,在设定定时器1
            } else if (i == 2) {       //数据为2,任务切换定时器。
                farjmp(0, 3 * 8);      //切换任务后再设定定时器2
                timer_settime(timer_ts, 2);
            }
        }
    }
}

harib12f:
  上面我们将刷新频率固定到0.01s一次。这里我们来测试一些程序的运行速度。在task_b_main中加入一些内容:

void task_b_main(struct SHEET *sht_back)
{    //......增加一个定时器
    timer_1s = timer_alloc();
    timer_init(timer_1s, &fifo, 100);
    timer_settime(timer_1s, 100);
    //.....
    if (i == 100) {    //100号;10s
    //这里没100次中断,显示count计数的值。
    sprintf(s, "%11d", count - count0);
    putfonts8_asc_sht(sht_back, 0, 128, COL8_FFFFFF, COL8_008484, s, 11);
    count0 = count;
    timer_settime(timer_1s, 100);
            }
    //........
}   //接下来笔者把显示计数的定时器去掉,重新测试了一下
    //发现速度的确有所提升,这里主要是要知道测试的方法。*****

harib12g:
  我们上面的多任务实在HM和TB中写入任务切换来实现的。下面来创建真正的多任务

  1、创建多任务函数

//mtask.c文件
#include "bootpack.h"
struct TIMER *mt_timer;
int mt_tr;
//初始化*mt_timer;mt_tr;
void mt_init(void)
{
    mt_timer = timer_alloc();//为定时器mt_timer分配内存
    //我们不再需要向FIFO中写数据了
    timer_settime(mt_timer, 2);//设置超时时间0.02s
    mt_tr = 3 * 8;//将要写到GDT的3号段
    return;
}
void mt_taskswitch(void)
{
    if (mt_tr == 3 * 8) {
        mt_tr = 4 * 8;
    } else {
        mt_tr = 3 * 8;
    }
    timer_settime(mt_timer, 2);//再定时0.02s
    farjmp(0, mt_tr);//任务切换
    return;
}

  2、修改定时器中断inthandler20()

void inthandler20(int *esp)
{    //.......
    for (;;) {
        /* timers的计时器全部在工作中,不需要flag */
        if (timer->timeout > timerctl.count) {
            break;           //没有超时,不发生中断。回去
        }
        /* 超时了,下面进行中断处理 */
        timer->flags = TIMER_FLAGS_ALLOC;
        if (timer != mt_timer) {  //发生中断的定时器不是*mt_timer;
                          //向FOFO写数据
            fifo32_put(timer->fifo, timer->data);
        } else {
        //*mt_timer;不用想FIFO写数据
            ts = 1;          /* mt_timer超时了 */
        }
        timer = timer->next;     /* 下一个计时器 */
    }
    timerctl.t0 = timer;       //t0放定时器链表的第一个地址
    timerctl.next = timer->timeout;//下一个定时器的超时时间。
    if (ts != 0) {
        //ts就是用来进行任务A\B切换的标志
        mt_taskswitch();
    }
    return;
}

  3、接下来,删除bootpack.c中原来的任务A.B切换的代码即可!

  QUE:为什么不在tm_timer超时的地方直接调用mt_taskswitch();?
  ANS:调用mt_taskswitch();进行任务切换的时候,即便中断还没有处理完成,IF(中断允许标识)可能会被重设回1,(因为任务切换的时候同时会被切换EFLAGS),这样,在定时器中断还没有处理完成的时候,会产生下一个中断请求,导致程序出错。记住:任务可以切换,中断时不能切换的。中断必须处理完成后,才能处理下一个中断,不然会有意想不到的错误。

时间: 2024-10-24 22:22:27

《30天自制操作系统》15_day_学习笔记的相关文章

《30天自制操作系统》学习笔记-第1天

为了加深对操作系统的理解,我决定照着<30天自制操作系统>这本书实践一下.项目的github链接是https://github.com/YatesXu/YatesOSASK/ 关于十六进制编辑器 第一个问题就是书中给的十六进制编辑器是日文的,在我的电脑上打开之后是一片乱码,于是我比较之后选用了这个十六进制编辑器wxMEdit,链接是https://wxmedit.github.io/. 另外,visual studio也可以,但是express版不能用,所以还是选用免费开源的软件吧(笑) (伪

《30天自制操作系统》读书笔记(3) 引入C语言

这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是乱码, 而且代码的分隔用了两个Tab, 在这里要处理一下: :%s/;.*//g 删除所有的注释; :%s/\t\t/\t 把两个Tab替换为一个Tab; 要让作者的nas文件和asm文件拥有相同的语法规则, 在_vimrc文件的最后一行添加 au BufNewFile,BufRead *.nas

《30天自制操作系统》读书笔记(2)hello, world

让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实上你用的是U盘还是软盘对我们的操作系统没有影响,缺点是你的U盘刷入系统后容量只能是1440 MB,即当年流行的3.5英寸软盘的大小,当然不用担心,再格式化一次(用DiskGeniu),就可以恢复. 我做事情的话,总是怕自己的努力的结果白费了,害怕辛辛苦苦看完这本书但是发现做出来的东西现在根本没法用,

《30天自制操作系统》读书笔记(4) 绘图

暑假果然是滋生懒散的温床. (╯‵□′)╯︵┻━┻ 好久不动都忘记之前做到哪里了, 上次好像做到了C语言的引入, 这一节所做的东西都相当轻松, 将会绘制出操作系统的基本界面. 绘图的原理 按照书中所说, 将值写入到显存中就能在屏幕上显示相应的像素, 在asmhead.nas 中有这一段: 1 CYLS EQU 0x0ff0 ; 设定启动区 2 LEDS EQU 0x0ff1 3 VMODE EQU 0x0ff2 ; 关于颜色数目的信息,颜色的位数 4 SCRNX EQU 0x0ff4 ; 分辨率

《30天自制操作系统》读书笔记(1)读前感

做一个自己的操作系统, 在我看来一直是不可以思议的,而且奇妙的,像是吉他手亲手打造一把自己的吉他? 似乎这个比喻不太恰当, 但是,感觉是一样的. <30天自制操作系统> 为日本的川和秀实先生所著, 有人说他是 "<XX天学会XXX>之类的书中为数不多的几本好书之一." 这本书的优点非常明显,通俗生趣,甚至于有点啰嗦:而且作者无私地提供了源代码而且允许你以任何方式使用,也提供了编译的所有工具,所有东西都是"开箱即用",不容易出问题. 作为后生我

《30天自制操作系统》读书笔记(5) GDT&amp;IDT

梳理项目结构 项目做到现在, 前头的好多东西都忘了, 还是通过Makefile重新理解一下整个项目是如何编译的: 现在我们拥有这么9个文件: ipl10.nas    InitialProgramLoader, 占用了软盘的第一个扇区并符合启动盘的规范, 默认被载入地址是0x7c00 到 0x7e00, 负责将10个柱面读入到0x8200到0x34fff (10个柱面共10*2*18 = 360 个扇区但是第一个没有被读入); asmhead.nas     包含一些暂时未知的设定; naskf

多定时器处理1(30天自制操作系统--读书笔记)

自认为写过很多MCU程序,但总是回头想想,我所了解的MCU编程思想大体有两种,其中具体的想法我得再找时间写下来. 总想总结出一个可扩展的,易移植的写法,但能力还没到这个层次.但<30天自制操作系统>这本书确实给我了一个思路,就像我已经写过的两篇读书笔记. 将两个独立的内容--FIFO和内存动态管理做到高度模块化,尤其是其中数据结构模型的设计更是我学习的好例子. 今天要学习的设计内容是多定时器处理.原书对这部分的处理讲的很详细,由浅入深,看得我由衷佩服作者,也可能是因为我水平低,稍稍看出点门道来

单字节的FIFO缓存(30天自制操作系统--读书笔记)

从今天起,写一些读书笔记.最近几个月都在看<30天自制操作系统这本书>,书虽说看的是电子书,但可以花钱买的正版书,既然花费了金钱,就总得有些收获. 任何人都不能总是固步自封,想要进步就得学习别人的知识,对于程序员而言,最简单的方法即是学习别人的代码. 今天的标题是“单字节的FIFO缓存”,其实就是做一个FIFO,看名字就知道了.也就4个函数和1个相关结构体,这样的小代码在嵌入式系统中很常用,也会很好用. 1.相关数据结构体 struct FIFO8 { unsigned char *buf;

《30天自制操作系统》笔记(12)——多任务入门

<30天自制操作系统>笔记(12)——多任务入门 进度回顾 上一篇介绍了设置显示器高分辨率的方法.本篇讲一下操作系统实现多任务的方法. 什么是多任务 对程序员来说,也许这是废话,不过还是说清楚比较好. 多任务就是让电脑同时运行多个程序(如一边写代码一边听音乐一边下载电影). 电脑的CPU只有固定有限的那么一个或几个,不可能真的同时运行多个程序.所以就用近似的方式,让多个程序轮换着运行.当轮换速度够快(0.01秒),给人的感觉就是"同时"运行了. 多任务之不实用版 我们首先从

《30天自制操作系统》笔记(03)——使用Vmware

<30天自制操作系统>笔记(03)--使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测试和U盘启动的一些问题.本篇就来解决之. 遇到的问题 物理机测试 简单来说,把软盘(U盘)做成启动盘后,自然想要用来启动物理机器.毕竟这才是真正的测试.(用QEMU总没多大的成就感)但物理机测试的麻烦在于太慢了,每次都要关掉Windows,重启,测试,然后再重启Windows.而且还没办法截图. 而用Vmwa