双语版

 


 

 

序言.... 7

1、简介.... 9

1-1 前后台系统.... 10

1-2 实时内核.... 11

1-3 实时系统(RTOS.... 14

1-4 uC/OS-III 14

1-5 uC/OS,II, III 的性能对比.... 21

1-6这本书的编排方式.... 24

1-7探针 uC/Probe. 25

1-8规定.... 26

1-9章节目录.... 28

2、目录和文件.... 33

2-1 应用代码.... 35

2-2 CPU.. 37

2-3 板级支持包(BSP.... 37

2-4 uC/OS-III 中独立于 CPU 的源文件.... 38

2-5 uC/OS-III 中 CPU 相关的源代码.... 41

2-6 uC/CPU,CPU 相关代码.... 42

2-7 uC/LIB 可移植的函数库.... 44

2-8 概要.... 45

3、开始学习 uC/OS-III 48

3-1 单任务应用.... 48

3-2 内核对象与多任务应用.... 57

4、临界段.... 66

4-1 关中断.... 67

4-2 锁住调度器.... 69

4-3 uC/OS-III 与长临界段.... 71

4-4 总结.... 72

5、任务管理.... 74

5-1 设置任务优先级.... 84

5-2 堆栈空间大小的确定.... 86

5-3 检测任务堆栈的溢出使用 MMU 或 MPU.. 87

5-4 任务管理服务.... 92

5-5 内部任务管理.... 93

5-6 内部任务.... 113

5-7 总结.... 131

6、就绪列表.... 132

6-1 优先级.... 132

6-2 就绪列表.... 136

6-3 添加任务到就绪队列.... 140

6-4 总结.... 142

7、调度.... 143

7-1 抢占式调度.... 143

7-2 调度点.... 146

7-3 循环轮转调度.... 149

7-4 调度的内部实现.... 151

7-5 总结.... 157

8、上下文切换.... 158

8-1 OSCtxSw() 161

8-2 OSIntCtxSw() 164

8-3 总结.... 166

9、中断管理.... 168

9-1 CPU 的中断处理.... 169

9-2 典型的中断服务程序.... 170

9-3 短中断服务程序(ISR.... 174

9-4  ALL INTERRUPTS VECTOR TO A COMMON LOCATION.. 175

9-5 每个中断向量指向不同的地址.... 177

9-6 直接提交和延迟提交.... 177

9-7 直接提交 VS 延迟提交.... 183

9-8 系统时基.... 186

9-9 总结.... 188

10、挂起队列.... 189

10-1 总结.... 195

11、时间管理.... 196

11-1 OSTimeDly() 197

11-2 OSTimeDlyHMSM() 202

11-3 OSTimeDlyResume() 204

11-4 OSTimeSet()和 OSTimeGet() 205

11-5 OSTimeTick() 206

11-6 总结.... 206

12、软件定时器管理.... 207

12-1 一次性定时模式.... 209

12-2 无初始定时周期模式.... 210

12-3 有初始定时周期模式.... 211

12-4 内部定时器管理.... 212

12-5 总结.... 224

13、资源管理.... 225

13-1 关中断.... 228

13-2 锁调度器.... 230

13-3 信号量.... 231

13-4 互斥信号量 mutex. 253

13-5 用信号量代替 mutex. 265

13-6 死锁.... 265

13-7 总结.... 270

14、同步.... 272

14-1 信号量.... 272

14-2 任务内建信号量.... 281

14-3 事件标志组.... 287

14-4 多任务同步.... 301

14-5 总结.... 303

15、消息传递.... 304

15-1 消息.... 305

15-2 消息队列.... 305

15-3 任务的消息队列.... 307

15-4 双向通信.... 308

15-5 流量控制.... 310

15-6 保持数据在作用域中.... 312

15-7 使用消息队列.... 314

15-8 客户端和服务器端.... 323

15-9 消息队列的组成.... 324

15-10 总结.... 328

16、挂起多个对象.... 330

16-1 总结.... 339

17、内存管理.... 341

17-1 创建一个内存分区.... 343

17-2 获得内存分区中的内存块.... 347

17-3 归还内存块给内存分区.... 348

17-4 使用内存分区.... 349

17-5 总结.... 353

18、移植 uC/OS-III 354

18-1 uC/CPU.. 357

18-2 uC/OS-III 移植.... 361

18-3 板级支持包 BSP. 363

18-4 总结.... 366

 


 

序言

什么是 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.


 

1、简介

在重要的地方,实时系统凭借其系统性的计算和及时的处理能力工作着。一共有 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.

 

1-1 前后台系统

简单的小型系统设计一般是基于前后台的或者无限循环的系统。包含一个无限循环的模块实现需要的操作(后台)。中断处理程序实现异步事件(前台)。前台也叫做中断级,后台也叫作任务级。

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.

1-2 实时内核

实时内核是一个能管理 MPUMCUDSP 时间和资源的软件。

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-21) 一个低优先级的任务正在被执行

A low-priority task is executing.

F1-22)发生一个中断,CPU 转向 负责服务中断设备的ISR

An interrupt occurs, and the CPU vectors to the ISR responsible for servicing the interrupting device.

F1-23ISR 响应中断请求设备,但是 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-24)当 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-25)高优先级任务执行必要的处理答复中断请求设备。

The higher-priority task executes and performs the necessary processing in response to the interrupt device.

F1-26)当高优先级任务完成时,返回原任务中断前的代码。

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-27)原任务在它被中断的地方开始执行。

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.

最后,为了更好地使用 CPUuC/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.

 

1-3 实时系统(RTOS

一个实时系统通常包括一个实时内核以及其他高级的服务,例如:文件管理,堆栈协议,图形用户接口等等。大多数服务都是跟 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/FSuC/TCP-IP uC/GUIuC/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.

1-4 uC/OS-III

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-5 uC/OS,II, III 的性能对比

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

申请中

 

 

1-6这本书的编排方式

这本书中可分为 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.

 

1-7探针 uC/Probe

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-TagRS-232CUSB 或者以太网接口等。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.

1-8规定

首先,如果有图解的话先看图解,或者小括号中的解释。图片中都会标注“F”以及图的编号。例如,F3-42)说明相关介绍在图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()。其中???是服务类型:SemTaskSem,FlagMutexQTaskQ。类似的,一个任务等待信号量或消息的时候需调用 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-9章节目录

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.

章节 19uC/OS-III RAM 使用量,最大关中断时间,最大调度器锁存时间等。附录 AuC/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.


 

2、目录和文件

 

