这节总结操作系统UCOSIII的内核对象------->信号量
1.计数型信号量和二值信号量
那信号量在操作系统的作用是什么?
- 任务与任务之间的同步,即一个任务的发送触发了另一个任务发生;
或者是一个任务与多个任务之间的同步即一个任务的发生触发了多个任务的运行,而运行的顺序按照任务的优先级一个一个执行,这个是主要作用 - 共享资源管理
那信号量是什么,其实是一个结构体
struct os_sem { /* Semaphore /
OS_OBJ_TYPE Type; / 存储内核类型 */
CPU_CHAR NamePtr; / 内核名字 /
OS_PEND_LIST PendList; / 等待列表 /
OS_SEM_CTR Ctr; / 信号量计数值,代表有多少个信号量 */
CPU_TS TS;
};
那么要使用信号量先要创建一个信号量,但是之前要使宏定义 OS_CFG_SEM_EN 置一,那么信号量所有的函数才有效
先创建一个信号量 函数为 OSSemCreate();
void OSSemCreate (OS_SEM *p_sem, //信号量结构体
CPU_CHAR *p_name, //信号量名字
OS_SEM_CTR cnt, //信号量初始值
OS_ERR *p_err) //返回的错误值
由于信号量初始值的不同分为两种 cnt=1为二值信号量 即信号量的值都在0和1之间变换的,它用于任务之间的同步比较多
cnt大于1位计数型信号量,信号量计数值在0-计数值最大值之前变换一般用于共享资源管理
那么信号量创建函数干了什么看代码
void OSSemCreate (OS_SEM *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
OS_ERR *p_err)
{
CPU_SR_ALLOC();
//参数检查代码省略
#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* 信号量结构体没有定义了,为空则创建信号量失败 */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
#endif
OS_CRITICAL_ENTER(); //进入临界段
//信号量结构体的参数赋值
p_sem->Type = OS_OBJ_TYPE_SEM; /* Mark the data structure as a semaphore */
p_sem->Ctr = cnt; /*设置信号量初始值 */
p_sem->TS = (CPU_TS)0;
p_sem->NamePtr = p_name; /* Save the name of the semaphore */
OS_PendListInit(&p_sem->PendList); /* 信号量的等待列表的初始化 */
OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段
*p_err = OS_ERR_NONE;
}
在初始化过程中发现上面有一个等待列表初始化,等待列表是什么
其实就是一个结构体
struct os_pend_list {
OS_PEND_DATA *HeadPtr;
OS_PEND_DATA *TailPtr;
OS_OBJ_QTY NbrEntries;
};
初始化就是将里面参数清0 看代码
void OS_PendListInit (OS_PEND_LIST *p_pend_list)
{
p_pend_list->HeadPtr = (OS_PEND_DATA *)0;
p_pend_list->TailPtr = (OS_PEND_DATA *)0;
p_pend_list->NbrEntries = (OS_OBJ_QTY )0;
}
那么这个等待列表结构体作用即:
当一个任务需要等待信号量的时候,它需要获取信号量,但是发现没有信号量即 计数值Ctr=0,那么要将任务暂时挂起来同时任务停止运行,那么此时就是将任务挂在等待列表,直到有任务发布信号量,此时会从等待列表里面移除该任务,同时将任务从等待态变成就绪态,就可以重新运行该任务
那任务怎么挂上等待列表
是运用双向链表的节点插入和删除,等待列表的 *HeadPtr 指针指向链表第一个节点,而 *TailPtr 指向链表的尾部,指向链表的最后一个节点,这样是方便可以从这两个指针获得链表的首部或尾部,从而在两个方向历遍整个双向链表
可是你又看见等待列表挂载的指针是数据类型是 OS_PEND_DATA 不是任务控制块,
怎么回事呢
那必然是这种数据类型是结构体,它里面有任务控制块的指针了
看结构体
typedef struct os_pend_data OS_PEND_DATA;
struct os_pend_data {
OS_PEND_DATA *PrevPtr;
OS_PEND_DATA *NextPtr; //这两个指针肯定是用来对等待列表的插入和删除,组成双向链表
OS_TCB *TCBPtr; //任务控制块指针
OS_PEND_OBJ *PendObjPtr; //存储内核对象的指针现在内核是信号量存的肯定是信号量结构体
OS_PEND_OBJ *RdyObjPtr;
void *RdyMsgPtr;
OS_MSG_SIZE RdyMsgSize;
CPU_TS RdyTS;
};
从上述看等待列表其实就相当于根节点,它下面挂载的子节点数据类型都是 OS_PEND_DATA
那么插入等待列表的原则是
根据任务的优先级,优先级越高的插在最前面,但是优先级越高它优先级数越小,所以整个等待列表是升序插入的
插入完的情况看图
等待列表的节点的插入实现过程看代码
/*等待列表节点插入函数*/
void OS_PendListInsertPrio (OS_PEND_LIST *p_pend_list,
OS_PEND_DATA *p_pend_data)
{
OS_PRIO prio;
OS_TCB *p_tcb;
OS_TCB *p_tcb_next;
OS_PEND_DATA *p_pend_data_prev;
OS_PEND_DATA *p_pend_data_next;
p_tcb = p_pend_data->TCBPtr; /*从等待列表中取出任务控制块首地址*/
prio = p_tcb->Prio; /*从任务控制块中获取任务优先级*/
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { /*等待列表元素为0表示该等待列表没有挂 */
p_pend_list->NbrEntries = (OS_OBJ_QTY)1; /*NbrEntries=1,表示一个任务挂在等待列表 */
p_pend_data->NextPtr = (OS_PEND_DATA *)0; /* 没有其他数据挂在等待列表 */
p_pend_data->PrevPtr = (OS_PEND_DATA *)0;
p_pend_list->HeadPtr = p_pend_data; /* */
p_pend_list->TailPtr = p_pend_data;
}
else { //等待列表非空
p_pend_list->NbrEntries++; /*等待列表元素加1即等待任务自增1 */
p_pend_data_next = p_pend_list->HeadPtr; //p_pend_data_next指向等待列表的首地址
while (p_pend_data_next != (OS_PEND_DATA *)0) { /* 找到那个可以插入的节点而p_pend_data_next为空作为退出循环的条件 */
p_tcb_next = p_pend_data_next->TCBPtr; //p_tcb_next指向等待列表下面挂的OS_PEND_DATA数据类型的任务控制块指针这个成员
if (prio < p_tcb_next->Prio) { //当前任务的优先级比等待列表的某一个任务优先级数低就退出循环
break; /*退出循环 */
} else { //反之当前任务比等待列表节点的任务优先级数高
p_pend_data_next = p_pend_data_next->NextPtr; /*找等待列表下一个节点再与当前任务比较优先级*/
}
}
if (p_pend_data_next == (OS_PEND_DATA *)0) { /*p_pend_data_next为空表示当前任务比等待列表上面挂的任务优先级还要低,所以应该插入到链表尾节点后面 注优先级数越大优先级越低 */
p_pend_data->NextPtr = (OS_PEND_DATA *)0;
p_pend_data_prev = p_pend_list->TailPtr;
p_pend_data->PrevPtr = p_pend_data_prev;
p_pend_data_prev->NextPtr = p_pend_data;
p_pend_list->TailPtr = p_pend_data;
}
else { //p_pend_data_next非空表示找到了要插入节点的地方,而不是尾结点后面
if (p_pend_data_next->PrevPtr == (OS_PEND_DATA *)0) { /*p_pend_data_next的前驱为空则表示当前任务插入到第一个节点的前面 Is new TCB highest priority? */
p_pend_data_next->PrevPtr = p_pend_data;
p_pend_data->PrevPtr = (OS_PEND_DATA *)0;
p_pend_data->NextPtr = p_pend_data_next;
p_pend_list->HeadPtr = p_pend_data;
}
else { //前驱非空则当前任务插入到等待列表两个链表节点的之间,插入的数据类型为OS_PEND_DATA型
p_pend_data_prev = p_pend_data_next->PrevPtr;
p_pend_data->PrevPtr = p_pend_data_prev;
p_pend_data->NextPtr = p_pend_data_next;
p_pend_data_prev->NextPtr = p_pend_data;
p_pend_data_next->PrevPtr = p_pend_data;
}
}
}
}
等待列表的节点的删除实现过程看代码
/*等待列表节点删除函数*/
void OS_PendListRemove (OS_TCB *p_tcb)
{
OS_OBJ_QTY n_pend_list; /* 记录等待列表任务的数量的变量 */
OS_PEND_DATA *p_pend_data;
OS_PEND_LIST *p_pend_list;
OS_PEND_OBJ *p_obj;
p_pend_data = p_tcb->PendDataTblPtr; /* PendDataTblPtr 在任务控制块这个指针是存储等待列表挂载的数据 OS_PEND_DATA结构的指针 */
n_pend_list = p_tcb->PendDataTblEntries; /* PendDataTblEntries=1 没有其他值在等待信号量会提到 */
while (n_pend_list > (OS_OBJ_QTY)0) {
p_obj = p_pend_data->PendObjPtr; /* 从OS_PEND_DATA数据类型里面取出内核对象指针,PendObjPtr 现在存储的是信号量指针 */
p_pend_list = &p_obj->PendList; /* 从信号量指针哪里取出等待列表指针 */
OS_PendListRemove1(p_pend_list, /* 这个函数是从等待列表移出任务的函数,即节点删除 */
p_pend_data);
p_pend_data++;
n_pend_list--;
}
p_tcb->PendDataTblEntries = (OS_OBJ_QTY )0;
p_tcb->PendDataTblPtr = (OS_PEND_DATA *)0;
}
/*等待列表节点删除函数调用的真正实现节点删除函数*/
void OS_PendListRemove1 (OS_PEND_LIST *p_pend_list,
OS_PEND_DATA *p_pend_data)
{
OS_PEND_DATA *p_prev;
OS_PEND_DATA *p_next;
if (p_pend_list->NbrEntries == 1u) { /* 等待列表只有一个任务 */
p_pend_list->HeadPtr = (OS_PEND_DATA *)0;
p_pend_list->TailPtr = (OS_PEND_DATA *)0;
} else if (p_pend_data->PrevPtr == (OS_PEND_DATA *)0) { /* 现在删除的是等待列表的第一个节点 */
p_next = p_pend_data->NextPtr;
p_next->PrevPtr = (OS_PEND_DATA *)0;
p_pend_list->HeadPtr = p_next;
} else if (p_pend_data->NextPtr == (OS_PEND_DATA *)0) { /* 现在删除的是等待列表最后一个节点 */
p_prev = p_pend_data->PrevPtr;
p_prev->NextPtr = (OS_PEND_DATA *)0;
p_pend_list->TailPtr = p_prev;
} else { /* 现在删除的是前驱节点和后继节点都不是空的节点 */
p_prev = p_pend_data->PrevPtr;
p_next = p_pend_data->NextPtr;
p_prev->NextPtr = p_next;
p_next->PrevPtr = p_prev;
}
p_pend_list->NbrEntries--; /* 等待列表任务数量减一 */
/* 节点删除是这里它是前驱和后继节点都没有指向 */
p_pend_data->NextPtr = (OS_PEND_DATA *)0;
p_pend_data->PrevPtr = (OS_PEND_DATA *)0;
}
在清楚的了解等待列表之后下面 对信号量的发送和等待的机理比较容易理解了
1等待信号量函数 OSSemPend()
OSSemPend ( OS_SEM p_sem, / 信号量结构体指针 /
OS_TICK timeout, / 等待信号量需要的节拍数 /
OS_OPT opt, / 等待信号量选项 */
CPU_TS p_ts, / 时间戳 */
OS_ERR p_err) / 返回的错误值 */
根据 opt 与 timeout 的结合可以有多种等待信号量的方式
Opt
OS_OPT_PEND_BLOCKING 就是指定信号量无效时,任务挂起以等待信号量
OS_OPT_PEND_NON_BLOCKING 信号量无效时任务直接返回,不挂起任务,继续执行当前任务
1 timeout=0 + Opt=OS_OPT_PEND_BLOCKING 不能立即收到信号量,任务挂起以等待信号量并阻塞任务 ,直到等到信号量就解挂 ,要是等待信号量马上就可以获取到信号量是不阻塞任务的
2 timeout>0 + Opt=OS_OPT_PEND_BLOCKING 要是等待信号量马上就可以获取到信号量是不阻塞任务的,要是不能立即收到信号量,任务挂起以等待信号量并阻塞任务,在等待完 timeout 个节拍数还没有收到信号量就解挂,继续运行任务,如果在这些节拍数只能收到信号量也解挂
3 timeout不管是多少 + Opt=OS_OPT_PEND_NON_BLOCKING 不管能不能立即收到信号量,任务都不堵塞不挂起,继续运行
注意:该函数不能在中断里面使用,该函数存在阻塞因素,而中断是不能注意长时间阻塞的,否则会进入硬件中断程序跑飞了”
看代码就知道为什么会有这些功能
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();
/* 参数检查代码省略 */
//临界区代码保护
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; /* 获取该信号量最后一次发布的时间戳 get timestamp of last post */
}
ctr = p_sem->Ctr; //获取信号量的当前资源数目
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_NONE; //返回错误类型为“无错误”
return (ctr);
}
/* 无信号量的情况 */
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /*如果没有资源可用,而且选择了不堵塞任务 Caller wants to block if not available? */
ctr = p_sem->Ctr; /*获取信号量的资源数目到 ctr 此时已经没有资源了crt=0 No */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK; //返回错误类型为“等待渴求堵塞”字面意思它希望你一直等待给阿信号量 不要就是没有信号量就不执行该任务
return (ctr);
} else { /* Yes 如果没有资源可用,但选择了堵塞任务 */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /*如果调度器被锁 Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED; //返回错误类型为“调度器被锁” 即无法任务调度
return ((OS_SEM_CTR)0);
}
}
OS_CRITICAL_ENTER_CPU_CRITICAL_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: /* 等待OK,获取到信号量 */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;
case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;
case OS_STATUS_PEND_TIMEOUT: /* 有限等待情况下没有收到信号量 */
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;
case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
if (p_ts != (CPU_TS *)0) {
*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);
}
ctr = p_sem->Ctr; /* 返回信号量*/
CPU_CRITICAL_EXIT();
return (ctr);
}
从 发送信号量函数OSSemPend() 函数看一旦开始任务阻塞要插入等待列表的准备工作在 OS_Pend()中运行
解析一下该函数看代码
void OS_Pend (OS_PEND_DATA *p_pend_data, //待插入等待列表的元素
OS_PEND_OBJ *p_obj, //等待的内核对象值
OS_STATE pending_on, //等待哪种对象内核
OS_TICK timeout)
{
OS_PEND_LIST *p_pend_list;
OSTCBCurPtr->PendOn = pending_on; /*等待的对象是信号量 */
OSTCBCurPtr->PendStatus = OS_STATUS_PEND_OK; //任务的等待状态为OS_STATUS_PEND_OK
OS_TaskBlock(OSTCBCurPtr, /*任务有限等待(timeout不为0)需要将任务控制块插入时钟节拍列表 */
timeout);
if (p_obj != (OS_PEND_OBJ *)0) { /*等待内核对象非空 */
p_pend_list = &p_obj->PendList; /*获取对象的等待列表 */
p_pend_data->PendObjPtr = p_obj; /*保存要等待的内核对象,以后可以通过该指针获取到等待列表 */
OS_PendDataInit((OS_TCB *)OSTCBCurPtr, /*初始化p_pend_data这个数据结构的成员 */
(OS_PEND_DATA *)p_pend_data,
(OS_OBJ_QTY )1);
OS_PendListInsertPrio(p_pend_list, /*按优先级大小将 p_pend_data 插入到等待列表*/
p_pend_data);
} else { //如果等待对象为空
OSTCBCurPtr->PendDataTblEntries = (OS_OBJ_QTY )0;
OSTCBCurPtr->PendDataTblPtr = (OS_PEND_DATA *)0;
}
}
在总结一下等待信号量的流程
- 如果在等待信号量的时候 发现有信号量即 信号量结构体 里面的 Ctr大于0,那么可以跳出 OSSemPend()函数,然后接着执行任务函数里面的代码
- 如果在等待信号量的时候 发现没有信号量即 信号量结构体 里面的 Ctr等于0,且任务选择无限时间等待信号量即 timeout=0 + Opt=OS_OPT_PEND_BLOCKING ,那么要将该任务插入等待列表,任务有就绪态变成等待态,然后停止运行,直到有任务发送信号量才可以继续执行任务函数; 当然如果没有信号量有选择有限时间等待信号量即 timeout>0 + Opt=OS_OPT_PEND_BLOCKING 那么那么要将该任务插入等待列表同时还有在时钟节拍列表插入该任务,当等待时间到了如果没有收到信号量就会在时钟节拍任务里面将该任务解挂,变回就绪态继续执行任务函数
2解析一下发送信号量函数OSSemPost()
OSSemPost (OS_SEM p_sem, / 信号量结构体指针 /
OS_OPT opt, / 发送信号量选项 */
OS_ERR p_err) ; / 返回的错误值 */
根据 opt 可以有多种发送信号量的方式
Opt
OS_OPT_POST_ALL 给等待同一个信号量的所有任务都发送信号量,让这些任务从等待态全部变成就绪态,用于多任务同步
OS_OPT_POST_1 给当前等待信号量的任务优先级最高的任务发送一个信号量,用于与一个任务之间的任务同步
OS_OPT_POST_NO_SCHED 在任务优先级比等待信号量任务还要低的时候, 发送信号量之后不马上做任务切换,等当前发送信号量的任务都执行完在做任务切换,反之如果发送信号量的任务优先级比等待信号量任务高,即使没有该选项也不会马上任务切换,会等待当前任务运行完(注意:如果当前等待信号量的任务比现在发送信号量的任务优先级高是会马上任务切换的,毕竟UCOSIII是抢占式内核)这个opt是和前两个选项配合使用的即可以或运算
例如: OS_OPT_POST_1 | OS_OPT_POST_NO_SCHED
注意该函数可以在中断中使用,即在中断可以发送一个信号量
看实现的代码
OS_SEM_CTR OSSemPost (OS_SEM *p_sem,
OS_OPT opt,
OS_ERR *p_err)
{
OS_SEM_CTR ctr;
/* 参数检查的代码*/
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /*OSIntNestingCtr 大于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);
}
#endif
ctr = OS_SemPost(p_sem, /*发送信号量 Post to semaphore */
opt,
ts,
p_err);
return (ctr);
}
从上面该函数知道它是可以在中断中使用的,在中断发生信号量是使用 OS_IntQPost();函数将信号量发送到中断队列,然后由中断服务函数发送信号量给任务
那不在中断里面 发送信号量用 OS_SemPost()函数
下面在解析一下该函数
OS_SemPost (OS_SEM *p_sem,
OS_OPT opt,
CPU_TS ts,
OS_ERR *p_err)
{
OS_OBJ_QTY cnt;
OS_SEM_CTR ctr;
OS_PEND_LIST *p_pend_list;
OS_PEND_DATA *p_pend_data;
OS_PEND_DATA *p_pend_data_next;
OS_TCB *p_tcb;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
p_pend_list = &p_sem->PendList; //取出信号量的等待列表的首地址
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { /*等待列表下挂载的元素个数为0,说明没有任务在等待信号量 */
//这里是判断信号量Ctr 有没有溢出,因为如果没有任务等待信号量而发送信号量 Ctr自增1
switch (sizeof(OS_SEM_CTR)) {
case 1u:
if (p_sem->Ctr == DEF_INT_08U_MAX_VAL) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SEM_OVF;
return ((OS_SEM_CTR)0);
}
break;
case 2u:
if (p_sem->Ctr == DEF_INT_16U_MAX_VAL) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SEM_OVF;
return ((OS_SEM_CTR)0);
}
break;
case 4u:
if (p_sem->Ctr == DEF_INT_32U_MAX_VAL) {
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SEM_OVF;
return ((OS_SEM_CTR)0);
}
break;
default:
break;
}
p_sem->Ctr++; /*没有信号量等待,且信号量个数没有溢出,则信号量个数加一*/
ctr = p_sem->Ctr; //将信号量个数的值给ctr
p_sem->TS = ts;
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return (ctr); //返回ctr
}
//这里是信号量的等待列表挂载了元素,需要给等待列表里面的元素发送信号量(其实就是给)
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
if ((opt & OS_OPT_POST_ALL) != (OS_OPT)0) { /*给所以等待信号量的任务发送信号量 */
cnt = p_pend_list->NbrEntries; /*获取等待任务数目到 cntYes*/
} else { //给等待信号量任务优先级最高的任务发送信号量
cnt = (OS_OBJ_QTY)1; /*将要操作的任务数为1,cnt 置1 */
}
p_pend_data = p_pend_list->HeadPtr; //获取等待列表的头结点的地址
while (cnt > 0u) {
p_tcb = p_pend_data->TCBPtr; //从OS_PEND_DATA中取出任务控制块
p_pend_data_next = p_pend_data->NextPtr;//p_pend_data_next指向等待列表的下一个节点
OS_Post((OS_PEND_OBJ *)((void *)p_sem), //发布信号量给当前任务
p_tcb,
(void *)0,
(OS_MSG_SIZE)0,
ts);
p_pend_data = p_pend_data_next; //处理下一个任务
cnt--;
}
ctr = p_sem->Ctr;
OS_CRITICAL_EXIT_NO_SCHED();
if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) { //如果 opt 没选择“发布时不调度任务”
OSSched(); /*进行任务切换 */
}
*p_err = OS_ERR_NONE;
return (ctr);
}
从发送信号量 OS_SemPost();函数可以看出如果发送信号量时如果没有任务在等待信号量 (即等待列表没有挂载任务) 判断信号量值Ctr 有没有溢出,因为如果溢出就 Ctr 自增1,然后退出发送函数;当然如果发现有任务在等待信号量 Ctr 不会自增1,然后发布信号量在 OS_Post ();函数
解析 OS_Post ();函数怎么解除等待信号量任务的挂起,看代码
void OS_Post (OS_PEND_OBJ *p_obj,
OS_TCB *p_tcb,
void *p_void,
OS_MSG_SIZE msg_size,
CPU_TS ts)
{
//注意该函数所注释的任务全部都是指等待列表上面挂载的任务,处于等待态
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY: /*任务就绪状态
case OS_TASK_STATE_DLY: /*任务延时状态
case OS_TASK_STATE_SUSPENDED: /*任务被挂起状态
case OS_TASK_STATE_DLY_SUSPENDED: /*任务在延时中被挂起 */
break; //这些任务状态不用处理,直接跳出
case OS_TASK_STATE_PEND: //如果任务处于无期限等待状态
case OS_TASK_STATE_PEND_TIMEOUT: //任务处于有限等待
if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个内核对象
OS_Post1(p_obj,
p_tcb,
p_void,
msg_size,
ts);
} else {
#if (OS_MSG_EN > 0u) //如果使能了消息队列
p_tcb->MsgPtr = p_void; /*保存消息指针到任务控制块 */
p_tcb->MsgSize = msg_size; /* 保存消息长度到任务控制块 */
#endif
p_tcb->TS = ts;
}
//从这里开始看,这里才是重点
if (p_obj != (OS_PEND_OBJ *)0) { //如果等待内核对象不为空
OS_PendListRemove(p_tcb); /*从等待列表中移除等待某内核的任务*/
//省略小部分调试用的代码
}
OS_TaskRdy(p_tcb); /*任务就绪列表插入任务同时如果是有限等待还要在时钟节拍列表中删除该任务因为已经有信号量发送了 */
p_tcb->TaskState = OS_TASK_STATE_RDY; /*任务等待状态变成就绪态 */
p_tcb->PendStatus = OS_STATUS_PEND_OK; /*任务等待状态变成等待完成 */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /*任务等待的内核对象为空=标记不再等待 */
break;
case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果任务在有期限等待中被挂起
if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) {
OS_Post1(p_obj,
p_tcb,
p_void,
msg_size,
ts);
} else {
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = p_void;
p_tcb->MsgSize = msg_size;
#endif
p_tcb->TS = ts;
}
OS_TickListRemove(p_tcb);
if (p_obj != (OS_PEND_OBJ *)0) {
OS_PendListRemove(p_tcb);
#if OS_CFG_DBG_EN > 0u
OS_PendDbgNameRemove(p_obj, p_tcb);
#endif
}
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //任务状态改为被挂起状态
p_tcb->PendStatus = OS_STATUS_PEND_OK; /*清除等待状态 Clear pend status */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /*标记不再等待 Indicate no longer pending */
break;
default:
break;
}
}
最后重复总结一下信号量
- 发送信号量的时候,会判断等待列表上面有没有挂载等待信号量的任务
情况1 如果没有挂载就会判断 Ctr 值有没有溢出,如果没有就自增1,然后直接退出发送函数OS_SemPost();
情况2 如果有挂载任务,Ctr不会自增, 还要根据opt来处理等待列表的任务
opt=OS_OPT_POST_NO_SCHED|OS_OPT_POST_1 =向最高优先级任务发送信号量但不进行任务切换
opt=OS_OPT_POST_NO_SCHED|OS_OPT_POST_ALL =向所有任务发送信号量但不进行任务切换
opt=OS_OPT_POST_ALL =向所有等待信号量任务发送信号量并进行任务切换
opt=OS_OPT_POST_1 =向等待信号量中任务优先级最高的任务发信号量,
(上述的可以切换任务是建立在发送信号量的任务比等待信号量任务优先级低,如果发送信号量的任务优先级比比等待信号量任务优先级高,是任务切换是没有意义的,因为切换也是切换回当前发送信号量的任务)
2.就是获得信号量之后对等待列表任务在等待列表上面移除,同时任务状态从等待态变就绪态, 如果是有限等待下收到信号量此时等待时间还没有完,还要在对应的时钟节拍列表上删除对应的任务
3.等待信号量函数是不能在中断中使用的而发送信号量函数可以在中断里面使用
4.等待信号量函数,在等待信号量发现没有信号量 即Ctr=0 才会插入到等待列表,同时将任务从就绪态变成等待态,如果在等待信号量发现没有信号量 即Ctr>0, 当前任务是不会插入到等待列表的,相当于直接获得信号量
- OSSemPend ()根据 opt 与 timeout 的结合可以有多种等待信号量的方式
Opt
OS_OPT_PEND_BLOCKING 就是指定信号量无效时,任务挂起以等待信号量
OS_OPT_PEND_NON_BLOCKING 信号量无效时任务直接返回,不挂起任务,继续执行当前任务
1 timeout=0 + Opt=OS_OPT_PEND_BLOCKING 不能立即收到信号量,任务挂起以等待信号量并阻塞任务 ,直到等到信号量就解挂 ,要是等待信号量马上就可以获取到信号量是不阻塞任务的
2 timeout>0 + Opt=OS_OPT_PEND_BLOCKING 要是等待信号量马上就可以获取到信号量是不阻塞任务的,要是不能立即收到信号量,任务挂起以等待信号量并阻塞任务,在等待完 timeout 个节拍数还没有收到信号量就解挂,继续运行任务,如果在这些节拍数只能收到信号量也解挂
3 timeout不管是多少 + Opt=OS_OPT_PEND_NON_BLOCKING 不管能不能立即收到信号量,任务都不堵塞不挂起,继续运行
注意:该函数不能在中断里面使用,该函数存在阻塞因素,而中断是不能注意长时间阻塞的,否则会进入硬件中断程序跑飞了