打印

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

[复制链接]
8807|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 编辑

首先定义一个寄存器结构
 
struct Registers
{
     unsigned int bp;
     unsigned int di;
     unsigned int si;
     unsigned int ds;
     unsigned int es;
     unsigned int dx;
     unsigned int ex;
     unsigned int bx;
     unsigned int ax;
     unsigned int ip;
     unsigned int cs;
     unsigned int flags;
};
这个寄存器结构是 TC 中断例程进入和退出 push/pop 所用到的寄存器。而下面的这个结构不用多说了。


 
struct Task
{
     unsigned int  sp;                断点的栈指针
     unsigned int  ss;                断点的栈段
     unsigned char*     stack;        当前任务的栈区
     int                sleepCounter;  sleep 计数器
     enum TaskState     state;     当前任务的状态
     int                ID;              当前任务的id
};

使用特权

评论回复
板凳
highgear|  楼主 | 2011-10-26 22:59 | 只看该作者
本帖最后由 highgear 于 2011-10-26 23:25 编辑

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

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

int CreateTask(int id, TaskCallback task)
{
     struct Registers* r;
     if (taskIndex >= TASK_COUNT || taskIndex < 0)
         return 0;

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

     tasks[taskIndex].sp = FP_OFF((struct Registers far *) r);
     tasks[taskIndex].ss = FP_SEG((struct Registers far *) r);
     r->cs = FP_SEG(task);
     r->ip = FP_OFF(task);
     r->ds =_DS;
     r->es =_ES;
     r->flags = INTERRUPT_ENABLE;
     tasks[taskIndex].state = READY;
     taskIndex++;
     enable();
     return 1;
}


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


使用特权

评论回复
地板
highgear|  楼主 | 2011-10-26 23:04 | 只看该作者
本帖最后由 highgear 于 2011-10-26 23:35 编辑

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


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


 
void interrupt Timer_Interrupt()
{
         (* old_TimerInterrupt)(); 执行原来的中断程序     
         if (mainTaskRunning) { 判断中断断点的来源
                   mainTask_ss = _SS; 断点来自主程序
                   mainTask_sp = _SP;
         }
         else {
                   tasks[taskIndex].ss = _SS; 断点来自任务
                   tasks[taskIndex].sp = _SP;
                   if (tasks[taskIndex].state == RUNNING)
                            tasks[taskIndex].state = READY;
         }
         CheckSleepCounter(); 处理进入 sleep 状态任务的计数器

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

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

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



使用特权

评论回复
5
highgear|  楼主 | 2011-10-26 23:17 | 只看该作者
本帖最后由 highgear 于 2011-10-27 02:10 编辑

源代码在此:

os.zip (2.03 KB)

使用特权

评论回复
评分
参与人数 2威望 +2 收起 理由
gdmgb520 + 1 正需要
渤海三叠浪 + 1
6
zoomone| | 2011-10-26 23:23 | 只看该作者
刚刚在proteus上弄了一晚上51移植ucos

楼主这个明天看看:lol

使用特权

评论回复
7
渤海三叠浪| | 2011-10-27 07:40 | 只看该作者
希望能在keil下运行,否则没人看的。

使用特权

评论回复
8
airwill| | 2011-10-27 08:20 | 只看该作者
楼主很努力, 强烈鼓励!
不过 51 移植 ucos, 这样玩太费力. 看的人也一样会很费力. 也没有什么实用性.
想玩 UCOS, 还是上 arm 级别的芯片吧

使用特权

评论回复
9
icecut| | 2011-10-27 08:59 | 只看该作者
希望能在keil下运行,否则没人看的。
渤海三叠浪 发表于 2011-10-27 07:40


谁爱看谁看....不要钱白给代码,已经很高尚了.

使用特权

评论回复
10
ginger8| | 2011-10-27 09:45 | 只看该作者
牛啊,学习了

使用特权

评论回复
11
HORSE7812| | 2011-10-27 10:07 | 只看该作者
楼主厉害呀,向楼主学习

使用特权

评论回复
12
baiyunfei.k.f| | 2011-10-27 11:07 | 只看该作者
:victory:

使用特权

评论回复
13
Cortex-M0| | 2011-10-27 12:33 | 只看该作者
好贴,墙裂帮顶~~~

使用特权

评论回复
14
airwill| | 2011-10-27 13:15 | 只看该作者
本帖最后由 airwill 于 2011-10-27 13:17 编辑

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

但是执行效率, 本人不敢恭维了, 感觉不大合适跑 51!

使用特权

评论回复
15
Soleagent| | 2011-10-27 14:37 | 只看该作者
KO使人落后

使用特权

评论回复
16
mcu5i51| | 2011-10-27 14:54 | 只看该作者
条例清晰;挺好的

使用特权

评论回复
17
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

怎么插入代码的,很整齐啊

使用特权

评论回复
18
天凉好个秋| | 2011-10-27 17:38 | 只看该作者
在 Turbo C 下运行

使用特权

评论回复
19
hqgboy| | 2011-10-27 19:05 | 只看该作者
mark.

使用特权

评论回复
20
kobesff| | 2011-10-27 19:22 | 只看该作者
教程贴都要支持。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

1222

帖子

61

粉丝