这个章节将会介绍 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 的架构以及与硬件的关系。包括硬件定时器和中断控制器。也应该包括 UARTsADCs,以太网等。

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 是以源代码形式提供的,所以它也可以被用于 UnixLinux,或者其它的开发平台。

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-11)应用代码包括与工程、产品相关文件。为了方便,这些被简单地叫做 APP.C APP.HMain()函数应该在 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-12)半导体厂家通常会提供库函数以控制那些 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-13)板级支持包通常被用来初始化目标板。例如打开或关闭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-14)这些是 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-15)这些 uC/OS-III 代码用于适应不同架构的 CPU,在名为 port 的文件夹中。uC/OS-III 源于 uC/OS-IIuC/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-16)在 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-17uC/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-18uC/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.

 

2-1 应用代码

如果 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.

 

2-2 CPU

在这个目录中,你会找到半导体厂商提供的外设库文件。

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.

2-3 板级支持包(BSP

板级支持包通常是目标器件的特殊配置。实时上,写得好的话,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

\*.*

2-4 uC/OS-III 中独立于 CPU 的源文件

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.

2-5 uC/OS-III CPU 相关的源代码

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.

 

2-6 uC/CPU,CPU 相关代码

μ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.

2-7 uC/LIB 可移植的函数库

μ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.

 

2-8 概要

下面的概要是基于 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.

 


 

3、开始学习 uC/OS-III

 

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 单任务应用

列表 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-12)定义任务控制块(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-13)每个任务需要创建自己的堆栈。堆栈的数据类型必须是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-14)这是一个函数原型。

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-21main()开始时调用一个 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-22)调用 OSInit(),用于初始化 uC/OS-IIIOSInit()初始化内部变量和数据结构,同时产生 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-26OSTaskCreate()的第四个参数是一个实参,第一次被调用时 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-27OSTaskCreate()的第五个参数是任务的优先级。优先级确立了任务间的重要性关系。参数值越小优先级越高。可以设置优先级数值为 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-28)是任务堆栈的基地址。基地址通常是分配给该任务的堆栈的最低内存位置。

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-29)第七个参数是地址“水印”,当堆栈生长到指定位置时就不再允许其生长。详见章节 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-210OSTaskCreate()的第八个参数定义了任务的堆栈大小( 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-211)接下来的三个参数将被跳过因为这三个参数跟当前的话题无关,直接设置为 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-212OSTaskCreate()的最后一个参数是一个指针,将接收根据函数执行结果所返回的错误代号。如果 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-213)调用 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-32BSP_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-33CPU_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-34BSP_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-35BSP_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-36)所有的 uC/OS-III 任务需要被设置为无限循环。

 

L3-37BSP_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-38)最后,每个任务可以调用 uC/OS-III 中的函数,可以让任务待一个事件(信号量,或来自于中断的消息,或来自于其它任务的消息。)而被挂起。任务可以设置等待期限(通过调用 OSTimeDly() 或者 OSTimeDlyHMSM()),章节 11“时间管理”介绍了等待期限的相关信息。

 

3-2 内核对象与多任务应用

列表 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-41) 分别为每个任务分配一个 OS_TCB

L3-4(1)    Allocate storage for the OS_TCBs of each task.

L3-42)互斥信号量(mutex)是一个内核对象(一个结构体),用于保护共享资源。任务要访问共享资源就必须先获得 mutexmutex 的拥有者使用完这个资源后就必须释放这个 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-43)消息队列是一个内核对象,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-45)用户需要申明这些任务。

L3-4(5)    The user must prototype the tasks.

列表 3-5 展示了 C 语言的入口——main()函数。

Listing 3-5 shows the entry point for C, main().

L3-51)调用 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-52)调用 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-53)第一个应用任务被创建。

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-61)通过调用 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-62)任务#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-71)该任务在执行前先等待一个时基。如果 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-72) 该任务向消息队列 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-74)当 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-75)如果任务完成对共享资源的使用,它必须调用 OSMutexPost() 释放这个 mutex

L3-7(5)    When the task is done with the shared resource, it must call OSMutexPost() to release the mutex.

L3-81)任务#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-82)测得消息是什么时候被发送的,用户就能测得任务接收这个消息所用的时间。读取现在的时间戳并减去消息被发送时的时间戳。请注意,消息被发送时,等待消息的任务可能不会立即接收到消息,因为 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-83)处理接收到的消息。

L3-8(3)    Proceed with processing the received message.


 

4、临界段

 

临界段代码,也称作临界域,是一段不可分割的代码。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.

 

4-1 关中断

设置 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.

 

4-2 锁住调度器

当设置 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-3 uC/OS-III 与长临界段

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.

 

4-4 总结

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.


 

5、任务管理

 

实时应用中一般将工作拆分为多个任务,每个任务都需要是可靠的。使用 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_CLRuC/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-13uC/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-14)堆栈指针 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-15TCB 中其它的字段会被赋值:任务优先级、任务名、任务状态、内部消息队列、内部信号量等。

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-21)任务最重要的部分是它的代码。正如前面提到的,任务的代码看起来像是个 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-22)每个任务都需要被设定一个优先级。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-23)每个任务对 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-24)因为 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-25)任务也可以访问全局变量。然而,因为 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-26)任务也可以访问一个或多个 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.

 

5-1 设置任务优先级

有些时候任务的优先级是显而易见的。例如,嵌入式系统中的重要的应用应该被设置为高优先级,一些显示操作就应该被设置为低优先级。然而,由于实时系统的复杂性,在大多数情况下任务的优先级是不能被事先确定的。多数系统中,不是所有的任务都是重要的,不重要的任务应该被设置为低优先级。

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

5-2 堆栈空间大小的确定

堆栈的大小取决于该任务的需求。设定堆栈大小时,你就需要考虑:所有可能被堆栈调用的函数及其函数的嵌套层数,相关局部变量的大小,中断服务程序所需要的空间。另外,堆栈还需存入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.52.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.

 

5-3 检测任务堆栈的溢出使用 MMU MPU

1) Using an MMU or MPU

如果处理器有MMU或者MPU,检测堆栈是否溢出是非常简单的。

MMUMPUCPU边上的特殊硬件设施,可以检测非法访问,如果任务企图访问未被允许的内存空间的话,就会产生警告。但设置MMUMPU不是本书所要介绍的。

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-5uC/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).

 

