📄 os.txt
字号:
uCOS-II 初级程序员指南
本文面向首次接触uC/OS-II的程序员,为他们介绍一下这个系统的一些基本特征和编程上的注意事项,并介绍几个值得了解的API。本文作者已经成功的将uC/OS-II移植到几种不同CPU之上。包括EPSON S1C33和Sunplus unSP?等,积累了丰富的经验,现在愿意和朋友们分享这些经历。希望本文的资料对于希望使用这个系统来开发的朋友有所帮助,作者乐意与您分享任何您成功的喜悦。
This passage is written for the basic programmers who are first developed with the uC/OS-II real time OS。I will talk about the basic structure of this system。And I will discuss how to use some of the useful API。I will also discuss the imp of the mutilty-tasking in uC/OS-II。
(一) uC/OS-II 简介
uC/OS-II是一种基于优先级的可抢先的硬实时内核。自从92年发布以来,在世界各地都获得了广泛的应用,它是一种专门为嵌入式设备设计的内核,目前已经被移植到40多种不同结构的CPU上,运行在从8位到64位的各种系统之上。尤其值得一提的是,该系统自从2.51版本之后,就通过了美国FAA认证,可以运行在诸如航天器等对安全要求极为苛刻的系统之上。鉴于uC/OS-II可以免费获得代码,对于嵌入式RTOS而言,选择uC/OS无疑是最经济的选择。
(二) uC/OS-II 应用程序基本结构
应用uC/OS-II,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项。
每一个uC/OS-II应用至少要有一个任务。而每一个任务必须被写成无限循环的形式。以下是推荐的结构:
void task ( void* pdata )
{
INT8U err;
InitTimer(); // 可选
For( ;; )
{
// 你的应用程序代码
…….
……..
OSTimeDly(1); // 可选
}
}
以上就是基本结构,至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就是“跑飞”了。所以,每一个任务必须被写成无限循环的形式。程序员一定要相信,自己的任务是会放弃CPU使用权的,而不管是系统强制(通过ISR)还是主动放弃(通过调用OS API)。
现在来谈论上面程序中的InitTimer()函数,这个函数应该由系统提供,程序员有义务在优先级最高的任务内调用它而且不能在for循环内调用。注意,这个函数是和所使用的CPU相关的,每种系统都有自己的Timer初始化程序。在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者OSStart()内调用Timer初始化程序,那会破坏系统的可移植性同时带来性能上的损失。所以,一个折中的办法就是象上面这样,在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数OSStartHighRdy()开始多任务后,首先执行的就是Timer初始化程序。或者专门开一个优先级最高的任务,只做一件事情,那就是执行Timer初始化,之后通过调用OSTaskSuspend()将自己挂起来,永远不再执行。不过这样会浪费一个TCB空间。对于那些RAM吃紧的系统来说,还是不用为好。
搜索更多相关主题的帖子: 程序员 指南 初级
本帖最近评分记录
flanix 总共收入 +2 精品文章 2008-5-31 21:06
UID12095 帖子6 精华0 积分102 阅读权限20 在线时间0 小时 注册时间2006-7-23 最后登录2006-7-23 查看详细资料
引用 使用道具 报告 评分 回复 TOP
lanso
技术员
个人空间 发短消息 加为好友 当前离线 2# 大 中 小 发表于 2006-7-23 21:55 只看该作者
(三) 一些重要的uC/OS-II API介绍
任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关。主要的有以下几类:
1)任务类
2)消息类
3)同步类
4)时间类
5)临界区与事件类
我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的API。下面我就来介绍比较重要的:
1) OSTaskCreate函数
这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,任务堆栈的首地址和任务的优先级。调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。
2) OSTaskSuspend函数
这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。如果挂起的是当前任务的话,那么还会引发系统执行任务切换先导函数OSShed来进行一次任务切换。这个函数只有一个参数,那就是指定任务的优先级。那为什么是优先级呢?事实上在系统内部,优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话说,优先级也就是任务的ID。所以uC/OS-II不允许出现相同优先级的任务。
3) OSTaskResume函数
这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。如果恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。
4) OS_ENTER_CRITICAL宏
很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。其实,它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。
5) OS_EXIT_CRITICAL宏
这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他们的确会破坏系统的多任务性能。
6) OSTimeDly函数
这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间后再次执行它,或者说,暂时放弃CPU的使用权。一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!
UID12095 帖子6 精华0 积分102 阅读权限20 在线时间0 小时 注册时间2006-7-23 最后登录2006-7-23 查看详细资料
引用 使用道具 报告 评分 回复 TOP
lanso
技术员
个人空间 发短消息 加为好友 当前离线 3# 大 中 小 发表于 2006-7-23 21:55 只看该作者
(四) uC/OS-II 多任务实现机制分析
前面已经说过,uC/OS-II是一种基于优先级的可抢先的多任务内核。那么,它的多任务机制到底如何实现的呢?了解这些原理,可以帮助我们写出更加健壮的代码来。由于我们面向的初级程序员,本文不打算写成又一篇uC/OS-II的源码分析,那样的文章太多了,本文打算从实现原理的角度探讨这个问题。
首先我们来看看为什么多任务机制可以实现?其实在单一CPU的情况下,是不存在真正的多任务机制的,存在的只有不同的任务轮流使用CPU,所以本质上还是单任务的。但由于CPU执行速度非常快,加上任务切换十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓的多任务机制。
由上面的描述,不难发现,要实现多任务机制,那么目标CPU必须具备一种在运行期更改PC的途径,否则无法做到切换。不幸的使,直接设置PC指针,目前还没有哪个CPU支持这样的指令。但是一般CPU都允许通过类似JMP,CALL这样的指令来间接的修改PC。我们的多任务机制的实现也正是基于这个出发点。事实上,我们使用CALL指令或者软中断指令来修改PC,主要是软中断。但在一些CPU上,并不存在软中断这样的概念,所以,我们在那些CPU上,使用几条PUSH指令加上一条CALL指令来模拟一次软中断的发生。
回想一下你在微机原理课程上学过的知识,当发生中断的时候,CPU保存当前的PC和状态寄存器的值到堆栈里,然后将PC设置为中断服务程序的入口地址,再下来一个机器周期,就可以去执行中断服务程序了。执行完毕之后,一般都是执行一条RETI指令,这条指令会把当前堆栈里的值弹出恢复到状态寄存器和PC里。这样,系统就会回到中断以前的地方继续执行了。那么设想一下?如果再中断的时候,人为的更改了堆栈里的值,那会发生什么?或者通过更改当前堆栈指针的值,又会发生什么呢?如果更改是随意的,那么结果是无法预料的错误。因为我们无法确定机器下一条会执行些什么指令,但是如果更改是计划好的,按照一定规则的话,那么我们就可以实现多任务机制。事实上,这就是目前几乎所有的OS的核心部分。不过他们的实现不像这样简单罢了。
下面,我们来看看uC/OS-II再这方面是怎么处理的。再uC/OS-II里,每个任务都有一个任务控制块(Task Control Block),这是一个比较复杂的数据结构。在任务控制快的偏移为0的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈,那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目的。
以上就是uC/OS-II的多任务实现机制,我们在这里大费笔墨谈论这个问题,是希望我们的程序员们可以善加利用这个机制,写出更健壮,更富有效率的代码来
Merphy
开发助工
个人空间 发短消息 加为好友 当前离线 1# 大 中 小 发表于 2008-11-6 14:42 只看该作者
[原创]uc/OS-II内核小析
一个月前才开始看操作系统。; F% e9 `/ z2 N# y8 m! l. S
在广州书城五楼买到邵贝贝翻译的《uc/OS-II》第二版,花了我75大洋。
_. R4 ` t U; g5 W1 y心疼啊,所以要好好的看才行。6 D! S c' n, I# W0 u
最近有些收获想发给同志们一起分享,不算成熟,望见谅。
9 I5 X" p+ N$ K0 {( z$ Y5 |2 `+ s9 K1 i6 f/ M3 D
原书第三章《内核结构》。
" F. b; x k6 h' F! P第73页——章3.00 临界段就不必多说了,用三种不同方式关中断进入临界区。2 d8 U; L' B4 i }
第76页——章3.01 任务,任务也很简单。
/ L% N {# c( P9 O0 [8 { 任务是一个无限循环;
0 p" x" v+ r W 任务完成,可以将其删除;$ ~' o" Q: m6 R6 J
每一个任务都有5种状态——睡眠、就绪、运行、等待、中断服务
" X7 S5 Z8 W; w0 z, I0 _) Z; K7 h+ Z! K: b$ U
第80页——章3.03 任务控制块
% l8 \/ V3 E, W每一个任务有一个任务控制块来保存其各种状态,以便于在各个状态之间切换。
8 y3 ]/ e! Z7 _" w+ |( W4 uOS_STK OSTCBStkPtr——系统任务控制块,堆栈指针——当前任务的堆栈栈顶。
7 W1 r" d/ l: x ( O! _: B$ o E1 d ]( m4 [
Struct os_tcb OSTCBNext——双向链表的后向指针。
7 w) |+ X) s( sStruct os_tcb OSTCBPrev——双向链表的前向指针。1 I" j: L* \, r# N" O+ A! h
, `( L' ~- A' [INT16U OSTCBDly——把任务延时若干时钟节拍,允许最大延时数。
) b4 {, R/ d( q! l' q% fINT8U OSTCBStat——如果这个变量值==OS_STAT_READY时,任务进入就绪态。
/ L+ b% U( H5 ~1 {INT8 OSTCBPrio——任务的优先级,优先级越高,数值愈小。3 a, }9 z+ f* B; M V8 R1 d
2 y" F Y8 q- y, F
INT8U OSTCBX——priority0x07 : t @$ _ Z! _2 q" g' B
INT8U OSTCBY——priority>>3
' U- w! n) g+ t q2 kINT8U OSTCBBitX——OSMapTbl[priority0x07]
$ f/ \+ k7 u' `" h0 N# TINT8U OSTCBBitY——OSMapTbl[priority>>3]9 q2 }+ J4 ]$ T6 k
加速进入就绪态算法,这个算法在下一个帖子再做讨论,用存储空间换取执行时间,很妙
) M3 G3 g; R* \ W( D- e 6 y$ E, V* F0 i, E1 v* y/ M: N! u d; \% m
以上的数据结构是必有的,以下的可以根据用户自定义的功能在代码中有条件编译。8 }0 F. F9 T' a
{+ K+ H% _9 j8 ^( z2 C
Void--OSTCBExtPtr0 O6 d+ \5 Z) i1 ^( G+ c! `+ ~( I
OS_STK--OSTCBStkBottom
: C* Z! \. k4 t" @ c7 K' CINT32U--OSTCBStkSize4 ^3 r# Z2 a' f2 c4 M
INT16U--OSTCBOpt p l Q4 o, r0 D! E
INT16U--OSTCBId
/ G s- P; }7 Z/ {4 ?- M. L" dOS_EVENT--OSTCBEventPtr/ G) G M1 @$ \5 Q
Void-- OSTCBMsg
. g4 I; k4 G s) e" l& U! wOS_FLAG_NODE--OSTCBFlagNode
( i. E' Q' d, C8 M$ V2 d ?OS_FLAG--OSTCBFlagsRdy4 H4 ~5 o1 q5 h+ v0 ^- d
BOOLEAN--OSTCBDelReq, k( [8 V$ f5 j" d+ ^; S6 }
}
; w1 n- K/ {+ ]1 |5 X/ A% [" w提示:在任务控制块的结构中存在着很多条件编译语句,这些语句的存在使得我们可以对ucos操作系统的功能做出裁剪的同时,大大减少系统对RAM的占用量,因为如果我们没有选择某项功能,就不会让与之相连的某些变量在预编译时有效,也就不会占用宝贵的RAM。这一点也很巧妙,有时候我真的很佩服作者的创造性思维。5 A. N3 `. x- R F4 o0 x+ _' c
8 p# ?' y5 i! L! P) x7 m/ L# z
) R1 @3 V) I L. A3 O系统可以执行的最大任务数量,用OS_MAX_TASKS这个宏来定义。当这个宏定义好之后,系统给用户程序分配的任务控制块的数量也就定下来了。适当的设置这个数值,可以有效减少实际的RAM消耗。
k. z) m' o* I' l
' U; S: n1 z" J ; g" P1 D# E$ |+ x8 a
所有的用户任务控制块,都会放在一个数组当中。这个数组就是OSTCBTbl[]。操作系统本身还会有自己的任务,数量为OS_N_SYS_TASKS个,通常是一个空闲任务,和一个统计CPU使用率的任务。因此OS_N_SYS_TASKS通常为2。' S1 p8 x* g) a
n- ~* F# D! ]$ Y' f# t6 p9 ^( Y
% a, N/ q9 J8 [. ?- h- D m" {# Y$ k. b& c, I
初始化时,系统会将所有的OS_MAX_TASKS+OS_N_SYS_TASKS个任务控制块链接成一个单向空任务链表,由空任务指针OSTCBFreeList来指向这个空链表。当某一个任务被“建立”时,这个任务就占用了空链表指向的这个空任务块。接着指针OSTCBFreeList会指向链表中的下一个空任务块。,以此类推,不断建立任务,就可以占满所有的任务控制块。一旦某个任务被删除,它所占用的任务控制块就会被退还给控制块数组。空指针就会指向这个空闲的控制块。. h q! {! B! s1 V4 _ x
这个方法也很巧妙,真的很值得我们细细体会,好好学习,呵呵。
1 s4 y8 s, ~# }" i' Z" P" r# z; S7 ?1 h F$ E% S' J Y$ I8 i
9 B k; L" Q: x* V1 m( E接下来就是建立任务时,空闲指针指定任务要用的控制块。这个控制块要经过初始化才能使用。因此作者设计了一个任务控制块的初始化函数OS_TCBInit()。这个函数专门负责初始化控制块。在这里就不具体说了,过程很严谨,也考虑到了条件编译的情况。% }% l! S i. H' J
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -