第12章 任务间通信与同步_第1页
第12章 任务间通信与同步_第2页
第12章 任务间通信与同步_第3页
第12章 任务间通信与同步_第4页
第12章 任务间通信与同步_第5页
已阅读5页,还剩106页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、温州大学物理与电子信息工程学院 杨卫波 嵌入式系统原理与应用 第12章 任务间通信与同步 * 嵌入式系统原理与应用 2 pRTX配置下是有并发的,但任一个时刻点上只有一个程序在处 理机上运行。实际上并行是会涉及很多问题的,如多人同时在 干活,他们需要共享到一些资源,那就涉及到沟通协调的额外 付出了。 pRTX的进程间通讯主要依赖于四种机制:事件(Event)、互斥 锁(Mutex)、信号量(Semaphore)、和邮箱(Mailbox)。 前三种机制侧重进程间的同步,邮箱侧重进程间的数据通讯。 前言 任务间的同步 任务间的同步任务间的同步 - 各任务运行的先后、触发等关系 备注:数据采集任务A

2、 和数据处理任务B 之间存在同步关系 任务间的互斥 任务间的互斥任务间的互斥 - 共享资源的申请使用 备注:数据采集任务A 和数据处理任务B 之间存在互斥关系 12.1 几个重要的概念 p临界段:代码的临界段也称为临界区,一旦这部分代码开始执 行,则不允许任何中断打断。为确保临界段代码的执行不被中 断,在进入临界段之前须关中断,而临界段代码执行完毕后, 要立即开中断。 p由于Cortex-M3/M4的RTX内核库中没有关闭中断的操作,也就是 说 RTX 的源码中不存在临界段。 p用户写应用的时候也有临界段的问题,比如以下两种: u读取或者修改变量(特别是任务间通信的全局变量)的代码 ,一般来说

3、这是最常见的临界代码。 u调用公共函数的代码,特别是不可重入的函数,如果多个任 务都访问这个函数,结果是可想而知的。 几个重要的概念 p中断锁:因为Cortex-M3/M4的RTX源码中没有关闭中断的操作, 所以也就没有提供开关中断函数。 p用户在自己的应用代码采用裸机时如何开关中断的,在使用了 RTX后仍然使用以前的开关中断函数即可。 任务锁 p任务锁:为了防止当前任务的执行被其它高优先级的任务打断 而提供的锁机制就是任务锁任务锁。实现任务锁可以通过给调度器加 锁或者直接关闭RTOS内核定时器(就是前面一直说的系统滴答 定时器)来实现。 u通过给调度器加锁实现给调度器加锁的话,就无法实现任务

4、 切换,高优先级任务也就无法抢占低优先级任务的执行,同 时高优先级任务也是无法向低优先级任务切换的。这种方式 只是禁止了调度器工作,并没有关闭任何中断。 u通过关闭 RTOS 内核定时器实现关闭了 RTOS 内核定时器的 话,也就关闭了通过 RTOS 内核定时器中断实现任务切换的 功能,因为在退出定时器中断时需要检测当前需要执行的最 高优先级任务,如果有高优先级任务就绪的话需要做任务切 换。RTX 操作系统是采用的这种方式实现任务锁的。 12.2 RTX 任务锁的实现 函数原型: voidvoid tsk_lock ( tsk_lock (voidvoid); ); 函数描述:禁止 RTX 内

5、核定时器中断,因此也就禁止了任务切换。 注意以下问题: p函数 tsk_lock 不支持嵌套调用; p不允许在中断服务程序中调用 tsk_lock; pRTX 内核定时器被关闭期间,RTX 内核任务调度器和需要时间片调度 的任务被阻塞。 p设置的任务延迟时间不再工作。 p因此,强烈建议关 RTX 内核定时器中断的时间越短越好。 RTX 任务锁的实现 函数原型: voidvoid tsk_unlock ( tsk_unlock (voidvoid); ); 函数描述: 用于使能 RTX 内核定时器中断,因此也就重新开启任务切 换。tsk_unlock 一定要跟 tsk_lock 配套使用。 使用

6、这个函数要注意以下问题:函数 tsk_lock 不支持嵌套调用;不允 许在中断服务程序中调用 tsk_lock。 使用举例: #include#include voidvoid protect_critical_op() protect_critical_op() tsk_lock(); tsk_lock(); do_critical_op(); do_critical_op(); tsk_unlock();tsk_unlock(); 12.2.3 例程说明 实验内容: 1. K1 按键按下,串口打印。 2. 在调用 printf 函数的地方都加上任务锁,防止多个任务调用此函数造成冲 突,以至

