双语版

9-4 ALL
INTERRUPTS VECTOR TO A COMMON LOCATION
什么是 uC/OS-III?
uC/OS-III(Micro
C OS Three 微型的 C 语言编写的操作系统第 3 版)是一个可升级的,可固化的,基于优先级的实时内核。它对任务的个数无限制。uC/OS-III 是一个第 3 代的系统内核,支持现代的实时内核所期待的大部分功能。例如资源管理,同步,任务间的通信等等。然而,uC/OS-III 提供的特色功能在其它的实时内核中是找不到的,比如说完备的运行时间测量性能,直接地发送信号或者消息到任务,任务可以同时等待多个内核对象等。
μC/OS-III
(pronounced “Micro C O S Three) is a scalable, ROMable, preemptive real-time
kernel that manages an unlimited number of tasks. μC/OS-III is a
third-generation kernel and offers all of the services expected from a modern
real-time kernel, such as resource management, synchronization, inter-task
communications, and more. However, μC/OS-III offers many unique features not
found in other real-time kernels, such as the ability to complete performance
measurements at run-time, to directly signal or send messages to tasks, achieve
pending on multiple kernel objects, and more.
为什么命名一个新的版本?
uC/OS 系列,第一代产生于 1992。经过了多年的使用和上千人的反馈,已经产生了很多的进化版本。
The
μC/OS series, first introduced in 1992, has undergone a number of changes over
the years based on feedback from thousands of people using and deploying its
evolving versions.
uC/OS-III 是这些反馈和经验的总结。在 uC/OS-II 中很少使用的功能已经被删除或者被更新,添加了更高效的功能和服务。其中最有用的功能应该是时间片轮转法(round robin),这个是 uC/OS-II 中不支持的,但是现在已经是 uC/OS-III 的一个功能了。
μC/OS-III
is the sum of this feedback and experience. Rarely used μC/OS-II features were
eliminated and newer, more efficient features and services, were added.
Probably the most common request was to add round robin scheduling, which was
not possible for μC/OS-II, but is now a feature of μC/OS-III.
uC/OS-III 会提供新的功能以更好地适应新出现的处理器。特别的,uC/OS-III 被设计用于 32 位处理器,但是它也能在 16 位或 8 位处理器中很好地工作。
μC/OS-III
also provides additional features that better exploit the capabilities of today’s
newer processors. Specifically, μC/OS-III was designed with 32-bit processors
in mind, although it certainly works well with 16- and even several 8-bit
processors.
uC/OS-III 的目标
uC/OS-III 最主要的目标是提供一流的实时内核以适应更新很快的嵌入式产品。使用像 uC/OS-III 那样具有雄厚的基础和稳定的框架的商业实时内核,能够帮助设计师们处理日益复杂的嵌入式设计。
The
main goal of μC/OS-III is to provide a best-in-class real-time kernel that
literally shaves months of development time from an embedded-product schedule.
Using a commercial real-time kernel such as μC/OS-III provides a solid
foundation and framework to the design engineer dealing with the growing
complexity of embedded designs.
这本书中的目标,是为了介绍 uC/OS-III 的内部工作。了解这些会帮助读者实现逻辑上的设计方案,协调统一硬件和软件会让你对整体的设计很有把握。
Another
goal for μC/OS-III, and therefore this book, is to explain inner workings of a
commercial-grade kernel. This understanding will assist the reader in making
logical design decisions and informed tradeoffs between hardware and software
that make sense.
在重要的地方,实时系统凭借其系统性的计算和及时的处理能力工作着。一共有 2 种类型的实时系统:软实时系统和硬实时系统。
Real-time
systems are systems whereby the correctness of the computed values and their
timeliness are at the forefront. There are two types of real-time systems, hard
and soft real time.
软实时系统和硬实时系统的区别在于一旦没有在规定的时间内完成任务所导致后果的严重性。超过时限后所得到的结果即使正确也可能是毫无作用的。
What
differentiates hard and soft real-time systems is their tolerance to missing
deadlines and the consequences associated with those misses. Correctly computed
values after a deadline has passed are often useless.
硬实时系统中,运算超时是不允许发生的。在很多情况下,超时会导致巨大的灾难,会威胁人们的生命安全。但是在软实时系统中,超时不会导致严重后果。
For
hard real-time systems, missing deadlines is not an option. In fact, in many
cases, missing a deadline often results in catastrophe, which may involve human
lives. For soft real-time systems, however, missing deadlines is generally not
as critical.
实时系统的应用范围很广,但很多实时系统是嵌入式的。一个嵌入式系统是计算机中添加操作系统,但是用户不公认这是个计算机。
Real-time
applications cover a wide range, but many real-time systems are embedded. An
embedded system is a computer built into a system and not acknowledged by the
user as being a computer.
以下列出嵌入式系统的一些例子
|
航空航天飞行管理系统喷射发动机控制武器系统 |
通讯路由器交换机手机 |
加工控制化学工厂工厂自动化食品加工 |
|
语音 MP3 播放器 |
计算机外围设备打印机 |
机器人 |
|
放大器和调谐器 |
扫描仪 |
|
|
汽车制造业反锁死制动系统气候控制引擎控制 GPS |
家用电器空气调节机恒温器大型家用电器 |
视频广播设备高清电视 |
|
办公室自动化传真机复印机 |
等等 |
|
实时系统的设计,调试和配置比非实时系统难得多。
Real-time
systems are typically more complicated to design, debug, and deploy than
non-real-time systems.
简单的小型系统设计一般是基于前后台的或者无限循环的系统。包含一个无限循环的模块实现需要的操作(后台)。中断处理程序实现异步事件(前台)。前台也叫做中断级,后台也叫作任务级。
Small
systems of low complexity are typically designed as foreground/background
systems or super-loops. An application consists of an infinite loop that calls
modules (i.e., tasks) to perform the desired operations (background). Interrupt
Service Routines (ISRs) handle asynchronous events (foreground). Foreground is
also called interrupt level; background is called task level.
临界操作应该在任务级中被执行,不可避免地必须在中断处理程序中执行也要确保是在很短的时间内完成。因为这会导致 ISR 占用更长的时间。通常的,ISR 中使能相关的信息而在后台程序中执行相应的操作。这叫做任务级响应。任务级响应的时间依赖于后台循环一次所需的时间,通常这不是一个固定常量。
Critical
operations that should be performed at the task level must unfortunately be
handled by the ISRs to ensure that they are dealt with in a timely fashion.
This causes ISRs to take longer than they should. Also, information for a
background module that an ISR makes available is not processed until the
background routine gets its turn to execute, which is called the task-level
response. The worst-case task-level response time depends on how long a
background loop takes to execute since the execution time of typical code is
not constant, the time for successive passes through a portion of the loop is
nondeterministic.
另外,如果其中的代码稍有改动,那么循环一次所用的时间也将有所变化。
Furthermore,
if a code change is made, the timing of the loop is affected.
大多数高产量低成本微控制器的应用软件(例如微波炉,电话玩具等)都是基于前后台系统的。
Most
high-volume and low-cost microcontroller-based applications (e.g., microwave
ovens, telephones, toys, etc.) are designed as foreground/background systems.

实时内核是一个能管理 MPU、MCU、DSP 时间和资源的软件。
A
real-time kernel is software that manages the time and resources of a
microprocessor, microcontroller or Digital Signal Processor (DSP).
实时内核的应用包括迅速地响应,可靠地完成工作的各个部分。任务(也叫做线程)是一段简单的程序,运行时完全地占用 CPU。在单 CPU 中,任何时候只有 1 个任务被执行。
The
design process of a real-time application involves splitting the work into
tasks, each responsible for a portion of the job. A task (also called a thread)
is a simple program that thinks it has the Central Processing Unit (CPU)
completely to itself. On a single CPU, only one task executes at any given
time.
内核的责任是管理任务,也做多任务处理。多任务处理的作用是协调和切换多个任务依次享用 CPU。多任务处理最大化 CPU 的功能同时会让我们感觉是多个 CPU 在同时运行。多任务处理也有利于处理模块化的应用。多任务处理一个最重要的方面在于它允许程序员管理复杂的实时应用。
The
kernel is responsible for the management of tasks. This is called multitasking.
Multitasking is the process of scheduling and switching the CPU between several
tasks. The CPU switches its attention between several sequential tasks.
Multitasking provides the illusion of having multiple CPUs and maximizes the
use of the CPU. Multitasking also helps in the creation of modular
applications. One of the most important aspects of multitasking is that it
allows the application programmer to manage the complexity inherent in
real-time applications.
在多任务处理中程序员可以简单的维护和升级产品。
Application
programs are easier to design and maintain when multitasking is used.
uC/OS-III 是一个抢占式内核,这意味着 uC/OS-III 总是执行最重要的就绪任务,如图 1-2。
μC/OS-III
is a preemptive kernel, which means that μC/OS-III always runs the most
important task that is ready to run as shown in Figure 1-2.

F1-2(1) 一个低优先级的任务正在被执行
A
low-priority task is executing.
F1-2(2)发生一个中断,CPU 转向 负责服务中断设备的ISR
An
interrupt occurs, and the CPU vectors to the ISR responsible for servicing the
interrupting device.
F1-2(3)ISR 响应中断请求设备,但是 ISR 只做非常少的工作。ISR 应该标记或发送消息到一个高优先级的任务,让中断能够快速处理完毕。例如,如果一个中断来自于以太网控制器,ISR 标记任务,在任务级响应以太网控制器。
The
ISR services the interrupt device, but actually does very little work. The ISR
will signal or send a message to a higher-priority task that will be
responsible for most of the processing of the interrupting device. For example,
if the interrupt comes from an Ethernet controller, the ISR simply signals a
task, which will process the received packet.
F1-2(4)当 ISR 执行完毕,uC/OS-III 注意到 ISR 创建的一个更高优先级的任务就绪。uC/OS-III 将不会返回到中断前的任务,它会切换到这个更高优先级的任务。
When
the ISR finishes, μC/OS-III notices that a more important task has been made
ready to run by the ISR and will not return to the interrupted task, but
instead context switch to the more important task.
F1-2(5)高优先级任务执行必要的处理答复中断请求设备。
The
higher-priority task executes and performs the necessary processing in response
to the interrupt device.
F1-2(6)当高优先级任务完成时,返回原任务中断前的代码。
When
the higher-priority task completes its work, it loops back to the beginning of
the task code and makes a μC/OS-III function call to wait for the next
interrupt from the device.
F1-2(7)原任务在它被中断的地方开始执行。
The
low-priority task resumes exactly at the point where it was interrupted, not
knowing what happened.
uC/OS-III 内核也负责管理任务间的交流,系统的资源(内存和I/O)。
Kernels
such as μC/OS-III are also responsible for managing communication between
tasks, and managing system resources (memory and I/O devices).
系统中加入内核需要额外的支出,因为内核提供服务时需要时间去处理。大多数的额外支出取决于服务的调用频繁度。在一个优秀的设计中,内核占用 CPU 的时间介于 2%到 4%之间。因为 uC/OS-III 是一个软件,添加到目标系统中需要额外的 ROM 和 RAM。
A
kernel adds overhead to a system because the services provided by the kernel
require time to execute. The amount of overhead depends on how often these
services are invoked. In a well-designed application, a kernel uses between 2%
and 4% of a CPU’s time. And, since μC/OS-III is software that is added to an
application, it requires extra ROM (code space) and RAM (data space).
低档的单片机很有可能不支持像 uC/OS-III 那样的实时内核,因为它只有很少的 RAM 可供访问。uC/OS-III 内核需要 1K 到 4K 之间的RAM,加上每个任务自己所需的堆栈空间。至少有 4K 大小 RAM 的处理器才有可能成功移植 uC/OS-III。
Low-end
single-chip microcontrollers are generally not able to run a real-time kernel
such as μC/OS-III since they have access to very little RAM. μC/OS-III requires
between 1 Kbyte and 4 Kbytes of RAM, plus each task requires its own stack
space. It is possible for μC/OS-III to work on processors having as little as 4
Kbytes of RAM.
最后,为了更好地使用 CPU,uC/OS-III 提供了大约 70 种常用的服务。当用过像 uC/OS-III 那样的具有实时内核的系统后,你将不会再去使用前后台系统了。
Finally,
μC/OS-III allows for better use of the CPU by providing approximately 70
indispensable services. After designing a system using a real-time kernel such as
μC/OS-III, you will not return to designing a foreground/background system.
一个实时系统通常包括一个实时内核以及其他高级的服务,例如:文件管理,堆栈协议,图形用户接口等等。大多数服务都是跟 I/O 有关的。
A
Real Time Operating System generally contains a real-time kernel and other
higher-level services such as file management, protocol stacks, a Graphical
User Interface (GUI), and other components. Most additional services revolve
around I/O devices.
Micrium 提供了 RTOS 一套完整的组件,包括 uC/FS、uC/TCP-IP、 uC/GUI、uC/USB 等。这些组件大部分都可以单独执除了 uC/TCP-IP。实时内核在应用中不是必须的。事实上,用户可以单独选择您的应用所需用的组件。详情和购买请联系 Micrium(www.Micrium.com)。
Micriμm
offers a complete suite of RTOS components including: μC/FS (an Embedded File
System), μC/TCP-IP (a TCP/IP stack), μC/GUI (a Graphical User Interface),
μC/USB (a USB device, host and OTG stack), and more. Most of these components
are designed to work standalone. Except for μC/TCP-IP, a real-time kernel is
not required to use the components in an application. In fact, users can pick
and choose only the components required for the application. Contact Micriμm
(www.micrium.com) for additional details and pricing.
uC/OS-III 是一个可扩展的,可固化的,抢占式的实时内核,它管理的任务个数不受限制。它是第三代内核,提供了现代实时内核所期望的所有功能包括资源管理、同步、内部任务交流等。uC/OS-III 也提供了很多特性是在其他实时内核中所没有的。比如能在运行时测量运行性能,直接得发送信号或消息给任务,任务能同时等待多个信号量和消息队列。
μC/OS-III
is a scalable, ROMable, preemptive real-time kernel that manages an unlimited
number of tasks. μC/OS-III is a third-generation kernel, offering all of the
services expected from a modern real-time kernel including resource management,
synchronization, inter-task communication, and more. However, μC/OS-III also
offers many unique features not found in other real-time kernels, such as the
ability to perform performance measurements at run time, directly signal or send
messages to tasks, and pending (i.e., waiting) on such multiple kernel objects
as semaphores and message queues.
以下列出 uC/OS-III 的特点:
Here
is a list of features provided by μC/OS-III:
源代码:uC/OS-III 完全根据 ANSI-C 标准写的。代码的规范是Micrium 团队的一种文化。虽然很多商业内核供应商提供他们产品的源代码,但是这些产品很有可能是笨重且难以利用的。除非代码严格地遵循标准并且产品有完整的带例子的说明书以展示代码是怎样工作的。通过这本书,你将会对 uC/OS-III 内部的工作情况有一个很深的了解。
Source
Code: μC/OS-III is provided in ANSI-C source form to licensees. The source code
for μC/OS-III is arguably the cleanest and most consistent kernel code
available. Clean source is part of the corporate culture at Micriμm. Although
many commercial kernel vendors provide source code for their products, unless
the code follows strict coding standards and is accompanied by complete
documentation with examples to show how the code works, these products may be
cumbersome and difficult to harness. With this book, you will gain a deep
understanding of the inner workings of μC/OS-III, which will protect your
investment.
应用程序接口(API):uC/OS-III 是很直观的。如果你熟悉类似的编码规范,你能轻松地知道函数名所对应的服务,以及需要怎样的参数。例如:指向对象的指针通常是第一个参数,指向错误代码的指针通常是最后一个参数。
Intuitive
Application Programming Interface (API): μC/OS-III is highly intuitive. Once
familiar with the consistent coding conventions used, it is simple to predict
the functions to call for the services required, and even predict which
arguments are needed. For example, a pointer to an object is always the first
argument, and a pointer to an error code is always the last one.
抢占式多任务处理:uC/OS-III 是一个抢占式多任务处理内核,因此,uC/OS-III 正在运行的经常是最重要的就绪任务。
Preemptive
multitasking: μC/OS-III is a preemptive multi-tasking kernel and therefore,
μC/OS-III always runs the most important ready-to-run task.
时间片轮转调度:uC/OS-III 允许多个任务拥有相同的优先级。当多个相同优先级的任务就绪时,并且这个优先级是目前最高的。 uC/OS-III 会分配用户定义的时间片给每个任务去运行。每个任务可以定义不同的时间片。当任务用不完时间片时可以让出 CPU 给另一个任务。
Round
robin scheduling of tasks at equal priority: μC/OS-III allows multiple tasks to
run at the same priority level. When multiple tasks at the same priority are
ready to run, and that priority level is the most important level, μC/OS-III
runs each task for a user-specified time called a time quanta. Each task can
define its own time quanta, and a task can also give up the CPU to another task
at the same priority if it does not require the full time quanta.
快速响应中断: uC/OS-III 有一些内部的数据结构和变量。 uC/OS-III 保护临界段可以通过锁定调度器代替关中断。因此关中断的时间会非常少。这样就使 uC/OS-III 可以响应一些非常快的中断源了。
Low
interrupt disable time: μC/OS-III has a number of internal data structures and
variables that it needs to access atomically. To ensure this, μC/OS-III is able
to protect these critical regions by locking the scheduler instead of disabling
interrupts. Interrupts are therefore disabled for very little time. This
ensures that μC/OS-III is able to respond to some of the fastest interrupt
sources.
确定性的:uC/OS-III 的中断响应时间是可确定的,uC/OS-III 提供的大部分服务的执行时间也是可确定的。
Deterministic:
Interrupt response with μC/OS-III is deterministic. Also, execution times of
most services provided by μC/OS-III are deterministic.
可扩展的:根据应用的需求,代码大小可以被调整。编译时通过调整 uC/OS-III 源代码中的大约 40 个#define(见 OS_CFG.H)可以在添加或移除一些功能。uC/OS-III 的服务还提供一些实时检查功能。特别的,uC/OS-III 能检传递的参数是否为 NULL 指针,ISR 是否就绪了任务级服务。参数有允许范围,指定选项都是有用的。检测功能可以被关闭(在编译时)以提供更好的性能和缩减代码大小。实际上,可扩展的 uC/OS-III 支持更广泛的应用和项目。
Scalable:
The footprint (both code and data) can be adjusted based on the requirements of
the application. This assumes access to the source code for μC/OS-III since
adding and removing features (i.e., services) is performed at compile time
through approximately 40 #defines (see OS_CFG.H). μC/OS-III also performs a
number of run-time checks on arguments passed to μC/OS-III services.
Specifically, μC/OS-III verifies that the user is not passing NULL pointers,
not calling task level services from ISRs, that arguments are within allowable
range, and options specified are valid, etc.. These checks can be disabled (at
compile time) to further reduce the code footprint and improve performance. The
fact that μC/OS-III is scalable allows it to be used in a wide range of applications
and projects.
易移植的:uC/OS-III 可以被移植到大部分的 CPU 架构中。大部分的支持 uC/OS-II 的器件通过改动就能支持 uC/OS-III。而 uC/OS-II 已经移植到 45 种 CPU 架构中了。
Portable:
μC/OS-III can be ported to a large number of CPU architectures. Most μC/OS-II
ports are easily converted to work on μC/OS-III with minimal changes in just a
matter of minutes and therefore benefit from more than 45 CPU architectures
already supported by μC/OS-II.
可固化的:uC/OS-III 专为嵌入式系统设计,它可以跟应用程序代码一起被固化。
ROMable:
μC/OS-III was designed especially for embedded systems and can be ROMed along
with the application code.
可实时配置的:uC/OS-III 允许用户在运行时配置内核。特别的,所有的内核对象如任务、堆栈、信号量、事件标志组、消息队列、消息、互斥信号量、内存分区、软件定时器等都是在运行时分配的,以免在编译时的过度分配。
Run-time
configurable: μC/OS-III allows the user to configure the kernel at run time.
Specifically, all kernel objects such as tasks, stacks, semaphores, event-flag
groups, message queues, number of messages, mutual exclusion semaphores, memory
partitions and timers, are allocated by the user at run time. This prevents
over-allocating resources at compile time.
任务数无限制:uC/OS-III 对任务数量无限制。实际上,任务的数量限制于处理器能提供的内存大小。每一个任务需要有自己的堆栈空间,uC/OS-III 在运行时监控任务堆栈的生长。uC/OS-III 对任务的大小无限制,
Unlimited
number of tasks: μC/OS-III supports an unlimited number of tasks. From a
practical standpoint, however, the number of tasks is actually limited by the
amount of memory (both code and data space) that the processor has access to.
Each task requires its own stack space and, μC/OS-III provides features to
allow stack growth of the tasks to be monitored at run-time. μC/OS-III does not
impose any limitations on the size of each task, except that there be a minimum
size based on the CPU used.
优先级数无限制:uC/OS-III 对优先级的数量无限制。然而,配置 uC/OS-III 的优先级在 32 到 256 之间已经满足大多数的应用了。
Unlimited
number of priorities: μC/OS-III supports an unlimited number of priority
levels. However, configuring μC/OS-III for between 32 and 256 different
priority levels is more than adequate for most applications.
内核对象数无限制:uC/OS-III 支持任何数量的任务、信号量、互斥信号量、事件标志组、消息队列、软件定时器、内存分区。用户在运行时分配所有的内核对象。
Unlimited
number of kernel objects: μC/OS-III allows for any number of tasks, semaphores,
mutual exclusion semaphores, event flags, message queues, timers, and memory
partitions. The user at run-time allocates all kernel objects.
服务:uC/OS-III 提供了高档实时内核所需要的所有功能,例如任务管理、时间管理、信号量、事件标志组、互斥信号量、消息队列、软件定时器、内存分区等。
Services:
μC/OS-III provides all the services expected from a high-end real-time kernel,
such as task management, time management, semaphores, event flags, mutexes,
message queues, software timers, fixed-size memory pools, etc.
互斥信号量(Mutexes):互斥信号量用于资源管理。它是一个内置优先级的特殊类型信号量,用于消除优先级反转。互斥信号量可以被嵌套,因此,任务可申请同一个互斥信号量多达 250 次。当然,互斥信号量的占有者需要释放同等次数。
Mutual
Exclusion Semaphores (Mutexes): Mutexes are provided for resource management.
Mutexes are special types of semaphores that have built-in priority inheritance,
which eliminate unbounded priority inversions. Accesses to a mutex can be
nested and therefore, a task can acquire the same mutex up to 250 times. Of
course, the mutex owner needs to release the mutex an equal number of times.
嵌套的任务停止:uC/OS-III 允许任务停止自身或者停止另外的任务。停止一个任务意味着这个任务将不再执行直到被其他的任务恢复。停止可以被嵌套到 250 级。换句话说,一个任务可以停止另外的任务多达 250 次。当然,这个任务必须被恢复同等次数才有资格再次获得 CPU。
Nested
task suspension: μC/OS-III allows a task to suspend itself or another task.
Suspending a task means that the task will not be allowed to execute until the
task is resumed by another task. Suspension can be nested up to 250 levels
deep. In other words, a task can suspend another task up to 250 times. Of
course, the task must be resumed an equal number of times for it to become
eligible to run on the CPU.
软件定时器:可以定义任意数量的一次性的、周期性的、或者两者兼有的定时器。定时器是倒计时的,执行用户定义的行为一直到计数减为 0。每一个定时器可以有自己的行为,如果一个定时器是周期性的,计数减为 0 时会自动重装计数值并执行用户定义的行为。
Software
timers: Define any number of “one-shot” and/or “periodic” timers. Timers are
countdown counters that perform a user-definable action upon counting down to
0. Each timer can have its own action and, if a timer is periodic, the timer is
automatically reloaded and the action is executed every time the countdown
reaches zero.
挂起多个对象:uC/OS-III 允许任务等待多个事件的发生。特别的,任务可以同时等待多个信号量和消息队列被提交。等待中的任务在事件发生的时候被唤醒。
Pend
on multiple objects: μC/OS-III allows an application to wait (i.e., pend) on
multiple events at the same time. Specifically, a task can wait on multiple
semaphores and/or message queues to be posted. The waiting task wakes up as
soon as one of the events occurs.
任务信号量:uC/OS-III 允许 ISR 或者任务直接地发送信号量给其它任务。这样就避免了必须产生一个中间级内核对象如一个信号量或者事件标志组只为了标记一个任务。提高了内核性能。
Task
Signals: μC/OS-III allows an ISR or task to directly signal a task. This avoids
having to create an intermediate kernel object such as a semaphore or event
flag just to signal a task, and results in better performance.
任务消息:uC/OS-III 允许 ISR 或者任务直接发送消息到另一个任务。这样就避免产生一个消息队列,提高了内核性能。
Task
Messages: μC/OS-III allows an ISR or a task to send messages directly to a
task. This avoids having to create and use a message queue, and also results in
better performance.
任务寄存器:每一个任务可以拥有用户可定义的任务寄存器,不同于 CPU 寄存器。
Task
registers: Each task can have a user-definable number of “task registers.” Task
registers are different than CPU registers. Task registers can be used to hold
“errno” type variable, IDs, interrupt disable time measurement on a per-task
basis, and more.
错误检测:uC/OS-III 能检测指针是否为 NULL、在 ISR 中调用的任务级服务是否允许、参数在允许范围内、配置选项的有效性、函数的执行结果等。每一个 uC/OS-III 的 API 函数返回一个对应于函数调用结果的错误代号。
Error
checking: μC/OS-III verifies that NULL pointers are not passed, that the user
is not calling task-level services from ISRs, that arguments are within
allowable range, that options specified are valid, that a handler is passed to
the proper object as part of the arguments to services that manipulate the
desired object, and more. Each μC/OS-III API function returns an error code
concerning the outcome of the function call.
内置的性能测量:uC/OS-III 有内置性能测量功能。能测量每一个任务的执行时间,每个任务的堆栈使用情况,任务的执行次数,CPU 的使用情况,ISR 到任务的切换时间,任务到任务的切换时间,列表中的峰值数,关中断、锁调度器平均时间等。
Built-in
performance measurements: μC/OS-III has built-in features to measure the
execution time of each task, stack usage of each task, number of times a task
executes, CPU usage, ISR-to-task and task-to-task response time, peak number of
entries in certain lists, interrupt disable and scheduler lock time on a
per-task basis, and more.
可优化: uC/OS-III 被设计于能够根据 CPU 的架构被优化。 uC/OS-III 所用的大部分数据类型能够被改变,以更好地适应 CPU 固有的字大小。优先级调度法则可以通过编写一些汇编语言而获益于一些 特 殊 的 指 令 如 位 设 置 、 位 清 除 、 计 数 清 零 指 令(CLZ),find-first-one(FF1)指令。
Can
easily be optimized: μC/OS-III was designed so that it could easily be
optimized based on the CPU architecture. Most data types used in μC/OS-III can
be changed to make better use of the CPU’s natural word size. Also, the
priority resolution algorithm can easily be written in assembly language to
benefit from special instructions such as bit set and clear, as well as
count-leading-zeros (CLZ), or find-first-one (FF1) instructions.
死锁预防:uC/OS-III 中所有的挂起服务都可以有时间限制,预防死锁。
Deadlock
prevention: All of the μC/OS-III “pend” services include timeouts, which help
avoid deadlocks.
任务级的时基处理:uC/OS-III 有时基任务,时基 ISR 触发时基任务。uC/OS-III 使用了哈希列表结构,可以大大减少处理延时和任务超时所产生的开支。
Tick
handling at task level: The clock tick manager in μC/OS-III is accomplished by
a task that receives a trigger from an ISR. Handling delays and timeouts by a
task greatly reduces interrupt latency. Also, μC/OS-III uses a hashed delta
list mechanism, which further reduces the amount of overhead in processing
delays and timeouts of tasks.
用户可定义的钩子函数:uC/OS-III 允许程序员定义 hook 函数, hook 函数被 uC/OS-III 调用。hook 函数允许用户扩展 uC/OS-III 的功能。有的 hook 函数在任务切换的时候被调用,有的在任务创建的时候被调用,有的在任务删除的时候被调用。
User
definable hooks: μC/OS-III allows the port and application programmer to define
“hook” functions, which are called by μC/OS-III. A hook is simply a defined
function that allows the user to extend the functionality of μC/OS-III. One
such hook is called during a context switch, another when a task is created,
yet another when a task is deleted, etc.
时间戳:为了测量时间,uC/OS-III 需要一个 16 位或者 32 位的时时间戳计数器。这个计数器值可以在运行时被读取以测量时间。例如:当 ISR 提交消息到任务时,时间戳计数器自动读取并保存作为消息。当接收者接收到这条消息,时间戳被提供在消息内。通过读取现在的时间戳,消息的响应时间可以被确定。
Timestamps:
For time measurements, μC/OS-III requires that a 16-bit or 32-bit free running
counter be made available. This counter can be read at run time to make time
measurements of certain events. For example, when an ISR posts a message to a
task, the timestamp counter is automatically read and saved as part of the
message posted. When the recipient receives the message, the timestamp is
provided to the recipient, and by reading the current timestamp, the time it
took for the message to be received can be determined.
嵌入的内核调试器:这个功能允许内核调试器查看 uC/OS-III 的变量和数据结构通过一个用户定义的通道。(但是只能在调试器遇到断点的时候查看)。uC/OS-III 内核也支持 uC/Probe(探针)在运行时显示信息。
Built-in
support for Kernel Awareness debuggers: This feature allows kernel awareness
debuggers to examine and display μC/OS-III variables and data structures in a
user-friendly way, but only when the debugger hits a breakpoint. Instead of a
static view of the environment the kernel awareness support in μC/OS-III is
also used by μC/Probe to display the same information at run-time.
对象名称:每个 uC/OS-III 的内核对象有一个相关联的名字。这样就能很容易的识别出对象所指定的作用。分配一个 ASCII 码的名字给任务、信号量、互斥信号量、事件标志组、消息队列、内存块、软件定时器。对象的名字长度没有限制,但是必须以空字符结束。
Object
names: Each μC/OS-III kernel object can have a name associated with it. This
makes it easy to recognize what the object is assigned to. Assign an ASCII name
to a task, a semaphore, a mutex, an event flag group, a message queue, a memory
partition, and a timer. The object name can have any length, but must be NUL
terminated.
表 1.1 列出了 uC/OS 的演变,比较每个版本的区别。
Table
1-1 shows the evolution of μC/OS over the years, comparing the features
available in each version.
|
功能 |
uC/OS |
uC/OS-II |
uC/OS-III |
|
诞生年份 |
1992 |
1998 |
2009 |
|
书 |
有 |
有 |
有 |
|
提供源代码 |
是 |
是 |
是 |
|
抢占式多任务 |
是 |
是 |
是 |
|
最大任务数 |
64 |
256 |
无限制 |
|
每个优先级的任务数 |
1 |
1 |
无限制 |
|
时间片轮转 |
否 |
否 |
是 |
|
信号量 |
是 |
是 |
是 |
|
互斥信号量 |
否 |
是 |
是(可嵌套的) |
|
事件标志 |
否 |
是 |
是 |
|
消息邮箱 |
是 |
是 |
否(不需要了) |
|
消息队列 |
是 |
是 |
是 |
|
固定大小的内存管理 |
否 |
是 |
是 |
|
不通过信号量标记一个任务 |
否 |
否 |
是 |
|
不通过消息队列发消息给任务 |
否 |
否 |
是 |
|
软件定时器 |
否 |
是 |
是 |
|
任务停止/恢复 |
否 |
是 |
是(可嵌套的) |
|
死锁预防 |
是 |
是 |
是 |
|
可扩展的 |
是 |
是 |
是 |
|
代码段需求 |
3K 到 8K |
6K 到 26K |
6K 到 20K |
|
数据段需求 |
1K+ |
1K+ |
1K+ |
|
可固化 |
是 |
是 |
是 |
|
在运行时配置 |
否 |
否 |
是 |
|
编译时配置 |
是 |
是 |
是 |
|
每个对象命名 |
否 |
是 |
是 |
|
挂起多个对象 |
否 |
是 |
是 |
|
任务寄存器 |
否 |
是 |
是 |
|
嵌入的测量功能 |
否 |
有限制 |
大量的 |
|
用户可定义的 hook 函数 |
否 |
是 |
是 |
|
时间戳 |
否 |
否 |
是 |
|
嵌入的内核调试 |
否 |
是 |
是 |
|
汇编可优化 |
否 |
否 |
是 |
|
任务级的时基定时器处理 |
否 |
否 |
是 |
|
提供的服务 |
~20 |
~90 |
~70 |
|
MISRA-C:1998 |
否 |
是(除了10个规则) |
N/A |
|
MISRA-C:2004 |
否 |
否 |
是(除了7个规则) |
|
DO178B EUROCAE ED-12B |
否 |
是 |
申请中 |
|
FDA 认证 |
否 |
是 |
申请中 |
|
SIL3/SIL4 IEC |
否 |
是 |
申请中 |
|
IEC-61508 |
否 |
是 |
申请中 |
这本书中可分为 2 部分
This
book consists of two books in one.
第一部分只是讲解了 uC/OS-III,并没有涉及到于 CPU 架构有关的内容。在这之中,读者可以学到实时内核的相关知识。例如,临界段代码、任务管理、就绪列表、任务调度、上下文切换、中断管理、阻塞列表、时间管理、软件定时器、资源管理、同步、内存管理、如何使用 uC/OS-III 的 API、如何配置 uC/OS-III、如何移植 uC/OS-III 到不同架构的 CPU 等。
Part
I describes μC/OS-III and is not tied to any specific CPU architecture. Here,
the reader will learn about real-time kernels through μC/OS-III. Specifically,
critical sections, task management, the ready list, scheduling, context
switching, interrupt management, wait lists, time management, timers, resource
management, synchronization, memory management, how to use μC/OS-III’s API, how
to configure μC/OS-III, and how to port μC/OS-III to different CPU
architectures, are all covered.
第二部分讲解了移植 uC/OS-III 到一个流行的 CPU 架构中,这这里,你可以学到 CPU 的架构知识及 uC/OS-III 是如何最大限度地利用CPU 的。例子都是在评估版中实际运行的。
Part
II describes the port of a popular CPU architecture. Here, learn about this CPU
architecture and how μC/OS-III gets the most out of the CPU. Examples are
provided to actually run code on the evaluation board that is available with
this book.
正如我刚才提到的,这本书假定你已经有一个评估板。CD/DVD 中一系列免费的工具(或者从网上下载)与这本书和评估板是相互补充的。只要在工程中没有商业的意图,使用评估板时辅助工具和 uC/OS-III 是免费的(用于教育时也是免费的)。换句话说,没有附加的要价除了最初的书本费,评估板费以及辅助工具费。
As I
just mentioned, this book assumes the presence of an evaluation board that
allows the user to experiment with the wonderful world of real-time kernels,
and specifically μC/OS-III. The book and board are complemented by a full set
of tools that are provided free of charge either in a companion CD/DVD, or
downloadable through the Internet. The tools and the use of μC/OS-III are free
as long as they are used with the evaluation board, and there is no commercial
intent to use them on a project. In other words, there is no additional charge
except for the initial cost of the book, evaluation board and tools, as long as
they are used for educational purposes.
这本书伴随着介绍了 Micrium 提供的测试工具 uC/Probe。这个测试工具允许用户监控和改变目标系统中 5 个变量。
The
book also comes with a trial version of an award-winning tool from Micriμm
called μC/Probe. The trial version allows the user to monitor and change up to
five variables in a target system.
uC/Probe 是一个基于应用的微型窗口,它允许用户在运行时查看目标系统中的变量。特别的,当目标系统在运行时可以显示或者改变任何变量。显示这些变量可以通过图表元件如仪表盘、电平表、柱状图、LEDs、数字指示器等等。滑动器、开关、按钮可以被用来改变变量的值。
μC/Probe
is a Microsoft Windows™ based application that enables the user to visualize
variables in a target at run time. Specifically, display or change the value of
any variable in a system while the target is running. These variables can be
displayed using such graphical elements as gauges, meters, bar graphs, virtual
LEDs, numeric indicators, and many more. Sliders, switches, and buttons can be
used to change variables. This is accomplished without the user writing a
single line of code!
uC/Probe 可以被连接到任何器件(8 位,16 位,32 位,64 位, DSP)通过 J-Tag、RS-232C、USB 或者以太网接口等。uC/Probe 可以显示或修改应用中任何变量(只要它是全局的),包括 uC/OS-III 的内部变量。
μC/Probe
interfaces to any target (8-, 16-, 32-, 64-bit, or even DSPs) through one of
the many interfaces supported (J-Tag, RS-232C, USB, Ethernet, etc.). μC/Probe
displays or changes any variable (as long as they are global) in the
application, including μC/OS-III’s internal variables.
uC/Probe 配合任何编译器、汇编器、链接器产生一个 ELF/DWARF 或者 IEEE695 文件。用户需要把这个文件下载到评估板或者目标器件。从这个文件中,uC/Probe 可以提取出变量的信息,以及确定变量在 RAM 或者 ROM 中的存储位置。
μC/Probe
works with any compiler/assembler/linker able to generate an ELF/DWARF or
IEEE695 file. This is the exact same file that the user will download to the
evaluation board or a final target. From this file, μC/Probe is able to extract
symbolic information about variables, and determine where variables are stored
in RAM or ROM.
uC/Probe 允许用户收集并记录数据到一个文件以便于最后分析。uC/Probe 是 uC/OS-III 中嵌入的一个功能。
μC/Probe
also allows users to log the data displayed into a file for analysis of the
collected data at a later time. μC/Probe also provides μC/OS-III kernel
awareness as a built-in feature.
这个测试版本只允许用户显示或者改变最多 5 个变量。
The
trial version that accompanies the book is limited to the display or change of
up to five variables.
uC/Probe 是一个工具,它是嵌入式软件工程师所必须的。当你购买了 uC/OS-III ,才会提供完整的 uC/Probe 。详情请登录 www.micrium.com。
μC/Probe
is a tool that serious embedded software engineers should have in their
toolbox. The full version of μC/Probe is included when licensing μC/OS-III. See
www.micrium.com for more details.
首先,如果有图解的话先看图解,或者小括号中的解释。图片中都会标注“F”以及图的编号。例如,F3-4(2)说明相关介绍在图F3-4 中的元素(2)中。这个规定也适用于列表“L”和表格“T”。
First,
notice that when a specific element in a figure is referenced, the element has
a number next to it in parenthesis. A description of this element follows the
figure and in this case, the letter “F” followed by the figure number, and then
the number in parenthesis. For example, F3-4(2) indicates that this description
refers to Figure 3-4 and the element (2) in that figure. This convention also
applies to listings (starts with an “L”) and tables (starts with a “T”).
其次,新的章节开始于新的页。换句话说,不要奇怪于叙述到页的一半结束了。新的章节开始于下一页,这是为了防止内容被页面所打断。
Second,
notice that sections and listings are started where it makes sense.
Specifically, do not be surprised to see the bottom half of a page empty. New
sections begin on a new page, and listings are found on a single page, instead
of breaking listings on two pages.
第三,在我的职业生涯中非常注重于代码的质量。在 Micrium 公司,令我们感到骄傲的是我们的代码都是非常高效的,可以参考这本书的例子。在 1992 年的 uC/OS 的书中公布了一些我创造的代码标准。这些标准经过了多年的进化,但其核心思想还是一直保持着。这些标准可以从 www.micrium.com 下载。
Third,
code quality is something I’ve been avidly promoting throughout my whole
career. At Micriμm, we pride ourselves in having the cleanest code in the
industry. Examples of this are seen in this book. I created and published a
coding standard in 1992 that was published in the original μC/OS book. This
standard has evolved over the years, but the spirit of the standard has been
maintained throughout. The Micriμm coding standard is available for download from
the Micriμm website, www.micrium.com
所有的函数、变量、宏、#define 常量都以“OS”为前缀(操作系统的标准),接下来的是组件的缩写,然后是函数的功能。例如 OSSemPost()表明函数属于 OS,是信号量服务的一部分,函数的功能是提交信号量。所有相关的函数在参考手册中成组说明,以便让用户使用起来更方便。
One
of the conventions used is that all functions, variables, macros and #define
constants are prefixed by “OS” (which stands for Operating System) followed by
the acronym of the module (e.g., Sem), and then the operation performed by the
function. For example OSSemPost() indicates that the function belongs to the OS
(μC/OS-III), that it is part of the Semaphore services, and specifically that
the function performs a Post (i.e., signal) operation. This allows all related
functions to be grouped together in the reference manual, and makes those
services intuitive to use.
发送信号量或消息到任务叫做提交 Post,等待信号量或者消息叫做 Pend。换句话说,ISR 或者任务要标记或者发送消息到另一个任务时需调用 OS???Post()。其中???是服务类型:Sem,TaskSem,Flag,Mutex,Q,TaskQ。类似的,一个任务等待信号量或消息的时候需调用 OS???Pend()。
Notice
that signaling or sending a message to a task is called posting, and waiting
for a signal or a message is called pending. In other words, an ISR or a task
signals or sends a message to another task by using OS???Post(), where ??? is
the type of service: Sem, TaskSem, Flag, Mutex, Q, and TaskQ. Similarly, a task
can wait for a signal or a message by calling OS???Pend().
图 1-3 显示了 uC/OS-III 的章节布局。这个图表对于理解章节间的关系是很有帮助的。第一列介绍的是 uC/OS-III 的结构。第二列介绍的是 uC/OS-III 的一些扩展功能。第三列将会帮助你移植 uC/OS-III 到不同架构的 CPU 中。
Figure
1-3 shows the layout and flow of Part I of the book. This diagram should be
useful to understand the relationship between chapters. The first column on the
left indicates chapters that should be read in order to understand μC/OS-III’s
structure. The second column shows chapters that are related to additional
services provided by μC/OS-III. The third column relates to chapters that will
help port μC/OS-III to different CPU architectures.
第四列的顶部介绍的是怎样从 uC/OS-III 中获得实时的编译时间,运行时间。当调试时使用内核感知功能或 uC/Probe 的时候这个是特别有用的。第四列的中部介绍的是 uC/OS-III 的 API 和配置指南。使用 uC/OS-III 的时候参考这些章节。最后,第四列的下部分介绍的是其它附件。
The
top of the fourth column explains how to obtain valuable run-time and
compile-time statistics from μC/OS-III. This is especially useful if developing
a kernel awareness plug-in for a debugger, or using μC/Probe. The middle of
column four contains the μC/OS-III API and configuration manuals. Reference
these sections regularly when designing a product using μC/OS-III. Finally, the
bottom of the last column contains miscellaneous appendices.

章节 1:简介
Chapter
1, Introduction. This chapter.
章节 2:目录和文件。这个章节介绍了 uC/OS-III 所包括目录结构和文件。了解那些文件是必须的,这些文件该被放在哪里,模块的功能等。
Chapter
2, Directories and Files. This chapter explains the directory structure and
files needed to build a μC/OS-III-based application. Learn about the files that
are needed, where they should be placed, which module does what, and more.
章节 3:开始学习 uC/OS-III。在这个章节中,学习怎样配置和开始基于 uC/OS-III 的应用。
Chapter
3, Getting Started with μC/OS-III. In this chapter, learn how to properly
initialize and start a μC/OS-III-based application.
章节 4:临界段。介绍了什么是临界段,怎么保护临界段。
Chapter
4, Critical Sections. This chapter explains what critical sections are, and how
they are protected.
章节 5:任务管理。介绍了实时内核中最重要的部分,在多任务环境中管理任务。
Chapter
5, Task Management. This chapter is an introduction to one of the most
important aspects of a real-time kernel, the management of tasks in a
multitasking environment.
章节 6:就绪队列。介绍 uC/OS-III 怎么有效地追踪所有的就绪任务。
Chapter
6, The Ready List. In this chapter, learn how μC/OS-III efficiently keeps track
of all of the tasks that are waiting to execute on the CPU.
章节 7: 任务调度。介绍了 uC/OS-III 的调度算法。
Chapter
7, Scheduling. This chapter explains the scheduling algorithms used by
μC/OS-III, and how it decides which task will run next.
章节 8:上下文切换。介绍了什么是上下文切换,描述了任务被挂起或恢复的过程。
Chapter
8, Context Switching. This chapter explains what a context switch is, and
describes the process of suspending execution of a task and resuming execution
of a higher-priority task.
章节 9:中断管理。介绍了 uC/OS-III 如何处理 ISRs 产生的未预见服务。以及为什么 uC/OS-III 支持几乎所有的中断控制器。
Chapter
9, Interrupt Management. Here is how μC/OS-III deals with interrupts and an
overview of services that are available from Interrupt Service Routines (ISRs).
Learn how μC/OS-III supports nearly any interrupt controller.
章节10:阻塞列表。任务可能位等待一个事件或资源而暂停运行。阻塞列表用来存放这些等待中的任务。本章介绍了 uC/OS-III 是如何管理这些列表的。
Chapter
10, Pend Lists (or Wait Lists). Tasks that are not able to run are most likely
blocked waiting for specific events to occur. Pend Lists (or wait lists), are
used to keep track of tasks that are waiting for a resource or event. This
chapter describes how μC/OS-III maintains these lists.
章节 11:时间管理。uC/OS-III 的服务允许用户定义任务挂起的时限。允许任务停止运行直到被恢复。这个章节也介绍了延时认识如何被恢复,怎样获取当前时基计数值,怎样设置时基计数值。
Chapter
11, Time Management. In this chapter, learn about μC/OS-III’s services that allow
users to suspend a task until some time expires. With μC/OS-III, specify to
delay execution of a task for an integral number of clock ticks or until the
clock-tick counter reaches a certain value. The chapter will also show how a
delayed task can be resumed, and describe how to get the current value of the
clock tick counter, or set this counter, if needed.
章节 12:软件定时器管理。uC/OS-III 允许用户定义任意数量的软件定时器。当一个定时到时时,函数可以被调用。定时器可以被设置为一次性的或者周期性的。这个章节还介绍了定时器管理模块的工作过程。
Chapter
12, Timer Management. μC/OS-III allows users to define any number of software
timers. When a timer expires, a function can be called to perform some action.
Timers can be configured to be either periodic or one-shot. This chapter also
explains how the timer-management module works.
章节 13:资源管理。介绍了多种共享资源的技巧。每种技巧的优点和缺点都会被提及。还介绍了信号量、互斥信号量的管理。
Chapter
13, Resource Management. In this chapter, learn different techniques so that
tasks share resources. Each of these techniques has advantages and
disadvantages that will be discussed. This chapter also explains the internals
of semaphores, and mutual exclusion semaphore management.
章节 14:同步。介绍了 uC/OS-III 提供了的 2 种同步服务:信号量和事件标志组。以及当调用同步模块时的过程。
Chapter
14, Synchronization. μC/OS-III provides two types of services for
synchronization: semaphores and event flags and these are explained in this
chapter, as well as what happens when calling specific services provided in
this module.
章节 15:消息通道:uC/OS-III 允许任务或 ISR 直接发送消息到任务。介绍了消息队列管理模块的一些服务。
Chapter
15, Message Passing. μC/OS-III allows a task or an ISR to send messages to a
task. This chapter describes some of the services provided by the message queue
management module.
章节 16:多对象挂起:uC/OS-III 允许应用同时挂起多个内核对象(信号量或消息队列)。这个功能使等待中的任务能在其中一个事件发生或超时时迅速被唤醒。
Chapter
16, Pending on multiple objects. In this chapter, see how μC/OS-III allows an
application to pend (or wait) on multiple kernel objects (semaphores or message
queues) at the same time. This feature makes the waiting task ready to run as
soon as any one of the objects is posted (i.e., OR condition), or a timeout
occurs.
章节 17:内存管理。介绍了 uC/OS-III 的内存管理模块如何动态地分配和回收内存块。
Chapter
17, Memory Management. Here is how μC/OS-III’s fixed-size memory partition
manager can be used to allocate and deallocate dynamic memory.
章节 18:移植 uC/OS-III。如何移植 uC/OS-III 到任何架构的 CPU。章节 19:实时统计。uC/OS-III 提供了实时运行环境的大量信息。例如上下文切换次数,CPU 使用率,每个任务的平均堆栈使用量。
Chapter
18, Porting μC/OS-III. This chapter explains, in generic terms, how to port
μC/OS-III to any CPU architecture.
章节 19:uC/OS-III 的 RAM 使用量,最大关中断时间,最大调度器锁存时间等。附录 A:uC/OS-III 的 API 手册:按字母排序的 uC/OS-III 中提供的API 服务。
Chapter
19, Run-Time Statistics. μC/OS-III provides a wealth of information about the
run-time environment, such as number of context switches, CPU usage (as a
percentage), stack usage on a per-task basis, μC/OS-III RAM usage, maximum
interrupt disable time, maximum scheduler lock time, and more.
这个章节将会介绍 uC/OS-III 的模块以及怎样设置他们。
μC/OS-III
is fairly easy to use once it is understood exactly which source files are
needed to make up a μC/OS-III-based application. This chapter will discuss the
modules available for μC/OS-III and how everything fits together.
图 2-1 显示的是 uC/OS-III 的架构以及与硬件的关系。包括硬件定时器和中断控制器。也应该包括 UARTs,ADCs,以太网等。
Figure
2-1 shows the μC/OS-III architecture and its relationship with hardware. Of
course, in addition to the timer and interrupt controller, hardware would most
likely contain such other devices as Universal Asynchronous Receiver
Transmitters (UARTs), Analog to Digital Converters (ADCs), Ethernet
controller(s) and more.
这个章节假定开发是基于 Window 平台的,并有供参考的典型目录结构。然而,因为 uC/OS-III 是以源代码形式提供的,所以它也可以被用于 Unix,Linux,或者其它的开发平台。
This
chapter assumes development on a Windows®-based platform and makes references
to typical Windows-type directory structures (also called Folder). However,
since μC/OS-III is available in source form, it can also be used on Unix, Linux
or other development platforms.
F2-1(1)应用代码包括与工程、产品相关文件。为了方便,这些被简单地叫做 APP.C 和 APP.H。Main()函数应该在 APP.C 代码中。
The
application code consists of project or product files. For convenience, these
are simply called APP.C and APP.H, however an application can contain any
number of files that do not have to be called APP.*. The application code is
typically where one would find main().
F2-1(2)半导体厂家通常会提供库函数以控制那些 CPU 或 MCU 的外设。这些库非常有用并且高效。因为对这些文件没有规定。所以假定为*.C,*.H。
Semiconductor
manufacturers often provide library functions in source form for accessing the
peripherals on their CPU or MCU. These libraries are quite useful and often
save valuable time. Since there is no naming convention for these files, *.C
and *.H are assumed.
F2-1(3)板级支持包通常被用来初始化目标板。例如打开或关闭LED、继电器、读取开关值、读取温度传感器等。
The
Board Support Package (BSP) is code that is typically written to interface to
peripherals on a target board. For example such code can turn on and off Light
Emitting Diodes (LEDs), turn on and off relays, or code to read switches,
temperature sensors, and more.

F2-1(4)这些是 uC/OS-III 的与处理器无关的代码。这些代码都是高度遵循 ANSI C 标准。
This
is the μC/OS-III processor-independent code. This code is written in highly
portable ANSI C and is available to μC/OS-III licensees only.
F2-1(5)这些 uC/OS-III 代码用于适应不同架构的 CPU,在名为 port 的文件夹中。uC/OS-III 源于 uC/OS-II。uC/OS-II 能移植成功的,只要稍有改动便能移植 uC/OS-III。详见附录 C。
This
is the μC/OS-III code that is adapted to a specific CPU architecture and is
called a port. μC/OS-III has its roots in μC/OS-II and benefits from being able
to use most of the 45 or so ports available for μC/OS-II. μC/OS-II ports,
however, will require small changes to work with μC/OS-III. These changes are
described in Appendix C, “Migrating from μC/OS-II to μC/OS-III” on page 599.
F2-1(6)在 Micrium,我们喜欢去总结 CPU 的功能。这些包括中断的使能和除能。CPU_???类型的文件都是独立于 CPU 的,在编译时用到,而且可能非常有用。
This
is the μC/OS-III code that is adapted to a specific CPU architecture and is
called a port. μC/OS-III has its roots in μC/OS-II and benefits from being able
to use most of the 45 or so ports available for μC/OS-II. μC/OS-II ports,
however, will require small changes to work with μC/OS-III. These changes are
described in Appendix C, “Migrating from μC/OS-II to μC/OS-III” on page 599.
F2-1(7)uC/LIB 是一系列的源文件,提供了常用基本的功能如内存拷贝,字符串,ASCII 相关的函数。一些可以代替编译器提供的 stdlib 的功能。这些文件是应用与应用间,编译器与编译器间可移植。uC/OS-III 不需要这些文件,但是 uC/CPU 需要。
μC/LIB
is of a series of source files that provide common functions such as memory copy,
string, and ASCII-related functions. Some are occasionally used to replace
stdlib functions provided by the compiler. The files are provided to ensure
that they are fully portable from application to application and especially,
from compiler to compiler. μC/OS-III does not use these files, but μC/CPU does.
F2-1(8)uC/OS-III 功能的配置文件(OS_CFG.H)包含在应用中,OS_CFG_APP.H 定义了 uC/OS-III 所需的变量类型大小、数据的结构、空闲任务堆栈的大小、时钟速率、内存池大小等。
μC/OS-III
configuration files defines μC/OS-III features (OS_CFG.H) to include in the
application, and specifies the size of certain variables and data structures
expected by μC/OS-III (OS_CFG_APP.H), such as idle task stack size, tick rate,
size of the message pool, etc.
如果 Micrium 提供了例子。那么它将被包含在如下的目录结构。
\Micrium
\Software
\EvalBoards
\<manufacturer>
\<board
name>
\<compiler>
\<project
name>
\*.*
\Micrium
这是我们存放软件或工程的地方,通常位于电脑的根目录。
This
is where we place all software components and projects provided by Micriμm.
This directory generally starts from the root directory of the computer.
\Software
子目录中是软件成分。
This
sub-directory contains all software components and projects.
\EvalBoards
子目录中包含了评估版的工程。
This
sub-directory contains all projects related to evaluation boards supported by
Micriμm.
\<manufacturer>
制造商的名字 名字中不包括"<"和">"。
This
is the name of the manufacturer of the evaluation board. The “<” and “>”
are not part of the actual name.
\<board name>
评估板的名字。Micrium 通常命名为 uC-Eval-xxxx。用 CPU 或 MCU类型替代''xxxx''。
This
is the name of the evaluation board. A board from Micriμm will typically be
called uC-Eval-xxxx where “xxxx” represents the CPU or MCU used on the board.
The “<” and “>” are not part of the actual name.
\<compiler>
代码所用编译器的名字
This
is the name of the compiler or compiler manufacturer used to build the code for
the evaluation board. The “<” and “>” are not part of the actual name.
\<project name>
工程名。例如,uC/OS-III 工程会被命名为"OS-Ex1"。"-Ex1"表明工程中值包含 uC/OS-III。命名为 OS-Probe-Ex1 表示工程中包含uC/OS-III 和 uC/Probe。
The
name of the project that will be demonstrated. For example, a simple μC/OS-III
project might have a project name of “OS-Ex1”. The “-Ex1” represents a project
containing only μC/OS-III. The project name OS-Probe-Ex1 contains μC/OS-III and
μC/Probe.
\*.*
这些是工程的源文件,main 文件可以被命名为 APP*.*。目录中也包括配置文件 OS_CFG.H,OS_CFG_APP.H 以及其它需要的源文件。
These
are the project source files. Main files can optionally be called APP*.*. This
directory also contains configuration files OS_CFG.H, OS_CFG_APP.H and other
required source files.
在这个目录中,你会找到半导体厂商提供的外设库文件。
The
directory where you will find semiconductor manufacturer peripheral interface
source files is shown below. Any directory structure that suits the
project/product may be used.
\Micrium
The
location of all software components and projects provided by Micriμm.
\Software
The
location of all software components and projects provided by Micriμm.
\CPU
This
sub-directory is always called CPU.
\<manufacturer>
Is
the name of the semiconductor manufacturer providing the peripheral library.
\<architecture>
The
name of the specific library, generally associated with a CPU name or an
architecture.
\*.*
Indicates
library source files. The semiconductor manufacturer names the files.
板级支持包通常是目标器件的特殊配置。实时上,写得好的话,BSP 将适用于多个工程。
The
Board Support Package (BSP) is generally found with the evaluation or target
board as it is specific to that board. In fact, when well written, the BSP
should be used for multiple projects.
\Micrium
\Software
\EvalBoards
\<manufacturer>
\<board name>
\<compiler>
\BSP
\*.*
The files
in these directories are available to μC/OS-III licensees (see Appendix F,
“Licensing Policy” on page 645).
\Micrium
\Software
\uCOS-III
\Cfg\Template
\OS_APP_HOOKS.C \OS_CFG.H
\OS_CFG_APP.H \Source
\OS_CFG_APP.C \OS_CORE.C \OS_DBG.C
\OS_FLAG.C \OS_INT.C \OS_MEM.C \OS_MSG.C \OS_MUTEX.C \OS_PEND_MULTI.C
\OS_PRIO.C \OS_Q.C \OS_SEM.C \OS_STAT.C \OS_TASK.C \OS_TICK.C \OS_TIME.C
\OS_TMR.C \OS_VAR \OS.H
\OS_TYPE.H
\Micrium
Contains
all software components and projects provided by Micriμm.
\Software
This
sub-directory contains all software components and projects.
\uCOS-III
This
is the main μC/OS-III directory.
\Cfg\Template
This
directory contains examples of configuration files to copy to the project
directory. You will then modify these files to suit the needs of the
application.
OS_APP_HOOKS.C
shows how to write hook functions that are called by μC/OS-III.
Specifically, this file contains eight empty functions.
OS_CFG.H specifies which
features of μC/OS-III are available for an application. The file is typically
copied into an application directory and edited based on which features are
required from μC/OS-III. If μC/OS-III is provided in linkable object code
format, this file will be provided to indicate features that are available in
the object file. See Appendix B, “μC/OS-III Configuration Manual” on page 579.
OS_CFG_APP.H is a
configuration file to be copied into an application directory and edited based
on application requirements. This file enables the user to determine the size
of the idle task stack, the tick rate, the number of messages available in the
message pool and more. See Appendix B, “μC/OS-III Configuration Manual” on page
579.
\Source
The directory
containing the CPU-independent source code for μC/OS-III. All files in this
directory should be included in the build (assuming you have the source code).
Features that are not required will be compiled out based on the value of #define constants in OS_CFG.H and OS_CFG_APP.H.
OS_CFG_APP.C declares
variables and arrays based on the values in OS_CFG_APP.H.
OS_CORE.C contains core
functionality for μC/OS-III such as OSInit() to initialize
μC/OS-III, OSSched() for the task
level scheduler, OSIntExit() for the
interrupt level scheduler, pend list (or wait list) management (see Chapter 10,
“Pend Lists (or Wait Lists)” on page 177), ready list management (see Chapter
6, “The Ready List” on page 123), and more.
OS_DBG.C
contains declarations of constant variables used by a kernel aware
debugger or μC/Probe.
OS_FLAG.C contains the
code for event flag management. See Chapter 14,
“Synchronization”
on page 251 for details about event flags.
OS_INT.C
contains code for the interrupt handler task, which is used when
OS_CFG_ISR_POST_DEFERRED_EN (see OS_CFG.H) is set to 1.
See Chapter 9, “Interrupt Management” on page 157 for details regarding the
interrupt handler task.
OS_MEM.C
contains code for the μC/OS-III fixed-size memory manager, see
Chapter 17, “Memory Management” on page 323.
OS_MSG.C contains code
to handle messages. μC/OS-III provides message queues and task specific message
queues. OS_MSG.C provides
common code for these two services.
See Chapter 15,
“Message Passing” on page 289.
OS_MUTEX.C
contains code to manage mutual exclusion semaphores, see Chapter 13,
“Resource Management” on page 209.
OS_PEND_MULTI.C contains the code
to allow code to pend on multiple semaphores or message queues. This is
described in Chapter 16, “Pending On Multiple Objects” on page 313.
OS_PRIO.C contains the
code to manage the bitmap table used to keep track of which tasks are ready to
run, see Chapter 6, “The Ready List” on page 123. This file can be replaced by
an assembly language equivalent to improve performance if the CPU used provides
bit set, clear and test instructions, and a count leading zeros instruction.
OS_Q.C contains code
to manage message queues. See Chapter 15, “Message Passing” on page 289.
OS_SEM.C contains code
to manage semaphores used for resource management and/or synchronization. See
Chapter 13, “Resource Management” on page 209 and Chapter 14, “Synchronization”
on page 251.
OS_STAT.C
contains code for the statistic task, which is used to compute the
global CPU usage and the CPU usage of each of tasks. See Chapter 5, “Task
Management” on page 75.
OS_TASK.C contains code
for managing tasks using OSTaskCreate(), OSTaskDel(), OSTaskChangePrio(), and many
more. See Chapter 5, “Task Management” on page 75.
OS_TICK.C contains code
to manage tasks that have delayed themselves or that are pending on a kernel
object with a timeout. See Chapter 5, “Task Management” on page 75.
OS_TIME.C contains code
to allow a task to delay itself until some time expires. See Chapter 11, “Time
Management” on page 183.
OS_TMR.C contains code
to manage software timers. See Chapter 12, “Timer Management” on page 193.
OS_VAR.C contains the
μC/OS-III global variables. These variables are for μC/OS-III to manage and
should not be accessed by application code.
OS.H contains the
main μC/OS-III header file, which declares constants, macros, μC/OS-III global
variables (for use by μC/OS-III only), function prototypes, and more.
OS_TYPE.H contains
declarations of μC/OS-III data types that can be changed by the port designed
to make better use of the CPU architecture. In this case, the file would
typically be copied to the port directory and then modified. μC/OS-III in
linkable object library format provides this file to enable the user to know
what each data type maps to. See Appendix B, “μC/OS-III Configuration Manual”
on page 579.
The μC/OS-III port developer provides these files. See also Chapter
18, “Porting μC/OS-III” on page 335.
\Micrium
\Software
\uCOS-III
\Ports
\<architecture>
\<compiler>
\OS_CPU.H \OS_CPU_A.ASM
\OS_CPU_C.C
\Micrium
Contains all software
components and projects provided by Micriμm.
\Software
This
sub-directory contains all software components and projects.
\uCOS-III
The main
μC/OS-III directory.
\Ports
The location of
port files for the CPU architecture(s) to be used.
\<architecture>
This is the
name of the CPU architecture that μC/OS-III was ported to. The “<” and
“>” are not part of the actual name.
\<compiler>
The name of the
compiler or compiler manufacturer used to build code for the port. The “<”
and “>” are not part of the actual name.
The files in
this directory contain the μC/OS-III port, see Chapter 18, “Porting μC/OS-III”
on page 335 for details on the contents of these files.
OS_CPU.H contains a
macro declaration for OS_TASK_SW(), as well as
the function prototypes for at least the following functions: OSCtxSw(), OSIntCtxSw() and OSStartHighRdy().
OS_CPU_A.ASM contains the
assembly language functions to implement at least the following functions: OSCtxSw(), OSIntCtxSw() and OSStartHighRdy().
OS_CPU_C.C
contains the C code for the port specific hook functions and code to
initialize the stack frame for a task when the task is created.
μC/CPU consists
of files that encapsulate common CPU-specific functionality and CPU and
compiler-specific data types. See Chapter 18, “Porting μC/OS-III” on page 335.
\Micrium
\Software
\uC-CPU
\CPU_CORE.C \CPU_CORE.H
\CPU_DEF.H
\Cfg\Template
\CPU_CFG.H
\<architecture>
\<compiler>
\CPU.H \CPU_A.ASM \CPU_C.C
\Micrium
Contains all
software components and projects provided by Micriμm.
\Software
This
sub-directory contains all software components and projects.
\uC-CPU
This is the
main μC/CPU directory.
CPU_CORE.C contains C
code that is common to all CPU architectures. Specifically, this file contains
functions to measure the interrupt disable time of the CPU_CRITICAL_ENTER() and CPU_CRITICAL_EXIT() macros, a
function that emulates a count leading zeros instruction and a few other
functions.
CPU_CORE.H contains
function prototypes for the functions provided in CPU_CORE.C and allocation
of the variables used by the module to measure interrupt disable time.
CPU_DEF.H contains
miscellaneous #define constants used
by the μC/CPU module.
\Cfg\Template
This directory
contains a configuration template file (CPU_CFG.H) that must be
copied to the application directory to configure the μC/CPU module based on
application requirements.
CPU_CFG.H determines
whether to enable measurement of the interrupt disable time, whether the CPU
implements a count leading zeros instruction in assembly language, or whether
it will be emulated in C, and more.
\<architecture>
The name of the
CPU architecture that μC/CPU was ported to. The “<” and “>” are not part
of the actual name.
\<compiler>
The name of the
compiler or compiler manufacturer used to build code for the μC/CPU port. The
“<” and “>” are not part of the actual name.
The files in
this directory contain the μC/CPU port, see Chapter 18, “Porting μC/OS-III” on
page 335 for details on the contents of these files.
CPU.H contains type
definitions to make μC/OS-III and other modules independent of the CPU and
compiler word sizes. Specifically, one will find the declaration of the CPU_INT16U, CPU_INT32U, CPU_FP32 and many other
data types. This file also specifies whether the CPU is a big or little endian
machine, defines the CPU_STK data type used
by μC/OS-III, defines the macros OS_CRITICAL_ENTER() and OS_CRITICAL_EXIT(), and contains
function prototypes for functions specific to the CPU architecture, and more.
CPU_A.ASM contains the
assembly language functions to implement code to disable and enable CPU
interrupts, count leading zeros (if the CPU supports that instruction), and
other CPU specific functions that can only be written in assembly language.
This file may also contain code to enable caches, setup MPUs and MMU, and more.
The functions provided in this file are accessible from C.
CPU_C.C contains C
code of functions that are based on a specific CPU architecture but written in
C for portability. As a general rule, if a function can be written in C then it
should be, unless there is significant performance benefits available by
writing it in assembly language.
μC/LIB consists of library functions meant to be highly portable and not
tied to any specific compiler. This facilitates third-party certification of
Micriμm products. μC/OS-III does not use any μC/LIB functions, however the
μC/CPU assumes the presence of LIB_DEF.H
for such definitions as: DEF_YES,
DEF_NO, DEF_TRUE, DEF_FALSE, DEF_ON, DEF_OFF and more.
\Micrium
\Software
\uC-LIB
\LIB_ASCII.C \LIB_ASCII.H
\LIB_DEF.H \LIB_MATH.C \LIB_MATH.H \LIB_MEM.C \LIB_MEM.H \LIB_STR.C \LIB_STR.H
\Cfg\Template
\LIB_CFG.H \Ports
\<architecture> \<compiler>
\LIB_MEM_A.ASM
\Micrium
Contains all software components and projects provided by Micriμm.
\Software
This sub-directory contains all software components and projects.
\uC-LIB
This is the main μC/LIB directory.
\Cfg\Template
This directory contains a configuration template file (LIB_CFG.H) that are required to be copied to the application directory to
configure the μC/LIB module based on application requirements.
LIB_CFG.H determines
whether to enable assembly language optimization (assuming there is an assembly
language file for the processor, i.e., LIB_MEM_A.ASM) and a few other #defines.
下面的概要是基于 uC/OS-III 工程的所有目录文件。后缀为"<-Cfg" 的文件通常要被拷贝到应用文件夹中并被修改。
Below
is a summary of all directories and files involved in a μC/OS-III-based
project. The “<-Cfg” on the far right indicates that these files are
typically copied into the application (i.e., project) directory and edited
based on the project requirements.



uC/OS-III 以函数的形式提供特定的服务。uC/OS-III 提供的服务包括任务管理,信号量,消息队列,互斥信号量等。uC/OS-III 提供了大约 70 种功能。
μC/OS-III
provides services to application code in the form of a set of functions that
perform specific operations. μC/OS-III offers services to manage tasks,
semaphores, message queues, mutual exclusion semaphores and more. As far as the
application is concerned, it calls the μC/OS-III functions as if they were any
other functions. In other words, the application now has access to a library of
approximately 70 new functions.
在这个章节中,读者可以领会使用 uC/OS-III 是多么简单。参考附录 A 的 uC/OS-III 的 API。
In
this chapter, the reader will appreciate how easy it is to start using
μC/OS-III. Refer to Appendix A, “μC/OS-III API Reference Manual” on page 375,
for the full description of several of the μC/OS-III services presented in this
chapter.
假定工程已被设置为前一章节那样(目录和文件已经就位),并且在 C 编译器中进行。然而,这章节对工具和处理器没有做出要求。
It
is assumed that the project setup (files and directories) is as described in
the previous chapter, and that a C compiler exists for the target processor that
is in use. However, this chapter makes no assumptions about the tools or the
processor that is used.
列表 3-1 显示的是应用文件 APP.C 的顶部代码。
Listing
3-1 shows the top portion of a simple application file called APP.C.

L3-1(1) 正如所有的 C 程序一样,添加必要的头文件到应用中。
As
with any C programs, include the necessary headers to build the application.
APP_CFG.H 是用于配置的头文件。例如,APP_CFG.H 中包含的 #define 常量确定了任务优先级,堆栈大小,以及其他特性。
APP_CFG.H
is a header file that configures the application. For our example, APP_CFG.H
contains #define constants to establish task priorities, stack sizes, and other
application specifics.
BSP.H 是 BSP 的头文件,包含了 #define 及函数原型如BSP_Init(),,SP_LED_On(),OS_TS_GET()等。
BSP.H
is the header file for the Board Support Package (BSP), which defines #defines
and function prototypes, such as BSP_Init(), BSP_LED_On(), OS_TS_GET() and
more.
OS.H 是 uC/OS-III 的主要头文件,包含了以下头文件:
OS.H
is the main header file for μC/OS-III, and includes the following header files:
OS_CFG.H
CPU.H
CPU_CFG.H
CPU_CORE.H
OS_TYPE.H
OS_CPU.H
L3-1(2)定义任务控制块(OS_TCB)。
L3-1(2)
We will be creating an application task and it is necessary to allocate a task
control block (OS_TCB) for this task.
L3-1(3)每个任务需要创建自己的堆栈。堆栈的数据类型必须是CPU_STK。堆栈可以被静态地分配或者通过 malloc()动态地分配。没有必要释放堆栈空间,因为任务将不会被删除,堆栈将一直被使用。
L3-1(3)
Each task created requires its own stack. A stack must be declared using the
CPU_STK data type. The stack can be allocated statically as shown here, or
dynamically from the heap using malloc(). It should not be necessary to free
the stack space, because the task should never be stopped, and the stack will
always be used.
L3-1(4)这是一个函数原型。
This
is the function prototype of the task that we will create.
大部分的 C 应用程序开始于 main(),见列表
3-2.
Most
C applications start at main() as shown in Listing 3-2.

L3-2(1)main()开始时调用一个 BSP 函数用于关闭所有中断。在大部分处理器中,中断在启动时是关闭的。无论如何,在启动时关闭所有的外设中断是更安全的。
L3-2(1) Start main() by calling a BSP
function that disables all interrupts. On most processors, interrupts are
disabled at startup until explicitly enabled by application code. However, it
is safer to turn off all peripheral interrupts during startup.
L3-2(2)调用 OSInit(),用于初始化 uC/OS-III。OSInit()初始化内部变量和数据结构,同时产生 2 个到 5 个内部任务。最低程度, uC/OS-III 须创建空闲任务 OS_IdleTask(),当没有其他任务运行时就运行空闲任务。uC/OS-III 也创建时基任务。
Call
OSInit(), which is responsible for initializing μC/OS-III. OSInit() initializes
internal variables and data structures, and also creates two (2) to five (5)
internal tasks. At a minimum, μC/OS-III creates the idle task (OS_IdleTask()),
which executes when no other task is ready to run. μC/OS-III also creates the
tick task, which is responsible for keeping track of time.
根据配置文件中所配置的, uC/OS-III 会创建统计任务
OS_StatTask() 、定时器任务 OS_TmrTask() 、中断队列处理任务
OS_IntQTask()。这些将会在第五章"任务管理"中详细介绍。
Depending
on the value of #define constants, μC/OS-III will create the statistic task
(OS_StatTask()), the timer task (OS_TmrTask()), and the interrupt handler queue
management task (OS_IntQTask()). Those are discussed in Chapter 5, “Task
Management” on page 75.
大多数的 uC/OS-III 函数会通过一个指向 OS_ERR 变量的指针返回一个错误代号。如果 OSInit()初始化函数运行成功,错误代号被设为 OS_ERR_NONE。如果在初始化不成功,uC/OS-III 会根据执行的结果返回对应的错误代号。参照 OS.H 中的错误代号。特别的,所有的错误代号都是以 OS_ERR_作为前缀的。
Most
of μC/OS-III’s functions return an error code via a pointer to an OS_ERR
variable, err in this case. If OSInit() was successful, err will be set to
OS_ERR_NONE. If OSInit() encounters a problem during initialization, it will
return immediately upon detecting the problem and set err accordingly. If this
occurs, look up the error code value in OS.H. Specifically, all error codes
start with OS_ERR_.
OSInit()必须在 uC/OS-III 的其它函数之前调用。
It
is important to note that OSInit() must be called before any other μC/OS-III
function.
L3-2(3) 通过调用 OSTaskCreate()创建任务。OSTaskCreate()需要 13 个参数。第一个参数是任务堆栈的地址。{该任务堆栈的开始地址}详见第 5 章。
L3-2(3) Create a task by calling
OSTaskCreate(). OSTaskCreate() requires 13 arguments. The first argument is the
address of the OS_TCB that is declared for this task. Chapter 5, “Task
Management” on page 75 provides additional information about tasks.
L3-2(4)OSTaskCreate()允许给每个任务分配名字。OS_TCB 中存储了指向任务名的指针。因而任务名长度无限制,必须以空字符结尾。
L3-2(4) OSTaskCreate() allows a name to
be assigned to each of the tasks. μC/OS-III stores a pointer to the task name
inside the OS_TCB of the task. There is no limit on the number of ASCII
characters used for the name.
L3-2(5) 第 3 个参数是指向任务代码的指针。典型的 uC/OS-III 任务是无限循环执行的如下。
L3-2(5) The third argument is the
address of the task code. A typical μC/OS-III task is implemented as an
infinite loop as shown:

任务第一次开始时接收一个参数。任务看起来像是一个可以被调用的
C 函数。然而,用户代码不允许调用任务。任务被创建后是由 uC/OS-III 调用的。
The
task receives an argument when it first starts. As far as the task is
concerned, it looks like any other C function that can be called by the code.
However, the code must not call MyTask(). The call is actually performed
through μC/OS-III.
L3-2(6)OSTaskCreate()的第四个参数是一个实参,第一次被调用时 OSTaskCreate()接收这个变量,传递给所创建的任务 MyTask()中的
"p_arg"。
L3-2(6) The fourth argument of
OSTaskCreate() is the actual argument that the task receives when it first
begins. In other words, the “p_arg” of MyTask(). In the example a NULL pointer
is passed, and thus “p_arg” for AppTaskStart() will be a NULL pointer.
任务的参数可以是任意的指针。例如,用户可以传送数据结构等给任务。{参数类型是 void*}
The
argument passed to the task can actually be any pointer. For example, the user
may pass a pointer to a data structure containing parameters for the task.
L3-2(7)OSTaskCreate()的第五个参数是任务的优先级。优先级确立了任务间的重要性关系。参数值越小优先级越高。可以设置优先级数值为 1 到 OS_CFG_PRIO_MAX-2。要避免使用优先级#0 和优先级 OS_CFG_PRIO_MAX-1 。因为这些是为 uC/OS-III 保留的。OS_CFG_PRIO_MAX 是编译时配置的,在 OS_CFG.H 中定义。
L3-2(7) The next argument to OSTaskCreate()
is the priority of the task. The priority establishes the relative importance
of this task with respect to the other tasks in the application. A low-priority
number indicates a high priority (or more important task). Set the priority of
the task to any value between 1 and OS_CFG_PRIO_MAX-2, inclusively. Avoid using
priority #0, and priority OS_CFG_PRIO_MAX-1, because these are reserved for
μC/OS-III. OS_CFG_PRIO_MAX is a compile time configuration constant, which is
declared in OS_CFG.H.
L3-2(8)是任务堆栈的基地址。基地址通常是分配给该任务的堆栈的最低内存位置。
L3-2(8) The sixth argument to
OSTaskCreate() is the base address of the stack assigned to this task. The base
address is always the lowest memory location of the stack.
L3-2(9)第七个参数是地址“水印”,当堆栈生长到指定位置时就不再允许其生长。详见章节 5。在例子中,当堆栈空间只剩下 10%的时候将会限制堆栈的生长。
L3-2(9) The next argument specifies
the location of a “watermark” in the task’s stack that can be used to determine
the allowable stack growth of the task. See Chapter 5, “Task Management” on
page 75 for more details on using this feature. In the code above, the value
represents the amount of stack space (in CPU_STK elements) before the stack is
empty. In other words, in the example, the limit is reached when there is 10%
of the stack left.
L3-2(10)OSTaskCreate()的第八个参数定义了任务的堆栈大小(以 CPU_STK 为数据类型而不是字节)。例如,如果要分配 1KB 大小的堆栈空间,因为 CPU_STK 是 32 位的,所以这个参数是 256.
L3-2(10) The eighth argument to OSTaskCreate()
specifies the size of the task’s stack in number of CPU_STK elements (not
bytes). For example, if allocating 1 Kbyte of stack space for a task and the
CPU_STK is a 32-bit word, then pass 256.
L3-2(11)接下来的三个参数将被跳过因为这三个参数跟当前的话题无关,直接设置为 0。再下面一个参数是 OSTaskCreate()的可选项。例如,在运行时堆栈会被检测(假定统计任务在 OS_CFG.H 中使能),任务创建时堆栈会被初始化。
L3-2(11) The next three arguments are skipped as
they are not relevant for the current discussion. The next argument to
OSTaskCreate() specifies options. In this example, we specify that the stack
will be checked at run time (assuming the statistic task was enabled in
OS_CFG.H), and that the contents of the stack will be cleared when the task is
created.
L3-2(12)OSTaskCreate()的最后一个参数是一个指针,将接收根据函数执行结果所返回的错误代号。如果 OSTaskCreate()函数执行成功,错误代号将会是 OS_ERR_NONE,否则会返回其它的错误代号(参见 OS.H 中错误代号的定义)。
L3-2(12) The last argument of OSTaskCreate() is a
pointer to a variable that will receive an error code. If OSTaskCreate() is
successful, the error code will be OS_ERR_NONE otherwise, look up the value of
the error code in OS.H (See OS_ERR_xxxx) to determine the problem with the
call.
L3-2(13)调用 uC/OS-III 过程在 main()函数中的最后一个步骤是调用 OSStart(),开始多任务处理。特别的,在 OSStart()调用之前 uC/OS-III 会选择最高优先级任务。最高优先级的任务通常是 OS_IntQTask()(假定在 OS_CFG.H 中定义了 OS_CFG_ISR_POST_DEFERRED_EN)。在种情况下,OS_IntQTask()将会执行一些它自身的初始化操作,然后 uC/OS-III 将会切换到下一个最高优先级的任务。
L3-2(13) The final step in main() is to call
OSStart(), which starts the multitasking process. Specifically, μC/OS-III will
select the highest-priority task that was created before calling OSStart(). The
highest-priority task is always
OS_IntQTask()
if that task is enabled in OS_CFG.H (through the
OS_CFG_ISR_POST_DEFERRED_EN
constant). If this is the case, OS_IntQTask() will perform some initialization
of its own and then μC/OS-III will switch to the next most important task that
was created.
OSTaskCreate()中的一些指针没有意义。首先,在调用 OSStart() 之前创建你想要创建的任务。然而,我们推荐你此时最好只创建一个任务,因为在这种情况下 uC/OS-III 可以确定 CPU 的速率,可以测量
CPU 的使用率。如果应用需要另外的内核对象如信号量、消息队列,那么推荐在调用 OSStart()之前创建它们。最后,注意中断还没有开启。这将会在下面一个函数 AppTaskStart()中讨论。见列表 3-3
A
few important points are worth noting. For one thing, create as many tasks as
you want before calling OSStart(). However, it is recommended to only create
one task as shown in the example because, having a single application task
allows μC/OS-III to determine how fast the CPU is, in order to determine the
percentage of CPU usage at run-time. Also, if the application needs other
kernel objects such as semaphores and message queues then it is recommended
that these be created prior to calling OSStart(). Finally, notice that that
interrupts are not enabled. This will be discussed next by examining the
contents of AppTaskStart(), which is shown in Listing 3-3.

L3-3(1)正如前面提到的,一个任务看起来像是一个 C 函数。参数
“p_arg”是 OSTaskCreate()传递给任务 AppTaskStart()的参数。
L3-3(1) As previously mentioned, a
task looks like any other C function. The argument “p_arg” is passed to
AppTaskStart() by OSTaskCreate(), as discussed in the previous listing
description.
L3-3(2)BSP_Init()用于初始化目标板的硬件。目标板可能会有一些 GPIO,继电器,传感器等需要被设置。这个函数是在 BSP.C 中定义的。
L3-3(2) BSP_Init() is a Board Support
Package (BSP) function that is responsible for initializing the hardware on an
evaluation or target board. The evaluation board might have General Purpose
Input Output (GPIO) lines that might need to be configured, relays, sensors and
more. This function is found in a file called BSP.C.
L3-3(3)CPU_Init()初始化 uC/CPU 的服务。uC/CPU 用于测量中断响应时间,读取时间戳,提供仿真的计数清零指令等(假定用户所使用的处理器没有那种汇编指令)。
L3-3(3) CPU_Init() initializes the
μC/CPU services. μC/CPU provides services to measure interrupt latency, receive
time stamps, and provides emulation of the count leading zeros instruction if
the processor used does not have that instruction and more.
L3-3(4)BSP_Cfg_Tick()设置 uC/OS-III 的时基中断。为此,这个函数需要初始化一个硬件定时器用于中断 CPU ,其频率为
OS_CFG_TICK_RATE_HZ(在 OS_CFG_APP.H 中定义)。
L3-3(4) BSP_Cfg_Tick() sets up the
μC/OS-III tick interrupt. For this, the function needs to initialize one of the
hardware timers to interrupt the CPU at a rate of:
OSCfg_TickRate_Hz,
which is defined in OS_CFG_APP.H (See OS_CFG_TICK_RATE_HZ).
L3-3(5)BSP_LED_Off()用于关闭 LED,参数为 0 表示关闭全部的 LED。这是个用户函数,可删除。
L3-3(5) BSP_LED_Off() is a function
that will turn off all LEDs because the function is written so that a zero
argument means all the LEDs.
L3-3(6)所有的 uC/OS-III 任务需要被设置为无限循环。
L3-3(7)BSP_LED_Toggle()用于打开 LED,同样的,参数为 0 表示打开全部 LED。改参数为 1 表示标号为#1 的 LED 被打开。但哪个 LED 标号为#1 呢?这取决于设计者。特别的,可以将 LED 的操作封装为如 BSP_LED_On(),BSP_LED_Off()、BSP_LED_Toggle()的函数。我们更喜欢定义 LED 为逻辑位(1,2,3 等),每一位对应一个端口值。该函数是用户函数,可删除
L3-3(8)最后,每个任务可以调用 uC/OS-III 中的函数,可以让任务待一个事件(信号量,或来自于中断的消息,或来自于其它任务的消息。)而被挂起。任务可以设置等待期限(通过调用 OSTimeDly() 或者 OSTimeDlyHMSM()),章节 11“时间管理”介绍了等待期限的相关信息。
列表 3-4 到 3-8 的代码展示了一个包含三个任务的完整例子,一个是 mutex,一个是信号量,一个是消息队列。
The
code of Listing 3-4 through Listing 3-8 shows a more complete example and
contains three tasks: a mutual exclusion, semaphore, and a message queue.

L3-4(1) 分别为每个任务分配一个 OS_TCB。
L3-4(1) Allocate storage for the
OS_TCBs of each task.
L3-4(2)互斥信号量(mutex)是一个内核对象(一个结构体),用于保护共享资源。任务要访问共享资源就必须先获得 mutex。mutex 的拥有者使用完这个资源后就必须释放这个 mutex。这个例子示范了这个过程。
L3-4(2) A mutual exclusion semaphore
(a.k.a. a mutex) is a kernel object (a data structure) that is used to protect
a shared resource from being accessed by more than one task. A task that wants
to access the shared resource must obtain the mutex before it is allowed to
proceed. The owner of the resource relinquishes the mutex when it has finished
accessing the resource. This process is demonstrated in this example.
L3-4(3)消息队列是一个内核对象,ISR 或任务可以直接发送消息到另一个任务。发送者制定一个消息并将其发送到目标任务的消息队列。目标任务等待消息的到达。如果消息到达了,目标任务取得这些消息。如果消息队列为空,目标将会被安放在挂起队列中并与消息队列保持联系。这个过程将会在这个例子中介绍。
L3-4(3) A message queue is a kernel
object through which Interrupt Service Routines (ISRs) and/or tasks send
messages to other tasks. The sender “formulates” a message and sends it to the
message queue. The task(s) wanting to receive these messages wait on the
message queue for messages to arrive. If there are already messages in the
message queue, the receiver immediately retrieves those messages. If there are
no messages waiting in the message queue, then the receiver will be placed in a
wait list associated with the message queue.This process will be demonstrated
in this example.
L3-4 (4)为每个任务分配堆栈。{由于是 CPU_STK 类型,它是32 位的,所以 128 个 CPU_STK 即位 512B}
L3-4(4) Allocate a stack for each
task.
L3-4(5)用户需要申明这些任务。
L3-4(5) The user must prototype the
tasks.
列表 3-5 展示了 C 语言的入口——main()函数。
Listing
3-5 shows the entry point for C, main().

L3-5(1)调用 OSMutexCreate()创建一个 mutex。指定 OS_MUTEX 对象的地址。详见章节 13"资源管理"。
L3-5(1)
Creating a mutex is simply a matter of calling OSMutexCreate(). Specify the
address of the OS_MUTEX object that will be used for the mutex. Chapter 13,
“Resource Management” on page 209 provides additional information about mutual
exclusion semaphores.
可以为 mutex 定义一个 ASCII 名字,对调试会很有用处。
You
can assign an ASCII name to the mutex, which is useful when debugging.
L3-5(2)调用 OSQCreate()创建消息队列,并指定 OS_Q 对象的地址。
详见章节 15“消息传递”。
L3-5(2)
Create the message queue by calling OSQCreate() and specifying the address of the
OS_Q object. Chapter 15, “Message Passing” on page 289 provides additional
information about message queues.
为消息队列命名。
Assign
an ASCII name to the message queue.
定义该消息队列可接受消息的个数。这个值必须大于 0。如果消息者发送消息数超过了消息接收任务的承受能力。那么消息将会被丢失。可以通过增加消息队列的大小或者提供消息接收任务的优先级提升其承受能力。
Specify
how many messages the message queue is allowed to receive. This value must be
greater than zero. If the sender sends messages faster than they can be
consumed by the receiving task, messages will be lost. This can be corrected by
either increasing the size of the message queue, or increasing the priority of
the receiving task.
L3-5(3)第一个应用任务被创建。
L3-5(3) The first application task is
created.
列表 3-6 展示了如何在多任务调度开始后创建任务。
Listing
3-6 shows how to create other tasks once multitasking as started.

L3-6(1)通过调用 OSTaskCreate()创建任务#1。如果被创建任务的优先级大于创建它的任务的优先级。uC/OS-III 会转向执行任务#1. 如果被创建任务的优先级小于创建它的任务的优先级,OSTaskCreate() 将会返回 AppTaskStart()继续执行下面的代码。
L3-6(1) Create Task #1 by calling
OSTaskCreate(). If this task happens to have a higher priority than the task
that creates it, μC/OS-III will immediately start Task #1. If the created task
has a lower priority, OSTaskCreate() will return to AppTaskStart() and continue
execution.
L3-6(2)任务#2 被创建,如果其优先级大于 AppTaskStart()的优先级 。
那么 uC/OS-III 将会立即去执行任务#2。
L3-6(2) Task #2 is created and if it
has a higher priority than AppTaskStart(), μC/OS-III will immediately switch to
that task.

L3-7(1)该任务在执行前先等待一个时基。如果 uC/OS-III 的时基频率为 1000HZ。那么这个任务每毫秒被执行一次。
L3-7(1) The task starts by waiting
for one tick to expire before it does anything useful. If the μC/OS-III tick
rate is configured for 1000 Hz, the task will execute every millisecond.
L3-7(2) 该任务向消息队列 AppQ 发送一个消息。为了说明, 在例子中发送的是一个 void* 1。但实际上消息中包含着的是一个地址。内存地址、函数的地址、或者其它需要被传送的地址。
L3-7(2) The task then sends a message
to another task using the message queue AppQ. In this case, the example shows a
fixed message “1,” but the message could have consisted of the address of a
buffer, the address of a function, or whatever would need to be sent.
L3-7 (3)该任务等待一个信号量。如果它需要访问已被其它任务占用的资源,APPTaskStart1()等待这个 mutex 被释放。第二个参数为其等待时限,以时基为单位,若 0 时就会一直等待下去。
L3-7(3) The task then waits on the
mutual exclusion semaphore since it needs to access a shared resource with
another task. If the resource is already owned by another task, AppTask1() will
wait forever for the mutex to be released by its current owner. The forever
wait is specified by passing 0 as the second argument of the call.
L3-7(4)当 OSMutexPend()返回了,就表明该任务占用了这个共享资源。共享资源可能是变量、数组、数据域、IO 等。
L3-7(4) When OSMutexPend() returns,
the task owns the resource and can therefore access the shared resource. The
shared resource may be a variable, an array, a data structure, an I/O device,
etc.
L3-7(5)如果任务完成对共享资源的使用,它必须调用 OSMutexPost() 释放这个 mutex。
L3-7(5) When the task is done with
the shared resource, it must call OSMutexPost() to release the mutex.

L3-8(1)任务#2 开始执行,并等待消息队列 AppQ 中的消息,这个任务会无限等待下去因为第三个参数为 0,意味着无限等待。
L3-8(1) Task #2 starts by waiting for
messages to be sent through the message queue AppQ. The task waits forever for
a message to be received because the third argument specifies an infinite
timeout.
消息的发送者和接收者都必须知道这个消息中所包含的信息。接收消息的大小存于"msg_size"。"p_msg"是调用该函数后返回的消息地址,指向内存区且"msg_size"中包含这这个内存区的大小。
When
the message is received p_msg will contain the message (i.e., a pointer to
“something”). Both the sender and receiver must agree as to the meaning of the
message. The size of the message received is saved in “msg_size”. Note that
“p_msg” could point to a buffer and “msg_size” would indicate the size of this
buffer.
当接收到消息时,"ts"中包含的是消息被发送时的时间戳。时间戳
的值读取于硬件定时器。时间戳是一个 32 位(或更大)的值。
Also,
when the message is received, “ts” will contain the timestamp of when the
message was sent. A timestamp is the value read from a fairly fast free-running
timer. The timestamp is typically an unsigned 32-bit (or more) value.
L3-8(2)测得消息是什么时候被发送的,用户就能测得任务接收这个消息所用的时间。读取现在的时间戳并减去消息被发送时的时间戳。请注意,消息被发送时,等待消息的任务可能不会立即接收到消息,因为 ISR 或更高优先级的任务需要被运行。
L3-8(2) Knowing when the message was
sent allows the user to determine how long it took this task to get the message.
Reading the current timestamp and subtracting the timestamp of when the message
was sent allows users to know how long it took for the message to be received.
Note that the receiving task may not get the message immediately since ISRs or
other higher-priority tasks might execute before the receiver gets to run.
L3-8(3)处理接收到的消息。
L3-8(3) Proceed with processing the received message.
临界段代码,也称作临界域,是一段不可分割的代码。uC/OS-III 中包含了很多临界段代码。如果临界段可能被中断,那么就需要关中断以保护临界段。如果临界段可能被任务级代码打断,那么需要锁调度器保护临界段。
A
critical section of code, also called a critical region, is code that needs to
be treated indivisibly. There are many critical sections of code contained in
μC/OS-III. If a critical section is accessible by an Interrupt Service Routine
(ISR) and a task, then disabling interrupts is necessary to protect the
critical region. If the critical section is only accessible by task level code,
the critical section may be protected through the use of a preemption lock.
uC/OS-III 中的临界段的保护方法决定于 ISR 中对消息的处理方式 。 详 见 章 节 9 “ 中 断 管 理 ” 。 如 果OS_CFG_ISR_POST_DEFERRED_EN 被设为 0(见 OS_CFG.H),在进 入 临 界 段 之 前 uC/OS-III 会 关 中 断 。 如果 OS_CFG_ISR_POST_DEFERRED_EN 被设为 1,在进入大多数临界段之前会关调度器。
Within μC/OS-III, the critical section access method depends on which ISR post method is used by interrupts (see Chapter 9, “Interrupt Management” on page 157). If OS_CFG_ISR_POST_DEFERRED_EN is set to 0 (see OS_CFG.H) then μC/OS-III will disable interrupts when accessing internal critical sections. If OS_CFG_ISR_POST_DEFERRED_EN is set to 1 then μC/OS-III will lock the scheduler when accessing most of its internal critical sections.
uC/OS-III 定义了一个进入临界段的宏和两个出临界段的宏。
μC/OS-III defines one macro for entering a critical section and two macros for leaving:
OS_CRITICAL_ENTER(),
OS_CRITICAL_EXIT(),
OS_CRITICAL_EXIT_NO_SCHED()
这些是 uC/OS-III 的内部宏,不能被用户代码调用。然而,如果你需要进入你自己定义的临界段。请查阅第十三章"资源管理"。
These
macros are internal to μC/OS-III and must not be invoked by the application
code. However, if you need to access critical sections in your application
code, consult Chapter 13, “Resource Management” on page 209.
设置 OS_CFG_ISR_POST_DEFERRED_EN 为 0 后,在进入临界段之前 uC/OS-III 会关中断,在离开临界段之后开中断。{这里所说的 CPU 寄存器是指辅助 CPU 进行处理的多个寄存器}
When
setting OS_CFG_ISR_POST_DEFERRED_EN to 0, μC/OS-III will disable interrupts
before entering a critical section and re-enable them when leaving the critical
section.
OS_CRITICAL_ENTER()调用 uC/CPU的 宏CPU_CRITICAL_ENTER() ,然 后 调 用 CPU_SR_Save() 。 CPU_SR_Save()是用汇编写的用于保存当前 CPU 寄存器并关中断。寄存器值以类型为“cpu_sr”的变量存于调用者堆栈。
OS_CRITICAL_ENTER()
invokes the μC/CPU macro CPU_CRITICAL_ENTER() that, in turn, calls
CPU_SR_Save(). CPU_SR_Save() is a function typically written in assembly language
that saves the current interrupt disable status and then disables interrupts.
The saved interrupt disable status is returned to the caller and in fact, it is
stored onto the caller’s stack in a variable called “cpu_sr”.
OS_CRITICAL_EXIT() 和 OS_CRITICAL_EXIT_NO_SCHED() 都会调用uC/CPU 的 宏 CPU_CRITICAL_EXIT() 。CPU_CRITICAL_EXIT()调用 CPU_SR_Restore()。CPU_SR_Restore()恢复所保存寄存器值到 CPU 寄存器,也就是
OS_CRITICAL_ENTER()调用前的状态。
OS_CRITICAL_EXIT()
and OS_CRITICAL_EXIT_NO_SCHED() both invoke the μC/CPU macro CPU_CRITICAL_EXIT(),
which maps to CPU_SR_Restore(). CPU_SR_Restore() is passed the value of the
saved “cpu_sr” variable to re-establish interrupts the way they were prior to
calling OS_CRITICAL_ENTER().
典型的宏代码如列表 4-1 所示
The typical
code for the macros is shown in Listing 4-1.

4-1-1 测量关中断时间
uC/CPU 提供了测量关中断时间的功能。通过设置 CPU_CFG.H 中的CPU_CFG_TIME_MEAS_INT_DIS_EN 为 1 启用该功能。
μC/CPU provides facilities to measure the amount of time interrupts are disabled. This is done by setting the configuration constant CPU_CFG_TIME_MEAS_INT_DIS_EN to 1 in CPU_CFG.H.
每次关中断前开始测量,开中断后结束测量。测量功能保存了 2 个方面的测量值,任务总的关中断时间,每个任务最近一次关中断的时间。因此,用户可以根据任务的关中断时间对其加以优化。
The
measurement is started each time interrupts are disabled and ends when
interrupts are re-enabled. The measurement keeps track of two values: a global
interrupt disable time, and an interrupt disable time for each task. Therefore,
it is possible to know how long a task disables interrupts, enabling the user
to better optimize their code.
每个任务的关中断时间在上文保存的时候被保存于 OS_TCB(详见 OS_CPU.C 中的 OSTaskSwHook()和第八章"上下文切换")。{我将上下文切换分成两个部分:上文保存、下文载入}
The
per-task interrupt disable time is saved in the task’s OS_TCB during a context
switch (see OSTaskSwHook() in OS_CPU_C.C and described in Chapter 8, “Context
Switching” on page 147).
时间戳的控制单元位于 CPU_TS 中。时间戳的速率决定于 CPU 的速率。例如,如果 CPU 速率为 1MHz,时间戳的速率为 1MHz。那么CPU_TS 的分辨率为 1 微秒。
The
unit of measure for the measured time is in CPU_TS (timestamp) units. It is
necessary to find out the resolution of the timer used to measure these
timestamps. For example, if the timer used for the timestamp is incremented at
1 MHz then the resolution of CPU_TS is 1 microsecond.
显然,测出的关中断时间还包括了测量时消耗的额外时间。然而,减掉测量时所耗时间就是实际上的关中断时间。
Measuring
the interrupt disable time obviously adds measurement artifacts and thus
increases the amount of time the interrupts are disabled. However, as far as
the measurement is concerned, measurement overhead is accounted for and the
measured value represents the actual interrupt disable time as if the
measurement was not present.
关中断时间跟处理器的指令、速度、内存访问速度有很大的关系。在这种情况下,硬件设计者应介绍内存的访问速度,它是影响整个系统性能的。
Interrupt
disable time is obviously greatly affected by the speed at which the processor
accesses instructions and thus, the memory access speed. In this case, the
hardware designer might have introduced wait states to memory accesses, which
affects overall performance of the system. This may show up as unusually long
interrupt disable times.
当设置 OS_CFG_ISR_POST_DEFERRED_EN 为 1 时,在进入临界段前 uC/OS-III 会锁住调度器,退出临界段后开启调度器。
When
setting OS_CFG_ISR_POST_DEFERRED_EN to 1, μC/OS-III locks the scheduler before
entering a critical section and unlocks the scheduler when leaving the critical
section.
OS_CRITICAL_ENTER()递增 OSSchedLockNestingCtr,给调度器加锁。这是一个决定调度器是否被开启的变量。如果它不为 0 则调度器被锁。{称它为调度器锁嵌套值,表示调度器被加了几把锁}
OS_CRITICAL_ENTER()
simply increments OSSchedLockNestingCtr to lock the scheduler. This is the
variable the scheduler uses to determine whether or not the scheduler is
locked.
It
is locked when the value is non-zero.
OS_CRITICAL_EXIT()将OSSchedLockNestingCtr 递减,给调度器解锁。{调度器锁嵌套值被减为 0 时,就会调用调度器}
OS_CRITICAL_EXIT()
decrements OSSchedLockNestingCtr and when the value reaches zero, invokes the
scheduler.
OS_CRITICAL_EXIT_NO_SCHED() 也 递 减 OSSchedLockNestingCtr 的值,不同的是当其值减为 0 时,不调用调度器。
OS_CRITICAL_EXIT_NO_SCHED()
also decrements OSSchedLockNestingCtr, but does not invoke the scheduler when
the value reaches zero.

4-2-1 测量锁调度器时间
uC/OS-III 提供了测量锁调度器时间的功能,通过设置 OS_CFG.H 中的 OS_CFG_SCHED_LOCK_TIME_MEAS_EN 为 1 开启。
μC/OS-III
provides facilities to measure the amount of time the scheduler is locked. This
is done by setting the configuration constant OS_CFG_SCHED_LOCK_TIME_MEAS_EN to
1 in OS_CFG.H.
加锁调度器前测量开始,解锁调度器后测量结束。测得的两种值为:总的锁调度器时间,每个任务的锁调度器时间。因此,用户可以知道每个任务的锁调度器时间,并根据此优化代码。
The
measurement is started each time the scheduler is locked and ends when the
scheduler is unlocked. The measurement keeps track of two values: a global
scheduler lock time, and a per-task scheduler lock time. It is therefore
possible to know how long each task locks the scheduler allowing the user to
better optimize code.
每个任务的锁调度器时间在上下文切换时保持于任务的 OS_TCB(详见 OS_CPU_C.C 中的 OSTaskSwHook()和第八章“上下文切换”)。
The
per-task scheduler lock time is saved in the task’s OS_TCB during a context
switch (see OSTaskSwHook() in OS_CPU_C.C and described in Chapter 8, “Context
Switching” on page 147).
测得的锁调度器时间还包括测量时额外增加的时间。减掉额外的时间就是锁调度器时间的准确值。
Measuring
the scheduler lock time adds measurement artifacts and thus increases the
amount of time the scheduler is actually locked. However, measurement overhead
is accounted for and the measured value represents the actual scheduler lock
time as if the measurement was not present.
表 4-1 展示了 uC/OS-III 某些情况下的较长临界段。了解这些内容会帮助用户管理 uC/OS-III 的临界段。
Table
4-1 shows several μC/OS-III features that have potentially longer critical
sections. Knowledge of these will help the user decide whether to direct
μC/OS-III to use one critical section over another.
表 4-1 关中断或锁调度器
|
功能 |
原因 |
|
多个任务具有相同优先级 Multiple tasks at the same priority |
这 uC/OS-III 的一个重要功能,多个任务具有相关优先级会产生一个长临界段。然而,当较少任务具有相同优先级时,中断延时会很小,此时可以用关中断方法保护临界段。 Although this is an important
feature of μC/OS-III, multiple tasks at the same priority create longer
critical sections. However, if there are only a few tasks at the same
priority, interrupt latency would
be relatively small. If
multiple tasks are not created at the same priority, use the interrupt
disable method. |
|
事件标志组详见章节 14 “同步” |
如果多个任务等待不同的事件,遍历这些任务需要很多处理时间,就会产生长临界段。如果较少的任务在等待事件,临界段会很短,那么可以使用关中断的方法保护临界段。 If multiple tasks
are waiting on different events, going through all of the tasks waiting for
events requires a fair amount of processing time, which means longer critical
sections. If only a few tasks (approximately one to five) are waiting on an
event flag group, the critical section would be short enough to use the
interrupt disable method. |
|
任务同时等待多 个对象 详见章节 16 |
任务同时等待多个对象是 uC/OS-III 提供的很复杂的功能,它需要很长的临界段。如果这样,推荐使用关调度器的方法。 Pending on multiple
objects is probably the most complex feature provided by μC/OS-III, requiring
interrupts to be disabled for fairly long periods of time should the
interrupt disable method be selected. If pending on multiple objects, it is
highly recommended that the user select the scheduler-lock method. If the
application does not use this feature, the interrupt disable method is an
alternative. |
|
广播消息 |
uC/OS-III 在处理广播消息时锁调度器保护临界段。 μC/OS-III disables interrupts while
processing a post to multiple
tasks in a broadcast. When not using the broadcast option, you can use the
interrupt disable method. |
uC/OS-III 中会用到临界段,用关中断 (OS_CFG.H 中设置
OS_CFG_ISR_POST_DEFERRED_EN 为 0)或者锁调度器(设置OS_CFG_ISR_POST_DEFERRED_EN 为 1)实现保护临界段的功能。
μC/OS-III
needs to access critical sections of code, which it protects by either
disabling interrupts (OS_CFG_ISR_POST_DEFERRED_EN set to 0 in OS_CFG.H), or locking
the scheduler (OS_CFG_ISR_POST_DEFERRED_EN set to 1 in OS_CFG.H).
用户程序不能使用这些代码:The application code must not use:
OS_CRITICAL_ENTER() OS_CRITICAL_EXIT() OS_CRITICAL_EXIT_NO_SCHED()
如 果 设 置 了 CPU_CFG.H 中 的CPU_CFG_TIME_MEAS_INT_DIS_EN 为 1 时。uC/CPU 将会任务总的关中断时间和每个任务的关中断时间。
When
setting CPU_CFG_TIME_MEAS_INT_DIS_EN in CPU_CFG.H, μC/CPU measures the maximum
interrupt disable time. There are two values available, one for the global
maximum and one for each task.
如 果 设 置 了 OS_CFG.H 中 的OS_CFG_SCHED_LOCK_TIME_MEAS_EN 为 1 时。uC/OS-III 会测量任务总的锁调度器时间和每个任务锁调度器时间。
When
setting OS_CFG_SCHED_LOCK_TIME_MEAS_EN to 1 in OS_CFG.H, μC/OS-III will measure
the maximum scheduler lock time.
实时应用中一般将工作拆分为多个任务,每个任务都需要是可靠的。使用 uC/OS-III 可以轻松地解决这个问题。任务(也叫做线程)是简单的程序。单 CPU 中,在任何时刻只能是一个任务被执行。
The
design process of a real-time application generally involves splitting the work
to be completed into tasks, each responsible for a portion of the problem.
μC/OS-III makes it easy for an application programmer to adopt this paradigm. A
task (also called a thread) is a simple program that thinks it has the Central
Processing Unit (CPU) all to itself. On a single CPU, only one task can execute
at any given time.
uC/OS-III 支持多任务且对任务数量没有限制,任务数仅取决于处理器内存的大小(RAM)。多任务调度是任务间占用 CPU 的过程。
μC/OS-III
supports multitasking and allows the application to have any number of tasks.
The maximum number of task is actually only limited by the amount of memory
(both code and data space) available to the processor. Multitasking is the
process of scheduling and switching the CPU between several tasks (this will be
expanded upon later).
CPU 有根据算法切换任务。多任务调度让人感觉到是有多个 CPU 在运行,并最大化利用 CPU。多任务调用有助于模块化应用,是最重要的功能之一,能帮助程序员管理复杂的实时性应用。
The
CPU switches its attention between several sequential tasks. Multitasking
provides the illusion of having multiple CPUs and, actually maximizes the use
of the CPU. Multitasking also helps in the creation of modular applications.
One of the most important aspects of multitasking is that it allows the
application programmer to manage the complexity inherent in real-time
applications.
它也使程序易于设计和维护。
Application
programs are typically easier to design and maintain when multitasking is used.
任务用于监控输入、更新输出、计算、循环控制、显示、读按钮和键盘、与其它系统交流等。有些应用中可能只包含少数任务,有些应用中也可能包含上百个任务。任务数多并不意味这设计有多好或者有多有效,这依赖于应用的需要。任务的功能也要根据应用设计。一个任务可能只需要工作几微秒,然而有些任务可能就需要工作几十毫秒了。
Tasks
are used for such chores as monitoring inputs, updating outputs, performing computations,
control loops, update one or more displays, reading buttons and keyboards,
communicating with other systems, and more. One application may contain a
handful of tasks while another application may require hundreds. The number of
tasks does not establish how good or effective a design may be, it really
depends on what the application (or product) needs to do. The amount of work a
task performs also depends on the application. One task may have a few
microseconds worth of work to perform while another task may require tens of
milliseconds.
任务看起来像 C 函数。有 2 种类型的任务:运行一次(列表 5-1)和无限循环(列表 5-2)。在大多数嵌入式系统中,任务通常是无限循环的。任务不能像 C 函数那样,它是不能 return 的。
Tasks
look like just any other C function except for a few small differences. There
are two types of tasks: run-to-completion (Listing 5-1) and infinite loop
(Listing 5-2). In most embedded systems, tasks typically take the form of an
infinite loop. Also, no task is allowed to return as other C functions can.
Given that a task is a regular C function, it can declare local variables.
当任务第一次执行时,会传入一个变量"p_arg"。这是一个指向 void 的指针。用于变量的地址、结构体地址、或者函数的地址等。如果需要,可以创建多个相同的任务,使用相同的代码(相同任务体),而产生有不同的运行结果。
When
a μC/OS-III task begins executing, it is passed an argument, p_arg. This
argument is a pointer to a void. The pointer is a universal vehicle used to
pass your task the address of a variable, a structure, or even the address of a
function, if necessary. With this pointer, it is possible to create many
identical tasks, that all use the same code (or task body), but, with different
run-time characteristics.
例如,4 个异步串行端口有各自的任务。然而,任务代码实际上是独立的,只是复制了 4 次而已,这些任务可以接收通过指针一个包含串口数据的结构体(如波特率、IO 端口地址,中断向量号等)。
For
example, one may have four asynchronous serial ports that are each managed by
their own task. However, the task code is actually identical. Instead of
copying the code four times, create the code for a “generic” task that receives
a pointer to a data structure, which contains the serial port’s parameters
(baud rate, I/O port addresses, interrupt vector number, etc.) as an argument. In
other words, instantiate the same task code four times and pass it different
data for each serial port that each instance will manage
只运行一次的任务结束时必须通过调用 OSTaskDel()删除自己。这样可以使系统中的任务数减少。在任务体中,任务可以调用 uC/OS-III 提供的大部分函数帮助完成其所需要完成的功能。
A
run-to-completion task must delete itself by calling OSTaskDel(). The task
starts, performs its function, and terminates. There would typically not be too
many such tasks in the embedded system because of the overhead associated with
“creating” and “deleting” tasks at run-time. In the task body, one can call
most of μC/OS-III’s functions to help perform the desired operation of the
task.

在 uC/OS-III 中,不管是 C 语言函数还是调用汇编语言函数。该函数都用可能被多个任务同时调用。一个可重入的函数不含有静态变量以及全局变量除非被保护(uC/OS-III 提供了这种保护机构)。
With
μC/OS-III, call either C or assembly language functions from a task. In fact,
it is possible to call the same C function from different tasks as long as the
functions are reentrant. A reentrant function is a function that does not use
static or otherwise global variables unless they are protected (μC/OS-III
provides mechanisms for this) from multiple access.
在嵌入式系统中通常使用无限循环的任务,因为应用中有很多需要重复的工作(例如,读取输入值、更新显示、控制操作等)。这是不同于 C 函数的一个方面(C 函数可以用 while(1)或 for(;;)实现相同的功能)。当你读懂 uC/OS-III 后,你会对这个观念更加清晰。\
If
shared C functions only use local variables, they are generally reentrant
(assuming that the compiler generates reentrant code). An example of a
non-reentrant function is the famous strtok() provided by most C compilers as
part of the standard library. This function is used to parse an ASCII string
for “tokens.” The first time you call this function, you specify the ASCII string
to parse and what constitute tokens. As soon as the function finds the first
token, it returns. The function “remembers” where it was last so when called
again, it can extract additional tokens, which is clearly non-reentrant.
The
use of an infinite loop is more common in embedded systems because of the
repetitive work needed in such systems (reading inputs, updating displays,
performing control operations, etc.). This is one aspect that makes a task
different than a regular C function. Note that one could use a “while (1)” or
“for (;;)” to implement the infinite loop, since both behave the same. The one
used is simply a matter of personal preference. At Micrium, we like to use
“while (DEF_ON)”. The infinite loop must call a μC/OS-III service (i.e., function)
that will cause the task to wait for an event to occur. It is important that
each task wait for an event to occur, otherwise the task would be a true
infinite loop and there would be no easy way for other tasks to execute. This
concept will become clear as more is understood regarding μC/OS-III.

任 务 可 以 延 时 一 段 时 间 ( 调 用 OSTimeDly() 或 者 OSTimeDlyHSM())。例如,应用中需要每 100ms 扫描键盘一次。在这种情况下,延时 100ms 然后检测键盘上是否有键被按下,然后执行相应的操作。
The
event the task is waiting for may simply be the passage of time (when
OSTimeDly() or OSTimeDlyHMSM() is called). For example, a design may need to
scan a keyboard every 100 milliseconds. In this case, simply delay the task for
100 milliseconds then see if a key was pressed on the keyboard and, possibly
perform some action based on which key was pressed. Typically, however, a
keyboard scanning task should just buffer an “identifier” unique to the key
pressed and use another task to decide what to do with the key(s) pressed.
同样的,任务等待的事件可以是以太网控制器发送的包。在这种情况下,任务会调用 OS???Pend()函数(以这种形式定义的函数)。一旦包接收完成,任务根据包内容进行下一步操作。
Similarly,
the event the task is waiting for could be the arrival of a packet from an
Ethernet controller. In this case, the task would call one of the OS???Pend()
calls (pend is synonymous with wait). The task will have nothing to do until
the packet is received. Once the packet is received, the task processes the
contents of the packet, and possibly moves the packet along a network stack.
值得注意的是,任务在等待事件时,它不会占用 CPU。
It’s
important to note that when a task waits for an event, it does not consume CPU
time.
uC/OS-III 需要通过调用函数 OSTaskCreate() 创建任务。OSTaskCreate()函数的原型如下所示。
Tasks
must be created in order for μC/OS-III to know about tasks. Create a task by
simply calling OSTaskCreate(). The function prototype for OSTaskCreate() is
shown below:

关于 OSTaskCreate()完整的描述以及它的参数详见于附录 A。另外,创建一个任务时必须为其分配一个 TCB,一个堆栈,一个优先级和其它一些参数。如图 5-1
A complete
description of OSTaskCreate() and its arguments is provided in Appendix A,
“μC/OS-III API Reference Manual” on page 375. However, it is important to
understand that a task needs to be assigned a Task Control Block (i.e., TCB), a
stack, a priority and a few other parameters which are initialized by
OSTaskCreate(), as shown in Figure 5-1.

F5-1 ( 1 )调用 OSTaskCreate() 时,参数为:堆栈的基地址(p_stk_base),堆栈的增长限制,堆栈大小等。
F5-1(1) When calling OSTaskCreate(),
one passes the base address of the stack (p_stk_base) that will be used by the
task, the watermark limit for stack growth (stk_limit) which is expressed in
number of CPU_STK entries before the stack is empty, and the size of that stack
(stk_size), also in number of CPU_STK elements.
F5-1 ( 2 ) 若 OSTaskCreate() 中 的 第 12 个 参 数 设 置 为OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,uC/OS-III将会初始化堆栈内容为全 0。
F5-1(2) When specifying
OS_OPT_TASK_STK_CHK + OS_OPT_TASK_STK_CLR in the opt argument of OSTaskCreate(),
μC/OS-III initializes the task’s stack with all zeros.
F5-1(3)uC/OS-III 将复制 CPU 寄存器内容并有序地存入任务堆栈的顶部。这使得上下文切换易于实现。
F5-1(3) μC/OS-III then initializes
the top of the task’s stack with a copy of the CPU registers in the same
stacking order as if they were all saved at the beginning of an ISR. This makes
it easy to perform context switches as we will see when discussing the context
switching process. For illustration purposes, the assumption is that the stack
grows from high memory to low memory, but the same concept applies for CPUs
that use the stack in the reverse order.
F5-1(4)堆栈指针 SP 存于任务的 TCB 中。
F5-1(4) The new value of the stack
pointer (SP) is saved in the TCB. Note that this is also called the
top-of-stack.
F5-1(5)TCB 中其它的字段会被赋值:任务优先级、任务名、任务状态、内部消息队列、内部信号量等。
F5-1(5) The remaining fields of the
TCB are initialized: task priority, task name, task state, internal message
queue, internal semaphore, and many others.
接下来,会调用一些定义在 OS_CPU_C.C 中的函数如
OSTaskCreateHook(),TCB 中有指针指向这个函数,用于扩展应用。例如,可以打印最新创建的 TCB 内容到某终端(利于调试)。
Next,
a call is made to a function that is defined in the CPU port,
OSTaskCreateHook() (see OS_CPU_C.C). OSTaskCreateHook() is passed the pointer
to the new TCB and this function allows you (or the port designer) to extend
the functionality of OSTaskCreate(). For example, one could printout the
contents of the fields of the newly created TCB onto a terminal for debugging
purposes.
然后该任务被放到就绪列表(详见第六章"就绪列表")。uC/OS-III 调用调度器,并切换到优先级最高的任务。
The
task is then placed in the ready-list (see Chapter 6, “The Ready List” on page
123) and finally, if multitasking has started, μC/OS-III will invoke the
scheduler to see if the created task is now the highest priority task and, if
so, will context switch to this new task.
任务可以调用 uC/OS-III 提供的函数。特别的,一个任务可以创建其它任务(调用 OSTaskCreate())、停止或者恢复其它任务(调用OSTaskSuspned()和 OSTaskResume())、提交信号量到其它任务、发送消息到其它任务、提供共享资源等。换句话说,任务不是只被限制于“等待事件”。
The
body of the task can invoke other services provided by μC/OS-III. Specifically,
a task can create another task (i.e., call OSTaskCreate()), suspend and resume
other tasks (i.e., call OSTaskSuspend() and OSTaskResume() respectively), post
signals or messages to other tasks (i.e., call OS??Post()), share resources
with other tasks, and more. In other words, tasks are not limited to only make “wait
for an event” function calls.
图 5-2 展示了任务与其它资源的典型关系。

F5-2(1)任务最重要的部分是它的代码。正如前面提到的,任务的代码看起来像是个 C 函数,除了它实现无限循环的方式。另外,任务不允许有返回值。
F5-2(1) An important aspect of a task
is its code. As previously mentioned, the code looks like any other C function,
except that it is typically implemented as an infinite loop. Also, a task is
not allowed to return.
F5-2(2)每个任务都需要被设定一个优先级。uC/OS-III 的工作是决定哪个任务应该占用 CPU。一般的,uC/OS-III 选择就绪队列中优先级最高的任务运行。
F5-2(2) Each task is assigned a
priority based on its importance in the application. μC/OS-III’s job is to
decide which task will run on the CPU. The general rule is that μC/OS-III will
run the most important ready-to-run task (highest priority).
在 uC/OS-III 中,数值越小优先级越高。
With
μC/OS-III, a low priority number indicates a high priority. In other words, a
task at priority 1 is more important than a task at priority 10.
uC/OS-III 允许用户在编译时配置优先级的范围(详见 OS_CFG.H 文件中的 OS_PRIO_MAX)。另外,uC/OS-III 允许任务具有相同优先级且对该任务数无限制。例如,uC/OS-III 可以被配置为拥有 64 种不同优先级,并可以设置几十个具有相同优先级的任务。(详见 5-1“设置任务优先级”)
μC/OS-III
supports a compile-time user configurable number of different priorities (see
OS_PRIO_MAX in OS_CFG.H). Thus, μC/OS-III allows the user to determine the
number of different priority levels the application is allowed to use. Also,
μC/OS-III supports an unlimited number of tasks at the same priority. For
example, μC/OS-III can be configured to have 64 different priority levels and
one can assign dozens of tasks at each priority level.
F5-2(3)每个任务对 CPU 寄存器都会有自己设置。如果一个任务被运行,那么它就会占用了实际的 CPU。
F5-2(3) A task has its own set of CPU
registers. As far as a task is concerned, the task thinks it has the actual CPU
all to itself.
F5-2(4)因为 uC/OS-III 是一个抢占式内核,每个任务都需要有自己的堆栈空间。堆栈驻留于 RAM 并经常用于保存变量、函数、嵌套的ISR 地址等。
F5-2(4) Because μC/OS-III is a
preemptive kernel, each task must have its own stack area. The stack always
resides in RAM and is used to keep track of local variables, function calls,
and possibly ISR (Interrupt Service Routine) nesting.
堆栈空间可以被分配为静态的(在编译时分配)或者是动态的(运行时分配)。定义静态的堆栈如下所示,定义是在任务之外的。
Stack
space can be allocated either statically (at compile-time) or dynamically (at
run-time). A static stack declaration is shown below. This declaration is made
outside of a function.

或者

注意“???”是堆栈的大小,依赖于任务的需求。通过 C 编译器提供的堆管理功能(malloc())堆栈空间可以被动态分配。如下所示。但是,必须关注存储碎片。多次创建和删除任务后,内存可能有很多存储碎片而不足以再分配给任务了。在嵌入式系统中动态地分配堆栈是被允许的,但是,一旦堆栈被动态分配,它就不能被回收。换句话说,对于有些不需要被删除的任务,动态分配它们的堆栈是一种很好的解决方法。
Note
that “???” indicates that the size of the stack (and thus the array) depends on
the task stack requirements. Stack space may be allocated dynamically by using
the C compiler’s heap management function (i.e., malloc()) as shown below.
However, care must be taken with fragmentation. If creating and deleting tasks,
the process of allocating memory might not be able to provide a stack for the
task(s) because the heap will eventually become fragmented. For this reason,
allocating stack space dynamically in an embedded system is typically allowed
but, once allocated, stacks should not be deallocated. Said another way, it’s
fine to create a task’s stack from the heap as long as you don’t free the stack
space back to the heap.

F5-2(5)任务也可以访问全局变量。然而,因为 uC/OS-III 是抢占式内核,所有必须注意多个任务同时访问全局变量的情况。幸运的是, uC/OS-III 提供了一些功能管理这些共享资源如(信号量、mutex 等)。
F5-2(5) A task can also have access
to global variables. However, because μC/OS-III is a preemptive kernel care
must be taken with code when accessing such variables as they may be shared
between multiple tasks. Fortunately, μC/OS-III provides mechanisms to help with
the management of such shared resources
(semaphores,
mutexes and more).
F5-2(6)任务也可以访问一个或多个 IO 设备(或者外设)。
F5-2(6) A task may also have access
to one or more Input/Output (I/O) devices (also known as peripherals). In fact,
it is common practice to assign tasks to manage I/O devices.
有些时候任务的优先级是显而易见的。例如,嵌入式系统中的重要的应用应该被设置为高优先级,一些显示操作就应该被设置为低优先级。然而,由于实时系统的复杂性,在大多数情况下任务的优先级是不能被事先确定的。多数系统中,不是所有的任务都是重要的,不重要的任务应该被设置为低优先级。
Sometimes
task priorities are both obvious and intuitive. For example, if the most
important aspect of the embedded system is to perform some type of control and
it is known that the control algorithm must be responsive then it is best to
assign the control task(s) a high priority while display and operator interface
tasks are assigned low priority. However, most of the time, assigning task
priorities is not so cut and dry because of the complex nature of real-time
systems. In most systems, not all tasks are considered critical, and
non-critical tasks should obviously be given low priorities.
An interesting
technique called rate monotonic scheduling (RMS) assigns task priorities based
on how often tasks execute. Simply put, tasks with the highest rate of
execution are given the highest priority. However, RMS makes a number of
assumptions, including:
■ All tasks are
periodic (they occur at regular intervals).
■ Tasks do not
synchronize with one another, share resources, or exchange data.
■ The CPU must always
execute the highest priority task that is ready to run. In other words,
preemptive scheduling must be used.
Given a set of n tasks that are
assigned RMS priorities, the basic RMS theorem states that all task hard
real-time deadlines are always met if the following inequality holds true:
![]()
Where Ei corresponds to
the maximum execution time of task i, and Ti corresponds to
the execution period of task i. In other
words, Ei/Ti corresponds to
the fraction of CPU time required to execute task i.
Table 5-1 shows
the value for size n(21/n – 1) based on the
number of tasks. The upper bound for an infinite number of tasks is given by ln(2), or 0.693, which means
that meeting all hard real-time deadlines based on RMS, CPU use of all
time-critical tasks should be less than 70 percent!
Note that one
can still have (and generally does) non time-critical tasks in a system and
thus use close to 100 percent of the CPU’s time. However, using 100 percent of
your CPU’s time is not a desirable goal as it does not allow for code changes
and added features. As a rule of thumb, always design a system to use less than
60 to 70 percent of the CPU.
RMS says that
the highest rate task has the highest priority. In some cases, the highest rate
task might not be the most important task. The application dictates how to
assign priorities.
However, RMS is
an interesting starting point.
|
Number of Tasks |
|
n(21/n-1) |
|
1 |
1.00 |
|
|
2 |
0.828 |
|
|
3 |
0.779 |
|
|
4 |
0.756 |
|
|
5 |
0.743 |
|
|
: |
: |
|
|
: |
: |
|
|
: |
: |
|
|
Infinite |
0.693 |
|
Table 5-1 Allowable CPU usage based on number of
tasks
堆栈的大小取决于该任务的需求。设定堆栈大小时,你就需要考虑:所有可能被堆栈调用的函数及其函数的嵌套层数,相关局部变量的大小,中断服务程序所需要的空间。另外,堆栈还需存入CPU寄存器,如果处理器有浮点数单元FPU寄存器的话还需存入FPU寄存器。嵌入式系统的潜规则,避免写递归函数。
The
size of the stack required by the task is application specific. When sizing the
stack, however, one must account for the nesting of all the functions called by
the task, the number of local variables to be allocated by all functions called
by the task, and the stack requirements for all nested interrupt service
routines. In addition, the stack must be able to store all CPU registers and
possibly Floating-Point Unit (FPU) registers if the processor has a FPU. As a
general rule in embedded systems, avoid writing recursive code.
可以人工地计算出任务需要的堆栈空间大小,逐级嵌套所有可能被调用的函数,添加被调用函数中所有的参数,添加上下文切换时的CPU寄存器空间,添加切换到中断时所需的CPU寄存器空间(假如CPU 没有独立的堆栈用于处理中断),添加处理ISRs所需的堆栈空间。把上述的全部相加,得到的值定义为最小的需求空间。
It
is possible to manually figure out the stack space needed by adding all the
memory required by all function call nesting (1 pointer each function call for
the return address), plus all the memory required by all the arguments passed
in those function calls, plus storage for a full CPU context (depends on the
CPU), plus another full CPU context for each nested ISRs (if the CPU doesn’t
have a separate stack to handle ISRs), plus whatever stack space is needed by
those ISRs. Adding all this up is a tedious chore and the resulting number is a
minimum requirement.
因为我们不可能计算出精确的堆栈空间。通常是再乘以1.5到2.0以确保任务的安全运行。这个计算是假定在任务所有的执行路线都是已知的情况下的,可这是不太可能的。比如说,如果调用printf()或者其它的函数, printf()函数所需要的空间是很难测得或者说就是不可能知道的。在这种情况下,刚开始时给任务设置一个较大的堆栈空间并监测运行时堆栈空间的实际使用量。{一般都是通过这种情况测得堆栈的最大使用量,然后乘1.5左右作为堆栈空间大小}
Most
likely one would not make the stack size that precise in order to account for
“surprises.” The number arrived at should probably be multiplied by some safety
factor, possibly 1.5 to 2.0. This calculation assumes that the exact path of
the code is known at all times, which is not always possible. Specifically,
when calling a function such as printf() or some other library function, it
might be difficult or nearly impossible to even guess just how much stack space
printf() will require. In this case, start with a fairly large stack space and
monitor the stack usage at run-time to see just how much stack space is
actually used after the application runs for a while.
编译器/汇编器提供的的连接映像信息是很有用的。在每个函数中,连接映像提供了函数在最坏情况下堆栈使用量。这个功能能够比较精确地评估每个任务所需的堆栈空间。当然,还需添加一个CPU寄存器空间、再一个CPU寄存器空间(如果CPU没有独立的空间处理 ISR的话)、再加上这些ISR需要的堆栈空间。
There
are really cool and clever compilers/linkers that provide this information in a
link map. For each function, the link map indicates the worst-case stack usage.
This feature clearly enables one to better evaluate stack usage for each task.
It is still necessary to add the stack space for a full CPU context plus,
another full CPU context for each nested ISR (if the CPU does not have a
separate stack to handle ISRs), plus whatever stack space is needed by those
ISRs. Again, allow for a safety net and multiply this value by some factor.
最后,为了保证可靠还有再乘以一个1.5左右的值。在产品的开发和测试阶段,通常在运行时用调试器测量堆栈的使用情况。
Always
monitor stack usage at run-time while developing and testing the product as
stack overflows occur often and can lead to some curious behaviors. In fact,
whenever someone mentions that his or her application behaves “strangely,”
insufficient stack size is the first thing that comes to mind.
1) Using an MMU or MPU
如果处理器有MMU或者MPU,检测堆栈是否溢出是非常简单的。
MMU和MPU是CPU边上的特殊硬件设施,可以检测非法访问,如果任务企图访问未被允许的内存空间的话,就会产生警告。但设置MMU和MPU不是本书所要介绍的。
Stack
overflows are easily detected if the processor has a Memory Management Unit
(MMU) or a Memory Protection Unit (MPU). Basically, MMUs and MPUs are special
hardware devices integrated alongside the CPU that can be configured to detect
when a task attempts to access invalid memory locations, whether code, data, or
stack. Setting up an MMU or MPU is well beyond the scope of this book.
2) Using a CPU with stack
overflow detection
一些处理器中有一些堆栈溢出检测寄存器。当CPU的堆栈指针小于(或大于,取决于堆栈的生长方向)设置于这个寄存器的值时。异常产生时异常处理程序就要确保未允许空间代码的安全(可能会发送警告给用户)。OS_TCB中的.StkLimitPtr就是为这种目的设置的如图5-3.堆栈必须被分配足够大的空间。在大多数情况下,该指针的值可以设置接近于&MYTaskStk[0]。
{假定堆栈是从高地址往低地址生长的}
Some
processors, however, do have simple stack pointer overflow detection registers.
When the CPU’s stack pointer goes below (or above depending on stack growth)
the value set in this register, an exception is generated and the exception
handler ensures that the offending code does not do further damage (possibly
issue a warning about the faulty code). The .StkLimitPtr field in the OS_TCB
(see Task Control Blocks) is provided for this purpose as shown in Figure 5-3.
Note that the position of the stack limit is typically set at a valid location
in the task’s stack with sufficient room left on the stack to handle the
exception itself (assuming the CPU does not have a separate exception stack).
In most cases, the position can be fairly close to &MyTaskStk[0].

在此提醒, StkLimitPtr 的值取决于 OSTaskCreate() 的参数"stk_limit"如下所示:
As a
reminder, the location of the .StkLimitPtr is determined by the “stk_limit”
argument passed to OSTaskCreate(), when the task is created as shown below:

当然,当 uC/OS-III 执行上下文切换的时候,StkLimitPtr 的值会被改变。这是非常棘手的,因为当堆栈溢出检测寄存器的值被改变时需将它首先被设置为 NULL,然后改变 CPU 的堆栈指针,最后把 TCB 的 stkLimitPtr 值存到堆栈检测寄存器中。为何?如果不按照这个过程,那么在堆栈指针或堆栈溢出检测寄存器改变时发生了中断就麻烦了。通过首先改变堆栈溢出检测寄存器指向一个地址(通常设为 NULL)从而让堆栈指针总是有效的。注意,我是在假定堆栈是从高地址往低地址生长的,堆栈生长方向从低往高的道理也是一样的。
Of
course, the value of .StkLimitPtr used by the CPU’s stack overflow detection
hardware needs to be changed whenever μC/OS-III performs a context switch. This
can be tricky because the value of this register may need to be changed so that
it first points to NULL, then change the CPU’s stack pointer, and finally set
the value of the stack checking register to the value saved in the TCB’s
.StkLimitPtr. Why? Because if the sequence is not followed, the exception could
be generated as soon as the stack pointer or the stack overflow detection
register is changed. One can avoid this problem by first changing the stack
overflow detection register to point to a location that ensures the stack
pointer is never invalid (thus the NULL as described above). Note that I
assumed here that the stack grows from high memory to low memory but the
concept works in a similar fashion if the stack grows in the opposite
direction.
3) Software-based stack overflow detection
当 uC/OS-III 从一个任务切换到另一个任务的时候,它会调用一个 hook 函数 OSTaskSwHook(),它允许用户扩展上下文切换时的功能。所以,如果处理器没有硬件支持溢出检测功能,就可以在该 hook 函数中添加代码软件模拟该功能。
Whenever
μC/OS-III switches from one task to another, it calls a “hook” function
(OSTaskSwHook()), which allows the μC/OS-III port programmer to extend the
capabilities of the context switch function. So, if the processor doesn’t have
hardware stack pointer overflow detection, it’s still possible to “simulate”
this feature by adding code in the context switch hook function and, perform
the overflow detection in software.
特别的,在切换到任务 B 前,代码会检测将要被载入 CPU 堆栈指针的值是否超出该任务 B 的 TCB 中StkLimitPtr 限制。因为软件不能在溢出时就迅速地做出反应,所以应该设置 StkLimitPtr 的值尽可能远离&MyTaskStk[0],保证有足够的溢出缓冲。如图 5-4。软件检测不会像硬件检测那样有效,但也可以防止堆栈溢出。
Specifically,
before a task is switched in, the code should ensure that the stack pointer to
load into the CPU does not exceed the “limit” placed in .StkLimitPtr. Because
the software implementation cannot detect the stack overflow “as soon” as the
stack pointer exceeds the value of .StkLimitPtr, it is important to position
the value of .StkLimitPtr in the stack fairly far from &MyTaskStk[0], as
shown in Figure 5-4. A software implementation such as this is not as reliable
as a hardware-based detection mechanism but still prevents a possible stack
overflow. Of course, the .StkLimitPtr field would be set using OSTaskCreate()
as shown above but this time, with a location further away from
&MyTaskStk[0].

4) Counting the amount of free stack space
另一种防止堆栈溢出的方法是分配的空间远大于可能需要用到的。然后,调试器检测在运行时任务所用的最大堆栈空间。这是易于实现的。首先,当任务创建时其堆栈被清零。然后,通过一个低优先级任务,计算该任务整个堆栈中值为 0 的内存大小。
Another
way to check for stack overflows is to allocate more stack space than is
anticipated to be used for the stack, then, monitor and possibly display actual
maximum stack usage at run-time. This is fairly easy to do. First, the task
stack needs to be cleared (i.e., filled with zeros) when the task is created.
Next, a low priority task walks the stack of each task created, from the bottom
(&MyTaskStk[0]) towards the top, counting the number of zero entries.
如果发现都不为 0,那么就需要扩展堆栈的大小。然后,调整堆栈为的相应大小。这是一种非常有效的方法。注意的是,程序需用运行很长的时间以让堆栈达到其需要的最大值。详见图 5-5。uC/OS-III 提供了一个函数OSTaskStkChk() 用于实现这个计算功能。事实上,这个函数被OS_StatTask()用于统计每个任务的堆栈使用情况。
When
the task finds a non-zero value, the process is stopped and the usage of the
stack can be computed (in number of bytes used or as a percentage). Then,
adjust the size of the stacks (by recompiling the code) to allocate a more
reasonable value (either increase or decrease the amount of stack space for
each task). For this to be effective, however, run the application long enough
for the stack to grow to its highest value. This is illustrated in Figure 5-5.
μC/OS-III provides a function that performs this calculation at run-time,
OSTaskStkChk() and in fact, this function is called by OS_StatTask() to compute
stack usage for every task created in the application (to be described later).

uC/OS-III 提供了很多与任务相关的函数。这些函数可以在OS_TASK.C 中找到,它们都以以 OSTask???()形式命名的。
μC/OS-III
provides a number of task-related services to call from the application. These
services are found in OS_TASK.C and they all start with OSTask???(). The type
of service they perform groups task-related services:
|
分组 |
函数 |
|
普通的 General |
OSTaskCteate() OSTaskDel() OSTaskChangePrio() OSTaskRegSet() OSTaskRegGet() OSTaskSuspend() OSTaskResume() OSTaskTimeQuantaSet() |
|
标记任务 Signaling a Task |
OSTaskSemPend() OSTaskSemPost() OSTaskSemPendAbort() |
|
给任务发送消息 Sending Messages to a Task |
OSTaskQPend() OSTaskQPost() OSTaskQPendAbort() OSTaskQFlush() |
uC/OS-III 任务相关函数的介绍详见附录 A。
5-5-1 任务状态
从用户的观点来看,任务可以是有 5 种状态,见图 5-6。展示了任务状态间的转换关系。{休眠状态,就绪状态,运行状态,挂起状态,中断状态}
From
a μC/OS-III user point of view, a task can be in any one of five states as
shown in Figure 5-6. Internally, μC/OS-III does not need to keep track of the
dormant state and the other states are tracked slightly differently. This will
be discussed after a discussion on task states from the user’s point of view.
Figure 5-6 also shows which μC/OS-III functions are used to move from one state
to another. The diagram is actually simplified as state transitions are a bit
more complicated than this.

F5-6(1)处于休眠状态的任务驻留于内存但未被 uC/OS-III 使能。
F5-6(1) The Dormant state corresponds
to a task that resides in memory but has not been made available to μC/OS-III.
通过调用 OSTaskCreate()函数 uC/OS-III 创建任务。任务代码是存在于 ROM 的。但需要用 OSTaskCreate()函数通知 uC/OS-III 关于任务的相关信息。
A
task is made available to μC/OS-III by calling a function to create the task,
OSTaskCreate(). The task code actually resides in code space but μC/OS-III
needs to be informed about it.
如果任务的使命完成了,就要调用 OSTaskDel()删除该任务。OSTaskDel()实际上不是删除任务的代码,只是让任务不再具有使用CPU 的资格而已。
When
it is no longer necessary for μC/OS-III to manage a task, call the task delete
function, OSTaskDel(). OSTaskDel() does not actually delete the code of the
task, it is simply not eligible to access the CPU.
F5-6(2)就绪状态的任务根据优先级有序地排列于就绪列表中。就绪列表中对就绪任务的个数没有限制。
F5-6(2) The Ready state corresponds
to a ready-to-run task, but is not the most important task ready. There can be
any number of tasks ready and μC/OS-III keeps track of all ready tasks in a
ready list (discussed later). This list is sorted by priority.
F5-6(3)正在运行的任务被置为运行状态。在单 CPU 中,任何时刻只能有一个任务被运行。
F5-6(3) The most important
ready-to-run task is placed in the Running state. On a single CPU, only one
task can be running at any given time.
当应用程序调用 OSStart() 或者调用 OSIntExit() 或者调用 OS_TASK_SW()时 uC/OS-III 从就绪队列中选择优先级最高的任务去运行。
The
task selected to run on the CPU is switched in by μC/OS-III from the ready
state when the application code calls OSStart(), or when μC/OS-III calls either
OSIntExit() or OS_TASK_SW().
正如前面所提到的,有些时候任务必须等待某些事件发生,若事件还未发生时,任务就会被设置为挂起状态。
As
previously discussed, tasks must wait for an event to occur. A task waits for
an event by calling one of the functions that brings the task to the pending
state if the event has not occurred.
F5-6(4)挂起状态的任务被放置在挂起列表中以表明任务在等待某些事件的发生。等待的时候,任务是不会占用 CPU 的。事件发生时,该任务会被放到就绪队列中。在这种情况下,正在运行的任务可能会被抢占(被放回就绪列表),并由 uC/OS-III 选择优先级最高的任务去运行。换句话说,如果新的任务优先级最高,那么它就会被立即运行。请注意,调用 OSTaskSuspend()会任务无条件地停止运行。有些时候调用 OSTaskSuspend()不是为了等待某个事件的发生,而是等待另一个任务调用 OSTaskResume()函数恢复这个任务。
F5-6(4) Tasks in the Pending state
are placed in a special list called a pend-list (or wait list) associated with
the event the task is waiting for. When waiting for the event to occur, the
task does not consume CPU time. When the event occurs, the task is placed back
into the ready list and μC/OS-III decides whether the newly readied task is the
most important ready-to-run task. If this is the case, the currently running
task will be preempted (placed back in the ready list) and the newly readied
task is given control of the CPU. In other words, the newly readied task will
run immediately if it is the most important task.
Note
that the OSTaskSuspend() function unconditionally blocks a task and this task
will not actually wait for an event to occur but in fact, waits until another
task calls OSTaskResume() to make the task ready to run.
F5-6(5)若中断发生,中断会挂起正在执行的任务并去处理 ISR。ISR 中可能有某些任务等待的事件。一般来说,中断用来通知任务某些事件的发生,并让在任务级处理实际的响应操作。ISR 程序越短越好,实际响应中断的操作应该被设置在任务级以便能让 uC/OS-III 管理这些操作。ISR 中只允许调用一些提交函数(OSFlagPost(),OSQPost(),OSSemPost(),OSTaskQPost(),OSTaskSemPost()),除了OSMutexPost()。因为 mutex 只允许在任务级被修改。
F5-6(5)
Assuming that CPU interrupts are enabled, an interrupting device will suspend
execution of a task and execute an Interrupt Service Routine (ISR). ISRs are
typically events that tasks wait for. Generally speaking, an ISR should simply
notify a task that an event occurred and let the task process the event. ISRs
should be as short as possible and most of the work of handling the interrupting
devices should be done at the task level where it can be managed by μC/OS-III.
ISRs are only allowed to make “Post” calls (i.e., OSFlagPost(), OSQPost(),
OSSemPost(), OSTaskQPost() and OSTaskSemPost()). The only post call not allowed
to be made from an ISR is OSMutexPost() since mutexes, as will be addressed
later, are assumed to be services that are only accessible at the task level.
正如图所示的那样,一个中断可以被另一个中断所抢占。这叫做中断嵌套,大多数处理器支持中断嵌套。然而,如果管理不当,中断嵌套是很容易引起堆栈溢出的。
As
the state diagram indicates, an interrupt can interrupt another interrupt. This
is called interrupt nesting and most processors allow this. However, interrupt
nesting easily leads to stack overflow if not managed properly.
uC/OS-III 一直追踪着任务的状态如图 5-7。事实上,这些都是以一个变量的形式保存在每个任务的 TCB 中。图小括号中的数值表示着任务的状态,每个任务都可以有 8 种状态。( 详见 OS.H ,OS_TASK_STATE_???)图中不包括休眠态,因为 uC/OS-III 不支持休眠态。在这里,中断以及中断的嵌套会有更深的讲解。
Internally,
μC/OS-III keeps track of task states using the state machine shown in Figure
5-7. The task state is actually maintained in a variable that is part of a data
structure associated with each task, the task’s TCB. The task state diagram was
referenced throughout the design of μC/OS-III when implementing most of
μC/OS-III’s services. The number in parentheses is the state number of the task
and thus, a task can be in any one of eight (8) states (see OS.H,
OS_TASK_STATE_???). Note that the diagram does not keep track of a dormant
task, as a dormant task is not known to μC/OS-III. Also, interrupts and
interrupt nesting is tracked differently as will be explained further in the
text.
这个状态图有助于理解函数与任务状态间的关系。
This
state diagram should be quite useful to understand how to use several functions
and their impact on the state of tasks. In fact, I’d highly recommend that the
reader bookmark the page of the diagram.

F5-7(1)状态 0 表示任务已经就绪。每个任务在被运行之前都必须处于就绪状态。
F5-7(1) State 0 occurs when a task is
ready to run. Every task “wants” to be ready to run as that is the only way it
gets to perform their duties.
F5-7(2)任务可以通过调用 OSTimeDly()或者 OSTimeDlyHMSM() 等待期满。当期满或者延时删除时(通过调用 OSTimeDlyResume()),任务会转为就绪状态。
F5-7(2) A task can decide to wait for
time to expire by calling either OSTimeDly() or OSTimeDlyHMSM(). When the time
expires or the delay is cancelled (by calling OSTimeDlyResume()), the task
returns to the ready state.
F5-7(3)任务可以通过调用挂起函数(OSFlagPend(),OSMutexPend(), OSQPend,OSSemPend,OSTaskQPend(),OSTaskSemPend())等待某事件的发生。当事件发生时、该任务被删除、或者被另一个任务取消等待时,等待停止。
F5-7(3) A task can wait for an event
to occur by calling one of the pend (i.e., wait) functions (OSFlagPend(),
OSMutexPend(), OSQPend(), OSSemPend(), OSTaskQPend(), or OSTaskSemPend()), and
specify to wait forever for the event to occur. The pend terminates when the
event occurs (i.e., a task or an ISR performs a “post”), the awaited object is
deleted or, another task decides to abort the pend.
F5-7(4)如前面所说,任务可以等待事件发生。但任务也可以被设置等待多少时间。如果在这段时间内事件没有发生,任务也会被设为就绪状态,并通知这个任务是等待超时而被挂起的。{挂起函数都有一个关于函数执行结果错误代号,可以查看这个代号知道任务是因何被就绪的}
F5-7(4) A task can wait for an event
to occur as indicated, but specify that it is willing to wait a certain amount
of time for the event to occur. If the event is not posted within that time,
the task is readied, then the task is notified that a timeout occurred. Again,
the pend terminates when the event occurs (i.e., a task or an ISR performs a
“post”), the object awaited is deleted or, another task decides to abort the
pend.
F5-7 (5)任务暂停自己或者被其他任务暂停(通过调用OSTaskSuspend())。暂停中的任务只能通过调用 OSTaskResume()被恢复。
F5-7(5) A task can suspend itself or
another task by calling OSTaskSuspend(). The only way the task is allowed to
resume execution is by calling OSTaskResume(). Suspending a task means that a task
will not be able to run on the CPU until it is resumed by another task.
F5-7(6)一个延时中的任务也可以被其它任务设置为停止。在这种情况下,效果会被叠加。换句话说,延时需被执行、停止状态需被解除。该任务才会被执行。
F5-7(6) A delayed task can also be
suspended by another task. In this case, the effect is additive. In other
words, the delay must complete (or be resumed by OSTimeDlyResume()) and the
suspension must be removed (by another task which would call OSTaskResume()) in
order for the task to be able to run.
F5-7(7)一个挂起状态中的任务也可能被其它任务设置为停止。同样的,效果会被叠加。事件发生且停止状态被移除后,任务才会被执行。
F5-7(7) A task waiting on an event to
occur may be suspended by another task. Again, the effect is additive. The
event must occur and the suspension removed (by another task) in order for the
task to be able to run. Of course, if the object that the task is pending on is
deleted or, the pend is aborted by another task, then one of the above two
condition is removed. The suspension , however, must be explicitly removed.
F5-7(8)任务可以等待事件的发生,但可以给它设定一个期限。同样的,它也可能被设为停止,效果是叠加的。除非移除停止状态并事件发生或等待事件超时,任务才会被执行。
F5-7(8) A task can wait for an event,
but only for a certain amount of time, and the task could also be suspended by
another task. As one might expect, the suspension must be removed by another
task (or the same task that suspended it in the first place), and the event
needs to either occur or timeout while waiting for the event.
5-5-2 任务控制块 TCB
任务控制块是被 uC/OS-III 用于维护任务的一个结构体。每个任务都必须有自己的 TCB。uC/OS-III 在 RAM 中分配 TCB。当调用 uC/OS-III 提供的与任务相关的函数(以 OSTask???()形式命名)时,任务的 TCB 地址需会被提供给该函数。TCB 的结构定义于 OS.H 中,如列表 5-3 所示(在 OS.H 中代码是有注释的)。
A
task control block (TCB) is a data structure used by kernels to maintain
information about a task. Each task requires its own TCB and, for μC/OS-III,
the user assigns the TCB in user memory space (RAM). The address of the task’s
TCB is provided to μC/OS-III when calling task-related services (i.e.,
OSTask???() functions). The task control block data structure is declared in
OS.H as shown in Listing 5-3. Note that the fields are actually commented in
OS.H, and some of the fields are conditionally compiled based on whether or not
certain features are desired. Both are not shown here for clarity.
TCB 中的一些变量可以根据具体应用进行裁剪。用户程序不应该访问这些变量(尤其不能更改它们)。换句话说,TCB 中的变量只能被 uC/OS-III 访问。
Also,
it is important to note that even when the user understands what the different
fields of the OS_TCB do, the application code must never directly access these
(especially change them). In other words, OS_TCB fields must only be accessed
by μC/OS-III and not the code.


.StrPtr
这个变量中包含了指向当前任务堆栈的指针。uC/OS-III 允许每个任务有自己的堆栈且对该堆栈的大小没有限制。 .StkPtr 可能是OS_TCB 数据类型中唯一一个需要被汇编语言所访问的(用于上下文切换部分的代码)。因此,这个变量被放在结构体中的第一个,让汇编语言更容易地访问到它(使它在 TCB 结构体中的偏移量为 0)。
This
field contains a pointer to the current top-of-stack for the task. μC/OS-III allows
each task to have its own stack and each stack can be any size. .StkPtr should
be the only field in the OS_TCB data structure accessed from assembly language
code (for the context-switching code). This field is therefore placed as the
first entry in the structure making access easier from assembly language code
(it will be at offset zero in the data structure).
.ExtPtr
这个变量中定义了指向用户用于扩展 TCB(如果需要)的指针。这个指针也是易于被汇编语言访问的。
This
field contains a pointer to a user-definable pointer to extend the TCB as
needed. This pointer is easily accessible from assembly language.
.StkLimitPtr
这个变量中保存了堆栈增长时的限制地址,它是在调用OSTaskCreate()时传递的参数"stk_limit"。有些处理器有硬件寄存器可以自动地检测并确保堆栈不发生溢出,如果处理器没有这些硬件设施,堆栈检测可以用软件模拟。然而,软件模拟不如硬件可靠。如果这个功能没有被用到,那么在调用 OSTaskCreate() 时可以设置"stk_limit"为 0。详见 5-3。
The
field contains a pointer to a location in the task’s stack to set a watermark
limit for stack growth and is determined from the value of the “stk_limit”
argument passed to OSTaskCreate(). Some processors have special registers that
automatically check the value of the stack pointer at run-time to ensure that
the stack does not overflow. .StkLimitPtr may be used to set this register
during a context switch. Alternatively, if the processor does not have such a
register, this can be “simulated” in software. However, it is not as reliable
as a hardware solution. If this feature is not used then the value of
“stk_limit” can be set to 0 when calling OSTaskCreate(). See also section 5-3
“Detecting Task Stack Overflows” on page 87).

.NextPtr 和.PrevPtr
这些指针用于就绪队列中双向链表。双向链表可以让 TCB 在列表中能更快地被插入或者删除。
These
pointers are used to doubly link OS_TCBs in the ready list. A doubly linked
list allows OS_TCBs to be quickly inserted and removed from the list.
.TickNextPtr 和.TickPrevPtr
这些指针用于挂起队列中双向链表。双向链表可以让 TCB 在列表中能更快地被插入或者删除。
These
pointers are used to doubly link OS_TCBs in the list of tasks waiting for time
to expire or to timeout from pend calls. Again, a doubly linked list allows
OS_TCBs to be quickly inserted and removed from the list.
.TickSpokePtr
这个指针用于表示时基轮转的轮辐。时基轮转会在第九章“中断管理”中详细介绍。
This
pointer is used to know which spoke in the “tick wheel” the task is linked to.
The tick wheel will be described in “Chapter 9, “Interrupt Management” on page
157.”
.NamePtr
这个指针存放了任务的名字。有名字的任务非常有助于调试,因为这样能友好地显示每个任务对应的 TCB 地址。该名字的字符串存于 ROM(如果以常量命名)或者 RAM。
This
pointer allows a name (an ASCII string) to be assigned to each task. Having a
name is useful when debugging, since it is user friendly compared to displaying
the address of the OS_TCB. Storage for the ASCII string is assumed to be in
user space in code memory (ASCII string declared as a const) or in RAM.
.StkBasePtr
{任务堆栈是由高地址向低地址生长}
这个指针指向了任务堆栈的基地址。任务的基地址通常是任务所驻留堆栈区的最低内存地址。任务堆栈的定义:
This
field points to the base address of the task’s stack. The stack base is
typically the lowest address in memory where the stack for the task resides. A
task stack is declared as follows:
CPU_STK MyTaskStk[???];
定义堆栈空间必须用 CPU_STK 数据类型,"???"是要定义的堆栈的大小。它的基地址通常是&MyTaskStk[0];
CPU_STK
is the data type you must use to declare task stacks and ??? is the size of the
stack associated with the task. The base address is always &MyTaskStk[0].
.TaskEntryAddr
这个变量中包含了任务代码的入口地址,正如前面提到的,任务用如下方式申明,
This
field contains the entry address of the task. As previously mentioned, a task is
declared as shown below and this field contains the address of MyTask.
void MyTask(void *p_arg);
.TaskEntryAtg
这个变量是当任务第一次运行时传递给任务的参数。正如上面提到的,这个变量值会传给 p_arg。
This
field contains the value of the argument that is passed to the task when the
task starts. As previously mentioned, a task is declared as shown below and
this field contains the value of p_arg.
.PendDataTblPtr
uC/OS-III 允许任务同时挂起多个信号量和消息队列。这个指针指向了包含这些被挂起对象的表。
μC/OS-III
allows the task to pend on any number of semaphores or message queues simultaneously.
This pointer points to a table containing information about the pended objects.
.PendDataEntries
这个变量与.PendDataTblPtr 一起工作,表示在同一时刻某任务等待的事件数。
This
field works with the .PendDataTblPtr, indicating the number of objects a task
is pending on at the same time.
.TS
这个变量存储了任务所等待事件出现的时间戳,当任务恢复执行时,时间戳会被返回给任务。
This
field is used to store a “time stamp” of when an event that the task was
waiting on occurred. When the task resumes execution, this time stamp is
returned to the caller.
.MsgPtr
当有消息发送给任务时,这个变量保存了该消息的地址。在编译时使能了消息队列服务(OS_CFG.H 中设置 OS_CFG_Q_EN 为 1)或使能了任务内建消息队列(OS_CFG.H 中设置 OS_CFG_TASK_Q_EN 为 1)。这个变量才出现在 TCB 中,不然就会被裁减。
When
a message is sent to a task, this field contains the message received. This
field only exists in a TCB if message queue services (OS_CFG_Q_EN is set to 1
in OS_CFG.H), or task message queue services, are enabled (OS_CFG_TASK_Q_EN is
set to 1 in OS_CFG.H) at compile time.
.MsgSize
当有消息发送给任务时,这个变量保存了消息的大小(以字节为单位)。这个变量仅出现在 TCB 中,如果消息队列服务(在 OS_CFG.H 中设置 OS_CFG_Q_EN 为 1)或者任务队列服务(在 OS_CFG.H 中设置 OS_CFG_TASK_Q_EN 为 1)编译时被使能的话。
When
a message is sent to a task, this field contains the size (in number of bytes)
of the message received. This field only exists in a TCB if message queue
services (OS_CFG_Q_EN is set to 1 in OS_CFG.H), or task message queue services,
(OS_CFG_TASK_Q_EN is set to 1 in OS_CFG.H) are enabled at compile time.
.MsgQ
uC/OS-III 允许任务或 ISR 直接发送消息给任务。实际上也有消息队列内建于每个任务的 TCB 中了(假设编译时在 OS_CFG.H 中设置OS_CFG_TASK_Q_EN 为 1)。.MsgQ 被用于 OSTaskQ???()函数。
μC/OS-III
allows tasks or ISRs to send messages directly to tasks. Because of this, a
message queue is actually built into each TCB. This field only exists in a TCB
if task message queue services are enabled at compile time (OS_CFG_TASK_Q_EN is
set to 1 in OS_CFG.H). .MsgQ is used by the OSTaskQ???() services.
.MsgQPendTime
保存了消息从创建到被接收所需的时间。当 OSTaskQPost()被调用时,读取当前的时间戳并保存在消息中。当 OSTaskQPend()接收到这个消息时,读取当前的时间戳,并与消息被提交时的时间戳相减,差值保存于这个变量中。调试器或者 uC/Probe 可以观察这个变量的值。
This
field contains the amount of time for a message to arrive. When OSTaskQPost()
is called, the current time stamp is read and stored in the message. When
OSTaskQPend() returns, the current time stamp is read again and the difference
between the two times is stored in this variable. A debugger or μC/Probe can be
used to indicate the time taken for a message to arrive by displaying this
field.
当编译时设置 OS_CFG.H 中的 OS_CFG_TASK_PROFILE_EN 为 1 时,这个变量才有效。
This
field is only available if setting OS_CFG_TASK_PROFILE_EN to 1 in OS_CFG.H.
.MsgQPendTimeMax
这个变量中保存了消息到达所用时间的最大值,它是.MsgQPendTime 的峰值。这个值可以被 OSStatReset()复位。
This
field contains the maximum amount of time it takes for a message to arrive. It
is a peak detector of the value of .MsgQPendTime. The peak can be reset by
calling OSStatReset().
在编译时设置 OS_CFG.H 中的OS_CFG_TASK_PROFILE_EN 为 1 时,这个变量才有效。
This
field is only available if setting OS_CFG_TASK_PROFILE_EN to 1 in OS_CFG.H.
.FlagsPend
当任务等待事件标志组,这个变量保存了任务所等待的标志位。当编译时设置了 OS_CFG.H 中的 OS_CFG_FLAG_EN 为 1 时这个变量才有效。
When
a task pends on event flags, this field contains the event flags (i.e., bits)
that the task is pending on. This field only exists in a TCB if event flags
services are enabled at compile time (OS_CFG_FLAG_EN is set to 1 in OS_CFG.H).
.FlagsOpt
当任务等待事件标志组,这个变量保存了任务所等待事件标志组的类型。在编译时设置了 OS_CFG.H 中的 OC_CFG_FLAG_EN 为 1 时这个变量才有效。
When
a task pends on event flags, this field contains the type of pend (pend on any
event flag bit specified in .FlagsPend or all event flag bits specified in
.FlagsPend). This field only exists in a TCB if event flags services are
enabled at compile time (OS_CFG_FLAG_EN is set to 1 in OS_CFG.H).
.FlagsRdy
这个变量保存了已经被提交的事件标志组(任务所等待的),换句话说,它让任务知道是哪个事件标志组让任务就绪的。在编译时设置了 OS_CFG.H 中的 OS_CFG_FLAG_EN 为 1 时这个变量才有效。
This
field contains the event flags that were posted and that the task was waiting
on. In other words, it allows a task to know which event flags made the task
ready to run. This field only exists in a TCB if event flags services are
enabled at compile time (OS_CFG_FLAG_EN is set to 1 in OS_CFG.H).
.RegTbl[]
这个数组中包含了任务的"寄存器",不同于 CPU 寄存器。任务寄存器用于存储任务 ID、软件错误等。需要注意的是这个数组的数据类型是 OS_REG,它是在编译时定义的。所有的任务寄存器都必须用这种数据类型。在编译时设置 OS_CFG.H 中的任务寄存器数组大小宏 OS_CFG_TASK_REG_TBL_SIZE 大于 0 时这个数组才会有效。
This
field contains a table of “registers” that are task-specific. These registers
are different than CPU registers. Task registers allow for the storage of such
task-specific information as task ID, “errno” common in some software
components, and more. Task registers may also store task-related data that
needs to be associated with the task at run time. Note that the data type for
elements of this array is OS_REG, which can be declared at compile time to be
nearly anything. However, all registers must be of this data type. This field
only exists in a TCB if task registers are enabled at compile time
(OS_CFG_TASK_REG_TBL_SIZE is greater than 0 in OS_CFG.H).
.SemCtr
这个变量保存了信号量的计数值。每个任务都有其的内建信号量。 ISR 或其它任务可以通过信号量标记这个任务。因此.SemCtr 用于记录该任务被标记了几次。.SemCtr 在 OSTaskSem???()中被用到。
This
field contains a semaphore counter associated with the task. Each task has its
own semaphore built-in. An ISR or another task can signal a task using this
semaphore. .SemCtr is therefore used to keep track of how many times the task
is signaled. .SemCtr is used by OSTaskSem???() services.
.SemPendTime
中保存着信号量从产生到被接收所用的时间。当 OSTaskSemPost() 被调用,当前的时间戳被读取并保存于信号量中。当 OSTaskSemPend() 函数收到信号量时,当前的时间戳被读取并与之前的时间戳的差值存于这个变量。调试器或者 uC/Probe 可以观察这个变量值。
This
field contains the amount of time taken for the semaphore to be signaled. When
OSTaskSemPost() is called, the current time stamp is read and stored in the
OS_TCB (see .TS). When OSTaskSemPend() returns, the current time stamp is read
again and the difference between the two times is stored in this variable.
当设置OS_CFG.H 中的 OS_CFG_TASK_PROFILE_EN 为 1 时变量才会有效。
This
field can be displayed by a debugger or μC/Probe to indicate how much time it
took for the task to be signaled.
.SemPendTimeMax
保存了信号量从产生到被接收所用时间的最大值。它是.SemPendTime 是峰值。可以调用 OSStatReset()复位这个变量。当设置 OS_CFG.H 中的
This
field contains the maximum amount of time it took for the task to be signaled.
It is a peak detector of the value of .SemPendTime. The peak can be reset by
calling OSStatReset().
OS_CFG_TASK_PROFILE_EN 为 1 时变量才会有效。
This
field is only available if setting OS_CFG_TASK_PROFILE_EN to 1 in OS_CFG.H.
.SuspendCtr
这个变量被 OSTaskSuspend()和 OSTaskResume()使用,用于记录任务被停止的次数。任务停止可以被嵌套,当.SuspendCtr 为 0 时,任务可以被执行。当编译时设置 OS_CFG.H 中的OS_CFG_TASK_SUSPEND_EN 为 1 时这个变量才会有效。
This
field is used by OSTaskSuspend() and OSTaskResume() to keep track of how many
times a task is suspended. Task suspension can be nested. When .SuspendCtr is
0, all suspensions are removed. This field only exists in a TCB if task
suspension is enabled at compile time (OS_CFG_TASK_SUSPEND_EN is set to 1 in
OS_CFG.H).
.StkSize
这个变量中保存了堆栈的大小(以 CPU_STK 为数据类型)。堆栈的定义如下
This
field contains the size (in number of CPU_STK elements) of the stack associated
with the task. Recall that a task stack is declared as follows:
CPU_STK MyTaskStk[???];
.StkSize 就是???的值。
.StkSize is the value of ??? in the above array.
.StkUsed 和.StkFree
在运行时,uC/OS-III 可以计算出堆栈的实际使用量和空余量,这是通过调用 OSTaskStkChk()实现的。堆栈使用量计算是假定堆栈在创建时被初始化的情况下的。换句话说,当 OS_TASK_OPT_STK_CLR 和 OS_TASK_OPT_STK_CHK 被使能时,OSTaskCreate()在任务创建时会先初始化任务堆栈的 RAM。{一般初始化都是将堆栈全部清零的}
μC/OS-III
is able to compute (at run time) the amount of stack space a task actually uses
and how much stack space remains. This is accomplished by a function called
OSTaskStkChk(). Stack usage computation assumes that the task’s stack is
“cleared” when the task is created. In other words, when calling
OSTaskCreate(), it is expected that the following options be specified:
OS_TASK_OPT_STK_CLR and OS_TASK_OPT_STK_CHK. OSTaskCreate() will then clear all
the RAM used for the task’s stack.
uC/OS-III 提供了一个叫做 OS_StatTask()的任务,它可以检测每一个任务的堆栈使用情况。OS_StatTask()通常被设置于低优先级,所以它可以在 CPU 空闲时运行。OS_StatTask()将检测结果保存于每个任务的 TCB 中。保存了任务堆栈的最大使用量(以字节形式的计算值)和空余量。当设置 OS_CFG.H 中的OS_CFG_STAT_TASK_STK_CHK_EN 为 1 时这两个变量才有效。
μC/OS-III
provides an internal task called OS_StatTask() that checks the stack of each of
the tasks at run-time. OS_StatTask() typically runs at a low priority so that
it does not interfere with the application code. OS_StatTask() saves the value
computed for each task in the TCB of each task in these fields, which represents
the maximum number of stack bytes used and the amount of stack space still
unused by the task. These fields only exist in a TCB if the statistic task is
enabled at compile time (OS_CFG_STAT_TASK_STK_CHK_EN is set to 1 in OS_CFG.H).
.Opt
当任务创建时传递给 OSTaskCreate()的参数。它定义任务的附加功能。
This field saves the “options” passed to OSTaskCreate()
when the task is created (see OS_TASK_OPT_??? in OS.H). Note that task options
are additive.
.TickCtrPrev
当 OSTimeDly()选择 OS_OPT_TIME_PERIODIC 形式时,该变量为 OSTickCtr 的初值。
This field stores the previous value of OSTickCtr when
OSTimeDly() is called with the OS_OPT_TIME_PERIODIC option.
.TickCtrMatch
当任务被延时一段时间,或者因等待事件而设置时限。任务就会被放到挂起队列中。在这个队列中,任务等待自己的 TickCtrMatch 值与时基计数值(OSTickCtr)相匹配。匹配发生时,任务就会被移出挂起队列。
When
a task is waiting for time to expire, or pending on an object with a timeout,
the task is placed in a special list of tasks waiting for time to expire. When
in this list, the task waits for .TickCtrMatch to match the value of the “tick
counter” (OSTickCtr). When a match occurs, the task is removed from that list.
.TickRemain
这个变量中保存了任务到时的剩余时间值,它在 OS_TickTask() 中被计算。调试时这个变量是很有用的。
This
field is computed at run time by OS_TickTask() to compute the amount of time
(expressed in “ticks”) left before a delay or timeout expires. This field is
useful for debuggers or run-time monitors for display purposes.
.TimeQuanta 和.TimeQuantaCtr
这两个变量用于时间切片,当多个就绪任务有相同的优先级时,.TimeQuanta 决定了时间片长度(多少个时基)。.TimeQuantaCtr 中保存了当前时间片的剩余长度。在任务切换开始时将.TimeQuanta 的值载入.TimeQuantaCtr。
These
fields are used for time slicing. When multiple tasks are ready to run at the
same priority, .TimeQuanta determines how much time (in ticks) the task will
execute until it is preempted by μC/OS-III so that the next task at the same
priority gets a chance to execute. .TimeQuantaCtr keeps track of the remaining
number of ticks for this to happen and is loaded with .TimeQuanta at the
beginning of the task’s time slice.
.CPUUsage
保存了 CPU 的使用率(0 到 100%),它是被 OS_StatTask()计算出来的。编译时设置了 OS_CFG.H 中的 OS_CFG_TASK_PROFILE_EN 为 1 时该变量有效。
This
field is computed by OS_StatTask() if OS_CFG_TASK_PROFILE_EN is set to 1 in
OS_CFG.H. .CPUUsage contains the CPU usage of a task in percent (0 to 100%).
.CtxSwCtr
保存了该任务被执行的次数。调试时可以查看这个变量的值。编译时设置 OS_CFG_TASK_PROFILE_EN 为 1 时这个变量有效。
This
field keeps track of how often the task has executed (not how long it has
executed).This field is generally used by debuggers or run-time monitors to see
if a task is executing (the value of this field would be non-zero and would be
incrementing). The field is enabled at compile time when OS_CFG_TASK_PROFILE_EN
is set to 1.
.CyclesDelta
这上下文切换时被计算,它保存了当前时间戳与 CyclesStart 的差值。调试时可以通过它知道该任务的执行时间。当设置OS_CFG_TASK_PROFILE_EN 为 1 时该变量有效。
.CyclesDelta
is computed during a context switch and contains the value of the current time
stamp (obtained by calling OS_TS_GET()) minus the value of .CyclesStart. This
field is generally used by debuggers or a run-time monitor to see how long a
task takes to execute. The field is enabled at compile time when
OS_CFG_TASK_PROFILE_EN is set to 1.
.CyclesStart
这个变量用于测量任务的执行时间,.CyclesStart 在上下文切换时被更新。它保存了任务切换时的时间戳(通过调用 OS_TS_GET()获得)。当编译时设置 OS_CFG_TASK_PROFILE_EN 为 1 时变量有效。
This
field is used to measure the execution time of a task. .CyclesStart is updated
when μC/OS-III performs a context switch. .CyclesStart contains the value of
the current time stamp (it calls OS_TS_GET()) when a task switch occurs. This
field is generally used by debuggers or a run-time monitor to see how long a
task takes to execute. The field is enabled at compile time when
OS_CFG_TASK_PROFILE_EN is set to 1.
.CyclesTotal
这个变量是 CyclesDelta 的累加,所以它包含了该任务被执行的总时间。这个变量被定义为 64 位防止溢出。使用一个 64 位的变量可以确保 1GHz 的 CPU 大约运行 600 年才溢出。当然,它要求编译器支持 64 位的数据类型。
This
field accumulates the value of .CyclesDelta, so it contains the total execution
time of a task. This is typically a 64-bit value because of the accumulation of
cycles over time. Using a 64-bit value ensures that we can accumulate CPU
cycles for almost 600 years even if the CPU is running at 1 GHz! Of course,
it’s assumed that the compiler supports 64-bit data types.
.IntDisTimeMax
这个变量中保存了该任务关中断的最大时间。使用这个的前提是 uC/CPU 支持关中断时间的测量。当设置了 OS_CFG.H 中的OS_CFG_TASK_PROFILE_EN 为 1 且定义了 CPU_CFG.H 中的CPU_CFG_TIME_MEAS_INT_DIS_EN 时变量才有效。{在 CPU_CFG.H 中我只找到 CPU_CFG_INT_DIS_MEAS_EN。估计是后来改名字了但手册上更新过来}
This
field keeps track of the maximum interrupt disable time of the task. The field
is updated only if μC/CPU supports interrupt disable time measurements. This field
is available only if setting OS_CFG_TASK_PROFILE_EN to 1 in OS_CFG.H and
μC/CPU’s CPU_CFG_TIME_MEAS_INT_DIS_EN is defined in DCPU_CFG.H.
.SchedLockTimeMax
保存了该任务锁调度器的最大时间。当设置了 OS_CFG.H 中的OS_CFG_TASK_PROFILE_EN 为 1 且设置OS_CFG_SCHED_LOCK_TIME_MEAS_EN 为 1 时变量才有效。
The
field keeps track of the maximum scheduler lock time of the task.
This
field is available only if you set OS_CFG_TASK_PROFILE_EN to 1 and
OS_CFG_SCHED_LOCK_TIME_MEAS_EN is set to 1 in OS_CFG.H.
.PendOn
该变量的值取决于任务因何被挂起(详见 OS.H 中的OS_TASK_PEND_ON_???)。
This
field indicates upon what the task is pending and contains
OS_TASK_PEND_ON_???(see OS.H).
.PendStatus
这个变量保存了任务被挂起后的状态,(详见 OS.H 中的OS_STATUS_PEND_??? )。{PendOn 和 TaskState 都好理解,这个包括 4 种状态,正常挂起、挂起取消、挂起对象被删除、挂起超时。详见 OS.H}
This
field indicates the outcome of a pend and contains OS_STATUS_PEND_??? (see
OS.H).
.TaskState
这个变量保存了任务当前的状态,包括 8 种状态(详见 OS.H 中的 OS_TASK_STATE_???)
This
field indicates the current state of a task and contains one of the eight (8)
task states that a task can be in, see OS_TASK_STATE_??? (see OS.H).
.Prio
它保存了任务的优先级, 该值介于 0 到 OS_CFG_PRIO_MAX-1之间。事实上,空闲任务需独占优先级 OS_CFG_PRIO_MAX-1。
This
field contains the current priority of a task. .Prio is a value between 0 and
OS_CFG_PRIO_MAX-1. In fact, the idle task is the only task at priority
OS_CFG_PRIO_MAX-1.
.DbgNextPtr
该变量是一个指针,在双向 TCB 列表中,它指向下一个 TCB。通过 OSTaskCreate()函数 uC/OS-III 将 TCB 放入该列表中。当设置了
OS_CFG.H 中的 OS_CFG_DBG_EN 为 1 时该变量才有效。
This
field contains a pointer to the next OS_TCB in a doubly linked list of OS_TCBs.
OS_TCBs are placed in this list by OSTaskCreate(). This field is only present
if OS_CFG_DBG_EN is set to 1 in OS_CFG.H. the current priority of a task.
.DbgPrevPtr
该变量是一个指针,在双向 TCB 列表中,它指向上一个 TCB。当设置了OS_CFG.H 中的 OS_CFG_DBG_EN 为 1 时该变量才有效。
This
field contains a pointer to the previous OS_TCB in a doubly linked list of
OS_TCBs.
OS_TCBs
are placed in this list by OSTaskCreate(). This field is only present if
OS_CFG_DBG_EN is set to 1 in OS_CFG.H.
.DbgNamePtr
该变量是一个指针,当任务在等待信号量、事件标志组、mutex、消息队列时,它指向目标对象的名字。调试时很有用,当设置了OS_CFG.H 中的OS_CFG_DBG_EN 为 1 时变量才有效。
This
field contains a pointer to the name of the object that the task is pending on
when the task is pending on an event flag group, a semaphore, a mutual
exclusion semaphore or a message queue. This information is quite useful during
debugging and thus, this field is only present if OS_CFG_DBG_EN is set to 1 in
OS_CFG.H.
在 uC/OS-III 初始化的时候,它会创建至少 2 个内部的任务(OS_IdleTask()和 OS_TickTask()),3 个可选择的任务(OS_StatTask(),OS_TmrTaks(),OS_IntQTask())。这些可选择的任务在编译时由OS_CFG.H 中的配置决定。
During
initialization, μC/OS-III creates a minimum of two (2) internal tasks
(OS_IdleTask() and OS_TickTask()) and, three (3) optional tasks (OS_StatTask(),
OS_TmrTask() and OS_IntQTask()). The optional tasks are created based on the
value of compile-time #defines found in OS_CFG.H.
5-6-1 空闲任务 OS_IdleTask()
OS_IdleTask()是 uC/OS-III 最先创建的任务。它的优先级通常是OS_CFG_PRIO_MAX-1。事实上,为了安全,它应该独占这个优先级。在其他任务创建的时候,OSTaskCreate()会确保他们不会跟空闲任务有相同的优先级。当 CPU 中没有其它就绪任务运行时,空闲会被运行。空闲任务的重要部分代码如下(详见 OS_CORE.C 中的全部代码)
OS_IdleTask()
is the very first task created by μC/OS-III and always exists in a
μC/OS-IIIbased application. The priority of the idle task is always set to
OS_CFG_PRIO_MAX-1. In fact, OS_IdleTask() is the only task that is ever allowed
to be at this priority and, as a safeguard, when other tasks are created,
OSTaskCreate() ensures that there are no other tasks created at the same
priority as the idle task. The idle task runs whenever there are no other tasks
that are ready to run. The important portions of the code for the idle task are
shown below (refer to OS_CORE.C for the complete code).

L5-4(1)空闲任务是一个无限循环的不会等待任何事件的任务。这是因为,在大部分的处理器中,当没有事情可做时,处理器依然会执行指令。当 uC/OS-III 中没有其它更高的就绪任务待运行时,uC/OS-III 就会把 CPU 分配给空闲任务。
L5-4(1) The idle task is a “true”
infinite loop that never calls functions to “wait for an event”. This is
because, on most processors, when there is “nothing to do,” the processor still
executes instructions. When μC/OS-III determines that there is no other
higher-priority task to run, μC/OS-III “parks” the CPU in the idle task.
Instead of having an empty “for loop” doing nothing, this “idle” time is used
to do something useful.
L5-4(2)空闲任务运行时,两个计数变量会递增。
L5-4(2) Two counters are incremented
whenever the idle task runs.
OSIdleTaskCtr 是用 32 位无符号整数定义的,在 uC/OS-III 初始化的时候它的值被复位。它用于表示空闲任务的活动情况。换句话说,如果用调试器查看该变量,就会看到介于 0x00000000 和 0xffffffff 之间的数。OSIdleTaskCtr 的增长速度取决于 CPU 的空闲情况。CPU 越空闲,该值增长越快。
OSIdleTaskCtr
is typically defined as a 32-bit unsigned integer (see OS.H). OSIdleTaskCtr is
reset once when μC/OS-III is initialized. OSIdleTaskCtr is used to indicate
“activity” in the idle task. In other words, if one monitors and displays
OSIdleTaskCtr, one should expect to see a value between 0x00000000 and
0xFFFFFFFF. The rate at which OSIdleTaskCtr increments depend on how busy the
CPU is at running the application code. The faster the increment, the less work
the CPU has to do in application tasks.
OSStatTaskCtr 也是用 32 位无符号整数定义的,提供给测量任务测量 CPU 的利用率。
OSStatTaskCtr
is also typically defined as a 32-bit unsigned integer (see OS.H) and is used
by the statistic task (described later) to get a sense of CPU utilization at
run time.
L5-4(3)空闲任务的每次循环,都会调用 OSIdleTaskHook()函数,这个函数提供给用户扩展应用。在这个函数中不要编写会让空闲任务被挂起的代码,对于 uC/OS-III 移植者来说这是一个常识。
L5-4(3)
Every time through the loop, OS_IdleTask() calls OSIdleTaskHook(), which is a
function that is declared in the μC/OS-III port for the processor used.
OSIdleTaskHook() allows the implementer of the μC/OS-III port to perform
additional processing during idle time. It is very important for this code to
not make calls that would cause the idle task to “wait for an event”. This is
generally not a problem as most programmers developing μC/OS-III ports know to
follow this simple rule.
OSIdleTaskHook()可以编写使 CPU 处于低功耗的代码。然而,这样的话就意味着 OSStatTaskCtr 不能再用于测量 CPU 的使用率了。
OSIdleTaskHook()
may be used to place the CPU in low-power mode for battery-powered applications
or to simply not waste energy as shown in the pseudo-code below. However, doing
this means that OSStatTaskCtr cannot be used to measure CPU utilization
(described later).

通常情况下,当中断发生时处理器退出低功耗模式。ISR 中可能会设置某些寄存器恢复 CPU 速度为全速或其想要的速度。ISR 可以唤醒了一个高优先级任务(每个任务的优先级都比空闲任务的优先级高),然后 ISR 不会返回到空闲任务,而是切换到这个高优先级任务。如果这个任务完成操作或者挂起,uC/OS-III 就会切换到空闲任务并进入 OSIdleTaskHook()并进入低功耗模式。然后,进入 OS_IdleTask() 并循环。
Typically,
most processors exit low-power mode when an interrupt occurs. Depending on the
processor, however, the Interrupt Service Routine (ISR) may have to write to
“special” registers to return the CPU to its full or desired speed. If the ISR
wakes up a high-priority task (every task is higher in priority than the idle
task) then the ISR will not immediately return to the interrupted idle task,
but instead switch to the higher-priority task. When the higher-priority task
completes its work and waits for its event to occur, μC/OS-III causes a context
switch to return to OSIdleTaskHook() just “after” the instruction that caused
the CPU to enter low-power mode. In turn, OSIdleTaskHook() returns
toOS_IdleTask() and causes another iteration through the “for loop.”
5-6-2 时基任务 OS-TickTask()
几乎所有的实时系统都需要有一个能提供周期性时间的时间源,叫做时基周期或系统周期。uC/OS-III 的时基周期处理程序封装在OS_TICK.C 文件中。
OS_TickTask()任务被 uC/OS-III 创建,其优先级是用户可配置的。
Nearly
every RTOS requires a periodic time source called a Clock Tick or System Tick
to keep track of time delays and timeouts. μC/OS-III’s clock tick handling is
encapsulated in the file OS_TICK.C.
(通过配置 OS_CFG_APP.H 中的 OS_CFG_TICK_TASK_PRIO)。通常设置其优先级较高。事实上,它的优先级应该设置比重要任务的优先级稍低。
OS_TickTask()
is a task created by μC/OS-III and its priority is configurable by the user through
μC/OS-III’s configuration file OS_CFG_APP.H (see OS_CFG_TICK_TASK_PRIO).
Typically OS_TickTask() is set to a relatively high priority. In fact, the
priority of this task is set slightly lower than the most important tasks.
OS_TickTask()用于追踪等待期满的任务、挂起超时的任务。OS_TickTask()是一个周期性任务,它等待来自于 ISR 的信号量(详见章节 9"中断管理")。
OS_TickTask() is used by μC/OS-III to keep track of tasks
waiting for time to expire or, for tasks that are pending on kernel objects
with a timeout. OS_TickTask() is a periodic task and it waits for signals from
the tick ISR (described in Chapter 9, “Interrupt Management” on page 157) as
shown in Figure 5-8.

F5-8(1)使用硬件定时器并被设置为以 10 到 1000Hz 之间的频率产生中断,同时要设置 OS_CFG_APP.H 中 OS_CFG_TICK_RATE 为硬件定时器的中断频率。
F5-8(1) A hardware timer is generally used and configured
to generate an interrupt at a rate between 10 and 1000 Hz (see OS_CFG_TICK_RATE
in OS_CFG_APP.H). This timer is generally called the Tick Timer. The actual
rate to use depends on such factors as: processor speed, desired time
resolution, and amount of allowable overhead to handle the tick timer, etc.
时基中断并不是一定要用 CPU 产生,事实上,它可以从其他的具有较精确的周期性时间源中获得,比如电源线(50-60Hz)等。
The tick interrupt does not have to be generated by a timer
and, in fact, it can come from other regular time sources such as the
power-line frequency (50 or 60 Hz), which are known to be fairly accurate over
long periods of time.
F5-8(2)假定 CPU 中断使能,CPU 接收时基中断,并抢占当前任务,程序指针 SP 指向时基中断服务程序。时基中断服务程序必须调用OSTimeTick()(详见 OS_TIME.C),然后时基 ISR 清除该中断标志位。然而,有些应用中就需要先清中断标志再调用 OSTimeTick()。如下所示
F5-8(2) Assuming CPU interrupts are enabled, the CPU
accepts the tick interrupt, preempts the current task, and vectors to the tick
ISR. The tick ISR must call OSTimeTick() (see OS_TIME.C), which accomplishes
most of the work needed by μC/OS-III. The tick ISR then clears the timer
interrupt (and possibly reloads the timer for the next interrupt). However,
some timers may need to be taken care of prior to calling OSTimeTick() instead
of after as shown below.

或

OSTimeTick()首先调用 OSTimeTickHook(),它提供给用户扩展。(当时定时断产生时用户需要做的工作)
OSTimeTick() calls OSTimeTickHook() at the very beginning
of
OSTimeTick() to give the opportunity to the μC/OS-III port
developer to react as soon as possible upon servicing the tick interrupt.
F5-8(3)OSTimeTick()用于标记时基任务并就绪时基任务。定时器中断后基任务可能不被立即执行,因为中断程序打断的可能是一个比时基任务更高优先级的任务,完成时基 ISR 后,uC/OS-III 会返回被打断的这个任务。
F5-8(3) OSTimeTick()
calls a service provided by μC/OS-III to signal the tick task and make that
task ready to run. The tick task executes as soon as it becomes the most
important task. The reason the tick task might not run immediately is that the
tick interrupt could have interrupted a task higher in priority than the tick
task and, upon completion of the tick ISR, μC/OS-III will resume the
interrupted task.
F5-8(4)当时基任务执行时,它会遍历队列中所有等待期满的任务、等待事件超时的任务。按照这个观点,这个会被叫做时基列表。时基任务会就绪时基列表中的那些期满、超时的任务。
F5-8(4) When
the tick task executes, it goes through a list of all tasks that are waiting
for time to expire or are waiting on a kernel object with a timeout. From this
point forward, this will be called the tick list. The tick task will make ready
to run all of the tasks in the tick list for which time or timeout has expired.
The process is explained below.
uC/OS-III 的时基队列中有时也有可能存放了上百个任务(如果应用需要很多任务)。时基队列通过一种方法检测这些任务是否期满,是否可以被设置为就绪,该方法不会占用太多 CPU 时间。如图 5-9
μC/OS-III may need to place literally hundreds of tasks (if
an application has that many tasks) in the tick list. The tick list is
implemented in such a way that it does not take much CPU time to determine if
time has expired for those tasks placed in the tick list and, possibly makes
those tasks ready to run. The tick list is implemented as shown in Figure 5-9.

F5-9(1)时基列表中包含了一个表(OSCfg_TickWheel[])和一个计数器(OSTickCtr)。
F5-9(1) The
tick list consists of a table (OSCfg_TickWheel[]) and a counter (OSTickCtr).
F5-9(2)这个表多达 OS_CFG_TICK_WHEEL_SIZE 个记录,它是在编译时配置的(详见 OS_CFG_APP.H)。记录数取决于处理器的 RAM 及应用中最大的任务数。推荐值为所有任务/4,不推荐使用偶数,避免设置 OS_CFG_TICK_WHEEL_SIZE 为 10(用 11 代替)。事实上,质数是一个很好的选择。
F5-9(2) The
table contains up to OS_CFG_TICK_WHEEL_SIZE entries, which is a compile time
configuration value (see OS_CFG_APP.H). The number of entries depends on the
amount of memory (RAM) available to the processor and the maximum number of
tasks in the application. A good starting point for OS_CFG_TICK_WHEEL_SIZE may
be: #Tasks / 4. It is recommended not to make OS_CFG_TICK_WHEEL_SIZE an even
multiple of the tick rate. If the tick rate is 1000 Hz and one has 50 tasks in
the application, avoid setting OS_CFG_TICK_WHEEL_SIZE to 10 or 20 (use 11 or 23
instead). Actually, prime numbers are good choices. Although it is not really
possible to plan at compile time what will happen at run time, ideally, the
number of tasks waiting in each entry of the table will be distributed
uniformly.
F5-9(3)表中的每个记录包含 3 个变量:.NbrEntriesMax,NbrEntries 和 FirstPtr。
F5-9(3) Each entry in the table contains three
fields:.NbrEntriesMax, .NbrEntries and .FirstPtr.
NbrEntries 表明链接到该记录的任务序号。
.NbrEntries indicates the number of tasks linked to this
table entry.
NbrEntriesMax 追踪到表中优先级最高的记录。这个值在调用OSStatReset()时被复位。
.NbrEntriesMax keeps track of the highest number of entries in the table. This value is reset when the application code calls OSStatReset().
.FirstPtr 包含了一个指向双向任务列表的指针。
.FirstPtr contains a pointer to a doubly linked list of
tasks (through the tasks OS_TCB) belonging to the list, at that table position.
当时基中断每产生一次,OSTickCtr 的值就会被 OS_TickTask()递增一次。
The counter is incremented by OS_TickTask() each time the
task is signaled by the tick ISR.
当调用 OSTimeDly???()或者 OS???Pend()时(所允许的超时时间大于 0),任务会被自动的插人时基列表。
Tasks are automatically inserted in the tick list when the
application programmer calls a OSTimeDly??? () function, or when an OS???Pend()
call is made with a non-zero timeout value.
例子 5-1
用一个例子来阐述时基列表中插入任务的过程,我们先假设时基列表为空,OS_CFG_TICK_WHEEL_SIZE 被设置为 12,当前的 OSTickCtr 值为 10。如图 5-10,当 OSTimeDly()被调用时,对应任务被放入时基列表。(假定 OSTimeDly()函数如下被调用)
Using an example to illustrate the process of inserting a
task in the tick list, let’s assume that the tick list is completely empty,
OS_CFG_TICK_WHEEL_SIZE is configured to 12, and the current value of OSTickCtr
is 10 as shown in Figure 5-10. A task is placed in the tick list when
OSTimeDly() is called and assume OSTimeDly() is called as follows:

上述表示该任务需要被延时 1 个时基。因为 OSTickCtr 的值为 10,该任务将会处于等待状态直到 OSTickCtr 为 11。
Referring to the μC/OS-III reference manual in Appendix A,
notice that this action indicates to μC/OS-III to delay the current task for 1
tick. Since OSTickCtr has a value of 10, the task will be put to sleep until OSTickCtr
reaches 11 or at the very next clock tick interrupt.
任务将会被插入到OSCfg_TickWheel[]表中用如下的等式:
Tasks are inserted in the OSCfg_TickWheel[] table using the
following equation:
MatchValue = OSTickCtr + dly
Index into OSCfg_TickWheel[] =MatchValue%OS_CFG_TICK_WHEEL_SIZE
"dly"是传递给 OSTimeDly()的第一个参数。在这个例子中为 1。
Where “dly” is the value passed in the first argument of
OSTimeDly() or, 1 in this example.
根据例子即:
We therefore obtain the following:
MatchValue = 11
Index into OSCfg_TickWheel[] = 11
因为这表是循环的(以模操作),表是一个时基轮,每一条记录都是时基轮的一部分。
Because of the “circular” nature of the table (a modulo
operation using the size of the table), the table is referred to as a tick
wheel and each entry is a spoke in the wheel.
OSCfg_TickWheel[]的索引 11 指向任务的 TCB。第一个任务的TCB 的TickNextPtr 地址被插入到表中 OSCfg_TickWheel[11].FirstPtr。表中索引 11 中的任务计数值递增(OSCfg_TickWheel[11].NbrEntries 会被设置为 1)。需要注意的是, TCB 中的.TickSpokePtr 会被链接到&OSCfg_TickWheel[11]。
"MatchValue"会被存于 TCB.TickCtrMatch 中。因为这是插入到记录11 的第一个任务,.TickNextPtr 和.TickPrevPtr 会被设置为指向 NULL。
The OS_TCB of the task being delayed is entered at index 11
in OSCfg_TickWheel[] (i.e., spoke 11 using the wheel analogy). The OS_TCB of
the task is inserted in the first entry of the list (i.e., pointed to by
OSCfg_TickWheel[11].FirstPtr), and the number of entries at spoke 11 is
incremented (i.e.,OSCfg_TickWheel[11].NbrEntries will be 1). Notice that the
OS_TCB also links back to &OSCfg_TickWheel[11] and the “MatchValue” are
placed in the OS_TCB field .TickCtrMatch. Since this is the first task inserted
in the tick list at spoke 11, the .TickNextPtr and .TickPrevPtr both point to
NULL.

OSTimeDly()还要照顾其它一些细节。特别的,当任务不再具备运行的资格时,该任务会被从 uC/OS-III 的就绪列表中移除。当 uC/OS-III需要运行下一个最重要的就绪任务时,调度器会被调用。在下一次时基到来之前,其它任务调用了 OSTimeDly()如下
OSTimeDly() takes care of a few other details.
Specifically, the task is removed from μC/OS-III’s ready list (described in
Chapter 6, “The Ready List” on page 123) since the task is no longer eligible
to run (because it is waiting for time to expire). Also, the scheduler is
called because μC/OS-III will need to run the next most important ready-to-run
task.

uC/OS-III 会计算其匹配值(match value)和记录号
|
MatchValue |
= 10+13 |
|
Index into 或 |
OSCfg_TickWheel[] = (10+13)%12 |
|
MatchValue |
= 23 |
|
Index into |
OSCfg_TickWheel[] = 11 |
第二个任务会被插入到同一表中如图 5-11 所示。任务共享这条记录并重新排序,确保等待时间最少的任务位于记录 11 队列的头部。
The “second task” will be inserted at the same table entry
as shown in Figure 5-11. Tasks sharing the same spoke are sorted in ascending
order such that the task with the least amount of time remaining is placed at
the head of the list.

{表中OS_TCB是任务TCB的一部分,其实是TCB中以OS_TCB类型定义的变量}
当时基任务被执行时(详见 OS_TICK.C 中的 OS_TickTask()和
OS_TickListUpdate())。它会递增 OSTickCtr 的值并确定时基表中的哪些记录需要被执行。然后,如果有任务在这个记录中(记录的.FirstPtr 不为NULL),记录中的每个 OS_TCB 会被检查其.TickCtrMatch 的值是否与 OSTickCtr 的值相匹配。如果匹配,将该任务的 OS_TCB 从记录列表中移除,如果该任务只是在等待期满,它将会被放入就绪列表中。如果任务是等待一个对象,且等待超时了,它不仅会被移出时基列表,也会被移出该对象的挂起队列。遍历完记录中的队列,直到TickCtrMatch 的值与 OSTickCtr 的值不再匹配为止。
When the tick task executes (see OS_TickTask() and also
OS_TickListUpdate() in OS_TICK.C), it starts incrementing OSTickCtr and
determines which table entry (i.e., which spoke) needs to be processed. Then,
if there are tasks in the list at this entry (i.e., .FirstPtr is not NULL),
each OS_TCB is examined to determine whether the .TickCtrMatch value “matches”
OSTickCtr and, if so, we remove the OS_TCB from the list. If the task is only
waiting for time to expire, it will be placed in the ready list (described
later). If the task is pending on an object, not only will the task be removed from
the tick list, but it will also be removed from the list of tasks waiting on
that object. The search through the list terminates as soon as OSTickCtr does
not match the task’s .TickCtrMatch value; since there is no point in looking
any further in the list.
需要注意的是,OS_TickTask()中与时基队列操作相关的大部分工作都需要以临界段的形式工作的。然而,因为记录被分类,临界段可以被缩为很短。
Note that OS_TickTask() does most of its work in a critical
section when the tick list is updated. However, because the list is sorted, the
critical section has a chance to be fairly short.
5-6-3 统计任务 OS_StatTask()
这个任务能够统计总的 CPU 使用率(0 到 100%),每个任务的 CPU 使用率(0 到 100%),每个任务的堆栈使用量。
μC/OS-III contains an internal task that provides such
run-time statistics as overall CPU utilization (0 to 100%), per-task CPU
utilization (0-100%), and per-task stack usage.
统计任务在 uC/OS-III 中是可选的,当设置 OS_CFG.H 中的OS_CFG_STAT_TASK_EN 为 1 时,统计任务的代码会被包含在程序中。
The statistic task is optional in a μC/OS-III application
and its presence is controlled by a compile-time configuration constant
OS_CFG_STAT_TASK_EN defined in OS_CFG.H.Specifically, the code is included in
the build when OS_CFG_STAT_TASK_EN is set to 1.
当然,统计任务的优先级和它的任务堆栈大小在 OS_CFG_APP.H 中配置。
Also, the priority of this task and the location and size
of the statistic task’s stack is configurable via OS_CFG_APP.H
(OS_CFG_STAT_TASK_PRIO).
最好在 main()中只创建的一个任务,通常叫做 AppTaskStat(),当使能了统计任务时,就必须在 AppTaskStat 任务中首先调用OSStatTaskCPUUsageInit()。如列表 5-5 所示。在调用 OSStart()之前,用户的启动代码只能创建一个任务,而是由这个任务创建其它任务。
If the application uses the statistic task, it should call
OSStatTaskCPUUsageInit() from the first, and only the application task created
in the main() function as shown in Listing 5-5. The startup code should create
only one task before calling OSStart(). The single task created is, of course,
allowed to create other tasks, but only after calling OSStatTaskCPUUsageInit().

L5-5(1)CPU 进入 main()函数中。
L5-5(1) The
C compiler should start up the CPU and bring it to main() as is typical in most
C applications.
L5-5(2)main()函数调用 OSInit()初始化 uC/OS-III。假定在OS_CFG_APP.H 中设置 OS_CFG_STAT_TASK_EN 为 1,使能统计任务。通过 uC/OS-III 返回的错误代号检测系统初始化是否成功。(详见 OS.H 中的错误代号 OS_ERR_???)。
L5-5(2) main()
calls OSInit() to initialize μC/OS-III. It is assumed that the statistics task
is enabled by setting OS_CFG_STAT_TASK_EN to 1 in OS_CFG_APP.H. Always examine
μC/OS-III’s returned error code to make sure the call was done properly. Refer
to OS.H for a list of possible errors, OS_ERR_???.
L5-5(3)创建一个叫做 AppTaskStart()的任务。创建这个任务的时候,给它一个相当高的优先级(不要用优先级 0,因为这是为 uC/OS-III 保留的)。
uC/OS-III 允许用户在调用 OSStart()之前创建任意个任务。然而,当用到统计任务统计 CPU 的使用率时,调用 OSStart()之前只能创建一个任务。
L5-5(3) As
the comment indicates, creates a single task called AppTaskStart() in the
example (its name is left to the creator’s discretion). When creating this
task, give it a fairly high priority (do not use priority 0 since it’s reserved
for μC/OS-III).
Normally, μC/OS-III allows the user to create as many tasks
as are necessary prior to calling OSStart(). However, when the statistic task
is used to compute overall CPU utilization, it is necessary to create only one
task.
L5-5(4)调用 OSStart(),让 uC/OS-III 开始运行优先级最高的任务,根据例子,这个任务是 AppTaskStart()。在这个时候,已经有五个任务被创建了:OS_IdleTask(),OS_TickTask(),OS_StatTask(),OS_TaskTmr(),AppTaskStart()。
L5-5(4) Call
OSStart() to let μC/OS-III start the highest-priority task which, should be
AppTaskStart(). At this point, there should be either four or five tasks
created (the timer task is optional): μC/OS-III creates up to four tasks
(OS_IdleTask(), OS_TickTask(), OS_StatTask() and OS_TaskTmr()), and now
AppTaskStart().
L5-5(5)这个任务应该先设置和开启时基中断,初始化用于时基时钟的硬件定时器,设置其中断的速率。(编译时设置 OS_CFG_APP.H 中的OS_CFG_STAT_TASK_RATE)。另外,Micrium 提供的例子工程中包含了基本的板级支持包 BSP。BSP 初始化了 CPU 很多方面的也包括 uC/OS-III 需要的周期时间源。如果需要,用户可以在开启任务中调用 BSP_Init()初始化 BSP 服务。
L5-5(5) The
start task should then configure and enable tick interrupts. This most likely
requires that the user initialize the hardware timer used for the clock tick
and have it interrupt at the rate specified by OS_CFG_STAT_TASK_RATE (see
OS_CFG_APP.H). Additionally, Micriμm provides sample projects that include a
basic board-support package (BSP). The BSP initializes many aspects of the CPU
as well as the periodic time source required by μC/OS-III. If available, the
user may utilize BSP services by calling BSP_Init() from the startup task.
After this point, no further time source initialization is required by the
user.
L5-5(6)调用 OSStatTaskCPUUsageInit()。当没有其它应用任务运行时,经过 1/OS_CFG_STAT_TASK_RATE 秒后 OSStatTaskCtr 的计数值就是OSStatTaskCtr 的最大值,它意味着 CPU 的空闲时的工作速率。例如,如果系统中不包含应用任务,OSStatTaskCtr 从 0 开始计数到10,000,000 用了
1/OS_CFG_STAT_TASK_RATE 秒。添加了应用任务后, OSStatTaskCtr 每 1/OS_CFG_STAT_TASK_RATE 秒检测一次。得到的值不会达到 10,000,000。CPU 的利用率如下计算:
L5-5(6) Call
OSStatTaskCPUUsageInit(). This function determines the maximum value that
OSStatTaskCtr (see OS_IdleTask()) can count up to for 1/OS_CFG_STAT_TASK_RATE
second when there are no other tasks running in the system (apart for the other
μC/OS-III tasks). For example, if the system does not contain an application
task and OSStatTaskCtr counts from 0 to 10,000,000 for 1/OS_CFG_STAT_TASK_RATE
second, when adding tasks, and the
test is redone every 1/OS_CFG_STAT_TASK_RATE second, the
OSStatTaskCtr will not reach 10,000,000 and actual CPU utilization is
determined as follows:
![]()
如果重新测得的 OSStatTaskCtr 值为 7,500,000。那么 CPU 的利用率为 25%。
For example, if when redoing the test, OSStatTaskCtr
reaches 7,500,000 the CPU is busy 25% of its time running application tasks:
![]()
L5-5(7)然后在 AppTaskStart()创建其它的应用任务。
AppTaskStart() can then create other application tasks as needed.
正如先前描述的,uC/OS-III 将任务的 CPU 使用率存于任务的 TCB。
As previously described, μC/OS-III stores run-time
statistics for a task in each task’s OS_TCB.
OS_StatTask()也可以计算每个任务堆栈的使用量(通过调用OSTaskStkChk())。并将计算结果存于每个任务 OS_TCB的StkFree和 StkUsed 中。
OS_StatTask() also computes stack usage of all created tasks
by calling OSTaskStkChk() and stores the return values of this function (free
and used stack space) in the .StkFree and .StkUsed field of the task’s OS_TCB,
respectively.
5-6-4 定时器任务 OS_TmrTask()
{这节所说的定时器都是软件定时器} uC/OS-III 为用户提供了定时器任务,相应代码在 OS_TMR.C 中。
μC/OS-III provides timer services to the application
programmer. Code to handle timers is found in OS_TMR.C.
定时器任务是可选的,通过将 OS_CFG.H 中的 OS_CFG_TMR_EN设置为 1 使能。当设置为 1 时,它的代码才会被添加到最终代码中。
The timer task is optional in a μC/OS-III application and
its presence is controlled by the compile-time configuration constant
OS_CFG_TMR_EN defined in OS_CFG.H. Specifically, the code is included in the
build when OS_CFG_TMR_EN is set to 1.
当定时器任务递减计数变量到 0 时,任务中就会调用回调函数。回调函数是一个函数,它被用户定义。因此,回调函数可以用来开启或关闭 LED、电机、或者其他的一些操作。用户可以创建任意个定时器(只限制于处理器的 RAM)。定时器管理在 12 章详细介绍。
Timers are countdown counters that perform an action when
the counter reaches zero. The action is provided by the user through a callback
function. A callback function is a function that the user declares and that
will be called when the timer expires. The callback can thus be used to turn on
or off a light, a motor, or perform whatever action needed. It is important to
note that the callback function is called from the context of the timer task.
The application programmer may create an unlimited number of timers (limited
only by the amount of available RAM). Timer management is fully described in
Chapter 12, “Timer Management” on page 193 and the timer services available to
the application programmer are described in Appendix A, “μC/OS-III API
Reference Manual” on page 375.
OS_TmrTask()是一个被 uC/OS-III 创建的任务(假定设置OS_CFG.H 中的 OS_CFG_TMR_EN 为 1),它通过 OS_CFG_APP.H 中的 OS_CFG_TMR_TASK_PRIO 设置优先级。一般情况下,它的优先级被设为中等。
OS_TmrTask() is a task created by μC/OS-III (this assumes
setting OS_CFG_TMR_EN to 1 in OS_CFG.H) and its priority is configurable by the
user through μC/OS-III’s configuration file OS_CFG_APP.H (see
OS_CFG_TMR_TASK_PRIO). OS_TmrTask() is typically set to a medium priority.
OS_TmrTask()是周期函数。中断源产生时基。然而,定时器通常需要较低的率(典型为 10Hz),可以通过软件将时基分频。换句话说,如果时基速率为 1000Hz,但是想要的定时器速率为 10Hz,定时器任务会每 100 个时基被标记一次。如图 5-12
OS_TmrTask() is a periodic task using the same interrupt
source that was used to generate clock ticks. However, timers are generally
updated at a slower rate (i.e., typically 10 Hz) and the timer tick rate is
divided down in software. In other words, if the tick rate is 1000 Hz and the
desired timer rate is 10 Hz, the timer task will be signaled every 100th tick
interrupt as shown in Figure 5-12.

5-6-5 中断处理任务 OS_IntQTask()
当设置 OS_CFG.H 中的 OS_CFG_ISR_POST_DEFERRED_EN 为1 时,uC/OS-III 就会创建一个任务,它的作用是尽快完成 ISR 中对 post 函数的调用,将信号量、消息等对象先存在媒介中,退出中断后,由中断处理任务完成将这些对象提交给任务。
When setting the compile-time configuration constant
OS_CFG_ISR_POST_DEFERRED_EN in OS_CFG.H to 1, μC/OS-III creates a task (called
OS_IntQTask()) responsible for “deferring” the action of OS service post calls
from ISRs.
正如第 4 章“临界段”所介绍的,uC/OS-III 通过开启/关闭中断、锁/开锁调度器管理临界段。如果选择后一种方法,ISR 调用的 post 函数不允许直接访问就绪列表、挂起队列等。
As described in Chapter 4, “Critical Sections” on page 69,
μC/OS-III manages critical sections either by disabling/enabling interrupts, or
by locking/unlocking the scheduler. If selecting the latter method (i.e.,
setting OS_CFG_ISR_POST_DEFERRED_EN to 1), μC/OS-III “post” functions called
from interrupts are not allowed to manipulate such internal data structures as
the ready list, pend lists, and others.
当 ISR 调用 uC/OS-III 提供的 post 函数时,ISR 要提交的数据会被复制,该数据的目的地会被放到一个特殊的队列—"holding"队列。当所有的嵌套中断结束时,uC/OS-III 切换到中断处理任务,它会将放置在"holding"队列中的信息重新提交到适当的任务。这个额外步骤的目的是减小关中断时间。
When an ISR calls one of the “post” functions provided by
μC/OS-III, a copy of the data posted and the desired destination is placed in a
special “holding” queue. When all nested ISRs complete, μC/OS-III context
switches to the ISR handler task (OS_IntQTask()), which “re-posts” the
information placed in the holding queue to the appropriate task(s). This extra
step is performed to reduce the amount of interrupt disable time that would
otherwise be necessary to remove tasks from wait lists, insert them in the
ready list, and perform other time-consuming operations.

OS_IntQTak()被 uC/OS-III 创建,它的优先级通常被设为 0(最高优先级)。如果 OS_CFG_ISR_POST_DEFERRED_EN 被设置为 1,使能了这个任务,其它的任务的优先级就不能设置为 0。
OS_IntQTask() is created by μC/OS-III and always runs at
priority 0 (i.e., the highest priority). If OS_CFG_ISR_POST_DEFERRED_EN is set
to 1, no other task will be allowed to use priority 0.
任务是一段简单的程序。在单 CPU 系统中,任何时间只能有一个任务被 CPU 运行。uC/OS-III 支持多任务并对任务数没有限制(仅限制于 CPU 的存储空间,包括代码空间和数据空间)。
A task is a simple program that thinks it has the CPU all
to itself. On a single CPU, only one task executes at any given time. μC/OS-III
supports multitasking and allows the application to have any number of tasks.
The maximum number of tasks is actually only limited by the amount of memory
(both code and data space) available to the processor.
A task can be implemented as a run-to-completion task in
which the task deletes itself when it is finished or more typically as an
infinite loop, waiting for events to occur and processing those events.
A task needs to be created. When creating a task, it is
necessary to specify the address of an OS_TCB to be used by the task, the
priority of the task, and an area in RAM for the task’s stack. A task can also
perform computations (CPU bound task), or manage one or more I/O (Input/Output)
devices.
uC/OS-III 创建五个内部任务:空闲任务,时基任务,中断处理任务,统计任务,定时器任务。空闲任务、时基任务是必须的,统计任务、定时器任务、中断处理任务是可选择的。
μC/OS-III creates up to five internal tasks: the idle task,
tick task, ISR handler task, statistics task, and timer task. The idle and tick
tasks are always created while statistics and timer tasks are optional.
准备运行的任务被放置于就绪列表中。就绪列表包括 2 个部分:位映像组包含了优先级信息,一个表包含了所有指向就绪任务的指针。
Tasks that are ready to execute are placed in the Ready
List. The ready list consists of two parts: a bitmap containing the priority
levels that are ready and a table containing pointers to all the tasks ready.
图 6-1 到 6-3 显示了优先级的位映像组。它的宽度取决于CPU_DATA 的数据类型(见 CPU.H),它可以是 8 位、16 位、32 位。根据处理器相应地设定。
Figures 5-1 to 5-3 show the bitmap of priorities that are
ready. The “width” of the table depends on the data type CPU_DATA (see CPU.H),
which can either be 8-, 16- or 32-bits. The width depends on the processor
used.
uC/OS-III 支持多达 OS_CFG_PRIO_MAX 种不同的优先级(见OS_CFG.H)。在 uC/OS-III 中,数值越小优先级越高。因此优先级 0 是优先级最高的。优先级 OS_CFG_PRIO_MAX-1 的优先级最低。
μC/OS-III allows up to OS_CFG_PRIO_MAX different priority
levels (see OS_CFG.H). In μC/OS-III, a low-priority number corresponds to a
high-priority level. Priority level zero (0) is thus the highest priority
level. Priority OS_CFG_PRIO_MAX-1 is the lowest priority level.
uC/OS-III 将最低优先级唯一地分配给空闲任务,其它任务不允许被设置为这个优先级。当任务准备好运行了,根据任务的优先级,位映像表中相应位就会被设置为 1。
μC/OS-III uniquely assigns the lowest priority to the idle
task. No other tasks are allowed at this priority level. If there are tasks
that are ready-to-run at a given a priority level, then its corresponding bit
is set (i.e., 1) in the bitmap table. Notice in Figures 5-1 to 5-3 that
“priority levels” are numbered from left to right and, the priority level
increases (moves toward lower priority) with an increase in table index. The
order was chosen to be able to use a special instruction called Count Leading
Zeros (CLZ), which is found on many modern processors.
如果处理器支持位清零指令 CLZ,这个指令会加快位映像表的设置过程。
This instruction greatly accelerates the process of
determining the highest priority level.



OS_PRIO.C 中包含了位映像表的设置、清除、查找的相关代码。这些函数都是 uC/OS-III 的内部函数,可以用汇编语言优化。
OS_PRIO.C contains the code to set, clear, and search the
bitmap table. These functions are internal to μC/OS-III and are placed in
OS_PRIO.C to allow them to be optimized in assembly language by replacing
OS_PRIO.C with an assembly language equivalent OS_PRIO.ASM, when necessary.
|
函数 |
功能 |
|
OS_PrioGetHighest() |
查找最高优先级 Find the highest priority level |
|
OS_PrioInsert() |
设置位映像表中相应的位 Set bit corresponding to priority level in the bitmap table |
|
OS_PrioInsert() |
清除位映像表中相应的位 Clear bit corresponding to priority level in the bitmap table |
表 6-1 优先级相关的函数
为了确定就绪列表中优先级最高的任务,位映像表会被扫描,通过OS_PrioGetHighest()函数找到优先级最高的任务。代码如列表 6-1 所示
To
determine the highest priority level that contains ready-to-run tasks, the
bitmap table is scanned until the first bit set in the lowest bit position is
found using OS_PrioGetHighest().The code for this function is shown in Listing
6-1.

L6-1(1)OS_PrioGetHighest()函数扫描 OSPrioTbl[]表直到找到非 0 的记录。这个循环最终会停止,因为总是有非 0 记录(空闲任务的存在)。
L6-1(1)
OS_PrioGetHighest() scans the table from OSPrioTbl[] until a non-zero entry is
found. The loop will always terminate because there will always be a non-zero
entry in the table because of the idle task.
L6-1(2) 当这个表中全是 0 记录时,就会从下一个表中查找。优先级"prio"会被增加(如果表长 32 位,就将 prio 加上 32)。
L6-1(2) Each time a zero entry is
found, we move to the next table entry and increment “prio” by the width (in
number of bits) of each entry. If each entry is 32-bits wide, “prio” is incremented
by 32.
L6-1(3)当找到第一个非 0 位时,计算该位之前 0 位的个数,返回该优先级值。如果 CPU 提供清零指令,可以通过这个指令优化代码。如果 CPU 没有这个指令,那么这个指令就只能用 C 语言模拟了。
L6-1(3) Once the first non-zero entry
is found, the number of “leading zeros” of that entry is simply added and
return the priority level back to the caller. Counting the number of zeros is a
CPU-specific function so that if a particular CPU has a built-in CLZ
instruction, it is up to the implementer of the CPU port to take advantage of
this feature. If the CPU used does not provide that instruction, the
functionality must be implemented in C.
函数 CPU_CntLeadZeros()统计了 CPU_DATA 记录中 0 的个数(从左边开始,以位计)。例如,假定位映像表长 32 位,0xF0001234 返回的非 0 位前 0 位数的个数是 0。0x00F01234 返回的是 8。
The
function CPU_CntLeadZeros() simply counts how many zeros there are in a CPU_DATA
entry starting from the left (i.e., most significant bit). For example,
assuming 32 bits, 0xF0001234 results in 0 leading zeros and 0x00F01234 results
in 8 leading zeros.
显而易见,线性地扫描这个表是效率低下的。然而,如果优先级数少的话,扫描是非常快的。事实上,有一些方法可以优化扫描。例如,如果使用 32 位处理器且应用中需要的优先级数少于 64,那么上述代码就可以被优化如列表 6-2 所示。若处理器中清零指令,该部分代码可以通过汇编被优化为很短的代码。
At
first view, the linear path through the table might seem inefficient. However,
if the number of priority levels is kept low, the search is quite fast. In
fact, there are several optimizations to streamline the search. For example, if
using a 32-bit processor and you are satisfied with limiting the number of
different priority levels to 64, the above code can be optimized as shown in
Listing 6-2. In fact, some processors have built-in “Count Leading Zeros”
instructions and thus, the code can be written with just a few lines of
assembly language. Remember that with μC/OS-III, 64 priority levels does not
mean that the user is limited to 64 tasks since with μC/OS-III, any number of
tasks are possible at a given priority level.

准备好运行的任务被放到就绪列表中,如图 6-1。就绪列表是一个数组(OSRdyList[]),它一共有 OS_CFG_PRIO_MAX 条记录,记录的数据类型为OS_RDY_LIST(见 OS.H)。就绪列表中的每条记录都包含了三个变量 .Entries 、.TailPtr 、.HeadPtr。
Tasks
that are ready to run are placed in the Ready List. As shown in Figure 6-1, the
ready list is an array (OSRdyList[]) containing OS_CFG_PRIO_MAX entries, with
each entry defined by the data type OS_RDY_LIST (see OS.H). An OS_RDY_LIST
entry consists of three fields: .Entries, .TailPtr and .HeadPtr.
.Entries 中该优先级的就绪任务数。当该优先级中没有任务就绪时,.Entries 就会被设置为 0。
.Entries
contains the number of ready-to-run tasks at the priority level corresponding
to the entry in the ready list. .Entries is set to zero (0) if there are no
tasks ready to run at a given priority level.
.TailPtr 和.HeadPtr 用于该优先级就绪任务的建立双向列表。.HeadPtr 指向列表的头部,.TailPtr 指向列表的尾部。
.TailPtr
and .HeadPtr are used to create a doubly linked list of all the tasks that are
ready at a specific priority. .HeadPtr points to the head of the list and
.TailPtr points to its tail.
表中的记录跟任务的优先级有关。例如,如果一个任务的优先级是 5,那么当它就绪时会被放入 OSRdyList[5]中。
The
“index” into the array is the priority level associated with a task. For
example, if a task is created at priority level 5 then it will be inserted in
the table at OSRdyList[5] if that task is ready to run.
表 6-2 中列出了与就绪列表相关的操作函数。这些都是 uC/OS-III 的内部函数,用户不能调用它们。
Table
6-2 shows the functions that μC/OS-III uses to manipulate entries in the ready
list.These functions are internal to μC/OS-III and the application code must
never call them.
|
函数名 |
功能 |
|
OS_RdyListInit() |
初始化就绪列表为空 Initialize the ready list to “empty” (see Figure 6-4) |
|
OS_RdyListInsert() |
插入一个 TCB 到就绪列表 Insert a TCB into the ready list |
|
OS_RdyListInsertHead() |
插入一个 TCB 到就绪列表的头部 Insert a TCB at the head of the list |
|
OS_RdyListInsertTail() |
插入一个 TCB 到就绪列表的尾部 Insert a TCB at the tail of the list |
|
OS_RdyListMoveHeadToTail() |
将 TCB 从列表的头部移到尾部 Move a TCB from the head to the tail of the list |
|
OS_RdyListRemove() |
将 TCB 从就绪列表中移除 Remove a TCB from the ready list |
表 6-2 与就绪列表相关的函数

F6-4(1)该图显示的是就绪列表为空时的状态。
F6-4(1) There is only one entry in
OSRdyList[OS_CFG_PRIO_MAX-1], the idle task.
F6-4(2)有多少种优先级,就绪列表中就由多少条记录。每个记录中都有 3 个变量。Entries 为该记录中的任务数。.PrevPtr 和.NextPtr 用于指向具有相同优先级到 TCB 组成的双向列表。对于空闲任务,这两个值为 NULL。
F6-4(2) The list points to OS_TCBs.
Only relevant fields of the TCB are shown. The .PrevPtr and .NextPtr are used
to form a doubly linked list of OS_TCBs associated to tasks at the same
priority. For the idle task, these fields always point to NULL.
F6-4(3)优先级 0 是为中断处理任务保留的(当设置 OS_CFG.H 中的OS_CFG_ISR_DEFERRED_EN 为 1)。在这种情况下,它是唯一一个能被设为优先级 0 的任务。
F6-4(3) Priority 0 is reserved to the
ISR handler task when OS_CFG_ISR_DEFERRED_EN is set to 1 in OS_CFG.H. In this
case, this is the only task that can run at priority 0.
假定 uC/OS-III 内部的任务都被使能。图 6-5 显示了当 OSInit()被调用后的就绪列表,
Assuming
all internal μC/OS-III’s tasks are enabled, Figure 6-5 shows the state of the
ready list after calling OSInit() (i.e., μC/OS-III’s initialization). It is
assumed that each μC/OS-III task had a unique priority. With μC/OS-III, this
does not have to be the case.

F6-5(1)如图所示,时基任务和另两个任务有它们自己的优先级。 uC/OS-III 允许多个任务有相同的优先级。特别的,时基任务的优先级要高于定时器任务,定时器任务的优先级需要于统计任务。
F6-5(1) The tick task and the other
two optional tasks have their own priority level, as shown. μC/OS-III enables
the user to have multiple tasks at the same priority and thus, the tasks could
be set up as shown. Typically, one would set the priority of the tick task
higher than the timer task and, the timer task higher in priority than the
statistic task.
F6-5(2)当某个优先级中只有一个任务的时候,它的头指针和尾指针会指向同一个 TCB。
F6-5(2) Both the tail and head
pointers point to the same TCB when there is only one TCB at a given priority
level.
uC/OS-III 提供很多服务可以把任务添加到就绪列表中。最明显的服务是 OSTaskCreate(),它通常创建准备运行的任务并将任务放入就绪列表中。如图 6-6 所示,就绪列表中该优先级中已经有两个任务了。OSTaskCreate()就会将这个任务插入到列表的未部。
Tasks
are added to the ready list by a number of μC/OS-III services. The most obvious
service is OSTaskCreate(), which always creates a task in the ready-to-run
state and adds the task to the ready list. As shown in Figure 6-6, when
creating a task, and specifying a priority level where tasks already exist (two
in this example) in the ready list at that priority level, OSTaskCreate() will
insert the new task at the end of the list of tasks at that priority level.

F6-6(1)在调用 OSTaskCreate()之前,已经有两个任务在就绪列表中的该优先级中了。
F6-6(1) Before calling OSTaskCreate()
(in this example), two tasks were in the ready list at priority “prio”.
F6-6(2)一个新的 TCB 传递给 OSTaskCreate(),然后 uC/OS-III 初始化这个 TCB。
F6-6(2) A new TCB is passed to
OSTaskCreate() and, μC/OS-III initialized the contents of that TCB.
F6-6(3)OSTaskCreate()调用 OS_RdyListInsertTail(),OS_RdyListInsertTail()的作用是将新添加进来的 TCB 链接到就绪队列中对应优先级记录(需设置四个指针,就绪列表中两个,任务重两个,并将记录中的.Entries 的值加一)。OSTaskCreate()还会调用 OS_PrioInsert()设置位映像表中的相应位。然而,处理 OS_PrioInsert()是非常快的,它不会影响到应用的性能。
F6-6(3)
OSTaskCreate() calls OS_RdyListInsertTail(), which links the new TCB to the
ready list by setting up four pointers and also incrementing the .Entries field
of OSRdyList[prio]. Not shown in Figure 6-6 is that OSTaskCreate() also calls
OS_PrioInsert() to set the bit in the bitmap table. Of course, this operation
is not necessary as there are already entries in the list at this priority.
However, OS_PrioInsert() is a very fast call and thus it should not affect
performance.
当一个任务创建了一个具有相同优先级的任务,这个新任务会被添加到该优先级队列的尾部(因为具有相同优先级情况下,没有理由让新任务先运行)。然而,当一个任务创建了一个具有不同优先级的任务时,这个新的任务就会放到对应优先级列表中的首部。
The
reason the new TCB is added to the end of the list is that the current head of
the list could be the task creator and, at the same priority, there is no
reason to make the new task the next task to run. In fact, a task being made
ready will be inserted at the tail of the list if the current task is at the
same priority. However, if a task is being made ready at a different priority
than the current task, it will be inserted at the head of the list.
uC/OS-III 支持任意数量的优先级。然而,绝大多数系统的需求量不会超过 64 种。
μC/OS-III
supports any number of different priority levels. However, 256 different
priority levels should be sufficient for the most complex applications and most
systems will not require more than 64 levels.
就绪队列包含两个数据结构:位映像表保存了哪个优先级中有任务待运行,优先级列表中包含了这该优先级下等待运行的任务。
The
ready list consist of two data structures: a bitmap table that keeps track of
which priority level is ready, and a table containing a list of all the tasks
ready at each priority level.
处理器支持清零指令 CLZ 会加快位映像表的扫描速度。{注意:正在运行的任务也被放在就绪列表中。}
Processors
having “count leading zeros” instructions can accelerate the table lookup
process used in determining the highest priority task.
调度器,决定了任务的运行顺序。uC/OS-III 是一个可抢占的,基于优先级的内核。根据其重要性每个任务都被分配了一个优先级。uC/OS-III 支持多个任务拥有相同的优先级。
The
scheduler, also called the dispatcher, is a part of μC/OS-III responsible for
determining which task runs next. μC/OS-III is a preemptive, priority-based
kernel. Each task is assigned a priority based on its importance. The priority
for each task depends on the application, and μC/OS-III supports multiple tasks
at the same priority level.
“可抢占的”意味当事件发生时,如果事件让高优先级任务被就绪,uC/OS-III 马上将 CPU 的控制权交给高优先级任务。因此,当一个任务提交信号量、发送消息给一个高优先级的任务(若该任务被就绪了),当前的任务就会被停止,更高优先级的任务获得 CPU 的控制权。类似的,当 ISR 提交信号量或发送消息给一更高优先级的任务(若该任务被就绪了),那么中断返回的时候不会返回到原任务,而是高优先级任务。
The
word preemptive means that when an event occurs, and that event makes a more
important task ready to run, then μC/OS-III will immediately give control of
the CPU to that task. Thus, when a task signals or sends a message to a
higher-priority task, the current task is suspended and the higher-priority
task is given control of the CPU. Similarly, if an Interrupt Service Routine
(ISR) signals or sends a message to a higher priority task, when the message is
completed, the interrupted task remains suspended, and the new higher priority
task resumes.
uC/OS-III 通过两种方法处理中断提交的事件:直接提交或延迟提交。这些会在章节 9 中详细介绍。从调度的角度看,这两种方法产生的结果是一样的;最高优先级的就绪任务会占用 CPU 如图 6-1 和 6-2 所示。
μC/OS-III
handles event posting from interrupts using two different methods: Direct and
Deferred Post. These will be discussed in greater detail in Chapter 9,
“Interrupt Management” on page 157. From a scheduling point of view, the end
result of the two methods is the same; the highest priority and ready task will
receive the CPU as shown in Figures 6-1 and 6-2.

F7-1(1)一个低优先级任务正在执行,这时中断发生了。
F7-1(1) A low priority task is
executing, and an interrupt occurs.
F7-1(2)如果中断使能,指令指针 IP 会跳转到中断服务程序。
F7-1(2) If interrupts are enabled,
the CPU vectors (i.e., jumps) to the ISR that is responsible for servicing the
interrupting device.
F7-1(3)中断服务程序处理相关的程序,发送信号量或消息给一个高优先级任务。高优先级任务被就绪。
F7-1(3) The ISR services the device
and signals or sends a message to a higher-priority task waiting to service
this device. This task is thus ready to run.
F7-1(4)中断服务程序完成操作后,它将 CPU 的控制权还给 uC/OS-III。
F7-1(4)When
the ISR completes its work it makes a service call to μC/OS-III.
F7-1(5)uC/OS-III 执行相应的操作。
F7-1(6)因为就绪队列中有一个更重要的任务,uC/OS-III 将不会返回中断前那个低优先级的任务。而是执行高优先级任务。
F7-1(6) Since there is a more
important ready-to-run task, μC/OS-III decides to not return to the interrupted
task but switches to the more important task. See Chapter 8, “Context
Switching” on page 147 for details on how this works.
F7-1(7)开始执行高优先级任务
F7-1(8)高优先级任务处理完成后,将 CPU 的控制权交给 uC/OS-III。
F7-1(8)The
higher priority task services the interrupting device and, when finished, calls
μC/OS-III asking it to wait for another interrupt from the device.
F7-1(9)uC/OS-III 执行相应的操作。
F7-1(10)uC/OS-III 转向执行原先那个低优先级任务。
F7-1(10) μC/OS-III blocks the high-priority task
until the next device interrupts. Since the device has not interrupted a second
time, μC/OS-III switches back to the original task (the one that was
interrupted).
F7-1(11)低优先级任务从被中断处继续执行。
F7-1(11) The interrupted task resumes
execution, exactly at the point where it was interrupted.
图 7-2 显示了当选择延迟提交中断产生的事件的方式时的一些额外步骤。最后的结果是一样的:高优先级任务抢占了低优先级任务。
Figure
7-2 shows that μC/OS-III performs a few extra steps when it is configured for
the Deferred Post method. Notice that the end results is the same; the
high-priority task preempts the low-priority one.

F7-2(1)中断服务程序中,不是直接发送信号量或消息给任务。而是先将信号量或消息放人外部消息队列,并就绪中断处理任务。
F7-2(1)The
ISR services the device and, instead of signaling or sending the message to the
task, μC/OS-III (through the POST call) places the post call into a special
queue and makes a very high-priority task (actually the highest-possible
priority) ready to run. This task is called the ISR Handler Task.
F7-2 (3)当 ISR 处理完它的工作时,就把 CPU 的使用权交给 uC/OS-III。
F7-2
(3)When the ISR completes its work, it makes a service call to
μC/OS-III.
F7-2(3)uC/OS-III 处理一些操作。
F7-2(4)因为中断处理任务被就绪,uC/OS-III 将 CPU 的使用权交给它。
F7-2(4)Since
the ISR made the ISR Handler Task ready to run, μC/OS-III switches to that
task.
F7-2(5)uC/OS-III 处理一些操作。
F7-2(6) 中断处理任务将外部消息队列中的消息移除并重新提交给相应的任务。这样,就将这个过程消息提交从中断级变成了任务级。使用这种方法的目的是为了减小关中断时间(详见第 9 章)。当消息队列为空时,uC/OS-III 将中断处理任务从就绪队列中移除,然后执行就绪队列中的最高优先级任务。
F7-2(6) The ISR Handler Task then removes the post call from the
message queue and reissues the post. This time, however, it does it at the task
level instead of the ISR level. The reason this extra step is performed is to
keep interrupt disable time as small as possible. See Chapter 9, “Interrupt
Management” on page 157 to find out more on the subject. When the queue is
emptied, μC/OS-III removes the ISR Handler Task from the ready list and
switches to the task that was signaled or sent a message.
当出现以下情况时会发生调度:
Scheduling
occurs at scheduling points and nothing special must be done in the application
code since scheduling occurs automatically based on the conditions described
below.
任务被标记或发送消息给另一个任务
A
task signals or sends a message to another task:
任务调用提交服务函数 OS???Post(),发送信号量或消息给其它任务时调度发生。调度在 OS???Post()函数的结束时发生。注意在有些情况下,调度是不会发生的(见 OS_OPT_POST_NO_SCHED 的可选参数)。
This
occurs when the task signaling or sending the message calls one of the post
services, OS???Post(). Scheduling occurs towards the end of the OS???Post()
call. Note that scheduling does not occur if one specifies (as part of the post
call) to not invoke the scheduler (i.e., set the option argument to
OS_OPT_POST_NO_SCHED).
任务调用 OSTimeDly()或 OSTimeDlyHMSM():
A task calls OSTimeDly() or OSTimeDlyHMSM():
如果延时参数不是 0,调度发生,调度会在该任务被放入挂起队列后马上执行。
If
the delay is non-zero, scheduling always occurs since the calling task is
placed in a list waiting for time to expire. Scheduling occurs as soon as the
task is inserted in the wait list.
任务所等待的事件发生或超时
A
task waits for an event to occur and the event has not yet occurred:
当 OS???Pend()被调用时。接收到该事件的任务、或超时的任务就会被移出等待队列。然后调度器选择就绪列表中优先级最高的任务执行。{移出等待队列的任务不一定就是就绪状态,因为它还可能在停止队列中,效果是可以叠加的。}
This
occurs when one of the OS???Pend() functions are called. The task is placed in
the wait list for the event and, if a non-zero timeout is specified, then the
task is also inserted in the list of tasks waiting to timeout. The scheduler is
then called to select the next most important task to run.
任务取消挂起
If a task aborts a pend:
一个任务可以被取消挂起,若另一个任务调用 OS???PendAbort()。当任务被移出等待列表中时调度发生。
A
task is able to abort the wait (i.e., pend) of another task by calling
OS???PendAbort(). Scheduling occurs when the task is removed from the wait list
for the specified kernel object.
新任务被创建
If a
task is created:
新的任务被创建时调度发生。
The
newly created task may have a higher priority than the task’s creator. In this
case, the scheduler is called.
任务被删除
If a
task is deleted:
当一个任务被删除时调度发生。
When
terminating a task, the scheduler is called if the current task is deleted.
内核对象被删除
If a
kernel object is deleted:
任务所等待的内核对象被删除时(事件标志组、信号量、消息队列、mutex 都是内核对象),这些任务就可能被就绪。然后调度发生。
If
you delete an event flag group, a semaphore, a message queue, or a mutual
exclusion semaphore, if tasks are waiting on the kernel object, those tasks
will be made ready to run and the scheduler will be called to determine if any
of the tasks have a higher priority than the task that deleted the kernel
object.
任务改变自身的优先级或其它任务的优先级
A
task changes the priority of itself or another task:
当任务改变自身优先级或其它任务优先级时,调度发生。
The
scheduler is called when a task changes the priority of another task (or
itself) and the new priority of that task is higher than the task that changed
the priority.
当任务通过调用 OSTaskSuspend()停止自身
A
task suspends itself by calling OSTaskSuspend():
当任务调用 OSTaskSuspend()停止自身时,调度发生。
The
scheduler is called since the task that called OSTaskSuspend() is no longer
able to execute, and must be resumed by another task.
任务调用 OSTaskResume()恢复其它停止了的任务
A
task resumes another task that was suspended by OSTaskSuspend():
任务调用 OSTaskResume()恢复其它停止了的任务时,调度发生。
The
scheduler is called if the resumed task has a higher priority than the task
that calls OSTaskResume().
退出中断服务程序
At
the end of all nested ISRs:
当退出中断服务程序时,调度发生。这种情况下,调度器调用OSIntExit()函数开始调度而不是 OSSched()。
The
scheduler is called at the end of all nested ISRs to determine whether a more
important task is made ready to run by one of the ISRs. The scheduling is
actually performed by OSIntExit() instead of OSSched().
通过调用 OSSchedUnlock()调度器被解锁
The
scheduler is unlocked by calling OSSchedUnlock():
调用 OSSchedLock()锁调度器,调度发生。需要注意的是,锁调度器可以被嵌套,解锁次数必须等于加锁次数。
The
scheduler is unlocked after being locked. Lock the scheduler by calling
OSSchedLock(). Note that locking the scheduler can be nested and the scheduler
must be unlocked a number of times equal to the number of locks.
调用 OSSchedRoundRobinYield()任务放弃了分配给它的时间片
A
task gives up its time quanta by calling OSSchedRoundRobinYield():
假定多个任务有相同的优先级,正在运行的任务放弃了分配给它
的时间片。调度发生。
This
assumes that the task is running alongside with other tasks at the same
priority and the currently running task decides that it can give up its time
quanta and let another task run.
用户调用 OSSched()
The
user calls OSSched():
用户程序调用 OSSched()时,调度发生。若调用 OS???Post()函数时设置参数为了 OS_OPT_POST_NO_SCHED,事件在被提交后,不调用调度器。当然,任务可以一次性提交多个事件,但在最后一个事件提交时才调用调度器。{因为当一个事件被提交时,调度器就会发生调度。所以用户设置了 OS_OPT_POST_NO_SCHED 后,用户一次性提交多个事件而只发生一次发生调度。}
The
application code can call OSSched() to run the scheduler. This only makes sense
if calling OS???Post() functions and specifying OS_OPT_POST_NO_SCHED so that
multiple posts can be accomplished without running the scheduler on every post.
Of course, in the above situation, the last post can be a post without the
OS_OPT_POST_NO_SCHED option.
当多个任务有相同的优先级时,uC/OS-III 允许每个任务运行规定的时间片。当任务没有用完分配给它的时间片时,它可以自愿地放弃CPU。uC/OS-III 允许任务在运行时开启或者关闭循环轮转调度。
When
two or more tasks have the same priority, μC/OS-III allows one task to run for
a predetermined amount of time (called a Time Quanta) before selecting another
task. This process is called Round-Robin Scheduling or Time Slicing. If a task
does not need to use its full time quanta it can voluntarily give up the CPU so
that the next task can execute. This is called Yielding. μC/OS-III allows the
user to enable or disable round robin scheduling at run time.
图 F7-3 是相同优先级下任务运行的时序图。如图,有三个优先级都为 X 的任务。为了说明,时间片的长度为 4。
Figure
7-3 shows a timing diagram with tasks running at the same priority. There are
three tasks that are ready to run at priority “X”. For sake of illustration,
the time quanta occurs every 4th clock tick. This is shown as a darker tick
mark.

F7-3(1)任务 3 正在被运行。在这段时间内,时基中断发生,但任务 3 还没有到期。
F7-3(1) Task #3 is executing. During
that time, tick interrupts occur but the time quanta have not expired yet for
Task #3.
F7-3(2)任务 3 主动放弃剩下的时间片。
F7-3(2) On the 4th tick interrupt,
the time quanta for Task #3 expire.
F7-3(3)uC/OS-III 恢复任务 1,因为它是队列中任务 3 的下一个任务。
F7-3(3) μC/OS-III resumes Task #1
since it was the next task in the list of tasks at priority “X” that was ready
to run.
F7-3(4)任务 1 被执行直到分配给它的时间片到期。
F7-3(4) Task #1 executes until its
time quanta expires (i.e., after four ticks).
F7-3(5)任务 3 正在被运行。在这段时间内,时基中断发生,但任务 3 还没有到期。
F7-3(6)任务 3 主动放弃剩下的时间片。
F7-3(7)有趣的事情发生了,uC/OS-III 会重新设置任务 1 的时间片长度为 4 个时基。但剩余时间片的计数是每个时基中断递减,即在第4 个时基中断发生时时间片到期。而任务 1 是在时基即将发生时接手的,所以事实上它的时间片会少一个时基。
F7-3(7) Here Task #3 executes but
decides to give up its time quanta by calling the μC/OS-III function
OSSchedRoundRobinYield(), which causes the next task in the list of tasks ready
at priority “X” to execute. An interesting thing occurred when μC/OS-III
scheduled Task #1. It reset the time quanta for that task to four ticks so that
the next time quanta will expire four ticks from this point.
F7-3(8)任务 1 执行
F7-3(8) Task #1 executes for its full
time quanta.
uC/OS-III 允许用户在任务运行时修改时间片的长度(通过 OSSchedRoundRobinCfg(),详见附录 A)。这个函数也可以用于开启或关闭循环轮转调度。
μC/OS-III
allows the user to change the time quanta at run time through the
OSSchedRoundRobinCfg() function (see Appendix A, “μC/OS-III API Reference
Manual” on page 375). This function also allows round robin scheduling to be
enabled/disabled, and the ability to change the default time quanta.
uC/OS-III 允许用户为每个任务设置不同的时间片。不同的任务可以有不同的时间片。当任务被创建时,其时间片长度被设置。也可以在运行时调用OSTaskTimeQuantaSet()修改。
μC/OS-III
also enables the user to specify the time quanta on a per-task basis. One task
could have a time quanta of 1 tick, another 12, another 3, and yet another 7,
etc. The time quanta of a task is specified when the task is created. The time
quanta of a task may also be changed at run time through the function
OSTaskTimeQuantaSet().
通过这两个函数完成调度功能:OSSched()和 OSIntExit()。OSSched()在任务级被调用,OSIntExit()在中断级被调用。这两个函数都在 OS_CORE.C 中定义。
Scheduling
is performed by two functions: OSSched() and OSIntExit(). OSSched() is called
by task level code while OSIntExit() is called by ISRs. Both functions are
found in OS_CORE.C.
图 7-1 阐述了调度所需用的两个数据结构。位映像表和就绪列表。
Figure
7-1 illustrates the two sets of data structures that the scheduler uses; the
priority ready bitmap and the ready list as described in Chapter 6, “The Ready
List” on page 123.

7-4-1 OSSched()
以下是任务级调度的伪代码。
The
pseudo code for the task level scheduler, OSSched() is shown in Listing 7-1.

L7-1(1)因为要进入调度程序,关中断。OSSched()首先确定自己不是被中断服务程序调用,因为它是任务级调度程序。中断级调度需调用 OSIntExit()。
L7-1(1) OSSched() starts by making
sure it is not called from an ISR as OSSched() is the task level scheduler.
Instead, an ISR must call OSIntExit(). If OSSched() is called by an ISR,
OSSched() simply returns.
L7-1(2)第二步是确保调度器没有被锁。如果调度器被锁,就不能运行调度器,函数马上返回。
L7-1(2) The next step is to make sure
the scheduler is not locked. If the code calls OSSchedLock() the user does not
want to run the scheduler and the function just returns.
L7-1(3)OSSched()通过扫描位映像表 OSPrioTbl[]找到就绪列表中优先级最高的位。(详见章节 6)
L7-1(3) OSSched() determines the
priority of the highest priority task ready by scanning the bitmap OSPrioTbl[]
as described in Chapter 6, “The Ready List” on page 123.
L7-1(4)确定好优先级后,索引到 OSRdyList[]并提取该记录中的首个 TCB(OSRdyList[highest priority].HeadPtr)。到现在为止,已经知道了哪个任务将要切换为运行状态。特别的,OSTCBCurPtr 指向的是当前任务的 TCB,OSTCBHighRdyPtr 指向的是将要被切换的 TCB。
L7-1(4) Once it is known which
priority is ready, index into the OSRdyList[] and extract the OS_TCB at the
head of the list (i.e., OSRdyList[highest priority].HeadPtr). At this point, it
is known which OS_TCB to switch to and which OS_TCB to save to as this was the
task that called OSSched(). Specifically, OSTCBCurPtr points to the current
task’s OS_TCB and OSTCBHighRdyPtr points to the new OS_TCB to switch to.
L7-1(5)当就绪表中的最高优先级任务不为当前正在运行的任务时(OSTCBCurPtr!=OSTCBHighRdyPtr),进行任务级的上下文切换。然后在开中断。
L7-1(5)
If the user is not attempting to switch to the same task that is currently
running, OSSched() calls the code that will perform the context switch (see
Chapter 8, “Context Switching” on page 147). As the code indicates, however,
the task level scheduler calls a task-level function to perform the context switch.
注意的是,调度时和上下文切换是运行在关中断的状态下的。这是必要的因为这些操作是原子性的。
Notice
that the scheduler and the context switch runs with interrupts disabled. This
is necessary because this process needs to be atomic.
7-4-2 OSIntExit()
以下是 ISR 级的调度器代码 OSIntExit()如列表 7-2 所示。需要注意它假定当调用了 OSIntExit()之后中断被关闭。
The
pseudo code for the ISR level scheduler, OSIntExit() is shown in Listing 7-2.
Note that interrupts are assumed to be disabled when OSIntExit() is called.

L7-2(1)OSIntExit()开始时确保中断嵌套级不为 0。{如果为 0 的话,那么接下来的 OSIntNestingCtr--就会发生溢出了}
L7-2(1) OSIntExit() starts by making sure
that the call to OSIntExit() will not cause OSIntNestingCtr to wrap around.
This would be extremely and unlikely occurrence, but not worth taking a chance
that it might.
L7-2(2)OSInrExit()将 OSIntNestingCtr 递减,若 OSIntNestingCtr 不为 0 时则返回。确保该函数是在第一级 ISR 中执行。当还有中断程序未被处理时就不能运行调度器。
L7-2(2) OSIntExit() decrements the
nesting counter as OSIntExit() is called at the end of an ISR. If all ISRs have
not nested, the code simply returns. There is no need to run the scheduler
since there are still interrupts to return to.
L7-2(3)OSIntExit()检查调度器是否被锁。如果被锁,OSIntExit() 不会运行调度器并返回到中断前的任务。
L7-2(3) OSIntExit() checks to see
that the scheduler is not locked. If it is, OSIntExit() does not run the
scheduler and simply returns to the interrupted task that locked the scheduler.
L7-2(4)当这是第一级中断且调度器没有被锁时。查找优先级最高的任务。
L7-2(4) Finally, this is the last
nested ISR (we are returning to task-level code) and the scheduler is not
locked. Therefore, we need to find the highest priority task that needs to run.
L7-2(5)从 OSRdyList[]中获得最高优先级的 TCB。
L7-2(5) Again, we extract the highest
priority OS_TCB from OSRdyList[].
L7-2(6)如果最高优先级就绪任务不是当前正在运行的任务。uC/OS-III 就执行中断级的上下文切换。中断级切换的上文已经在中断开始时被保存了,它可以直接切换到任务。这些在第 8 章中详细介绍。
L7-2(6) If the highest-priority task
is not the current task μC/OS-III performs an ISR level context switch. The ISR
level context switch is different as it is assumed that the interrupted task’s
context was saved at the beginning of the ISR and it is only left to restore
the context of the new task to run. This is described in Chapter 8, “Context
Switching” on page 147.
7-4-3 OS_SchedRoundRobin()
当同一优先级中有多个任务。一个任务的时间片期满,uC/OS-III 就会运行同优先级就绪的任务。OS_SchedRoundRobin()就是执行这种操作的,它被OSTimeTick()或者 OS_IntQTask()调用。OS_SchedRoundRobin()在 OS_CORE.C 中定义。
When
the time quanta for a task expires and there are multiple tasks at the same
priority, μC/OS-III will select and run the next task that is ready to run at
the current priority. OS_SchedRoundRobin() is the code used to perform this
operation. OS_SchedRoundRobin() is either called by OSTimeTick() or
OS_IntQTask(). OS_SchedRoundRobin() is found in OS_CORE.C.
当你选择直接提交方式时,OS_SchedRoundRobin()被 OSTimeTick() 调用。当选择延迟提交方式时,OS_SchedRoundRobin()被OS_IntQTask()调用。
OS_SchedRoundRobin()
is called by OSTimeTick() when you selected the Direct Method of posting (see
Chapter 9, “Interrupt Management” on page 157). OS_SchedRoundRobin() is called
by OS_IntQTask() when selecting the Deferred Post Method of posting, described
in Chapter 8.
OS_SchedRoundRobin()的伪代码如列表 7-3 所示
The
pseudo code for the round-robin scheduler is shown in Listing 7-3.

L7-3(1)OS_SchedRoundRobin()开始时先确定循环轮转调度已经被使能。(必须通过调用 OSSchedRoundRobinCfg()开启循环轮转调度)。
L7-3(1) OS_SchedRoundRobin() starts
by making sure that round robin scheduling is enabled. Recall that to enable
round robin scheduling, one must call OSSchedRoundRobinCfg().
L7-3(2)时间片计数值,驻留于正在运行任务的 TCB 中,每次时基中断时它被递减。当它的值不为 0 时 OS_SchedRoundRobin()返回。
L7-3(2) The time quanta counter,
which resides inside the OS_TCB of the running task, is decremented. If the
value is still non-zero then OS_SchedRoundRobin() returns.
L7-3(3)当时间片计数值为 0 时,检查是否有其它同优先级任务等待被运行。如果没有,就返回。
L7-3(3) Once the time quanta counter
reaches zero, check to see that there are other ready-to-run tasks at the
current priority. If there are none, return. Round robin scheduling only
applies when there are multiple tasks at the same priority and the task doesn’t
completes its work within its time quanta.
L7-3(4)当调度器被锁时,OS_SchedRoundRobin()返回。
L7-3(4) OS_SchedRoundRobin() also
returns if the scheduler is locked.
L7-3(5)若当前任务的时间片到期,OS_SchedRoundRobin()将当前任务的 TCB 移动到该优先级队列的末尾(从头部移到尾部)。
L7-3(5) Next, OS_SchedRoundRobin()
move the OS_TCB of the current task from the head of the ready list to the end.
L7-3(6)在该优先级记录队列首部的任务的时间片值被载入到任务。每个任务都可以有自己的时间片值。(任务创建时被设置或通过OSTaskTimeQuantaSet()设置)。如果该值为 0,uC/OS-III 就假定时间片值为默认值OSSchedRoundRobinDfltTimeQuanta。
L7-3(6)
The time quanta for the task at the head of the list are loaded. Each task may
specify its own time quanta when the task is created or through
OSTaskTimeQuantaSet(). If specifying 0, μC/OS-III assumes the default time
quanta, which corresponds to the value in the variable
OSSchedRoundRobinDfltTimeQuanta.
uC/OS-III 是可抢占的调度器,所以它总是执行优先级最高的就绪任务。
μC/OS-III
is a preemptive scheduler so it will always execute the highest priority task
that is ready to run.
uC/OS-III 运行多个任务具有相同的优先级。这时,它可以被设置为循环轮转调度。
μC/OS-III
allows for multiple tasks at the same priority. If there are multiple
ready-to-run tasks, μC/OS-III will round robin between these tasks.
调度发生在一些特定的时间点。
Scheduling
occurs at specific scheduling points, when the application calls μC/OS-III
functions.
uC/OS-III 有 2 种调度方式:OSSched()被用于任务级。OSIntExit() 被用于中断级。
μC/OS-III
has two schedulers: OSSched(), which is called by task-level code, and
OSIntExit() called at the end of each ISR.
当 uC/OS-III 转向执行另一个任务的时候,它保存了当前任务的CPU 寄存器到堆栈。并从新任务的的堆栈中 CPU 寄存器载入 CPU。这个过程叫做上下文切换。
When
μC/OS-III decides to run a different task (see Chapter 7, “Scheduling” on page
133), it saves the current task’s context, which typically consists of the CPU
registers, onto the current task’s stack and restores the context of the new
task and resumes execution of that task. This process is called a Context
Switch.
上下文切换需要一些开支。CPU 的寄存器越多,开支越大。上下文切换的时间基本取决于有多少个 CPU 寄存器需要被存储和载人。
Context
switching adds overhead. The more registers a CPU has, the higher the overhead.
The time required to perform a context switch is generally determined by how
many registers must be saved and restored by the CPU.
上下文切换部分的代码是移植 uC/OS-III 时需编写的。该部分代码要适用于处理器。这些代码被放在 C 和汇编语言文件中:OS_CPU.H,OS_CPU_C.C,OS_CPU_A.ASM(章节 18“移植 uC/OS-III”会详细介绍)。根据不同架构的 CPU,移植 uC/OS-III 时需要注意很多细节。
The context
switch code is generally part of a processor’s port of μC/OS-III. A port is the
code needed to adapt μC/OS-III to the desired processor. This code is placed in
special C and assembly language files: OS_CPU.H, OS_CPU_C.C and OS_CPU_A.ASM.
Chapter 18, “Porting μC/OS-III” on page 335, Porting μC/OS-III provides more
details on the steps needed to port μC/OS-III to different CPU architectures.
在这个章节中,我们在常见的 CPU 架构中讨论上下文切换的过程如图 8-1。这个 CPU 包含了 16 个整数寄存器(R0 到 R15)和一个中断堆栈寄存器和一个状态寄存器{共 18 个寄存器,R0 到 R15,R14"和SR}。每个寄存器都是 32 位的,且这 16 个整数寄存器都能存储数据或地址。指令指针寄存器是 R15,两个堆栈指针寄存器是 R14 和 R14"。
In
this chapter, we will discuss the context switching process in generic terms
using a fictitious CPU as shown in Figure 8-1. Our fictitious CPU contains 16
integer registers (R0 to R15), a separate ISR stack pointer, and a separate
status register (SR). Every register is 32 bits wide and each of the 16 integer
registers can hold either data or an address. The program counter (or
instruction pointer) is R15 and there are two separate stack pointers labeled
R14 and R14”.
R14 是指向任务的堆栈的指针 TSP,R14"是指向 ISR 的堆栈的指针 ISP。当 CPU 接收到一个中断自动切换到 ISR 堆栈。{也就是处理器有独立的用于处理中断的堆栈}
R14
represents a task stack pointer (TSP), and R14” represents an ISR stack pointer
(ISP). The CPU automatically switches to the ISR stack when servicing an
exception or interrupt. The task stack is accessible from an ISR (i.e., we can
push and pop elements onto the task stack when in an ISR), and the interrupt
stack is also accessible from a task.

在 uC/OS-III 中,任务切换时的堆栈设置类似于中断发生时的那样,所有的 CPU 寄存器都被保存。我们假定任务堆栈中的信息将要被载入到 CPU 中,如图 8-2。
In
μC/OS-III, the stack frame for a ready task is always setup to look as if an
interrupt has just occurred and all processor registers were saved onto it.
Tasks enter the ready state upon creation and thus their stack frames are
pre-initialized by software in a similar manner. Using our fictitious CPU,
we’ll assume that a stack frame for a task that is ready to be restored is shown
in Figure 8-2.
TSP 指向任务堆栈中最后一个被保存的寄存器。程序指针寄存器和状态寄存器是最先被保存在任务堆栈中。事实上,当中断发生时这些是被 CPU 自动执行的。其它的寄存器通过软件被压入任务堆栈,TSP 不会被保存到堆栈,但会被保存到任务的 TCB。
The
task stack pointer points to the last register saved onto the task’s stack. The
program counter and status registers are the first registers saved onto the
stack. In fact, these are saved automatically by the CPU when an exception or
interrupt occurs (assuming interrupts are enabled) while the other registers
are pushed onto the stack by software in the exception handler. The stack
pointer (R14) is not actually saved on the stack but instead is saved in the
task’s OS_TCB.
ISP 指向当前中断堆栈的顶部。当中断服务程序被执行时,处理器把 R14"作为堆栈指针用于指向函数和局部参数。
The
interrupt stack pointer points to the current top-of-stack for the interrupt
stack, which is a different memory area. When an ISR executes, the processor
uses R14” as the stack pointer for function calls and local arguments.

有两种上下文切换方式:一个是任务级,一个是中断级。任务级切换通过调用OSCtxSw()实现,实际上它是被宏 OS_TASK_SW()调用的。
There
are two types of context switches: one performed from a task and another from
an ISR. The task level context switch is implemented by the code in OSCtxSw(),
which is actually invoked by the macro OS_TASK_SW(). A macro is used as there
are many ways to invoke OSCtxSw() such as software interrupts, trap
instructions, or simply calling the function.
中断级切换通过调用 OSIntCtxSw()实现。它是用汇编语言写的,保存于 OS_CPU_A.ASM。
The ISR context switch is implemented by OSIntCtxSw(). The code for both functions is typically written in assembly language and is found in a file called OS_CPU_A.ASM.
当有一个高优先级就绪任务需要被执行,任务级调度器会调用OSCtxSw()。图 8-3 显示了在调用 OSCtxSw() 之前的一些变量和数据结构。
OSCtxSw()
is called when the task level scheduler (OSSched()) determines that a new high
priority task needs to execute. Figure 8-3 shows the state of several μC/OS-III
variables and data structures just prior to calling OSCtxSw().

F8-3(1)OSTCBCurPtr 指向了当前正在运行任务的 OS_TCB。然后
OSSchedule()被调用。
F8-3(1) OSTCBCurPtr points to the
OS_TCB of the task that is currently running and that called OSSched().
F8-3(2)通过 OSTCBHighRdyPtr,OSSched()找到将要被运行任务的OS_TCB。
F8-3(2) OSSched() finds the new task
to run by having OSTCBHighRdyPtr point to its OS_TCB.
F8-3(3)OSTCBHighRdyPtr->StkPtr 指向将要被运行任务堆栈的顶部。{假定堆栈是从内存高地址向地地址生长的}
F8-3(3) OSTCBHighRdyPtr->StkPtr
points to the top of stack of the new task to run.
F8-3(4)执行任务的上下文切换时,所有的 CPU 寄存器被保存到该任务堆栈。由于保存了任务的当前状态,所以任务可以被恢复。
F8-3(4) When μC/OS-III creates or
suspends a task, it always leaves the stack frame to look as if an interrupt
just occurred and all the registers saved onto it. This represents the expected
state of the task so it can be resumed.
F8-3(5)调用 OSSched()后 CPU 的 TSP 会指向任务的堆栈。根据OSCtxSw()的被调用形式。TSP 也可能指向 OSCtxSw()的返回地址。
F8-3(5) The CPU’s stack pointer
points within the stack area (i.e., RAM) of the task that called OSSched(). Depending
on how OSCtxSw() is invoked, the stack pointer may be pointing at the return
address of OSCtxSw().
图 8-4 展示了 OSCtxSw()实现上下文切换的相关步骤。
Figure
8-4 shows the steps involved in performing the context switch as implemented by
OSCtxSw().

F8-4(1)OSCtxSw()开始执行,保存状态寄存器和程序指针寄存器到当前的任务堆栈。保存的顺序与中断发生时 CPU 保存寄存器的顺序相同。假定 SR 先入栈,然后其它寄存器入栈。
F8-4(1) OSCtxSw() begins by saving
the status register and program counter of the current task onto the current
task’s stack. The saving order of register depends on how the CPU expects the
registers on the stack frame when an interrupt occurs. In this case, it is
assumed that the SR is stacked first. The remaining registers are then saved
onto the stack.
F8-4(2)当任务被停止时,OSCtxSw()保存 CPU 的 TSP 到该任务的OS_TCB 中,换句话说,OSTCBCurPtr->StkPtr=R14。
F8-4(2) OSCtxSw() saves the contents
of the CPU’s stack pointer into the OS_TCB of the task being suspended. In
other words, OSTCBCurPtr->StkPtr = R14.
F8-4(3)然后 OSCtxSw()将新任务 OS_TCB 的顶部地址存入 CPU 的TSP 中。换句话说,R14=OSTCBCurPtr->StkPtr。
F8-4(3) OSCtxSw() then loads the CPU
stack pointer with the saved top-of-stack from the new task’s OS_TCB. In other
words, R14 = OSTCBHighRdyPtr->StkPtr.
F8-4(4)最后,OSCtxSw()将新任务堆栈中 R13~~R0 关内容载入 CPU 寄存器。再然后(此时的会 TSP 指向 PC,如图),通过一个中断返回指令{比如汇编中的 IRET},程序指针寄存器和状态寄存器被恢复到 CPU 的寄存器中。
F8-4(4) Finally, OSCtxSw() retrieves
the CPU register contents from the new stack. The program counter and status
registers are generally retrieved at the same time by executing a return from
interrupt instruction.
在 ISR 中就绪了高优先级任务 B,ISR 返回时将不会回到中断前的任务 A,而是直接转向到执行高优先级任务 B。此时,由于中断产生时已经将任务 A 的状态保存在任务 A 的堆栈中,所以 ISR 返回时无需再保存任务 A 的状态,而是直接载入任务 B 的 CPU 寄存器到硬件CPU 寄存器中即可。调用 OSIntCtxSw()执行该操作。图 8-5 显示了在调用 OSIntCtxSw() 之前的一些变量和数据结构。
OSIntCtxSw()
is called when the ISR level scheduler (OSIntExit()) determines that a new high
priority task is ready to execute. Figure 8-5 shows the state of several
μC/OS-III variables and data structures just prior to calling OSIntCtxSw().

在进入 ISR 之前 CPU 寄存器会被保存在任务 A 的堆栈中。因此,OSTCBCurPtr->StkPtr 包含了指向任务顶部寄存器的指针。
μC/OS-III
assumes that CPU registers are saved onto the task’s stack at the beginning of
an ISR (see Chapter 9, “Interrupt Management” on page 157). Because of this,
notice that OSTCBCurPtr->StkPtr contains a pointer to the top-of-stack
pointer of the task being suspended (the one on the left). OSIntCtxSw() does
not have to worry about saving the CPU registers of the suspended task since
that is already finished.
图 8-6 显示了 OSIntCtxSw()执行载入任务 B 的 CPU 寄存器步骤。
这部分的步骤跟 OSCtxSw()的下半部分步骤是一样的。
Figure
8-6 shows the operations performed by OSIntCtxSw() to complete the second half
of the context switch. This is exactly the same process as the second half of
OSCtxSw().

F8-6 (1)OSIntCtxSw()将新任务 OS_TCB 的顶部堆栈地址值载入CPU 中的 TSP。即 R14=OSTCBHighRdyPtr->StkPtr。
F8-6(1) OSIntCtxSw() loads the CPU
stack pointer with the saved top-of-stack from the new task’s OS_TCB. R14 =
OSTCBHighRdyPtr->StkPtr.
F8-6(2)然后 OSIntCtxSw()将新堆栈的相关内容载入到 CPU 寄存器中。通过一个中断返回指令,程序指针寄存器和状态寄存器被载入。
F8-6(2) OSIntCtxSw() then retrieves
the CPU register contents from the new stack. The program counter and status
registers are generally retrieved at the same time by executing a return from
interrupt instruction.
上下文切换包括两部分内容,保存旧任务的内容,载入新任务的内容。任务级切换时,通过调用 OSSched()实现。中断级切换时,通过调用 OSIntExit()实现。
A
context switch consists of saving the context (i.e., CPU registers) associated
with one task and restoring the context of a new, higher-priority task.
The new
task to be switched to is determined by OSSched() when a context switch is
initiated by task level code, and OSIntExit() when initiated by an ISR.
OSSched()中调用 OSCtxSw()实现上下文切换。OSIntExit()中调用SIntCtxSw()实现上下文切换。然而,OSIntCtxSw()只需用做上下文切换的第二部分,因为中断时被中断任务的 CPU 寄存器已经被保存到被中断任务的堆栈中了。
OSCtxSw()
performs the context switch for OSSched() and OSIntCtxSw() performs the context
switch for OSIntExit(). However, OSIntCtxSw() only needs to perform the second
half of the context switch because it is assumed that the ISR saved CPU
registers upon entry to the ISR.
中断是硬件机制,用于通知 CPU 有异步事件发生。当中断被响应时,CPU 保存部分(或全部)寄存器值并跳转到中断服务程序(ISR)。
An
interrupt is a hardware mechanism used to inform the CPU that an asynchronous
event occurred. When an interrupt is recognized, the CPU saves part (or all) of
its context (i.e., registers) and jumps to a special subroutine called an
Interrupt Service Routine (ISR).
ISR 响应这个事件,当 ISR 处理完成后,程序会返回中断前的任务或更高优先级的任务。
The
ISR processes the event, and – upon completion of the ISR – the program either
returns to the interrupted task, or the highest priority task, if the ISR made
a higher priority task ready to run.
在实时系统中,关中断的时间越短越好。长时间关中断可能会导致中断来不及响应而重叠,即多次中断被当做一次中断。
In a
real-time environment, interrupts should be disabled as little as possible.
Disabling interrupts affects interrupt latency possibly causing interrupts to
be missed.
通常处理器允许中断嵌套,它意味着当处理 ISR 时,可以有另一个中断发生。
在实时系统中,最大的关中断时间是一个重要的性能,关中断是为了处理临界段代码。
Processors
generally allow interrupts to be nested, which means that while servicing an
interrupt, the processor recognizes and services other (more important)
interrupts.
Interrupts
allow a microprocessor to process events when they occur (i.e.,
asynchronously), which prevents the microprocessor from continuously polling
(looking at) an event to see if it occurred. Task level response to events is
typically better using interrupt mode as opposed to polling mode, however at
the possible cost of increased interrupt latency. Microprocessors allow
interrupts to be ignored or recognized through the use of two special
instructions: disable interrupts and enable interrupts, respectively.
One
of the most important specifications of a real-time kernel is the maximum
amount of time that interrupts are disabled. This is called interrupt disable
time. All real-time systems disable interrupts to manipulate critical sections
of code and re-enable interrupts when critical sections are completed. The
longer interrupts are disabled, the higher the interrupt latency.
中断响应时间定义为:接收到中断到开始处理 ISR 中代码的这段时间。通常,中断时用户代码的上文(CPU 寄存器)会被放入堆栈中。
Interrupt
response is defined as the time between the reception of the interrupt and the
start of the user code that handles the interrupt. Interrupt response time
accounts for the entire overhead involved in handling an interrupt. Typically,
the processor’s context (CPU registers) is saved on the stack before the user
code is executed.
中断恢复时间定义为:执行完 ISR 中最后一句代码后到恢复到任务级代码的这段时间。
Interrupt
recovery is defined as the time required for the processor to return to the
interrupted code or to a higher priority task if the ISR made such a task ready
to run.
任务延迟时间定义为:中断发生到恢复到任务级代码的这段时间。
Task
latency is defined as the time it takes from the time the interrupt occurs to
the time task level code resumes.
目前市场上有很多种架构的 CPU,处理器通常有多个中断源。例如,UART 中断、DMA 中断、ADC 中断、定时器中断等。
There
are many popular CPU architectures on the market today, and most processors
typically handle interrupts from a multitude of sources. For example, a UART
receives a character, an Ethernet controller receives a packet, a DMA
controller completes a data transfer, an Analog-to-Digital Converter (ADC)
completes an analog conversion, a timer expires, etc.
在大多数情况下,中断控制器捕捉所有的中断提交给处理器,如图 9-1 所示
中断器件标志中断处理器,然后中断处理器将优先级最高的中断提交给 CPU。
In
most cases, an interrupt controller captures all of the different interrupts presented
to the processor as shown in Figure 9-1 (note that the “CPU Interrupt
Enable/Disable” is typically part of the CPU, but is shown here separately for
sake of the illustration).
Interrupting
devices signal the interrupt controller, which then prioritizes the interrupts
and presents the highest-priority interrupt to the CPU.

现在的中断控制器都是智能的,允许定义中断优先级,记住哪些中断还处于挂起状态。在大多数情况下,中断控制器会直接提供对应ISR 的向量地址给CPU。
Modern
interrupt controllers have built-in intelligence that enable the user to
prioritize interrupts, remember which interrupts are still pending and, in many
cases, have the interrupt controller provide the address of the ISR (also
called the vector address) directly to the CPU.
如果全局中断被关闭,CPU 就会忽视来自于中断控制器的中断请求,但这些请求会在中断控制器中被挂起直到 CPU 重新开启中断。
If
“global” interrupts (i.e., the switch in Figure 9-1) are disabled, the CPU will
ignore requests from the interrupt controller, but they will be held pending by
the interrupt controller until the CPU re-enables interrupts.
CPU 处理中断有两种模式:
CPUs
deal with interrupts using one of two models:
所有的中断指向同一个 ISR
All
interrupts vector to a single interrupt handler.
每个中断指向各自的 ISR
Each
interrupt vectors directly to an interrupt handler.
Before
discussing these two methods, it is important to understand how μC/OS-III
handles CPU interrupts.
进入中断时 CPU 需执行的一些步骤。其中(2)(3)(10)(11)是处理器自动完成的。(4)~(9)需用户在 ISR 中编写。
μC/OS-III
requires that an interrupt service routine be written in assembly language.
However, if a C compiler supports in-line assembly language, the ISR code can
be placed directly into a C source file. The pseudo-code for a typical ISR when
using μC/OS-III is shown in Listing 9-1.

L9-1(1)ISR 通常是用汇编写的,也可以用 C 语言编写。
L9-1(1) As mentioned above, an ISR is
typically written in assembly language. MyISR corresponds to the name of the
handler that will handle the interrupting device.
L9-2(2)在进入临界段之前,关中断。有些处理器进入中断服务程序时会自动关中断,有些则需要用户手动关中断。
L9-1(2) It is important that all
interrupts are disabled before going any further. Some processors have
interrupts disabled whenever an interrupt handler starts. Others require the
user to explicitly disable interrupts as shown here. This step may be tricky if
a processor supports different interrupt priority levels.
However,
there is always a way to solve the problem.
L9-3(3)中断服务程序首先要做的就是保存当前任务的上文到任务堆栈。有些 CPU 会自动将中断前的上文保存到中断堆栈,这样就节省了使用宝贵的任务堆栈。然而,对于 uC/OS-III,中断的上文需要被保存到任务堆栈。
L9-1(3) The first thing the interrupt
handler must do is save the context of the CPU onto the interrupted task’s
stack. On some processors, this occurs automatically. However, on most
processors it is important to know how to save the CPU registers onto the
task’s stack. Save the full “context” of the CPU, which may also include
Floating-Point Unit (FPU) registers if the CPU used is equipped with an FPU.
Certain
CPUs also automatically switch to a special stack just to process interrupts (i.e.,
an interrupt stack). This is generally beneficial as it avoids using up
valuable task stack space. However, for μC/OS-III, the context of the
interrupted task needs to be saved onto that task’s stack.
如果处理器没有专用于指向 ISR 的堆栈指针{也就是 R14"},那么它就需要用软件模拟。特别的,进入 ISR 时,保存当前的任务堆栈,然后将 R14"指向处理器的中断堆栈{也可以用户软件模拟}。
If
the processor does not have a dedicated stack pointer to handle ISRs then it is
possible to implement one in software. Specifically, upon entering the ISR,
simply save the current task stack, switch to a dedicated ISR stack, and when
done with the ISR switch back to the task stack.
当然,这意味着需要写一些附加的代码,这是非常有用的因为没必要再分配给任务堆栈额外的空间(嵌套层数很多情况下所需的堆栈空间)。
Of
course, this means that there is additional code to write, however the benefits
are enormous since it is not necessary to allocate extra space on the task
stacks to accommodate for worst case interrupt stack usage including interrupt
nesting.
L9-1(4)下一步,调用 OSIntEnter(),或将 OSIntNestingCtr 的值递增。将 OSIntNestingCtr 的值递增更高效。OSIntNestingCtr 保存了当前的嵌套层数。
L9-1(4) Next, either call
OSIntEnter(), or simply increment the variable OSIntNestingCtr in assembly
language. This is generally quite easy to do and is more efficient than calling
OSIntEnter(). As its name implies, OSIntNestingCtr keeps track of the interrupt
nesting level.
L9-1(5)如果这是嵌套的第一层,将中断前的任务的 TSP 保存到任务的OS_TCB。指针 OSTCBCurPtr 指向中断前的任务的 OS_TCB。
OSTCBCurPtr->StkPtr 保存为 OS_TCB 中偏移量为 0 的地址。
{OSTCBCurPtr->StkPtr 等价于 当前任务的 TCB->StkPtr}
L9-1(5) If this is the first nested
interrupt, save the current value of the stack pointer of the interrupted task
into its OS_TCB. The global pointer OSTCBCurPtr conveniently points to the
interrupted task’s OS_TCB. The very first field in OS_TCB is where the stack
pointer needs to be saved. In other words, OSTCBCurPtr->StkPtr happens to be
at offset 0 in the OS_TCB (this greatly simplifies assembly language).
L9-1(6)可以在此清除中断标志。
L9-1(6) At this point, clear the
interrupting device so that it does not generate another interrupt until it is
ready to do so. The user does not want the device to generate the same
interrupt if re-enabling interrupts (refer to the next step). However, most
people defer the clearing of the source and prefer to perform the action within
the user ISR handler in “C.”
L9-1(7)如果用户相应支持嵌套中断,重新开启中断。这个步骤是可选的。
L9-1(7) At this point, it is safe to
re-enable interrupts if the developer wants to support nested interrupts. This
step is optional.
L9-1(8)此时,可以调用用户函数。然而,ISR 越短越好。事实上,最好在 ISR 发送信号量或消息给任务,让任务处理中断时需要执行的大部分操作。
L9-1(8) At this point, further
processing can be deferred to a C function called from assembly language. This
is especially useful if there is a large amount of processing to do in the ISR
handler. However, as a general rule, keep the ISRs as short as possible. In
fact, it is best to simply signal or send a message to a task and let the task
handle the details of servicing the interrupting device.
ISR 可以调用 OSSemPost()、OSTaskSemPost()、OSFlagPost()、OSQPost()、OSTaskQPost()通知任务。ISR 越短越好。
The
ISR must call one of the following functions: OSSemPost(), OSTaskSemPost(),
OSFlagPost(), OSQPost() or OSTaskQPost(). This is necessary since the ISR will
notify a task, which will service the interrupting device. These are the only
functions able to be called from an ISR and they are used to signal or send a
message to a task. However, if the ISR does not need to call one of these
functions, consider writing the ISR as a “Short Interrupt Service Routine,” as
described in the next section.
L9-1(9)ISR 的工作完成后,用户必须调用 OSIntExit()告诉 uC/OS-III 中断服务程序已经完成。OSIntExit()中将 OSIntNestingCtr 递减,如果其值变为 0,就意味着 ISR 将会返回到任务级代码(否则返回前一层的中断)。中断服务中可能使能了比原任务更高优先级的任务。这时 uC/OS-III 选择优先级最高的就绪任务或返回原任务。
L9-1(9) When completing the ISR, the
user must call OSIntExit() to tell μC/OS-III that the ISR has completed.
OSIntExit() simply decrements OSIntNestingCtr and, if OSIntNestingCtr goes to
0, this indicates that the ISR will return to task-level code (instead of a
previously interrupted ISR). μC/OS-III will need to determine whether there is
a higher priority task that needs to run because of one of the nested ISRs. In
other words, the ISR might have signaled or sent a message to a higher-
priority task waiting for this signal or message. In this case, μC/OS-III will
context switch to this higher priority task instead of returning to the
interrupted task. In this latter case, OSIntExit() does not actually return,
but takes a different path.
L9-1(10)如果 ISR 使能了一个更低优先级的任务,OSIntExit()返回原任务。
L9-1(10) If the ISR signaled or sent a message to
a lower-priority task than the interrupted task, OSIntExit() returns. This
means that the interrupted task is still the highest-priority task to run and
it is important to restore the previously saved registers.
L9-1(11)ISR 返回,恢复原任务的状态。
L9-1(11) The ISR performs a return from interrupts
and so resumes the interrupted task.
NOTE:
From this point on, (1) to (6) will be referred to as the ISR Prologue and (9)
to (11) as the ISR Epilogue.
上述顺序的代码是假定 ISR 会发送信号量或消息给任务。然而,在很多情况下,ISR 不需要发送通知任务,而是在 ISR 中直接完成需要的工作(假定需要完成的工作代码较短)。在这种情况下,ISR 的结构如列表 9-2 所示
The
above sequence assumes that the ISR signals or sends a message to a task.
However, in many cases, the ISR may not need to notify a task and can simply
perform all of its work within the ISR (assuming it can be done quickly). In
this case, the ISR will appear as shown in Listing 9-2.

L9-2(1)正如上面提到的,ISR 通常是用汇编写的。
L9-2(1) As mentioned above, an ISR is
typically written in assembly language. MyShortISR corresponds to the name of
the handler that will handle the interrupting device.
L9-2(2)在这里,保存 CPU 寄存器。
L9-2(2) Here, save sufficient
registers as required to handle the ISR.
L9-2(3)清除中断标志位,防止 ISR 返回时产生同样的中断。
L9-2(3) The user may want to clear
the interrupting device to prevent it from generating the same interrupt once
the ISR returns.
L9-2(4)在这里不要重新开启中断。
L9-2(4) Do not re-enable interrupts
at this point since another interrupt could make μC/OS-III calls, forcing a
context switch to a higher-priority task. This means that the above ISR would
complete, but at a much later time.
L9-2(5)响应该中断需要的操作,调用用户程序。
L9-2(5) Now take care of the
interrupting device in assembly language or call a C function, if necessary.
L9-2(6)结束之后,重新恢复原任务的 CPU 寄存器。
L9-2(6) Once finished, simply restore
the saved CPU registers.
L9-2(7)返回到原任务。
L9-2(7) Perform a return from
interrupt to resume the interrupted task.
短 ISR 程序,如上所定义,是一个例外。它不遵循 uC/OS-III 的规则,所有 uC/OS-III 不知道 ISR 中发生了什么。
Short
ISRs, as described above, should be the exception and not the rule since
μC/OS-III has no way of knowing when these ISRs occur.
Even though an interrupt controller is present in most designs, some
CPUs still vector to a common interrupt handler, and an ISR queries the
interrupt controller to determine the source of the interrupt. At first glance,
this might seem silly since most interrupt controllers are able to force the
CPU to jump directly to the proper interrupt handler. It turns out, however,
that for μC/OS-III, it is easier to have the interrupt controller vector to a
single ISR handler than to vector to a unique ISR handler for each source.
Listing 9-3 describes the sequence of events to be performed when the interrupt
controller forces the CPU to vector to a single location.
An interrupt occurs;
(1)
The CPU vectors to a common
location;
(2)
The ISR code performs the “ISR
prologue”
(3)
The C handler performs the
following:
(4)
while (there are still interrupts to
process) {
(5)
Get vector address from interrupt
controller;
Call
interrupt handler;
}
The “ISR epilogue” is executed;
(6)
Listing 9-3 Single interrupt vector for all interrupts
|
L9-3(1) |
An interrupt occurs from any device.
The interrupt controller activates the interrupt pin on the CPU. If there are
other interrupts that occur after the first one, the interrupt controller
will latch them and properly prioritize the interrupts. |
|
L9-3(2) |
The CPU vectors to a single interrupt
handler address. In other words, all interrupts are to be handled by this one
interrupt handler. |
|
L9-3(3) |
Execute the “ISR prologue” code needed by
μC/OS-III. as previously described. This ensures that all ISRs will be able to
make μC/OS-III “post” calls. |
|
L9-3(4) |
Call a μC/OS-III C handler, which will
continue processing the ISR. This makes the code easier to write (and read).
Notice that interrupts are not re-enabled. |
|
L9-3(5) |
The μC/OS-III C handler then
interrogates the interrupt controller and asks it: “Who caused the
interrupt?” The interrupt controller will either respond with a number (1 to
N) or with the address of the interrupt handler for the interrupting device.
Of course, the μC/OS-III C handler will know how to handle the specific
interrupt controller since the C handler is written specifically for that
controller. |
If the interrupt controller provides a number between 1 and N, the C handler
simply uses this number as an index into a table (in ROM or RAM) containing the
address of the interrupt service routine servicing the interrupting device. A
RAM table is handy to change interrupt handlers at run-time. For many embedded
systems, however, the table may also reside in ROM.
If the interrupt controller responds with the address of the
interrupt service routine, the C handler only needs to call this function.
In both of the above cases, all interrupt handlers need to be
declared as follows:
void
MyISRHandler (void);
There is one such handler for each possible interrupt source
(obviously, each having a unique name).
The “while” loop terminates when there are no other interrupting devices to
service.
L9-3(6) The μC/OS-III “ISR epilogue” is executed to see if it is
necessary to return to the interrupted task, or switch to a more important one.
A couple of interesting points to notice:
■ If another device caused an interrupt before the C handler had a
chance to query the interrupt controller, most likely the interrupt controller
will capture that interrupt. In fact, if that second device happens to be a
higher-priority interrupting device, it will most likely be serviced first, as
the interrupt controller will prioritize the interrupts.
■ The loop will not terminate until all pending interrupts are
serviced. This is similar to allowing nested interrupts, but better, since it
is not necessary to redo the ISR prologue and epilogue.
The disadvantage of this method is that a high priority interrupt
that occurs after the servicing of another interrupt that has already started
must wait for that interrupt to complete before it will be serviced. So, the
latency of any interrupt, regardless of priority, can be as long as it takes to
process the longest interrrupt.
中断向量控制器中的中断向量都指向对应的 ISR 地址。ISR 中的程序尽可能用汇编写(如章节 9-2 所示)。当然,这样会导致编程的复杂化。然而,其中大部分的代码都是从另一个 ISR 中复制和黏贴的。
If
the interrupt controller vectors directly to the appropriate interrupt handler,
each of the ISRs must be written in assembly language as described in section
9-2 “Typical μC/OS-III Interrupt Service Routine (ISR)” on page 159. This, of
course, slightly complicates the design. However, copy and paste the majority
of the code from one handler to the other and just change what is specific to
the actual device.
除了一些用户要改变的代码。
If
the interrupt controller allows the user to query it for the source of the
interrupt, it may be possible to simulate the mode in which all interrupts
vector to the same location by simply setting all vectors to point to the same
location. Most interrupt controllers that vector to a unique location, however,
do not allow users to query it for the source of the interrupt since, by
definition, having a unique vector for all interrupting devices should not be
necessary.
uC/OS-III 有两种方法处理来自于中断的时间;直接提交和延迟提交。通过 OS_CFH.H 中的 OS_CFG_ISR_POST_DEFERRED_EN 来选择。当设置为 0 时,uC/OS-III 使用直接提交方法。 当设置为 1 时,使用推迟提交方法。
根据应用选择一种方法。
μC/OS-III
handles event posting from interrupts using two different methods: Direct and
Deferred Post. The method used in the application is selected by changing the
value of OS_CFG_ISR_POST_DEFERRED_EN in OS_CFG.H (this assumes you have access
to μC/OS-III’s source code). When set to 0, μC/OS-III uses the Direct Post
Method and when set to 1, μC/OS-III uses the Deferred Post Method.
As
far as application code and ISRs are concerned, these two methods are
completely transparent. It is not necessary to change anything except the
configuration value OS_CFG_ISR_POST_DEFERRED_EN to switch between the two
methods. Of course, changing the configuration constant will require
recompiling the product and μC/OS-III.
Before
explaining why to use one versus the other, let us review their differences.
9-6-1 直接提交
图 9-2 显示了直接提交方法的过程。
The
Direct Post Method is used by μC/OS-II and is replicated in μC/OS-III. Figure
9-2 shows a task diagram of what takes place in a Direct Post.

F9-2(1)中断产生
F9-2(1) A device generates an
interrupt.
F9-2(2)中断服务程序开始执行。通常中断中包含着任务等待的事件。
F9-2(2) The Interrupt Service Routine
(ISR) responsible to handle the device executes (assuming interrupts are
enabled). The device interrupt is generally the event a task is waiting for.
The task waiting for this interrupt to occur either has a higher priority than
the interrupted task, or lower (or equal) in priority.
F9-2(3)若 ISR 使能了低于或等于原任务优先级的任务,ISR 结束时,uC/OS-III 返回原任务并从被中断处继续执行。
F9-2(3) If the ISR made a lower (or
equal) priority task ready to run then upon completion of the ISR, μC/OS-III
returns to the interrupted task exactly at the point the interrupt occurred.
F9-2(4)若 ISR 使能了高于原任务优先级的任务,ISR 结束后,进入调度。uC/OS-III 切换到高优先级任务。
F9-2(4) If the ISR made a higher
priority task ready to run, μC/OS-III will context switch to the new
higher-priority task since the more important task was waiting for this device
interrupt.
F9-2(5)在直接提交方法中,uC/OS-III 必须保护好临界段(通过关中断方式)。
F9-2(5) In the Direct Post Method,
μC/OS-III must protect critical sections by disabling interrupts as some of
these critical sections can be accessed by ISRs.
然而,如果用户程序调用某些 uC/OS-III 服务时,中断可能被关闭。当设置 OS_CFG_ISR_POST_DEFERRED_EN 为 0 时,在遇到临界段代码时 uC/OS-III 会关闭中断。这样,中断就可能不被响应直到 uC/OS-III 重新开中断。
The
above discussion assumed that interrupts were enabled and that the ISR could
respond quickly to the interrupting device. However, if the application code
makes μC/OS-III service calls (and it will at some point), it is possible that
interrupts would be disabled. When OS_CFG_ISR_POST_DEFERRED_EN is set to 0,
μC/OS-III disables interrupts while accessing critical sections. Thus,
interrupts will not be responded to until μC/OS-III re-enables interrupts. Of
course, attempts were made to keep interrupt disable times as short as
possible, but there are complex features of μC/OS-III that disable interrupts
for a longer period than the user would like.
直接提交方法的一个重要的因素是 uC/OS-III 关中断的时间。这个容易被确定因为 uC/CPU 中提供了测量最大关中断时间的功能。这些代码可以被使能用于测试。测量时,让应用程序运行够长的时间并读取变量CPU_IntDisMeasMaxRaw_cnts。这个变量的精度取决于用于测量的定时器速度。
The
key factor in determining whether to use the Direct Post Method is generally
the μC/OS-III interrupt disable time. This is fairly easy to determine since
the μC/CPU files provided with the μC/OS-III port for the processor used
includes code to measure maximum interrupt disable time. This code can be
enabled (assumes you have the source code) for testing purposes and removed
when ready to deploy the product. The user would typically not want to leave
measurement code in production code to avoid introducing measurement artifacts.
Once instrumented, let the application run for sufficiently long and read the
variable CPU_IntDisMeasMaxRaw_cnts. The resolution (in time) of this variable
depends on the timer used during the measurement.
中断延迟时间、中断响应时间 、中断恢复时间、任务延迟时间的计算代码如下:
Determine
the interrupt latency, interrupt response, interrupt recovery, and task latency
by adding the execution times of the code involved for each, as shown below.
中断延迟 = uC/OS-III 的最大关中断时间;
{假定中断在 uC/OS-III 中刚进入临界段时发生}
中断响应 = 中断延迟 + 转向 ISR 时间 + ISR 的前序时间
中断恢复=处理中断器件+发送信号量或消息给任务+OSIntExit()+OSIntCtxSw()
任务延迟 = 中断响应 + 中断恢复 + 锁调度器时间
|
Interrupt Latency = |
Maximum
interrupt disable time; |
|
|
Interrupt Response = |
Interrupt
latency +
Vectoring to the interrupt handler +
ISR prologue; |
|
|
Interrupt Recovery |
= |
Handling
of the interrupting device + Posting a signal or a message
to a task +
OSIntExit() +
OSIntCtxSw(); |
|
Task Latency |
= |
Interrupt
response +
Interrupt recovery +
Time scheduler is locked; |
uC/OS-III 的 ISR 前序,ISR 后序,OSIntExit(),OSIntCtxSw()操作的时间可以被测量。
The execution
times of the μC/OS-III ISR prologue, ISR epilogue, OSIntExit(), and
OSIntCtxSw(), can be measured independently and should be fairly constant.
调用 OS_TS_GET()也可以测量“post”函数的处理时间。
It
should also be easy to measure the execution time of a post call by using
OS_TS_GET().
在直接提交方式中,只有在处理定时器任务时才会锁调度器。如果定时器任务中创建的定时器不多的话,任务延迟将会很短。uC/OS-III 也可以测量锁调度器时间。
In
the Direct Post Method, the scheduler is locked only when handling timers and
therefore, task latency should be fast if there are not too many timers with
short callbacks expiring at the same time. See Chapter 12, “Timer Management”
on page 193. μC/OS-III is also able to measure the amount of time the scheduler
is locked, providing task latency.
9-6-2 延迟提交方式
延迟提交方式(通过设置 OS_CFG_ISR_POST_DEFERRED_EN为 1),代替了关中断方式用于处理临界段。uC/OS-III 锁住调度器,这可以避免其它任务访问临界段代码。延迟提交方式中,中断几乎没被关闭。然而,该方式有点复杂。如图 9-3 所示
In
the Deferred Post Method (OS_CFG_ISR_POST_DEFERRED_EN is set to 1), instead of
disabling interrupts to access critical sections, μC/OS-III locks the
scheduler. This avoids having other tasks access critical sections while
allowing interrupts to be recognized and serviced. In the Deferred Post Method,
interrupts‘ are almost never disabled. The Deferred Post Method is, however, a
bit more complex as shown in Figure 9-3.

F9-3(1)中断产生
F9-3(1) A device generates an
interrupt.
F9-3(2)ISR 被执行。
F9-3(2) The ISR responsible for handling
the device executes (assuming interrupts are enabled). The device interrupt is
the event that a task was waiting for. The task waiting for this interrupt to
occur is either higher in priority than the interrupted task, lower, or equal
in priority.
F9-3(3)ISR 中调用"post"函数发送信号量或消息给任务。然而,它不是直接发送给任务,而是先发送到中断队列。然后中断处理函数被就绪。这是 uC/OS-III 的内部任务且具有最高优先级(优先级为 0)。
F9-3(3) The ISR calls one of the post
services to signal or send a message to a task. However, instead of performing
the post operation, the ISR queues the actual post call along with arguments in
a special queue called the Interrupt Queue. The ISR then makes the Interrupt
Queue Handler Task ready to run. This task is internal to μC/OS-III and is
always the highest priority task (i.e., Priority 0).
F9-3(4)ISR 的最后,uC/OS-III 会切换到中断处理任务,它将中断队列中消息发送给任务。在此,我们关闭中断防止在处理中断队列时被另外的中断程序打断。最后,该任务使能中断,锁住调度器,提交信息。信息是在任务级被提交的。这样,就是在任务级完成临界段的处理了。
F9-3(4) At the end of the ISR,
μC/OS-III always context switches to the interrupt queue handler task, which
then extracts the post command from the queue. We disable interrupts to prevent
another interrupt from accessing the interrupt queue while the queue is being
emptied. The task then re-enables interrupts, locks the scheduler, and performs
the post call as if the post was performed at the task level all along. This
effectively manipulates critical sections at the task level.
F9-3(5)当中断处理任务清空中断队列时,它就会将自己停止,重
新开启调度器,调用调度器选择下一个任务运行。
F9-3(5) When the interrupt queue
handler task empties the interrupt queue, it makes itself not ready to run and
then calls the scheduler to determine which task must run next. If the original
interrupted task is still the highest priority task, μC/OS-III will resume that
task.
F9-3(6)如果优先级更高的任务被使能,uC/OS-III 会上换到该任务。
F9-3(6) If, however, a more important
task was made ready to run because of the post, μC/OS-III will context switch
to that task.
执行一些额外的处理,在处理临界段时可以避免关中断。额外的处理只包括调用"post"函数,将消息或信号量插入队列,并从队列中提取,并执行额外的上下文切换。
All
the extra processing is performed to avoid disabling interrupts during critical
sections of code. The extra processing time only consist of copying the post
call and arguments into the queue, extracting it back out of the queue, and
performing an extra context switch.
类似于直接提交方式,
Similar
to the Direct Post Method, it is easy to determine interrupt latency, interrupt
response, interrupt recovery, and task latency, by adding execution times of
the pieces of code involved for each as shown below.
中断延迟 = uC/OS-III 的最大关中断时间;
中断响应 = 中断延迟+ 转向 ISR 时间 + ISR 的前序时间
中断恢复 = 处理中断器件+ 发送信号量或消息给中断队列 + OSIntExit()
+ OSIntCtxSw()切换到中断队列处理任务
任务延迟 = 中断响应+ 中断恢复+ 将信号量或消息提交给任务
+ 上下文切换到任务 + 锁调度器时间
|
Interrupt Latency = |
Maximum
interrupt disable time; |
|
Interrupt Response = |
Interrupt
latency +
Vectoring to the interrupt handler +
ISR prologue; |
|
Interrupt Recovery = |
Handling
of the interrupting device + Posting a signal or a message
to the Interrupt Queue |
+ OSIntExit()
+ OSIntCtxSw() to Interrupt Queue Handler Task;
Task Latency = Interrupt
response
+ Interrupt recovery
+ Re-issue the post to the object or task
+ Context switch to task
+ Time scheduler is locked;
事实上,延迟提交方式中的"post"函数会很短,因为这包括复制"post" 函数和该函数的参数到中断队列中。
The execution
times of the μC/OS-III ISR prologue, ISR epilogue, OSIntExit(), and
OSIntCtxSw(), can be measured independently and should be constant.
It
should also be easy to measure the execution time of a post call by using
OS_TS_GET(). In fact, the post calls should be short in the Deferred Post
Method because it only involves copying the post call and its arguments into
the interrupt queue.
不同的是,延迟提交方式中关中断的时间是非常短的。然而,任务延迟时间变长因为 uC/OS-III 会锁住调度器,并访问临界段。
The
difference is that in the Deferred Post Method, interrupts are disabled for a
very short amount of time and thus, the first three metrics should be fast.
However, task latency is higher as μC/OS-III locks the scheduler to access
critical sections.
在直接提交方式中,uC/OS-III 访问临界段时关中断。然而,在延迟提交方式中,uC/OS-III 访问临界段时锁调度器。
In
the Direct Post Method, μC/OS-III disables interrupts to access critical
sections. In comparison, while in the Deferred Post Method, μC/OS-III locks the
scheduler to access the same critical sections.
在延迟提交方式中,访问中断队列时 uC/OS-III 需要关中断。然而,这段关中断时间是非常短的且是相当固定的。
In
the Deferred Post Method, μC/OS-III must still disable interrupts to access the
interrupt queue. However, the interrupt disable time is very short and fairly
constant.

如果在应用中关中断时间是关键性的,因为应用中有非常频繁的中断源,且应用不能接受直接提交方式那样较长的关中断时间。推荐使用延迟提交方式。
If
interrupt disable time is critical in the application because there are very
fast interrupt sources and the interrupt disable time of μC/OS-III is not
acceptable using the Direct Post Method, use the Deferred Post Method.
然而,如果你想要使用表 9-1 的一些功能,那么就考虑用延迟提交方式。
However,
if you are planning on using the features listed in Table 9-1, consider using
the Deferred Post Method, described in the next section.
|
功能 |
原因 |
|
多个任务具有相同优先级 Multiple
tasks at the same priority |
这
uC/OS-III 的一个重要功能,多个任务具有相关优先级会产生一个长临界段。然而,当很少任务具有相同优先级时,中断延时会很小。当多任务具有不同优先级时,可以用关中断方法。 Although
this is an important feature of μC/OS-III, multiple
tasks at the same priority create longer critical sections. However, if there
are only a few tasks at the same priority, interrupt latency will be
relatively small. If the user does not create multiple tasks at the same
priority, the Direct Post Method is recommended. |
|
事件标志组详见章节
14“同步” Event
Flags Chapter
14, Synchronization” on page 251 |
如果多个任务等待不同的事件,遍历这些等待事件的任务需要很多处理时间,意味着会产生长临界段。如果很少的任务在等待事件,临界段会很短,那么可以使用关中断的方法处理。 If
multiple tasks are waiting on different events, going through all of the tasks
waiting for events requires a fair amount of processing time, which means
longer critical sections. If
only a few tasks (approximately one to five) are waiting on an event flag
group, the critical section will be short enough to use the Direct Post Method. |
|
任务等待多个对象 详见章节 16 Pend
on multiple objects Chapter
16,“Pending On Multiple Objects” on page 313 |
挂起多个对象是
uC/OS-III 提供的很复杂的功能,它需要很长的临界段。如果这样,推荐使用关调度器的方法。如果挂起较少,关中断也是可以的。 Pending
on multiple objects is probably the most complex feature provided by μC/OS-III and requires interrupts to be disabled for fairly long
periods of time when using the Direct Post Method. If
pending on multiple objects, the Deferred Post Method is highly recommended. If
the application does not use this feature, the user may select the Direct
Post Method. |
|
广播消息详见附录
A 中的 Broadcast
on Post calls OSSemPost()和 OSQPost() |
uC/OS-III 在处理广播消息时通过关调度器保护临界段。当不使用广播功能的时候,你可以用关中断方法 μC/OS-III disables interrupts while processing a post to multiple
tasks in a broadcast.If not using the broadcast option, use the Direct Post
Method.Note that broadcasts only apply to semaphores and message queues. |
表 9-1 当使用直接提交方式时避免使用这些功能
uC/OS-III 需要一个能提供周期性时间的时基源,叫做系统时基。
μC/OS-III-based
systems generally require the presence of a periodic time source called the
clock tick or system tick.
硬件定时器可以被设置为每秒产生 10 到 1000Hz 的中断提供系统时基。也可以从交流电中获得 50Hz 到 60Hz 的时基源。事实上,也可以从交流电中获得 100Hz 到 120Hz 的过零点作为时基。
A
hardware timer configured to generate an interrupt at a rate between 10 and
1000 Hz provides the clock tick. A tick source may also be obtained by
generating an interrupt from an AC power line (typically 50 or 60 Hz). In fact,
one can easily derive 100 or 120 Hz by detecting zero crossings of the power
line.
时基可以看做是系统的心跳。它的速率决定于时基源。然而,时基速率越快,系统的额外支出就越大。
The
clock tick interrupt can be viewed as the system’s heartbeat. The rate is
application specific and depends on the desired resolution of this time source.
However, the faster the tick rate, the higher the overhead imposed on the
system.
The
clock tick interrupt allows μC/OS-III to delay tasks for an integral number of
clock ticks and provide timeouts when tasks are waiting for events to occur.

时基中断程序必须调用 OSTimeTick()。OSTimeTick()的伪代码如列表 9-4 所示。
The
clock tick interrupt must call OSTimeTick(). The pseudocode for OSTimeTick() is
shown in Listing 9-4.
L9-4(1)时基中断程序首先调用一个 hook 函数,OSTimeTickHook()。它允许当时基中断出现时用户扩展一些操作。Hook 函数在时基中断发生时首先被调用的,用它访问一些周期性元素。如读取传感器(需被周期性检测)、更新 PWM 寄存器等等。
L9-4(1) The time tick ISR starts by
calling a hook function, OSTimeTickHook(). The hook function allows the
implementer of the μC/OS-III port to perform additional processing when a tick
interrupt occurs. In turn, the tick hook can call a user-defined tick hook if
its corresponding pointer, OS_AppTimeTickHookPtr, is non-NULL. The reason the
hook is called first is to give the application immediate access to this
periodic time source. This can be useful to read sensors at a regular interval
(not as subject to jitter), update Pulse Width Modulation (PWM) registers, and
more.
L9-4(2)如果 uC/OS-III 被设置为延迟提交方式,uC/OS-III 读取当前时间戳并放置到中断处理队列。然后中断队列处理任务发送信号量给时基任务。
L9-4(2) If μC/OS-III is configured
for the Deferred Post Method, μC/OS-III reads the current timestamp and defers
the call to signal the tick task by placing an appropriate entry in the
interrupt queue. The tick task will thus be signaled by the Interrupt Queue
Handler Task.
L9-4(3)如果 uC/OS-III 被设置为直接提交方式,时基 ISR 直接发送信号量给时基任务。
L9-4(3) If μC/OS-III is configured
for the Direct Post Method, μC/OS-III signals the tick task so that it can
process the time delays and timeouts.
L9-4(4)使用循环轮转算法检测是否当前任务是否期满。
L9-4(4) μC/OS-III runs the
round-robin scheduling algorithm to determine whether the time slot for the
current task has expired.
L9-4(5)标记定时器任务,定时器任务也用时基任务的时基作为基准。
L9-4(5) The tick task is also used as
the time base for the timers (see Chapter 13, “Resource Management” on page
209).
uC/OS-III 必须有系统时基是普遍的误解。事实上,很多低功耗应用中没有系统时基,因为需额外的能量用于维护时基源。换句话说,将能量用于维护时基源是不合理的。因为 uC/OS-III 是一个可抢占式内核,一个事件可以唤醒进入低功耗模式处理器(按键或其它事件)。没有时基意味着用户不能再对任务进行延时或超时设置。用户在研发低功耗产品时可以考虑这个特性。
A
common misconception is that a system tick is always needed with μC/OS-III. In
fact, many low-power applications may not implement the system tick because of
the power required to maintain the tick list. In other words, it is not
reasonable to continuously power down and power up the product just to maintain
the system tick. Since μC/OS-III is a preemptive kernel, an event other than a
tick interrupt can wake up a system placed in low power mode by either a
keystroke from a keypad or other means. Not having a system tick means that the
user is not allowed to use time delays and timeouts on system calls. This is a
decision required to be made by the designer of the low-power product.
uC/OS-III 提供管理中断的服务。ISR 应该越短越好,发送信号量或消息给任务,让该任务实现该中断所需的大部分操作。即尽量让操作在任务级完成。
μC/OS-III
provides services to manage interrupts. An ISR should be short in length, and
signal or send a message to a task, which is responsible for servicing the
interrupting device.
ISR 也可以不发送信号量或消息给任务。ISR 自己处理响应操作。但 ISR 越短越好。
ISRs
that are short and do not need to signal or send a message to a task, are not
required to do so.
uC/OS-III 允许处理器中断时指向同一个 ISR,或是指向各自的ISR。
μC/OS-III
supports processors that vector to a single ISR for all interrupting devices,
or to a unique ISR for each device.
uC/OS-III 支持两种方式:直接提交方式和延迟提交方式。直接提交方式需要关中断保护临界段。延迟提交方式需要关调度器保护临界段。
μC/OS-III
supports two methods: Direct and Deferred Post. The Direct Post Method assumes
that μC/OS-III critical sections are protected by disabling interrupts. The
Deferred Post Method locks the scheduler when μC/OS-III accesses critical
sections of code.
uC/OS-III 需要时基源,用于任务的延时和超时功能。
μC/OS-III
assumes the presence of a periodic time source for applications requiring time
delays and timeouts on certain services.
当任务等待信号量、mutex、事件标志组、消息队列时,该任务会被放入挂起队列。
A task
is placed in a Pend List (also called a Wait List) when it is waiting on a
semaphore to be signaled, a mutual exclusion semaphore to be released, an event
flag group to be posted, or a message queue to be posted.
|
参见 |
挂起队列 |
内核对象 |
|
章节 13“资源管理” |
信号量、mutex |
OS_SEM、OS_MUTEX |
|
章节 14“同步” |
信号量、事件标志组 |
OS_SEM、OS_FLAG_GRP |
|
章节 15“消息提交” |
消息队列 |
OS_Q |
表 10-1 挂起队列与内核对象
挂起队列类似于就绪队列,挂起队列中放的是等待内核对象的任务。另外,任务在挂起队列中是根据优先级分类的。高优先级任务被放置在队列的头部,低优先级任务被放置在队列的尾部。
A
pend list is similar to the Ready List, except that instead of keeping track of
tasks that are ready-to-run, the pend list keeps track of tasks waiting for an
object to be posted. In addition, the pend list is sorted by priority; the
highest priority task waiting on the object is placed at the head of the list,
and the lowest priority task waiting on the object is placed at the end of the
list.
挂起队列是一个 OS_PEND_LIST 类型的数据结构,它包含了三部分内容,如图 10-1
A
pend list is a data structure of type OS_PEND_LIST, which consists of three
fields as shown in Figure 10-1.

.NbrEntries 挂起队列中有几个任务。
.NbrEntries
Contains the current number of entries in the pend list. Each entry in the pend
list points to a task that is waiting for the kernel object to be posted.
.TailPtr 指向队列的尾部(最低优先级的任务) .HeadPtr 指向队列的首部(最高优先级的任务)
.TailPtr Is a pointer to the
last task in the list (i.e., the lowest priority task). .HeadPtr Is a pointer to
the first task in the list (i.e., the highest priority task).
图 10-2 显示了每个内核对象中的包含了三个相同的部分,我们把这三个部分命名为 OS_PEND_OBJ。注意的是,第一个部分“Type” 用于区别这个内核对象的类型:信号量、mutex、事件标记组、消息队列。
Figure
10-2 indicates that each kernel object using a pend list contains the same
three fields at the beginning of the kernel object that we called an
OS_PEND_OBJ. Notice that the first field is always a “Type” which allows
μC/OS-III to know if the kernel object is a semaphore, a mutual exclusion semaphore,
an event flag group, or a message queue object.

表 10-2 显示了当内核对象被创建时"Type"部分被设置的值。调试时可以方便用户识别内核对象的类型。
Table
10-2 shows that the “Type” field of each of the above objects is initialized to
contain four ASCII characters when the respective object is created. This
allows the user to identify these objects when performing a memory dump using a
debugger.

事实上,挂起队列中不是指向任务的 OS_TCB ,而是指向 OS_PEND_DATA。如图 10-3 所示。当任务被放入挂起队列时,OS_PEND_DATA 结构体被动态地分配到该任务的堆栈中。这意味着任务堆栈中需有足够的空间用于存储这个结构体。
A
pend list does not actually point to a task’s OS_TCB, but instead points to
OS_PEND_DATA objects as shown in Figure 10-3. Also, an OS_PEND_DATA structure
is allocated dynamically on the current task’s stack when a task is placed on a
pend list. This implies that a task stack needs to be able to allocate storage
for this data structure.

.PrevPtr 它指向正在队列中等待的高优先级或同等优先级的任务。
.PrevPtr Is a pointer to an
OS_PEND_DATA entry in the pend list. This pointer points to a higher or equal
priority task waiting on the kernel object.
.NextPtr 它指向正在队列中等待的低优先级的任务。
.NextPtr Is a pointer to an OS_PEND_DATA
entry in the pend list. This pointer points to a lower or equal priority task
waiting on the kernel object.
.TCBPtr 指向该任务的 OS_TCB。
.TCBPtr Is a pointer to the OS_TCB of the
task waiting on the pend list.
.PendObjPtr 指向任务所等待的内核对象。换句话说,这个指针可以指向 OS_SEM,OS_MUTEX,OS_FLAG_GRP,OS_Q。(内核对象中都有一个相同的结构体 OS_PEND_OBJ)
.PendObjPtr Is a pointer to the kernel object
that the task is pending on. In other words, this pointer can point to an
OS_SEM, OS_MUTEX, OS_FLAG_GRP or OS_Q by using an OS_PEND_OBJ as the common
data structure.
.RdyObjPtr 如果任务等待多个内核对象,该指针指向任务被放入挂起列表前已经被提交的内核对象(详见章节 16“挂起多个对象”)。
.RdyObjPtr Is a pointer to the kernel
object that is ready if the task actually waits for multiple kernel objects.
See Chapter 16, “Pending On Multiple Objects” on page 313 for more on this.
.RdyMsgPtr 如果任务等待多个内核对象,该指针指向任务被放入挂起列表后被提交的内核对象(详见章节 16“挂起多个对象”)。
.RdyMsgPtr Is a pointer to the message posted
through OSQPost() if the task is pending on multiple kernel objects. Again, see
Chapter 16, “Pending On Multiple Objects” on page 313.
.RdyTS 它存储了内核对象被提交时的时间戳。任务等待多个内核对象时会用到这个变量。(详见章节 16“挂起多个对象”)。
.RdyTS Is a timestamp of when
the kernel object was posted. This is used when a task pends on multiple kernel
objects as described in Chapter 16, “Pending On Multiple Objects” on page 313.
图 10-4 展示了当任务被插入挂起队列时,OS_PEND_DATA 结构体之间是相互联系的。该图假定两个任务等待同一个信号量。
Figure
10-4 exhibits how all data structures connect to each other when tasks are
inserted in a pend list. This drawing assumes that there are two tasks waiting
on a semaphore.

F10-4 ( 1 )结构体 OS_SEM 中包含了一个 OS_PEND_OBJ 。OS_PEND_OBJ 中包含了 OS_PEND_LIST,其中.NbrEntries 值为 2表示有两个任务正在等待该信号量。
F10-4(1) The OS_SEM data type contains an
OS_PEND_OBJ, which in turn contains an OS_PEND_LIST. The .NbrEntries field in
the pend list indicates that there are two tasks waiting on the semaphore.
F10-4(2).HeadPtr 指向高优先级任务的结构体 OS_PEND_DATA。
F10-4(2) The .HeadPtr field of the pend list
points to the OS_PEND_DATA structure associated with the highest priority task
waiting on the semaphore.
F10-4(3).TailPtr 指向低优先级任务的结构体 OS_PEND_DATA。
F10-4(3) The .TailPtr field of the pend list
points to the OS_PEND_DATA structure associated with the lowest priority task
waiting on the semaphore.
F10-4(4)这两个任务的结构体 OS_PEND_DATA 中分别通过指针 PendObjPtr 指回 OS_SEM 结构体。该指针认为自己指向的是 OS_PEND_OBJ{也就是该指针是以 OS_PEND_OBJ 类型定义的}。通过检查 OS_PEND_OBJ 中的.Type 就可以知道该内核对象是信号量。
F10-4(4) Both OS_PEND_DATA structures in turn
point back to the OS_SEM data structure. The pointers think they are pointing
to an OS_PEND_OBJ. We know that the OS_PEND_OBJ is a semaphore by examining the
.Type field of the OS_PEND_OBJ.
F10-4(5)每个 OS_PEND_DATA 结构体分别指向各自的 OS_TCB。换句话说,这样可以知道哪个任务在等待这个信号量。
F10-4(5) Each OS_PEND_DATA structure points to its
respective OS_TCB. In other words, we know which task is pending on the
semaphore.
F10-4(6)每个任务指针指回 OS_PEND_DATA 结构体。
F10-4(6) Each task points back to the OS_PEND_DATA
structure.
F10-4(7)最后,OS_PEND_DATA 结构体组织成一个双向列表, uC/OS-III 就可以方便地添加或移除列表中的记录。
F10-4(7) Finally, the OS_PEND_DATA structure forms
a doubly linked list so that the μC/OS-III can easily add or remove entries in
this list.
表 10-3 显示了 uC/OS-III 用于维护挂起队列的函数。这些函数都是 uC/OS-III 的内部函数(代码在 OS_CORE.C 中),用户不能调用它们。
Although
this may seem complex, the reasoning will become apparent in Chapter 16,
“Pending On Multiple Objects” on page 313. For now, assume all of the links are
necessary.
Table
10-3 shows the functions that μC/OS-III uses to manipulate entries in a pend
list. These functions are internal to μC/OS-III and the application code must
never call them.
The
code is found in OS_CORE.C.
|
函数 |
功能 |
|
OS_PendListChangePrio() |
改变挂起队列中的任务的优先级 Change
the priority of a task in a pend list |
|
OS_PendListInit() |
初始化挂起队列 Initialize
a pend list |
|
OS_PendListInsertHead() |
插入一个
OS_PEND_DATA 到队首 Insert
an OS_PEND_DATA at the head of the pend list |
|
OS_PendListInsertPrio() |
根 据 优 先 级 插 入 一 个OS_PEND_DATA到队列 Insert
an OS_PEND_DATA in priority order in the pend list |
|
OS_PendListRemove() |
队列中移除多个
OS_PEND_DATA Remove
multiple OS_PEND_DATA from the pend list |
|
OS_PendListRemove1() |
队列中移除一个
OS_PEND_DATA Remove
single OS_PEND_DATA from the pend list |
表 10-3 挂起队列相关函数
uC/OS-III 将等待信号量、mutex、事件标志组、消息队列的任务存储到挂起队列中。
μC/OS-III
keeps track of tasks waiting for semaphores, mutual exclusion semaphores, event
flag groups and message queues using pend lists.
挂起队列中包括数据结构 OS_PEND_LIST。它包含在另一个叫做OS_PEND_OBJ 的数据结构中任务不是直接链接到挂起队列中,而是通过叫做 OS_PEND_DATA 的数据结构作为媒介。这个媒介在任务被放入挂起队列中分配到任务堆栈的。
A
pend list consists of a data structure of type OS_PEND_LIST. The pend list is
further encapsulated into another data type called an OS_PEND_OBJ.
用户代码不能访问挂起队列,必须调用 uC/OS-III 提供的函数。
Tasks
are not directly linked to the pend list but instead are linked through an
intermediate data structure called an OS_PEND_DATA which is allocated on the
stack of the task waiting on the kernel object.
Application
code must not access pend lists, since these are internal to μC/OS-III.
uC/OS-III 为用户提供了与时间管理相关的服务。
μC/OS-III
provides time-related services to the application programmer.
在第 9 章“中断管理”中,在 uC/OS-III 中设置了能提供时基中断的中断源。该中断源提供 10Hz 到 1000Hz 之间的中断(需设置 OS_CFG_APP.H 中的 OS_CFG_TICK_RATE_HZ 为中断源提供的频率)。然而,频率越高,CPU 额外的消耗就越多。
In
Chapter 9, “Interrupt Management” on page 157, it was established that
μC/OS-III generally requires (as do most kernels) that the user provide a
periodic interrupt to keep track of time delays and timeouts. This periodic
time source is called a clock tick and should occur between 10 and 1000 times per
second, or Hertz (see OS_CFG_TICK_RATE_HZ in OS_CFG_APP.H). The actual
frequency of the clock tick depends on the desired tick resolution of the
application. However, the higher the frequency of the ticker, the higher the
overhead.
uC/OS-III 提供了一些与时间相关的函数如表 11-1,这些代码在OS_TIME.C 中
μC/OS-III
provides a number of services to manage time as summarized in Table 11-1, and
the code is found in OS_TIME.C.
|
函数名 |
功能 |
|
OSTimeDly() |
延时执行任务 n 个时基 |
|
OSTimeDlyHMSM() |
延时执行任务 HH:MM:SS.mmm |
|
OSTimeDlyResume() |
恢复处于延时状态的任务 |
|
OSTimeGet() |
获得当前的时基计数值 |
|
OSTimeGet() |
设置当前的时基计数值 |
|
OSTimeTick() |
用于标记,表示发生了一个时基中断 |
表 11-1 时间服务相关的 API 总结
详见附录 A“uC/OS-III 的 API 参考手册”。
The application
programmer should refer to Appendix A, “μC/OS-III API Reference Manual” on page
375 for a detailed description of these services.
任务调用这个函数后就会被挂起直到期满。这个函数可以有三种模式:相对延时模式,周期性延时模式,绝对定时模式。
A
task calls this function to suspend execution until some time expires. The
calling function will not execute until the specified time expires. This
function allows three modes: relative, periodic and absolute.
列表 11-1 显示了 OSTimeDly()的相关模式。
Listing
11-1 shows how to use OSTimeDly() in relative mode.

L11-1(1)第一个参数是任务的延时时基数。如果时基速率被设置为1000Hz,任务会每次执行都会被延时大约 2 毫秒。然而,并不是精确地延时 2 个时基,因为任务被挂起后是检测时基中断发生的次数与任务的延时值是否相同来判断是否超时的。也就是说,当任务在时基中断将要到来时被挂起,那么实际的延时时基会少 1 个时基。
L11-1(1) The first argument specifies the amount
of time delay (in number of ticks) from when the function is called. For
example if the tick rate (OS_CFG_TICK_RATE_HZ in OS_CFG_APP.H) is set to 1000
Hz, the user is asking to suspend the current task for approximately 2
milliseconds. However, the value is not accurate since the count starts from
the next tick which could occur almost immediately. This will be explained
shortly.
L11-1(2)参数为 OS_OPT_TIME_DLY 表明用户选择的相对延时模式。
L11-1(2) Specifying OS_OPT_TIME_DLY indicates that
the user wants to use “relative” mode.
L11-1(3)如大多数 uC/OS-III 函数一样,错误代号会被返回。当所有的参数都是有效时会返回 OS_ERR_NONE。
L11-1(3) As with most μC/OS-III services an error
return value will be returned. The example should return OS_ERR_NONE as the
arguments are all valid. Refer to Chapter 11, “Time Management” on page 183 for
a list of possible error codes.
L11-1(4)当返回不为 OS_ERR_NONE 时,OSTimeDly()将不会执行延时操作。
如上所述,延时时间不是精确的。详见图 11-1
L11-1(4) Always check the error code returned by
μC/OS-III. If “err” does not contain OS_ERR_NONE, OSTimeDly() did not perform
the intended work. For example, another task could remove the time delay
suspension by calling OSTimeDlyResume() and when MyTask() returns, it would not
have returned because the time had expired.
As
mentioned above, the delay is not accurate. Refer to Figure 11-1 and its
description below to understand why.

HPT: 高优先级任务,LPT 低优先级任务
F11-1(1)时基中断产生,进入时基 ISR。
F11-1(1) We get a tick interrupt and
μC/OS-III services the ISR.
F11-1(2)ISR 结束后,切换到高优先级任务。这段执行时间是不可预测的。
F11-1(2) At the end of the ISR, all Higher
Priority Tasks (HPTs) execute. The execution time of HPTs is unknown and can
vary.
F11-1(3)当高优先级任务执行完毕后,uC/OS-III 转到想要调用OSTimeDly()的任务。
F11-1(3) Once all HPTs have executed,
μC/OS-III runs the task that has called OSTimeDly() as shown above. For the
sake of discussion, it is assumed that this task is a lower priority task
(LPT).
F11-1(4)任务调用 OSTimeDly(),并设置为等待两个时基。此时, uC/OS-III 会将这个任务放入时基队列。等待期满时任务不会占用CPU 时间。
F11-1(4) The task calls OSTimeDly() and
specifies to delay for two ticks in “relative” mode. At this point, μC/OS-III
places the current task in the tick list where it will wait for two ticks to
expire. The delayed task consumes zero CPU time while waiting for the time to
expire.
F11-1(5)下一个时基中断产生。当有高优先级的任务等待此次时基中断时,高优先级任务会被执行。
F11-1(5) The next tick occurs. If there are
HPTs waiting for this particular tick, μC/OS-III will schedule them to run at
the end of the ISR.
F11-1(6)执行高优先级任务。
F11-1(6) The HPTs execute.
F11-1(7)再下一个时基中断产生。当有高优先级的任务等待此次时基中断时,高优先级任务会被执行。
F11-1(7) The next tick interrupt occurs.
This is the tick that the LPT was waiting for and will now be made ready to run
by μC/OS-III.
F11-1(8)因为此刻没有高优先级的中断就绪,uC/OS-III 上下文切换到低优先级任务,也就是调用了 OSTimeDly()的任务。
F11-1(8) Since there are no HPTs to execute
on this tick, μC/OS-III switches to the LPT.
F11-1(9)时基中断后,因为高优先级任务需要被执行,所以该任务的延时时间不会是精确的 2 个时基。
F11-1(9) Given the execution time of the
HPTs, the time delay is not exactly two ticks, as requested.
需要考虑的是:任务调用 OSTimeDly()延时 2 个时基时,若下一个时基中断会迅速地产生,如上图。在这种情况下,要延时 2 个时基时,就需要设置延时 3 个时基。
In
fact, it is virtually impossible to obtain a delay of exactly the desired
number of ticks. One might ask for a delay of two ticks, but the very next tick
could occur almost immediately after calling OSTimeDly()! Just imagine what
might happen if all HPTs took longer to execute and pushed (3) and (4) further
to the right. In this case, the delay would actually appear as one tick instead
of two.
当参数设置为了 OT_OPT_TIME_PERIDSIC 时,OSTimeDly()被设置为周期性延时模式。任务设置匹配值决定了任务被唤醒的周期。如图 11-2 所示。当匹配 值 等 于 OSTickCtr 时,任务被唤醒 。
OSTimeDly()
can also be called with the OS_OPT_TIME_PERIODIC option as shown in Listing
11-2. This option allows delaying the task until the tick counter reaches a
certain periodic match value and thus ensures that the spacing in time is
always the same as it is not subject to CPU load variations.
μC/OS-III determines the “match value” of OSTickCtr to determine when the task will need to wake up based on the desired period. This is shown in Figure 11-2. μC/OS-III checks to ensure that if the match is computed such that it represents a value that has already gone by then, the delay will be zero.

L11-2(1)第一个参数设置了任务执行的周期,如上被设置为 4 个时基。
L11-2(1) The first argument specifies the period
for the task to execute, specifically every four ticks. Of course, if the task
is a low-priority task, μC/OS-III only schedules and runs the task based on its
priority relative to what else needs to be executed.
L11-2(2)第二个参数 OS_OPT_TIME_PERIODIC 表明任务被设置为周期性运行。
L11-2(2) Specifying OS_OPT_TIME_PERIODIC indicates
that the task is to be ready to run when the tick counter reaches the desired
period from the previous call.
L11-2(3)检查 uC/OS-III 返回的错误代号。
L11-2(3) You should always check the error code
returned by μC/OS-III.
相对延时模式和周期性延时模式看起来是不一样的,但是它们类似的。它们都可能丢失一个时基当有高优先级任务被执行很长时间时。
Relative
and Periodic modes might not look different, but they are. In Relative mode, it
is possible to miss one of the ticks when the system is heavily loaded, missing
a tick or more on occasion. In Periodic mode, the task may still execute later,
but it will always be synchronized to the desired number of ticks. In fact,
Periodic mode is the preferred mode to use to implement a time-of-day clock.
最后,你可以使用绝对时间模式处理对时间要求很高的任务。例如,必须产品在上电的第 10 秒关闭 LED。在这种情况下,你就需要设置为绝对时间模式 OS_OPT_TIME_MATCH。设置 OSTickCtr 值为10 乘以时基频率。
Finally,
you can use the absolute mode to perform a specific action at a fixed time
after power up. For example, turn off a light 10 seconds after the product
powers up. In this case, you would specify OS_OPT_TIME_MATCH while “dly”
actually corresponds to the desired value of OSTickCtr you want to reach.
总结:当 OSTickCtr 与以下值时匹配时任务被唤醒:
To
summarize, the task will wake up when OSTickCtr reaches the following value:
|
选项 |
唤醒时的匹配值 |
|
OS_OPT_TIME_DLY |
OSTickCtr + dly |
|
OS_OPT_TIME_PERIODIC |
OSTCBCurPtr->TickCtrPrev+dly |
|
OS_OPT_TIME_MATCH |
dly |
dly
为上述函数的第一个参数
任务可以调用这个函数为任务设置延时,这个函数更“友好”于用户。特别的,可以设置为小时,分钟,秒,毫秒(HMSM 由此四个英文首字母得来)。这个函数只在相对延时模式下运行。
A task
may call this function to suspend execution until some time expires by
specifying the length of time in a more user-friendly way. Specifically,
specify the delay in hours, minutes, seconds, and milliseconds (thus the HMSM).
This function only works in “Relative” mode.
列表 L11-3 介绍了 OSTimeDlyHMSM()。
Listing
11-3 indicates how to use OSTimeDlyHMSM().

L11-3(1)这四个参数设置了延时的时间(分别对应为时、分、秒、毫秒)。在这个例子中,设置了延时 1 秒。延时的分辨率决定于时基频率。例如,如果时基频率为 1000Hz 那么延时的分辨率为 1 毫秒。如果时基的频率为 100Hz 那么延时的分辨率为 10 毫秒。同样的,延时时间不会很精确。
L11-3(1) The first four arguments specify the
amount of time delay (in hours, minutes, seconds, and milliseconds) from this
point in time. In the above example, the task should delay by 1 second. The
resolution greatly depends on the tick rate. For example, if the tick rate
(OS_CFG_TICK_RATE_HZ in OS_CFG_APP.H) is set to 1000 Hz there is technically a
resolution of 1 millisecond. If the tick rate is 100 Hz then the delay of the
current task is in increments of 10 milliseconds. Again, given the relative
nature of this call, the actual delay may not be accurate.
L11-3(2)设置 OS_OPT_TIME_HMSM_STRICT 后会检测函数的参数是否合理。小时的范围是 0 到 99,分的范围是 0 到 59,秒的范围是 0 到 59,毫秒的范围是 0 到 999。
L11-3(2) Specifying OS_OPT_TIME_HMSM_STRICT
verifies that the user strictly passes valid values for hours, minutes, seconds
and milliseconds. Valid hours are 0 to 99, valid minutes are 0 to 59, valid
seconds are 0 to 59, and valid milliseconds are 0 to 999.
如果设置为 OS_OPT_TIME_HMSM_NON_STRICT,函数会接受参数的范围变大。小时的范围是 0 到 999,分的范围是 0 到 9999,秒的范围是 0 到 65535,毫秒的范围是 0 到 4294967295。
If
specifying OS_OPT_TIME_HMSM_NON_STRICT, the function will accept nearly any
value for hours (between 0 to 999), minutes (from 0 to 9999), seconds (any
value, up to 65,535), and milliseconds (any value, up to 4,294,967,295).
OSTimeDlyHMSM(203, 101, 69, 10000) may be accepted. Whether or not this makes
sense is a different story.
限制小时范围为 0 到 999 的原因是:一般是用 32 位的数记录时基值的。如果时基的频率为 1000Hz,那么最多能计数 4294967 秒,大约 1193 小时。因此设置 999 小时为上限。
The
reason hours is limited to 999 is that time delays typically use 32-bit values
to keep track of ticks. If the tick rate is set at 1000 Hz then, it is possible
to only track 4,294,967 seconds, which corresponds to 1,193 hours, and
therefore 999 is a reasonable limit.
L11-3(3)如大多数 uC/OS-III 函数一样返回一个错误代号。
L11-3(3)
As with most μC/OS-III services the user will receive an error return value.
The example should return OS_ERR_NONE since the arguments are all valid. Refer
to Appendix A, “μC/OS-III API Reference Manual” on page 375 for a list of
possible error codes.
虽然 uC/OS-III 允许任务有很长的延时时间,但不推荐任务长时间延时。因为任务可能早就被删除了。
Even
though μC/OS-III allows for very long delays for tasks, it is actually not
recommended to delay tasks for a long time. There is no indication that the
task is actually “alive” unless it is possible to monitor the amount of time
remaining for the delay. It is better to have the task wake up approximately
every minute or so, and have it “tell you” that it is still ok.
OSTimeDly()和 OSTimeDlyHMSM()经常被用于创建周期性的任务。例如,设置任务每 50 毫秒扫描一次键盘、每 10 毫秒读取 AD 输入等。
OSTimeDly()
and OSTimeDlyHMSM() are often used to create periodic tasks (tasks that execute
periodically). For example, it is possible to have a task that scans a keyboard
every 50 milliseconds and another task that reads analog inputs every 10
milliseconds, etc.
任务可以调用 OSTimeDlyResume()恢复其它被 OSTimeDly()或OSTimeDlyHMSM() 延时的任务。列表 11-4 显示了如何使用OSTimeDlyResume()。
A task
can resume another task that called OSTimeDly() or OSTimeDlyHMSM() by calling
OSTimeDlyResume(). Listing 11-4 shows how to use OSTimeDlyResume(). The task
that delayed itself will not know that it was resumed, but will think that the
delay expired.
Because
of this, use this function with great care.

每个时基中断发生时 uC/OS-III 会递增时基计数值。通过这个计数值能大概看出系统上电后经过了多长时间。
μC/OS-III
increments a tick counter every time a tick interrupt occurs. This counter
allows the application to make coarse time measurements and have some notion of
time (after power up).
OSTimeGet()能获得时基计数值。
OSTimeGet()
allows the user to take a snapshot of the tick counter. As shown in a previous
section, use this value to delay a task for a specific number of ticks and
repeat this periodically without losing track of time.
OSTimeSet()允许用户设置时基计数值。虽然 uC/OS-III 允许这种操作,但调用这个函数时需慎重。
OSTimeSet()
allows the user to change the current value of the tick counter. Although
μC/OS-III allows for this, it is recommended to use this function with great
care.
当时基中断发生时,时基 ISR 必须调用这个函数。uC/OS-III 用这个函数更新时基计数值。OSTimeTick()是 uC/OS-III 的内部函数。
The
tick Interrupt Service Routine (ISR) must call this function every time a tick
interrupt occurs. μC/OS-III uses this function to update time delays and
timeouts on other system calls. OSTimeTick() is considered an internal function
to μC/OS-III.
uC/OS-III 提供了延时函数,用户可以以时基为单位将任务延时。可以以小时、分、秒、毫秒为单位将任务延时。延时的精确地依赖于时基的频率,延时的最小分辨率为时基。
μC/OS-III
provides services to applications so that tasks can suspend their execution for
user-defined time delays. Delays are either specified by a number of clock
ticks or hours, minutes, seconds, and milliseconds.
用户可以调用 OSTimeDlyResume()使处于延时状态的任务恢复。
Application
code can resume a delayed task by calling OSTimeDlyResume(). However, its use
is not recommended because resumed task will not know that they were resumed as
opposed to the time delay expired.
通过 OSTimeSet() 设置时基计数值,通过 OSTimeGet()获取时基计数值。
μC/OS-III
keeps track of the number of ticks occurring since power up or since the number
of ticks counter was last changed by OSTimeSet(). The counter may be read by
the application code using OSTimeGet().
uC/OS-III 提供了软件定时器服务(相关代码在 OS_TMR.C 中)。当设置 OS_CFG.H 中的 OS_CFG_TMR_EN 为 1 时软件定时器服务被使能。{为了简洁,这章中的定时器均为软件定时器}
μC/OS-III
provides timer services to the application programmer and code to handle timers
is found in OS_TMR.C. Timer services are enabled when setting OS_CFG_TMR_EN to
1 in OS_CFG.H.
定时器递减其计数值,当计数值为 0 的,就是定时期满的时候。通过回调函数执行相应的操作(打开或关闭 LED、开启电机或其它操作)。回调函数是用户定义的,当定时器期满时可以被调用。然而,不要用回调函数调用如下函数 OSTimeDly(),OSTimeDlyHMSM(), OS???Pend(),或其它能导致该定时器任务被阻塞或被删除的函数。
Timers
are down counters that perform an action when the counter reaches zero. The
user provides the action through a callback function (or simply callback). A
callback is a user-declared function that will be called when the timer
expires. The callback can be used to turn a light on or off, start a motor, or
perform other actions. However, it is important to never make blocking calls
within a callback function (i.e., call OSTimeDly(), OSTimeDlyHMSM(),
OS???Pend(), or anything that causes the timer task to block or be deleted).
Timers are useful in protocol stacks (retransmission timers, for example), and
can also be used to poll I/O devices at predefined intervals.
应用中可以定义任意个定时器(限制于处理器的 RAM)。uC/OS-III 的定时器服务通过调用 OSTmr???()开始。详见附录 A
An
application can have any number of timers (limited only by the amount of RAM
available). Timer services in μC/OS-III start with the OSTmr???() prefix, and
the services available to the application programmer are described in Appendix
A, “μC/OS-III API Reference Manual” on page 375.
uC/OS-III 定时器的分辨率决定于时基频率,也就是变量OS_CFG_TMR_TASK_RATE_HZ 的值,它是以 Hz 为单位的。如果时基任务的频率设置为 10Hz,所有定时器的分辨率为十分之一秒。事实上,这是用于定时器的推荐值。定时器用于不精确时间尺度的任务。
The
resolution of all the timers managed by μC/OS-III is determined by the
configuration constant: OS_CFG_TMR_TASK_RATE_HZ, which is expressed in Hertz
(Hz). So, if the timer task (described later) rate is set to 10, all timers
have a resolution of 1/10th of a second (ticks in the diagrams to follow). In
fact, this is the typical recommended value for the timer task. Timers are to
be used with “coarse” granularity.
uC/OS-III 提供了一些函数用于管理定时器如表 12-1
μC/OS-III
provides a number of services to manage timers as summarized in Table 12-1.
|
函数名 |
功能 |
|
OSTmrCreate() |
创建和设置定时器 |
|
OSTmrDel() |
删除一个定时器 |
|
OSTmrRemainGet() |
获得定时器的剩余期限值 |
|
OSTmrStart() |
开始定时器运行 |
|
OSTmrStateGet() |
获得定时器当前的状态 |
|
OSTmrStop() |
暂停定时器 |
表 12-1 定时器的 API 总结
定时器被使用之前必须被创建。通过调用 OSTmrCreate(),并设置这个函数的相关参数。一旦定时器的操作模式被设置,就不能被改动直到定时器被删除并被重新创建。OSTmrCreate()的原型如下:
A
timer needs to be created before it can be used. Create a timer by calling
OSTmrCreate() and specify a number of arguments to this function based on how
the timer is to operate. Once the timer operation is specified, its operating
mode cannot be changed unless the timer is deleted and recreated. The function
prototype for OSTmrCreate() is shown below as a quick reference:

一旦定时器被创建,它可以被开始或停止任意次。定时器可以被设置为 3 种模式:一次性定时模式,无初始定时周期模式(没有初始的定时),有初始定时周期模式(有初始的定时)。
Once
created, a timer can be started (or restarted) and stopped as often as is necessary.
Timers can be created to operate in one of three modes: One-shot, Periodic (no
initial delay), and Periodic (with initial delay).
正如其名字所表达,定时器会递减被设置初始的定时值,当该值为 0 时就会调用回调函数并停止定时器。如图 12-1。初始的定时值通过调用 OSTmrSrart()设置,延时期满时,回调函数被调用(假定回调函数在定时器创建的时候被提供)。完成之后,定时器不做任务事情直到调用 OSTmrStart()被重新开启。
As
its name implies, a one-shot timer will countdown from its initial value, call
the callback function when it reaches zero, and stop. Figure 12-1 shows a
timing diagram of this operation. The countdown is initiated by calling
OSTmrStart(). At the completion of the time delay, the callback function is
called, assuming a callback function was provided when the timer was created.
Once completed, the timer does not do anything unless restarted by calling
OSTmrStart(), at which point the process starts over.
通过调用 OSTmrStop()停止定时器。
Terminate
the countdown process of a timer (before it reaches zero) by calling
OSTmrStop(). In this case, specify that the callback function be called or not.

如图 12-2 所示,在定时值为 0 之前调用 OSTmrStart()后一次性定时器被再次触发。这个特性可以被用来模拟看门狗的功能。
As
shown in Figure 12-2, a one-shot timer is retriggered by calling OSTmrStart()
before the timer reaches zero. This feature can be used to implement watchdogs
and similar safeguards.

如图 12-3 所示,定时器被设置为无初始定时周期模式。当定时器期满时,回调函数被调用,定时值被定时周期值重载,如此周期性地重复。
As
indicated in Figure 12-3, timers can be configured for periodic mode. When the
countdown expires, the callback function is called, the timer is automatically
reloaded, and the process is repeated. If specifying a delay of zero (i.e., dly
== 0) when the timer is created, when started, the timer immediately uses the
“period” as the reload value. Calling OSTmrStart() at any point in the
countdown restarts the process.

如图 12-4 所示,定时器可以被设置为有初始定延周期模式。第一周期的递减值由 OSTmrCreate()中的参数"dly"设置,以后的重载值由"period"值确定。调用 OSTmrStart()重新开始。
As
shown in Figure 12-4, timers can be configured for periodic mode with an
initial delay that is different than its period. The first countdown count
comes from the “dly” argument passed in the OSTmrCreate() call, and the reload
value is the “period”. Calling OSTmrStart() restarts the process including the
initial delay.

12-4-1 内部定时器管理-定时器状态
图 12-5 显示了定时器的状态图
Figure
12-5 shows the state diagram of a timer.
任务调用 OSTmrStateGet()获得定时器的状态。当然,也可以调用 OSTmrRemainGet()获得剩余定时时间。定时值是以时基为单位的。如果时基定时器的频率率为 10Hz,那么定时值设置为 50 意味着延时5 秒。如果定时器被停止,那其定时值也将被停止,直到定时器被恢复时,定时器值继续被定时器任务递减。
Tasks
can call OSTmrStateGet() to find out the state of a timer. Also, at any time
during the countdown process, the application code can call OSTmrRemainGet() to
find out how much time remains before the timer reaches zero (0). The value
returned is expressed in “timer ticks.” If timers are decremented at a rate of
10 Hz then a count of 50 corresponds to 5 seconds. If the timer is in the stop
state, the time remaining will correspond to either the initial delay (one shot
or periodic with initial delay), or the period if the timer is configured for
periodic without initial delay.

F12-5(1)"Unused"状态意味着定时器尚未被创建或已经被删除。换句话说,uC/OS-III 不知道该定时器的相关状态。
F12-5(1) The “Unused” state is a timer that has
not been created or has been “deleted.” In other words, μC/OS-III does not know
about this timer.
F12-5(2)当创建了定时器或调用了 OSTmrStop(),定时器会处于停止模式。
F12-5(2) When creating a timer or calling
OSTmrStop(), the timer is placed in the “stopped” state.
F12-5(3)当调用 OSTmrStart()后定时器处于运行状态。
F12-5(3) A timer is placed in running state when
calling OSTmrStart(). The timer stays in that state unless it’s stopped,
deleted, or completes its one shot.
F12-5(4)一次性定时模式的定时器延时期满后处于完成状态"Completed"。
F12-5(4) The “Completed” state is the state a
one-shot timer is in when its delay expires.
12-4-2 定时器内部管理——OS_TMR
定时器是 uC/OS-III 中的内核对象,其数据类型为 OS_TMR(见OS.H)。如列表 12-1 所示。uC/OS-III 中管理定时器的相关代码在文件 OS_TMR.C 中。在编译时通过设置 OS_CFG.H 中的 OS_CFG_TMR_EN 为 1 开启定时器功能。
A
timer is a kernel object as defined by the OS_TMR data type (see OS.H) as shown
in Listing 12-1.
The
services provided by μC/OS-III to manage timers are implemented in the file
OS_TMR.C. A μC/OS-III licensee has access to the source code. In this case,
timer services are enabled at compile time by setting the configuration
constant OS_CFG_TMR_EN to 1 in OS_CFG.H.

L12-1(1)在 uC/OS-III 中,所有的结构体都分配一个数据类型。事实上,所有的数据类型都是以"OS_"为开头的并且全部大写。当定时器被创建时,用数据类型为 OS_TMR 的变量描述这个定时器。
L12-1(1) In μC/OS-III, all structures are given a data type. In fact, all data types start with “OS_” and are all uppercase. When a timer is declared, simply use OS_TMR as the data type of the variable used to declare the timer.
L12-1(2)结构体开始于一个"Type"域,uC/OS-III 可以通过这个域辨认它是个定时器(其它内核对象的结构体首部也有"Type")。如果函数需传递一种内核对象,uC/OS-III 会检测"Type"域是否为参数所需的类型。例如,如果传递消息队列 QS_Q 到定时器,uC/OS-III 会检测是否为消息队列被提交,并返回错误代号。
L12-1(2) The structure starts with a “Type” field,
which allows it to be recognized by μC/OS-III as a timer. Other kernel objects
will also have a “Type” as the first member of the structure. If a function is
passed a kernel object, μC/OS-III is able to confirm that it is passed the
proper data type. For example, if passing a message queue (OS_Q) to a timer
service (for example OSTmrStart()) then μC/OS-III will be able to recognize
that an invalid object was passed, and return an error code accordingly.
L12-1(3)每个内核对象都可以被命名,以利于调式器或 uC/Probe 的调试。这是一个指向内核对象名的指针。
L12-1(3) Each kernel object can be given a name
for easier recognition by debuggers or μC/Probe. This member is simply a
pointer to an ASCII string which is assumed to be NUL terminated.
L12-1(4).CallbackPtr 是一个指向函数的指针,被指向的函数称作回调函数,当定时器期满时回调函数被调用。如果定时器创建时该指针值为 NULL,回调函数将不会被调用。
L12-1(4) The .CallbackPtr member is a pointer to a
function that is called when the timer expires. If a timer is created and
passed a NULL pointer, a callback would not be called when the timer expires.
L12-1(5)当回调函数需要接受一个参数时(.CallbackPtr 不为 NULL),这个参数通过该指针传递给回调函数。
L12-1(5) If there is a non-NULL .CallbackPtr then
the application code could have also specified that the callback be called with
an argument when the timer expires.
This
is the argument that would be passed in this call.
L12-1(6).NextPtr 和.PrevPtr 都是指针,用于将定时器链接成一个双向链表。
L12-1(6) .NextPtr and .PrevPtr are pointers used
to link a timer in a doubly linked list. These are described later.
L12-1(7) 当定时器管理器中的变量 OSTmrTickCtr 的值等于.Match 值时,定时器期满。
L12-1(7) A timer expires when the timer manager
variable OSTmrTickCtr reaches the value stored in a timer’s .Match field. This
is also described later.
L12-1(8).Remain 中保存了距定时器期满还有多少个时基。这个值每经过一个 OS_CFG_TMR_WHEEL_SIZE(稍后说明)被更新。
L12-1(8) The .Remain contains the amount of time
remaining for the timer to expire. This value is updated once per
OS_CFG_TMR_WHEEL_SIZE (see OS_CFG_APP.H) that the timer task executes
(described later). The value is expressed in multiples of
1/OS_CFG_TMR_TASK_RATE_HZ of a second (see OS_CFG_APP.H).
L12-1(9).Dly 域包含了定时器的定时值。这个值以时基为最小单位。
L12-1(9)
The .Dly field contains the one-shot time when the timer is configured (i.e.,
created) as a one-shot timer and the initial delay when the timer is created as
a periodic timer. The value is expressed in multiples of
1/OS_CFG_TMR_TASK_RATE_HZ of a second (see OS_CFG_APP.H).
L12-1(10).Period 是定时器的定时周期(当被设置为周期模式时)。
这个值以时基为最小单位。
L12-1(10) The .Period is the timer period when the timer
is created to operate in periodic mode. The value is expressed in multiples of 1/OS_CFG_TMR_TASK_RATE_HZ of a second
(see OS_CFG_APP.H).
L12-1(11).Opt 域包含了传递给 OSTmrCreate()的参数(可选)。
L12-1(11) The .Opt field contains options as passed to
OSTmrCreate().
L12-1(12).State 中包含了定时器当前的状态(见图 12-5)。
即使了解 OS_TMR 结构体的内容,用户代码也不能直接访问这些内容。必须通过 uC/OS-III 提供的 API 访问。
L12-1(12) The .State field represents the current state
of the timer (see Figure 12-5).
Even
if the internals of the OS_TMR data type are understood, the application code
should never access any of the fields in this data structure directly. Instead,
always use the Application Programming Interfaces (APIs) provided with
μC/OS-III.
12-4-3 内部定时器管理——定时器任务
通过设置 OS_CFG.H 中的 OS_CFG_TMR_EN 为 1 使能定时器任务OS_TmrTask() ,该任务的优先级通过 OS_CFG_APP.H 中的 OS_CFG_TMR_TASK_PRIO 设置。OS_TmrTask()的优先级通常被设置为中等大小。
OS_TmrTask()
is a task created by μC/OS-III (assumes setting OS_CFG_TMR_EN to 1 in OS_CFG.H)
and its priority is configurable by the user through μC/OS-III’s configuration
file OS_CFG_APP.H (see OS_CFG_TMR_TASK_PRIO). OS_TmrTask() is typically set to
a medium priority.
OS_TmrTask()是一个周期性的任务,它使用时基中断源作为它的时钟计数源。然而,定时器通常产生较低的周期信号(可以为 10Hz 等)。它的周期信号是从时基信号中分频得来的。如果时基频率为1000Hz,定时器想要的频率为 10Hz,那么定时器任务需被设置为每100 个时基产生一次信号,也就是分频值为 100。如图 12-6
OS_TmrTask()
is a periodic task and uses the same interrupt source used to generate clock
ticks. However, timers are generally updated at a slower rate (i.e., typically
10 Hz or so) and thus, the timer tick rate is divided down in software. If the
tick rate is 1000 Hz and the desired timer rate is 10 Hz then the timer task
will be signaled every 100th tick interrupt as shown in Figure 12-6.

图 12-7 定时器管理任务的相关运行情况

F12-7(1)中断产生
F12-7(1) The tick ISR occurs and assumes
interrupts are enabled and executes.
F12-7(2)时基 ISR 标记时基任务。
F12-7(2) The tick ISR signals the tick task that
it is time for it to update timers.
F12-7(3)然而,有高优先级任务需要被执行。因此,uC/OS-III 切换到高优先级任务。
F12-7(3) The tick ISR terminates, however there
are higher priority tasks that need to execute (assuming the timer task has a
lower priority). Therefore, μC/OS-III runs the higher priority task(s).
F12-7(4)当所有的高优先级任务被执行完毕,uC/OS-III 切换到定时器任务,假设有三个定时器期满。
F12-7(4) When all higher priority tasks have
executed, μC/OS-III switches to the timer task and determines that there are
three timers that expired.
F12-7(5)第一个定时器的回调函数被执行。
F12-7(5) The callback for the first timer is
executed.
F12-7(6)第二个定时器的回调函数被执行。
F12-7(6) The callback for the second expired timer
is executed.
F12-7(7) 第三个定时器的回调函数被执行。
F12-7(7) The callback for the third expired timer
is executed.
有一些有趣的事情需注意:
There
are a few interesting things to notice:
■ 回调函数是在定时器任务被切换后执行的。这意味着定时器任务需要有足够的堆栈空间供回调函数去执行。
Execution
of the callback functions is performed within the context of the timer task.
This means that the application code will need to make sure there is sufficient
stack space for the timer task to handle these callbacks.
■ 回调函数是在根据定时器队列中依次存放的。所以期满后回调函数是依次被执行的。
The
callback functions are executed one after the other based on the order they are
found in the timer list.
■ 定时器任务的执行时间决定于:有多少个定时器期满,执行定时器中的回调函数需多少时间。因为回调函数是用户提供,它可能很大程度上影响了定时器任务的执行时间。
The
execution time of the timer task greatly depends on how many timers expire and
how long each of the callback functions takes to execute. Since the callbacks
are provided by the application code they have a large influence on the
execution time of the timer task.
■ 定时器中的回调函数不能等待事件的发生,因为这样可能会让定时器任务被挂起。
The
timer callback functions must never wait on events that would delay the timer
task for excessive amounts of time, if not forever.
■ 回调函数被执行时会锁调度器,所以你必须让回调函数尽可能地短。Callbacks are called with the scheduler locked, so you should ensure that callbacks execute as quickly as possible.
12-4-4 内部定时器管理——定时器列表
有些情况下,uC/OS-III 可能要维护上百个定时器。使用定时器列表会大大降低更新定时器列表所占用的 CPU 时间。定时器列表类似于时基列表,如图 12-8 所示。
μC/OS-III
might need to literally maintain hundreds of timers (if an application requires
that many). The timer list management needs to be implemented such that it does
not take too much CPU time to update the timers. The timer list works similarly
to a tick list as shown in Figure 12-8.

F12-8(1)定时器列表包含两部分:表(OSCfg_TmrWheel[])和定时器计数变量(OSTmrTickCtr)。
F12-8(1) The timer list consists of a table
(OSCfg_TmrWheel[]) and a counter (OSTmrTickCtr).
F12-8(2)表中包含了 OS_CFG_TMR_WHEEL_SIZE 条记录,这是在编译时设定的(见 OS_CFG_APP.H)。记录的多少仅限于处理器的RAM 空间。推荐的设置值为定时器数 /4 。不推荐设置 OS_CFG_TMR_WHEEL_SIZE 为偶数值。换句话说,如果定时器任务是 10Hz 的,避免设置 OS_CFG_TMR_WHEEL_SIZE 为 10,而是用 11 代替。
F12-8(2) The table contains up to
OS_CFG_TMR_WHEEL_SIZE entries, which is a compile time configuration value (see
OS_CFG_APP.H). The number of entries depends on the amount of RAM available to
the processor and the maximum number of timers in the application. A good
starting point for OS_CFG_TMR_WHEEL_SIZE might be: #Timers/4. It is not
recommended to make OS_CFG_TMR_WHEEL_SIZE an even multiple of the timer task
rate. In other words, if the timer task is 10 Hz, avoid setting
OS_CFG_TMR_WHEEL_SIZE to 10 or 100 (use 11 or 101 instead). Also, use prime
numbers for the timer wheel size. Although it is not really possible to plan at
compile time what will happen at run time, ideally the number of timers waiting
in each entry of the table is distributed uniformly.
F12-8 ( 3 )表中的每个记录都由3部分组:.NbrEntriesMax,.NbrEntries,.FirstPtr。.NbrEntriesMax 表明该记录中有多少个定时器。.NbrEntriesMax 表明该记录中最大时存放了多少个定时器。.FirstPtr 指向当前记录的定时器链表。
当时基 ISR 标记这个定时器任务时,定时器任务调用 OS_TmrTask() 递增定时器计数值。
F12-8(3)
Each entry in the table contains three fields: .NbrEntriesMax, .NbrEntries and
.FirstPtr. .NbrEntries indicates how many timers are linked to this table
entry. .NbrEntriesMax keeps track of the highest number of entries in the
table. Finally, .FirstPtr contains a pointer to a doubly linked list of timers
(through the tasks OS_TMR) belonging into the list at that table position.
通过调用 OSTmrStart()将定时器插入到定时器列表中。然而,定时器必须在被使用之前被创建。
The
counter is incremented by OS_TmrTask() every time the tick ISR signals the
task.
用一个例子阐述一个定时器被插入到定时器列表中的过程。如列表 12-2 所 示 。 我们先假定定时器列表是空的,设置OS_CFG_TMR_WHEEL_SIZE 为 9,当前的 OSTmrTickCtr 为 12。调用 OSTmrStart()时定时器被放入定时器列表。假定定时器创建时被设置延时为 1,且这个任务是一次性定时。
Timers
are inserted in the timer list by calling OSTmrStart(). However, a timer must
be created before it can be used.
An
example to illustrate the process of inserting a timer in the timer list is as
follows. Let’s assume that the timer list is completely empty,
OS_CFG_TMR_WHEEL_SIZE is configured to 9, and the current value of OSTmrTickCtr
is 12 as shown in Figure 12-9. A timer is placed in the timer list when calling
OSTmrStart(), and assumes that the timer was created with a delay of 1 and that
this timer will be a one-shot timer as follows:

因为 OSTmrTickCtr 的值为 12,定时器的定时值为 1。当 OSTmrTickCtr 的值变为 13 时定时器期满。定时器会被放入 OSCfg_TmrWheel[],经过下式运算:
Since
OSTmrTickCtr has a value of 12, the timer will expire when OSTmrTickCtr reaches
13, or during the next time the timer task is signaled. Timers are inserted in
the OSCfg_TmrWheel[] table using the following equation:
MatchValue=OSTmrTickCtr+dly
Index intoOSCfg_TmrWheel[]=MatchValue%
OS_CFG_TMR_WHEEL_SIZE
"dly"是传递给 OSTmrCreate()的第三个参数。将上述公式代入本例,
我们会得到以下等式:
MatchValue =13
Index into OSCfg_TmrWheel[] =4
定时器被插入到 OScfg_TmrWheel[4] 中,在这种情况下,OS_TMR 被放在队列中的首位置 (OSCfg_TmrWheel[4].FirstPtr 指向这个 OS_TMR),并且索引 4 的计数值递增(OSCfg_TmrWheel[4].NbrEntries 此时为 1)。匹配值"MatchValue"被放在 OS_TMR 的.Match 中。因为这是索引 4 的唯一一个定时器,.NextPtr 和.PrevPtr 都指向 NULL。
The timer
is entered at index 4 in the timer wheel, OSCfg_TmrWheel[]. In this case, the
OS_TMR is placed at the head of the list (i.e., pointed to by
OSCfg_TmrWheel[4].FirstPtr), and the number of entries at index 4 is
incremented (i.e., OSCfg_TmrWheel[4].NbrEntries will be 1). “MatchValue” is
placed in the OS_TMR field .Match. Since this is the first timer inserted in
the timer list at index 4, the .NextPtr and .PrevPtr both point to NULL.

以 下 的 代 码 显 示 创 建 和 开 启 定 时 器 的 操 作 。
The
code below shows creating and starting another timer. This is performed
“before” the timer task is signaled.

uC/OS-III 会计算匹配值和索引值如下:
μC/OS-III
will calculate the match value and index as follows:
MatchValue =12+10 =22
Index into OSCfg_TmrWheel[] =22%9 =4
第二个定时器也被插入到索引为 4 的表记录中,如图 12-10。队列被重新分配,剩余时间最少的定时器被放到队列的首部,剩余时间最多的定时器被放到队列的尾部。
The
“second timer” will be inserted at the same table entry as shown in Figure
12-10, but sorted so that the timer with the least amount of time remaining
before expiration is placed at the head of the list, and the timer with the
longest to wait at the end.

当定时器任务被执行(见 OS_TMR.C 中的 OS_TmrTask()),它首先递增 OSTmrTickCtr,然后决定表中的哪条记录需被更新。然后,如果这条记录中有定时器队列(.FirstPtr 不为 NULL),每个 OS_TMR 都会被检测其.Match 是否与 OSTmrTickCtr 相等。如果相等,这个定时器会被移出该队列,然后 OS_TmrTask()调用这个定时器的回调函数(假定这个定时器被创建时有回调函数)。遍历该记录的整个队列直到没有定时器的.Match 值与 OSTmrTickCtr 匹配。注意的是:队列会被重新排序。
When
the timer task executes (see OS_TmrTask() in OS_TMR.C), it starts by
incrementing OSTmrTickCtr and determines which table entry (i.e., spoke) it
needs to update. Then, if there are timers in the list at this entry (i.e.,
.FirstPtr is not NULL), each OS_TMR is examined to determine whether the .Match
value “matches” OSTmrTickCtr and, if so, the OS_TMR is removed from the list
and OS_TmrTask() calls the timer callback function, assuming one was defined
when the timer was created. The search through the list terminates as soon as
OSTmrTickCtr does not match the timer’s .Match value. There is no point in
looking any further in the list since the list is already sorted.
OS_TmrTask()任务的大部分工作都是在锁调度器的状态下进行的。然而,因为队列会被重新分配(依次排序),所以遍历这个队列的时间会非常短的,也就是临界段会非常短的。
Note
that OS_TmrTask() does most of its work with the scheduler locked. However,
because the list is sorted, and the search through the list terminates as soon
as there no longer is a match, the critical section should be fairly short.
当定时器的计数值递减为 0 时定时器期满,并执行回调函数。回调函数由用户定义。
Timers
are down counters that perform an action when the counter reaches zero. The
action is provided by the user through a callback function.
uC/OS-III 允许用户建立任意数量的定时器(只限制于处理器的RAM 大小)。
回调函数在定时器任务中被调用,且运行回调函数时调度器处于被锁状态。回调函数越短越好,且不能在回调函数中等待事件发生。否则定时器任务会被挂起,导致定时器任务崩溃。
μC/OS-III
allows application code to create any number of timers (limited only by the
amount of RAM available).
The
callback functions are executed in the context of the timer task with the
scheduler locked. Keep callback functions as short and as fast as possible and
do not have the callbacks make blocking calls.
这个章节会介绍共享资源相关的服务。共享资源可以是:变量(静态的或全局的)、结构体、内存空间、I/O 等。
This
chapter will discuss services provided by μC/OS-III to manage shared resources.
A shared resource is typically a variable (static or global), a data structure,
table (in RAM), or registers in an I/O device.
我们推荐用 mutex 保护共享资源。在这个章节中,其它的方法也会被介绍。
When
protecting a shared resource it is preferred to use mutual exclusion
semaphores, as will be described in this chapter. Other methods are also
presented.
多个任务可能会同时要求占用资源:内存空间、全局变量、指针、缓冲区、列表、环形缓冲区等。通过共享资源,任务间通信将会比较简单。当然,避免任务间的竞争和防止资源被破坏是非常重要的。
Tasks
can easily share data when all tasks exist in a single address space and can
reference global variables, pointers, buffers, linked lists, ring buffers, etc.
Although sharing data simplifies the exchange of information between tasks, it
is important to ensure that each task has exclusive access to the data to avoid
contention and data corruption.
例如,如果用实现手表的计时功能,那么就需有小时、分钟、秒。可定义为 TimeOfDay()任务如列表 13-1 所示
For
example, when implementing a module that performs a simple time-of-day
algorithm in software, the module obviously keeps track of hours, minutes and
seconds. The TimeOfDay() task may appear as that shown in Listing 13-1.
想象一下,如果这个任务的分钟递增到 60 被设置为 0,且将要递增小时的时候,中断发生了,高优先级任务抢占 CPU。这时高优先级任务读取当前时间,会是什么样的情况?因为,在中断前小时没有被递增,所以高优先级任务读到的值是错误的,它会慢了整整一个小时。
Imagine
if this task was preempted by another task because an interrupt occurred, and,
the other task was more important than the TimeOfDay() task) after setting the
Minutes to 0. Now imagine what will happen if this higher priority task wants
to know the current time from the time-of-day module. Since the Hours were not
incremented prior to the interrupt, the higher-priority task will read the time
incorrectly and, in this case, it will be incorrect by a whole hour.
让 TimeOfDay()任务中更新时间部分的代码具有不可分割性。这些变量可以被认为是共享资源,且代码要访问这些变量时必须以临界段的形式看待这些变量。uC/OS-III 提供 mutex 服务保护这些共享资源,并创建临界段。
The
code that updates variables for the TimeOfDay() task must treat all of the
variables indivisibly (or atomically) whenever there is possible preemption.
Time-of-day variables are considered shared resources and any code that
accesses those variables must have exclusive access through what is called a
critical section. μC/OS-III provides services to protect shared resources and
enables the easy creation of critical sections.

大部分独占资源的方法都是创建临界段:关中断方式、锁调度器方式、信号量方式、mutex 方式。采用哪种保护机制决定于访问共享资源的代码长度,如表 13-1 所示
The most
common methods of obtaining exclusive access to shared resources and to create critical
sections are: ■ disabling interrupts■ disabling the scheduler■ using semaphores■
using mutual exclusion semaphores (a.k.a. a mutex)。The mutual exclusion mechanism used depends on how fast the
code will access a shared resource, as shown in Table 13-1.
|
资源共享方式 Resource Sharing Method |
什么时候该用 When should you use? |
|
关中断方式 Disable/Enable Interrupts |
能很快地结束访问共享资源,不推荐使用这种方法,因为会导致中断延迟 When access to shared resource is very quick (reading
from or writing to few variables) and access is faster than μC/OS-III’s interrupt
disable time. It is highly recommended to not use this method as it
impacts interrupt latency. |
|
锁调度器方式 Locking/Unlocking the Scheduler |
访问共享资源比较久时 When access time to the shared resource is longer
than μC/OS-III’s interrupt disable time, but shorter than μC/OS-III’s scheduler lock time. Locking the scheduler has the same effect as making
the task that locks the scheduler the highest-priority task. It is recommended not to use this method since it
defeats the purpose of using μC/OS-III.
However, it is a better method than disabling interrupts, as it does not
impact interrupt latency. |
|
信号量方式 Semaphores |
当该共享资源经常被多个任务使用时采用。但信号量可能会造出优先级倒置。然而,信号量方式的执行时间少于 mutex 方式 When all tasks that need to access a shared resource
do not have deadlines. This is because semaphores may cause unbounded
priority inversions (described later). However, semaphore services are
slightly faster (in execution time) than mutual-exclusion semaphores. |
|
mutex 方式 Mutual Exclusion Semaphores |
推荐使用这种方法访问共享资源,尤其当任务要访问的共享资源有截止时间。 This is the preferred method for accessing shared resources,
especially if the tasks that need to access a shared resource have deadlines. uC/OS-III 的 mutex 有内置的优先级,这样可防止优先级倒置。 Remember that μC/OS-III’s mutual exclusion semaphores
have a built-in priority inheritance mechanism, which avoids unbounded priority
inversions. 然而,mutex 方式慢于信号量方式,多了个操作:需改变信号量的优先级 However, mutual exclusion semaphore services are
slightly slower (in execution time) than semaphores since the priority of the
owner may need to be changed, which requires CPU processing. |
表 13-1 资源共享
独占共享资源的最快和最简单方法是关中断,如列表 13-2 所示
The
easiest and fastest way to gain exclusive access to a shared resource is by
disabling and enabling interrupts, as shown in the pseudo-code in Listing 13-2.

μC/OS-III
uses this technique (as do most, if not all, kernels) to access certain
internal variables and data structures, ensuring that these variables and data
structures are manipulated atomically.
然而,关/开中断是和 CPU 相关的操作,其相关代码被放在与 CPU 相关的文件中(见 CPU.H)。uC/OS-III 中与 CPU 相关的模块叫做 uC/CPU。每种架构的 CPU 都需要设置相适应的 uC/CPU 文件。
However,
disabling and enabling interrupts are actually CPU-related functions rather
than OS-related functions and functions in CPU-specific files are provided to
accomplish this (see the CPU.H file of the processor being used). The services
provided in the CPU module are called μC/CPU. Each different target CPU
architecture has its own set of μC/CPU-related files.

L13-3(1)当使用开/关中断宏时 CPU_SR_ALLOC()宏是必需的。这个宏为一个本地的变量分配了存储空间用于保存关中断前的 CPU 状态寄存器 SR。{临界段时关中断前只需保存 SR}
L13-3(1)
The CPU_SR_ALLOC() macro is required when the other two macros that disable/enable
interrupts are used. This macro simply allocates storage for a local variable
to hold the value of the current interrupt disable status of the CPU. If
interrupts are already disabled we do not want to enable them upon exiting the
critical section.
L13-3(2)CPU_CRITICAL_ENTER()关全局中断。
L13-3(2) CPU_CRITICAL_ENTER() saves the current
state of the CPU interrupt disable flag(s) in the local variable allocated by
CPU_SR_ALLOC() and disables all maskable interrupts.
L13-3(3)访问临界段时不会被中断或任务打断,因为中断被关闭。
换句话说,临界段的操作是原子性的。
L13-3(3) The critical section of code is then
accessed without fear of being changed by either an ISR or another task because
interrupts are disabled. In other words, this operation is now atomic.
L13-3(4)CPU_CRITICAL_EXIT()从这个本地变量中恢复关中断前的 CPU 状态,并开全局中断。
L13-3(4) CPU_CRITICAL_EXIT() restores the
previously saved interrupt disable status of the CPU from the local variable.
CPU_CRITICAL_ENTER()和 CPU_CRITICAL_EXIT()经常成对地使用。关中断时间越短越好,不然会影响系统响应外部事件的及时性。
CPU_CRITICAL_ENTER()
and CPU_CRITICAL_EXIT() are always used in pairs. Interrupts should be disabled
for as short a time as possible as disabling interrupts impacts the response of
the system to interrupts. This is known as interrupt latency. Disabling and
enabling is used only when changing or copying a few variables.
当临界段很短时可以使用关中断方法。
Note,
this is the only way that a task can share variables or data structures with an
ISR. μC/CPU provides a way to actually measure interrupt latency.
注意的是:只有这种方法才可以让任务和 ISR 共享资源。
When
using μC/OS-III, interrupts may be disabled for as much time as μC/OS-III does,
without affecting interrupt latency. Obviously, it is important to know how
long μC/OS-III disables interrupts, which depends on the CPU used.
Although
this method works, avoid disabling interrupts as it affects the responsiveness
of the system to real-time events.
如果任务不需要和 ISR 共享资源,就可以通过锁调度器来访问共享资源。如列表 13-4 所示。
If
the task does not share variables or data structures with an ISR, disable and
enable μC/OS-III’s scheduler while accessing the resource, as shown in Listing
13-4.

使用这种方法,多个任务就可以无竞争的访问共享资源。注意,只有调度器被锁,中断是使能的,如果在处理临界段时中断发生,ISR 程序就会被执行。在 ISR 的末尾,uC/OS-III 会返回原任务(即使 ISR 中有高优先级任务被就绪)。
Using
this method, two or more tasks share data without the possibility of
contention. Note that while the scheduler is locked, interrupts are enabled and
if an interrupt occurs while in the critical section, the ISR is executed
immediately. At the end of the ISR, the kernel always returns to the
interrupted task even if a higher priority task is made ready to run by the
ISR. Since the ISR returns to the interrupted task, the behavior of the kernel
is similar to that of a non-preemptive kernel (while the scheduler is locked).
OSSchedLock()和 OSSchedUnlock()可以被嵌套多达 250 级。当OSSchedUnlock()与 OSSchedLock()被调用的次数相同时调度器才被开启。
OSSchedLock()
and OSSchedUnlock() can be nested up to 250 levels deep. The scheduler is
invoked only when OSSchedUnlock() is called the same number of times the
application called OSSchedLock().
直到调度器开启,uC/OS-III 切换到高优先级任务。
After
the scheduler is unlocked, μC/OS-III performs a context switch if a higher
priority task is ready to run.
uC/OS-III 不允许用户在锁调度器时堵塞呼叫{不能在锁调度器时等待某事件的发生}。不然程序会崩溃。
μC/OS-III
will not allow the user to make blocking calls when the scheduler is locked. If
the application were able to make blocking calls, the application would most
likely fail.
虽然这个方法用起来挺好,然而锁调度器影响了抢占式内核的初衷。
Although
this method works well, avoid disabling the scheduler as it defeats the purpose
of having a preemptive kernel. Locking the scheduler makes the current task the
highest priority task.
将信号量用于同步的概念是荷兰的电脑科学家 Edgser Dijkstra 在 1959 年发明的。在电脑软件中,信号量是一种用于多任务调度的协议机制。最初用于控制共享资源的访问,现在用于同步(详见第 14 章“同步”)。然而,介绍信号量是如何被用于控制共享资源是非常必要的。
A
semaphore originally was a mechanical signaling mechanism. The railroad
industry used the device to provide a form of mutual exclusion for railroads
tracks shared by more than one train. In this form, the semaphore signaled
trains by closing a set of mechanical arms to block a train from a section of
track that was currently in use. When the track became available, the arm would
swing up and the waiting train would then proceed.
信号量是一个“锁定机构”,代码需要获得钥匙才可以访问共享资源。占用该资源的任务不再使用该资源并释放资源时,其它任务才能够访问这个资源。
The
notion of using a semaphore in software as a means of synchronization was
invented by the Dutch computer scientist Edgser Dijkstra in 1959. In computer
software, a semaphore is a protocol mechanism offered by most multitasking
kernels. Semaphores, originally used to control access to shared resources, now
are used for synchronization as described in Chapter 14, “Synchronization” on
page 251. However, it is useful to describe how semaphores can be used to share
resources. The pitfalls of semaphores will be discussed in a later section.
A
semaphore was originally a “lock mechanism” and code acquired the key to this
lock to continue execution. Acquiring the key means that the executing task has
permission to enter the section of otherwise locked code. Entering a section of
locked code causes the task to wait until the key becomes available.
通常有两种类型的信号量:二值信号量和多值信号量。二值信号量的值只能是 0 或 1.多值信号量计数值可以是 0 到 4294967295(依赖于计数值是 8 位,16 位或 32 位)。特别的,uC/OS-III 中的信号量计数值最大为 OS_SEM_CTR(见 OS_TYPE.H)。根据信号量计数值, uC/OS-III 可以知道有该信号量可以再被多少个任务获得。
Typically,
two types of semaphores exist: binary semaphores and counting semaphores. As
its name implies, a binary semaphore can only take two values: 0 or 1. A
counting semaphore allows for values between 0 and 255, 65,535, or
4,294,967,295, depending on whether the semaphore mechanism is implemented
using 8, 16, or 32 bits, respectively. For μC/OS-III, the maximum value of a
semaphore is determined by the data type OS_SEM_CTR (see OS_TYPE.H), which can
be changed as needed (assuming μC/OS-III’s source code is available). Along
with the semaphore’s value, μC/OS-III also keeps track of tasks waiting for the
semaphore’s availability.
只有任务才允许使用信号量,ISR 是不允许的。
Only tasks are allowed to use semaphores when semaphores are used for sharing resources; ISRs are not allowed.
信号量是内核对象,通过数据类型 OS_SEM 定义(见 OS.H)。应用中可以有任意多个信号量(只限于处理器的 RAM)。
A
semaphore is a kernel object defined by the OS_SEM data type, which is defined
by the structure os_sem (see OS.H). The application can have any number of
semaphores (limited only by the amount of RAM available).
与信号量操作相关的函数如表 13-2 所示。在这个章节中,只介绍三个经常使用的函数:OSSemCreate(),OSSemPend(),OSSemPost()。其它的函数在附录 A 中介绍。当信号量被用于共享资源时,信号量相关函数只能被任务调用(绝不能被 ISR 调用)。但将信号量用于标记任务时可以被 ISR 调用。
There
are a number of operations the application is able to perform on semaphores,
summarized in Table 13-2. In this chapter, only three functions used most often
are discussed: OSSemCreate(), OSSemPend(), and OSSemPost(). Other functions are
described in Appendix A, “μC/OS-III API Reference Manual” on page 375. When
semaphores are used for sharing resources, every semaphore function must be
called from a task and never from an ISR. The same limitation does not apply
when using semaphores for signaling, as described later in Chapter 13.
|
函数名 |
功能 |
|
OSSemCreate() |
创建一个信号量 Create a semaphore. |
|
OSSemDel() |
删除一个信号量 Delete a semaphore. |
|
OSSemPend() |
等待某个信号量 Wait on a semaphore. |
|
OSSemPendAbort() |
取消等待某个信号量 Abort the wait on a semaphore. |
|
OSSemPost() |
释放或标记信号量 Release or signal a semaphore. |
|
OSSemSet() |
设置信号量计数值 Force the semaphore count to a desired value. |
表 13-2 信号量相关的 API 函数总结
13-3-1 二值信号量
任务要访问共享资源就必须执行一个等待操作。如果信号量是有效的(信号量计数值大于 0),信号量计数值递减,任务访问该共享资源。如果信号量计数值为 0,任务会被放入挂起队列中等待该信号量有效。uC/OS-III 允许设置等待的时限。如果等待超时,该挂起任务会被就绪,等待该信号量的"pend"函数会返回一个错误代号。
A
task that wants to acquire a resource must perform a Wait (or Pend) operation.
If the semaphore is available (the semaphore value is greater than 0), the
semaphore value is decremented, and the task continues execution (owning the
resource). If the semaphore’s value is 0, the task performing a Wait on the
semaphore is placed in a waiting list. μC/OS-III allows a timeout to be
specified. If the semaphore is not available within a certain amount of time,
the requesting task is made ready to run, and an error code (indicating that a
timeout has occurred) is returned to the caller.
任务通过"post"函数释放信号量。如果没有任务在等待这个信号量,信号量计数值会被递增。如果有任务在等待这个信号量,其中高优先级的挂起任务被就绪,但信号量计数值不会递增。
A
task releases a semaphore by performing a Signal (or Post) operation. If no
task is waiting for the semaphore, the semaphore value is simply incremented.
If there is at least one task waiting for the semaphore, the highest-priority
task waiting on the semaphore is made ready to run, and the semaphore value is
not incremented. If the readied task has a higher priority than the current
task (the task releasing the semaphore), a context switch occurs and the
higher-priority task resumes execution. The current task is suspended until it
again becomes the highest-priority task that is ready to run.
如列表 13-5 所示
The
operations described above are summarized using the pseudo-code shown in
Listing 13-5.

L13-5(1)定义一个信号量,信号量的数据类型必须是 OS_SEM。
L13-5(1) The application must declare a semaphore
as a variable of type OS_SEM. This variable will be referenced by other
semaphore services.
L13-5(2)通过调用 OSSemCreate()创建一个信号量,将信号量地址传递给函数的第一个参数。信号量必须在创建后才能被其他任务使用。在这里,信号量是在 main()函数中被创建的,也可以在任务中被创建。
L13-5(2) Create a semaphore by calling
OSSemCreate() and pass the address to the semaphore allocated in (1). The
semaphore must be created before it can be used by other tasks. Here, the
semaphore is initialized in startup code (i.e., main ()), however it could also
be initialized by a task (but it must be initialized before it is used).
L13-5(3)为信号量分配一个名字,调试时就很容易识别。名字可以存储于 ROM,因为 ROM 的空间远大于 RAM。如果需要在程序运行时改变信号量的名字,将名字以数组的形式存在 RAM 中,并传递数组地址给 OSSemCreate()。当然,该数组必须以空字符截止。
L13-5(3) Assign an ASCII name to the semaphore,
which can be used by debuggers or μC/Probe to easily identify the semaphore.
Storage for the ASCII characters is typically in ROM, which is typically more
plentiful than RAM. If it is necessary to change the name of the semaphore at
runtime, store the characters in an array in RAM and simply pass the address of
the array to OSSemCreate(). Of course, the array must be NUL terminated.
L13-5(4)设置信号量计数值。当共享资源只能有被一个任务占有时设置信号量计数值为 1.
L13-5(4) Specify the initial value of the
semaphore. Initialize the semaphore to 1 when the semaphore is used to access a
single shared resource (as in this example).
L13-5(5)根据信号量的创建结果 OSSemCreate()返回一个错误代号。
如果所有的参数都是有效的,错误代号为 OS_ERR_NONE。
L13-5(5) OSSemCreate() returns an error code based
on the outcome of the call. If all the arguments are valid, err will contain
OS_ERR_NONE. Refer to the description of OSSemCreate() in Appendix A,
“μC/OS-III API Reference Manual” on page 375 for a list of other error codes
and their meaning.

L13-6(6)通过调用 OSSemPend()函数等待一个信号量。任务必须指定所等待的信号量,且这个信号量之前已经被创建。
L13-6(6) The task pends (or waits) on the
semaphore by calling OSSemPend(). The application must specify the desired
semaphore to wait upon, and the semaphore must have been previously created.
L13-6(7)这个参数是等待信号量的期限值,以时基为最小单位。事实上,等待期限决定于时基频率。当时基频率为 1000Hz 时,10 个时基的期限等待时间是 10 毫秒。设置期限值为 0 意味着任务将一直等待者信号量。
L13-6(7) The next argument is a timeout specified
in number of clock ticks. The actual timeout depends on the tick rate. If the
tick rate (see OS_CFG_APP.H) is set to 1000, a timeout of 10 ticks represents
10 milliseconds. Specifying a timeout of zero (0) means waiting forever for the
semaphore.
L13-6 ( 8 )第三个参数设置了挂起的方式。有两种方式: OS_OPT_PEND_BLOCKING 和 OS_OPT_PEND_NON_BLOCKING。第一种方式:有等待挂起。在信号量无效时,调用 OSSemPend()后任务会被挂起直到接收到信号量或期满。第二种方式:无等待挂起。在信号量无效时,调用 OSSemPend()后回迅速返回(任务不被挂起)。
在使用信号量保护共享资源时第二种方式很少被用到。
L13-6(8) The third argument specifies how to wait.
There are two options: OS_OPT_PEND_BLOCKING and OS_OPT_PEND_NON_BLOCKING. The
blocking option means that if the semaphore is not available, the task calling
OSSemPend() will wait until the semaphore is posted or until the timeout
expires. The non-blocking option indicates that if the semaphore is not
available, OSSemPend() will return immediately and not wait. This last option
is rarely used when using a semaphore to protect a shared resource.
L13-6(9)信号量被提交时, uC/OS-III 读取当前时间戳,并当
OSSemPend()返回时返回这个时间戳。这个功能可以让用户知道信号量是什么时候被提交的。任务接收到信号量后,调用 OS_TS_GET() 读取当前的时间戳并计算差值,就知道等待时间了
{信号量被提交后到任务接收到信号量所等待的时间}。
L13-6(9) When the semaphore is posted, μC/OS-III
reads a “timestamp” and returns this timestamp when OSSemPend() returns. This
feature allows the application to know “when” the post happened and the
semaphore was released. At this point, OS_TS_GET() is read to get the current
timestamp and compute the difference, indicating the length of the wait.
L13-6(10)根据信号量的创建结果 OSSemPend()返回一个错误代号。如果信号量创建成功,错误代号为 OS_ERR_NONE。如果创建失败,错误代号会包含错误的原因。检测错误代号值是很有必要的,因为其它任务可能删除了这个信号量或取消这个任务的挂起状态。然而,在程序运行时不推荐删除内核对象,因为这可能会导致严重的后果。
L13-6(10) OSSemPend() returns an error code based on the outcome
of the call. If the call is successful, err will contain OS_ERR_NONE. If not,
the error code will indicate the reason for the error. See Appendix A,
“μC/OS-III API Reference Manual” on page 375 for a list of possible error code
for OSSemPend(). Checking for error return values is important since other
tasks might delete or otherwise abort the pend. However, it is not a
recommended practice to delete kernel objects at run time as the action may
cause serious problems.
L13-6(11)当 OSSemPend()正确返回时,任务就可以访问这个共享资源。
L13-6(11) The resource can be accessed when OSSemPend()
returns, if there are no errors.
L13-6(12)资源访问结束后,任务调用 OSSemPost()释放这个信号量。
L13-6(12) When finished accessing the resource, simply
call OSSemPost() and specify the semaphore to be released.
L13-6(13)OS_OPT_POST_1 意味着这个信号量只能被单个任务占用。
L13-6(13) OS_OPT_POST_1 indicates that the semaphore is
signaling a single task, if there are many tasks waiting on the semaphore. In
fact, always specify this option when a semaphore is used to access a shared
resource.
L13-7(14)如大多数 uC/OS-III 函数一样,该地址对应的变量中存放函数返回的错误代号。
L13-6(14) As with most μC/OS-III functions, specify the
address of a variable that will receive an error message from the call.

L13-7(15)其它要访问共享资源的任务也要经过同样的步骤。
L13-7(15)
Another task wanting to access the shared resource needs to use the same
procedure to access the shared resource.
尤其在共享 I/O 时,信号量非常有用。想象一个如果两个任务共同发字符给打印机。打印机交错地打印这两个任务的字符。例如,任务一想要打印"I am Taks 1",任务二想要打印"I am Task 2",那么结果就可能为"I Ia amm TTasask k1 2"。
Semaphores
are especially useful when tasks share I/O devices. Imagine what would happen
if two tasks were allowed to send characters to a printer at the same time. The
printer would contain interleaved data from each task. For instance, the
printout from Task 1 printing “I am Task 1,” and Task 2 printing “I am Task 2,”
could result in “I Ia amm T Tasask k1 2”.
在这种情况下,可以用信号量并将信号量计数值设为 1(二值信号量)。规则是简单的:在访问打印机之前任务必须先获得该信号量。如图 13-1 显示了两个任务竞争,都想独占问打印机,设置一个锁,在访问打印机之前必须会得钥匙,信号量就是这个钥匙。
In
this case, use a semaphore and initialize it to 1 (i.e., a binary semaphore).
The rule is simple: to access the printer each task must first obtain the
resource’s semaphore. Figure 13-1 shows tasks competing for a semaphore to gain
exclusive access to the printer. Note that a key, indicating that each task
must obtain this key to use the printer, represents the semaphore symbolically.

上述例子表明任务必须获得对应的信号量才能访问资源。这是一种能保护临界段很好的方法。任务获得共享资源前必须先获得信号量。例如,一个 RS-232C 端口被多个任务共享,发送命令到目标器件并接收响应。如图 13-2 所示
The
above example implies that each task knows about the existence of the semaphore
to access the resource. It is almost always better to encapsulate the critical
section and its protection mechanism. Each task would therefore not know that
it is acquiring a semaphore when accessing the resource. For example, an
RS-232C port is used by multiple tasks to send commands and receive responses
from a device connected at the other end as shown in Figure 13-2.

函数CommSendCmd()被调用,它有三个参数:命令字符串、指向响应字符串的指针、时间期限(在规定时间内可能没有响应)。这个函数的伪代码如列表 13-8
The
function CommSendCmd() is called with three arguments: the ASCII string
containing the command, a pointer to the response string from the device, and
finally, a timeout in case the device does not respond within a certain amount
of time. The pseudo-code for this function is shown in Listing 13-8

每个任务都必须调用这个函数才能发送命令给RS-232C端口。假定这个信号量计数值已经被设置为 1(通过该器件的初始化函数)。第一个任务调用CommSendCmd()获得信号量,并发送命令,等待响应。此时端口处于忙状态,这时第二个任务想要发送命令,那么第二个任务就被挂起,直到信号量被释放。第一个任务释放信号量后,第二个任务获得信号量并被允许访问RS-232C端口。
Each
task that needs to send a command to the device must call this function. The
semaphore is assumed to be initialized to 1 (i.e., available) by the
communication driver initialization routine. The first task that calls
CommSendCmd() acquires the semaphore, proceeds to send the command, and waits
for a response. If another task attempts to send a command while the port is
busy, this second task is suspended until the semaphore is released. The second
task appears simply to have made a call to a normal function that will not
return until the function performs its duty. When the semaphore is released by
the first task, the second task acquires the semaphore and is allowed to use
the RS-232C port.
13-3-2 信号量计数值
当共享资源同时可以被多个任务访问时,信号量计数值用于标记共享资源能同时被多少个任务访问。例如,缓冲池可以被多个任务同时访问,如图 13-3,假定缓冲池中定义了 10 个缓冲区。任务通过调用 BufReq()获得一个缓冲区,任务通过调用 BufRel()释放一个缓冲区。伪代码如列表 13-9 所示。
A
counting semaphore is used when elements of a resource can be used by more than
one task at the same time. For example, a counting semaphore is used in the
management of a buffer pool, as shown in Figure 13-3. Assume that the buffer
pool initially contains 10 buffers. A task obtains a buffer from the buffer
manager by calling BufReq(). When the buffer is no longer needed, the task
returns the buffer to the buffer manager by calling BufRel(). The pseudo-code
for these functions is shown in Listing 13-9.
缓冲池允许 10 个任务各自占用一个缓冲区,所以信号量计数值被初始化为 10。当所有的缓冲区被分配完,任务再申请缓冲区时会被挂起直到信号量被释放。
The
buffer manager satisfies the first 10 buffer requests because the semaphore is
initialized to 10. When all buffers are used, a task requesting a buffer is
suspended until a buffer becomes available. Use μC/OS-III’s OSMemGet() and
OSMemPut() (see Chapter 17, “Memory Management” on page 323) to obtain a buffer
from the buffer pool.
当任务完成对缓冲区的使用后调用 BufRel() 释放缓冲区给缓冲区管理器,若有任务等待缓冲区,则直接将此缓冲区分配给任务。若没有任务等待缓冲区,这个缓冲区被插入到缓冲区队列,并将信号量计数值递增。通过缓冲区管理器的接口 BufReq() 和 BufRel(),调用者不必了解具体的实现细节。
When
a task is finished with the buffer it acquired, the task calls BufRel() to
return the buffer to the buffer manager and the buffer is inserted into the
linked list before the semaphore is signaled. By encapsulating the interface to
the buffer manager in BufReq() and BufRel(), the caller does not need to be
concerned with actual implementation details.


13-3-3 信号量需注意的事项
用信号量访问共享资源不会导致中断延迟。当任务在执行信号量所保护的共享资源时,ISR 或高优先级任务可以抢占该任务。
Using
a semaphore to access a shared resource does not increase interrupt latency. If
an ISR or the current task makes a higher priority task ready to run while
accessing shared data, the higher priority task executes immediately.
应用中可以有任意个信号量用于保护共享资源。然而,推荐将信号量用于 I/O 端口的保护,而不是内存地址。
An
application may have as many semaphores as required to protect a variety of different
resources. For example, one semaphore may be used to access a shared display,
another to access a shared printer, another for shared data structures, and
another to protect a pool of buffers, etc. However, it is preferable to use
semaphores to protect access to I/O devices rather than memory locations.
信号量经常被过度使用。很多情况下,访问一个简短的共享资源时不推荐使用信号量,请求和释放信号量会消耗 CPU 时间。通过关/ 开中断能更有效地执行这些操作。
Semaphores
are often overused. The use of a semaphore to access a simple shared variable
is overkill in most situations. The overhead involved in acquiring and
releasing the semaphore consumes valuable CPU time. Perform the job more
efficiently by disabling and enabling interrupts, however there is an indirect
cost to disabling interrupts: even higher priority tasks that do not share the
specific resource are blocked from using the CPU.
为了说明,假设两个任务共享一个32 位的整数变量。第一个任务将这个整数变量加 1,第二个任务将这个变量清零。考虑到执行这些操作用时很短,不需要使用信号量。执行这个操作前任务只需关中断,执行完毕后再开中断。若操作浮点数变量且处理器不支持硬件浮点操作时,就需要用到信号量。因为在这种情况下处理浮点数变量需较长时间。
Suppose,
for instance, that two tasks share a 32-bit integer variable. The first task
increments the variable, while the second task clears it. When considering how
long a processor takes to perform either operation, it is easy to see that a
semaphore is not required to gain exclusive access to the variable. Each task
simply needs to disable interrupts before performing its operation on the
variable and enable interrupts when the operation is complete. A semaphore
should be used if the variable is a floating-point variable and the microprocessor
does not support hardware floating-point operations. In this case, the time
involved in processing the floating-point variable may affect interrupt latency
if interrupts are disabled.
信号量会导致一种严重的问题:优先级反转。
Semaphores
are subject to a serious problem in real-time systems called priority
inversion, which is described in section 13-3-5 “Priority Inversions” on page
232.
13-3-4 信号量的结构
正如前面提到的,信号量是内核对象,通过数据类型 OS_SEM 定义,OS_SEM 源于结构体 os_sem(见 OS.H)。如表 13-10 所示。
As
previously mentioned, a semaphore is a kernel object as defined by the OS_SEM
data type, which is derived from the structure os_sem (see OS.H) as shown in
Listing 13-10.
uC/OS-III 中与信号量相关的代码都被放在 OS_SEM.C 中。通过设置 OS_CFG.H 中的 OS_CFG_SEM_EN 为 1 使能信号量。
The
services provided by μC/OS-III to manage semaphores are implemented in the file
OS_SEM.C. μC/OS-III licensees have access to the source code. In this case,
semaphore services are enabled at compile time by setting the configuration
constant OS_CFG_SEM_EN to 1 in OS_CFG.H.

L13-10(1)在 uC/OS-III 中,所有的结构体都会特定的数据类型,以“OS_”开头并且全部大写。
L13-10(1) In μC/OS-III, all structures are given a data
type. All data types start with “OS_” and are uppercase. When a semaphore is
declared, simply use OS_SEM as the data type of the variable used to declare
the semaphore.
L13-10(2)这个结构体的第一个变量是“Type”域,表明 uC/OS-III 识别所定义的是一个信号量。其它的内核对象也有“Type”域作为结构体的第一个变量。如果函数要调用一个内核对象,uC/OS-III 会检测所调用的内核对象的数据类型是否对应。例如,如果需要传递一个消息队列 OS_Q 给函数,但实际传递的是一个信号量 OS_SEM, uC/OS-III 就会检测出这是一个无效的参数,并返回错误代号。
L13-10(2) The structure starts with a “Type” field, which
allows it to be recognized by μC/OS-III as a semaphore. Other kernel objects
will also have a “.Type” as the first member of the structure. If a function is
passed a kernel object, μC/OS-III will confirm that it is being passed the
proper data type. For example, if passing a message queue (OS_Q) to a semaphore
service (for example OSSemPend()), μC/OS-III will recognize that an invalid
object was passed, and return an error code accordingly.
L13-10(3)每个内核对象都可以被赋予一个名字,名字有 ASCII 字符串组成,但必须以空字符结尾。
L13-10(3) Each kernel object can be given a name for
easier recognition by debuggers or μC/Probe. This member is simply a pointer to
an ASCII string, which is assumed to be NUL terminated.
L13-10(4)若有多个任务等待信号量,信号量就会将这些任务放入其挂起队列中。(见章节 10“挂起队列”)
L13-10(4) Since it is possible for multiple tasks to wait
(or pend) on a semaphore, the semaphore object contains a pend list as
described in Chapter 10, “Pend Lists (or Wait Lists)” on page 177.
L13-10(5)信号量中包含一个信号量计数变值。信号量计数值可以定义为 8 位,16 位,或 32 位,取决于 OS_TYPE.H 中的 OS_SEM_CTR 是如何被定义的。
L13-10(5) A semaphore contains a counter. As explained
above, the counter can be implemented as either an 8-, 16- or 32-bit value,
depending on how the data type OS_SEM_CTR is declared in OS_TYPE.H.
uC/OS-III 中二值信号量和多值信号量的定义是一样的。只有信号量被创建后才有区别。如果创建信号量时将信号量计数值被初始化为 1,那么它就是二值信号量。如果创建信号量时将信号量计数值初始化大于 1,那么它就是多值信号量。
μC/OS-III
does not make a distinction between binary and counting semaphores. The distinction
is made when the semaphore is created. If creating a semaphore with an initial
value of 1, it is a binary semaphore. When creating a semaphore with a value
> 1, it is a counting semaphore. In the next chapter, we discover that a
semaphore is more often used as a signaling mechanism and therefore, the
semaphore counter is initialized to zero.
L13-10(6)信号量中包含了一个时间戳变量,存储了上一次信号量被提交时的时间戳。当信号量被提交时,CPU 的时间戳被读取并存在信号量的时间戳变量中,当 OSSemPend()被调用时就能读取这个时间戳变量。
L13-10(6) A semaphore contains a timestamp used to indicate
the last time the semaphore was posted. μC/OS-III assumes the presence of a
free-running counter that allows the application to make time measurements.
When the semaphore is posted, the free-running counter is read and the value is
placed in this field, which is returned when OSSemPend() is called. The value
of this field is more useful when a semaphore is used as a signaling mechanism
(see Chapter 14, “Synchronization” on page 251), as opposed to a
resource-sharing mechanism.
即使用户了解 OS_SEM 的组成,用户代码也不能直接访问信号量中的变量。必须通过 uC/OS-III 提供的 API。
Even
if the user understands the internals of the OS_SEM data type, the application
code should never access any of the fields in this data structure directly.
Instead, always use the APIs provided with μC/OS-III.
信号量在使用之前必须被创建。
As
previously mentioned, semaphores must be created before they can be used by an
application.
在访问共享资源之前任务必须通过 OSSemPend()获得这个信号量。如列表 13-11 所示
A
task waits on a semaphore before accessing a shared resource by calling
OSSemPend() as shown in Listing 13-11 (see Appendix A, “μC/OS-III API Reference
Manual” on page 375 for details regarding the arguments).

L13-11(1)调用 OSSemPend(),该函数它首先检测传递给它的参数是否有效。
如果信号量计数值大于 0(OS_SEM.Ctr),参数均有效,信号量计数值会被递减,并且 OSSemPend()返回。任务就可以占用该共享资源。
L13-11(1)
When called, OSSemPend() starts by checking the arguments passed to this
function to make sure they have valid values.
If
the semaphore counter (.Ctr of OS_SEM) is greater than zero, the counter is
decremented and OSSemPend() returns. If OSSemPend() returns without error, then
the task now owns the shared resource.
如果选择了 OS_OPT_PEND_NON_BLOCKING 模式,当信号量无效时 OSSemPend()迅速返回错误代号。如果选择了 OS_OPT_PEND_BLOCKING 模式,信号量无效时任务会被放入该信号量的挂起队列中,并根据优先级排序,高优先级任务排在队列是首部。
When
the semaphore counter is zero, this indicates that another task owns the
semaphore, and the calling task may need to wait for the semaphore to be
released. If specifying OS_OPT_PEND_NON_BLOCKING as the option (the application
does not want the task to block), OSSemPend() returns immediately to the caller
and the returned error code indicates that the semaphore is unavailable. Use
this option if the task does not want to wait for the resource to be available,
and would prefer to do something else and check back later.
If
specifying the OS_OPT_PEND_BLOCKING option, the calling task will be inserted
in the list of tasks waiting for the semaphore to become available. The task is
inserted in the list by priority order and therefore, the highest priority task
waiting on the semaphore is at the beginning of the list.
如果设置了非 0 的等待期限,该任务也会被插入到时基队列中,等待期限为 0 意味着要永远地等待下去直到信号量被释放。大多数情况下,设置无限的等待时间。设置等待期限可以解决的死锁问题,这比在应用级中防止死锁(不要在同一时间占用多个信号量)好多了。
If
specifying a non-zero timeout, the task will also be inserted in the tick list.
A zero value for a timeout indicates that the user is willing to wait forever
for the semaphore to be released. Most of the time, specify an infinite timeout
when using the semaphore in resource sharing. Adding a timeout may temporarily
break a deadlock, however, there are better ways of preventing deadlock at the
application level (e.g., never hold more than one semaphore at the same time;
resource ordering; etc.).
The
scheduler is called since the current task is no longer able to run (it is
waiting for the semaphore to be released). The scheduler will then run the next
highest-priority task that is ready to run.
When
the semaphore is released and the task that called OSSemPend() is again the
highest-priority task, μC/OS-III examines the task status to determine the
reason why OSSemPend() is returning to its caller.
OSSemPend()返回的原因:
The
possibilities are:
1.获得了信号量
1.The
semaphore was given to the waiting task
2.其它任务取消了该任务的挂起
2.The pend was aborted by another task
3.等待超时
3.The semaphore was not posted within the specified timeout
4.信号量被删除
4) The semaphore was deleted
OSSemPend()的返回代号包含这些信息。
When
OSSemPend() returns, the caller is notified of the above outcome through an
appropriate error code.
L13—11(2)检测 OSSemPend()返回的错误代号,若为 OS_ERR_NONE 则表示任务获得这个信号量。
L13-11(2) If OSSemPend() returns with err set to OS_ERR_NONE,
assume that you now have access to the resource.
L13-11(3)任务对共享资源访问完毕后,必须调用 OSSemPost()释放这个信号量。同样的,OSSemPost()会检测传递给它的参数是否有效。
L13-11(2) If OSSemPend() returns with err set to OS_ERR_NONE,
assume that you now have access to the resource.
然后 OSSemPost()调用 OS_TS_GET()获得当前的时间戳,并保持在信号量中的变量中。再检查是否有其它任务等待这个信号量。
If
err contains anything else, OSSemPend() either timed out (if the timeout
argument was non-zero), the pend was aborted by another task, or the semaphore
was deleted by another task. It is always important to examine the returned
error code and not assume that everything went well.
L13-11(3) When the task is finished accessing the
resource, it needs to call OSSemPost() and specify the same semaphore. Again,
OSSemPost() starts by checking the arguments passed to this function to make
sure there are valid values.
OSSemPost()
then calls OS_TS_GET() to obtain the current timestamp so it can place that
information in the semaphore to be used by OSSemPend().
OSSemPost()
checks to see if any tasks are waiting for the semaphore. If not, OSSemPost()
simply increments p_sem->Ctr, saves the timestamp in the semaphore, and
returns.
If
there are tasks waiting for the semaphore to be released, OSSemPost() extracts
the highest-priority task waiting for the semaphore. This is a fast operation
as the pend list is sorted by priority order.
When
calling OSSemPost(), it is possible to specify as an option to not call the
scheduler. This means that the post is performed, but the scheduler is not
called even if a higher priority task waits for the semaphore to be released.
This allows the calling task to perform other post functions (if needed) and
make all posts take effect simultaneously without the possibility of context
switching in between each post.
13-3-5 优先级反转
优先级反转是实时系统中的一个常见问题,仅存在于基于优先级的抢占式内核中。图 13-4 显示了一种优先级反转的情况。任务 H 的优先级高于任务 M,任务 M 的优先级高于任务 L。
Priority
inversion is a problem in real-time systems, and occurs only when using a
priority-based preemptive kernel. Figure 13-4 illustrates a priority-inversion
scenario. Task H (high priority) has a higher priority than Task M (medium
priority), which in turn has a higher priority than Task L (low priority).

F13-4(1)任务 H(High 优先级)和任务 M(Medium 优先级)都在等待事件发生,任务 L(Low 优先级)正在被运行。
F13-4(1) Task H and Task M are both waiting for an
event to occur and Task L is executing.
F13-4(2)这时,任务 L 想申请信号量。
F13-4(2) At some point, Task L acquires a
semaphore, which it needs before it can access a shared resource.
F13-4(3)任务 L 访问共享资源。
F13-4(3) Task L performs operations on the
acquired resource.
F13-4(4)任务 H 所等待的事件发生,uC/OS-III 挂起任务 L。
F13-4(4) The event that Task H was waiting for
occurs, and the kernel suspends Task L and start executing Task H since Task H
has a higher priority.
F13-4(5)开始执行任务 H。
F13-4(5) Task H performs computations based on the
event it just received.
F13-4(6)现在任务 H 想要访问任务 L 所占用的共享资源(此时该共享资源被任务 L 占用)。因为该资源被任务 L 占用,任务 H 被放入挂起队列中等待这个信号量被释放。
F13-4(6) Task H now wants to access the resource
that Task L currently owns (i.e., it attempts to get the semaphore that Task L
owns). Because Task L owns the resource, Task H is placed in a list of tasks
waiting for the semaphore to be free.
F13-4(7)任务 L 被恢复并继续执行。
F13-4(7) Task L is resumed and continues to access the shared resource.
F13-4(8)任务 L 被任务 M 抢占因为任务 M 所等待的事件发生。
F13-4(8) Task L is preempted by Task M since the event
that Task M was waiting for occurred.
F13-4(9)任务 M 开始执行。
F13-4(9) Task M handles the event.
F13-4(10)任务 M 执行完毕,uC/OS-III 将 CPU 控制权交还给任务L。
F13-4(10) When Task M completes, the kernel relinquishes
the CPU back to Task L.
F13-4(11)任务 L 被执行。
F13-4(11) Task L continues accessing the resource.
F13-4(12)任务 L 执行完毕并释放资源。此时,任务 H 获得该资源,uC/OS-III上下文切换到任务 H。
F13-4(12) Task L finally finishes working with the resource
and releases the semaphore. At this point, the kernel knows that a
higher-priority task is waiting for the semaphore, and a context switch takes
place to resume Task H.
F13-4(13)任务 H 开始执行。
F13-4(13)Task
H has the semaphore and can access the shared resource.
可以看出,任务 H 在任务 L 后被执行,因为任务 H 等待任务 L所占用的资源。麻烦在于任务 M 抢占任务 L,若任务 M 需要执行很长时间,则任务 H 会被延迟很长时间才执行,这叫做优先级反转。
So,
what happened here is that the priority of Task H has been reduced to that of
Task L since it waited for the resource that Task L owned. The trouble begins
when Task M preempted Task L, further delaying the execution of Task H. This is
called an unbounded priority inversion. It is unbounded because any medium
priority can extend the time Task H has to wait for the resource. Technically,
if all medium-priority tasks have known worst-case periodic behavior and
bounded execution times, the priority inversion time is computable. This
process, however, may be tedious and would need to be revised every time the
medium priority tasks change.
可以通过提升任务 L 的优先级解决这种问题(只在该任务访问共享资源时),访问结束后就恢复任务的优先级。任务 L 的优先级需要被上升到任务 H 的优先级。
This
situation can be corrected by raising the priority of Task L, only during the
time it takes to access the resource, and restore the original priority level
when the task is finished. The priority of Task L should be raised up to the
priority of Task H. μC/OS-III contains a special type of semaphore that does
just that called a mutual-exclusion semaphore.
uC/OS-III 支持一种特殊类型的二值信号量叫做 mutex,用于解决优先级反转问题。图 13-5 显示了优先级反正是如何通过 mutex 解决的。
μC/OS-III
supports a special type of binary semaphore called a mutual exclusion semaphore
(also known as a mutex) that eliminates unbounded priority inversions.Figure
13-5 shows how priority inversions are bounded using a Mutex.

F13-5(1)任务 H 和任务 M 等待事件的发生,任务 L 被执行。
F13-5(1) Task H and Task M are both waiting for an
event to occur and Task L is executing.
F13-5(2)此时,任务 L 申请并获得一个 mutex。
F13-5(2) At some point, Task L acquires a mutex,
which it needs before it is able to access a shared resource.
F13-5(3)任务 L 被执行。
F13-5(3) Task L performs operations on the
acquired resource.
F13-5(4)任务 H 和任务 M 所等待的事件发生,任务 L 被抢占。
F13-5(4) The event that Task H waited for occurs
and the kernel suspends Task L and begins executing Task H since Task H has a
higher priority.
F13-5(5)任务 H 开始执行。
F13-5(5) Task H performs computations based on the
event it just received.
F13-5(6)任务 H 想要访问任务 L 占用的共享资源。考虑到任务 L 占用这个资源,uC/OS-III 提升任务 L 的优先级与任务 H 相同。这样就防止了任务 L 被任务 M 抢占(被中等优先级的任务抢占)。
F13-5(6) Task H now wants to access the resource
that Task L currently owns (i.e., it attempts to get the mutex from Task L).
Given that Task L owns the resource, μC/OS-III raises the priority of Task L to
the same priority as Task H to allow Task L to finish with the resource and
prevent Task L from being preempted by medium-priority tasks.
F13-5(7)任务 L 继续访问这个资源,但现在任务 L 的优先级等于任务 H 的优先级。注意的是任务 H 被挂起因为它要等待任务 L 释放 mutex。换句话说,任务 H 被插入到 mutex 的挂起队列中。
F13-5(7) Task L continues accessing the resource,
however it now does so while it is running at the same priority as Task H. Note
that Task H is not actually running since it is waiting for Task L to release
the mutex. In other words, Task H is in the mutex wait list.
F13-5(8)任务 L 对共享资源的访问执行完毕并释放 mutex。uC/OS-III 恢复任务 L 到原有的优先级。然后 uC/OS-III 将这个 mutex 交给任务 H。{并不是任务 L 已经全部执行完毕,而是当它释放信号量时就会发生调度而转到高优先级任务}
F13-5(8) Task L finishes working with the resource
and releases the mutex. μC/OS-III notices that Task L was raised in priority
and thus lowers Task L to its original priority. After doing so, μC/OS-III
gives the mutex to Task H, which was waiting for the mutex to be released.
F13-5(9)任务 H 开始执行。
F13-5(9) Task H now has the mutex and can access
the shared resource.
F13-5(10)任务 H 对共享资源的访问完毕,并释放这个 mutex。
F13-5(10) Task H is finished accessing the shared
resource, and frees up the mutex.
F13-5(11)任务 H 继续执行。
F13-5(11) There are no higher-priority tasks to execute,
therefore Task H continues execution.
F13-5(12)任务 H 执行完毕。uC/OS-III 将 CPU 控制权交给任务 M。
F13-5(12) Task H completes and decides to wait for an
event to occur. At this point, μC/OS-III resumes Task M, which was made ready
to run while Task H or Task L were executing.
F13-5(13) 任务 M 被执行。
F13-5(13) Task M executes.
现在没有优先级反转的问题了。当然,任务 L 占用共享资源的时间越短越好。
mutex 是一个内核对象,它被数据类型 OS_MUTEX 所定义,(OS_MUTEX 的原型是 os_mutex,见 OS.H)。应用中可以有任意个 mutex(仅限于处理器的 RAM)。只有任务才可以使用 mutex(ISR 不可以使用 mutex)。
Note
that there is no priority inversion, only resource sharing. Of course, the
faster Task L accesses the shared resource and frees up the mutex, the better.
μC/OS-III
implements full-priority inheritance and therefore if a higher priority
requests the resource, the priority of the owner task will be raised to the
priority of the new requestor.
A
mutex is a kernel object defined by the OS_MUTEX data type, which is derived
from the structure os_mutex (see OS.H). An application may have an unlimited
number of mutexes (limited only by the RAM available).
Only
tasks are allowed to use mutual exclusion semaphores (ISRs are not allowed).
uC/OS-III 允许任务嵌套占有 mutex。如果任务获得 mutex,那么它可以嵌套获得这个 mutex 多达 250 次。该任务需要释放相同次数才能释放掉这个mutex。在一些情况下,应用中很难知道任务调用了多少次 OSMutexPend()。如列表 13-12 所示
μC/OS-III
enables the user to nest ownership of mutexes. If a task owns a mutex, it can
own the same mutex up to 250 times. The owner must release the mutex an
equivalent number of times. In several cases, an application may not be
immediately aware that it called OSMutexPend() multiple times, especially if
the mutex is acquired again by calling a function as shown in Listing 13-12.


L13-12(1)任务请求并获得 mutex。OSMutexPend()设置嵌套计数值为 1。
L13-12(1) A task starts by pending on a mutex to access
shared resources. OSMutexPend() sets a nesting counter to 1.
L13-12(2)检测返回的错误代号,如果没有错误,任务 Mytask()占用共享资源 MySharedResource。
L13-12(2) Check the error return value. If no errors
exist, MyTask() owns MySharedResource.
L13-12(3)函数 MyLibFunction(),其中将访问共享资源。
L13-12(3) A function is called that will perform
additional work.
L13-12(4)函数 MyLibFunction()在访问共享资源前需获得 mutex。因为先前已经获得 mutex,所以没必要再次获得。但是,函数 MyLibFunction()可能被其它不需要访问共享资源的函数调用。 uC/OS-III 允许嵌套 mutex,所以MyLibFunction()可以再次被调用。嵌套计数值增加到 2.
L13-12(4) The designer of MyLibFunction() knows that, to
access MySharedResource, it must acquire the mutex. Since the calling task
already owns the mutex, this operation should not be necessary. However,
MyLibFunction() could have been called by yet another function that might not
need access to MySharedResource. μC/OS-III allows nested mutex pends, so this
is not a problem. The mutex nesting counter is thus incremented to 2.
L13-12(5)MyLibFunction()访问共享资源。
L13-12(5) MyLibFunction() can access the shared resource.
L13-12(6)mutex 被释放,嵌套计数值减为 1,OSMutexPost()返回, MyLibFunction()返回。mutex 还是被这个任务占用。
L13-12(6)
The mutex is released and the nesting counter is decremented back to 1. Since
this indicates that the mutex is still owned by the same task, nothing further
needs to be done, and OSMutexPost() simply returns. MyLibFunction() returns to
its caller.
L13-12(7)mutex 再次被释放,此时,嵌套计数值减为 0。其它任务可以获得这个 mutex。
L13-12(7)
The mutex is released again and, this time, the nesting counter is decremented
back to 0 indicating that other tasks can now acquire the mutex.
通常检测 OSMutexPend()的返回值,确保已经获得了 mutex。因为在这些情况下,OSMutexPend()也将返回错误代号:mutex 被删除、被其它任务调用 OSMutexPendAbort()取消了对该 mutex 的申请。
Always
check the return value of OSMutexPend() (and any kernel call) to ensure that
the function returned because you properly obtained the mutex, and not because
the return from OSMutexPend() was caused by the mutex being deleted, or because
another task called OSMutexPendAbort() on this mutex.
一般而言,不用在临界段中调用函数。
As a
general rule, do not make function calls in critical sections.
表 13-3 列出了与 mutex 相关的函数。然而,在这个章节中,我们只介绍三个经常用到的函数 OSMutexCreate(),OSMutexPend(), OSMutexPost()。其它任务在附录 A 中介绍。
All
mutual exclusion semaphore calls should be in the leaf nodes of the source code
(e.g., in the low level drivers that actually touches real hardware or in other
reentrant function libraries).
There
are a number of operations that can be performed on a mutex, as summarized in
Table 13-3. However, in this chapter, we will only discuss the three functions
that most often used: OSMutexCreate(), OSMutexPend(), and OSMutexPost(). Other
functions are described in Appendix A, “μC/OS-III API Reference Manual” on page
375.
表 13-3 mutex 的 API 总结
|
函数名 |
功能 |
|
OSMutexCreate() |
创建一个 mutex Create a mutex |
|
OSMutexDle() |
删除一个 mutex Delete a mutex |
|
OSMutexPend() |
等待一个 mutex Wait on a mutex |
|
OSMutexPendAbort() |
任务取消等待 mutex Abort the wait on a mutex |
|
OSMutexPost() |
释放 mutex Release a mutex |
13-4-1 mutex 的内部机制
A
mutex is a kernel object defined by the OS_MUTEX data type, which is derived
from the structure os_mutex (see OS.H) as shown in Listing 13-13:

L13-13(1)在 uC/OS-III 中,所有的结构体都会被定义一个数据类型。所有的数据类型以"OS_"开头且全部大写。用数据类型 OS_MUTEX 定义 mutex。
L13-13(1) In μC/OS-III, all structures are given a data
type. All data types begin with “OS_” and are uppercase. When a mutex is
declared, simply use OS_MUTEX as the data type of the variable used to declare
the mutex.
L13-13(2)结构体开始于"Type"域,让 uC/OS-III 能识别它是一个 mutex。
L13-13(2) The structure starts with a “Type” field, which
allows it to be recognized by μC/OS-III as a mutex. Other kernel objects may
also have a “.Type” as the first member of the structure. If a function is
passed a kernel object, μC/OS-III will be able to confirm that it is being
passed the proper data type. For example, if passing a message queue (OS_Q) to
a mutex service (for example OSMutexPend()), μC/OS-III will recognize that the
application passed an invalid object and return an error code accordingly.
L13-13(3)每个内核对象都被分配一个名字。
L13-13(3) Each kernel object can be given a name to make
them easier to recognize by debuggers or μC/Probe. This member is simply a
pointer to an ASCII string, which is assumed to be NUL terminated.
L13-13(4)因为可能有多个任务等待这个 mutex,mutex 中包含了一个挂起队列用于存放等待该 mutex 的任务。
L13-13(4) Because it is possible for multiple tasks to
wait (or pend on a mutex), the mutex object contains a pend list as described
in Chapter 10, “Pend Lists (or Wait Lists)” on page 177.
L13-13(5)如果任务占用这个 mutex,那么该变量.OwnerTCBPtr 会指向占用这个 mutex 的任务的 OS_TCB。
L13-13(5) If the mutex is owned by a task, it will point
to the OS_TCB of that task.
L13-13(6)如果任务占用这个 mutex,那么该变量 OwnerOriginalPrio 中存放着任务的原优先级,当占用 mutex 任务的优先级被提升时就会用到这个变量。
L13-13(6) If the mutex is owned by a task, this field
contains the “original” priority of the task that owns the mutex. This field is
required in case the priority of the task must be raised to a higher priority
to prevent unbounded priority inversions.
L13-13(7)uC/OS-III 允许任务占用同样的 mutex 多达 250 次。但也需要释放相同次数才能真正释放调这个 mutex。
L13-13(7) μC/OS-III allows a task to “acquire” the same
mutex multiple times. In order for the mutex to be released, the owner must
release the mutex the same number of times that it was acquired. Nesting can be
performed up to 250-levels deep.
L13-13(8)mutex 中的变量 TS 用于保存该 mutex 最后一次被释放的时间戳。当 mutex 被释放,读取时基计数值并存放到该变量中。
L13-13(8) A mutex contains a timestamp, used to indicate
the last time it was released. μC/OS-III assumes the presence of a free-running
counter that allows applications to make time measurements. When the mutex is
released, the free-running counter is read and the value is placed in this
field, which is returned when OSMutexPend() returns.
用户代码不能直接访问这个结构体。必须通过 uC/OS-III 提供的API 访问。
mutex 被使用前必须被创建。列表 13-14 显示了如何创建一个 mutex。
Application
code should never access any of the fields in this data structure directly.
Instead, always use the APIs provided with μC/OS-III.
A
mutual exclusion semaphore (mutex) must be created before it can be used by an
application. Listing 13-14 shows how to create a mutex.

L13-14(1)定义一个 OS_MUTEX 类型的变量。
L13-14(1) The application must declare a variable of type
OS_MUTEX. This variable will be referenced by other mutex services.
L13-14(2)调用 OSMutexCreate()创建一个 mutex,传递变量地址到该函数的第一个参数。
L13-14(2) Create a mutex by calling OSMutexCreate() and
pass the address to the mutex allocated in (1).
L13-14(3)分配一个名字给 mutex。
L13-14(3) Assign an ASCII name to the mutex, which can be
used by debuggers or μC/Probe to easily identify this mutex. There are no
practical limits to the length of the name since μC/OS-III stores a pointer to
the ASCII string, and not to the actual characters that makes up the string.
L13-14(4)OSMutexCreate()基于创建的结果返回一个错误代号。如果函数中所有的参数都是有效的,那么错误代号将是 S_ERR_NONE。
L13-14(4) OSMutexCreate() returns an error code based on
the outcome of the call. If all the arguments are valid, err will contain
OS_ERR_NONE.
注意:mutex 是二值信号量,所以没必要初始化计数值。
Note
that since a mutex is always a binary semaphore, there is no need to initialize
a mutex counter.
任务占用共享资源前必须获得 mutex。通过调用 OSMutexPend()申请这个信号量。如列表 13-15 所示
A
task waits on a mutual exclusion semaphore before accessing a shared resource
by calling OSMutexPend() as shown in Listing 13-15 (see Appendix A, “μC/OS-III
API Reference Manual” on page 375 for details regarding the arguments).

L13-15(1)调用 OSMutexPend()时先检测传递给它的参数释放有效。
L13-15(1)
When called, OSMutexPend() starts by checking the arguments passed to this
function to make sure they have valid values.
如果 mutex 有效,OSMutexPend()将 mutex 分配给该任务,并将该任务的OS_TCB 地址放入 mutex 的指针 OwnerTCPPtr 中,将该任务的优先级放入mutex 的变量 OwnerOriginalPrio 中,设置 mutex 的嵌套计数值为 1。然后 OSMutexPend()返回的错误代号为 OS_ERR_NONE。
If
the mutex is available, OSMutexPend() assumes the calling task is now the owner
of the mutex and stores a pointer to the task’s OS_TCB in
p_mutex->OwnerTCPPtr, saves the priority of the task in
p_mutex->OwnerOriginalPrio, and sets a mutex nesting counter to 1.
OSMutexPend() then returns to its caller with an error code of OS_ERR_NONE.
如果该任务已经占用这个 mutex,OSMutexPend()只是简单地将嵌套计数值递增。并返回错误代号 OS_ERR_MUTEX_OWNER。
If
the task that calls OSMutexPend() already owns the mutex, OSMutexPend() simply
increments a nesting counter. Applications can nest calls to OSMutexPend() up
to 250-levels deep. In this case, the error returned will indicate
OS_ERR_MUTEX_OWNER.
如果其它任务已经占用这个 mutex,该任务调用"pend"函数时为
OS_OPT_PEND_NON_BLOCKING 模式,OSMutexPend()立即返回错误代号而不被挂起,因为任务不想等到该 mutex 被释放。
If the mutex is already owned by
another task and OS_OPT_PEND_NON_BLOCKING is specified, OSMutexPend() returns
since the task is not willing to wait for the mutex to be released by its
owner.
如果 mutex 已被更低优先级任务占用,uC/OS-III 会提升这个任务的优先级与该任务的优先级相同。
If
the mutex is owned by a lower-priority task, μC/OS-III will raise the priority
of the owner to match the priority of the current task.
如果其它高优先级任务已经占用这个 mutex,该任务调用"pend"函数时为 OS_OPT_PEND_BLOCKING 模式,该任务会被放入挂起队列直到 mutex 被释放。任务在队列中根据优先级被排序,最高优先级排在队列的首部。
If
specifying OS_OPT_PEND_BLOCKING as the option, the calling task will be
inserted in the list of tasks waiting for the mutex to be available. The task
is inserted in the list by priority order and the highest priority task waiting
on the mutex is at the beginning of the list.
如果设置的等待期限为非 0,任务还会被插入到时基队列。如果等待期限为 0,则该任务不会被插入时基队列(也就是说任务会一直等待该 mutex 被释放)。
If
further specifying a non-zero timeout, the task will also be inserted in the
tick list. A zero value for a timeout indicates a willingness to wait forever
for the mutex to be released.
调度器被调用,因为当前任务不再被执行(它在等待 mutex)。mutex 被释放后,任务获得 mutex。需检测为什么 OSMutexPend()会返回,
The
scheduler is then called since the current task is no longer able to run (it is
waiting for the mutex to be released). The scheduler will then run the next
highest-priority task that is ready to run.
When
the mutex is finally released and the task that called OSMutexPend() is again
the highest-priority task, a task status is examined to determine the reason
why OSMutexPend() is returning to its caller.
有以下 4 种可能:
1)任务获得 mutex
2)在 mutex 中的等待任务被其它任务取消等待
3)等待超时
4)这个 mutex 被删除
The
possibilities are:
1) The mutex was given to the waiting task
2) The pend was aborted by another task
3) The mutex was not posted within the
specified timeout
4) The mutex was deleted
可以通过 OSMutexPend()函数返回的错误代号可以知道执行该函数的结果。
When
OSMutexPend() returns, the caller is notified of the outcome through an
appropriate error code.
L13-15(2)如果 OSMutexPend()返回的错误代号为 OS_ERR_NONE,就表明该 mutex 被任务占用。如果错误代号为其它值,OSMutexPend() 就会暂停。检测 OSMutexPend()返回的错误代号是很重要的。
L13-15(2) If OSMutexPend() returns with err set to
OS_ERR_NONE, assume that the calling task now owns the resource and can proceed
with accessing it. If err contains anything else, then OSMutexPend() either
timed out (if the timeout argument was non-zero), the pend was aborted by
another task, or the mutex was deleted by another task. It is always important
to examine returned error codes and not assume everything went as planned.
If
“err” is OS_ERR_NESTING_OWNER, then the caller attempted to pend on the same
mutex.
L13-15(3)当任务完成对共享资源的访问后,就必须调用OSMutexPost()。同样的,OSMutexPost()先检测传递给它的参数。
L13-15(3) When your task is finished accessing the
resource, it must call OSMutexPost() and specify the same mutex. Again,
OSMutexPost() starts by checking the arguments passed to this function to make
sure they contain valid values.
OSMutexPost()调用 OS_TS_GET()获得当前的时间戳并放入 mutex 中的变量中,其它任务调用 OSMutexPend()时可以读取到这个时间戳。
OSMutexPost()
now calls OS_TS_GET() to obtain the current timestamp and place that
information in the mutex, which will be used by OSMutexPend().
OSMutexPost()递减嵌套计数值,如果该值依旧非 0,OSMutexPost() 返回。在这种情况下,mutex 的占用任务不是全部地释放了 mutex。
OSMutexPost()
decrements the nesting counter and, if still non-zero, OSMutexPost() returns to
the caller. In this case, the current owner has not fully released the mutex.
返回的错误代号为 OS_ERR_MUTEX_NESTING。
The
error code will indicate OS_ERR_MUTEX_NESTING.
Mutex 被完成释放后,如果没有任务等待这个 mutex,OSMutexPost()设置 mutex 中的 OwnerTCBPtr 为 NULL,并清空 mutex 的嵌套计数值。
If
there are no tasks waiting for the mutex, OSMutexPost() sets
p_mutex->OwnerTCBPtr to a NULL pointer and clears the mutex nesting counter.
如果 uC/OS-III 提升了占用 mutex 任务的优先级,OSMutexPost() 中会设置任务为原有的优先级。
If
μC/OS-III had to raise the priority of the mutex owner, it is returned to its
original priority at this time.
若有任务等待这个 mutex。OSMutexPost()运行后,挂起队列中优先级最高的任务占用这个 mutex。这个操作非常快因为队列是按优先级排序的。
The
highest-priority task waiting on the mutex is then extracted from the pend list
and given the mutex. This is a fast operation since the pend list is sorted by
priority.
The
scheduler is called to see if the new mutex owner has a higher priority than
the current task. If so, μC/OS-III will switch context to the new mutex owner.
注意:任何时候 mutex 只能提供给一个任务。事实上,推荐当你申请一个 mutex 时,最好不要申请其它内核对象。
You
should note that you should only acquire one mutex at a time. In fact, it’s
highly recommended that when you acquire a mutex, you don’t acquire any other
kernel objects.
然而,如果该等待有期限,就必须优先考虑使用 mutex。因为信号量容易导致优先级反转。
A semaphore
can be used instead of a mutex if none of the tasks competing for the shared
resource have deadlines to be satisfied.
However,
if there are deadlines to meet, you should use a mutex prior to accessing
shared resources. Semaphores are subject to unbounded priority inversions,
while mutex are not.
死锁,就是两个任务互相等待对方所占用的资源的情况。
A
deadlock, also called a deadly embrace, is a situation in which two tasks are
each unknowingly waiting for resources held by the other.
假定任务 T1 占用资源 R1,任务 T2 占用资源 R2。如列表 13-16所示。
Assume
Task T1 has exclusive access to Resource R1 and Task T2 has exclusive access to
Resource R2 as shown in the pseudo-code of Listing 13-16.


L13-16(1)假定任务 T1 所等待的事情发生,任务 1 被执行。
L13-16(1) Assume that the event that task T1 is waiting
for occurs and T1 is now the highest priority task that must execute.
L13-16(2)任务 T1 申请 M1。(mutex 1)
L13-16(2) Task T1 executes and acquires M1.
L13-16(3)任务 T1 访问资源 R1。
L13-16(3) Resource R1 is accessed.
L13-16(4)中断发生,中断中使能了任务 T2。由于任务 T2 的优先级高于任务 T1,CPU 切换到任务 T2。
L13-16(4) An interrupt occurs causing the CPU to switch
to task T2 as T2 is now the highest-priority task. Actually, this could be a
blocking call when the task is suspended and the CPU is given to another task.
L13-16(5)该中断即是任务 T2 所等待的事件。
L13-16(5) The ISR is the event that task T2 was waiting
for and therefore T2 resumes execution.
L13-16(6)任务 T2 申请 M2 并占用资源 R2。
L13-16(6)
Task T2 acquires mutex M2 and is able to access resource R2.
L13-16(7)任务T2申请 M1,但是 M1 已被任务 T1 占用。任务 T2 被挂起。
L13-16(7)
Task T2 tries to acquire mutex M1, but μC/OS-III knows that mutex M1 is owned
by another task.
L13-16(8)uC/OS-III 切换到任务 T1。
L13-16(8) μC/OS-III switches back to task T1 because Task
T2 can no longer continue. It needs mutex M1 to access resource R1.
L13-16(9)此时任务 T1 申请 M2,但是 M2 已经被任务 T2 占用。
L13-16(9) Task T1 now tries to access mutex M2 but,
unfortunately, mutex M2 is owned by task T2. At this point, the two tasks are
deadlocked.
此时,两个任务互相等待,这就算死锁。可以用以下方式防止死锁:
Techniques
used to avoid deadlocks are for tasks to:
■ 同一个时间不要申请多于一个 mutex
Never
acquire more than one mutex at a time
■ 不要直接地申请 mutex(该申请放到器件驱动中和可重入函数中)
Never
acquire a mutex directly (i.e., let them be hidden inside drivers and reentrant
library calls)
■ 在处理之前先获得全部所需要的 mutex
Acquire
all resources before proceeding
■ 任务间以同样的顺序申请资源
Always
acquire resources in the same order
当申请信号量或 mutex 时允许设置期限,这样能防止死锁,但是同样的死锁可能稍后再次出现。
μC/OS-III
allows the calling task to specify a timeout when acquiring a semaphore or a
mutex. This feature allows a deadlock to be broken, but the same deadlock may
then recur later, or many times later. If the semaphore or mutex is not
available within a certain period of time, the task requesting the resource
resumes execution. μC/OS-III returns an error code indicating that a timeout
occurred. A return error code prevents the task from thinking it has properly
obtained the resource.
处理之前先获得全部所需要的 mutex 的伪代码如列表 13-7
The
pseudo-code avoids deadlocks by first acquiring all resources as shown in
Listing 13-17.

任务间以同样的顺序申请资源的伪代码如 13-18 所示。这个例子类似于先前的例子,它不是先获得全部的 mutex,而是所有任务都是以相同顺序申请资源。
The
pseudo-code to acquire all of the mutexes in the same order is shown in Listing
13-18. This is similar to the previous example, except that it is not necessary
to acquire all the mutexes first, only to make sure that the mutexes are
acquired in the same order for both tasks.

mutex 的使用依赖于访问共享资源的操作能否快速执行。如表13-4。
The
mutual exclusion mechanism used depends on how fast code will access the shared
resource, as shown in Table 13-4.
|
资源共享方式 |
什么时候该用 |
|
关中断方式 Disable/Enable Interrupts |
能很快地结束访问共享资源,不推荐使用这种方法,因为会导致中断延迟 When access to shared
resource is very quick (reading from or writing to just a few variables) and
the access is actually faster than μC/OS-III’s
interrupt disable time. It is highly recommended to not use this method as it impacts interrupt latency. |
|
锁调度器方式 Locking/Unlocking the Scheduler |
访问共享资源比较久时。 When
access time to the shared resource is longer than μC/OS-III’s interrupt disable time, but shorter than μC/OS-III’s scheduler lock time. Locking
the scheduler has the same effect as making the task that locks the scheduler
the highest priority task. It is recommended to not use this method since it defeats the purpose of using μC/OS-III. However, it’s a better method than disabling interrupts as it does not impact interrupt latency. |
|
信号量方式 Semaphores |
当该共享资源经常被多个被使用时。但信号量可能会导致优先级反转。 When all tasks that need to access a shared resource do not have deadlines. This is because semaphores can cause unbounded priority inversions. However, semaphore services are slightly faster (in execution time) than mutual exclusion semaphores. |
|
mutex 方式 Mutual Exclusion Semaphores |
推荐使用这种方法访问共享资源,尤其当任务要访问的共享资源有截止时间。 This is the preferred method for accessing shared resources, especially if the tasks that need to access a shared resource have deadlines. uC/OS-III 的 mutex 有内置的优先级,这样可防止优先级倒置。 Remember that mutual
exclusion semaphores have a built-in priority inheritance mechanism, which
avoids unbounded priority inversions.
然而,mutex 方式慢于信号量方式,mutex 需执行额外的操作:改变信号量的优先级 However, mutual exclusion semaphore services are slightly slower (in execution time) than semaphores, because the priority of the owner may need to be changed, which requires CPU processing. |
这个章节主要介绍任务是如何于 ISR 或其它任务同步运行的
This
chapter focuses on how tasks can synchronize their activities with Interrupt
Service Routines (ISRs), or other tasks.
当 ISR 执行时,它可以发送信号量或消息给任务,表明任务所等待的事件发生。ISR 执行完毕后,调用调度器。推荐在任务级执行中断所需的大部分操作。因为这样会减少关中断的时间且利于调试。
When
an ISR executes, it can signal a task telling the task that an event of
interest has occurred. After signaling the task, the ISR exits and, depending
on the signaled task priority, the scheduler is run. The signaled task may then
service the interrupting device, or otherwise react to the event. Serving
interrupting devices from task level is preferred whenever possible, since it
reduces the amount of time that interrupts are disabled and the code is easier
to debug.
uC/OS-III 中用于同步的两种机制:信号量和事件标志组。
There
are two basic mechanisms for synchronizations in μC/OS-III: semaphores and
event flags.
正如第 13 章所介绍,信号量是绝大多数多任务内核所提供的协议机制。信号量最初用于控制共享资源的访问。信号量可用于 ISR 与任务间、任务与任务间的同步,如图 14-1 所示。
As defined
in Chapter 13, “Resource Management” on page 209, a semaphore is a protocol
mechanism offered by most multitasking kernels. Semaphores were originally used
to control access to shared resources. However, better mechanisms exist to
protect access to shared resources, as described in Chapter 12. Semaphores are
best used to synchronize an ISR to a task, or synchronize a task with another
task as shown in Figure 14-1.
图中信号量被画成一个旗帜,用于表明该事件发生的标志。信号量的初始值通常为 0,表明没有事件发生。
Note
that the semaphore is drawn as a flag to indicate that it is used to signal the
occurrence of an event. The initial value for the semaphore is typically zero
(0), indicating the event has not yet occurred.
“N”表示信号量可以被累计。初始化时也可以设置为非 0 值,表明已经有事件发生。ISR 或任务可以提交信号量多次,信号量计数值会记录该信号量一共可被多少次。
The
value “N” next to the flag indicates that the semaphore can accumulate events
or credits. It is possible to initialize the semaphore with a value other than
zero, indicating that the semaphore initially contains that number of events.
An ISR (or a task) can post (or signal) multiple times to a semaphore and the
semaphore will remember how many times it was posted.
任务旁边的沙漏表示任务可以设置等待该信号量的期限。
Also,
the small hourglass close to the receiving task indicates that the task has an
option to specify a timeout. This timeout indicates that the task is willing to
wait for the semaphore to be signaled (or posted to) within a certain amount of
time. If the semaphore is not signaled within that time, μC/OS-III resumes the
task and returns an error code indicating that the task was made ready to run
because of a timeout and not the semaphore was signaled.

信号量操作的相关函数如表 14-1 所示。注意的是所有的信号量函数都可以被任务调用,但是 ISR 中只能调用 OSSemPost()。
There
are a number of operations to perform on semaphores as summarized in Table 14-1
and Figure 14-1. However, in this chapter, we will only discuss the three
functions used most often: OSSemCreate(), OSSemPend(), and OSSemPost(). The
other functions are described in Appendix A, “μC/OS-III API Reference Manual”
on page 375. Also note that every semaphore function is callable from a task,
but only OSSemPost() can be called by an ISR
|
函数名 |
功能 |
|
OSSemCreate() |
创建一个信号量 Create a semaphore |
|
OSSemDel() |
删除一个信号量 Delete a semaphore |
|
OSSemPend() |
等待一个信号量 Wait on a semaphore |
|
OSSemPendAbort() |
取消等待该信号量 Abort the wait on a semaphore |
|
OSSemPost() |
提交一个信号量 Signal a semaphore |
|
OSSemSet() |
设置信号量计数值 Force the semaphore count to a desired value |
表 14-1 信号量的 API 总结
用于同步时,信号量计数值中记录了它该信号量可被分配的次数。该计数值在 0 到 25565535,或 0 到 4294987295 之间,决定于该计数变量的位数,8位,16 位,32 位。特别的,信号量计数值的上限为OS_SEM_CTR(见 OS_TYPE.H)。
When
used for synchronization, a semaphore keeps track of how many times it was
signaled using a counter. The counter can take values between 0 and 255,
65,535, or 4,294,967,295, depending on whether the semaphore mechanism is
implemented using 8, 16, or 32 bits, respectively. For μC/OS-III, the maximum value
of a semaphore is determined by the data type OS_SEM_CTR (see OS_TYPE.H), which
is changeable, as needed (assuming access to μC/OS-III’s source code). Along
with the semaphore’s value, μC/OS-III keeps track of tasks waiting for the
semaphore to be signaled.
14-1-1 单向同步
图 14-2 显示了通过信号量,任务可以与 ISR 或任务同步。通过信号量的传递,这表明了 ISR 或任务发生了。使用信号量实现同步叫做单向同步。
Figure
14-2 shows that a task can be synchronized with an ISR (or another task) by
using a semaphore. In this case, no data is exchanged, however there is an indication
that the ISR or the task (on the left) has occurred. Using a semaphore for this
type of synchronization is called a unilateral rendezvous.

当任务要使用 I/O 端口,它就需获得信号量而调用 OSSemPend()。
A
unilateral rendezvous is used when a task initiates an I/O operation and waits
(i.e., call OSSemPend()) for the semaphore to be signaled (posted).
当任务完成对 I/O 端口的访问完成后,就必须调用 OSSemPost()释放这个信号量。这个过程是单向同步的。如图 14-3,上述操作的 ISR 和任务代码如列表 14-1 所示。
When
the I/O operation is complete, an ISR (or another task) signals the semaphore
(i.e., calls OSSemPost()), and the task is resumed. This process is also shown
on the timeline of Figure 14-3 and described below. The code for the ISR and
task is shown in Listing 14-1.

F14-3(1)任务 H 被执行。该任务与 ISR 同步(也就是等待 ISR 的发生),然后调用 OSSemPend()申请一个信号量。
F14-3(1) A high priority task is executing. The
task needs to synchronize with an ISR (i.e., wait for the ISR to occur) and
call OSSemPend().
F14-3(2)转向 uC/OS-III 函数。
F14-3(3)OSSemPend()的相关操作。
F14-3(4)因为 ISR 尚未发生,任务 H 被放入挂起队列,并调用调度器。
F14-3(4) Since the ISR has not occurred, the task
will be placed in the waiting list for the semaphore until the event occurs The
scheduler in μC/OS-III will then select the next most important task and
context switch to that task.
F14-3(5)任务 L 被执行。
F14-3(5) The low-priority task executes.
F14-3(6)中断发生,任务 L 被保存,CPU 转向 ISR。
F14-3(7)开始执行 ISR。
F14-3(7) The event that the original task was
waiting for occurs. The lower-priority task is immediately preempted (assuming
interrupts are enabled), and the CPU vectors to the interrupt handler for the
event.
F14-3(8)ISR 中调用 OSSemPost()提交了任务 H 所等待的信号量。
F14-3(9)信号量被发送给任务 H。
F14-3(9) The ISR handles the interrupting device
and then calls OSSemPost() to signal the semaphore. When the ISR completes,
μC/OS-III is called.
F14-4(10)任务 H 被就绪,调度器将 CPU 的控制权交给任务 H。
F14-3(10) μC/OS-III notices that a higher-priority task
is waiting for this event to occur and context switches back to the original
task.
F14-4(11)任务 H 获得信号量,并继续执行。
F14-3(11) The original task resumes execution immediately
after the call to OSSemPend().

注意到一些有趣的事情:第一,uC/OS-III 总是让优先级最高的就绪任务被执行,最大化地利用了 CPU。第二,任务等待信号量时是不消耗 CPU 的执行时间。最后,当任务所等待的信号量出现时, uC/OS-III 能迅速地告知任务,并调用调度器。
A
few interesting things are worth noting about this process. First, the task
does not need to know about the details of what happens behind the scenes. As
far as the task is concerned, it called a function (OSSemPend()) that will
return when the event it is waiting for occurs. Second, μC/OS-III maximizes the
use of the CPU by selecting the next most important task, which executes until
the ISR occurs. In fact, the ISR may not occur for many milliseconds and,
during that time, the CPU will work on other tasks. As far as the task that is
waiting for the semaphore is concerned, it does not consume CPU time while it
is waiting. Finally, the task waiting for the semaphore will execute
immediately after the event occurs (assuming it is the most important task that
needs to run).
14-1-2 信号量计数值
前面提到,信号量计数值中保存了它还能被分配多少次。换句话说,当 ISR 提交该信号量 n 次,那么该信号量计数值就会增加 n。如图 14-4
As previously
mentioned, a semaphore “remembers” how many times it was signaled (or posted
to). In other words, if the ISR occurs multiple times before the task waiting
for the event becomes the highest-priority task, the semaphore will keep count
of the number of times it was signaled. When the task becomes the highest
priority ready-to-run task, it will execute without blocking as many times as
there were ISRs signaled. This is called Credit Tracking and is illustrated in
Figure 14-4 and described below.

F14-4(1)高优先级任务 H 被执行。
F14-4(1)
A high-priority task is executing.
F14-4(2)中断发生。
F14-4(3)ISR 中提交了一个信号量。
F14-4(3) An event meant for a lower-priority task
occurs which preempts the task (assuming interrupts are enabled). The ISR
executes and posts the semaphore.
At
this point the semaphore count is 1.
F14-4(4)信号量被提交后,uC/OS-III 需进行一些操作。
F14-4(5)uC/OS-III 执行相应操作。
F14-4(6)由于没有更高优先级任务被就绪,uC/OS-III 继续执行任务 H。
F14-4(6) μC/OS-III is called at the end of the ISR
to see if the ISR caused a higher-priority task to be ready to run. Since the
ISR was an event that a lower-priority task was waiting on, μC/OS-III will
resume execution of the higher-priority task at the exact point where it was
interrupted.
F14-4(7)任务 H 被执行。
F14-4(7)
The high-priority task is resumed and continues execution.
F14-4(8)中断发生。
F14-4(9)第二次中断发生了,ISR 中提交了一个信号量。 F14-4(10)信号量被提交后,uC/OS-III 需进行一些操作。
F14-4(9) The interrupt occurs a second time. The
ISR executes and posts the semaphore.
At
this point the semaphore count is 2.
F14-4(11)uC/OS-III 执行相应操作。
F14-4(12)由于没有更高优先级任务被就绪,uC/OS-III 继续执行任务 H。
F14-4(12) μC/OS-III is called at the end of the ISR to
see if the ISR caused a higher-priority task to be ready to run. Since the ISR
was an event that a lower-priority task was waiting on, μC/OS-III resumes
execution of the higher-priority task at the exact point where it was
interrupted.
F14-4(13)任务 H 被执行。
F14-4(14)任务 H 执行完毕。
F14-4(14) The high-priority task resumes execution and
actually terminates the work it was doing. This task will then call one of the
μC/OS-III services to wait for “its” event to occur.
F14-4(15)uC/OS-III 执行相应的上下文切换工作。
F14-4(16)uC/OS-III 将 CPU 的控制权交给任务 L。
F14-4(16) μC/OS-III will then select the next most
important task, which happens to be the task waiting for the event and will
context switch to that task.
F14-4(17)任务 L 被执行。
F14-4(17) The new task executes and will know that the
ISR occurred twice since the semaphore count is two. The task will handle this
accordingly.
14-1-3 多个任务等待一个信号量
多个任务可以同时等待同样的信号量,假设每个任务都被设置了定时期限。如图 14-5 所示
It
is possible for more than one task to wait on the same semaphore, each with its
own timeout as illustrated in Figure 14-5.

当该信号量被提交时,uC/OS-III 会让挂起队列中优先级最高的任务就绪。然而,也可以让挂起队列中所有的任务被就绪,这叫做广播信号量,调用OSSemPost()时选择参数 OS_OPT_POST_ALL 就能实现广播的功能。
When
the semaphore is signaled (whether by an ISR or task), μC/OS-III makes the
highest-priority task waiting on the semaphore ready to run. However, it is
also possible to specify that all tasks waiting on the semaphore be made ready
to run. This is called broadcasting and is accomplished by specifying
OS_OPT_POST_ALL as an option when calling OSSemPost(). If any of the waiting
tasks has a higher priority than the previously running task, μC/OS-III will
execute the highest-priority task made ready by OSSemPost().
广播用于多个任务间的同步。然而,若任务还需要与不在信号量挂起队列中的其它任务同步,可以同时使用信号量和事件标志组实现同步的功能。
Broadcasting
is a common technique used to synchronize multiple tasks and have them start
executing at the same time. However, some of the tasks that we want to
synchronize might not be waiting for the semaphore. It is fairly easy to
resolve this problem by combining semaphores and event flags. This will be
described after examining event flags.
经常通过发送信号量实现同步。每个任务都有内建的信号量,通过任务内建的信号量不仅可以简化信号量通信的代码而且更加有效。如图 14-7 所示
Signaling
a task using a semaphore is a very popular method of synchronization and, in
μC/OS-III, each task has its own built-in semaphore. This feature not only
simplifies code, but is also more efficient than using a separate semaphore
object. The semaphore, which is built into each task, is shown in Figure 14-7.
Task
semaphore services in μC/OS-III start with the OSTaskSem???() prefix, and the
services available to the application programmer are described in Appendix A,
“μC/OS-III API Reference Manual” on page 375. Task semaphores are built into
μC/OS-III and cannot be disabled at compile time as can other services. The
code for task semaphores is found in OS_TASK.C.
Use
this feature if the code knows which task to signal when the event occurs. For
example, if receiving an interrupt from an Ethernet controller, signal the task
responsible for processing the received packet as it is preferable to perform
this processing using a task instead of the ISR.

与任务内建的信号量相关的函数都是以 OSTaskSem???()为前缀的。相关的代码都在 OS_TASK.C 中。如表 14-2
There
are a number of operations to perform on task semaphores, summarized in Table
14-2.
|
函数名 |
功能 |
|
OSTaskSemPend() |
等待一个任务信号量 Wait on a task semaphore |
|
OSTaskSemPendAbort() |
取消等待 Abort the wait on a task semaphore |
|
OSTaskSemPost() |
发送信号量给任务 Signal a task |
|
OSTaskSemSet() |
设置信号量计数值 Force the semaphore count to a desired value |
表 14-2 任务内建信号量函数总结
14-2-2挂起(等待)任务信号量
当任务被创建时,也会内建一个信号量,信号量计数值初始化为0.等待任务信号量如列表 14-6 所示。
When
a task is created, it automatically creates an internal semaphore with an
initial value of zero (0). Waiting on a task semaphore is quite simple, as
shown in Listing 14-6.

L14-6(1)任务通过调用 OSTaskSemPend()等待任务信号量(其它任务)。第一个参数为等待期限,以时基为单位。
L14-6(1)
A task pends (or waits) on the task semaphore by calling OSTaskSemPend(). There
is no need to specify which task, as the current task is assumed. The first
argument is a timeout specified in number of clock ticks. The actual timeout
obviously depends on the tick rate. If the tick rate (see OS_CFG_APP.H) is set
to 1000, a timeout of 10 ticks represents 10 milliseconds. Specifying a timeout
of zero (0) means that the task will wait forever for the task semaphore.
L14-6 ( 2 )第二个参数是挂起方式。一共有两种方式:
OS_OPT_PEND_BLOCKING
和
OS_OPT_PEND_NON_BLOCKING。
L14-6(2) The second argument specifies how to
pend. There are two options: OS_OPT_PEND_BLOCKING and OS_OPT_PEND_NON_BLOCKING.
The blocking option means that, if the task semaphore has not been signaled (or
posted to), the task will wait until the semaphore is signaled, the pend is
aborted by another task or, until the timeout expires.
L14-6(3)函数返回时,该参数中保存着信号量被提交时的时间戳。
L14-6(3) When the semaphore is signaled, μC/OS-III
reads a “timestamp” and places it in the receiving task’s OS_TCB. When
OSTaskSemPend() returns, the value of the timestamp is placed in the local
variable “ts”. This feature captures “when” the signal actually happened. Call
OS_TS_GET() to read the current timestamp and compute the difference. This
establishes how long it took for the task to receive the signal from the
posting task or ISR.
L14-6(4)函数返回时,根据执行结果保存着一个错误代号。
提交(标记)任务信号量ISR 或任务通过调用 OSTaskSemPost()提交任务信号量,如列表14-7 所示
L14-6(4) OSTaskSemPend() returns an error code
based on the outcome of the call. If the call was successful, err will contain
OS_ERR_NONE. If not, the error code will indicate the reason of the error (see
Appendix A, “μC/OS-III API Reference Manual” on page 375 for a list of possible
error code for OSTaskSemPend().

L14-7(1)通过调用 OSTaskSemPost()将任务信号量提交给任务。该参数为目标任务的 OS_TCB 地址。
L14-7(1) A task posts (or signals) the task by
calling OSTaskSemPost(). It is necessary to pass the address of the desired
task’s OS_TCB. Of course, the task must exist.
L14-7(2)这个参数为提交方式的选择,一个有两种提交方式:
OS_OPT_POST_NONE 提交完成后调用调度器。
OS_OPT_POST_NO_SCHED 提交完成后调用调度器。(提交多个信号量时,可以只执行一次调度)
L14-7(2) The next argument specifies how the user
wants to post. There are only two choices.
Specify
OS_OPT_POST_NONE, which indicates the use of the default option of calling the
scheduler after posting the semaphore.
Or,
specify OS_OPT_POST_NO_SCHED to indicate that the scheduler is not to be called
at the end of OSTaskSemPost(), possibly because there will be additional
postings, and rescheduling would take place when finished (the last post would
not specify this option).
L14-7(3)函数返回时,根据执行结果保存着一个错误代号。
双向同步
L14-7(3) OSTaskSemPost() returns an error code based
on the outcome of the call. If the call was successful, err will contain
OS_ERR_NONE. If not, the error code will indicate the reason of the error (see
Appendix A, “μC/OS-III API Reference Manual” on page 375 for a list of possible
error codes for OSTaskSemPost().
两个任务间可以用两个信号量实现双向同步,如图 14-8。任务与 ISR 间不能双向同步,因为 ISR 中不能等待信号量(ISR 中不能有阻塞呼叫)。
Two
tasks can synchronize their activities by using two task semaphores, as shown
in Figure 14-8, and is called a bilateral rendezvous. A bilateral rendezvous is
similar to a unilateral rendezvous, except that both tasks must synchronize
with one another before proceeding. A bilateral rendezvous cannot be performed
between a task and an ISR because an ISR cannot wait on a semaphore.

双向同步的代码如列表 14-8 所示。当然,双向同步可以由两个外部信号量实现,但使用任务信号量会更加简单。
The
code for a bilateral rendezvous is shown in Listing 14-8. Of course, a
bilateral rendezvous can use two separate semaphores, but the built-in task
semaphore makes setting up this type of synchronization quite straightforward.

L14-8(1)任务 1 发送信号量给任务 2。
L14-8(1) Task #1 is executing and signals Task #2’s semaphore.
L14-8(2)任务 1 等待其内部的信号量,与任务 2 同步。因为任务 2 尚未被执行,任务 1 被挂起,等待任务 2 给其发送信号量。
L14-8(2) Task #1 pends on its internal semaphore
to synchronize with Task #2. Because Task #2 has not executed yet, Task #1 is
blocked waiting on its semaphore to be signaled. μC/OS-III context switches to
Task #2.
L14-8(3)切换到任务 2 并执行,给任务 1 发送信号量。
L14-8(3) Task #2 executes, and signals Task #1’s
semaphore.
L14-8(4)此时任务 2 与任务 1 同步。如果任务 1 优先级较高,uC/OS-III 切换到任务 1,否则,任务 2 继续执行。
L14-8(4) Since it has already been signaled, Task
#2 is now synchronized to Task #1. If Task #1 is higher in priority than Task
#2, μC/OS-III will switch back to Task #1. If not, Task #2 continues execution.
当任务要与多个事件同步时可以使用事件标志。若其中的任意一个事件发生时任务被就绪,叫做逻辑或(OR)。若所有的事件都发生时任务被就绪,叫做逻辑与(AND)。如图 14-9 所示。
Event
flags are used when a task needs to synchronize with the occurrence of multiple
events. The task can be synchronized when any of the events have occurred,
which is called disjunctive synchronization (logical OR). A task can also be
synchronized when all events have occurred, which is called conjunctive
synchronization (logical AND). Disjunctive and conjunctive synchronization are
shown in Figure 14-9.
用户可以创建任意个事件标志组(限制于 RAM)。uC/OS-III 中与事件标志组相关的函数都是以 OSFlag???()为前缀。与事件标志组相关的函数代码都在 OS_FLAG.C 中。
The
application programmer can create an unlimited number of event flag groups
(limited only by available RAM). Event flag services in μC/OS-III start with
the OSFlag???() prefix. The services available to the application programmer
are described in Appendix A, “μC/OS-III API Reference Manual” on page 375.
设置 OS_CFG.H 中的
OS_CFG_FLAG_EN 为 1 开启事件标志组功能。
The
code for event flag services is found in the file OS_FLAG.C, and is enabled at
compile time by setting the configuration constant OS_CFG_FLAG_EN to 1 in
OS_CFG.H.

F14-9(1)事件标志组是 uC/OS-III 的内核对象,以 OS_FLAG_GRP 为数据类型(见 OS.H)。 它可以是 8 位,16 位,32 位,决定于OS_TYPE.H 中所定义的 OS_FLAGS。事件标志组中的位是任务所等待事件是否发生的标志。事件标志组必须在创建后使用。
F14-9(1) A μC/OS-III “event flag group” is a kernel
object of type OS_FLAG_GRP (see OS.H), and consists of a series of bits (8-,
16- or 32-bits, based on the data type OS_FLAGS defined in OS_TYPE.H). The
event flag group also contains a list of tasks waiting for some (or all) of the
bits to be set (1) or clear (0). An event flag group must be created before it
can be used by tasks and ISRs. Create event flags prior to starting μC/OS-III,
or by a startup task in the application code.
F14-9(2)任务或 ISR 可以提交标志。然而,只有任务可以将在事件标志组中等待的其它任务删除,取消等待,只有任务才能让任务在事件标志组中等待。
F14-9(2) Tasks or ISRs can post to event flags. In
addition, only tasks can create, delete, and stop other task from pending on
event flag groups.
F14-9(3)任务可以等待事件标志组中的任意个位被设置。等待也可以被设置期限,以时基为单位。
F14-9(3) A task can wait (i.e., pend) on any
number of bits in an event flag group (i.e., a subset of all the bits). As with
all μC/OS-III pend calls, the calling task can specify a timeout value such
that if the desired bits are not posted within a specified amount of time (in
ticks), the pending task is resumed and informed about the timeout.
F14-9(4)任务等待事件标志组中的位,可以被设置为 OR 模式,或者是 AND 模式。
F14-9(4) The task can specify whether it wants to
wait for “any” subset of bits (OR) to be set (or clear), or wait for “all” bits
in a subset of bit (AND) to be set (or clear).
There
are a number of operations to perform on event flags, as summarized in Table
14-3.
|
函数名 |
功能 |
|
OSFlagCreate() |
创建一个事件标志组 Create an event flag group |
|
OSFlagDel() |
删除一个事件标志组 Delete an event flag group |
|
OSFlagPend() |
在事件标志组中挂起 Pend (i.e., wait) on an event flag group |
|
OSFlagPendAbort() |
取消等待 Abort waiting on an event flag group |
|
OSFlagPendGetFalgsRdy() |
获得事件标志组中导致任务被就绪的位 Get flags that caused task to become ready |
|
OSFlagPost() |
提交标志到事件标志组 Post flag(s) to an event flag group |
表 14-3 事件标志组 API 总结
14-3-1 使用事件标志组
当任务或 ISR 提交标志到事件标志组,满足条件的任务会被就绪。
When
a task or an ISR posts to an event flag group, all tasks that have their wait
conditions satisfied will be resumed.
事件标志组中位的含义由用户定义,应用中可以有多个事件标志组。在事件标志组中,可以定义位 0 表示温度过低(由温度传感器接收),位 1 表示电压过低,位 2 表示某个开关被按下等。任务或 ISR 检测这些传感器并调用OSFlagPost()设置相应的标志位。任务可以调用 OSFlagPend()检测相应的标志是否发生。
It’s
up to the application to determine what each bit in an event flag group means
and it is possible to use as many event flag groups as needed. In an event flag
group you can, for example, define that bit #0 indicates that a temperature
sensor is too low, bit #1 may indicate a low battery voltage, bit #2 could
indicate that a switch was pressed, etc. The code (tasks or ISRs) that detects
these conditions would set the appropriate event flag by calling OSFlagPost()
and the task(s) that would respond to those conditions would call OSFlagPend().
列表 14-9 显示了如何使用事件标志组。
Listing
14-9 shows how to use event flags.
![]()
L14-9(1)设置相应位所表示的含义。
L14-9(1) Define some bits in the event flag group.
L14-9(2)定义一个事件标志组,其类型为 OS_FLAG_GRP。为了说明,假定事件标志组的位长为 16,(在 OS_TYPE.H 中设置)
L14-9(2) Declare an object of type OS_FLAG_GRP.
This object will be referenced in all subsequent μC/OS-III calls that apply to
this event flag group. For the sake of discussions, assume that event flags are
declared to be 16-bits in OS_TYPE.H (i.e., of type CPU_INT16U).
L14-9(3)事件标志组必须被创建后才能使用,最后在应用的启动代码(或者说初始化代码)中就创建事件标志组。在这个例子中,事件标志组被赋予一个名字,且全部位被清零。
L14-9(3) Event flag groups must be “created”
before they can be used. The best place to do this is in your startup code as
it ensures that no tasks, or ISR, will be able to use the event flag group
until μC/OS-III is started. In other words, the best place is to create the
event flag group is in main(). In the example, the event flag was given a name
and all bits start in their cleared state (i.e., all zeros).
L14-9(4)假定 MyTask()在事件标志组中被挂起。
L14-9(4) Assume that the application created
“MyTask()” which will be pending on the event flag group.
L14-9(5)调用 OSFlagPend(),并传入事件标志组地址,任务就会被挂起在事件标志组中。
L14-9(5) To pend on an event flag group, call
OSFlagPend() and pass it the address of the desired event flag group.
第二个参数是任务等待哪些位被置位,这里设置了 2 个位。
The
second argument specifies which bits the task will be waiting to be set
(assuming the task is triggered by set bits instead of cleared bits).
第三个参数是等待时限。
Specify
how long to wait for these bits to be set. A timeout value of zero (0)
indicates that the task will wait forever. A non-zero value indicates the
number of ticks the task will wait until it is resumed if the desired bits are
not set.
第四个参数是等待的方式,若设置为 OS_OPT_FLAG_SET_ANY,表明两个位中只要有一个位被置位,任务就会被就绪。第五个参数中存放了当事件标志被提交时的时间戳。
Specifying
OS_OPT_FLAG_SET_ANY indicates that the task will wake up if either of the two
bits specified is set. A timestamp is read and saved when the event flag group
is posted to. This timestamp can be used to determine the response time to the
event.
第六个参数是根据函数执行结果返回的错误代号。
OSFlagPend()
performs a number of checks on the arguments passed (i.e., did you pass NULL
pointers, invalid options, etc.), and returns an error code based on the
outcome of the call. If the call was successful “err” will be set to
OS_ERR_NONE.
L14-9(6)中断被设置为检测电池电压。当电压过低时,中断发生,ISR 就需要告诉任务,并让任务执行相应操作。
L14-9(6) An ISR (it can also be a task) is setup
to detect when the battery voltage of the product goes low (assuming the
product is battery operated). The ISR signals the task, letting the task
perform whatever corrective action is needed.
L14-9(7)通过调用 OSFlagPost()函数,ISR 中设置事件标志组中相应的位。第三个参数是选项参数,位被置一或清零。
L14-9(7) The desired event flag group is specified
in the post call as well as which flag the ISR is setting. The third option
specifies that the error condition will be “flagged” as a set bit. Again, the
function sets “err” based on the outcome of the call.
事件标志组通常用于表示的短暂的事件或状态信息。通常用不同的事件标志组处理如列表 14-10。
Event
flags are generally used for two purposes: status and transient events.
Typically use different event flag groups to handle each of these as shown in
Listing 14-10.
ISR 或任务可以测量一些状态信息,例如温度、转速、电压等。用于状态信息时一般不设置等待时限。
Tasks
or ISRs can report status information such as a temperature that has exceeded a
certain value, that RPM is zero on an engine or motor, or there is fuel in the
tank, and more. This status information cannot be “consumed” by the tasks
waiting for these events, because the status is managed by other tasks or ISRs.
Event flags associated with status information are monitored by other task by
using non-blocking wait calls.
ISR 或任务可以测量一些短暂的事件,如按下开关、爆炸等。用于短暂事件时一般设置了等待时限。
Tasks
will report transient events such as a switch was pressed, an object was
detected by a motion sensor, an explosion occurred, etc. The task that responds
to these events will typically block waiting for any of those events to occur
and “consume” the event.

14-3-2 事件标志组内部结构
用户可以创建任意个事件标志组(仅限制于处理器的 RAM)。通过设置OS_CFG.H 中的 OS_CFG_FLAG_EN 为 1 开启事件标志组功能。
The
application programmer can create an unlimited number of event flag groups
(limited only by available RAM). Event flag services in μC/OS-III start with
OSFlag and the services available to the application programmer are described
in Appendix A, “μC/OS-III API Reference Manual” on page 375. Event flag
services are enabled at compile time by setting the configuration constant OS_CFG_FLAG_EN
to 1 in OS_CFG.H.
事件标志组是一个内核对象,由数据类型 OS_FLAG_GRP 定义,该数据类型由 os_flag_grp 定义(见 OS.H)。与事件标志组相关的代码在 OS_FLAG.C 中。如列表 14-10。
An
event flag group is a kernel object as defined by the OS_FLAG_GRP data type,
which is derived from the structure os_flag_grp (see OS.H) as shown in Listing
14-10.
The
services provided by μC/OS-III to manage event flags are implemented in the
file OS_FLAG.C. μC/OS-III licensees have access to the source code.

L14-10(1)在 uC/OS-III 中,所有的结构体都会定义一个数据类型。都是以"OS_"开头并且全部大写。
L14-10(1) In μC/OS-III, all structures are given a data
type. In fact, all data types start with “OS_” and are uppercase. When an event
flag group is declared, simply use OS_FLAG_GRP as the data type of the variable
used to declare the event flag group.
L14-10(2)结构体的第一个变量为"Type"。用于辨认该对象为事件标志组。
L14-10(2) The structure starts with a “Type” field, which allows it to be recognized by μC/OS-III as an event flag group. In other words, other kernel objects will also have a “Type” as the first member of the structure. If a function is passed a kernel object, μC/OS-III will be able to confirm that it is being passed the proper data type. For example, if passing a message queue (OS_Q) to an event flag service (for example OSFlagPend()), μC/OS-III will be able to recognize that an invalid object was passed, and return an error code accordingly.
L14-10(3)每个内核对象都可以被分配一个名字。
L14-10(3) Each kernel object can be given a name to make
them easier to be recognized by debuggers or μC/Probe. This member is simply a
pointer to an ASCII string, which is assumed to be NUL terminated.
L14-10(4)因为可以有多个任务同时等待事件标志组中的事件,所以事件标志组中包含了一个用于控制挂起队列的结构体(见第十章)。
L14-10(4) Because it is possible for multiple tasks to be
waiting (or pending) on an event flag group, the event flag group object
contains a pend list as described in Chapter 10, “Pend Lists (or Wait Lists)”
on page 177.
L14-10(5)事件标志组中包含了很多标志位,这个变量中保存了当前这些标志位的状态。这个变量可以为 8 位,16 位或 32 位。决定于 OS_TYPE.H 中的 OS_FLAGS 的位数。
L14-10(5) An event flag group contains a series of flags
(i.e., bits), and this member contains the current state of these flags. The
flags can be implemented using either an 8-, 16- or 32-bit value depending on
how the data type OS_FLAGS is declared in OS_TYPE.H.
L14-10(6)事件标志组中包含了一个变量,存储了最后一次标志被提交的时间戳。用户代码不能直接访问事件标志组,必须通过 uC/OS-III 提供的函数访问事件标志组。
L14-10(6) An event flag group contains a timestamp used
to indicate the last time the event flag group was posted to. μC/OS-III assumes
the presence of a free-running counter that allows users to make time
measurements. When the event flag group is posted to, the free-running counter
is read and the value is placed in this field, which is returned when
OSFlagPend() is called. This value allows an application to determine either
when the post was performed, or how long it took for your the to obtain control
of the CPU from the post. In the latter case, call OS_TS_GET() to determine the
current timestamp and compute the difference.
Even
if the user understands the internals of the OS_FLAG_GRP data type, application
code should never access any of the fields in this data structure directly.
Instead, always use the APIs provided with μC/OS-III.
创建事件标志组,如列表 14-11 所示
Event
flag groups must be created before they can be used by an application as shown
in Listing 14-11.

L14-11(1)定义事件标志组。
L14-11(1) The application must declare a variable of type
OS_FLAG_GRP. This variable will be referenced by other event flag services.
L14-11(2)调用 OSFlagGreate()创建一个事件标志组。第一个参数为事件标志组的地址。
L14-11(2) Create an event flag group by calling
OSFlagCreate() and pass the address to the event flag group allocated in (1).
L14-11(3)给事件标志组赋予名字。名字必须以空字符结尾(不是空格)。
L14-11(3) Assign an ASCII name to the event flag group,
which can be used by debuggers or μC/Probe to easily identify this event flag
group. μC/OS-III stores a pointer to the name so there is no practical limit to
its size, except that the ASCII string needs to be NUL terminated.
L14-11(4)一般情况下,将事件标志组中的所有标志位初始化为 0.
L14-11(4) Initialize the flags inside the event flag
group to zero (0) unless the task and ISRs signal events with bits cleared
instead of bits set. If using cleared bits, initialize all the bits to ones
(1).
L14-11(5)返回一个错误代号。调用 OSFlagPend(),任务等待一个或多个标志位被设置。
L14-11(5) OSFlagCreate() returns an error code based on
the outcome of the call. If all the arguments are valid, err will contain
OS_ERR_NONE.

L14-12(1)OSFlagPend()开始时先检查参数的有效性。若函数所等待的标志位被设置,OSFlagPend()会返回哪个位被设置了。
L14-12(1)
When called, OSFlagPend() starts by checking the arguments passed to this
function to ensure they have valid values. If the bits the task is waiting for
are set (or cleared depending on the option), OSFlagPend() returns and indicate
which flags satisfied the condition. This is the outcome that the caller
expects.
如果设置为 OS_OPT_PEND_NON_BLOCKING,无论所等待的位有没有被设置,OSFlagPend()会立即返回,并返回错误代号。
If
the event flag group does not contain the flags that the caller is looking for,
the calling task might need to wait for the desired flags to be set (or
cleared). If specifying OS_OPT_PEND_NON_BLOCKING as the option (the task is not
to block), OSFlagPend() returns immediately to the caller and the returned
error code indicates that the bits have not been set (or cleared).
如果设置为 OS_OPT_PEND_BLOCKING,所等待位没有被设置时任务会被放入挂起队列。可以设置等待时限。队列中的任务不是以优先级方式排序的,而是后入先出方式排序,也就是后到的任务会被放入队首。这是因为,无论哪个位被设置,uC/OS-III 会遍历整个队列的。
If
specifying OS_OPT_PEND_BLOCKING as the option, the calling task will be
inserted in the list of tasks waiting for the desired event flag bits. The task
is not inserted in priority order but simply inserted at the beginning of the
list. This is done because whenever bits are set (or cleared), it is necessary
to examine all tasks in this list to see if their desired bits have been
satisfied.
If
further specifying a non-zero timeout, the task will also be inserted in the
tick list. A zero value for a timeout indicates that the calling task is
willing to wait forever for the desired bits.
The
scheduler is then called since the current task is no longer able to run (it is
waiting for the desired bits). The scheduler will run the next highest-priority
task that is ready to run.
When
the event flag group is posted to and the task that called OSFlagPend() has its
desired bits set or cleared, a task status is examined to determine the reason
why OSFlagPend() is returning to its caller.
OSFlagPend()的返回有 4 种可能
1)所等待的标志位被设置
2)挂起被其它任务取消
3)等待超时
4)事件标志组被删除
The possibilities are:
1) The desired bits were set (or cleared)
2) The pend was aborted by another task
3) The bits were not set (or cleared) within
the specified timeout
4) The event flag group was deleted
检测 OSFlagPend()返回的错误代号就能知道是什么原因导致返回。
When
OSFlagPend() returns, the caller is notified of the above outcome through an
appropriate error code.
L14-12(2)如果 OSFlagPend()返回的错误代号为 OS_ERR_NONE,表明任务等待的位被设置,并正常返回。
L14-12(2)
If OSFlagPend() returns with err set to OS_ERR_NONE, assume that the desired
bits were set (or cleared) and the task can proceed with servicing the ISR or
task that created those events. If err contains anything else, OSFlagPend()
either timed out (if the timeout argument was non-zero), the pend was aborted
by another task or, the event flag group was deleted by another task. It is
always important to examine the returned error code and not assume everything
went as planned.
调用 OSFlagPost()设置或清除标志位,如列表 14-13 所示
To
set (or clear) event flags (either from an ISR or a task), simply call
OSFlagPost(), as shown in Listing 14-13.

L14-13(1)ISR 或任务调用 OSFlagPost()提交标志位。这个参数是事件标志组的地址。
L14-13(1) A task posts to the event flag group by calling
OSFlagPost(). Specify the desired event flag group to post by passing its
address. Of course, the event flag group must have been previously created.
L14-13(2)第二个参数是时间标志组中的哪些位需要被设置或清除。
L14-13(2) The next argument specifies which bit(s) the
ISR (or task) will be setting or clearing in the event flag group.
L14-13 ( 3 )可选: OS_OPT_POST_FLAG_SET或OS_OPT_POST_FLAG_CLR。
如果设置为 OS_OPT_POST_FLAG_SET,第二个参数提交对应位被置 1。
L14-13(3) Specify OS_OPT_POST_FLAG_SET or
OS_OPT_POST_FLAG_CLR.
If
specifying OS_OPT_POST_FLAG_SET, the bits specified in the second arguments
will set the corresponding bits in the event flag group. For example, if
MyEventFlagGrp.Flags contains 0x03, the code in Listing 14-13 will change
MyEventFlagGrp.Flags to 0x0F.
如果设置为 OS_OPT_POST_FLAG_CLR,第二个参数提交对应位被置 0。
当还设置为 OS_OPT_POST_NO_SCHED(与上面的选项相加“+”,传入该参数)。任务可以提交标志位而不发生调度。当有多个事件标志组时,通过该设置,可以实现仅在最后一次提交时发生调度(需在最后一次提交时不设置该选项)。
If
specifying OS_OPT_POST_FLAG_CLR, the bits specified in the second arguments
will clear the corresponding bits in the event flag group. For example, if
MyEventFlagGrp.Flags contains 0x0F, the code in Listing 14-13 will change
MyEventFlagGrp.Flags to 0x03.
When
calling OSFlagPost() specify as an option (i.e., OS_OPT_POST_NO_SCHED) to not
call the scheduler. This means that the post is performed, but the scheduler is
not called even if a higher-priority task was waiting for the event flag group.
This allows the calling task to perform other post functions (if needed) and
make all the posts take effect simultaneously.
L14-13(4)根据函数执行结果返回一个错误代号。
L14-13(4)
OSFlagPost() returns an error code based on the outcome of the call. If the
call was successful, err will contain OS_ERR_NONE. If not, the error code will
indicate the reason of the error (see Appendix A, “μC/OS-III API Reference
Manual” on page 375 for a list of possible error codes for OSFlagPost().
通过广播信号量实现多任务同步是通用的方法。显然的,在单 CPU 系统中,同一时间只能执行一个任务。然而,多个任务可以同时被就绪。这叫做多任务同步。
Synchronizing
the execution of multiple tasks by broadcasting to a semaphore is a commonly
used technique. It may be important to have multiple tasks start executing at
the same time. Obviously, on a single processor, only one task will actually
execute at one time.
然而,需要同步一些不需要接收广播的信号量的任务,解决这个问题的办法是将信号量和时间标志组同时用于同步。如图 14-11 所示。假定左边任务的优先级比右边任务的优先级低。
However,
the start of their execution will be synchronized to the same time. This is
called a multiple task rendezvous. However, some of the tasks synchronized
might not be waiting for the semaphore when the broadcast is performed. It is
fairly easy to resolve this problem by combining semaphores and event flags, as
shown in Figure 14-11. For this to work properly, the task on the left needs to
have a lower priority than the tasks waiting on the semaphore.

F14-11(1)右边的每个任务都需要跟事件标志组的位对应。
F14-11(1) Each task that needs to synchronize at the
rendezvous needs to set an event flag bit (and specify OS_OPT_POST_NO_SCHED).
F14-11(2)右边任务需等待信号量才能被就绪。
F14-11(2) The task needs to wait for the semaphore to be
signaled.
F14-11(3)当事件标志组中与需要同步的任务对应位都被置位后,左边任务才能广播信号量。
F14-11(3) The task that will be broadcasting must wait
for “all” of the event flags corresponding to each task to be set.
F14-11(4)左边任务广播信号量给右边任务。
F14-11(4) When all waiting tasks are ready, the task that
will synchronize the waiting task issues a broadcast to the semaphore.
有三种方法可以让 ISR 或任务标记另一个(或多个)任务:信号量、任务信号量、事件标志组。
Three
methods are presented to allow an ISR or a task to signal one or more tasks:
semaphores, task semaphores, and event flags.
信号量和任务信号量中都有信号量计数值表示该信号量还可以被分配几次。若 ISR 或任务需要标记一个任务时,推荐使用任务信号量因为这可以避免定义一个外部信号量且更为有效。
Both
semaphores and task semaphores contain a counter allowing them to perform
credit tracking and accumulate the occurrence of events. If an ISR or task
needs to signal a single task (as opposed to multiple tasks when the event
occurs), it makes sense to use a task semaphore since it prevents the user from
having to declare an external semaphore object.
Also,
task semaphore services are slightly faster (in execution time) than semaphores.
当任务需要与一个或多个事件同步时使用事件标志组。
Event
flags are used when a task needs to synchronize with the occurrence of one or
more events. However, event flags cannot perform credit tracking since a single
bit (as opposed to a counter) represents each event.
有些情况下任务或 ISR 与另一个任务间进行通信,这种信息交换叫做作业间的通信。可以有两种方法实现这种通信:全局变量、发送消息。
It
is sometimes necessary for a task or an ISR to communicate information to
another task. This information transfer is called inter-task communication.
Information can be communicated between tasks in two ways: through global data,
or by sending messages.
正如第 13 章“资源管理”所介绍。如果使用全局变量,任务或 ISR 就须确保它独占该变量。如果防止被 ISR 嵌套,那么就只有关中断这种方法来保护这个变量。如果是任务间共享该变量,那么可以通过关中断、锁调度器、信号量、mutex 保护该变量。
As
seen in Chapter 13, “Resource Management” on page 209, when using global
variables, each task or ISR must ensure that it has exclusive access to
variables. If an ISR is involved, the only way to ensure exclusive access to
common variables is to disable interrupts. If two tasks share data, each can
gain exclusive access to variables either by disabling interrupts, locking the
scheduler, using a semaphore, or preferably, using a mutual-exclusion
semaphore.
需注意的是:任务与ISR 通信只能通过全局变量。如果全局变量被 ISR 改变,任务将不会知道全局变量被改变,除非该任务检测该变量或者 ISR 标记任务告知该变量被改变。
Note
that a task can only communicate information to an ISR by using global
variables. A task is not aware when a global variable is changed by an ISR,
unless the ISR signals the task, or the task polls the contents of a variable
periodically.
消息可以被发送到媒介—消息队列中,也可以直接发送给任务,因为 uC/OS-III 中每个任务都有其内建的消息队列。如果多个任务等待这个消息时就将该消息发送到外部的消息队列。当只有一个任务等待该消息时直接将消息发送给任务。
Messages
can either be sent to an intermediate object called a message queue, or
directly to a task since in μC/OS-III, each task has its own built-in message
queue. Use an external message queue if multiple tasks are to wait for
messages. Send a message directly to a task if only one task will process the
data received.
任务等待消息到达时,不占用 CPU。
When
a task waits for a message to arrive, it does not consume CPU time.
消息中包含一个指向数据的指针、该数据的大小、时间戳变量。该指针可以指向数据区域甚至是一个函数。当然,消息的发送方和消息的接收方都应该知道消息所包含的意义。换句话说,接收方知道接收发到消息的含义。
A
message consists of a pointer to data, a variable containing the size of the
data being pointed to, and a timestamp indicating when the message was sent.
The pointer can point to a data area or even a function. Obviously, the sender
and the receiver must agree as to the contents and the meaning of the message.
In other words, the receiver of the message will know the meaning of the
message received to be able to process it. For example, an Ethernet controller
receives a packet and sends a pointer to this packet to a task that knows how
to handle the packet.
消息的内容(即数据)通常保留在其作用域中因为发送的是数据的地址而不是数据。换句话说,数据不是被拷贝并发送给任务,而是告诉任务数据的地址,并让任务自己去访问。
The
message contents must always remain in scope since the data is actually sent by
reference instead of by value. In other words, data sent is not copied.
Consider using dynamically allocated memory as described in Chapter 17, “Memory
Management” on page 323. Alternatively, pass pointers to a global variable, a
global data structure, a global array, or a function, etc.
消息队列是内核对象。事实上,可以分配任意个消息队列(只要处理器的 RAM 足够的话)。
A
message queue is a kernel object allocated by the application. In fact, you can
allocate any number of message queues. The only limit is the amount of RAM
available.
通过消息队列用户可以做很多事情,如图 15-1.然而,ISR 中只能调用 OSQPost()。
There
are a number of operations that the user can perform on message queues,
summarized in Figure 15-1. However, an ISR can only call OSQPost(). A message
queue must be created before sending messages through it.

消息队列是先入先出模式(FIFO)。然而,uC/OS-III 也可以将其设置为后入先出模式(LIFO)。若任务或 ISR 发送紧急消息给另一个任务时,后入先出模式是非常有用的,在这种情况下,该紧急消息绕过消息队列中的其他消息。消息队列的长度可以在运行时设置。
Message
queues are drawn as a first-in, first-out pipe (FIFO). However, with μC/OS-III,
it is possible to post messages in last-in, first-out order (LIFO). The LIFO
mechanism is useful when a task or an ISR must send an “urgent” message to a
task. In this case, the message bypasses all other messages already in the
message queue. The size of the message queue is configurable at run time.
如上图,接收任务旁的沙漏表示该任务可以设置等待期限。如果任务没有在规定时间内接收到该消息,uC/OS-III 会返回一个错误代号表示任务被就绪不是因为接收到消息,而是等待超时。
The
small hourglass close to the receiving task indicates that the task has an
option to specify a timeout. This timeout indicates that the task is willing to
wait for a message to be sent to the message queue within a certain amount of
time. If the message is not sent within that time, μC/OS-III resumes the task
and returns an error code indicating that the task was made ready to run
because of a timeout, and not because the message was received. It is possible
to specify an infinite timeout and indicate that the task is willing to wait
forever for the message to arrive.
消息队列中存放了等待该消息的任务。多个任务可以在消息队列中等待消息,如图 15-2 所示。当一个消息被发送到消息队列时,等待该消息的高优先级任务接收这个消息。消息发送者可以广播这个消息给消息队列中的所有任务。在这种情况下,如果接收到消息中有优先级高于消息发送者优先级的任务,uC/OS-III 就会切换到这个高优先级的任务。注意:不是每个任务都需要设置等待期限,有些任务可能需要永远等待这个消息。
The
message queue also contains a list of tasks waiting for messages to be sent to
the message queue. Multiple tasks can wait on a message queue as shown in
Figure 15-2. When a message is sent to the message queue, the highest priority
task waiting on the message queue receives the message. Optionally, the sender
can broadcast a message to all tasks waiting on the message queue. In this
case, if any of the tasks receiving the message from the broadcast has a higher
priority than the task sending the message (or interrupted task, if the message
is sent by an ISR), μC/OS-III will run the highest-priority task that is
waiting. Notice that not all tasks must specify a timeout; some tasks may want
to wait forever.

很少会见到多个任务同时在一个消息队列中等待。因为这样,uC/OS-III 在任务中内建了一个消息队列。用户可以直接发送消息给任务而不通过外部消息队列。这个特性不仅简化了代码,还提供了效率。每个任务都内建一个消息队列,如图 15-3 所示。
It
is fairly rare to find applications where multiple tasks wait on a single
message queue. Because of this, a message queue is built into each task and the
user can send messages directly to a task without going through an external
message queue object. This feature not only simplifies the code but, is also
more efficient than using a separate message queue object. The message queue
that is built into each task is shown in Figure 15-3.

uC/OS-III 中与任务消息队列相关的服务都是以 OSTask???()开头的。设置 OS_CFG.H 中的 OS_CFG_TASK_EN 使能任务的消息队列服务。与任务消息队列相关的代码在 OS_TASK.C 中。
Task
message queue services in μC/OS-III start with the OSTaskQ???() prefix, and
services available to the application programmer are described in Appendix A,
“μC/OS-III API Reference Manual” on page 375. Setting OS_CFG_TASK_EN in
OS_CFG.H enables task message queue services. The code for task message queue
management is found in OS_TASK.C.
Use
this feature if the code knows which task to send the message(s) to. For
example, if receiving an interrupt from an Ethernet controller, send the
address of the received packet to the task that will be responsible for
processing the received packet.
两个任务可以通过两个消息队列同步,如图 15-4 所示。这叫做双向通信,这两个任务间可能互相发送消息给对方。任务与 ISR 间不能双向通信,因为 ISR 不能在消息队列中等待。
Two
tasks can synchronize their activities by using two message queues, as shown in
Figure 15-4. This is called a bilateral rendezvous and works the same as with
semaphores except that both tasks may send messages to each other. A bilateral
rendezvous cannot be performed between a task and an ISR since an ISR cannot
wait on a message queue.

这两个消息队列都被初始化为空。当左边的任务到达约定点时,它就会发送消息给顶部消息队列并在底部消息队列中等待消息。类似的,当右边的任务到达某个时刻,它就会发送消息给底部消息队列,并在顶部消息队列中等待消息。
In a
bilateral rendezvous, each message queue holds a maximum of one message. Both
message queues are initially created empty. When the task on the left reaches
the rendezvous point, it sends a message to the top message queue and waits for
a message to arrive on the bottom message queue. Similarly, when the task on
the right reaches its rendezvous point, it sends a message to the message queue
on the bottom and waits for a message to arrive on the top message queue.
图 15-5 显示了如何用实现双向通信
Figure
15-5 shows how to use task-message queues to perform a bilateral rendezvous.

任务间的通信经常通过从任务 A 中传送数据给任务 B。然而,任务 A 创建该数据需要时间,且任务 B 可能在该数据被创建后经过很长时间才接收到该数据。换句话说,如果更高优先级的任务抢占了任务 B,那么任务 A 所存放在消息队列中的数据就可能被溢出。解决这个问题的一种方法是在处理中添加流量控制如图 15-6 所示
Task-to-task
communication often involves data transfer from one task to another. One task
produces data while the other consumes it. However, data processing takes time
and consumers might not consume data as fast as it is produced. In other words,
it is possible for the producer to overflow the message queue if a
higher-priority task preempts the consumer. One way to solve this problem is to
add flow control in the process as shown in Figure 15-6.

在这里,使用了一个信号量计数值,该值初始化为任务 B 中消息队列长度。
Here,
a counting semaphore is used, initialized with the number of allowable messages
that can be sent by the consumer. If the consumer cannot queue more than 10
messages, the counting semaphore contains a count of 10.
如列表 15-1 所示,任务 A 在发送消息给任务 B 之前必须获得信号量。任务 B 消息队列的空余量为多少,信号量计数值就为多少。
As
shown in the pseudo code of Listing 15-1, the producer must wait on the
semaphore before it is allowed to send a message. The consumer waits for
messages and, when processed, signals the semaphore.

将任务的消息队列和信号量队列一起使用(见第 14 章“同步”),实现流量控制将是简单的,如图 15-7 所示。在这种情况下,需要调用 OSTaskSemSet()函数设置任务 B 的信号量计数值为任务 B 消息队列的消息容量。
Combining the task message queue and task semaphores (see Chapter 14, “Synchronization” on page 251), it is easy to implement flow control as shown in Figure 15-7. In this case, however, OSTaskSemSet() must be called immediately after creating the task to set the value of the task semaphore to the same value as the maximum number of allowable messages in the task message queue.

消息通常指向结构体、变量、数组等。然而,数据必须被保持在其作用域(结构体、变量、数组)中直到接收者完成对这些数据的操作。消息一旦被发送,发送者就不能访问被发送的消息中所包含的数据。这看起来很显然,但经常被忘记。
The
messages sent typically point to data structures, variables, arrays, tables,
etc. However, it is important to realize that the data must remain static until
the receiver of the data completes its processing of the data. Once sent, the
sender must not touch the sent data. This seems obvious, however it is easy to
forget.
通过 uC/OS-III 中的分区管理器(见第 17 章“内存管理”)动态地分配和回收内存块用于传递这些数据(消息中所包含的数据)。图 15-8 显示了一个例子,假定一个器件通过某种协议发送字节数据包给UART。在这种情况下,数据包的开始字节和结束字节应该独一无二的。
One
possibility is to use the fixed-size memory partition manager provided with
μC/OS-III (see Chapter 17, “Memory Management” on page 323) to dynamically
allocate and free memory blocks used to pass the data. Figure 15-8 shows an
example. For sake of illustration, assume that a device is sending data bytes
to the UART in packets using a protocol. In this case, the first byte of a
packet is unique and the end-of-packet byte is also unique.

F15-8(1)这里,UART 接收到开始字节并中断。
F15-8(1) Here, a UART generates an interrupt when
characters are received.
F15-8(2)列表 15-2 中显示了 UART 的 ISR 代码。ISR 从 UART 中读取接收到的数据并观测它是否为数据包的开始字节。如果是,就获得一个内存块。
F15-8(2) The pseudo-code in Listing 15-2 shows
what the UART ISR code might look like. There are a lot of details omitted for
sake of simplicity. The ISR reads the byte received from the UART and sees if
it corresponds to a start of packet. If it is, a buffer is obtained from the
memory partition.
F15-8(3)接收到的数据被放入内存块中。
F15-8(3) The received byte is then placed in the buffer.
F15-8(4)如果检测到包的结束字节,就提交这个内存块的地址给消息队列,以便让任务处理这个接收包。
F15-8(4) If the data received is an end-of-packet
byte, simply post the address of the buffer to the message queue so that the
task can process the received packet.
F15-8(5)如果这个消息使 UART 任务就绪,且其为最高优先级任务, uC/OS-III 在执行完 ISR 之后就会跳转到该任务。该任务从消息队列中获得数据包。注意的是:OSQPend()被调用后返回数据包的字节数和时间戳(存了该消息被发送时的时间戳)。
F15-8(5) If the message sent makes the UART task
the highest priority task, μC/OS-III will switch to that task at the end of the
ISR instead of returning to the interrupted task. The task retrieves the packet
from the message queue. Note that the OSQPend() call also returns the number of
bytes in the packet and a time stamp indicating when the message was sent.
F15-8(6)任务处理完这个数据包后,通过调用 OSMemPut()将这个内存块释放。
F15-8(6) When the task is finished processing the
packet, the buffer is returned to the memory partition it came from by calling
OSMemPut().

表 15-1 是关于消息队列函数的总结。详见附录 A
Table
15-1 shows a summary of message-queue services available from μC/OS-III. Refer
to Appendix A, “μC/OS-III API Reference Manual” on page 375 for a full
description on their use.
|
函数名 |
功能 |
|
OSQCreate() |
创建一个消息队列 Create a message queue. |
|
OSQDel() |
删除一个消息队列 Delete a message queue. |
|
OSQFlush() |
清空一个消息队列 Empty the message queue. |
|
OSQPend() |
任务等待消息 Wait for a message. |
|
OSQPendAbort() |
任务被不再等待该消息 Abort waiting for a message. |
|
OSQPost() |
提交一个消息给消息队列 Send a message through a message queue. |
表 15-1 消息队列的 API 总结
表 15-2 是任务中消息队列函数的总结,详见附录 A。
Table
15-2 is a summary of task message queue services available from μC/OS-III.
Refer to Appendix A, “μC/OS-III API Reference Manual” on page 375, for a full
description on how to their use.
|
函数名 |
功能 |
|
OSTaskQPend() |
等待一个消息 Wait for a message. |
|
OSTaskQPendAbort() |
任务被不再等待该消息 Abort the wait for a message. |
|
OSTaskQPost() |
发送一个消息给任务 Send a message to a task. |
|
OSTaskQFlush() |
清空这个消息队列 Empty the message queue. |
表 15-2 任务消息队列的 API 总结
图 15-9 显示了通过消息队列配置旋转轮的旋转速率。
Figure
15-9 shows an example of using a message queue when determining the speed of a
rotating wheel.

F15-9(1)本例的目标是测量旋转轮的旋转速率。
F15-9(1) The goal is to measure the RPM of a rotating wheel.
F15-9(2)传感器被用于测量该旋转轮的速率。轮的周围有很多小孔,这是个光传感器。
F15-9(2) A sensor is used to detect the passage of
a hole in the wheel. In fact, to receive additional resolution, the wheel could
contain multiple holes that are equally spaced.
F15-9(3)32 位的输入捕获寄存器用于捕获每次中断时的 CPU 计数值(也就是时间戳)。
F15-9(3) A 32-bit input capture register is used
to capture the value of a free-running counter when the hole is detected.
F15-9(4)检测到一个孔时中断产生。ISR 读取当前的捕获寄存器值并减去先前的捕获值,就能知道旋转的速率。
F15-9(4) An interrupt is generated when the hole
is detected. The ISR reads the current count of the input capture register and
subtracts the value of the previous capture to receive the time it took for one
rotation (assuming only a single hole).

F15-9(5)Delta 值被发送到消息队列。因为消息实际上是一个指针,如果这个指针是 32 位的,直接将 Delta 值存到这个指针中被发送到消息队列。但更安全的方法是分配一个内存块并将 Delta 值存入内存块并将内存块地址存入消息。
F15-9(5) The delta counts are sent to a message
queue. Since a message is actually a pointer, if the pointer is 32-bits wide on
the processor in use, simply cast the 32-bit delta counts to a pointer and send
this through the message queue. A safer and more portable approach is to
dynamically allocate storage to hold the delta counts using a memory block from
μC/OS-III’s memory management services (see Chapter 17, “Memory Management” on
page 323) and send the address of the allocated memory block.
F15-9(6)当消息被发送后,测速任务被唤醒,并计算每分钟的旋转速率:
F15-9(6) When the message is sent, the RPM
measurement task wakes up and computes the RPM as follows:

用户可以给测速任务定义期限并且任务可能因为超时而被唤醒。这可能就表明旋转轮的速率为 0.
The
user may specify a timeout on the pend call and the task will wake up if a
message is not sent within the timeout period. This allows the user to easily
detect that the wheel is not rotating and therefore, the RPM is 0.
F15-9(7)该任务也可以计算平均旋转速率,最大旋转速率等待。
F15-9(7)
Along with computing RPM, the task can also compute average RPM, maximum RPM,
and whether the speed is above or below thresholds, etc.
在这个了例子中有一些有趣的事情。第一,ISR 非常短,读取捕获值并将 Delta 值发送给任务。第二,若测速任务挂起超时,就可以知道旋转轮被停止了。最后,测速任务中还可以添加一些操作,比如测量旋转轮是否转得过快或过慢,并可以告知旋转结果给其它任务。
A
few interesting things are worth noting about the above example. First, the ISR
is very short; read the input capture and post the delta counts to the task to
accomplish the time-consuming math. Second, with the timeout on the pend, it is
easy to detect that the wheel is stopped. Finally, the task can perform
additional calculations and can further detect such errors as the wheel
spinning too fast or too slow. In fact, the task can notify other tasks about
these errors, if needed.
列表 15-3 显示了如何用消息队列测量旋转速率。
Listing
15-3 shows how to implement the RPM measurement example using μC/OS-III’s
message queue services. Some of the code is pseudo-code, while the calls to
μC/OS-III services are actual calls with their appropriate arguments.


L15-3(1)消息队列被定义。
L15-3(1) Variables are declared. Notice that it is
necessary to allocate storage for the message queue itself.
L15-3(2)调用 OSInit()后,创建消息队列。
L15-3(2) Call OSInit() and create the message
queue before it is used. The best place to do this is in startup code.
L15-3(3)ISR 清除传感器中断并读取捕获寄存器的值。在 ISR 中计算 Delta 计数值。也可以直接发送捕获值,并在测速任务中计算 Delta 值。然而,这个计算是很快的,它所增加的 ISR 时间可忽略不计。
L15-3(3) The RPM ISR clears the sensor interrupt
and reads the value of the 32-bit input capture. Note that it is possible to
read RPM if there is only a 16-bit input capture. The problem with a 16-bit
input capture is that it is easy for it to overflow, especially at low RPMs.
The RPM ISR also computes delta counts directly in the ISR. It is just as easy to post the current counts and let the task compute the delta. However, the subtraction is a fast operation and does not significantly increase ISR processing time.
L15-3(4)将 Delta 值发送到测量任务,并执行相应计算。注意的是:当消息队列满的时侯再往里提交就会导致溢出。消息被快速提交时就会出现这种情况。很不幸,不能通过流量控制解决这个问题因为消息是在 ISR 中被提交的。
L15-3(4) Send the delta counts to the RPM task,
responsible for computing the RPM and perform additional computations. Note
that the message gets lost if the queue is full when the user attempts to post.
This happens if data is generated faster than it is processed. Unfortunately,
it is not possible to implement flow control in the example because it is
dealing with an ISR.
L15-3(5)测量任务等待测量 ISR 中的消息。第三个参数设置了等待期限。在这里设置了等待期限为 10 秒。参数 ts 包含了消息被提交时的时间戳。通过 OS_TS_GET()获得当前的时间戳。该消息的相应时间就能计算:
L15-3(5) The RPM task starts by waiting for a
message from the RPM ISR by pending on the message queue. The third argument
specifies the timeout. In this case, ten seconds worth of timeout is specified.
However, the value chosen depends on the requirements of an application.
Also
notice that the ts variable contains the timestamp of when the post was
completed. Determine the time it took for the task to respond to the message
received by calling OS_TS_GET(), and subtract the value of ts:

L15-3(6)等待超时(假定如旋转轮停止旋转)。
L15-3(6) If a timeout occurs, assume the wheel is
no longer spinning.
L15-3(7)计算旋转速率。
L15-3(7) The RPM is computed from the delta counts
received, and from the reference frequency of the free-running counter.
L15-3(8)其它的一些操作。事实上,在检测到错误的情况下消息可以被发送给其它任务。例如,当旋转速率很快,其它任务就可以让旋转轮减速。
L15-3(8) Additional computations are performed as
needed. In fact, messages can be sent to different tasks in case error
conditions are detected. The messages would be processed by the other tasks.
For example, if the wheel spins too fast, another task can initiate a shutdown
on the device that is controlling the wheel speed.
如列表 L15-4,在这个例子中,用 OSTaskQPost()和 OSTaskQPend() 代替 OSQPost()和 OSQPend()。代码将会更简单且不需要创建一个外部消息队列。然而,当创建测量任务时,就必须设置其消息队列大小并在编译时配置 OS_CFG_TASK_Q_EN 为 1。使用外部消息队列和任务内部消息队列的区别如下介绍。
In
Listing 15-4, OSQPost() and OSQPend() are replaced with OSTaskQPost() and
OSTaskQPend() for the RPM measurement example. Notice that the code is slightly
simpler to use and does not require creating a separate message queue object.
However, when creating the RPM task, it is important to specify the size of the
message queue used by the task and compile the application code with
OS_CFG_TASK_Q_EN set to 1. The differences between using message queues and the
task’s message queue will be explained.


L15-4(1)任务的 OS_TCB 也可以接收消息。
L15-4(1) Instead of declaring a message queue, it
is important to know the OS_TCB of the task that will be receiving messages.
L15-4(2)创建测量任务并设置其消息队列的大小为 10。当然,在实时系统中最好不要将该值固定。但在这里为了说明,将该值固定为
10。
L15-4(2) The RPM task is created and a queue size of
10 entries is specified. Of course, hard-coded values should not be specified
in a real application, but instead, use #defines. Fixed numbers are used here
for sake of illustration.
L15-4(3)ISR 直接将消息发送给任务。
L15-4(3) Instead of posting to a message queue,
the ISR posts the message directly to the task, specifying the address of the
OS_TCB of the task. This is known since the OS_TCB is allocated when creating
the task.
L15-4(4)测速任务通过调用 OSTaskQPend()等待 ISR 发送的消息。这是一个内部的调用,所以没必要设置 OS_TCB 的地址。第二个参数设置等待期限。
L15-4(4) The RPM task starts by waiting for a
message from the RPM ISR by calling OSTaskQPend(). This is an inherent call so
it is not necessary to specify the address of the OS_TCB to pend on as the
current task is assumed. The second argument specifies the timeout. Here, ten
seconds worth of timeout is specified, which corresponds to 6 RPM.
消息队列另一种用法如图 15-10 所示。错误管理任务管理其它任务或 ISR 发给它的错误情况。例如,测速任务测得转盘旋转速率过快,它发送消息给错误管理任务。错误管理任务作相应的操作。
Another
interesting use of message queues is shown in Figure 15-10. Here, a task (the
server) is used to monitor error conditions that are sent to it by other tasks
or ISRs (clients). For example, a client detects whether the RPM of the
rotating wheel has been exceeded, another client detects whether an
over-temperature exists, and yet another client detects that a user pressed a
shutdown button. When the clients detect error conditions, they send a message
through the message queue. The message sent indicates the error detected, which
threshold was exceeded, the error code that is associated with error
conditions, or even suggests the address of a function that will handle the
error, and more.

消息由四个变量组成:指向下一条消息的指针、用于表明该消息所指向数据的大小的变量、存放消息最后一次被提交的时间戳的变量、消息中包含一个指向实际数据的指针。如图 15-11 所示
As
previously described, a message consists of a pointer to actual data, a
variable indicating the size of the data being pointed to and a timestamp
indicating when the message was actually sent. When sent, a message is placed
in a data structure of type OS_MSG, shown in Figure 15-11.
消息发送者和接收者都不知道消息中数据的结构因为这些都通过 uC/OS-III 的 API 隐藏起来了。
The
sender and receiver are unaware of this data structure since everything is
hidden through the APIs provided by μC/OS-III.

uC/OS-III 维护一个消息池。消息池的大小通过 OS_CFG_APP.H中的 OS_CFG_MSG_POOL_SIZE 设置。当 uC/OS-III 被初始化时,消息就会以单向列表的形式链接起来如图 15-12。注意的是这个列表由结构体 OS_MSG_POOL 管理,它包含 3 个部分:.NextPtr 指向该消息列表、.NbrFree 包含了该队列的空闲消息数、.NbrUsed 包含了该队列中已被使用的消息数。
μC/OS-III
maintains a pool of free OS_MSGs. The total number of available messages in the
pool is determined by the value of OS_CFG_MSG_POOL_SIZE found in OS_CFG_APP.H.
When μC/OS-III is initialized, OS_MSGs are linked in a single linked list as
shown in Figure 15-12. Notice that the free list is maintained by a data
structure of type OS_MSG_POOL, which contains three fields: .NextPtr, which
points to the free list; .NbrFree, which contains the number of free OS_MSGs in
the pool; and finally .NbrUsed, which contains the number of OS_MSGs allocated
to application.

消息的排列由结构体 OS_MSG_Q 控制,如图 15-13
Messages
are queued using a data structure of type OS_MSG_Q, as shown in Figure 15-13.

.InPtr 指向下一个将要被插入到队列的消息。
.InPtr This field contains a pointer to where
the next OS_MSG will be inserted in the queue. In fact, the OS_MSG will be
inserted “after” the OS_MSG pointed to.
.OutPtr 指向下一个将要被释放的消息。
.OutPtr This field contains a
pointer to where the next OS_MSG will be extracted.
.NbrEntriesSize 包含了该队列所能接受的最大消息数。队列满后再往其中发送消息,消息将不会被插入。
.NbrEntriesSize This field contains the maximum number of
OS_MSGs that the queue will hold. If an application attempts to send a message
and the .NbrEntries matches this value, the queue is considered to be full and
the OS_MSG will not be inserted.
.NbrEntries 当前队列中的消息数
.NbrEntries This field contains the
current number of OS_MSGs in the queue.
.NbrEntriesMax 记录了到目前为止队列中存放的最大消息数。
.NbrEntriesMax This field contains the highest number of
OS_MSGs existing in the queue at any given time.
uC/OS-III 中有一些函数用于操纵空闲的队列和消息。比如:OS_MsgQPut()将消息插入到 OS_MSG_Q,OS_MsgQGet()从 OS_MSG_Q 中得到一个消息,OS_MsgQFreeAll()将所有 OS_MSQ_Q 中的消息释放回消息池中。OS_MSG.C 中的其它一些 OS_MsgQ??()在初始化时使用。
A
number of internal functions are used by μC/OS-III to manipulate the free list
and messages. Specifically, OS_MsgQPut() inserts an OS_MSG in an OS_MSG_Q,
OS_MsgQGet() extracts an OS_MSG from an OS_MSG_Q, and OS_MsgQFreeAll() returns
all OS_MSGs in an
OS_MSG_Q
to the pool of free OS_MSGs. There are other OS_MsgQ??() functions in OS_MSG.C
that are used during initialization.
图 15-14 显示了有 4 个消息的 OS_MSG_Q。
Figure
15-14 shows an example of an OS_MSG_Q when four OS_MSGs are inserted.

OS_MSG_Q 通常包含在两种结构体内:OS_Q 和 OS_TCB。创建一个 OS_Q 时就内建一个 OS_MSG_Q。当设置 OS_CFG.H 中的 OS_CFG_TASK_Q_EN 为 1 时,每个任务都有其内建的消息队列。如图 15-15 所示。
OS_MSG_Qs
are used inside two additional data structures: OS_Q and OS_TCB. Recall that an
OS_Q is declared when creating a message queue object. An OS_TCB is a task
control block and, as previously mentioned, each OS_TCB can have its own
message queue when the configuration constant OS_CFG_TASK_Q_EN is set to 1 in
OS_CFG.H. Figure 15-15 shows the contents of an OS_Q and partial contents of an
OS_TCB containing an OS_MSG_Q. The OS_MSG_Q data structure is shown as an
“exploded view” to emphasize the structure within the structure.

当任务或 ISR 发送消息给另一个任务时,通过消息队列是很有效的方法。被发送的消息中的数据必须被存储,因为传送的是数据地址而不是实际的数据。
Message
queues are useful when a task or an ISR is to send data to another task. The
data sent must remain in scope as it is actually sent by reference instead of
by value. In other words, the data sent is not copied.
任务在等待消息的时候不会占用 CPU。
The
task waiting for the data will not consume CPU time while waiting for a message
to be sent to it.
使用任务内部的消息堆栈更为简单和高效。当设置 OS_CFG.H 中的OS_CFG_TASK_Q_EN 为 1 时使能任务内部消息队列。
If
it is known which task is responsible for servicing messages sent by producers,
use task message queue (i.e., OSTaskQ???()) services since they are simple and
fast. Task message queue services are enabled when OS_CFG_TASK_Q_EN is set to 1
in OS_CFG.H.
如果多个任务等待同一个消息队列中的消息,分配一个 OS_Q 并让任务所等待的消息发送到 OS_Q 中。特别的,可以广播消息给所有在消息队列中等待的任务。设置 OS_CFG.H 中的 OS_CFG_Q_EN 为1 开启消息队列服务。
If
multiple tasks must wait for messages from the same message queue, allocate an
OS_Q and have the tasks wait for messages to be sent to the queue. Alternatively,
broadcast special messages to all tasks waiting on a message queue. Regular
message queue services are enabled when OS_CFG_Q_EN is set to 1 in OS_CFG.H.
消息通过结构体 OS_MSG(从 uC/OS-III 的消息池中获得)被发送。设置消息队列的大小,和消息池的大小。
Messages
are sent using an OS_MSG data structure obtained by μC/OS-III from a pool. Set
the maximum number of messages that can be sent to a message queue, or as many
messages as are available in the pool.
在章节 10“挂起队列”中介绍了多个任务如何等待一个内核对象如信号量、mutex、事件标志组、消息队列。在这个章节中,将介绍任务等待多个对象。然而,uC/OS-III 只允许同时等待多个信号量和消息队列。换句话说,不能同时等待多个事件标志组或 mutex。
In
Chapter 10, “Pend Lists (or Wait Lists)” on page 177 we saw how multiple tasks
can pend (or wait) on a single kernel object such as a semaphore, mutual
exclusion semaphore, event flag group, or message queue. In this chapter, we
will see how tasks can pend on multiple objects. However, μC/OS-III only allows
for pend on multiple semaphores and/or message queues. In other words, it is
not possible to pend on multiple event flag groups or mutual exclusion
semaphores.
如图 16-1 所示,任务可以同时等待多个信号量和消息队列。任务接收到一个信号量或消息就会被就绪。任务通过调用 OSPendMulti() 等待多个对象,并可使设置等待时限。这个时限对应于所有的对象。当在这个时限内没有收到一个对象,任务就会返回一个错误代号表示等待超时。
As
shown in Figure 16-1, a task can pend on any number of semaphores or message
queues at the same time. The first semaphore or message queue posted will make
the task ready to run and compete for CPU time with other tasks in the ready
list. As shown, a task pends on multiple objects by calling OSPendMulti() and
specifies an optional timeout value. The timeout applies to all of the objects.
If none of the objects are posted within the specified timeout, the task
resumes with an error code indicating that the pend timed out.

列表 16-1 显示了 OSPendMulti()的原型,图 16-2 显示了一个数据类型为 OS_PEND_DATA 的数组。
Listing
16-1 shows the function prototype of OSPendMulti() and Figure 16-2 exhibits an
array of OS_PEND_DATA elements.


L16-1(1)OSPendMulti 的第一个参数:数据类型为 OS_PEND_DATA 的数组。数组的大小决定了任务所等待的内核对象数。例如,如果任务要等待 3 个信号量和 2 个消息,那么数组的大小为 5。
L16-1(1)
OSPendMulti() is passed an array of OS_PEND_DATA elements. The caller must
instantiate an array of OS_PEND_DATA. The size of the array depends on the
total number of kernel objects that the task wants to pend on. For example, if
the task wants to pend on three semaphores and two message queues then the
array contains five OS_PEND_DATA elements as shown below:

每个数组元素中的.PendObjPtr 都要被初始化、指向所等待的对象。例如:
The
calling task needs to initialize the .PendObjPtr of each element of the array
to point to each of the objects to be pended on. For example:

L16-1(2)第二个参数为数组的大小,这个例子中为 5。
L16-1(2) This argument specifies the size of the
OS_PEND_DATA table. In the above example, this is 5.
L16-1(3)设置等待期限,如果在这段时间内没有一个对象被提交就会超时。设置 0 值表示永远等待下去。
L16-1(3) Specify whether or not to timeout in case
none of the objects are posted within a certain amount of time. A non-zero
value indicates the number of ticks to timeout. Specifying zero indicates the
task will wait forever for any of the objects to be posted.
L16-1(4)参数"opt"设置了等待的方式。OS_OPT_PEND_BLOCKING和OS_OPT_PEND_NON_BLOCKING(任务不被挂起)。
L16-1(4) The “opt” argument specifies whether to wait
for objects to be posted (set opt to OS_OPT_PEND_BLOCKING) or, not block if
none of the objects have already been posted (set opt to
OS_OPT_PEND_NON_BLOCKING).
F16-2(1)如大多数 uC/OS-III 函数一样,函数会返回代表此函数执行结果的错误代号。
F16-2(1)
As with most μC/OS-III function calls, specify the address of a variable that
will receive an error code based on the outcome of the function call. See
Appendix A, “μC/OS-III API Reference Manual” on page 375 for a list of possible
error codes. As always, it is highly recommended to examine the error return
code.
F16-2(2)所有的对象的类型都是 OS_PEND_OBJ。
F16-2(2) Note that all objects are cast to
OS_PEND_OBJ data types.
当 OSPendMulti()被调用,它首先确认数组中所有的元素是否为OS_SEM 或 OS_Q。如果不是,就返回对应的错误代号。
When
called, OSPendMulti() first starts by validating that all of the objects
specified in the OS_PEND_DATA table are either an OS_SEM or an OS_Q. If not, an
error code is returned.
OSPendMulti()先遍历数组,查看其中的对象是否已经被提交。如果有,OSPendMulti()就在表中该索引中填入相应的值:RdyObjPtr,RdyMsgPtr,.RdyMsgSize,.RdyTs。
Next,
OSPendMulti() goes through the OS_PEND_DATA table to see if any of the objects
have already posted. If so, OSPendMulti() fills the following fields in the
table: .RdyObjPtr, .RdyMsgPtr, .RdyMsgSize and .RdyTS.
.RdyObjPtr 指向已经被提及的对象。例如,表中索引 0 的信号量已经被提交,那么 my_pend_multi_tbl[0].RdyObjPtr 的值会被设置为 my_pend_multi_tbl[0].PendObjPtr。
.RdyObjPtr
is a pointer to the object if
the object has been posted. For example, if the first object in the table is a
semaphore and the semaphore has been posted to, my_pend_multi_tbl[0].RdyObjPtr
is set to my_pend_multi_tbl[0].PendObjPtr.
.RdyMsgPtr 如果表中该索引等待的是消息队列,且有消息被接收,那么该指针指向消息。
.RdyMsgPtr is a pointer to a message if the
object in the table at this entry is a message queue and a message was received
from the message queue.
.RdyMsgSize 如果表中该索引等待的是消息队列,且有消息被接收,那么该值为消息中数据的大小。
.RdyMsgSize is the size of the message received if the
object in the table at this entry is a message queue and a message was received
from the message queue.
.RdyTS 存放着对象被提交时的时间戳。
.RdyTS is the timestamp of
when the object posted. This allows the user to know when a semaphore or
message queue posts.
如果没有对象被提交,OSPendMulti()就会将任务放入所有对象的挂起队列中,这是一个复杂的操作因为其它任务也可能在这些对象中等待。
If
there are no objects posted, then OSPendMulti() places the current task in the
wait list of all the objects that it is pending on. This is a complex and
tedious process for OSPendMulti() since there can be other tasks in the pend
list of some of these objects we are pending on.
图 16-3 是任务在两个信号量中挂起的例子:
To
indicate how tricky things get, Figure 16-3 is an example of a task pending on
two semaphores.

Figure
16-3 Task pending on two semaphores
F16-3(1)任务的 OS_TCB 中有指针指向由 OS_PEND_DATA 组成的表(如上面所述)。
F16-3(1)
A pointer to the base address of the OS_PEND_DATA table is placed in the OS_TCB
of the task placed in the pend list of the two semaphores.
F16-3(2)OS_TCB 中存放了 OS_PEND_DATA 所组成的表中的 OS_PEND_DATA 的数量。表中有两个记录。
F16-3(2)
The numbers of entries in the OS_PEND_DATA table are also placed in the OS_TCB.
Again, this task is waiting on two semaphores and therefore there are two
entries in the table.
F16-3(3)第一个信号量指向第一个 OS_PEND_DATA。
F16-3(3) Entry [0] of the OS_PEND_DATA table is
linked to the semaphore object specified by that entry’s .PendObjPtr.
F16-3(4)OS_PEND_DATA 表中记录 0 的.PendObjPtr 指向第一个信号量。通过调用 OSPendMulti()等待对象并设定指针指向。
F16-3(4) This pointer was specified by the caller
of OSPendMulti().
F16-3(5)因为信号量中只有 1 个任务在等待,所以.PrevPtr 和.NextPtr 都指向 NULL。
F16-3(5) Since there is only one task in the pend
list of the semaphore, the .PrevPtr and .NextPtr are pointing to NULL.
F16-3(6)第二个信号量指向第二个 OS_PEND_DATA。
F16-3(6) The second semaphore points to the second
entry in the OS_PEND_DATA table.
F16-3(7)OS_PEND_DATA 表中记录 0 的.PendObjPtr 指向第二个信号量。通过调用 OSPendMulti()等待对象并设定指针指向。
F16-3(7) This pointer was specified by the caller
of OSPendMulti().
F16-3(8)因为信号量中只有 1 个任务在等待,所以.PrevPtr 和.NextPtr 都指向 NULL。
F16-3(8) The second semaphore only has one entry
in its pend list. Therefore the .PrevPtr and .NextPtr both point to NULL.
F16-3(9)任务中有指针链接到两个 OS_PEND_DATA 结构体。
F16-3(9) OSPendMulti() links back each
OS_PEND_DATA entry to the task that is waiting on the two semaphores.
图 16-4 是一个更加复杂的例子:一个任务等待两个信号量,另一个任务等待其中的一个信号量。例子只是呈现了信号量,也可以扩展到消息队列及两者的组合。
Figure
16-4 is a more complex example where one task is pending on two semaphores
while another task also pends on one of the two semaphores. The examples
presented so far only show semaphores, but they could be combinations of
semaphores and message queues.

当任务或 ISR 发送消息给等待该消息的任务,OSPendMulti()返回。表示OS_PEND_DATA 表中有对象被提交,这些通过在表中相应索引中填入对应值实现,如图 16-2。
When
either an ISR or a task signals or sends a message to one of the objects that
the task is pending on, OSPendMulti() returns, indicating in the OS_PEND_DATA
table which object was posted. This is done by only filling in “one” of the
.RdyObjPtr entries, the one that corresponds to the object posted as shown in
Figure 16-2.
当任务等待 5 个内核对象时,且在任务调用 OSPendMulti()之前已经有一个对象就绪,那么表中结构将会是图 16-5
Only
one of the entries in the OS_PEND_DATA table will have a .RdyObjPtr with a
non-NULL value while all the other entries have the .RdyObjPtr set to NULL.
Going back to the case where a task waits on five semaphores and two message
queues, if the first message queue is posted while the task is pending on all
those objects, the OS_PEND_DATA table will be as shown in Figure 16-5.

uC/OS-III 允许任务等待多个内核对象。
μC/OS-III
allows tasks to pend on multiple kernel objects.
OSPendMulti()只能挂起多个信号量和消息队列。不能挂起多个事件标志组和 mutex。
OSPendMulti()
can only pend on multiple semaphores and message queues, not event flags and
mutual-exclusion semaphores.
在调用 OSPendMulti()之前对象已经被提交,uC/OS-III会“告诉”OSPendMulti()那些对象已经被提交。
If
the objects are already posted when OSPendMulti() is called, μC/OS-III will
specify which of the objects in the list of objects have already been posted.
如果没有已提交的对象,OSPendMulti()就会将任务放入所有对象的挂起队列中。当有一个对象被提交时 OSPendMulti()就会返回。
If
none of the objects are posted, OSPendMulti() will place the calling task in
the pend list of all the desired objects.
这这种情况下,OSPendMulti()会标记哪个对象已被提交。
OSPendMulti()
will return as soon as one of the objects is posted. In this case,
OSPendMulti() will indicate which object was posted.
OSPendMulti()是一个复杂的函数,可能会导致长临界段。
OSPendMulti()
is a complex function that has potentially long critical sections.
可以通过使用编译器提供的函数 malloc()和 free()动态地分配和释放内存快。然而,在嵌入式实时系统中使用 malloc()和 free()可能是非常危险的。最后,它可能会导致很多内存碎片。
An application
can allocate and free dynamic memory using any ANSI C compiler’s malloc() and
free() functions, respectively. However, using malloc() and free() in an
embedded real-time system may be dangerous. Eventually, it might not be
possible to obtain a single contiguous memory area due to fragmentation.
Fragmentation is the development of a large number of separate free areas
(i.e., the total free memory is fragmented into small, non-contiguous pieces).
Execution time of malloc() and free() is generally nondeterministic given the
algorithms used to locate a contiguous block of free memory.
uC/OS-III 可以获得连续的内存块,如图 17-1 所示。内存块大小可以相同,所有的内存分区包含了整数个内存块。在特定的时间执行内存块的分配和释放。内存分区以内存块数组的形式被静态分配的。如果分配后不被释放,也可以调用 malloc()动态分配。
μC/OS-III
provides an alternative to malloc() and free() by allowing an application to
obtain fixed-sized memory blocks from a partition made from a contiguous memory
area, as illustrated in Figure 17-1. All memory blocks are the same size, and
the partition contains an integral number of blocks. Allocation and
deallocation of these memory blocks is performed in constant time and is
deterministic. The partition itself is typically allocated statically (as an
array), but can also be allocated by using malloc() as long as it is never
freed.

如图 17-2 所示,可以同时有多个内存分区且每个分区的内存块个数和大小可以不同,根据需求设置,但内存块被分配后必须返回给它所在的内存分区。这种管理方式仅会导致内存块块内的碎片,决定于应用中所设置内存块的大小。
As indicated
in Figure 17-2, more than one memory partition may exist in an application and
each one may have a different number of memory blocks and be a different size.
An application can obtain memory blocks of different sizes based upon
requirements. However, a specific memory block must always be returned to the
partition that it came from. This type of memory management is not subject to
fragmentation except that it is possible to run out of memory blocks. It is up
to the application to decide how many partitions to have and how large each
memory block should be within each partition.

在使用内存分区之前必须创建它。这样 uC/OS-III 就可以知道这个内存分区的相关信息并管理他们。调用 OSMemCreate()创建一个内存分区。如图 17-3 所示
Before
using a memory partition, it must be created. This allows μC/OS-III to know
something about the memory partition so that it can manage their allocation and
deallocation. Once created, a memory partition is as shown in Figure 17-3.
Calling OSMemCreate() creates a memory partition.

F13-3(1)当创建一个内存分区时,内存控制块(OS_MEM)的地址需作为参数被提供。通常内存控制块从静态内存中分配,然而,也可以调用 malloc()从堆中获得。用户代码不能释放内存控制块。
F17-3(1) When creating a partition, the
application code supplies the address of a memory partition control block
(OS_MEM). Typically, this memory control block is allocated from static memory,
however it can also be obtained from the heap by calling malloc(). The
application code should however never deallocate it.
F13-3(2)OSMemCreate()将内存块链接起来并将其链表的首地址赋值给OS_MEM 结构体。
F17-3(2) OSMemCreate() organizes the continuous
memory provided into a singly linked list and stores the pointer to the
beginning of the list in the OS_MEM structure.
F13-3(3)每个内存块的大小必须大于一个指针。
F17-3(3) Each memory block must be large enough to
hold a pointer. Given the nature of the linked list, a block needs to be able
to point to the next block.
列表 17-1 中显示了如何用 uC/OS-III 创建一个内存分区
Listing
17-1 indicates how to create a memory partition with μC/OS-III.

L17-1(1)给内存分区分配一个内存控制块。可以静态分配或用 malloc() 分配。但是,用户代码不能删除这个内存控制块。
L17-1(1)
An application needs to allocate storage for a memory partition control block.
This can be a static allocation as shown here or malloc() can be used in the
code.
However,
the application code must not deallocate the memory control block.
L17-1(2)给内存分区分配多个内存块。这可以静态分配或用 malloc() 分配。
L17-1(2) The application also needs to allocate
storage for the memory that will be split into memory blocks. This can also be
a static allocation or malloc() can be used. The same reasoning applies. Do not
deallocate this storage since other tasks may rely on the existence of this
storage.
L17-1(3)内存分区在被创建之前需被分配内存控制块和内存块。最好在 main()中创建内存分区。当然,也可以在 main()中调用能分配内存分区的函数。
L17-1(3) Memory partition must be created before
allocating and deallocating blocks from the partition. One of the best places
to create memory partitions is in main() prior to starting the multitasking
process. Of course, an application can call a function from main() to do this
instead of actually placing the code directly in main().
L17-1(4)将内存控制块的地址传递给 OSMemCreate()。用户代码不能访问 OS_MEM 结构体。只能通过 uC/OS-III 的 API 访问。
L17-1(4) Pass the address of the memory partition
control block to OSMemCreate(). Never reference any of the internal members of
the OS_MEM data structure.
Instead, use μC/OS-III’s API.
L17-1(5)给内存分区分配一个名字。
L17-1(5) Assign a name to the memory partition.
There is no limit to the length of the ASCII string as μC/OS-III saves a
pointer to the ASCII string in the partition control block and not the actual
characters.
L17-1(6)将内存块数组的首地址传递给 OSMemCreate()。
L17-1(6) Pass the base address of the storage area
reserved for the memory blocks.
L17-1(7)该参数为分配给内存分区的内存块数。
L17-1(7) Specify how many memory blocks are
available from this memory partition. Hard coded numbers are used for the sake
of the illustration but one should instead use #define constants.
L17-1(8)标明该内存分区中每个内存块的大小。用数字值是为了说明,最好用#define 定义的宏代替。
L17-1(8) Specify the size of each memory block in
the partition. Again, a hard coded value is used for illustration, which is not
recommended in real code.
L17-1(9)OSMemCreate()根据函数的执行结果返回一个错误代号。
L17-1(9) As with most μC/OS-III services,
OSMemCreate() returns an error code indicating the outcome of the service. The
call is successful if “err” contains
OS_ERR_NONE.
列表 17-2 介绍了如何使用 malloc()创建一个内存分区。同样的,用户代码不能释放内存控制块和内存块。
Listing
17-2 shows how to create a memory partition with μC/OS-III, this time using
malloc() to allocate storage. Do not deallocate the memory control block or the
storage for the partition.

L17-2(1)定义一个指针指向 malloc()分配的内存控制块。
L17-2(1) Instead of allocating static storage for
the memory partition control block, assign a pointer that receives the OS_MEM
allocated using malloc().
L17-2(2)malloc()分配空间给内存控制块 OS_MEM。
L17-2(2) The application allocates storage for the
memory control block.
L17-2(3)给内存分区分配内存块。
L17-2(3) Allocate storage for the memory partition.
L17-2(4)将 OS_MEM 的地址传递给 OSMemCreate()。
L17-2(4) Pass a pointer to the allocated memory
control block to OSMemCreate().
L17-2(5)将内存块的基地址传递给 OSMemCreate()。
L17-2(5) Pass the base address of the storage used
for the partition.
L17-2(6)最后,传递内存块的个数和大小给 OSMemCreate()。同样的,用数字分配是为了说明,但最好用#defines 定义的宏常量代替。
L17-2(6) Finally, pass the number of blocks and
the size of each block so that μC/OS-III creates the linked list of 12 blocks
of 100 bytes each. Again, hard coded numbers are used, but these would
typically be replaced by #defines.
应用代码通过调用 OSMemGet()可以从内存分区中申请内存块。如列表 17-3 所示。(假定内存分区已经被创建)
Application
code can request a memory block from a partition by calling OSMemGet() as shown
in Listing 17-3. The code assumes that the partition was already created.

L17-3(1)所有要访问该内存分区的任务或 ISR 必须可以访问这个内存分区的内存控制块。(内存控制块在任务或 ISR 的作用域范围内)
L17-3(1) The memory partition control block must
be accessible by all tasks or ISRs that will be using the partition.
L17-3(2)调用 OSMemGet()从内存分区中获得一个内存块。函数返回后该指针将指向被分配内存块的基地址。(类似于 malloc())
L17-3(2) Simply call OSMemGet() to obtain a memory
block from the desired partition. A pointer to the allocated memory block is
returned. This is similar to malloc(), except that the memory block comes from
a pool that is guaranteed to not fragment.
L17-3(3)根据返回的错误代号检测函数的执行结果。
L17-3(3) It is important to examine the returned
error code to ensure that there are free memory blocks and that the application
can start putting content in the memory blocks.
当用户对内存块的使用完毕后,必须将该内存块归还给对应的内存分区。调用 OSMemPut()实现这个功能。如列表 17-4 所示。
The
application code must return an allocated memory block back to the proper
partition when finished. Do this by calling OSMemPut() as shown in Listing
17-4. The code assumes that the partition was already created.

L17-4(1)所有要访问该内存分区的任务或 ISR 必须可以访问这个内存分区的内存控制块。
L17-4(1) The memory partition control block must be
accessible by all tasks or ISRs that will be using the partition.
L17-4(2)调用 OSMemPut()将内存块归还给对应内存分区。注意的是 uC/OS-III 不检测内存块是否被归还给对应内存分区。所以使用时必须非常小心。
L17-4(2) Simply call OSMemPut() to return the
memory block back to the memory partition. Note that there is no check to see
whether the proper memory block is being returned to the proper partition
(assuming you have multiple different partitions). It is therefore important to
be careful (as is necessary when designing embedded systems).
L17-4(3)该指针指向要归还的内存块地址。它的类型为"void *"。
L17-4(3) Pass the pointer to the data area that is
allocated so that it can be returned to the pool. Note that a “void *” is
assumed.
L17-4(4)检测错误代号。
L17-4(4) Examine the returned error code to ensure
that the call was successful.
编译时设置 OS_CFG.H 中的 OS_CFG_MEM_EN 为 1 开启内存管理服务。
Memory
management services are enabled at compile time by setting the configuration
constant OS_CFG_MEM_EN to 1 in OS_CFG.H.
表 13-1 是内存管理相关函数的总结。
There
are a number of operations to perform on memory partitions as summarized in
Table 13-1.
|
函数名 |
功能 |
|
OSMemCreate() |
创建一个内存分区 Create a memory partition. |
|
OSMemGet() |
从内存分区中获得一个内存块 Obtain a memory block from a memory partition. |
|
OSMemPut() |
归还一个内存块给相应的内存分区 Return a memory block to a memory partition. |
表 17-1 内存分区 API 总结
OSMemCreate()只能在任务级被调用,但是 OSMemGet()和OSMemPut()可以在 ISR 中被调用。
OSMemCreate()
can only be called from task-level code, but OSMemGet() and OSMemPut() can be
called by Interrupt Service Routines (ISRs).
列表 17-4 显示了如何动态地分配内存。在这个例子中,左边任务读取模拟输入值(可以是压力、温度、电压),并发送消息给右边任务,消息中包含了根据模拟输入所计算出的相关信息的地址。
Listing
17-4 shows an example of how to use the dynamic memory allocation feature of
μC/OS-III, as well as message-passing capabilities (see Chapter 15, “Message
Passing” on page 289). In this example, the task on the left reads and checks
the value of analog inputs (pressures, temperatures, and voltage) and sends a
message to the second task if any of the analog inputs exceed a threshold. The
message sent contains information about which channel had the error, an error
code, an indication of the severity of the error, and other information.
在这个例子中错误的处理被集中在右边任务中。其它任务或 ISR也可以发送消息给该错误处理任务。
Error
handling in this example is centralized. Other tasks, or even ISRs, can post
error messages to the error-handling task. The error-handling task could be
responsible for displaying error messages on a monitor (a display), logging
errors to a disk, or dispatching other tasks to take corrective action based on
the error.

F17-4(1)任务读取模拟输入。如果测得模拟输入超范围,就发送消息给错误处理函数。
F17-4(1) The analog inputs are read by the task.
The task determines that one of the inputs is outside a valid range and an
error message needs to be sent to the error handler.
F17-4(2)任务从内存分区中申请一个内存块用于存放检测模拟输入所得的相关数据。
F17-4(2) The task then obtains a memory block from
a memory partition so that it can place information regarding the detected
error.
F17-4(3)任务将检测模拟输入所得的相关数据存入内存块。然而,没必要在内存块中存放时间戳因为时间戳被存放在消息中。
F17-4(3) The task writes this information to the
memory block. As mentioned above, the task places the analog channel that is at
fault, an error code, an indication of the severity, possible solutions, and
more. There is no need to store a timestamp in the message, as time stamping is
a built-in feature of μC/OS-III so the receiving task will know when the
message was posted.
F17-4(4)任务提交消息给错误处理任务。当然,错误处理任务知道消息中所包含的数据的含义。一旦消息被发送,发送者任务不能再访问消息所指向的内存块。
F17-4(4) Once the message is complete, it is
posted to the task that will handle such error messages. Of course the
receiving task needs to know how the information is placed in the message. Once
the message is sent, the analog input task is no longer allowed (by convention)
to access the memory block since it sent it out to be processed.
F17-4(5)错误处理任务通常在消息队列中等待。
F17-4(5) The error handler task (on the right)
normally pends on the message queue.
This
task will not execute until a message is sent to it.
F17-4(6)错误处理任务接收到消息后,执行相应的操作。
F17-4(6) When a message is received, the error
handler task reads the contents of the message and performs necessary
action(s). As indicated, once sent, the sender will not do anything else with
the message.
F17-4(7)错误处理任务处理完毕相应的操作后,将内存块归还给对应的内存分区。因此,发送者和接收者都必须知道内存块是属于哪个内存分区的,或者发送者可以将内存分区的地址作为要发送消息的数据中的一部分。这样,错误处理函数就知道将这个内存块归还给哪个内存分区了。
F17-4(7) Once the error handler task is finished
processing the message, it simply returns the memory block to the memory
partition. The sender and receiver therefore need to know about the memory
partition or, the sender can pass the address of the memory partition as part
of the message and the error handler task will know where to return the memory
block.
任务在内存分区被分配完时可以等待内存块。uC/OS-III 不支持任务等待内存分区,但是可以通过一个信号量用于内存分区中内存块的分配。如图 17-5
Sometimes
it is useful to have a task wait for a memory block in case a partition runs
out of blocks. μC/OS-III does not support pending on partitions, but it is
possible to support this requirement by adding a counting semaphore (see
Chapter 13, “Resource Management” on page 209) to guard the memory partition.
This is illustrated in Figure 17-5.

F17-5(1)获得一个内存块时,先调用 OSSemPend()获得一个信号量,然后再调用 OSMemGet()获得一个内存块。
F17-5(1)
To obtain a memory block, simply obtain the semaphore by calling OSSemPend() and
call OSMemGet() to receive the memory block.
F17-5(2)释放一个内存块时,先调用 OSMemPut()释放这个内存块,然后再调用 OSSemPost()释放这个信号量。
F17-5(2)
To release a block, simply return the memory block by calling OSMemPut() and
signal the semaphore by calling OSSemPost().
上面的操作必须以这个顺序执行。
The
above operations must be performed in order.
用户可以在 ISR 中使用 OSMemGet()和 OSMemPut(),因为这两个函数不会被阻塞且能快速地被执行。(不能在 ISR 中调用会引起阻塞的函数)
Note
that the user may call OSMemGet() and OSMemPut() from an ISR since these
functions do not block and in fact, execute very quickly. However, you cannot
use blocking calls from ISRs.
不要在嵌入式系统中使用 malloc()和 free(),因为这样会导致内存碎片。
{就是说定义不需要释放的空间时可以使用 malloc(),这样能使所定义的空间的利用率接近为 100%}
Do
not use malloc() and free() in embedded systems since they lead to
fragmentation.
可以用 malloc()动态的分配内存空间,但不要释放这些内存空间。
It
is possible to use malloc() to allocate memory from the heap, but do not
deallocate the memory.
用户可以创建任意个内存分区(限制于处理器的 RAM)。
The
application programmer can create an unlimited number of memory partitions
(limited only by the amount of available RAM).
Memory
partition services in μC/OS-III start with the OSMem???() prefix, and the
services available to the application programmer are described in Appendix A,
“μC/OS-III API Reference Manual” on page 375.
uC/OS-III 中与内存分区相关的函数都是以 OSMem???()为前缀。详见附录 A。
通过设置 OS_CFG.H 中的 OS_CFG_MEM_EN 为 1 开启内存管理服务。
Memory
management services are enabled at compile time by setting the configuration
constant OS_CFG_MEM_EN to 1 in OS_CFG.H.
OSMemGet()和 OSMemPur()可以在 ISR 中被调用。
OSMemGet() and OSMemPut() can be called from ISRs.
这个章节介绍了如何移植 uC/OS-III 到不同的处理器中。为了提高可移植性,绝大多数的 uC/OS-III 代码都是用 C 编写的。然而,依旧有一些需根据特定的处理器而编写相应的 C 和汇编代码。比如uC/OS-III 直接控制处理器寄存器部分的代码,就只能用汇编实现。
This
chapter describes in general terms how to adapt μC/OS-III to different
processors. Adapting μC/OS-III to a microprocessor or a microcontroller is
called porting. Most of μC/OS-III is written in C for portability. However, it
is still necessary to write processor-specific code in C and assembly language.
μC/OS-III manipulates processor registers, which can only be done using
assembly language.
因为 uC/OS-III 类似于
uC/OS-II,用户可以先从移植 uC/OS-II 开始。
Porting
μC/OS-III to different processors is relatively easy as μC/OS-III was designed
to be portable and, since μC/OS-III is similar to μC/OS-II, the user can start
from a μC/OS-II port. If there is already a port for the processor to be used,
it is not necessary to read this chapter unless, of course, there is an
interest in knowing how μC/OS-III processor-specific code works.
如果处理器满足如下条件,就能移植 uC/OS-III。
μC/OS-III
can run on a processor if it satisfies the following general requirements:
■ 处理器有对应的能产生可重入代码的 C 编译器
The
processor has an ANSI C compiler that generates reentrant code.
■ 处理器支持中断且能提供周期性的中断(通常介于 10 到 1000Hz 之间)。
The
processor supports interrupts and can provide an interrupt that occurs at
regular intervals (typically between 10 and 1000 Hz).
■ 可以关中断和开中断
Interrupts
can be disabled and enabled.
■ 处理器支持存储和载入堆栈指针、CPU 寄存器、堆栈的指令。
The
processor supports a hardware stack that accommodates a fair amount of data
(possibly many kilobytes).
■
The
processor has instructions to save and restore the stack pointer and other CPU
registers, either on the stack or in memory.
■ 处理器有足够的 RAM 用于存放 uC/OS-III 的变量、结构体、内部任务堆栈、任务堆栈等
The
processor has access to sufficient RAM for μC/OS-III’s variables and data
structures as well as internal task stacks.
■ 编译器支持 64 位的数据类型
The
compiler should support 64-bit data types (typically “long long”).
图 18-1 显示了 uC/OS-III 的架构和它与其他软件、硬件成分的关系。
Figure
18-1 shows the μC/OS-III architecture and its relationship with other software
components and hardware. When using μC/OS-III in an application, the user is
responsible for providing application software and μC/OS-III configuration
sections.

F18-1(1)移植 uC/OS-III 需修改 3 个与内核相关的文件:OS_CPU.H、
OS_CPU_A.ASM、OS_CPU_C.C。
F18-1(1) A μC/OS-III port consists of writing or
changing the contents of three kernel-specific files: OS_CPU.H, OS_CPU_A.ASM
and OS_CPU_C.C.
F18-1(2)移植 uC/OS-III 需修改 3 个与 CPU 相关的文件:CPU.H、
CPU_A.ASM、CPU_CORE.C。
F18-1(2) A port also involves writing or changing
the contents of three CPU specific files: CPU.H, CPU_A.ASM and CPU_CORE.C.
F18-1(3)BSP 中通常包含了 uC/OS-III 与定时器(产生时基的定时器)、中断控制器的接口。
F18-1(3) A Board Support Package (BSP) is
generally necessary to interface μC/OS-III to a timer (which is used for the
clock tick) and an interrupt controller.
F18-1(4)有些半导体厂商会提高相应的固件库文件,这些文件会被包含在 CPU/MCU 中。
F18-1(4) Some semiconductor manufacturers provide
source and header files to access on-chip peripherals. These are contained in
CPU/MCU specific files.
根据不同的处理器,移植可能会改变 100 到 400 行代码。
Porting
μC/OS-III is quite straightforward once the subtleties of the target processor
and the C compiler/assembler are understood. Depending on the processor, a port
consists of writing or changing between 100 and 400 lines of code, which takes
a few hours to a few days to accomplish. The easiest thing to do, however, is
to modify an existing port from a processor that is similar to the one intended
for use.
uC/OS-III 的移植看起像是 uC/OS-II 的移植。从 uC/OS-II 转换到 uC/OS-III 大约需要一小时时间。这个过程详见附录 C。
A
μC/OS-III port looks very much like a μC/OS-II port. Since μC/OS-II was ported
to well over 45 different CPU architectures it is easy to start from a μC/OS-II
port. Converting a μC/OS-II port to μC/OS-III takes approximately an hour. The
process is described in Appendix C, “Migrating from μC/OS-II to μC/OS-III” on
page 599.
移植包括三方面内容:CPU、OS、BSP。
A
port involves three aspects: CPU, OS and board-specific code. The
board-specific code is often called a Board Support Package (BSP) and from μC/OS-III’s
point of view, requires very little.
与 CPU 相关的代码决定于 CPU 的架构。例如,关中断和开中断、堆栈的字长,堆栈的生长方向等等。与 CPU 相关的代码被封装在叫做 uC/CPU 的模块中。
CPU-specific
code is related to the CPU and the compiler in use, and less so with μC/OS-III.
For example, disabling and enabling interrupts and the word width of the stack,
whether the stack grows from high-to-low memory or from low-to-high memory and
more are all CPU specific and not OS specific. Micriμm encapsulates CPU
functions and data types into a module called μC/CPU.
表 18-1 中显示了 uC/CPU 中的文件及他们所在的位置。
Table
18-1 shows the name of μC/CPU files and where they should be placed on the
computer used to develop a μC/OS-III-based application.
Here,
<processor> is the name of the processor that the CPU*.* files apply to,
and <compiler> is the name of the compiler that these files assume
because of different assembly language directives that different tool chains
use.
The
above source files for the CPU that came with this book are found when
downloading the code from the Micriμm website.

CPU_DEF.H
该文件不需要被改变:CPU_DEF.H 中包含了 Micrium 公司提供的软件所用到的#define 定义的宏。
This
file should not require any changes. CPU_DEF.H declares #define constants that
are used by Micriμm software components.
CPU.H
不同 CPU 间的字长可能不同,CPU.H 中定义了很多数据类型。在 Micrium 公司中,不会使用 C 编译器的数据类型 int,short,long,char 等,而是定义了更易于看懂的数据类型。查阅编译器的用户手册,看其数据类型所对应的字长。如果使用 32 位 CPU 时,以下的定义无需改变。
Many
CPUs have different word lengths and CPU.H declares a series of type
definitions that ensure portability. Specifically, at Micriμm, C data types
int, short, long, char, etc., are not used. Instead, clearer data types are
defined. Consult compiler documentation to determine whether the standard
declarations described below must be changed for the CPU/compiler used. When
using a 32-bit CPU, the declarations below should work without change.

(1)uC/OS-III 中非常重要的数据类型是 CPU_STK,它设置了堆栈的长度。决定了压入和弹出堆栈的数据是 8 位、16 位、32 位或者是64 位的。
Especially
important for μC/OS-III is the definition of the CPU_STK data type, which sets
the width of a stack entry. Specifically, is the width of data pushed to and
popped from the stack 8 bits, 16 bits, 32 bits or 64 bits?
(2) CPU_SR 数据类型表示的是处理器的状态寄存器,中断时用于保存 CPU 状态的寄存器。
CPU_SR
defines the data type for the processor’s status register (SR) that generally
holds the interrupt disable status.
(3) CPU_TS 是时间戳的数据类型。
The
CPU_TS is a time stamp used to determine when an operation occurred, or to
measure the execution time of code.
CPU.H 中也定义了关中断、开中断的宏:CPU_CRITICAL_ENTER()和CPU_CRITICAL_EXIT()。还有定义了 CPU_C.C 和 CPU_CORE.C 中函数的原型。
CPU.H
also declares macros to disable and enable interrupts: CPU_CRITICAL_ENTER() and
CPU_CRITICAL_EXIT(), respectively. Finally, CPU.H declares function prototypes
for a number of functions found in either CPU_C.C or CPU_CORE.C.
CPU_C.C
这是个可选的文件,存放了 CPU 的中断控制器、定时器的相关代码。绝大多数应用中是不包含这个文件的。
This
is an optional file containing CPU-specific functions to set the interrupt
controller, timer prescalers, and more. Most implementations will not contain
this file.
CPU_CFG.H
这是个配置文件,根据应用更改相应的#define。
This
is a configuration file to be copied into the product directory and changed
based on the options to exercise in μC/CPU. The file contains #define constants
that may need to be changed based on the desired use of μC/CPU. For example, to
assign a “name” to the CPU, the name can be queried and displayed. Also, to
name the CPU, one must specify the length of the ASCII string.
CPU_CORE.C
这是通用的文件,不需要被改变。然而,它必须被包含。
This
file is generic and does not need to be changed. However it must be included in
all builds.
CPU_CORE.C 中定义了 CPU_Init(),CPU_CntLeadZeros()以及测量CPU 最大关中断时间的函数等。
CPU_CORE.C
defines such functions as CPU_Init(), CPU_CntLeadZeros(), and code to measure
maximum CPU interrupt disable time.
必须在调用 OSInit()之前调用 CPU_Init()。
CPU_Init()
must be called before calling OSInit().
CPU_CntLeadZeros()被调度器用于查找最高优先级的就绪任务(。 CPU_CORE.C 用 C 模拟了计数清零指令。如果处理器中有该指令,那么就#define CPU_CFG_LEAD_ZEROS_ASM_PRESENT。CPU_CntLeadZeros()可以用汇编语言编写的函数代替(放在CPU_A.ASM 中)。使用这个函数之前需在 CPU.H 中定义CPU_CFG_DATA_SIZE 的值。
CPU_CntLeadZeros()
is used by the μC/OS-III scheduler to find the highest priority ready task (see
Chapter 7, “Scheduling” on page 133). CPU_CORE.C implements a count leading
zeros in C. However, if the processor used provides a built-in instruction to
count leading zeros, define CPU_CFG_LEAD_ZEROS_ASM_PRESENT, and replace this
function by an assembly language equivalent (in CPU_A.ASM). It is important to
properly declare CPU_CFG_DATA_SIZE in CPU.H for this function to work.
CPU_CORE.C 中也包含了能读取时间戳的函数。uC/CPU 的时间戳是 32 位的。然而,uC/CPU 可以返回 64 位的时间戳。用时间戳可以知道事件发生的时刻,可以测量代码的执行时间。使用时间戳的前提是 CPU 计数值可以被读取。读取时间戳的代码可以被放在 BSP 中。
CPU_CORE.C
also includes code that allows you to read timestamps. μC/CPU timestamps
(CPU_TS) are 32-bit values. However, μC/CPU can return a 64-bit timestamp since
μC/CPU keeps track of overflows of the low part of the 64-bit timestamp. Also
use timestamps to determine when events occur, or to measure the execution time
of code. Timestamp support requires a 16- or 32-bit free-running counter/timer
that can be read. The code to read this timer will be placed in the BSP (Board
Support Package) of the evaluation board or target board used.
CPU_CORE.H
这里包含了 CPU_CORE.C 中函数的原型。
This
header file is required by CPU_CORE.C to define function prototypes.
CPU_A.ASM
这个文件中包含了汇编代码,如关中断函数、开中断函数,计数清零函数等待。最低程度,这个函数需包括 CPU_SR_Save()和CPU_SR_Restore()。
This
file contains assembly language code to implement such functions as disabling
and enabling interrupts, a more efficient count leading zeros function, and
more. At a minimum, this file should implement CPU_SR_Save() and
CPU_SR_Restore().
CPU_SR_Save()在中断时保存当前 CPU 的状态寄存器到堆栈。然而,在返回时,必须关掉中断。CPU_SR_Save()被宏CPU_CRITICAL_ENTER()调用。
CPU_SR_Save()
reads the current value of the CPU status register where the current interrupt
disable flag resides and returns this value to the caller. However, before
returning, CPU_SR_Save() must disable all interrupts. CPU_SR_Save() is actually
called by the macro CPU_CRITICAL_ENTER().
CPU_SR_Restore()恢复中断前 CPU 的状态寄存器。CPU_SR_Restore()被宏 CPU_CRITICAL_EXIT()调用。
CPU_SR_Restore()
restores the CPU’s status register to a previously saved value.
CPU_SR_Restore() is called from the macro CPU_CRITICAL_EXIT().
表 18-2 显示了需被配置的 uC/OS-III 文件。
Table
18-2 shows the name of μC/OS-III files and where they are typically found.

Here,
<processor> is the name of the processor that the OS_CPU*.* files apply
to, and <compiler> is the name of the compiler that these files assume
because of the different assembly language directives that different tool
chains use.
OS_CPU.H 这个文件中必须定义宏 OS_TASK_SW(),这个宏被 OSSched()调用用于上下文切换。
This
file must define the macro OS_TASK_SW(), which is called by OSSched() to
perform a task-level context switch. The macro can translate directly to a call
to OSCtxSw(), trigger a software interrupt, or a TRAP. The choice depends on
the CPU architecture.
OS_CPU.H
OS_CPU.H 中必须定义宏 OS_TS_GET(),用于获得当前的时间戳。时间戳的数据类型为 CPU_TS,是 32 位的。
OS_CPU.H
must also define the macro OS_TS_GET() which obtains the current time stamp. It
is expected that the time stamp is type CPU_TS, which is typically declared as
at least a 32-bit value.
OS_CPU.H 中还需要定义 OSCtxSw()、OSIntCtxSw()、 OSStartHighRdy()等函数的原型。
OS_CPU.H
also defines function
prototypes for OSCtxSw(), OSIntCtxSw(),OSStartHighRdy()
and possibly other functions required by the port.
OS_CPU_A.ASM
这个文件中包含了以下汇编函数:
This
file contains the implementation of the following assembly language functions:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
和一个可选函数
and optionally,
OSTickISR()
OS_CPU_A.ASM 中存放的大多是直接操作 CPU 寄存器的函数,这些函数不能被 C 语言实现。
OSTickISR()
may optionally be placed in OS_CPU_A.ASM if it does not change from one product
to another. The functions in this file are implemented in assembly language
since they manipulate CPU registers, which is typically not possible from C.
The functions are described in Appendix A, “μC/OS-III API Reference Manual” on
page 375.
OS_CPU_C.C
这个文件中包含了钩子函数:
This
file contains the implementation of the following C functions:
OSIdleTaskHook()
OSInitHook()
OSStatTaskHook()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskReturnHook()
OSTaskStkInit()
OSTaskSwHook()
OSTimeTickHook()
详见附录 A。OS_CPU_C.C 中可以定义其他函数,但这些函数是强制的。
The
functions are described in Appendix A, “μC/OS-III API Reference Manual” on page
375. OS_CPU_C.C can declare other functions as needed by the port, however the
above functions are mandatory.
板级支持包的代码跟用户所使用的目标板有关。例如,BSP 中定义的函数用于开启关闭 LED、读按键值、初始化时钟等。
A
board support package refers to code associated with the actual evaluation board
or the target board used. For example, the BSP defines functions to turn LEDs
on or off, reads push-button switches, initializes peripheral clocks, etc.,
providing nearly any functionality to multiple products/projects.
BSP 的文件包括:
Names
of typical BSP files include:
BSP.C BSP.H
BSP_INT.C
BSP_INT.H
这些文件所在的目录为:
All
files are generally placed in a directory as follows:

Here,
<manufacturer> is the name of the evaluation board or target board
manufacturer, <board_name> is the name of the evaluation or target board
and <compiler> is the name of the compiler that these files assume,
although most should be portable to different compilers since the BSP is
typically written in C.
BSP.C 和 BSP.H
这两个文件中包含了函数的定义和申明如 BSP_Init()、BSP_LED_On()、BSP_LED_Off()、BSP_LED_Toggle()、BSP_PB_Rb() 等。用户可以定义自己的函数,最好以 BSP_作为前缀。
These
files normally contain functions and their definitions such as BSP_Init(),
BSP_LED_On(), BSP_LED_Off(), BSP_LED_Toggle(), BSP_PB_Rd(), and others. It is
up to the user to decide if the functions in this file start with the prefix
BSP_. In other words, use LED_On() and PB_Rd() if this is clearer. However, it
is a good practice to encapsulate this type of functionality in a BSP type file.
在 BSP.C 中,可以添加 CPU_TS_TmrInit()用于初始化 CPU 的时钟速率。
In
BSP.C, add CPU_TS_TmrInit() to initialize the μC/CPU timestamp feature. This
function must return the number 16 if using a 16-bit timer and 0 for a 32-bit
timer.
CPU_TS_TmrGet()用于读取 CPU 时钟计数值。如果定时器是 16 位的,那么就需定义两个字,将其转换为 32 位的。如果定时器是 32 位的,CPU_TS_TmrGet()将直接返回这个 32 位的值。
CPU_TS_TmrGet()
is responsible for reading the value of a 16- or 32-bit free-running timer. If
the timer is 16 bits, this function will need to return the value of the timer,
but shifted to the left 16 places so that it looks like a 32-bit timer. If the
timer is 32 bits, simply return the current value of the timer. Note that the
timer is assumed to be an up counter. If the timer counts down, the BSP code
will need to return the ones-complement of the timer value
(prior
to the shift).
BSP_INT.C 和 BSP_INT.H
这些文件中用于存放与中断控制器相关的函数。例如,可以设置特定的关中断、开中断,响应中断控制器,将所有中断的 ISR 指向同一地址等( 见第 9 章“中断管理”)。
These
files are typically used to declare interrupt controller related functions. For
example, code that enables or disables specific interrupts from the interrupt
controller, acknowledges the interrupt controller, and code to handle all
interrupts if the CPU vectors to a single location when an interrupt occurs
(see Chapter 9, “Interrupt Management” on page 157).
The pseudo code below shows an example of the latter.

(1) Here assume that the handler for the interrupt controller is called from the assembly language code that saves the CPU registers upon entering an ISR (see Chapter 9, “Interrupt Management” on page 157).
(2) The handler queries the interrupt controller to ask it for the address of the ISR that needs to be executed in response to the interrupt. Some interrupt controllers return an integer value that corresponds to the source. In this case, simply use this integer value as an index into a table (RAM or ROM) where those vectors are placed.
(3) The interrupt controller is asked to provide the highest priority interrupt pending. It is assumed here that the CPU may receive multiple simultaneous interrupts (or closely spaced interrupts), and that the interrupt will prioritize the interrupts received. The CPU will then service each interrupt in priority order instead of on a first-come basis. However, the scheme used greatly depends on the interrupt controller itself.
(4) Check to ensure that the interrupt controller did not return a NULL pointer.
(5) Simply call the ISR associated with the interrupt device.
(6) The interrupt controller generally needs to be acknowledged so that it knows that the interrupt presented is taken care of.
移植包括三个部分代码:CPU,OS,BSP。
A
port involves three aspects: CPU, OS and board specific (BSP) code.
移植需修改 3 个内核文件: OS_CPU.H , OS_CPU_A.ASM ,OS_CPU_C.C
μC/OS-III
port consists of writing or changing the contents of three kernel specific
files: OS_CPU.H, OS_CPU_A.ASM and OS_CPU_C.C.
移植需修改 3 个 CPU 文件:CPU.H,CPU_A.ASM,CPU_C.C
It
is necessary to write or change the content of three CPU specific files: CPU.H,
CPU_A.ASM and CPU_C.C.
最后创建适应于目标板的板级支持包 BSP。
Finally
create or change a Board Support Package (BSP) for the evaluation board or
target board being used.
uC/OS-III 的移植类似于 uC/OS-III 的移植
A
μC/OS-III port is similar to a μC/OS-II port, therefore start from one of the
many μC/OS-II ports already available (see Appendix C, “Migrating from μC/OS-II
to μC/OS-III” on page 599).