PK使人进步:OS 多任务调度内核揭秘

[复制链接]
11217|43
 楼主| highgear 发表于 2011-10-26 22:47 | 显示全部楼层 |阅读模式
mcu 上使用多任务OS 的争论曾经热火朝天,我也偶尔参与了一些探讨,现在还时不时还冒出几个关于 OS 的帖子。我想在这里给大家展示一个精简的调度内核,希望能给有心学习 os 的朋友对多任务有一个基本的认识, 虽然,严格地说,这个多任务调度内核算不上真正的 OS

网上各种多任务os 的源代码很多,但那些代码对于老鸟阅读理解都很费力,更不用说那些初学者。所以一个简洁易懂的代码更有意义,这也是我做这个多任务调度内核的想法。这个多任务调度内核非常简单,仅有几个函数,200行左右的纯 C代码,没有任何汇编程序,在 Turbo C 下运行。本打算在Borland C++ 5 下编写,不料 BC++ 竟然不支持16bit 源代码调试,所以改在 Turbo C 2.0 下编译。大部分学过 C 的人都会TC,而且 TC 短小精悍,不足之处是不支持鼠标。




什么是操作系统:
http://zh.wikipedia.org/wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F

什么是多任务:
(http://zh.wikipedia.org/wiki/%E5%A4%9A%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86)
多任务处理是指计算机同时运行多个程序的能力。多任务的一般方法是运行第一个程序的一段代码,保存工作环境;再运行第二个程序的一段代码,保存环境;……恢复第一个程序的工作环境,执行第一个程序的下一段代码……现代的多任务,每个程序的时间分配相对平均。



    多任务调度的概念上面说的十分清楚,但是,问题是如何?如何保存工作环境,再运行第二个程序的一段代码。什么机制能够切入到第一个程序中?是中断。对于大多数普通的 cpu,  中断是一个最有效手段来打断一个程序,获取程序的控制权,是实现多任务调度内核中的内核。

8086 DOS/BIOS 中断的介绍:
http://tol.bit.edu.cn/download/b4d300d1-49f3-42e5-9782-203fd518eaa5.pdf

    DOS 中的定时中断int 08,大约 55ms 中断一次,其中还调用了int 1C。这里我们不使用int 1C, 而是直接使用 int 08, 为了以后便于更改中断时间。


    8086 DOS/BIOS的中断与8051的中断不同,是向量中断,即cpu 从中断向量表中取出中断程序的地址,然后转到中断程序。对于定时中断 08,中断程序的地址存放在0000:0020 (绝对地址 00020, 通过改写中断向量,可以指向我们自己的中断程序。要改写中断向量,可以直接读写0000:0020,或者使用TC的函数setvectgetvect.




评分

参与人数 2威望 +11 收起 理由
Cortex-M0 + 1
hotpower + 10

查看全部评分

 楼主| highgear 发表于 2011-10-26 22:50 | 显示全部楼层
本帖最后由 highgear 于 2011-10-26 22:53 编辑

首先定义一个寄存器结构

  1. struct Registers
  2. {
  3.      unsigned int bp;
  4.      unsigned int di;
  5.      unsigned int si;
  6.      unsigned int ds;
  7.      unsigned int es;
  8.      unsigned int dx;
  9.      unsigned int ex;
  10.      unsigned int bx;
  11.      unsigned int ax;
  12.      unsigned int ip;
  13.      unsigned int cs;
  14.      unsigned int flags;
  15. };
这个寄存器结构是 TC 中断例程进入和退出 push/pop 所用到的寄存器。而下面的这个结构不用多说了。



  1. struct Task
  2. {
  3.      unsigned int  sp;                断点的栈指针
  4.      unsigned int  ss;                断点的栈段
  5.      unsigned char*     stack;        当前任务的栈区
  6.      int                sleepCounter;  sleep 计数器
  7.      enum TaskState     state;     当前任务的状态
  8.      int                ID;              当前任务的id
  9. };
 楼主| highgear 发表于 2011-10-26 22:59 | 显示全部楼层
本帖最后由 highgear 于 2011-10-26 23:25 编辑

重要的核心函数有下列4个:
CreateTask
RunTasks
Timer_Interrupt
SwitchTask

我分别一一解释,以便大家能理解。


  1. int CreateTask(int id, TaskCallback task)
  2. {
  3.      struct Registers* r;
  4.      if (taskIndex >= TASK_COUNT || taskIndex < 0)
  5.          return 0;

  6.      disable();
  7.      tasks[taskIndex].ID = id;
  8.      tasks[taskIndex].stack = malloc(TASK_STACK_SIZE);
  9.      r = (struct Registers*) (tasks[taskIndex].stack + TASK_STACK_SIZE - sizeof(struct Registers));

  10.      tasks[taskIndex].sp = FP_OFF((struct Registers far *) r);
  11.      tasks[taskIndex].ss = FP_SEG((struct Registers far *) r);
  12.      r->cs = FP_SEG(task);
  13.      r->ip = FP_OFF(task);
  14.      r->ds =_DS;
  15.      r->es =_ES;
  16.      r->flags = INTERRUPT_ENABLE;
  17.      tasks[taskIndex].state = READY;
  18.      taskIndex++;
  19.      enable();
  20.      return 1;
  21. }


这个函数分配一段内存作为当前任务的stack, 由于8086 stack 自顶向下,所以任务寄存器环境指针 r 指向顶部。要被执行的任务地址task 放入 CS:IP 中,以后会由 iret 指令(中断返回) 弹出,去运行 task


 楼主| highgear 发表于 2011-10-26 23:04 | 显示全部楼层
本帖最后由 highgear 于 2011-10-26 23:35 编辑


  1. void interrupt RunTasks()
  2. {
  3.          disable(); 禁止中断
  4.          taskIndex = 0;
  5.          old_TimerInterrupt = getvect(TIMER_INTERRUPT_NUMBER);保存旧的中断向量
  6.          setvect(TIMER_INTERRUPT_NUMBER, Timer_Interrupt);更改中断向量
  7.          mainTask_ss = _SS; 保存主程序的堆栈。     
  8.          mainTask_sp = _SP;
  9.          _SS = tasks[taskIndex].ss; 把任务栈设为 SS:SP
  10.          _SP = tasks[taskIndex].sp;
  11.          enable(); 允许中断
  12. }


注意这个函数使用了
interrupt,表明这个函数是中断例程,但RunTasks通过普通调用而不是通过中断来执行。记得上面的CS:IP 中的内容么?是任务指针。使用了 interrupt 这个关键字后, RunTasks 将会pop bp ax 的寄存器,最后由 iret 弹出栈中的断点地址(现在已经是 task 的地址了)和状态寄存器,返回到断点处。



  1. void interrupt Timer_Interrupt()
  2. {
  3.          (* old_TimerInterrupt)(); 执行原来的中断程序     
  4.          if (mainTaskRunning) { 判断中断断点的来源
  5.                    mainTask_ss = _SS; 断点来自主程序
  6.                    mainTask_sp = _SP;
  7.          }
  8.          else {
  9.                    tasks[taskIndex].ss = _SS; 断点来自任务
  10.                    tasks[taskIndex].sp = _SP;
  11.                    if (tasks[taskIndex].state == RUNNING)
  12.                             tasks[taskIndex].state = READY;
  13.          }
  14.          CheckSleepCounter(); 处理进入 sleep 状态任务的计数器

  15.          if (FindNextTask() < 0) { 查找下一个就绪态的任务
  16.                    mainTaskRunning = 1; 没有就绪的任务,则返回到主任务
  17.                    _SS = mainTask_ss; 恢复主任务环境
  18.                    _SP = mainTask_sp;
  19.          }
  20.          else {
  21.                    mainTaskRunning = 0;  
  22.                    _SS = tasks[taskIndex].ss; 恢复任务环境
  23.                    _SP = tasks[taskIndex].sp;
  24.                    tasks[taskIndex].state = RUNNING; 运行任务
  25.          }
  26. }

这个定时中断函数是核心函数,任务的切换就是在这里完成。 在FindNextTask 这个函数里,如果加入优先级判断逻辑, 可以很容易地给这个调度器加入优先级。

另一个函数 SwitchTask Timer_Interrupt大同小异,用来在某个任务中出让 cpu, 切换到下一个就绪任务。



 楼主| highgear 发表于 2011-10-26 23:17 | 显示全部楼层
本帖最后由 highgear 于 2011-10-27 02:10 编辑

源代码在此:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

评分

参与人数 2威望 +2 收起 理由
gdmgb520 + 1 正需要
渤海三叠浪 + 1

查看全部评分

zoomone 发表于 2011-10-26 23:23 | 显示全部楼层
刚刚在proteus上弄了一晚上51移植ucos

楼主这个明天看看:lol
渤海三叠浪 发表于 2011-10-27 07:40 | 显示全部楼层
希望能在keil下运行,否则没人看的。
airwill 发表于 2011-10-27 08:20 | 显示全部楼层
楼主很努力, 强烈鼓励!
不过 51 移植 ucos, 这样玩太费力. 看的人也一样会很费力. 也没有什么实用性.
想玩 UCOS, 还是上 arm 级别的芯片吧
icecut 发表于 2011-10-27 08:59 | 显示全部楼层
希望能在keil下运行,否则没人看的。
渤海三叠浪 发表于 2011-10-27 07:40


谁爱看谁看....不要钱白给代码,已经很高尚了.
ginger8 发表于 2011-10-27 09:45 | 显示全部楼层
牛啊,学习了
HORSE7812 发表于 2011-10-27 10:07 | 显示全部楼层
楼主厉害呀,向楼主学习
baiyunfei.k.f 发表于 2011-10-27 11:07 | 显示全部楼层
:victory:
Cortex-M0 发表于 2011-10-27 12:33 | 显示全部楼层
好贴,墙裂帮顶~~~
airwill 发表于 2011-10-27 13:15 | 显示全部楼层
本帖最后由 airwill 于 2011-10-27 13:17 编辑

作为教程, 这些代码的结构性好, 可读性不错. 值得看看

但是执行效率, 本人不敢恭维了, 感觉不大合适跑 51!
Soleagent 发表于 2011-10-27 14:37 | 显示全部楼层
KO使人落后
mcu5i51 发表于 2011-10-27 14:54 | 显示全部楼层
条例清晰;挺好的
sysdriver 发表于 2011-10-27 16:59 | 显示全部楼层
首先定义一个寄存器结构
  
struct Registers
{
     unsigned int bp;
     unsigned int di;
     unsigned int si;
     unsigned int ds;
     unsigned int es;
     unsigned int dx;
     unsigned int ex;
...
highgear 发表于 2011-10-26 22:50

怎么插入代码的,很整齐啊
天凉好个秋 发表于 2011-10-27 17:38 | 显示全部楼层
在 Turbo C 下运行
hqgboy 发表于 2011-10-27 19:05 | 显示全部楼层
kobesff 发表于 2011-10-27 19:22 | 显示全部楼层
教程贴都要支持。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

1222

帖子

61

粉丝
快速回复 在线客服 返回列表 返回顶部