7、于串口打印出现乱码。 3. 各个任务实现的功能如下: AppTaskUserIF任务 :按键消息处理。 AppTaskLED 任务:LED 闪烁,并串口打印任务正在运行。 AppTaskMsgPro 任务:消息处理,用作 LED 闪烁和串口打印任务正在 运行。 AppTaskStart 任务:启动任务,最高优先级任务,实现按键扫描。 RTX配置 四个 RTX 任务的实现 _task _task voidvoid AppTaskMsgPro( AppTaskMsgPro(voidvoid) ) whilewhile (1) (1) bsp_LedToggle(1); bsp_LedToggle(

8、1); bsp_LedToggle(4);bsp_LedToggle(4); / /* * 开启任务锁开启任务锁 */ tsk_lock();tsk_lock(); printf(printf( 任务任务AppTaskMsgPro正在运行正在运行rn);); / /* * 关闭任务锁关闭任务锁 */ tsk_unlock();tsk_unlock(); os_dly_wait(300);os_dly_wait(300); RTX 任务调试信息 12.3 事件标志组 事件标志组是实现多任务同步的有效机制之一。初学者会问采用事件 标志组多麻烦,搞个全局变量不是更简单,其实不然。在裸机编程 时,使用

9、全局变量的确比较方便,但相比事件标志组主要有如下三 个问题: p使用事件标志组可以让 RTOS 内核有效的管理任务,全局变量是无 法做到的,任务的超时等机制需要用户自己去实现。 p使用了全局变量就要防止多任务的访问冲突,使用事件标志组已经 处理好了这个问题。用户无需担心。 p使用事件标志组可以有效的解决中断服务程序和任务之间的同步问 题。 12.3.1 事件标志组的实现 创建 2 个任务 Task1 和 Task2,运行过程描述如下: p任务 Task1 运行过程中调用函数 os_evt_wait_and,等待事件标志位 被设置,任务 Task1 由运行态进入到挂起态 p任务 Task2 设置

10、了任务 Task1 的事件标志,任务 Task1 由挂起态进 入到就绪态,在调度器的作用下由就绪态又进入到运行态。 各个任务之间使用事件标志组实现任务的通信或者同步。RTX 每 个任务创建的时候,会自动创建 16 个事件标志,事件标志被存储 到每个任务的任务控制块中,即每个任务支持 16 个事件标志。 12.3.2 中断方式事件标志组的实现 指中断函数和 RTX 任务之间使用事件标志 运行条件:创建 1 个任务和一个串口接收中断 运行过程描述如下: p任务 Task1 运行过程中调用函数 os_evt_wait_and,等待事件标志位 被设置,任务 Task1 由运行态进入到挂起态 pTask

11、1 挂起的情况下,串口接收到数据进入到了串口中断服务程序, 在串口中断服务程序中设置 Task1 的事件标志,任务 Task1 由挂起 态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。 12.3.2 中断方式事件标志组的实现 实际应用中,中断方式的消息机制切记注意以下四个个问题: p中断函数的执行时间越短越好,防止其它低于这个中断优先级 的异常不能得到及时响应。 p实际应用中,建议不要在中断中实现消息处理,用户可以在中 断服务程序里面发送消息通知任务,在任务中实现消息处理, 这样可以有效的保证中断服务程序的实时响应。同时此任务也 需要设置为高优先级,以便退出中断函数后任务可以得到及时

12、执行。 p中断服务程序中一定要调用专用于中断的事件标志设置函数 isr_evt_setisr_evt_set。 p在 RTX 操作系统中实现中断函数跟裸机编程是一样的。 中断方式 * 嵌入式系统原理与应用 18 p节拍时钟任务不仅仅在时钟节拍中断产生时运行,而且当一个 中断调用isr_函数时也执行。这是因为中断不能使当前任务等 待,因此也不能进行任务的切换。 p中断可以产生事件、信号量或是消息(使用isr_库函数)来表 明一个更高优先级的任务正在等待。 p更高优先级的任务将抢占当前的任务,但必须在中断函数结束 后才能进行。这样就强制产生在当前中断结束后运行的时钟节 拍中断,而且激发任务调度。任

13、务调度处理所有的任务,并将 就绪的最高优先级的任务投入运行,最高优先级的任务因此可 以持续的运行。 * 嵌入式系统原理与应用 19 PendSV中断 12.3.3 事件标志组 API 函数 使用如下6个函数可以实现 RTX 的事件标志组: pos_evt_clros_evt_clr();(); pos_evt_getos_evt_get();(); pos_evt_setos_evt_set();(); pos_evt_wait_andos_evt_wait_and();(); pos_evt_wait_oros_evt_wait_or();(); pisr_evt_setisr_evt_se