5-4 任务管理服务

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 内部任务管理

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-61)处于休眠状态的任务驻留于内存但未被 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-62)就绪状态的任务根据优先级有序地排列于就绪列表中。就绪列表中对就绪任务的个数没有限制。

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-63)正在运行的任务被置为运行状态。在单 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-64)挂起状态的任务被放置在挂起列表中以表明任务在等待某些事件的发生。等待的时候,任务是不会占用 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-65)若中断发生,中断会挂起正在执行的任务并去处理 ISRISR 中可能有某些任务等待的事件。一般来说,中断用来通知任务某些事件的发生,并让在任务级处理实际的响应操作。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-71)状态 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-72)任务可以通过调用 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-73)任务可以通过调用挂起函数(OSFlagPend()OSMutexPend() OSQPendOSSemPendOSTaskQPend()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-74)如前面所说,任务可以等待事件发生。但任务也可以被设置等待多少时间。如果在这段时间内事件没有发生,任务也会被设为就绪状态,并通知这个任务是等待超时而被挂起的。{挂起函数都有一个关于函数执行结果错误代号,可以查看这个代号知道任务是因何被就绪的}

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-76)一个延时中的任务也可以被其它任务设置为停止。在这种情况下,效果会被叠加。换句话说,延时需被执行、停止状态需被解除。该任务才会被执行。

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-77)一个挂起状态中的任务也可能被其它任务设置为停止。同样的,效果会被叠加。事件发生且停止状态被移除后,任务才会被执行。

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-78)任务可以等待事件的发生,但可以给它设定一个期限。同样的,它也可能被设为停止,效果是叠加的。除非移除停止状态并事件发生或等待事件超时,任务才会被执行。

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 用于维护任务的一个结构体。每个任务都必须有自己的 TCBuC/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.

 

5-6 内部任务

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-41)空闲任务是一个无限循环的不会等待任何事件的任务。这是因为,在大部分的处理器中,当没有事情可做时,处理器依然会执行指令。当 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-42)空闲任务运行时,两个计数变量会递增。

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-43)空闲任务的每次循环,都会调用 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-81)使用硬件定时器并被设置为以 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-82)假定 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-83OSTimeTick()用于标记时基任务并就绪时基任务。定时器中断后基任务可能不被立即执行,因为中断程序打断的可能是一个比时基任务更高优先级的任务,完成时基 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-84)当时基任务执行时,它会遍历队列中所有等待期满的任务、等待事件超时的任务。按照这个观点,这个会被叫做时基列表。时基任务会就绪时基列表中的那些期满、超时的任务。

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-91)时基列表中包含了一个表(OSCfg_TickWheel[])和一个计数器(OSTickCtr)

F5-9(1)    The tick list consists of a table (OSCfg_TickWheel[]) and a counter (OSTickCtr).

F5-92)这个表多达 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-93)表中的每个记录包含 3 个变量:.NbrEntriesMaxNbrEntries 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-51CPU 进入 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-52main()函数调用 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-53)创建一个叫做 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-54)调用 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-55)这个任务应该先设置和开启时基中断,初始化用于时基时钟的硬件定时器,设置其中断的速率。(编译时设置 OS_CFG_APP.H 中的OS_CFG_STAT_TASK_RATE)。另外,Micrium 提供的例子工程中包含了基本的板级支持包 BSPBSP 初始化了 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-56)调用 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,000CPU 的利用率如下计算:

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-57)然后在 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_TCBStkFree 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.

 

5-7 总结

任务是一段简单的程序。在单 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.


 

6、就绪列表

 

准备运行的任务被放置于就绪列表中。就绪列表包括 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-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-11OS_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-12) 当这个表中全是 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-13)当找到第一个非 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 位数的个数是 00x00F01234 返回的是 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-2 就绪列表

准备好运行的任务被放到就绪列表中,如图 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-41)该图显示的是就绪列表为空时的状态。

F6-4(1)    There is only one entry in OSRdyList[OS_CFG_PRIO_MAX-1], the idle task.

F6-42)有多少种优先级,就绪列表中就由多少条记录。每个记录中都有 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-43)优先级 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-51)如图所示,时基任务和另两个任务有它们自己的优先级。 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-52)当某个优先级中只有一个任务的时候,它的头指针和尾指针会指向同一个 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.

 

6-3 添加任务到就绪队列

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-61)在调用 OSTaskCreate()之前,已经有两个任务在就绪列表中的该优先级中了。

F6-6(1)    Before calling OSTaskCreate() (in this example), two tasks were in the ready list at priority “prio”.

F6-62)一个新的 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-63OSTaskCreate()调用 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.

 

6-4 总结

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.


 

7、调度

 

调度器,决定了任务的运行顺序。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.

 

7-1 抢占式调度

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-11)一个低优先级任务正在执行,这时中断发生了。

F7-1(1)    A low priority task is executing, and an interrupt occurs.

F7-12)如果中断使能,指令指针 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-13)中断服务程序处理相关的程序,发送信号量或消息给一个高优先级任务。高优先级任务被就绪。

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-14中断服务程序完成操作后,它将 CPU 的控制权还给 uC/OS-III

F7-14When the ISR completes its work it makes a service call to μC/OS-III.

F7-15uC/OS-III 执行相应的操作。

 

F7-16)因为就绪队列中有一个更重要的任务,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-17)开始执行高优先级任务

 

F7-18)高优先级任务处理完成后,将 CPU 的控制权交给 uC/OS-III

F7-18The 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-19uC/OS-III 执行相应的操作。

 

F7-110uC/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-111)低优先级任务从被中断处继续执行。

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-21)中断服务程序中,不是直接发送信号量或消息给任务。而是先将信号量或消息放人外部消息队列,并就绪中断处理任务。

F7-21The 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 3When the ISR completes its work, it makes a service call to μC/OS-III.

F7-23uC/OS-III 处理一些操作。

 

F7-24)因为中断处理任务被就绪,uC/OS-III CPU 的使用权交给它。

F7-24Since the ISR made the ISR Handler Task ready to run, μC/OS-III switches to that task.

F7-25uC/OS-III 处理一些操作。

 

F7-26) 中断处理任务将外部消息队列中的消息移除并重新提交给相应的任务。这样,就将这个过程消息提交从中断级变成了任务级。使用这种方法的目的是为了减小关中断时间(详见第 9 章)。当消息队列为空时,uC/OS-III 将中断处理任务从就绪队列中移除,然后执行就绪队列中的最高优先级任务。

F7-26) 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.

 

7-2 调度点

当出现以下情况时会发生调度:

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.

 

7-3 循环轮转调度

当多个任务有相同的优先级时,uC/OS-III 允许每个任务运行规定的时间片。当任务没有用完分配给它的时间片时,它可以自愿地放弃CPUuC/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-31)任务 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-32)任务 3 主动放弃剩下的时间片。

F7-3(2)    On the 4th tick interrupt, the time quanta for Task #3 expire.

F7-33uC/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-34)任务 1 被执行直到分配给它的时间片到期。

