C语言三十天自制操作系统(5)

第14天

眼前大家的操作系统使用的分辨率为320*200,大家得以想艺术把分辨率升高上去。以前设置分辨率的时候是用ah
= 0; al =
画面形式;设置的。更大一些的镜头模试叫作VBE。在很早时候电脑规格是由IBM集团制定的
,当然也确定了显卡画面情势,各家显卡集团就以IBM的科班营造显卡。可是后来显卡集团的技巧能力当先了IBM,原来的显卡标准早已不适用了,各家显卡公司就制定了协调的业内。为了让操作系统和应用程序能使用各家店铺的显卡,各家显卡集团一同起来建立了VESA(Video
Electronics Standards
Association),也就是视频电子标准协会。那几个社团制定了体现通用的设定方法,也创建了专门的BIOS。这一个BIOS被称作VESA
BIOS extension,简称为VBE。切换来VBE使用ax = 0x4f02; bx = 画面格局。

  • 0x101 6404808位彩色
  • 0x103 8006008位彩色
  • 0x105 10247688位彩色
  • 0x107 128010248位彩色

想要提升分辨率要先查询一下机械帮衬不接济VBE的来得方式。

MOV     AX,0x9000
MOV     ES,AX
MOV     DI,0
MOV     AX,0x4f00
INT     0x10
CMP     AX,0x004f
JNE     scrn320

先把es赋值为0x9000,di赋值为0,ax赋值为0x4f00,然后int
0x10。如若ax变为0x004f的话就印证有VBE不是其一值的话就不得不动用320*200的分辨率了。

接下去检查VBE的本子是或不是2.0之上,如若不是2.0之上那也不可能动用高分辨率。

MOV     AX,[ES:DI+4]
CMP     AX,0x0200
JB      scrn320 

接下来再检查0x105的镜头格局能无法应用

MOV     CX,VBEMODE
MOV     AX,0x4f01
INT     0x10
CMP     AX,0x004f
JNE     scrn320

好,如果申明0x105可以应用了,我们再确认0x105的画面音信,首要的音讯有6个。

  • word[es:di+0x00] 格局属性,bit7不是1就倒霉办(能加上0x4000)
  • word[es:di+0x12] X的分辨 率
  • word[es:di+0x14] Y的分辨率
  • byte[es:di+0x19] 颜色数,必须为8
  • byte[es:di+0x1b] 颜色的指定方法,必须为4,调色板格局
  • word[es:di+0x28] VRAM的地址

下边的6个举足轻重性质我们只要确定3个,都是不利的未来,就足以把那么些音信定入指定的内存了,然后跳过scrn320程序段直接进入高级画面格局了。

接下去做键盘输入的拍卖。大家已经得以从键盘中断处理程序中取到键盘控制寄存器中的值了。那一个值我实际对应哪个按键被按下或者松手有一个遥相呼应的报表。其中按键松手时的值为按键按下时值加上0x80。可以创建一个数组,从0伊始根据这一个表格把键盘的扫描码换成字符的ASCII码。以前大家早就用ASCII码创制过字体文件,然后按照取得的ASCII码在屏幕上显得出来。

一旦我们把光标按照键盘输入情形左右活动也很简单。首先定义一个cursor_x变量,用于存储光标的岗位。一早先鼠标靠近窗口的最左侧。然后判断键盘输入的按键是或不是是要求出示的按键,如果必要展示,那么在cursor_x地点开写入对应的字符,然后cursor_x+8,然后在新的cursor_x地方再次画出光标。

那本书还落到实处了按住鼠标拖动完成鼠标跟着鼠标指针动。然而跟大家平日windows下活动鼠标的两样,这里只是简短得已毕。处理鼠标循环的有的中,先判断鼠标左键是或不是早就按下,借使按下,那把窗口及时移动到鼠标所在的地方。

第15天

这一天想艺术落实多任务。所谓的多任务就是CPU在全速得切换种种职分,使电脑使用者感到CPU在同时处理分歧的任务。切换职责的速度无法太快也无法太慢。因为切换义务也是有本钱的,要是太快,切换任伤的花费就太大,借使太慢,还不如不要多作务,因为把应太慢。

当CPU处理任务切换时,会先把寄存器中的值全部写入内存,然后把运行另一个义务所须求的CPU寄存器的值从内存中读取出来,那样就成功了一遍切换。那写入内存和读取内存所必要的年华就一职责切换所急需的支付。
寄存器写入内存的数据结构叫做“义务处境段”(task status segment)。

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