14、t();(); 函数 os_evt_set voidvoid os_evt_set ( U16 event_flags, /* 16位的事件标志设置位的事件标志设置 */ OS_TID task ); /* 要设置事件标志的任务要设置事件标志的任务ID */ 函数描述:用于设置指定任务的事件标志。 p第1个参数表示16个可设置的事件标志位。因为RTX的每个任务创建时有16 个可设置的事件标志,这里用 U16 类型的变量 event_flag 就可以表示, 变量 event_flag 的某个位设置为 1,那么指定 RTX 任务的事件标志相应 位就设置为 1。变量 event_flag 设置为 0

15、 的位对 RTX 任务的事件标志相 应位没有影响。比如设置变量 event_flag = 0 x0003 就表示将 RTX 任务事 件标志的位 0 和位 1 设置为 1,其余位没有变化。 p第 2 个参数是任务 ID。 函数 os_evt_set 使用这个函数要注意: 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函 数,中断服务程序中使用的是 isr_evt_set #include #include _task void task1 (_task void task1 (voidvoid) . ) . os_evt_set (0 x0003, tsk2); os_evt_se

16、t (0 x0003, tsk2); . . 函数 isr_evt_set voidvoid isr_evt_set ( U16 event_flags, /* 16位的事件标志设置位的事件标志设置 */ OS_TID task ); /* 要设置事件标志的任务要设置事件标志的任务ID */ 函数描述函数描述:用于设置指定任务的事件标志。 p第1个参数表示16个可设置的事件标志位。 p第 2 个参数是任务 ID。 使用这个函数要注意以下问题: 1.此函数是用于中断服务程序中调用的。 2. 调用不能太频繁,太频繁的话会大大增加系统内核的开销,会造成事件标志 得不到及时处理从而造成丢失事件标志的情

17、况。 #include #include voidvoid EXTI0_IRQHandler ( EXTI0_IRQHandler (voidvoid) . ) . isr_evt_set (0 x0003, tsk2); . isr_evt_set (0 x0003, tsk2); . 函数 os_evt_wait_and OS_RESULT os_evt_wait_and ( U16 wait_flags, /* 16位的事件标志等待位的事件标志等待 */ U16 timeout ); /* 超时时间设置超时时间设置 */ 函数描述函数描述:用于等待事件标志被设置。 p第1 个参数表示任务

18、等待的事件标志位。因为 RTX 的每个任务创建时有 16 个可以设置的事件标志,这里用 U16 类型的变量 event_flag 就可以设 置,变量 event_flag 的那位设置为 1,那么 RTX 任务的事件标志就等待 那个位被设置为 1。而且要所有要求的位都被设置为 1 才可以。比如设置 变量 event_flag = 0 x0003 就表示 RTX 任务在等待事件标志的位 0 和位 1 都被设置为 1。 p第 2 个参数表示设在的等待时间,范围 0-0 xFFFF,当参数设置为 0- 0 xFFFE 时,表示等这么多个时钟节拍,参数设置为 0 xFFFF 时表示无限等 待直到事件标志

19、满足要求。 函数 os_evt_wait_and p函数返回 OS_R_EVT 表示等待的事件标志位都被设置了,也就是返回成功 。返回 OS_R_TMO 表示超时。 使用这个函数要注意以下问题: 1. 当要求的事件标志位都被设置为 1 时或者设置的超时时间溢出时,函数 os_evt_wait_and 才会返回。 2. 如果函数 os_evt_wait_and 返回前所要求的事件标志位都设置了,那么此 函数会在返回前将相应的事件标志位清零,其它位不受此影响。 函数os_evt_wait_or os_evt_wait_or(U16wait_flags,U16timeout); 与事件等待,参数意义

20、与和事件等待相同。 这两个操作的差别在于(其实应该比较一目了然)对于和事件等待而言,所 有标示都满足了,进程才会被唤醒,对于与事件等待而言,任一标示状态 满足了就会唤醒进程。 例子: os_evt_wait_and (0 xABCD, 0 xFFFF); os_evt_wait_or (0 xABCD, 0 xFFFF); p第一个语句的话,进程会等待15,13,11,9,8,7,6,3,2,0位(0 xABCD)的标 示全部被设为1时才会被唤醒; p第二个语句而言,15,13,11,9,8,7,6,3,2,0位中的任一一位标示被设为1,进 程就会被唤醒。 使用举例 #include#incl

21、ude #define#define BIT_0BIT_0 (1 0) (1 0) #define#define BIT_1BIT_1 (1 1) (1 1) #define#define BIT_ALLBIT_ALL ( (BIT_0BIT_0 | | BIT_1BIT_1) ) _task _task voidvoid AppTaskMsgPro( AppTaskMsgPro(voidvoid) ) OS_RESULT xResult;OS_RESULT xResult; constconst uint16_t usMaxBlockTime = 500; uint16_t usMaxBlo