F7-3(4)    Task #1 executes until its time quanta expires (i.e., after four ticks).

F7-35)任务 3 正在被运行。在这段时间内,时基中断发生,但任务 3 还没有到期。

 

F7-36)任务 3 主动放弃剩下的时间片。

 

F7-37)有趣的事情发生了,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-38)任务 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().

 

7-4 调度的内部实现

通过这两个函数完成调度功能: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-11)因为要进入调度程序,关中断。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-12)第二步是确保调度器没有被锁。如果调度器被锁,就不能运行调度器,函数马上返回。

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-13OSSched()通过扫描位映像表 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-14)确定好优先级后,索引到 OSRdyList[]并提取该记录中的首个 TCBOSRdyList[highest priority].HeadPtr)。到现在为止,已经知道了哪个任务将要切换为运行状态。特别的,OSTCBCurPtr 指向的是当前任务的 TCBOSTCBHighRdyPtr 指向的是将要被切换的 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-15)当就绪表中的最高优先级任务不为当前正在运行的任务时(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-21OSIntExit()开始时确保中断嵌套级不为 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-22OSInrExit() 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-23OSIntExit()检查调度器是否被锁。如果被锁,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-24)当这是第一级中断且调度器没有被锁时。查找优先级最高的任务。

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-25)从 OSRdyList[]中获得最高优先级的 TCB

L7-2(5)    Again, we extract the highest priority OS_TCB from OSRdyList[].

L7-26)如果最高优先级就绪任务不是当前正在运行的任务。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-31OS_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-32)时间片计数值,驻留于正在运行任务的 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-33)当时间片计数值为 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-34)当调度器被锁时,OS_SchedRoundRobin()返回。

L7-3(4)    OS_SchedRoundRobin() also returns if the scheduler is locked.

L7-35)若当前任务的时间片到期,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-36)在该优先级记录队列首部的任务的时间片值被载入到任务。每个任务都可以有自己的时间片值。(任务创建时被设置或通过OSTaskTimeQuantaSet()设置)。如果该值为 0uC/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.

 

7-5 总结

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.


 

8、上下文切换

 

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.HOS_CPU_C.COS_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 是指向任务的堆栈的指针 TSPR14"是指向 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.

 

8-1 OSCtxSw()

当有一个高优先级就绪任务需要被执行,任务级调度器会调用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-31OSTCBCurPtr 指向了当前正在运行任务的 OS_TCB。然后

OSSchedule()被调用。

F8-3(1)    OSTCBCurPtr points to the OS_TCB of the task that is currently running and that called OSSched().

F8-32)通过 OSTCBHighRdyPtrOSSched()找到将要被运行任务的OS_TCB

F8-3(2)    OSSched() finds the new task to run by having OSTCBHighRdyPtr point to its OS_TCB.

F8-33OSTCBHighRdyPtr->StkPtr 指向将要被运行任务堆栈的顶部。{假定堆栈是从内存高地址向地地址生长的}

F8-3(3)    OSTCBHighRdyPtr->StkPtr points to the top of stack of the new task to run.

F8-34)执行任务的上下文切换时,所有的 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-35)调用 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-41OSCtxSw()开始执行,保存状态寄存器和程序指针寄存器到当前的任务堆栈。保存的顺序与中断发生时 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-42)当任务被停止时,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-43)然后 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-44)最后,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.

 

8-2 OSIntCtxSw()

ISR 中就绪了高优先级任务 BISR 返回时将不会回到中断前的任务 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 1OSIntCtxSw()将新任务 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-62)然后 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.

 

8-3 总结

上下文切换包括两部分内容,保存旧任务的内容,载入新任务的内容。任务级切换时,通过调用 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.

9、中断管理

 

中断是硬件机制,用于通知 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.

 

9-1 CPU 的中断处理

目前市场上有很多种架构的 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.

 

9-2 典型的中断服务程序

进入中断时 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-11ISR 通常是用汇编写的,也可以用 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-22)在进入临界段之前,关中断。有些处理器进入中断服务程序时会自动关中断,有些则需要用户手动关中断。

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-33)中断服务程序首先要做的就是保存当前任务的上文到任务堆栈。有些 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-14)下一步,调用 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-15)如果这是嵌套的第一层,将中断前的任务的 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-16)可以在此清除中断标志。

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-17)如果用户相应支持嵌套中断,重新开启中断。这个步骤是可选的。

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-18)此时,可以调用用户函数。然而,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-19ISR 的工作完成后,用户必须调用 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-110)如果 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-111ISR 返回,恢复原任务的状态。

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.

 

9-3 短中断服务程序(ISR

上述顺序的代码是假定 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-21)正如上面提到的,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-22)在这里,保存 CPU 寄存器。

L9-2(2)    Here, save sufficient registers as required to handle the ISR.

L9-23)清除中断标志位,防止 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-24)在这里不要重新开启中断。

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-25)响应该中断需要的操作,调用用户程序。

L9-2(5)    Now take care of the interrupting device in assembly language or call a C function, if necessary.

L9-26)结束之后,重新恢复原任务的 CPU 寄存器。

L9-2(6)    Once finished, simply restore the saved CPU registers.

L9-27)返回到原任务。

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.

 

9-4  ALL INTERRUPTS VECTOR TO A COMMON LOCATION

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.

9-5 每个中断向量指向不同的地址

中断向量控制器中的中断向量都指向对应的 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.

 

9-6 直接提交和延迟提交

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-21)中断产生

F9-2(1)    A device generates an interrupt.

F9-22)中断服务程序开始执行。通常中断中包含着任务等待的事件。

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-23)若 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-24)若 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-25)在直接提交方法中,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-31)中断产生

F9-3(1)    A device generates an interrupt.

F9-32ISR 被执行。

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-33ISR 中调用"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-34ISR 的最后,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-35)当中断处理任务清空中断队列时,它就会将自己停止,重

新开启调度器,调用调度器选择下一个任务运行。

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-36)如果优先级更高的任务被使能,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.

 

9-7 直接提交 VS 延迟提交

在直接提交方式中,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 当使用直接提交方式时避免使用这些功能

 

9-8 系统时基

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-41)时基中断程序首先调用一个 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-42)如果 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-43)如果 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-44)使用循环轮转算法检测是否当前任务是否期满。

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-45)标记定时器任务,定时器任务也用时基任务的时基作为基准。

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.

 

9-9 总结

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.


 

10、挂起队列

 

当任务等待信号量、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_SEMOS_MUTEX

章节 14“同步”

信号量、事件标志组

OS_SEMOS_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_SEMOS_MUTEXOS_FLAG_GRPOS_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-42.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-43.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-44)这两个任务的结构体 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-45)每个 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-46)每个任务指针指回 OS_PEND_DATA 结构体。

