ucos(四)共享资源保护
一、共享资源
- 关中断
- 禁止任务调度
- 使用信号量
- 使用互斥型信号量(互斥锁)

1、关/开中断
和CPU 相关的操作,其相关代码被放在与CPU相关的文件中(见CPU.H)。uC/OS-III 中与CPU相关的模块叫做uC/CPU。每种架构的CPU 都需要设置相适应的uC/CPU 文件。
只有这种方法才能任务和中断程序共享资源。只要关中断的时间不比系统本身的关中断时间长,就不会影响到系统的中断延时。
2、锁调度器
如果任务不需要和ISR 共享资源,就可以通过锁调度器来访问共享资源。
实际上就是禁止任务调度已达到资源的独占

注意,只有调度器被锁,中断是使能的,如果在处理临界段时中断发生,ISR程序就会被执行。在ISR 的末尾,uC/OS-III会返回原任务(即使ISR中有高优先级任务被就绪)。
支持嵌套250 级。当OSSchedUnlock()与OSSchedLock()被调用的次数相同时调度器才被锁调度器影响了抢占式内核的初衷,内核行为实际与不可剥夺的内核已经是一样的了。
3、信号量
一种上锁机制,代码需要获取对应的钥匙才能访问共享资源(继续执行),我的理解是实际上只是对访问共享资源区的代码段进行上锁,如果没有获取到钥匙(信号量)则等待无法继续执行,只有别的任务施放了信号量之后才能继续执行下去。
二进制信号量和计数型信号量:
二进制信号量只能取0和1两个值,计数型信号量的信号量值大于1,计数型信号量的范围由OS_SEM_CTR决定,OS_SEM_CTR可以为8位,16位和32位,取值范围分别为:0~255,0~65535和0~4294967295。
二值信号量用于那些一次只能一个任务使用的资源,比如I/O设备,打印机,数型信号量用于某些资源可以同时被几个任务所使用,比如一个缓存池有10个缓存块,那么同时最多可以支持10个任务来使用内存池。应用中可以使用任意数量的信号量,但是一般用于IO保护,很多情况下,访问一个简短的共享资源时不推荐使用信号量,请求和释放信号量会消耗CPU时间。
(1)定义一个信号量
1 | OS_SEM XXX |
(2)创建一个信号量
1 2 3 4 | void OSSemCreate (OS_SEM *p_sem, //信号量结构体,其中OS_SEM_CTR Ctr表示信号量的值 CPU_CHAR *p_name, //名字 OS_SEM_CTR cnt, //信号量初始值 OS_ERR *p_err) |
(3)删除一个信号量
1 2 3 4 5 | OS_OBJ_QTY OSSemDel(OS_SEM *p_sem, OS_OPT opt, //1.NO_PEND 仅当没有信号请求的时候才删除 //2.ALWAYS 直接删除,不管有没有信号请求 OS_ERR *p_err) |
(4)请求/等待一个信号量
1 2 3 4 5 6 7 | OS_SEM_CTR OSSemPend(OS_SEM *p_sem, OS_TICK timeout, //等待的时间,如果为0:一直等待下去 OS_OPT opt, //1.暂时无效直接挂起 OS_OPT_PEND_BLOCKING //2.无效直接返回OS_OPT_PEND_NON_BLOCKING CPU_TS *p_ts, //时间戳:记录接受信号量的时刻,0或者null则不需要时间戳。 OS_ERR *p_err) |
(5)取消等待
1 2 3 4 5 6 | OS_OBJ_QTY OSSemPendAbort(OS_SEM *p_sem, OS_OPT opt, //1. 仅终止等待该信号量的最高优先级任务 //2. 中止所有的等待该信号量的任务 //3. 禁止任务调度 OS_ERR *p_err) |
(6)释放一个信号量
1 2 3 4 5 6 | OS_SEM_CTR OSSemPost(OS_SEM *p_sem, OS_OPT opt, //1. 仅发送给等待该信号量的最高优先级任务 //2. 发送给所有的等待该信号量的任务 //3. 禁止任务调度 OS_ERR *p_err) |
(7)步骤:
- 先定义OS_SEM。
- Create创建信号量.
- OSSemPend等待信号量.
- OSSemPost释放信号量.
4、互斥信号量
使用信号量访问共享资源会有优先级反转问题:高优先级A任务执行访问共享资源的时候被迫等待低优先级任务D释放信号量,此时会发生任务切换到低优先级。如果此时又有高一级的任务B中断了D的任务,虽然B任务比A任务优先级更低,也会发生任务调度。使得高优先级任务A实际上被拉到占用信号量的低优先级任务D同一优先级别,违背了实时RTOS。
互斥信号量实际解决方法:将低优先级任务D的优先级临时提升至任务A同一优先级,等信号量释放后,再将任务D的优先级恢复,这样任务切换只在这两个任务之间执行(如果有比A更高的还是会切换)。
(1)创建互斥信号量
1 2 3 | void OSMutexCreate(OS_MUTEX *p_mutex, //指向互斥信号量的控制块 CPU_CHAR *p_name, //互斥信号量名字 OS_ERR *p_err) //调用此函数后返回的错误码 |
(2)请求/等待互斥信号量
1 2 3 4 5 6 7 | void OSMutexPend(OS_MUTEX *p_mutex, //指向互斥信号量的控制块 OS_TICK timeout, //指定等待互斥信号量的超时节拍,超时继续执行,0位一直等待 OS_OPT opt, //是否阻塞 //OS_OPT_PEND_BLOCKING无效挂起 //OS_OPT_PEND_NON_BLOCKING无效直接返回 CPU_TS *p_ts, //时间戳 OS_ERR *p_err) //错误码 |
(3)释放互斥信号量
1 2 3 4 5 6 7 | void OSMutexPost(OS_MUTEX *p_mutex, //指向互斥信号量 OS_OPT opt, //指定是否进行任务调度操作 //OS_OPT_POST_NONE不指定特定的选项 //OS_OPT_POST_NO_SCHED 禁止在本函数内执行任务调度 OS_ERR *p_err) //错误码 |
(4)步骤:
- 先定义OS_MUTEX xxxx
- Create创建信号量
- OSMutexPend等待信号量
- OSMutexPost释放信号量
5、死锁
任务1需要的资源要等到任务2释放,任务2的资源需要等待任务1释放,造成死锁。
(1)假定任务T1 所等待的事情发生,任务1被执行。
(2)任务T1 申请M1。(mutex 1)
(3)任务T1 访问资源R1。(7)任务T2 申请M1,但是M1 已被任务T1 占用。任务T2被挂起。
(8)uC/OS-III 切换到任务T1。
(9)此时任务T1 申请M2,但是M2 已经被任务T2 占用。
此时,两个任务互相等待,这就算死锁。
用以下方式防止死锁:
- 同一个时间不要申请多于一个mutex
- 不要直接地申请mutex(该申请放到器件驱动中和可重入函数中)
- 在处理之前先获得全部所需要的mutex
- 任务间以同样的顺序申请资源
当申请信号量或mutex 时允许设置时间期限,这样能防止死锁,但是同样的死锁可能稍后再次出现。
二、选择题
1.以下请问使用( A )方式保护最为合适。
1 2 3 4 5 6 7 8 9 | void T1( void *parg) { while (1) { ........... 喂狗 ........... } } |
2.以下请问使用( B )方式保护最为合适。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | uint32 g=0; void T1( void *parg) { while (1) { ........... g++; ........... } } void T2( void *parg) { uint32_t a=0; while (1) { ........... a = g; ........... } } |
3.以下请问使用( A )方式保护最为合适。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | uint32 g=0; void T1( void *parg) { while (1) { ........... g++; ........... } } void T2( void *parg) { uint32_t a=0; while (1) { ........... a = g; ........... } } void USART1_IRQHandler( void ) { ........... g = USART_ReceiveData(USART1); ........... } |
4.以下请问使用( C )方式保护最为合适。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void T1( void *parg) { while (1) { ........... printf ( "hello teacher.wen\r\n" ); ........... } } void T2( void *parg) { while (1) { ........... printf ( "good bye teacher.wen\r\n" ); ........... } } |
5.以下请问使用( D )方式保护最为合适。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | void T1( void *parg) { while (1) { ........... printf ( "hello teacher.wen\r\n" ); ........... } } void T2( void *parg) { while (1) { ........... printf ( "good bye teacher.wen\r\n" ); ........... } } void T3( void *parg) { while (1) { ........... printf ( "he's teacher.wen\r\n" ); ........... } } |
三、辨析题
1.请分析以下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void T1( void *parg) { while (1) { 调度器上锁 printf ( "hello teacher.wen\r\n" ); delay_ms(1000) 调度器解锁 } } void T2( void *parg) { while (1) { 调度器上锁 printf ( "good bye teacher.wen\r\n" ); delay_ms(1000) 调度器解锁 } } |
2.请分析以下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void T1( void *parg) { while (1) { 等待互斥锁 printf ( "hello teacher.wen\r\n" ); OLED_ShowString(0,2, "this is task 1\r\n" ); 释放互斥锁 } } void T2( void *parg) { uint8_t buf[5]={0}; while (1) { 等待互斥锁 printf ( "good bye teacher.wen\r\n" ); dht11_read(buf) ...... 释放互斥锁 } } |
3.请分析以下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void T1( void *parg) { while (1) { 等待互斥锁 printf ( "hello teacher.wen\r\n" ); OLED_ShowString(0,2, "this is task 1\r\n" ); 释放互斥锁 } } void T2( void *parg) { uint8_t buf[5]={0}; while (1) { 等待互斥锁 printf ( "good bye teacher.wen\r\n" ); dht11_read(buf) ...... 释放互斥锁 } } |
4.请分析以下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void app_task_init( void *parg) { 创建信号量,初值为0 } void T1( void *parg) { while (1) { 等待信号量 printf ( "hello teacher.wen\r\n" ); 释放信号量 } } void T2( void *parg) { while (1) { 等待信号量 printf ( "good bye teacher.wen\r\n" ); 释放信号量 } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2019-08-15 单片机时钟电路