μC/OSIII学习day8
介绍
信号量,互斥量
# 信号量
# 信号量的基本概念
- 信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源.在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持.
- 信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态.通常一个信号量的计数值用于对应有效的资源数,表示剩下可被占用的临界资源数,其值的含义分两种情况:
- 0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞的任务
- 正值:表示有一个或多个释放信号量操作
ps:μC/OS的信号量并没有二值信号量与计数信号量之分(下面是为了解释清楚信号量而已)
# 二值信号量
- 二值信号量既可以用于临界资源访问也可以用于同步功能.
- 二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别:互斥量有优先级继承机制,二值信号量则没有这个机制.这使得二值信号量更偏向应用于同步功(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的互斥访问.
- 用作同步时,信号量在创建后应被置为空,任务1获取信号量而进入阻塞,任务2在某种条件发生后,释放信号量,于是任务1获得信号量得以进入就绪态,如果任务1的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步.同样的,在中断服务函数中释放信号量,任务1也会得到信号量,从而达到任务与中断间的同步.
# 计数信号量
- 计数信号量用于计数,在实际的使用中,我们常将计数信号量用于事件计数与资源管理.每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加 1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减1),信号量的计数值则表示还有多少个事件没被处理.此外,系统还有很多资源,我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为0的时候任务就无法访问该资源了.
- 计数型信号量允许多个任务对其进行操作,但限制了任务的数量.我们的信号量在使用之后需要减一,当信号量为0时,后面的任务对资源的访问便无法进行,只有当我们释放了这个资源,后面的任务才能对这个资源进行访问.
# 信号量的使用场景
在嵌入式操作系统中二值信号量是任务间,任务与中断间同步的重要手段,信号量使用最多的一般都是二值信号量与互斥量.为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是0,信号量资源被释放,信号量值就是1,把这种只有0和1两种情况的信号量称之为二值信号量.
在多任务系统中,我们经常会使用这个二值信号量,比如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,但是这样子做,就会很消耗CPU资源并且妨碍其他任务执行,更好的做法是任务的大部分时间处于阻塞状态(允许其他任务执行),直到某些事件发生该任务才被唤醒去执行.可以使用二进制信号量实现这种同步,当任务取信号量时,因为此时尚未发生特定事件,信号量为空,任务会进入阻塞状态;当事件的条件满足后,任务/中断便会释放信号量,告知任务这个事件发生了,任务取得信号量便被唤醒去执行对应的操作,任务执行完毕并不需要归还信号量,这样子的CPU的效率可以大大提高,而且实时响应也是最快的.
某个任务使用信号量在等中断的标记的发生,在这之前任务已经进入了阻塞态,在等待着中断的发生,当在中断发生之后,释放一个信号量,也就是我们常说的标记,当它退出中断之后,操作系统会进行任务的调度,如果这个任务能够运行,系统就会去执行这个任务,这样子就大大提高了我们的效率.
二值信号量在任务与任务中同步的应用场景:假设我们有一个温湿度的传感器,假设是1s采集一次数据,那么我们让他在液晶屏中显示数据出来,这个周期也是要1s一次的,如果液晶屏刷新的周期是100ms更新一次,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在1s后温湿度数据更新的时候刷新即可,否则CPU就是白白做了多次的无效数据更新,CPU的资源就被刷新数据这个任务占用了大半,造成CPU资源浪费,如果液晶屏刷新的周期是10s更新一次,那么温湿度的数据都变化了10次,液晶屏才来更新数据,那拿这个产品有啥用,根本就是不准确的,所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费 CPU 的资源.
二值信号量在任务与中断同步的应用场景:我们在串口接收中,我们不知道啥时候有数据发送过来,有一个任务是做接收这些数据处理,总不能在任务中每时每刻都在任务查询有没有数据到来,那样会浪费CPU资源,所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来.
计数信号量用于资源统计,比如当前任务来了很多个消息,但是这些消息都放在缓冲区中,尚未处理,这时候就可以利用计数信号量对这些资源进行统计,每来一个消息就加一,每处理完个消息就减一,这样子系统就知道有多少资源未处理的
# 二值信号量运作机制
创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数,二值信号量的最大可用信号量个数为1.
二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待其他任务/中断释放信号量.在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中.
在二值信号量无效的时候,假如此时有任务获取该信号量的话,那么任务将进入阻塞状态.
假如某个时间中断/任务释放了信号量,其过程具体见图中断/任务释放信号量,那么,由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态.
# 计数信号量运作机制
计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任务的最大数目.访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量.这就是计数型信号量的运作机制,虽然计数信号量允许多个任务访问同一个资源,但是也有限定,比如某个资源限定只能有3个任务访问,那么第4个任务访问的时候,会因为获取不到信号量而进入阻塞,等到有任务(比如任务1)释放掉该资源的时候,第4个任务才能获取到信号量从而进行资源的访问.
# 信号量控制块
μC/OS的信号量由多个元素组成,在信号量被创建时,需要由我们自己定义信号量控制块(也可以称之为信号量句柄),因为它是用于保存信号量的一些信息的,其数据结构
OS_SEM
除了信号量必须的一些基本信息外,还有PendList
链表与Ctr
,为的是方便系统来管理信号量.struct os_sem{ OS_OBJ_TYPE Type; //信号量的类型 CPU_CHAR *NamePtr; //信号量的名字 OS_PEND_LIST PendList; //等待信号量的任务列表 #if OS_CFG_DBG_EN > 0u OS_SEM *DbgPrevPtr; OS_SEM *DbgNextPtr; CPU_CHAR *DbgNamePtr; #endif OS_SEM_CTR Ctr; //可用信号量的个数,如果为0则表示无可用信号量. CPU_TS TS; //用于记录时间戳 };
1
2
3
4
5
6
7
8
9
10
11
12
13
# 信号量函数接口
函数名称 | 函数作用 |
---|---|
OSSemCreate() | 创建信号量 |
OSSemDel() | 删除信号量 |
OSSemPost() | 释放信号量 |
OSSemPend() | 获取信号量 |
创建信号量函数
在定义完信号量结构体变量后就可以调用
OSSemCreate()
函数进行创建一个信号量.内核对象使用之前一定要先创建,这个创建过程必须要保证在所有可能使用内核对象的任务之前,所以一般我们都是在创建任务之前就创建好系统需要的内核对象(如信号量等).参数 含义 *p_sem 指向信号量变量的指针. *p_name 指向信号量变量名字字符串的指针. cnt 信号量的初始值,用作资源保护的信号量这个值通常跟资源的数量相同,用做标志事件发生的信号量这个值设置为0,标志事情还没有发生. *p_err 指向返回错误类型的指针. 错误类型 含义 OS_ERR_CREATE_ISR 在中断中创建信号量是不被允许的,返回错误. OS_ERR_ILLEGAL_CREATE_RUN_TIME 在定义OSSafetyCriticalStartFlag 为DEF_TRUE后就不运行创建任何内核对象. OS_ERR_NAME 参数p_name是个空指针. OS_ERR_OBJ_CREATED 信号量已经被创建. OS_ERR_OBJ_PTR_NULL 参数p_sem是个空指针. OS_ERR_OBJ_TYPE 参数p_sem被初始化为别的内核对象了. OS_ERR_NONE 无错误. void OSSemCreate(OS_SEM *p_sem, //信号量控制块指针 CPU_CHAR *p_name, //信号量名称 OS_SEM_CTR cnt, //资源数目或事件是否发生标志 OS_ERR *p_err){ CPU_SR_ALLOC();//使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变 //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存 SR) //,开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不继续执行 } #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 //如果启用(默认禁用)了安全关键 //如果是在调用 OSSafetyCriticalStart() 后创建该信号量 if (OSSafetyCriticalStartFlag == DEF_TRUE) { *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为"非法创建内核对象" return; //返回,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_CREATE_ISR; //错误类型为"在中断函数中创建对象" return; //返回,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测 if (p_sem == (OS_SEM *)0) //如果参数 p_sem 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"信号量对象为空" return; //返回,不继续执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_sem->Type = OS_OBJ_TYPE_SEM; //初始化信号量指标 p_sem->Ctr = cnt; p_sem->TS = (CPU_TS)0; p_sem->NamePtr = p_name; OS_PendListInit(&p_sem->PendList); //初始化该信号量的等待列表 #if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量 OS_SemDbgListAdd(p_sem); //将该定时添加到信号量双向调试链表 #endif OSSemQty++; //信号量个数加 1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57使用实例
OS_SEM SemOfKey; //标志 KEY1 是否被按下的信号量 /* 创建信号量 SemOfKey */ OSSemCreate((OS_SEM *)&SemOfKey, //指向信号量变量的指针 (CPU_CHAR *)"SemOfKey", //信号量的名字 (OS_SEM_CTR )0,//信号量这里是指示事件发生,所以赋值为 0,表示事件还没有发生 (OS_ERR *)&err); //错误类型
1
2
3
4
5
6
7
8信号量删除函数
信号量删除函数是根据信号量结构(信号量句柄)直接删除的,删除之后这个信号量的所有信息都会被系统清空,而且不能再次使用这个信号量了,但是需要注意的是,如果某个信号量没有被定义,那也是无法被删除的,如果有任务阻塞在该信号量上,那么尽量不要删除该信号量.想要使用互斥量删除函数就必须将
OS_CFG_SEM_DEL_EN
宏定义配置为1.参数 含义 *p_sem 指向信号量变量的指针. opt 删除信号量时候的选项. *p_err 指向返回错误类型的指针. 选项 功能 OS_OPT_DEL_NO_PEND 当信号量的等待列表上面没有相应的任务的时候才删除信号量. OS_OPT_DEL_ALWAYS 不管信号量的等待列表是否有相应的任务都删除信号量. 错误类型 含义 OS_ERR_DEL_ISR 企图在中断中删除信号量. OS_ERR_OBJ_PTR_NULL 参数p_sem是空指针. OS_ERR_OBJ_TYPE 参数p_sem指向的内核变量类型不是信号量. OS_ERR_OPT_INVALID opt在给出的选项之外. OS_ERR_TASK_WAITING 在选项opt是OS_OPT_DEL_NO_PEND的时候,并且信号量等待列表上有等待的任务. OS_ERR_NONE 无错误. #if OS_CFG_SEM_DEL_EN > 0u //如果启用了 OSSemDel() 函数 OS_OBJ_QTY OSSemDel(OS_SEM *p_sem, //信号量指针 OS_OPT opt, //选项 OS_ERR *p_err){ OS_OBJ_QTY cnt; OS_OBJ_QTY nbr_tasks; OS_PEND_DATA *p_pend_data; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_DEL_ISR; //返回错误类型为"在中断中删除" return ((OS_OBJ_QTY)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_sem == (OS_SEM *)0) //如果 p_sem 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为"内核对象为空" return ((OS_OBJ_QTY)0); //返回 0(有错误),不继续执行 } switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果选项在预期之内 case OS_OPT_DEL_ALWAYS: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为"选项非法" return ((OS_OBJ_QTY)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) //如果 p_sem 不是信号量类型 { *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为"内核对象类型错误" return ((OS_OBJ_QTY)0); //返回 0(有错误),不继续执行 } #endif CPU_CRITICAL_ENTER(); //关中断 p_pend_list = &p_sem->PendList; //获取信号量的等待列表到 p_pend_list cnt = p_pend_list->NbrEntries; //获取等待该信号量的任务数 nbr_tasks = cnt; switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果只在没有任务等待的情况下删除信号量 if (nbr_tasks == (OS_OBJ_QTY)0) //如果没有任务在等待该信号量 { #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_SemDbgListRemove(p_sem); //将该信号量从信号量调试列表移除 #endif OSSemQty--; //信号量数目减 1 OS_SemClr(p_sem); //清除信号量内容 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //返回错误类型为"无错误" } else {//如果有任务在等待该信号量 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TASK_WAITING;//返回错误类型为"有任务在等待该信号量" } break; case OS_OPT_DEL_ALWAYS: //如果必须删除信号量 OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并开中断 ts = OS_TS_GET(); //获取时间戳 while (cnt > 0u)//逐个移除该信号量等待列表中的任务 { p_pend_data = p_pend_list->HeadPtr; p_tcb = p_pend_data->TCBPtr; OS_PendObjDel((OS_PEND_OBJ *)((void *)p_sem), p_tcb, ts); cnt--; } #if OS_CFG_DBG_EN > 0u //如果启用了调试代码和变量 OS_SemDbgListRemove(p_sem);//将该信号量从信号量调试列表移除 #endif OSSemQty--; //信号量数目减 1 OS_SemClr(p_sem); //清除信号量内容 OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不进行调度 OSSched();//任务调度,执行最高优先级的就绪任务 *p_err = OS_ERR_NONE; //返回错误类型为"无错误" break; default: //如果选项超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为"选项非法" break; } return ((OS_OBJ_QTY)nbr_tasks);//返回删除信号量前等待其的任务数 } #endif
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105使用实例
OS_SEM SemOfKey;; //声明信号量 OS_ERR err; /* 删除信号量 sem*/ OSSemDel ((OS_SEM *)&SemOfKey, //指向信号量的指针 OS_OPT_DEL_NO_PEND, OS_ERR *)&err);
1
2
3
4
5
6
7
8
9信号量释放函数OSSemPost()
- 信号量的释放可以在任务,中断中使用.
- 当信号量有效的时候,任务才能获取信号量,什么函数可以使得信号量变得有效?有两个方式:
- 在创建的时候进行初始化,将它可用的信号量个数设置一个初始值;如果该信号量用作二值信号量,那么我们在创建信号量的时候其初始值的范围 0~1,假如初始值为 1 个可用的信号量的话,被获取一次就变得无效了,那就需要我们释放信号量,μC/OS 提供了信号量释放函数,每调用一次该函数就释放一个信号量.但是有个问题,能不能一直释放?很显然如果用作二值信号量的话,一直释放信号量就达不到同步或者互斥访问的效果,虽然说 μC/OS 的信号量是允许一直释放的,但是,信号量的范围还需我们用户自己根据需求进行决定,当用作二值信号量的时候,必须确保其可用值在 0~1 范围内;而用作计数信号量的话,其范围是由用户根据实际情况来决定的,在写代码的时候,要注意代码的严谨性.
参数 含义 *p_sem 指向要提交的信号量的指针. opt 发布信号时的选项,可能有以下几个选项. *p_err 指向返回错误类型的指针. 选项 功能 OS_OPT_POST_1 发布给信号量等待列表中优先级最高的任务. OS_OPT_POST_ALL 发布给信号量等待列表中所有的任务. OS_OPT_POST_NO_SCHED 提交信号量之后要不要进行任务调度,默认是要进行任务调度的,选择该选项可能的原因是想继续运行当前任务,因为发布信号量可能让那些等待信号量的任务就绪,这个选项没有进行任务调度,发布完信号量当前任务还是继续运行.当任务想发布多个信号量,最后同时调度的话也可以用这个选项.可以跟上面两个选项之一相与做为参数. 错误类型 含义 OS_ERR_SEM_OVF 信号量计数值已经达到最大范围了,这次提交会引起信号量计数值溢出. OS_ERR_NONE 无错误. OS_SEM_CTR OSSemPost(OS_SEM *p_sem, //信号量控制块指针 OS_OPT opt, //选项 OS_ERR *p_err){ OS_SEM_CTR ctr; CPU_TS ts; #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测功能 if (p_sem == (OS_SEM *)0) //如果 p_sem 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为"内核对象指针为空" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } switch (opt) //根据选项情况分类处理 { case OS_OPT_POST_1: //如果选项在预期内,不处理 case OS_OPT_POST_ALL: case OS_OPT_POST_1 | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED: break; default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为"选项非法" return ((OS_SEM_CTR)0u); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) //如果 p_sem 的类型不是信号量类型 { *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为"对象类型错误" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif ts = OS_TS_GET(); //获取时间戳 #if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_SEM,//将该信号量发布到中断消息队列 (void *)p_sem, (void *)0, (OS_MSG_SIZE)0, (OS_FLAGS )0, (OS_OPT )opt, (CPU_TS )ts, (OS_ERR *)p_err); return ((OS_SEM_CTR)0); //返回 0(尚未发布),不继续执行 } #endif ctr = OS_SemPost(p_sem, //将信号量按照普通方式处理 opt, ts, p_err); return (ctr); //返回信号的当前计数值 }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65使用实例
OS_SEM SemOfKey; //标志 KEY1 是否被按下的信号量 OSSemPost((OS_SEM *)&SemOfKey, //发布 SemOfKey (OS_OPT )OS_OPT_POST_ALL, //发布给所有等待任务 (OS_ERR *)&err); //返回错误类型
1
2
3
4
5信号量获取函数OSSemPend()
- 信号量的获取可以在任务中使用.
- 与释放信号量对应的是获取信号量,我们知道,当信号量有效的时候,任务才能获取信号量,当任务获取了某个信号量的时候,该信号量的可用个数就减一,当它减到0的时候,任务就无法再获取了,并且获取的任务会进入阻塞态(假如用户指定了阻塞超时时间的话).如果某个信号量中当前拥有1个可用的信号量的话,被获取一次就变得无效了,那么此时另外一个任务获取该信号量的时候,就会无法获取成功,该任务便会进入阻塞态,阻塞时间由用户指定.
- μC/OS支持系统中多个任务获取同一个信号量,假如信号量中已有多个任务在等待,那么这些任务会按照优先级顺序进行排列,如果信号量在释放的时候选择只释放给一个任务,那么在所有等待任务中最高优先级的任务优先获得信号量,而如果信号量在释放的时候选择释放给所有任务,则所有等待的任务都会获取到信号量.
参数 含义 *p_sem 指向要获取的信号量变量的指针. timeout 可能是以下几个选项之一. opt 这个参数是设置的是获取不到信号量的时候等待的时间.如果这个值为0,表示一直等待下去,如果这个值不为0,则最多等待timeout 个时钟节拍. *p_ts 指向等待的信号量被删除,等待被强制停止,等待超时等情况时的时间戳的指针. *p_err 指向返回错误类型的指针. 选项 功能 OS_OPT_PEND_BLOCKING 如果不能即刻获得信号量,选项表示要继续等待. OS_OPT_PEND_NON_BLOCKING 如果不能即刻获得信号量,选项表示不等待信号量. 错误 含义 OS_ERR_OBJ_DEL 信号量已经被删除了. OS_ERR_OBJ_PTR_NULL 输入的信号量变量指针是空类型. OS_ERR_OBJ_TYPE p_sem指向的变量内核对象类型不是信号量. OS_ERR_OPT_INVALID 参数opt不符合要求. OS_ERR_PEND_ABORT 等待过程,其他的任务调用了函数OSSemPendAbort强制取消等待. OS_ERR_PEND_ISR 企图在中断中等待信号量. OS_ERR_PEND_WOULD_BLOCK 开始获取不到信号量,且没有要求等待. OS_ERR_SCHED_LOCKED 调度器被锁住. OS_ERR_STATUS_INVALID 系统出错,导致任务控制块的元素PendStatus不在可能的范围内. OS_ERR_TIMEOUT 等待超时. OS_ERR_NONE 无错误. OS_SEM_CTR OSSemPend(OS_SEM *p_sem, //信号量指针 OS_TICK timeout, //等待超时时间 OS_OPT opt, //选项 CPU_TS *p_ts, //等到信号量时的时间戳 OS_ERR *p_err){ OS_SEM_CTR ctr; OS_PEND_DATA pend_data; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION();//执行安全检测异常函数 return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_PEND_ISR; //返回错误类型为"在中断中等待" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_sem == (OS_SEM *)0) //如果 p_sem 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为"内核对象为空" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } switch (opt) //根据选项分类处理 { case OS_OPT_PEND_BLOCKING: //如果选择"等待不到对象进行阻塞" case OS_OPT_PEND_NON_BLOCKING: //如果选择"等待不到对象不进行阻塞" break; //直接跳出,不处理 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为"选项非法" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) //如果 p_sem 不是信号量类型 { *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为"内核对象类型错误" return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } #endif if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS)0;//初始化(清零)p_ts,待用于返回时间戳 } CPU_CRITICAL_ENTER(); //关中断 if (p_sem->Ctr > (OS_SEM_CTR)0) //如果资源可用 { p_sem->Ctr--; //资源数目减 1 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_sem->TS; //获取该信号量最后一次发布的时间戳 } ctr = p_sem->Ctr; //获取信号量的当前资源数目 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //返回错误类型为"无错误" return (ctr);//返回信号量的当前资源数目,不继续执行 } if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0)//如果没有资源可用,而且选择了不阻塞任务 { ctr = p_sem->Ctr; //获取信号量的资源数目到 ctr CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_WOULD_BLOCK;//返回错误类型为"等待渴求阻塞" return (ctr);//返回信号量的当前资源数目,不继续执行 } else {//如果没有资源可用,但选择了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED;//返回错误类型为"调度器被锁" return ((OS_SEM_CTR)0);//返回 0(有错误),不继续执行 } } OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并重开中断 OS_Pend(&pend_data, //阻塞等待任务,将当前任务脱离就绪列表, (OS_PEND_OBJ *)((void *)p_sem), //并插入节拍列表和等待列表. OS_TASK_PEND_ON_SEM, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,但不进行调度 OSSched();//找到并调度最高优先级就绪任务 /* 当前任务(获得信号量)得以继续运行 */ CPU_CRITICAL_ENTER(); //关中断 switch (OSTCBCurPtr->PendStatus)//根据当前运行任务的等待状态分类处理 { case OS_STATUS_PEND_OK: //如果等待状态正常 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取信号被发布的时间戳 } *p_err = OS_ERR_NONE; //返回错误类型为"无错误" break; case OS_STATUS_PEND_ABORT: //如果等待被终止中止 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取等待被中止的时间戳 } *p_err = OS_ERR_PEND_ABORT; //返回错误类型为"等待被中止" break; case OS_STATUS_PEND_TIMEOUT: //如果等待超时 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //清零 p_ts } *p_err = OS_ERR_TIMEOUT; //返回错误类型为"等待超时" break; case OS_STATUS_PEND_DEL: //如果等待的内核对象被删除 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //获取内核对象被删除的时间戳 } *p_err = OS_ERR_OBJ_DEL;//返回错误类型为"等待对象被删除" break; default: //如果等待状态超出预期 *p_err = OS_ERR_STATUS_INVALID;//返回错误类型为"等待状态非法" CPU_CRITICAL_EXIT(); //开中断 return ((OS_SEM_CTR)0); //返回 0(有错误),不继续执行 } ctr = p_sem->Ctr; //获取信号量的当前资源数目 CPU_CRITICAL_EXIT(); //开中断 return (ctr); //返回信号量的当前资源数目 }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134使用实例
OSSemPend ((OS_SEM *)&SemOfKey, //等待该信号量被发布 (OS_TICK )0, //无期限等待 (OS_OPT )OS_OPT_PEND_BLOCKING, //如果没有信号量可用就等待 (CPU_TS *)&ts_sem_post, //获取信号量最后一次被发布的时间戳 (OS_ERR *)&err);
1
2
3
4
5
6
# 使用信号量的注意事项
- 信号量访问共享资源不会导致中断延迟.当任务在执行信号量所保护的共享资源时,ISR或高优先级任务可以抢占该任务.
- 应用中可以有任意个信号量用于保护共享资源.然而,推荐将信号量用于I/O端口的保护,而不是内存地址.
- 信号量经常会被过度使用.很多情况下,访问一个简短的共享资源时不推荐使用信号量,请求和释放信号量会消耗CPU时间.通过关/开中断能更有效地执行这些操作.假设两个任务共享一个32位的整数变量.第一个任务将这个整数变量加1,第二个任务将这个变量清零.考虑到执行这些操作用时很短,不需要使用信号量.执行这个操作前任务只需关中断,执行完毕后再开中断.但是若操作浮点数变量且处理器不支持硬件浮点操作时,就需要用到信号量.因为在这种情况下处理浮点数变量需较长时间.
- 信号量会导致一种严重的问题:优先级翻转.
# 信号量实验
在μC/OS中创建了两个任务,一个是获取信号量任务,一个是释放信号量任务,两个任务独立运行,获取信号量任务是一直在等待信号量,其等待时间是无期限等待,等到获取到信号量之后,任务开始执行任务代码,如此反复等待另外任务释放的信号量.
释放信号量任务在检测按键是否按下,如果按下则释放信号量,此时释放信号量会唤醒获取任务,获取任务开始运行,然后形成两个任务间的同步,LED 进行翻转,因为如果没按下按键,那么信号量就不会释放,只有当信号量释放的时候,获取信号量的任务才会被唤醒,如此一来就达到任务与任务的同步,同时程序的运行会在串口打印出相关信息.
声明信号量,并定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象
按下WKUP键时LED1闪烁.
ps:μC/OS的信号量并没有二值信号量与计数信号量之分,在程序设计时可以通过对任务函数的编写来实现二值信号和技术信号.
# 互斥量
# 互斥量的基本概念
- 互斥量又称互斥信号量(本质也是一种信号量,不具备传递数据功能),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权,递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理.任意时刻互斥量的状态只有两种,开锁或闭锁.当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权.当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权.当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有.持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁.
- 如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务间同步,但是互斥量更多的是用于保护资源的互锁.
- 用于互锁的互斥量可以充当保护资源的令牌,当一个任务希望访问某个资源时,它必须先获取令牌.当任务使用完资源后,必须还回令牌,以便其他任务可以访问该资源.是不是很熟悉,在我们的二值信号量里面也是一样的,用于保护临界资源,保证多任务的访问井然有序.当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源.但是信号量会导致的另一个潜在问题,那就是任务优先级翻转.而μC/OS提供的互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响,所以,用于临界资源的保护一般建议使用互斥量.
# 互斥量的优先级继承机制
- 在 μC/OS 操作系统中为了降低优先级翻转问题利用了优先级继承算法. 优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值. 因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占.
- 互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有. 也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同(防止优先级较低的任务在使用资源时被较高优先级的任务打断,导致更高优先级的任务要花费更长的时间来等待资源的释放),这个优先级提升的过程叫作优先级继承. 这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短(更高优先级任务的阻塞时间仅仅是低优先级占用资源时的执行时间),将已经出现的"优先级翻转"危害降低到最小.
# 互斥量使用场景
- 互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在.在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态.互斥量更适合于:
- 可能会引起优先级翻转的情况.
- 任务可能会多次获取互斥量的情况下,这样可以避免同一任务多次递归持有而造成死锁的问题.
- 多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问. 另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响
- 互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,而在中断的上下文环境中毫无意义
# 互斥量运作机制
多任务环境下会存在多个任务访问同一个临界资源的场景,该资源会被任务单独处理.其他任务在资源被占用的情况下不允许对该临界资源进行访问,这时候就需要用到μC/OS的互斥量来进行资源保护.
用互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量,而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性.
①:因为互斥量具有优先级继承机制,一般选择使用互斥量对资源进行保护,如果资源被占用的时候,无论是什么优先级的任务想要使用该资源都会被阻塞.
②:假如正在使用该资源的任务1比阻塞中的任务2的优先级还低,那么任务1将被系统临时提升到与高优先级任务2相等的优先级(任务1的优先级从L变成H).
③:当任务1使用完资源之后,释放互斥量,此时任务1的优先级会从H变回原来的L.
④-⑤:任务2此时可以获得互斥量,然后进行资源的访问,当任务2访问了资源的时候,该互斥量的状态又为闭锁状态,其他任务无法获取互斥量.
# 互斥量控制块
μC/OS的互斥量由多个元素组成,在互斥量被创建时,需要由我们自己定义互斥量(也可以称之为互斥量句柄),因为它是用于保存互斥量的一些信息的,其数据结构
OS_MUTEX
除了互斥量必须的一些基本信息外,还有指向任务控制块的指针OwnerTCBPtr
,任务优先级变量OwnerOriginalPrio
,PendList
链表与OwnerNestingCtr
变量等,为的是方便系统来管理互斥量.struct os_mutex { /* ------------------ GENERIC MEMBERS ------------------ */ OS_OBJ_TYPE Type; //互斥量的类型 CPU_CHAR *NamePtr; //互斥量的名字 OS_PEND_LIST PendList; //等待互斥量的任务代码 #if OS_CFG_DBG_EN > 0u OS_MUTEX *DbgPrevPtr; OS_MUTEX *DbgNextPtr; CPU_CHAR *DbgNamePtr; #endif /* ------------------ SPECIFIC MEMBERS ------------------ */ OS_TCB *OwnerTCBPtr; //指向持有互斥量任务控制块的指针,如果任务占用这个mutex,那么该变量OwnerTCBPtr会指向占用这个mutex的任务的OS_TCB. OS_PRIO OwnerOriginalPrio; //用于记录持有互斥量任务的优先级,如果任务占用这个mutex,那么该变量OwnerOriginalPrio中存放着任务的原优先级,当占用mutex任务的优先级被提升时就会用到这个变量. OS_NESTING_CTR OwnerNestingCtr; //表示互斥量是否可用,当该值为0的时候表示互斥量处于开锁状态,互斥量可用.μC/OS允许任务递归调用同一个mutex多达 256次,每递归调用一次mutex该值就会加一,但也需要释放相同次数才能真正释放掉这个mutex. CPU_TS TS; //mutex中的变量TS用于保存该mutex最后一次被释放的时间戳.当mutex被释放,读取时基计数值并存放到该变量中. };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19PS:用户代码不能直接访问这个结构体,必须通过μC/OS提供的API访问.
# 互斥量函数接口讲解
函数名称 | 函数作用 |
---|---|
OSMutexCreate() | 创建互斥量 |
OSMutexDel() | 删除互斥量 |
OSMutexPend() | 获取互斥量 |
OSMutexPost() | 释放互斥量 |
创建互斥量函数OSMutexCreate()
在定义完互斥量结构体变量后就可以调用 OSMutexCreate() 函数进行创建一个互斥量,跟信号量
的创建差不多,我们知道,其实这里的"创建互斥量"指的就是对内核对象(互斥量)的一些初
始化.要特别注意的是内核对象使用之前一定要先创建,这个创建过程必须要保证在所有可能使
用内核对象的任务之前,所以一般我们都是在创建任务之前就创建好系统需要的内核对象(如互
斥量等).
参数 作用 *p_mutex 指向mutex变量的指针. *p_name 指向mutex变量名字字符串的指针. *p_err 指向返回错误类型指针. 错误类型 含义 OS_ERR_CREATE_ISR 任务企图在中断中引用mutex创建函数. OS_ERR_CREATE_ISR 参数p_name是空指针. OS_ERR_OBJ_CREATED mutex变量已经被创建,但是OSMutexCreate函数中没有涉及这个错误的相关代码. OS_ERR_OBJ_PTR_NULL 变量p_mutex是空指针. OS_ERR_NONE 无错误. void OSMutexCreate(OS_MUTEX *p_mutex, //互斥量指针 CPU_CHAR *p_name, //取互斥量的名称 OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不继续执行 } #endif #ifdef OS_SAFETY_CRITICAL_IEC61508//如果启用(默认禁用)了安全关键 //如果是在调用 OSSafetyCriticalStart() 后创建 if (OSSafetyCriticalStartFlag == DEF_TRUE) { *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为"非法创建内核对象" return; //返回,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_CREATE_ISR; //错误类型为"在中断函数中定时" return; //返回,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用(默认启用)了参数检测 if (p_mutex == (OS_MUTEX *)0) //如果参数 p_mutex 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"创建对象为空" return; //返回,不继续执行 } #endif OS_CRITICAL_ENTER(); //进入临界段,初始化互斥量指标 //标记创建对象数据结构为互斥量 p_mutex->Type = OS_OBJ_TYPE_MUTEX; p_mutex->NamePtr = p_name; p_mutex->OwnerTCBPtr = (OS_TCB *)0; p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; p_mutex->TS = (CPU_TS )0; p_mutex->OwnerOriginalPrio = OS_CFG_PRIO_MAX; OS_PendListInit(&p_mutex->PendList); //初始化该互斥量的等待列表 #if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量 OS_MutexDbgListAdd(p_mutex); //将该互斥量添加到互斥量双向调试链表 #endif OSMutexQty++; //互斥量个数加 1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57互斥量创建函数的使用实例
OS_MUTEX mutex; //声明互斥量 /* 创建互斥量 mutex */ OSMutexCreate ((OS_MUTEX *)&mutex, //指向互斥量变量的指针 (CPU_CHAR *)"Mutex For Test", //互斥量的名字 (OS_ERR *)&err);
1
2
3
4
5
6删除互斥量函数OSMutexDel()
互斥量删除函数是根据互斥量结构(互斥量句柄)直接删除的,删除之后这个互斥量的所有信息都会被系统清空,而且不能再次使用这个互斥量了,但是需要注意的是,如果某个互斥量没有被定义,那也是无法被删除的,如果有任务阻塞在该互斥量上,那么尽量不要删除该互斥量.想要使用互斥量删除函数就必须将
OS_CFG_MUTEX_DEL_EN
宏定义配置为1.参数 作用 *p_mutex 指向mutex变量指针. opt 选项可分为以下两种. *p_err 指向返回错误类型的指针. 选项 功能 OS_OPT_DEL_NO_PEND 要在mutex等待列表上没有等待任务的时候才可以删除mutex. OS_OPT_DEL_ALWAYS 不管mutex等待列表上是否有等待的任务都直接删除mutex. 错误类型 含义 OS_ERR_TASK_WAITING 参数opt是OS_OPT_DEL_NO_PEND,但是mutex等待列表上有等待的任务. OS_ERR_STATE_INVALID 在还原拥有mutex任务优先级的时候是根据任务的状态来的,如果检测到任务的状态超出范围即返回这个错误. OS_ERR_DEL_ISR 从中断中调用删除函数. OS_ERR_NONE 无错误 #if OS_CFG_MUTEX_DEL_EN > 0u //如果启用了 OSMutexDel() OS_OBJ_QTY OSMutexDel(OS_MUTEX *p_mutex, //互斥量指针 OS_OPT opt, //选项 OS_ERR *p_err){ OS_OBJ_QTY cnt; OS_OBJ_QTY nbr_tasks; OS_PEND_DATA *p_pend_data; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; OS_TCB *p_tcb_owner; CPU_TS ts; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_DEL_ISR; //错误类型为"在中断中中止等待" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"对象为空" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果选项在预期内 case OS_OPT_DEL_ALWAYS: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 非互斥量类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型错误" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_pend_list = &p_mutex->PendList; //获取互斥量的等待列表 cnt = p_pend_list->NbrEntries; //获取等待该互斥量的任务数 nbr_tasks = cnt; switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果只在没任务等待时删除互斥量 if (nbr_tasks == (OS_OBJ_QTY)0) //如果没有任务在等待该互斥量 { #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_MutexDbgListRemove(p_mutex);//将该互斥量从互斥量调试列表移除 #endif OSMutexQty--; //互斥量数目减 1 OS_MutexClr(p_mutex); //清除互斥量内容 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_NONE; //错误类型为"无错误" } else {//如果有任务在等待该互斥量 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_TASK_WAITING; //错误类型为"有任务正在等待" } break; //跳出 case OS_OPT_DEL_ALWAYS: //如果必须删除互斥量 p_tcb_owner = p_mutex->OwnerTCBPtr; //获取互斥量持有任务 if ((p_tcb_owner != (OS_TCB *)0)&&(p_tcb_owner->Prio != p_mutex->OwnerOriginalPrio))//如果持有任务存在,而且优先级被提升过. { switch (p_tcb_owner->TaskState) //根据其任务状态处理 { case OS_TASK_STATE_RDY: //如果是就绪状态 OS_RdyListRemove(p_tcb_owner); //将任务从就绪列表移除 p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;//还原任务的优先级 OS_PrioInsert(p_tcb_owner->Prio); //将该优先级插入优先级表格 OS_RdyListInsertTail(p_tcb_owner); //将任务重插入就绪列表 break; //跳出 case OS_TASK_STATE_DLY: //如果是延时状态 case OS_TASK_STATE_SUSPENDED: //如果是被挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果是延时中被挂起状态 p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;//还原任务的优先级 break; case OS_TASK_STATE_PEND: //如果是无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果是有期限等待状态 case OS_TASK_STATE_PEND_SUSPENDED://如果是无期等待中被挂状态 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED://如果是有期等待中被挂状态 //改变任务在等待列表的位置 OS_PendListChangePrio(p_tcb_owner,p_mutex->OwnerOriginalPrio); break; default: //如果状态超出预期 OS_CRITICAL_EXIT(); *p_err = OS_ERR_STATE_INVALID; //错误类型为"任务状态非法" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } } ts = OS_TS_GET(); //获取时间戳 while(cnt > 0u) //移除该互斥量等待列表中的所有任务. { p_pend_data = p_pend_list->HeadPtr; p_tcb = p_pend_data->TCBPtr; OS_PendObjDel((OS_PEND_OBJ *)((void *)p_mutex), p_tcb, ts); cnt--; } #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_MutexDbgListRemove(p_mutex); //将互斥量从互斥量调试列表移除 #endif OSMutexQty--; //互斥量数目减 1 OS_MutexClr(p_mutex); //清除互斥量内容 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段,但不调度 OSSched(); //调度最高优先级任务运行 *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 default: //如果选项超出预期 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" break; //跳出 } return (nbr_tasks); //返回删除前互斥量等待列表中的任务数 } #endif
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138删除互斥量函数的使用实例
OS_SEM mutex;; //声明互斥量 OS_ERR err; /* 删除互斥量 mutex*/ OSMutexDel((OS_MUTEX *)&mutex, //指向互斥量的指针 OS_OPT_DEL_NO_PEND, (OS_ERR *)&err);
1
2
3
4
5
6
7
8获取互斥量函数OSMutexPend()
当互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其他任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权.任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级.
参数 作用 *p_mutex 指向mutex 变量指针. timeout 如果任务一开始获取不到信号量等待的节拍数.0表示无限期地等待. opt 选项有两种. *p_ts 指向等待的信号量被删除,等待被强制停止,等待超时等情况时的时间戳的指针.这个参数输入空指针表示不想获取时间戳. *p_err 指向返回错误类型的指针. 选项 功能 OS_OPT_PEND_BLOCKING 一开始获取不到信号量就阻塞任务,阻塞的时候根据参数timeout 决定. OS_OPT_PEND_NON_BLOCKING 一开始获取不到信号量就退出函数,继续运行任务. 错误类型 含义 OS_ERR_MUTEX_OWNER 信号量已经被占用. OS_ERR_PEND_ABORT 返回的时候不是因为获得mutex,而是被强制解除等待状态. OS_ERR_PEND_ISR 从中断中调用等待函数. OS_ERR_PEND_WOULD_BLOCK 没有获取到mutex,输入的参数选项是OS_OPT_PEND_NON_BLOCKING的时候. OS_ERR_SCHED_LOCKED 调度器被锁住了. OS_ERR_TIMEOUT 等待mutex超时了 OS_ERR_NONE 无错误 void OSMutexPend(OS_MUTEX *p_mutex, //互斥量指针 OS_TICK timeout, //超时时间(节拍) OS_OPT opt, //选项 CPU_TS *p_ts, //时间戳 OS_ERR *p_err){ OS_PEND_DATA pend_data; OS_TCB *p_tcb; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_PEND_ISR; //错误类型为"在中断中等待" return; //返回,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测 if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为"内核对象为空" return; //返回,不继续执行 } switch (opt) //根据选项分类处理 { case OS_OPT_PEND_BLOCKING: //如果选项在预期内 case OS_OPT_PEND_NON_BLOCKING: break; default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测 if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 非互斥量类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"内核对象类型错误" return; //返回,不继续执行 } #endif if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //初始化(清零)p_ts,待用于返回时间戳 } CPU_CRITICAL_ENTER(); //关中断 if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) //如果互斥量可用 { p_mutex->OwnerTCBPtr = OSTCBCurPtr; (7)//让当前任务持有互斥量 p_mutex->OwnerOriginalPrio = OSTCBCurPtr->Prio; //保存持有任务的优先级 p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1; //开始嵌套 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_mutex->TS; //返回互斥量的时间戳记录 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return; //返回,不继续执行 } /* 如果互斥量不可用 */ if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) //如果当前任务已经持有该互斥量 { p_mutex->OwnerNestingCtr++; //互斥量嵌套数加 1 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_mutex->TS; //返回互斥量的时间戳记录 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_MUTEX_OWNER; //错误类型为"任务已持有互斥量" return; //返回,不继续执行 } /* 如果当前任务非持有该互斥量 */ if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"渴求阻塞" return; //返回,不继续执行 } else {//如果选择了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return; //返回,不继续执行 } } /* 如果调度器未被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并重开中断 p_tcb = p_mutex->OwnerTCBPtr; //获取互斥量持有任务 if (p_tcb->Prio > OSTCBCurPtr->Prio) //如果持有任务优先级低于当前任务 { switch (p_tcb->TaskState) //根据持有任务的任务状态分类处理 { case OS_TASK_STATE_RDY: //如果是就绪状态 OS_RdyListRemove(p_tcb); //从就绪列表移除持有任务 p_tcb->Prio = OSTCBCurPtr->Prio; //提升持有任务的优先级到当前任务 OS_PrioInsert(p_tcb->Prio); //将该优先级插入优先级表格 OS_RdyListInsertHead(p_tcb); //将持有任务插入就绪列表 break; //跳出 case OS_TASK_STATE_DLY: //如果是延时状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果是延时中被挂起状态 case OS_TASK_STATE_SUSPENDED: //如果是被挂起状态 p_tcb->Prio = OSTCBCurPtr->Prio; //提升持有任务的优先级到当前任务 break; //跳出 case OS_TASK_STATE_PEND: //如果是无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果是有期限等待状态 case OS_TASK_STATE_PEND_SUSPENDED: //如果是无期限等待中被挂起状态 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果是有期限等待中被挂起状态 OS_PendListChangePrio(p_tcb,OSTCBCurPtr->Prio); //改变持有任务在等待列表的位置 break; //跳出 default: (24)//如果任务状态超出预期 OS_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_STATE_INVALID; //错误类型为"任务状态非法" return; //返回,不继续执行 } } /*阻塞任务,将当前任务脱离就绪列表,并插入节拍列表和等待列表.*/ OS_Pend(&pend_data, (OS_PEND_OBJ *)((void *)p_mutex), OS_TASK_PEND_ON_MUTEX, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,但不进行调度 OSSched(); (26)//调度最高优先级任务运行 CPU_CRITICAL_ENTER(); //开中断 switch (OSTCBCurPtr->PendStatus) //根据当前运行任务的等待状态分类处理 { case OS_STATUS_PEND_OK: //如果等待正常(获得互斥量) if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回互斥量最后一次被释放的时间戳 } *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 case OS_STATUS_PEND_ABORT: //如果等待被中止 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回等待被中止时的时间戳 } *p_err = OS_ERR_PEND_ABORT; //错误类型为"等待被中止" break; //跳出 case OS_STATUS_PEND_TIMEOUT: //如果超时内为获得互斥量 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //清零 p_ts } *p_err = OS_ERR_TIMEOUT; //错误类型为"超时" break; //跳出 case OS_STATUS_PEND_DEL: //如果互斥量已被删除 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回互斥量被删除时的时间戳 } *p_err = OS_ERR_OBJ_DEL; //错误类型为"对象被删除" break; //跳出 default: //根据等待状态超出预期 *p_err = OS_ERR_STATUS_INVALID;//错误类型为"状态非法" break; //跳出 } CPU_CRITICAL_EXIT(); //开中断 }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176PS:如果任务获取互斥量成功,那么在使用完毕需要立即释放,否则很容易造成其他任务无法获取互斥量,因为互斥量的优先级继承机制是只能将优先级危害降低,而不能完全消除.同时还需注意的是,互斥量是不允许在中断中操作的,因为互斥量特有的优先级继承机制在中断是毫无意义的.
互斥量获取函数的使用实例
OS_MUTEX mutex; //声明互斥量 OS_ERR err; OSMutexPend ((OS_MUTEX *)&mutex, //申请互斥量 mutex (OS_TICK )0, //无期限等待 (OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到互斥量就阻塞任务 (CPU_TS *)0, //不想获得时间戳 (OS_ERR *)&err);
1
2
3
4
5
6
7
8
9释放互斥量函数OSMutexPost()
- 任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问.在前面的讲解中,我们知道,当互斥量有效的时候,任务才能获取互斥量,那么,是什么函数使得互斥量变得有效呢?μC/OS给我们提供了互斥量释放函数
OSMutexPost()
,任务可以调用该函数进行释放互斥量,表示我已经用完了,别人可以申请使用,但是要注意的是,互斥量的释放只能在任务中,不允许在中断中释放互斥量. - 使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用
OSMutexPost()
函数时会释放一次互斥量,当互斥量的成员变量OwnerNestingCtr
为 0 的时候,互斥量状态才会成为开锁状态,等待获取该互斥量的任务将被唤醒.如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被完全释放后,任务的优先级将恢复为原本设定的优先级.
参数 作用 *p_mutex 指向Mutex变量的指针. opt 提交Mutex时的选项,可以为以下两种. *p_err 指向返回错误类型指针. 选项 功能 OS_OPT_POST_NO_SCHED 发布mutex后不要进行调度. OS_OPT_POST_NONE 这个是默认的选项,发布mutex后进行任务调度. 错误 含义 OS_ERR_MUTEX_NESTING Mutex被发布后,仍然处于嵌套中,即前面提到的元素OwnerNestingCtr还不为0,Mutex还不能为其他任务占有. OS_ERR_MUTEX_NOT_OWNER 只有拥有mutex的任务才可以释放这个mutex.当在一个不是拥有mutex 的任务中释放mutex的时候,返回这个错误. OS_ERR_OBJ_PTR_NULL 参数p_mutex是空指针. OS_ERR_OBJ_TYPE 参数p_mutex指向的内核变量类型不是mutex. OS_ERR_POST_ISR ISR不是任务,不可能拥有一个mutex,所以也不能在中断中提交mutex. OS_ERR_NONE 无错误. void OSMutexPost(OS_MUTEX *p_mutex, //互斥量指针 OS_OPT opt, //选项 OS_ERR *p_err){ OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; CPU_SR_ALLOC();//使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_POST_ISR; //错误类型为"在中断中等待" return; //返回,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"内核对象为空" return; //返回,不继续执行 } switch (opt) //根据选项分类处理 { case OS_OPT_POST_NONE: //如果选项在预期内,不处理 case OS_OPT_POST_NO_SCHED: break; default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u(7)//如果启用了对象类型检测 if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 的类型不是互斥量类型 { *p_err = OS_ERR_OBJ_TYPE; //返回,不继续执行 return; } #endif CPU_CRITICAL_ENTER(); //关中断 if(OSTCBCurPtr != p_mutex->OwnerTCBPtr) //如果当前运行任务不持有该互斥量 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_MUTEX_NOT_OWNER; //错误类型为"任务不持有该互斥量" return; //返回,不继续执行 } OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,开中断 ts = OS_TS_GET(); //获取时间戳 p_mutex->TS = ts; //存储互斥量最后一次被释放的时间戳 p_mutex->OwnerNestingCtr--; //互斥量的嵌套数减 1 if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) //如果互斥量仍被嵌套 { OS_CRITICAL_EXIT(); //解锁调度器 *p_err = OS_ERR_MUTEX_NESTING; //错误类型为"互斥量被嵌套" return; //返回,不继续执行 } /* 如果互斥量未被嵌套,已可用 */ p_pend_list = &p_mutex->PendList; //获取互斥量的等待列表 if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) //如果没有任务在等待该互斥量 { p_mutex->OwnerTCBPtr = (OS_TCB *)0; //清空互斥量持有者信息 p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; OS_CRITICAL_EXIT(); //解锁调度器 *p_err = OS_ERR_NONE; //错误类型为"无错误" return; //返回,不继续执行 } /* 如果有任务在等待该互斥量 */ if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio) //如果当前任务的优先级被改过 { OS_RdyListRemove(OSTCBCurPtr); //从就绪列表移除当前任务 OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio; //还原当前任务的优先级 OS_PrioInsert(OSTCBCurPtr->Prio); //在优先级表格插入这个优先级 OS_RdyListInsertTail(OSTCBCurPtr); //将当前任务插入就绪列表尾端 OSPrioCur = OSTCBCurPtr->Prio; //更改当前任务优先级变量的值 } p_tcb = p_pend_list->HeadPtr->TCBPtr; //获取等待列表的首端任务 p_mutex->OwnerTCBPtr = p_tcb; //将互斥量交给该任务 p_mutex->OwnerOriginalPrio = p_tcb->Prio; p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1; //开始嵌套 /* 释放互斥量给该任务 */ OS_Post((OS_PEND_OBJ *)((void *)p_mutex), (OS_TCB *)p_tcb, (void *)0, (OS_MSG_SIZE )0, (CPU_TS )ts); OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不执行任务调度 if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) //如果 opt 没选择"发布时不调度任务" { OSSched(); //任务调度 } *p_err = OS_ERR_NONE; //错误类型为"无错误" }
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109已经获取到互斥量的任务拥有互斥量的所有权,能重复获取同一个互斥量,但是任务获取了多少次互斥量就要释放多少次互斥量才能彻底释放掉互斥量,互斥量的状态才会变成开锁状态,否则在此之前互斥量都处于无效状态,别的任务就无法获取该互斥量.使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一次该互斥量,它的
OwnerNestingCtr
成员变量就减1.当该互斥量的OwnerNestingCtr
成员变量为0时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒.如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级.互斥量释放函数使用实例
OS_MUTEX mutex; //声明互斥互斥量 OS_ERR err; OSMutexPost ((OS_MUTEX *)&mutex, //释放互斥互斥量 mutex (OS_OPT )OS_OPT_POST_NONE, //进行任务调度 (OS_ERR *)&err);
1
2
3
4
5
6- 任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问.在前面的讲解中,我们知道,当互斥量有效的时候,任务才能获取互斥量,那么,是什么函数使得互斥量变得有效呢?μC/OS给我们提供了互斥量释放函数
# 实验
# 模拟优先级翻转实验
模拟优先级翻转实验是在μC/OS中创建了三个任务与一个二值信号量,任务分别是高优先级任务 AppTaskLed3
,中优先级任务AppTaskLed2
,低优先级任务AppTaskLed1
,用于模拟产生优先级翻转.低优先级任务在获取信号量的时候,被中优先级打断,中优先级的任务开始执行,因为低优先级还未释放信号量,那么高优先级任务就无法取得信号量继续运行,此时就发生了优先级翻转,任务在运行中,使用串口打印出相关信息.
声明信号量,并定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象
低优先级的LED1任务被较高优先级LED2打断,导致高优先级的LED3无法继续运行.
# 互斥量实验
互斥量实验是基于优先级翻转实验进行修改的,将信号量改为互斥量,目的是为了测试互斥量的优先级继承机制是否有效.
声明互斥信号量,并定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象
低优先级的LED1任务经优先级继承后并未被较高优先级的LED2任务打断.