22、ckTime = 500; / /* * 延迟周期延迟周期 */ whilewhile (1) (1) xResult = os_evt_wait_and(xResult = os_evt_wait_and(BIT_ALLBIT_ALL, usMaxBlockTime);, usMaxBlockTime); switchswitch (xResult) (xResult) casecase OS_R_EVT: OS_R_EVT:/ /* * 接收到接收到bit1和和bit0都被设置的消息都被设置的消息 */ printf(printf( 接收到接收到bit0和和bit1都被设置的消息都被设置的

23、消息rn);); breakbreak; ; casecase OS_R_TMO: OS_R_TMO: / /* * 超时超时 */ bsp_LedToggle(1);bsp_LedToggle(1); bsp_LedToggle(4);bsp_LedToggle(4); breakbreak; ; defaultdefault: : / /* * 其他值不处理其他值不处理 */ breakbreak; ; * 嵌入式系统原理与应用 28 os_evt_get() 12.3.4 例程说明(任务间通信) 内容: 1. K1 按键按下,串口打印。 2. K2 键按下,直接发送事件标志给任务AppT

24、askMsgPro,设置 bit0 3. K3 键按下,直接发送事件标志给任务 AppTaskMsgPro,设置 bit1 4. 任务 AppTaskMsgPro 只有接收到 bit0 和 bit1 都被设置了才执行串口打印信息。 各个任务实现的功能如下: AppTaskUserIF 任务:按键消息处理。 AppTaskLED 任务:LED 闪烁。 AppTaskMsgPro 任务:消息处理,等待任务 AppTaskUserIF 发来的事件 标志。 AppTaskStart 任务 :启动任务,最高优先级任务,实现按键扫描。 RTX 配置 RTX 任务调试信息 Event Value:任务 Ap

25、pTaskMsgPro 当前的事件标志数值。 Event Mask:任务 AppTaskMsgPro 等待的事件标志数值。 12.3.4 例程说明(中断方式通信) 内容: 1. K1 按键按下,串口打印。 2. K2键按下,启动单次定时器中断,50ms后在定时器中断给任务AppTaskMsgPro发 送事件标志,设置 bit0。 3. K3键按下,启动单次定时器中断,50ms后在定时器中断给任务AppTaskMsgPro发 送事件标志,设置 bit1。 4. 任务 AppTaskMsgPro 只有接收到 bit0 和 bit1 都被设置了才执行串口打印信息。 各个任务实现的功能如下: AppT

26、askUserIF 任务:按键消息处理。 AppTaskLED 任务:LED 闪烁。 AppTaskMsgPro 任务 :消息处理,等待定时器中断发来的事件标志。 AppTaskStart 任务 :启动任务,也是最高优先级任务,这里实现按键扫描。 * 嵌入式系统原理与应用 33 #define#define EVT_KEYEVT_KEY 0 x0001 0 x0001 OS_TID pr_task;OS_TID pr_task; intint num_ints; num_ints; / /* *- External 0 Interrupt Service Routine- External 0

27、 Interrupt Service Routine-* */ / voidvoid ext0_int( ext0_int(voidvoid) _irq ) _irq isr_evt_set(isr_evt_set(EVT_KEYEVT_KEY, pr_task); , pr_task); / /* * Send event to process_task Send event to process_task* */ / . _task _task voidvoid process_task( process_task(voidvoid) ) num_ints = 0;num_ints = 0

28、; whilewhile (1) (1) os_evt_wait_or(os_evt_wait_or(EVT_KEYEVT_KEY, 0 xffff);, 0 xffff); num_ints+;num_ints+; voidvoid init_task( init_task(voidvoid) _task ) _task . pr_task = os_tsk_create(process_task,100);pr_task = os_tsk_create(process_task,100); os_tsk_delete_self(); os_tsk_delete_self(); / /* *

29、 Terminate this task Terminate this task * */ / RTX核中使用中断 任务process_task仅是记录发生中断的次数 * 嵌入式系统原理与应用 34 分析程序的功能 #include RTL.h OS_TID id1, id2; _task void task1 (void); _task void task2 (void); _task void task1 (void) id1 = os_tsk_self (); id2 = os_tsk_create (task2, 0); for (;) os_evt_set (0 x0004, id2