TSS32结构体中共有26个int变量,合计104字节第一行保存的不是寄存器数据,而是与职务育设置
相关的其余数据。第二行是32位寄存器。第二行是16位寄存器,但是大家仍然用32位内存空间存储它。第4行也是与职分有关的其他设置。我们暂时将ldtr设置为0,将iomap设置为0x40000000。

要开展义务切换要用jmp指令。jmp指令分三种,第一种只改写EIP也就是所谓的near格局;第两种改写cs和eip,就是所谓的far情势。假如一条jmp指令的靶子地方段不是可举行的代码,而是tss的话,cpu就不会履行日常的改写cs和eip,而是将那条指令驾驭为义务切换。CPU中还有一个TR寄存器,task
register,它的听从是让CPU记住当前正在运转哪一个职务,大家给TR赋值的时候,必须把GDT编号乘以8。给TR赋值无法用MOV指令,有一个特其他下令:LTR。上边大家看一下tss结构体怎么着赋值:

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;
tss_b.gs = 1 * 8;

我们先从后6个段寄存器赋值开首看,大家给cs赋值为GDT2号,其余是GDT1号,和bootpack.c使用了平等的地址段。然后是eip,大家把task_b_main的函数地址赋给它。然后是esp,也就是栈地址,我们新申请了64K内存空间,给taskB。假若和原来的主函数使用相同的栈那么些切换任务的时候势必会出难题。

我们先这么尝试多职责:在主函数定时器10秒超时的时候切换来职分b,然后在职分b的定时候5秒超时的时候切换回职责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_sti();
      io_hlt();
    } else {
      i = fifo32_get(&fifo);
      io_sti();
      if (i == 1) { 
        taskswitch3(); 
      }
    }
  }
}

已经落实了三个职分之间的跳转。接下来大家完毕A,B七个义务每过0.02秒就变换四回。先安装一个定时器time_ts变量,超时的光阴为0.02秒,就算超时向队列中发送0x2。义务A可以承受鼠标和键盘输入的,大家很不难确认职分A是不是在运行,难题就出在义务B大家什么样规定职责B也能正常运转吧?在职责B中装置一个计数器,每0.01秒在显示屏上写出计数器的数值就足以了。
唯独遭受一个题材,怎样才能让职务B知道sht_back,唯有让任务B知道那一个地方才能在桌面上显示数字。大家应用栈来传递数据。首先要精通C语言的函数传递参数就是用栈,比如C语言的函数void
setA(int
a);那一个函数被调用后,函数会从esp+4的内存中取出a这些数值。我们那边就选择C语言的那些特点。在跳到职分B的时候传递一个参数,职务B的函数评释为
void task_b_main(struct SHEET
*sht_back);那样运行职分B的时候可以一贯利用sht_back这一个变量。大家先将任务B的esp减去8,然后把esp+4内存地址的值赋为sht_back,那样就可以了,职责B函数在运用sht_back变量值的时候就是esp+4。

眼前大家都是在任务中平昔写切换职责的程序段,如果要真正贯彻多义务最好是写一段程序调动职责之间的切换,而不是证职务协调切换。

struct TIMER *mt_timer;
int mt_tr;

void mt_init(void)
{
  mt_timer = timer_alloc();
  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;
 }

如上是兑现任务切换的函数,那里安装了一个0.02秒的计时器,然后在计时器中断里如若出现这一个中断时调用mt_taskwitch这么些函数就足以了,五个职分的程序中就不须求协调写切换职务的顺序了。

第13天

在这前边的主次中老是新安装一个定时器都要开创一个队列与之对应,那样功用低而且导致操作系统主程序中逻辑复杂。大家先想方法把定时器的音信队列合并。怎么统一呢?首先想转手大家怎么事先要把定时器的音信队列分开设置,为种种定时器分配一个行列呢。重如果因为每个定时器超时所对应的操作差别,因而为了分化差异定时器超时操作,所以才把队列分开。那假诺大家给各样定时器往队列中放入数据的值都不可同日而语,再每个定时器超时的时候先判断队列中的数据属于哪个计时器,大家就足以独家开展差距的操作了,也高达了事先的当前。而且代码肯定更简便了,起码不用作那么多判断了。

接下去还要做过多计时器的优化办事,不过大家前几天还没有设想怎么样测试优化的结果。大家得以这么,在计时器3秒中断的时候把count设置为0,一向大力计数,然后在10秒中断的时候甘休计数,并把结果显示在窗口中。如果数字越大表达系统推行进程越快,质量也就越好。

俺们事先有别定时器中断时候,是用向信息队列中发送数据的例外来区分的。那能无法我们把鼠标和键盘使用的音讯队列也跟定时器合在一起,也用向新闻队列中传递的数据来区分呢?下边大家定义一下暂停类型。

  • 0~1 光标闪烁定时器
  • 3 3秒定时器
  • 10 10秒定时器
  • 256~511 键盘输入(从键盘控制器读入的值再拉长256)
  • 512~767 鼠标输入(从键盘控制器读入的值再增进 512)