F10-4(6)  Each task points back to the OS_PEND_DATA structure.

F10-47)最后,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 挂起队列相关函数

 

10-1 总结

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.


 

11、时间管理

 

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 总结

详见附录 AuC/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.

 

11-1 OSTimeDly()

任务调用这个函数后就会被挂起直到期满。这个函数可以有三种模式:相对延时模式,周期性延时模式,绝对定时模式。

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-11)第一个参数是任务的延时时基数。如果时基速率被设置为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-12)参数为 OS_OPT_TIME_DLY 表明用户选择的相对延时模式。

L11-1(2)  Specifying OS_OPT_TIME_DLY indicates that the user wants to use “relative” mode.

L11-13)如大多数 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-14)当返回不为 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-11)时基中断产生,进入时基 ISR

F11-1(1)   We get a tick interrupt and μC/OS-III services the ISR.

F11-12ISR 结束后,切换到高优先级任务。这段执行时间是不可预测的。

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-13)当高优先级任务执行完毕后,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-14)任务调用 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-15)下一个时基中断产生。当有高优先级的任务等待此次时基中断时,高优先级任务会被执行。

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-16)执行高优先级任务。

F11-1(6)   The HPTs execute.

F11-17)再下一个时基中断产生。当有高优先级的任务等待此次时基中断时,高优先级任务会被执行。

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-18)因为此刻没有高优先级的中断就绪,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-19)时基中断后,因为高优先级任务需要被执行,所以该任务的延时时间不会是精确的 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-21)第一个参数设置了任务执行的周期,如上被设置为 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-22)第二个参数 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-23)检查 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 为上述函数的第一个参数

11-2 OSTimeDlyHMSM()

任务可以调用这个函数为任务设置延时,这个函数更“友好”于用户。特别的,可以设置为小时,分钟,秒,毫秒(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-31)这四个参数设置了延时的时间(分别对应为时、分、秒、毫秒)。在这个例子中,设置了延时 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-32)设置 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-33)如大多数 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.

 

11-3 OSTimeDlyResume()

任务可以调用 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.

11-4 OSTimeSet() OSTimeGet()

每个时基中断发生时 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.

 

11-5 OSTimeTick()

当时基中断发生时,时基 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.

 

11-6 总结

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().


 

12、软件定时器管理

 

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).

 

12-1 一次性定时模式

正如其名字所表达,定时器会递减被设置初始的定时值,当该值为 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-2 无初始定时周期模式

如图 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-3 有初始定时周期模式

如图 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 内部定时器管理

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-51"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-52)当创建了定时器或调用了 OSTmrStop(),定时器会处于停止模式。

F12-5(2)  When creating a timer or calling OSTmrStop(), the timer is placed in the “stopped” state.

F12-53)当调用 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-54)一次性定时模式的定时器延时期满后处于完成状态"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-11)在 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-12)结构体开始于一个"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-13)每个内核对象都可以被命名,以利于调式器或 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-14.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-15)当回调函数需要接受一个参数时(.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-16.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-17) 当定时器管理器中的变量 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-18.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-19.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-110.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-111.Opt 域包含了传递给 OSTmrCreate()的参数(可选)。

L12-1(11) The .Opt field contains options as passed to OSTmrCreate().

L12-112.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-71)中断产生

F12-7(1)  The tick ISR occurs and assumes interrupts are enabled and executes.

F12-72)时基 ISR 标记时基任务。

F12-7(2)  The tick ISR signals the tick task that it is time for it to update timers.

F12-73)然而,有高优先级任务需要被执行。因此,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-74)当所有的高优先级任务被执行完毕,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-75)第一个定时器的回调函数被执行。

F12-7(5)  The callback for the first timer is executed.

F12-76)第二个定时器的回调函数被执行。

F12-7(6)  The callback for the second expired timer is executed.

F12-77) 第三个定时器的回调函数被执行。

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-81)定时器列表包含两部分:表(OSCfg_TmrWheel[])和定时器计数变量(OSTmrTickCtr)

F12-8(1)  The timer list consists of a table (OSCfg_TmrWheel[]) and a counter (OSTmrTickCtr).

F12-82)表中包含了 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.

 

12-5 总结

当定时器的计数值递减为 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.


 

13、资源管理

 

这个章节会介绍共享资源相关的服务。共享资源可以是:变量(静态的或全局的)、结构体、内存空间、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-IIIs 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-IIIs interrupt disable time, but shorter than μC/OS-IIIs 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-IIIs 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-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-31)当使用开/关中断宏时 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-32CPU_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-33)访问临界段时不会被中断或任务打断,因为中断被关闭。

换句话说,临界段的操作是原子性的。

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-34CPU_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.

 

13-2 锁调度器

如果任务不需要和 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.

 

13-3 信号量

将信号量用于同步的概念是荷兰的电脑科学家 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-51)定义一个信号量,信号量的数据类型必须是 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-52)通过调用 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-53)为信号量分配一个名字,调试时就很容易识别。名字可以存储于 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-54)设置信号量计数值。当共享资源只能有被一个任务占有时设置信号量计数值为 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-55)根据信号量的创建结果 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-66)通过调用 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-67)这个参数是等待信号量的期限值,以时基为最小单位。事实上,等待期限决定于时基频率。当时基频率为 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-69)信号量被提交时, 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-610)根据信号量的创建结果 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-611)当 OSSemPend()正确返回时,任务就可以访问这个共享资源。

L13-6(11) The resource can be accessed when OSSemPend() returns, if there are no errors.

L13-612)资源访问结束后,任务调用 OSSemPost()释放这个信号量。

L13-6(12) When finished accessing the resource, simply call OSSemPost() and specify the semaphore to be released.

L13-613OS_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-714)如大多数 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-715)其它要访问共享资源的任务也要经过同样的步骤。

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-101)在 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-102)这个结构体的第一个变量是“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-103)每个内核对象都可以被赋予一个名字,名字有 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-104)若有多个任务等待信号量,信号量就会将这些任务放入其挂起队列中。(见章节 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-105)信号量中包含一个信号量计数变值。信号量计数值可以定义为 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-106)信号量中包含了一个时间戳变量,存储了上一次信号量被提交时的时间戳。当信号量被提交时,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-111)调用 OSSemPend(),该函数它首先检测传递给它的参数是否有效。

如果信号量计数值大于 0OS_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.其它任务取消了该任务的挂起

2The pend was aborted by another task

3.等待超时

3The 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.

L13112)检测 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-113)任务对共享资源访问完毕后,必须调用 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-41)任务 HHigh 优先级)和任务 MMedium 优先级)都在等待事件发生,任务 LLow 优先级)正在被运行。