30、); os_evt_wait_or (0 x0004, 0 xffff); os_dly_wait (5); _task void task2 (void) for (;) os_evt_wait_or (0 x0004, 0 xffff); os_dly_wait (2); os_evt_set (0 x0004, id1); int main (void) os_sys_init (task1); 12.4 信号量 信号量(semaphores)是20世纪60年代中期Edgser Dijkstra发 明的。最初目的是为了给共享资源建立一个表示该共享资源被占 用情况的标志。当一个任务在访问共

31、享资源前,先对这个标志进 行查询,在了解资源被占用的情况后,再来决定自己的行为。 如有个 30 人的电脑机房,创建信号量的初始化值是30,表示30 个可用资源。要求一个同学使用一台电脑,有一个同学使用一台 电脑,信号量的数值就减一。当信号量的数值为 0时,还有同学 没有电脑可以使用,那么这个同学就得等待,直到有同学离开, 有一个同学离开,那么信号量的数值就加 1,依次类推。刚才没 有电脑用的同学此时就有电脑可以用了。这么一个过程就是使用 信号量来管理共享资源的过程。 12.4 信号量 平时使用信号量主要实现以下两个功能: p两个任务或者中断函数跟任务之间的同步功能,和事件标志组 是类似的。其实

32、就是共享资源为 1 的时候。 p多个共享资源的管理,就像上面机房上机的例子。 简单说来,信号量有三个部分: u第一部分就是一个代币容器(一个整形变量),记录着可用的 资源数; u第二部分就是取用资源(P,尝试的荷兰语)的操作; u第三个就是还回资源(V, 增加的荷兰语)的操作。当资源数量 为0时任何取用资源的操作都会被阻断,直到资源数量增加。 12.4.2 RTX 任务间信号量的实现 指各个任务之间使用信号量实现任务的同步或者资源共享功能。 运行过程描述如下: pTask1运行中调用os_sem_wait获取信号量 资源,如果信号量没有被Task2占用, Task1 将直接获取资源。如果信号被

33、 Task2 占用,Task1 将由运行态转到挂起状态, 等待资源可用。一旦获取了资源并使用完 毕后会通过函数 os_sem_send 释放掉资源 pTask2运行中调用os_sem_wait获取信号量 资源,如果信号量没有被任务Task2占用, Task1将直接获取资源。如果信号被Task2 占用,Task1将由运行态转到挂起状态,等 待资源可以。一旦获取了资源并使用完毕 后会通过函数 os_sem_send 释放掉资源。 运行条件:创建 2 个任务 Task1 和 Task2,创建信 号量可用资源为 1。 12.4.3 中断方式信号量的实现 指中断函数和 RTX 任务之间使用信号量,主要是

34、用于实现任务同步。 p创建 1 个任务 Task1 和一个串口接收中断。 p信号量的初始值为 0,串口中断调用函数 isr_sem_send 释放信号 量,任务 Task1 调用函数 os_sem_wait 获取信号量资源。 运行过程描述 p任务 Task1 运行过程中调用函数 os_sem_wait,由于信号量的 初始值是 0,没有信号量资源可用,任务 Task1 由运行态进入 到挂起态。 pTask1 挂起的情况下,串口接收到数据进入到了串口中断服务程 序,在串口中断服务程序中调用函数 isr_sem_send 释放信号量 资源,信号量数值加 1,此时信号量计数值为 1,任务 Task1

35、由 挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运 行态,任务 Task1 获得信号量后,信号量数值减1,此时信号量 计数值又变成了 0。 p再次循环执行时,任务 Task1 调用函数 os_sem_wait 由于没有 资源可用再次进入到挂起态,等待串口释放信号量资源,如此 往复循环。 注意以下四个问题 p中断函数的执行时间越短越好,防止其它低于这个中断优先级 的异常不能得到及时响应。 p实际应用中,建议不要在中断中实现消息处理,用户可以在中 断服务程序里面发送消息通知任务,在任务中实现消息处理, 这样可以有效的保证中断服务程序的实时响应。同时此任务也 需要设置为高优先级,以便退出中断