俺们事先的队列定的采纳的数据类型为char唯有8位,现在我们将其改为int类型。其余中央没有怎么变动。只是在队列中读入键盘和鼠标灵数据的时候要分别减去256和512。

在处理定时器中断的时候根本的小时支付是找下一个逾期定时器之后的活动操作,大家就是要想艺术打消活动操作。

大家在TIMER结构体中新定义一个TIMER指针,指向下一个快要超时的定时器。

struct TIMER {
  struct TIMER *next;
  unsigned int timeout, flags;
  struct FIFO32 *fifo;
  int data;
};

事实上就是把线性表改造成链表操作,中断处理程序这么改写

void inthandler20(int *esp)
{
  int i;
  struct TIMER *timer;
  io_out8(PIC0_OCW2, 0x60);
  timerctl.count++;
  if (timerctl.next > timerctl.count) {
    return;
  }
  timer = timerctl.t0; //首先把最前面的地址赋给timer
  for (i = 0; i < timerctl.using; i++) {
    //因为timers的定时器都处于运行状态,所以不确认flags
     if (timer->timeout > timerctl.count) {
      break;
    }
    //超时
    timer->flags = TIMER_FLAGS_ALLOC;
    fifo32_put(timer->fifo, timer->data);
    timer = timer->next; //下一个定时器的地址赋给timer
  }
  timerctl.using -= i;
/*新移位 */
  timerctl.t0 = timer;
  /* timerctl.next的设定*/
  if (timerctl.using > 0) {
    timerctl.next = timerctl.t0->timeout;
  } else {
    timerctl.next = 0xffffffff;
  }
  return;
}

定时器设置函数

 void timer_settime(struct TIMER *timer, unsigned int timeout)
{
  int e;
  struct TIMER *t, *s;
  timer->timeout = timeout + timerctl.count;
  timer->flags = TIMER_FLAGS_USING;
  e = io_load_eflags();
  io_cli();
  timerctl.using++;
  if (timerctl.using == 1) {
    timerctl.t0 = timer;
    timer->next = 0; 
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  t = timerctl.t0;
  if (timer->timeout <= t->timeout) {
    timerctl.t0 = timer;
    timer->next = t;
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  for (;;) {
    s = t;
    t = t->next;
    if (t == 0) {
      break; 
    }
    if (timer->timeout <= t->timeout) {
      s->next = timer; 
      timer->next = t; 
      io_store_eflags(e);
      return;
    }
  }
  s->next = timer;
  timer->next = 0;
  io_store_eflags(e);
  return;
}

虽说先后变长了,但是由于引入了链表的定义,不再须要做活动操作,在定时器多的情状下,功能绝比较线性表逐步移位要快很多。

分析一下下边程序变长的案由。重假诺插入链表的时候暴发了4种情景:1、运行中的定时器唯有一个;2、插入到最前边的情状;3、插入到中游的景况;4、插入到最终的情况。

为了减小插边链表时候所考虑的情景,大家引入了哨兵的概念。也就是设置一个永无存在且连续在最终到期的定时器。有了这一个定时器之后,插入链表的时候就唯有2种状态了,插入到最前边的景色和插入到中游的图景。

修改将来的定时器设置函数

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
  int e;
  struct TIMER *t, *s;
  timer->timeout = timeout + timerctl.count;
  timer->flags = TIMER_FLAGS_USING;
  e = io_load_eflags();
  io_cli();
  t = timerctl.t0;
  if (timer->timeout <= t->timeout) {
    timerctl.t0 = timer;
    timer->next = t; 
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  for (;;) {
    s = t;
    t = t->next;
    if (timer->timeout <= t->timeout) {
      s->next = timer; 
      timer->next = t; 
      io_store_eflags(e);
      return;
    }
  }
}

可以看来简化了不胜枚举。简化后的中断处理函数

void inthandler20(int *esp)
{
  struct TIMER *timer;
  io_out8(PIC0_OCW2, 0x60); 
  timerctl.count++;
  if (timerctl.next > timerctl.count) {
    return;
  }
  timer = timerctl.t0; 
  for (;;) {
    if (timer->timeout > timerctl.count) {
      break;
    }
    timer->flags = TIMER_FLAGS_ALLOC;
    fifo32_put(timer->fifo, timer->data);
    timer = timer->next; 
  }
  timerctl.t0 = timer;
  timerctl.next = timer->timeout;
  return;
}