目录
一、信号量的创建
首先要了解信号量在内存里是存的些什么东西,上图
void OSSemCreate (OS_SEM *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
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
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) { /* Not allowed to be called from an ISR */
*p_err = OS_ERR_CREATE_ISR;
return;
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* Validate 'p_sem' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
#endif
CPU_CRITICAL_ENTER();
p_sem->Type = OS_OBJ_TYPE_SEM; /* Mark the data structure as a semaphore */
p_sem->Ctr = cnt; /* Set semaphore value */
p_sem->TS = (CPU_TS)0;
p_sem->NamePtr = p_name; /* Save the name of the semaphore */
OS_PendListInit(&p_sem->PendList); /* Initialize the waiting list */
#if OS_CFG_DBG_EN > 0u
OS_SemDbgListAdd(p_sem);
#endif
OSSemQty++;
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
}
所以创建信号量的过程其实很简单,难的是信号量这个东西里面有些什么,创建的时候出了常规的参数检查之后,给信号量赋值(你传的什么)
有一个关键的函数OS_PendListInit(&p_sem->PendList);
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;
}
这里可以对比上图就是第三个方块,它里面管理的是一个双向链表,和一个数字,这里把初始值都附上。这个工作完成了之后基本上就完成了一个信号量的创建。
二、发布信号量
S_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);
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* Validate 'p_sem' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((OS_SEM_CTR)0);
}
#endif
#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_sem->Type != OS_OBJ_TYPE_SEM) { /* Make sure semaphore was created */
*p_err = OS_ERR_OBJ_TYPE;
return ((OS_SEM_CTR)0);
}
#endif
ts = OS_TS_GET(); /* Get timestamp */
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* See if called from an ISR */
OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_SEM, /* Post to ISR queue */
(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);
}
这里进行的操作就是,把你需要往哪个信号量发布传进来,这个函数其实做的事情很少,主要就是获取了一个时间(当前的系统时间ts)然后再传递给OS_SemPost()这个函数
OS_SEM_CTR 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) { /* Any task waiting on semaphore? */
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++; /* No */
ctr = p_sem->Ctr;
p_sem->TS = ts; /* Save timestamp in semaphore control block */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return (ctr);
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
if ((opt & OS_OPT_POST_ALL) != (OS_OPT)0) { /* Post message to all tasks waiting? */
cnt = p_pend_list->NbrEntries; /* Yes */
} else {
cnt = (OS_OBJ_QTY)1; /* No */
}
p_pend_data = p_pend_list->HeadPtr;
while (cnt > 0u) {
p_tcb = p_pend_data->TCBPtr;
p_pend_data_next = p_pend_data->NextPtr;
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) {
OSSched(); /* Run the scheduler */
}
*p_err = OS_ERR_NONE;
return (ctr);
}
1、先把等待列表取出来看,里面是否有正在等待信号量的任务(这里也需要看你是发给所有任务还是单个等待的任务)
2、如果没有任务,那么进入判断你传递的信号量是什么类型的,char?int?,来判断你发布的信号量是否到达了最大值,如果到了就直接GG,如果没有就对计数值++,还把传递进来的ts保存在了信号量里面。
3、如果有任务在等待,先判断你的opt是发给所有任务还是发布给一个任务。
如果是发给所有任务:先把等待列表里面的任务个数传递出来给cnt,再循环遍历所有等待任务并且把他们从等待列表中踢出去(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: /* Cannot Pend Abort a task that is ready */
case OS_TASK_STATE_DLY: /* Cannot Pend Abort a task that is delayed */
case OS_TASK_STATE_SUSPENDED: /* Cannot Post a suspended task */
case OS_TASK_STATE_DLY_SUSPENDED: /* Cannot Post a suspended task that was also dly'd */
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, /* Indicate which object was posted to */
p_tcb,
p_void,
msg_size,
ts);
} else {
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = p_void; /* Deposit message in OS_TCB of task waiting */
p_tcb->MsgSize = msg_size; /* ... assuming posting a message */
#endif
p_tcb->TS = ts;
}
if (p_obj != (OS_PEND_OBJ *)0) {
OS_PendListRemove(p_tcb); /* Remove task from wait list(s) */
#if OS_CFG_DBG_EN > 0u
OS_PendDbgNameRemove(p_obj,
p_tcb);
#endif
}
OS_TaskRdy(p_tcb); /* Make task ready to run */
p_tcb->TaskState = OS_TASK_STATE_RDY;
p_tcb->PendStatus = OS_STATUS_PEND_OK; /* Clear pend status */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /* Indicate no longer pending */
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, /* Indicate which object was posted to */
p_tcb,
p_void,
msg_size,
ts);
} else {
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = p_void; /* Deposit message in OS_TCB of task waiting */
p_tcb->MsgSize = msg_size; /* ... assuming posting a message */
#endif
p_tcb->TS = ts;
}
OS_TickListRemove(p_tcb); /* Cancel any timeout */
if (p_obj != (OS_PEND_OBJ *)0) {
OS_PendListRemove(p_tcb); /* Remove task from wait list(s) */
#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;
}
}
这里进来就是判断你任务的状态是否符合要求,比如你一个被挂起的任务不能是准备状态这种,并且将这个ts保存在任务的tcb中然后就调用 OS_PendListRemove(p_tcb);
void OS_PendListRemove (OS_TCB *p_tcb)
{
OS_OBJ_QTY n_pend_list; /* Number of pend lists */
OS_PEND_DATA *p_pend_data;
OS_PEND_LIST *p_pend_list;
OS_PEND_OBJ *p_obj;
p_pend_data = p_tcb->PendDataTblPtr; /* Point to the first OS_PEND_DATA to remove */
n_pend_list = p_tcb->PendDataTblEntries; /* Get number of entries in the table */
while (n_pend_list > (OS_OBJ_QTY)0) {
p_obj = p_pend_data->PendObjPtr; /* Get pointer to pend list */
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;
}
进入到这个里面就是把任务从等待列表中移除出去,第一个pend_data基本上就是任务的实体,第二个是任务被几个内核对象等待。 p_tcb->PendDataTblPtr; p_tcb->PendDataTblEntries;这两个变量是在任务调用挂起等待信号量的函数的时候被输出化(意思就是任务被加入这个挂起等待列表的时候就初始化这两个变量)移除操作就是一个双线链表,然后把挂起的任务从这个双向链表中移除但是这里的双向链表不是直接管理的任务tcb,是管理的pen_Data,
是这样一个结构,并不是直接将tcb进行双向连接的。
这里还有一个调试的代码也顺便讲解一下。
void OS_PendDbgNameRemove (OS_PEND_OBJ *p_obj,
OS_TCB *p_tcb)
{
OS_PEND_LIST *p_pend_list;
OS_PEND_DATA *p_pend_data;
OS_TCB *p_tcb1;
p_tcb->DbgNamePtr = (CPU_CHAR *)((void *)" "); /* Remove name of object pended on for readied task */
p_pend_list = &p_obj->PendList;
p_pend_data = p_pend_list->HeadPtr;
if (p_pend_data != (OS_PEND_DATA *)0) {
p_tcb1 = p_pend_data->TCBPtr;
p_obj->DbgNamePtr = p_tcb1->NamePtr;
} else {
p_obj->DbgNamePtr = (CPU_CHAR *)((void *)" "); /* No other task pending on object */
}
}
这里其实就是这样的任务tcb下还有一个任务名字,这个debug就指向第一个tcb的名字这里,如果移除的话就需要把这个debug指向下一个名字,通过这个方式来调试,任务是否被成功移除了这个列表。
void OS_TaskRdy (OS_TCB *p_tcb)
{
OS_TickListRemove(p_tcb); /* Remove from tick list */
if ((p_tcb->TaskState & OS_TASK_STATE_BIT_SUSPENDED) == (OS_STATE)0) {
OS_RdyListInsert(p_tcb); /* Insert the task in the ready list */
}
}
这里把任务从列表中移除了之后就是把你刚刚移除的任务tcb重新的改成就绪态。
后续还有一个点,等待多个内核对象,这里相当于做的处理就是,我一个任务在等待多个内核对象,然后因为其中一个等待的内核对象释放了,所以我任务移除了等待列表,此时任务里就会保存一个变量,相当于是一个标志,是因为哪个内核对象释放了,然后让任务脱离了等待列表(相当于你被人抓了,然后谁来救的你,我就保存谁)后续移除等待列表的操作都是一样的利用OS_PendListRemove这个函数。
三、挂起等待信号量
1、常规参数检查(略过了太无聊)
2、查看信号量是否大于1
(1)、大于1,这时候是最简单的,相当于你想拿信号量,此时也有,信号量-1,接着运行工作,此时如果你设置的需要时间检查,就把信号量发布时候的保存的ts返回出去
if (p_sem->Ctr > (OS_SEM_CTR)0) { /* Resource available? */
p_sem->Ctr--; /* Yes, caller may proceed */
if (p_ts != (CPU_TS *)0) {
*p_ts = p_sem->TS; /* get timestamp of last post */
}
ctr = p_sem->Ctr;
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return (ctr);
}
(2)、信号量为0,
此时又要分你是等待信号量是阻塞还是非阻塞,如果是非阻塞,那么直接退出,告诉你当前没有信号量给你用
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
ctr = p_sem->Ctr; /* No */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK;
return (ctr);
}
如果此时是阻塞等待,就需要把任务挂起到等待列表中。调用OS_Pend去挂起,去挂起任务
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_Pend这个函数,这里可以看作利用传参,来初始化这个需要挂载的pen_data,然后再把这个pen_data经行初始化后挂载到等待信号量的双向链表里面去(依旧是按照优先级来插入有顺序的),但是这里还要注意,任务等待信号量如果添加了超时选项,那么任务就会被插入到时钟基准的列表里去。等待匹配值到来在移除出去。
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; /* Resource not available, wait until it is */
OSTCBCurPtr->PendStatus = OS_STATUS_PEND_OK;
OS_TaskBlock(OSTCBCurPtr, /* Block the task and add it to the tick list if needed */
timeout);
if (p_obj != (OS_PEND_OBJ *)0) { /* Add the current task to the pend list ... */
p_pend_list = &p_obj->PendList; /* ... if there is an object to pend on */
p_pend_data->PendObjPtr = p_obj; /* Save the pointer to the object pending on */
OS_PendDataInit((OS_TCB *)OSTCBCurPtr, /* Initialize the remaining field */
(OS_PEND_DATA *)p_pend_data,
(OS_OBJ_QTY )1);
OS_PendListInsertPrio(p_pend_list, /* Insert in the pend list in priority order */
p_pend_data);
} else {
OSTCBCurPtr->PendDataTblEntries = (OS_OBJ_QTY )0; /* If no object being pended on the clear these fields */
OSTCBCurPtr->PendDataTblPtr = (OS_PEND_DATA *)0; /* ... in the TCB */
}
#if OS_CFG_DBG_EN > 0u
OS_PendDbgNameAdd(p_obj,
OSTCBCurPtr);
#endif
}
最后在进行任务的调度,重新选择优先级高的任务去运行。
OSSched();
当任务拿到信号量的时候,程序又从此处继续执行。
CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* We got the semaphore */
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: /* Indicate that we didn't get semaphore within 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);
四、放弃等待信号量
void OS_PendAbort (OS_PEND_OBJ *p_obj,
OS_TCB *p_tcb,
CPU_TS ts)
{
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY: /* Cannot Pend Abort a task that is ready */
case OS_TASK_STATE_DLY: /* Cannot Pend Abort a task that is delayed */
case OS_TASK_STATE_SUSPENDED: /* Cannot Pend Abort a suspended task */
case OS_TASK_STATE_DLY_SUSPENDED: /* Cannot Pend Abort a suspended task that was also dly'd */
break;
case OS_TASK_STATE_PEND:
case OS_TASK_STATE_PEND_TIMEOUT:
if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) {
OS_PendAbort1(p_obj, /* Indicate which object was pend aborted */
p_tcb,
ts);
}
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = ts;
if (p_obj != (OS_PEND_OBJ *)0) {
OS_PendListRemove(p_tcb); /* Remove task from all pend lists */
}
OS_TaskRdy(p_tcb);
p_tcb->TaskState = OS_TASK_STATE_RDY; /* Task will be ready */
p_tcb->PendStatus = OS_STATUS_PEND_ABORT; /* Indicate pend was aborted */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /* Indicate no longer pending */
break;
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) {
OS_PendAbort1(p_obj, /* Indicate which object was pend aborted */
p_tcb,
ts);
}
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = ts;
if (p_obj != (OS_PEND_OBJ *)0) {
OS_PendListRemove(p_tcb); /* Remove task from all pend lists */
}
OS_TickListRemove(p_tcb); /* Cancel the timeout */
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; /* Pend Aborted task is still suspended */
p_tcb->PendStatus = OS_STATUS_PEND_ABORT; /* Indicate pend was aborted */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /* Indicate no longer pending */
break;
default:
break;
}
}
就说白了就是你一直等待一个信号量,然后你在逻辑里面不想等了,就把这玩意儿调一下就可以不等了。原理也是去看你的等待列表里移除出去。解除和内核对象的关联,然后记录时间。