36、函数后任务可以得到及时 执行。 p中断服务程序中一定要调用专用于中断的信号量设置函数 isr_sem_send。 p在 RTX 操作系统中实现中断函数和裸机编程是一样的。 12.4.4 信号量 API 函数 使用如下4个函数可以实现 RTX 的信号量: pos_sem_initos_sem_init pos_sem_sendos_sem_send pisr_sem_sendisr_sem_send pu os_sem_wait u os_sem_wait 函数 os_sem_init void os_sem_init ( OS_ID semaphore, /* os_sem类型变量类型变量 *

37、/ U16 token_count ); /* 信号量初始值信号量初始值 */ 函数描述函数描述:用于信号量的初始化并设置初始值 p第1个参数填写数据类型为OS_SEM的变量,同时也作为ID标识; p第2个参数是信号量初始值,也就是可用资源个数。 使用举例使用举例: #include #include OS_SEM semaphore; OS_SEM semaphore; static void AppObjCreate(void)static void AppObjCreate(void) /* 创建信号量计数值是创建信号量计数值是0, 用于任务同步用于任务同步 */ os_sem_init

38、(os_sem_init( 为什么变量semaphore 前 面加一个取地址符 /* OS_SEM类型变量类型变量 */ 函数描述:函数描述:用于释放信号量,调用后信号量计数值加 1。 p第 1 个参数参数填写数据类型为 OS_SEM 的变量,同时也作为 ID 标识。 p返回值永远是 OS_R_OK。 p使用此函数前一定要调用函数 os_sem_init 进行初始化。 使用举例:使用举例: #include #include OS_SEM semaphore; OS_SEM semaphore; _task void task1 (void) . _task void task1 (void)

39、 . os_sem_init ( os_sem_init ( os_sem_send ( . os_sem_send ( . 函数 isr_sem_send void isr_sem_send ( OS_ID semaphore ); /* OS_SEM类型变量类型变量 */ 函数描述:函数描述:用于释放信号量,调用后信号量计数值加 1。 p第 1 个参数参数填写数据类型为 OS_SEM 的变量,同时也作为 ID 标识。 p使用此函数前一定要调用函数 os_sem_init 进行初始化。 使用举例:使用举例: #include #include OS_SEM semaphore; OS_SEM

40、 semaphore; void EXTI0_IRQHandler (void) . void EXTI0_IRQHandler (void) . isr_sem_send ( . isr_sem_send ( . 函数 os_sem_wait OS_RESULT os_sem_wait ( OS_ID semaphore, /* OS_SEM类型变量类型变量 */ U16 timeout ); /* 超时时间设置超时时间设置 */ 函数描述:用于获取信号量,如果当前的信号量计数值大于函数描述:用于获取信号量,如果当前的信号量计数值大于 0,那么调用函数,那么调用函数 os_sem_wait

41、后可以成功获取信号量,并将信号量的计数值减后可以成功获取信号量,并将信号量的计数值减 1。如果信号。如果信号 量计数值等于量计数值等于 0,调用此函数的任务将由运行态转到挂起态,等待信号量资源,调用此函数的任务将由运行态转到挂起态,等待信号量资源 可用,也就是等待信号量计数值大于可用,也就是等待信号量计数值大于 0。 p第 1 个参数参数填写数据类型为 OS_SEM 的变量,同时也作为 ID 标识。 p第 2 个参数表示设置的等待时间,范围 0-0 xFFFF,当参数设置为 0-0 xFFFE 时,表示等待这么多个时钟节拍,参数设置为 0 xFFFF 时表示无限等待直到有 信号量资源可用。 p

42、函数返回 OS_R_SEM 表示函数设置的超时时间范围内收到信号量可用资源 。函数返回 OS_R_TMO 表示超时。函数返回 OS_R_OK 表示无需等待,立 即获得可用信号量资源。 使用举例 #include OS_SEM semaphore; _task void AppTaskMsgPro(void) OS_RESULT xResult; const uint16_t usMaxBlockTime = 200; /* 延迟周期 */ while (1) xResult = os_sem_wait( switch (xResult) case OS_R_OK: printf(无需等待接受到

43、信号量同步信号rn);break; case OS_R_SEM: printf(信号量不可用,内收到信号量同步信号rn);break; case OS_R_TMO: /* 超时 */ bsp_LedToggle(1); bsp_LedToggle(4); break; default:break; 使用举例 os_semsemaphore;os_semsemaphore; _taskvoid task1(_taskvoid task1(voidvoid) ) os_sem_init(semaphore, 0);os_sem_init(semaphore, 0); whilewhile (1)

44、(1) Function1();Function1(); os_sem_send(semaphore);os_sem_send(semaphore); _taskvoid task2(_taskvoid task2(voidvoid) ) whilewhile (1) (1) os_sem_wait(semaphore, 0 xFFFF);os_sem_wait(semaphore, 0 xFFFF); Function2();Function2(); 信号量的作用就是确保在每一次 调用Function2之前,Function1 都有一次完整的调用。 使用举例 os_semArrived1,

45、Arrived2;os_semArrived1, Arrived2; _task void task1(_task void task1(voidvoid) ) os_sem_init(Arrived1, 0);os_sem_init(Arrived1, 0); os_sem_init(Arrived2, 0);os_sem_init(Arrived2, 0); whilewhile (1) (1) FunctionA1();FunctionA1(); os_sem_send(Arrived1);os_sem_send(Arrived1); os_sem_wait(Arrived2, 0 xF

46、FFF);os_sem_wait(Arrived2, 0 xFFFF); FunctionA2();FunctionA2(); _task void task2(_task void task2(voidvoid) ) whilewhile (1) (1) FunctionB1();FunctionB1(); os_sem_send(Arrived2);os_sem_send(Arrived2); os_sem_wait(Arrived1, 0 xFFFF);os_sem_wait(Arrived1, 0 xFFFF); FunctionB2();FunctionB2(); 目的是让Funct

47、ionA1, functionB1都完成以后,再执 行FunctionA2和FunctionB2 使用举例 os_semMultiplex;os_semMultiplex; _taskvoid task(_taskvoid task(voidvoid) ) os_sem_init(Multiplex, 5);os_sem_init(Multiplex, 5); whilewhile (1) (1) os_sem_wait(Multiplex, 0 xFFFF);os_sem_wait(Multiplex, 0 xFFFF); Function();Function(); os_sem_send

48、(Multiplex);os_sem_send(Multiplex); 保证最多只有五个进程能够同时调用Function()。 12.4.5 实验例程说明(任务间通信) 目的: 学习 RTX 的信号量 实验内容: 1. K1 按键按下,串口打印。 2. K2 键按下,直接发送信号量同步信号给任务 AppTaskMsgPro。任务 AppTaskMsgPro 接收到消息后进行消息处理。 3. 各个任务实现的功能如下: AppTaskUserIF 任务:按键消息处理。 AppTaskLED 任务:LED 闪烁。 AppTaskMsgPro 任务 :消息处理,等待任务 AppTaskUserIF 发

49、来的信号量同 步信号。 AppTaskStart 任务 :启动任务,也是最高优先级任务,这里实现按键扫描。 RTX 配置 RTX 任务调试信息 12.4 .5例程说明(中断方式通信) 目的: 学习 RTX 的信号量(中断方式) 实验内容: 1. K1 按键按下,串口打印。 2. K2 键按下,启动单次定时器中断,50ms 后在定时器中断给任务 AppTaskMsgPro 发送信号量同步信号。任务 AppTaskMsgPro 接收到消息后进行消息处理。 3. 各个任务实现的功能如下: AppTaskUserIF 任务 :按键消息处理。 AppTaskLED 任务:LED 闪烁。 AppTaskM

50、sgPro 任务:消息处理,等待定时器中断发来的信号量同步信号。 AppTaskStart 任务:启动任务,最高优先级任务,实现按键扫描。 定时器中断回调函数中发送信号量同步信号: 定时器中断的初始化和中断函数在 bsp_timer.c 文件中实现, RTX 的 信号量函数在中断服务程序中使用: staticstatic voidvoid TIM_CallBack1( TIM_CallBack1(voidvoid) ) / /* * 发送信号量同步信号发送信号量同步信号 */ isr_sem_send(isr_sem_send( RTX 任务调试信息 * 嵌入式系统原理与应用 56 课堂作业

51、建立两个任务 AppTaskPost 和 AppTaskPend。按键按下时任务 AppTaskPost 当每隔 200ms 向等待的任务 AppTaskPend 发送一个任 务信号量,并通过串口打印出任务信号量的值。当按键松开时任务 AppTaskPend每隔 200ms请求一个任务信号量,红色 LED 闪烁表示请 求到一个任务信号量,任务信号量的值减小,任务AppTaskPost通过串 口打印任务信号量的值。当任务信号量的值减小到 0时,任务 AppTaskPend请求不到任务信号量,红色LED不再闪烁。 12.5 互斥信号量 p互斥信号量就是信号量的一种特殊形式,也就是信号量初始值 为

52、1 的情况。有些 RTOS 中也将信号量初始值设置为 1 的情 况称之为二值信号量。为什么叫二值信号量呢?因为信号量资 源被获取了,信号量值就是 0,信号量资源被释放,信号量值 就是 1,把这种只有 0 和 1 两种情况的信号量称之为二值信 号量。 p互斥信号量的主要作用就是对资源实现互斥访问。 运行条件 p让两个任务 Task1 和 Task2 都有运行串口打印 printf,对函数 printf 通过二值信号量实现互斥访问。如果不对函数 printf 进行互斥访问, 串口打印容易出现乱码。 p用信号量实现二值信号量只需将信号量的初始值设置为 1 即可。 创建二值信号量创建二值信号量 OS_

53、SEM semaphore;OS_SEM semaphore; staticstatic voidvoid AppObjCreate( AppObjCreate(voidvoid) ) / /* * 创建二值信号量,实现对互斥资源的独享创建二值信号量,实现对互斥资源的独享 */ os_sem_init(semaphore, 0);os_sem_init(semaphore, 0); 二值信号量实现互斥访问 _task _task voidvoid AppTaskLED( AppTaskLED(voidvoid) ) constconst uint16_t usFrequency = 1000;

54、 uint16_t usFrequency = 1000; os_itv_set(usFrequency);os_itv_set(usFrequency); whilewhile (1) (1) / /* * 永久等待互斥资源可用永久等待互斥资源可用 */ os_sem_wait(os_sem_wait( printf(printf( 任务任务AppTaskLED正在运行正在运行rn);); / /* * 释放互斥资源释放互斥资源 */ os_sem_send(os_sem_send( / /* * os_itv_wait os_itv_wait是绝对延迟,是绝对延迟,os_dly_wait是

55、相对延迟。是相对延迟。*/ os_itv_wait();os_itv_wait(); _task void AppTaskMsgPro(void) const uint16_t usFrequency = 1000; /* 延迟周期 */ os_itv_set(usFrequency); /* 设置延迟周期 */ while (1) /* 永久等待互斥资源可以 */ printf(任务AppTaskMsgPro正在运行rn); /* 释放互斥资源 */ os_sem_send( /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/ os_itv_wait(); 12.

56、5.2 优先级翻转问题 p3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。 pTask1和Task3采用二值信号实现互斥访问串口打印printf。 p起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开 始执行任务 Task1,也就是图的起始位置。 优先级翻转问题 pTask1 运行的过程需要调用printf,发现Task3 正在调用,Task1 会被 挂起,等待 Task3 释放函数 printf。 p在调度器的作用下,Task3 得到运行,Task3 运行的过程中,由于 Task2 就绪,抢占了 Task3 的运行。优先级翻转问

57、题就出在这里了, 从任务执行的现象上看,高优先级任务 Task1 需要等待低优先级任务 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了。 pTask2 执行完毕后,Task3 恢复执行,Task3 释放互斥资源后,Task1 得到互斥资源,从而可以继续执行。 优先级翻转问题(2) 考虑三个进程,T1,T2,T3。T3的优先度大于T2,T2的优先度大于T1,采 用的调度算法算法是优先度高的可以打断优先度低的进程,临界区等待可以阻断 高优先度的进程。 问题:进程3被什么 阻断了? 答:进程3因为要求 资源R的临界区,而 此时资源R临界区被 进程1占用。 所以,进程3实际上是被进程2

58、阻断了,低优先度的进程阻断了高优先度的进 程,这就是优先度翻转问题。 优先度继承 优先度继承的意思是,将临界区内的进程的优先度提高为该进程阻断的进程里 的优先级最高进程的优先级。 当进程3要求进入R临界区执行时,因为被进程1阻断了,进程1此时在临界区 内,而且它阻断的是进程3,那么此时它的优先度就会提升为进程3的优先度 ,也就是说继承了进程3的优先度。当退出临界执行区后,进程1优先度又会 降为原先的优先度。这就很好地解决了高优先度进程被无条件阻断的问题 死锁 优先度继承的意思是,将临界区内的进程的优先度提高为该进程阻断的进程里 的优先级最高进程的优先级。 T1被创造 T1要求进入资源A临界区

59、T2被创建 T2要讲求进入资源B临界区 T2要求进入资源A临界区 T1要求进入资源B临界区 当进程2要求资源A临界区时,因为进程1正在资源A临界区内,所以其优先 级继承了进程2的优先级,但进程1执行了一段时间后又要求进入资源B临界 区以完成当前任务,退出临界区A。两个进程此时都在等待对方退出临界区 ,而自己却不会主动退出自己占有的临界区,所以死锁。 优先度天花板 具体说来就是,为每个资源定义优先度天花板,资源的优先度天花板是所有 可能要互斥使用该资源的进程的最高优先度。这种策略只允许满足一个条件 的进程去进入临界区,该进程的优先度要大于(等于不足够)所有其他进程 占用的资源的优先度天花板的最高

60、优先度。同时保留优先度继承策略。 当进程2想进入资源B临界区时,被阻断了,因为它优先度并不大于其他进程 (进程1)占用资源(A)的优先度天花板(进程2的优先度),被进程1阻断 ,与此同时,进程1的优先度提升为进程2的优先度,继续执行,直到退出两个 临界区。 任务任务A申请共享资源申请共享资源S S是否被占用是否被占用 任务任务A获得共享资源获得共享资源S S被任务被任务B占用,任务占用,任务A被挂起被挂起 Priority(B)Priority( A)? 提升提升B的优先级到的优先级到PIP B释放资源释放资源s并恢复原来的优先级并恢复原来的优先级 等待队列等待队列中优先级最高的任务获得中优先

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论