F13-4(1)  Task H and Task M are both waiting for an event to occur and Task L is executing.

F13-42)这时,任务 L 想申请信号量。

F13-4(2)  At some point, Task L acquires a semaphore, which it needs before it can access a shared resource.

F13-43)任务 L 访问共享资源。

F13-4(3)  Task L performs operations on the acquired resource.

F13-44)任务 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-45)开始执行任务 H

F13-4(5)  Task H performs computations based on the event it just received.

F13-46)现在任务 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-47)任务 L 被恢复并继续执行。

F13-4(7)  Task L is resumed and continues to access the shared resource.

F13-48)任务 L 被任务 M 抢占因为任务 M 所等待的事件发生。

F13-4(8)  Task L is preempted by Task M since the event that Task M was waiting for occurred.

F13-49)任务 M 开始执行。

F13-4(9)  Task M handles the event.

F13-410)任务 M 执行完毕,uC/OS-III CPU 控制权交还给任务L

F13-4(10) When Task M completes, the kernel relinquishes the CPU back to Task L.

F13-411)任务 L 被执行。

F13-4(11) Task L continues accessing the resource.

F13-412)任务 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-413)任务 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.

 

13-4 互斥信号量 mutex

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-51)任务 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-52)此时,任务 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-53)任务 L 被执行。

F13-5(3)  Task L performs operations on the acquired resource.

F13-54)任务 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-55)任务 H 开始执行。

F13-5(5)  Task H performs computations based on the event it just received.

F13-56)任务 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-57)任务 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-58)任务 L 对共享资源的访问执行完毕并释放 mutexuC/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-59)任务 H 开始执行。

F13-5(9)  Task H now has the mutex and can access the shared resource.

F13-510)任务 H 对共享资源的访问完毕,并释放这个 mutex

F13-5(10) Task H is finished accessing the shared resource, and frees up the mutex.

F13-511)任务 H 继续执行。

F13-5(11) There are no higher-priority tasks to execute, therefore Task H continues execution.

F13-512)任务 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-513) 任务 M 被执行。

F13-5(13) Task M executes.

现在没有优先级反转的问题了。当然,任务 L 占用共享资源的时间越短越好。

mutex 是一个内核对象,它被数据类型 OS_MUTEX 所定义,(OS_MUTEX 的原型是 os_mutex,见 OS.H)。应用中可以有任意个 mutex(仅限于处理器的 RAM)。只有任务才可以使用 mutexISR 不可以使用 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-121)任务请求并获得 mutexOSMutexPend()设置嵌套计数值为 1

L13-12(1) A task starts by pending on a mutex to access shared resources. OSMutexPend() sets a nesting counter to 1.

L13-122)检测返回的错误代号,如果没有错误,任务 Mytask()占用共享资源 MySharedResource

L13-12(2) Check the error return value. If no errors exist, MyTask() owns MySharedResource.

L13-123)函数 MyLibFunction(),其中将访问共享资源。

L13-12(3) A function is called that will perform additional work.

L13-124)函数 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-125MyLibFunction()访问共享资源。

L13-12(5) MyLibFunction() can access the shared resource.

L13-126mutex 被释放,嵌套计数值减为 1OSMutexPost()返回, 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-127mutex 再次被释放,此时,嵌套计数值减为 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-131)在 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-132)结构体开始于"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-133)每个内核对象都被分配一个名字。

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-134)因为可能有多个任务等待这个 mutexmutex 中包含了一个挂起队列用于存放等待该 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-135)如果任务占用这个 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-136)如果任务占用这个 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-137uC/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-138mutex 中的变量 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-141)定义一个 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-142)调用 OSMutexCreate()创建一个 mutex,传递变量地址到该函数的第一个参数。

L13-14(2) Create a mutex by calling OSMutexCreate() and pass the address to the mutex allocated in (1).

L13-143)分配一个名字给 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-144OSMutexCreate()基于创建的结果返回一个错误代号。如果函数中所有的参数都是有效的,那么错误代号将是 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-151)调用 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.

如果该任务已经占用这个 mutexOSMutexPend()只是简单地将嵌套计数值递增。并返回错误代号 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-152)如果 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-153)当任务完成对共享资源的访问后,就必须调用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()递减嵌套计数值,如果该值依旧非 0OSMutexPost() 返回。在这种情况下,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 被完成释放后,如果没有任务等待这个 mutexOSMutexPost()设置 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.

若有任务等待这个 mutexOSMutexPost()运行后,挂起队列中优先级最高的任务占用这个 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.

 

13-5 用信号量代替 mutex

然而,如果该等待有期限,就必须优先考虑使用 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.

 

13-6 死锁

死锁,就是两个任务互相等待对方所占用的资源的情况。

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-161)假定任务 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-162)任务 T1 申请 M1(mutex 1)

L13-16(2) Task T1 executes and acquires M1.

L13-163)任务 T1 访问资源 R1

L13-16(3) Resource R1 is accessed.

L13-164)中断发生,中断中使能了任务 T2。由于任务 T2 的优先级高于任务 T1CPU 切换到任务 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-165)该中断即是任务 T2 所等待的事件。

L13-16(5) The ISR is the event that task T2 was waiting for and therefore T2 resumes execution.

L13-166)任务 T2 申请 M2 并占用资源 R2

L13-16(6) Task T2 acquires mutex M2 and is able to access resource R2.

L13-167)任务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-168uC/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-169)此时任务 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.


13-7 总结

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-IIIs 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-IIIs interrupt disable time, but shorter than μC/OS-IIIs 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, its 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.


 

14、同步

 

这个章节主要介绍任务是如何于 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.

 

14-1 信号量

正如第 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-31)任务 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-32)转向 uC/OS-III 函数。

F14-33OSSemPend()的相关操作。

F14-34)因为 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-35)任务 L 被执行。

F14-3(5)  The low-priority task executes.

F14-36)中断发生,任务 L 被保存,CPU 转向 ISR

F14-37)开始执行 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-38ISR 中调用 OSSemPost()提交了任务 H 所等待的信号量。

F14-39)信号量被发送给任务 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-410)任务 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-411)任务 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-41)高优先级任务 H 被执行。

F14-4(1) A high-priority task is executing.

F14-42)中断发生。

F14-43ISR 中提交了一个信号量。

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-44)信号量被提交后,uC/OS-III 需进行一些操作。

F14-45uC/OS-III 执行相应操作。

F14-46)由于没有更高优先级任务被就绪,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-47)任务 H 被执行。

F14-4(7) The high-priority task is resumed and continues execution.

F14-48)中断发生。

F14-49)第二次中断发生了,ISR 中提交了一个信号量。 F14-410)信号量被提交后,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-411uC/OS-III 执行相应操作。

F14-412)由于没有更高优先级任务被就绪,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-413)任务 H 被执行。

F14-414)任务 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-415uC/OS-III 执行相应的上下文切换工作。

F14-416uC/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-417)任务 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-2 任务内建信号量

经常通过发送信号量实现同步。每个任务都有内建的信号量,通过任务内建的信号量不仅可以简化信号量通信的代码而且更加有效。如图 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-61)任务通过调用 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-63)函数返回时,该参数中保存着信号量被提交时的时间戳。

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-64)函数返回时,根据执行结果保存着一个错误代号。

提交(标记)任务信号量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-71)通过调用 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-72)这个参数为提交方式的选择,一个有两种提交方式:

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-73)函数返回时,根据执行结果保存着一个错误代号。

双向同步

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-81)任务 1 发送信号量给任务 2

L14-8(1)  Task #1 is executing and signals Task #2’s semaphore.

L14-82)任务 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-83)切换到任务 2 并执行,给任务 1 发送信号量。

L14-8(3)  Task #2 executes, and signals Task #1’s semaphore.

L14-84)此时任务 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.

 

14-3 事件标志组

当任务要与多个事件同步时可以使用事件标志。若其中的任意一个事件发生时任务被就绪,叫做逻辑或(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-91)事件标志组是 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-92)任务或 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-93)任务可以等待事件标志组中的任意个位被设置。等待也可以被设置期限,以时基为单位。

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-94)任务等待事件标志组中的位,可以被设置为 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-91)设置相应位所表示的含义。

L14-9(1)  Define some bits in the event flag group.

L14-92)定义一个事件标志组,其类型为 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-93)事件标志组必须被创建后才能使用,最后在应用的启动代码(或者说初始化代码)中就创建事件标志组。在这个例子中,事件标志组被赋予一个名字,且全部位被清零。

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-94)假定 MyTask()在事件标志组中被挂起。

L14-9(4)  Assume that the application created “MyTask()” which will be pending on the event flag group.

L14-95)调用 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-96)中断被设置为检测电池电压。当电压过低时,中断发生,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-97)通过调用 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-101)在 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-102)结构体的第一个变量为"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-103)每个内核对象都可以被分配一个名字。

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-104)因为可以有多个任务同时等待事件标志组中的事件,所以事件标志组中包含了一个用于控制挂起队列的结构体(见第十章)。

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-105)事件标志组中包含了很多标志位,这个变量中保存了当前这些标志位的状态。这个变量可以为 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-106)事件标志组中包含了一个变量,存储了最后一次标志被提交的时间戳。用户代码不能直接访问事件标志组,必须通过 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-111)定义事件标志组。

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-112)调用 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-113)给事件标志组赋予名字。名字必须以空字符结尾(不是空格)。

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-114)一般情况下,将事件标志组中的所有标志位初始化为 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-115)返回一个错误代号。调用 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-121OSFlagPend()开始时先检查参数的有效性。若函数所等待的标志位被设置,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-122)如果 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-131ISR 或任务调用 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-132)第二个参数是时间标志组中的哪些位需要被设置或清除。

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_SETOS_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-134)根据函数执行结果返回一个错误代号。

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().

 

14-4 多任务同步

通过广播信号量实现多任务同步是通用的方法。显然的,在单 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-111)右边的每个任务都需要跟事件标志组的位对应。

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-112)右边任务需等待信号量才能被就绪。

F14-11(2) The task needs to wait for the semaphore to be signaled.

F14-113)当事件标志组中与需要同步的任务对应位都被置位后,左边任务才能广播信号量。

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-114)左边任务广播信号量给右边任务。

F14-11(4) When all waiting tasks are ready, the task that will synchronize the waiting task issues a broadcast to the semaphore.

 

14-5 总结

有三种方法可以让 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.

 


 

15、消息传递

 

有些情况下任务或 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.

 

15-1 消息

消息中包含一个指向数据的指针、该数据的大小、时间戳变量。该指针可以指向数据区域甚至是一个函数。当然,消息的发送方和消息的接收方都应该知道消息所包含的意义。换句话说,接收方知道接收发到消息的含义。

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.

 

15-2 消息队列

消息队列是内核对象。事实上,可以分配任意个消息队列(只要处理器的 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.

 

15-3 任务的消息队列

很少会见到多个任务同时在一个消息队列中等待。因为这样,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 双向通信

两个任务可以通过两个消息队列同步,如图 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.

15-5 流量控制

任务间的通信经常通过从任务 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.

15-6 保持数据在作用域中

消息通常指向结构体、变量、数组等。然而,数据必须被保持在其作用域(结构体、变量、数组)中直到接收者完成对这些数据的操作。消息一旦被发送,发送者就不能访问被发送的消息中所包含的数据。这看起来很显然,但经常被忘记。

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-81)这里,UART 接收到开始字节并中断。

F15-8(1)  Here, a UART generates an interrupt when characters are received.

F15-82)列表 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-83)接收到的数据被放入内存块中。

F15-8(3)  The received byte is then placed in the buffer.

F15-84)如果检测到包的结束字节,就提交这个内存块的地址给消息队列,以便让任务处理这个接收包。

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-85)如果这个消息使 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-86)任务处理完这个数据包后,通过调用 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-7 使用消息队列

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-91)本例的目标是测量旋转轮的旋转速率。

F15-9(1)  The goal is to measure the RPM of a rotating wheel.

F15-92)传感器被用于测量该旋转轮的速率。轮的周围有很多小孔,这是个光传感器。

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-9332 位的输入捕获寄存器用于捕获每次中断时的 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-94)检测到一个孔时中断产生。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-95Delta 值被发送到消息队列。因为消息实际上是一个指针,如果这个指针是 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-96)当消息被发送后,测速任务被唤醒,并计算每分钟的旋转速率:

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-97)该任务也可以计算平均旋转速率,最大旋转速率等待。

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-31)消息队列被定义。

L15-3(1)  Variables are declared. Notice that it is necessary to allocate storage for the message queue itself.

L15-32)调用 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-33ISR 清除传感器中断并读取捕获寄存器的值。在 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-34)将 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-35)测量任务等待测量 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-36)等待超时(假定如旋转轮停止旋转)。

L15-3(6)  If a timeout occurs, assume the wheel is no longer spinning.

L15-37)计算旋转速率。

L15-3(7)  The RPM is computed from the delta counts received, and from the reference frequency of the free-running counter.

L15-38)其它的一些操作。事实上,在检测到错误的情况下消息可以被发送给其它任务。例如,当旋转速率很快,其它任务就可以让旋转轮减速。

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-41)任务的 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-42)创建测量任务并设置其消息队列的大小为 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-43ISR 直接将消息发送给任务。

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-44)测速任务通过调用 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-8 客户端和服务器端

消息队列另一种用法如图 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-9 消息队列的组成

消息由四个变量组成:指向下一条消息的指针、用于表明该消息所指向数据的大小的变量、存放消息最后一次被提交的时间戳的变量、消息中包含一个指向实际数据的指针。如图 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_QOS_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.

15-10 总结

当任务或 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.


 

16、挂起多个对象

 

在章节 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-11OSPendMulti 的第一个参数:数据类型为 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-12)第二个参数为数组的大小,这个例子中为 5

L16-1(2)  This argument specifies the size of the OS_PEND_DATA table. In the above example, this is 5.

L16-13)设置等待期限,如果在这段时间内没有一个对象被提交就会超时。设置 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-14)参数"opt"设置了等待的方式。OS_OPT_PEND_BLOCKINGOS_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-21)如大多数 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-22)所有的对象的类型都是 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()就在表中该索引中填入相应的值:RdyObjPtrRdyMsgPtr.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-31)任务的 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-32OS_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-33)第一个信号量指向第一个 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-34OS_PEND_DATA 表中记录 0 .PendObjPtr 指向第一个信号量。通过调用 OSPendMulti()等待对象并设定指针指向。

F16-3(4)  This pointer was specified by the caller of OSPendMulti().

F16-35)因为信号量中只有 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-36)第二个信号量指向第二个 OS_PEND_DATA

F16-3(6)  The second semaphore points to the second entry in the OS_PEND_DATA table.

F16-37OS_PEND_DATA 表中记录 0 .PendObjPtr 指向第二个信号量。通过调用 OSPendMulti()等待对象并设定指针指向。

F16-3(7)  This pointer was specified by the caller of OSPendMulti().

F16-38)因为信号量中只有 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-39)任务中有指针链接到两个 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.

16-1 总结

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.


 

17、内存管理

 

可以通过使用编译器提供的函数 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.

17-1 创建一个内存分区

在使用内存分区之前必须创建它。这样 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-31)当创建一个内存分区时,内存控制块(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-32OSMemCreate()将内存块链接起来并将其链表的首地址赋值给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-33)每个内存块的大小必须大于一个指针。

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-11)给内存分区分配一个内存控制块。可以静态分配或用 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-12)给内存分区分配多个内存块。这可以静态分配或用 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-13)内存分区在被创建之前需被分配内存控制块和内存块。最好在 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-14)将内存控制块的地址传递给 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-IIIs API.

L17-15)给内存分区分配一个名字。

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-16)将内存块数组的首地址传递给 OSMemCreate()

L17-1(6)  Pass the base address of the storage area reserved for the memory blocks.

L17-17)该参数为分配给内存分区的内存块数。

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-18)标明该内存分区中每个内存块的大小。用数字值是为了说明,最好用#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-19OSMemCreate()根据函数的执行结果返回一个错误代号。

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-21)定义一个指针指向 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-22malloc()分配空间给内存控制块 OS_MEM

L17-2(2)  The application allocates storage for the memory control block.

L17-23)给内存分区分配内存块。

L17-2(3)  Allocate storage for the memory partition.

L17-24)将 OS_MEM 的地址传递给 OSMemCreate()

L17-2(4)  Pass a pointer to the allocated memory control block to OSMemCreate().

L17-25)将内存块的基地址传递给 OSMemCreate()

L17-2(5)  Pass the base address of the storage used for the partition.

L17-26)最后,传递内存块的个数和大小给 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.

 

17-2 获得内存分区中的内存块

应用代码通过调用 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-31)所有要访问该内存分区的任务或 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-32)调用 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-33)根据返回的错误代号检测函数的执行结果。

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.

 

17-3 归还内存块给内存分区

当用户对内存块的使用完毕后,必须将该内存块归还给对应的内存分区。调用 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-41)所有要访问该内存分区的任务或 ISR 必须可以访问这个内存分区的内存控制块。

L17-4(1)  The memory partition control block must be accessible by all tasks or ISRs that will be using the partition.

L17-42)调用 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-43)该指针指向要归还的内存块地址。它的类型为"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-44)检测错误代号。

L17-4(4)  Examine the returned error code to ensure that the call was successful.

 

17-4 使用内存分区

编译时设置 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-41)任务读取模拟输入。如果测得模拟输入超范围,就发送消息给错误处理函数。

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-42)任务从内存分区中申请一个内存块用于存放检测模拟输入所得的相关数据。

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-43)任务将检测模拟输入所得的相关数据存入内存块。然而,没必要在内存块中存放时间戳因为时间戳被存放在消息中。

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-44)任务提交消息给错误处理任务。当然,错误处理任务知道消息中所包含的数据的含义。一旦消息被发送,发送者任务不能再访问消息所指向的内存块。

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-45)错误处理任务通常在消息队列中等待。

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-46)错误处理任务接收到消息后,执行相应的操作。

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-47)错误处理任务处理完毕相应的操作后,将内存块归还给对应的内存分区。因此,发送者和接收者都必须知道内存块是属于哪个内存分区的,或者发送者可以将内存分区的地址作为要发送消息的数据中的一部分。这样,错误处理函数就知道将这个内存块归还给哪个内存分区了。

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-51)获得一个内存块时,先调用 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-52)释放一个内存块时,先调用 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.

 

17-5 总结

不要在嵌入式系统中使用 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.


 

18、移植 uC/OS-III

 

这个章节介绍了如何移植 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-11)移植 uC/OS-III 需修改 3 个与内核相关的文件:OS_CPU.H

OS_CPU_A.ASMOS_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-12)移植 uC/OS-III 需修改 3 个与 CPU 相关的文件:CPU.H

CPU_A.ASMCPU_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-13BSP 中通常包含了 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-14)有些半导体厂商会提高相应的固件库文件,这些文件会被包含在 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.

移植包括三方面内容:CPUOSBSP

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.

 

18-1 uC/CPU

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 编译器的数据类型 intshortlongchar 等,而是定义了更易于看懂的数据类型。查阅编译器的用户手册,看其数据类型所对应的字长。如果使用 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_PRESENTCPU_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 移植

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()

详见附录 AOS_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.

18-3 板级支持包 BSP

板级支持包的代码跟用户所使用的目标板有关。例如,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.

 

18-4 总结

移植包括三个部分代码:CPUOSBSP

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.HCPU_A.ASMCPU_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).