个人笔记,主要内容均来自原文
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。
——百度百科
Small RTOS51 中的信号量由两部分组成:一个是信号量的计数值,它是一个 8 位的无符号整数;另一个是由等待该信号量的任务组成的等待任务表。Small RTOS51 信号量相关配置可以在 Os.cfg.h 中修改。
#define EN_OS_SEM 0 /* 禁止(0)或允许(1)信号量 */
#define EN_OS_SEM_CHK 0 /* 禁止(0)或允许(1)校验信号量索引 */
#define OS_SEM_MEM_SEL idata /* 信号量储空间选择,keil c51有用,必须为idata、xdata */
/* 不是keil c51时它必须为空格 */
#define OS_MAX_SEMS 2 /* 最大信号量数目 */
#define EN_OS_SEM_PENT 1 /* 禁止(0)或允许(1)等待信号量 */
#define EN_OS_SEM_ACCEPT 0 /* 禁止(0)或允许(1)无等待请求信号量 */
#define EN_OS_SEM_POST 1 /* 禁止(0)或允许(1)中发送信号量 */
#define EN_OS_SEM_QUERY 0 /* 禁止(0)或允许(1)查询信号量
在 Small RTOS51 中,用一个 0 到 OS_MAX_SEMS -1 的值作索引标识一个信号量,在使用一个信号量之前,需要先进行初始化(调用 OSSemCreate() 函数),对信号量的计数值进行赋值(0~255)。如果信号量是用于表示一个或多个事件的发生,那么该信号量初始值应该设置为 0;如果信号量是用于对共享资源的访问,那么该信号的初始值应该设置为 1(当做二值信号量)。
信号量的大部分代码均是临界区代码,所以必须先关中断,操作完信号量后再开中断。
在 Small RTSO51 中,存在一些类似下面 __C51__
条件编译内的代码,当 __C51__
被定义,方可执行指定的代码。下面条件编译内的代码的作用是将变量保存到任务堆栈和从任务堆栈中恢复变量。
/* 使用堆栈是为了使函数具有重入性 */
#ifdef __C51__
SP++;
*((uint8 data *)SP) = Index;
#endif
/* 信号量无效 */
OS_TaskSuspend(OSRunningTaskID()); /* 任务进入等待状态 */
OSSched(); /* 运行下一个任务 */
#ifdef __C51__
Index = *((uint8 data *)SP);
SP--;
#endif
使用堆栈的原因:Keil C51 不是标准 C,标准 C 会把局部变量分配到堆栈中,而 Keil C51 会把局部变量分配到内存固定地址,所以 Keil C51 的函数一般是不可重入的。当编译器把所有局部变量分配到寄存器时,函数才是可重入的,而 Small RTOS51 的所有内核函数和系统服务函数都要求是可重入的。
Small RTOS51 信号量的数据结构:(OS_MAX_SEMS 表示用户程序需要的信号量总数,OS_SEM_MEM_SEL 是需要用户来选择的存储空间,需要定义为 idata、xdata 或 pdata 等)
/* 分配信号量存储空间 */
#if EN_OS_SEM > 0
#if OS_MAX_TASKS < 9
uint8 OS_SEM_MEM_SEL OsSemBuf[OS_MAX_SEMS * 2];
#else
uint8 OS_SEM_MEM_SEL OsSemBuf[OS_MAX_SEMS * 3];
#endif
在 Small RTOS51 中,所有信号量存于一个 uint8 型的数组中,为了节省 RAM 的使用,OsSemBuf[] 在不同情况下有不同的尺寸。当任务数小于 9 时,OsSemBuf[] 占用 OS_MAX_SEMS * 2 个字节(单个信号量的等待任务表 1 个字节,计数值 1 个字节,其中 OsSemBuf[2 * index] 为信号量的计数值,OsSemBuf[2 * index + 1] 为等待任务表);如果任务数大于 8 时,OsSemBuf[] 占用 OS_MAX_SEMS * 3 个字节(单个信号量的等待任务表 2 个字节,计数值 1 个字节,其中 OsSemBuf[3 * index] 为信号量的计数值,OsSemBuf[3 * index + 1] 和 OsSemBuf[3 * index + 2]为等待任务表)
OSSemCreate() 函数用来初始化一个信号量,它首先判断信号量索引值是否超出最大范围,接着给信号量的计数值赋初值,然后清空信号量的等待任务表。
/*********************************************************************************************************
** 函数名称: OSSemCreate
** 功能描述: 初始化消息队列
** 输 入: Index:信号量索引
** data:信号量初始值
** 输 出: NOT_OK:没有这个信号量
** OS_SEM_OK:成功
** 全局变量: 无
** 调用模块: 无
**
** 作 者: 陈明计
** 日 期: 2002年9月1日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
uint8 OSSemCreate(uint8 Index,uint8 Data)
{
OS_ENTER_CRITICAL();
if (Index < OS_MAX_SEMS )
{
#if OS_MAX_TASKS < 9
OsSemBuf[2 * Index] = Data; /* 计数器置初值 */
OsSemBuf[2 * Index + 1] = 0; /* 清空等待队列 */
#else
OsSemBuf[3 * Index] = Data; /* 计数器置初值 */
/* 清空等待队列 */
OsSemBuf[3 * Index + 1] = 0;
OsSemBuf[3 * Index + 2] = 0;
#endif
OS_EXIT_CRITICAL();
return OS_SEM_OK;
}
OS_EXIT_CRITICAL();
return NOT_OK;
}
OSSemPend() 用于等待一个信号量,如果信号量有效(计数值不为 0),则返回 OS_SEM_OK;如果信号量无效(计数值为 0),则当前任务进入等待状态,直到信号量有效或到达超时时间。
函数先将当前任务放进信号量的等待任务表中,接着在 while 循环中判断信号量值是否有效,无效则挂起任务(有效则退出循环),然后进行任务调度,当任务再次运行,判断是否到达超时时间,如果已到,则退出循环,否则继续循环。退出循环后,,将当前任务从消息队列的等待任务表中删除,另外还需要判断信号量的值是否为 0,如果不为 0 (等待到了信号量)还需要将信号量的值减 1,恢复到无效状态。
/*********************************************************************************************************
** 函数名称: OSSemPend
** 功能描述: 等待一个信号量
** 输 入: Index:信号量索引
** Tick:等待时间
** 输 出: NOT_OK:参数错误
** OS_SEM_OK:得到信号量
** OS_SEM_TMO:超时到
** OS_SEM_NOT_OK:没有得到信号量
** 全局变量: 无
** 调用模块: OSRunningTaskID,OS_TaskSuspend,OSSched,OS_ENTER_CRITICAL,OS_EXIT_CRITICAL
**
** 作 者: 陈明计
** 日 期: 2002年9月1日
**-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年12月30日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
#if EN_OS_SEM_PENT > 0
uint8 OSSemPend(uint8 Index, uint8 Tick)
{
#if EN_OS_SEM_CHK > 0
if (Index >= OS_MAX_SEMS)
{
return 0;
}
#endif
OS_ENTER_CRITICAL();
OSWaitTick[OSRunningTaskID()] = Tick; /* 设置超时时间 */
/* 可以优化寄存器的使用 */
/* 把任务加入等待任务队列 */
#if OS_MAX_TASKS < 9
OsSemBuf[Index * 2 + 1] |= OSMapTbl[OSRunningTaskID()];
#else
if (OSRunningTaskID() < 8)
{
OsSemBuf[Index * 3 + 1] |= OSMapTbl[OSRunningTaskID()];
}
else
{
OsSemBuf[Index * 3 + 2] |= OSMapTbl[OSRunningTaskID() & 0x07];
}
#endif
/* 信号量是否有效 */
#if OS_MAX_TASKS < 9
while (OsSemBuf[Index * 2] == 0)
{
#else
while (OsSemBuf[Index * 3] == 0)
{
#endif
/* 使用堆栈是为了使函数具有重入性 */
#ifdef __C51__
SP++;
*((uint8 data *)SP) = Index;
#endif
/* 信号量无效 */
OS_TaskSuspend(OSRunningTaskID()); /* 任务进入等待状态 */
OSSched(); /* 运行下一个任务 */
#ifdef __C51__
Index = *((uint8 data *)SP);
SP--;
#endif
/* 任务再次运行,如果超时到,退出循环 */
if (OSWaitTick[OSRunningTaskID()] == 0)
{
break;
}
}
/* 将任务从等待队列中清除(可以删除) */
#if OS_MAX_TASKS < 9
OsSemBuf[Index * 2 + 1] &= ~OSMapTbl[OSRunningTaskID()];
#else
if (OSRunningTaskID() < 8)
{
OsSemBuf[Index * 3 + 1] &= ~OSMapTbl[OSRunningTaskID()];
}
else
{
OsSemBuf[Index * 3 + 2] &= ~OSMapTbl[OSRunningTaskID() & 0x07];
}
#endif
/* 判断信号量是否有效。有效,信号量计数器减一 */
#if OS_MAX_TASKS < 9
if (OsSemBuf[Index * 2] > 0)
{
OsSemBuf[Index * 2]--;
#else
if (OsSemBuf[Index * 3] > 0)
{
OsSemBuf[Index * 3]--;
#endif
OS_EXIT_CRITICAL();
return OS_SEM_OK;
}
else
{
/* 无信号返回信号无效 */
OS_EXIT_CRITICAL();
return OS_SEM_TMO;
}
}
#endif
原文提到 Small RTOS51 具有两个发送信号量的函数 OSSemIntPort() 和 OSSemPort(),一个在中断中使用,一个在任务中使用,但我所用的源码(V1.20.0)中 OSSemIntPort 实际调用的还是 OSSemPort()。
函数先判断信号量计数值加 1 后是否溢出,如果不会溢出,则信号量的计数值就加 1。然后查找信号量任务中优先级最高的任务,将其从任务表中删除,同时将其设置为就绪状态。
/*********************************************************************************************************
** 函数名称: OSSemPost
** 功能描述: 中断中发送一个信号量
** 输 入: Index:信号量索引
** 输 出: NOT_OK:参数错误
** OS_SEM_OK:发送成功
** 全局变量: 无
** 调用模块: OSTaskResume,OS_ENTER_CRITICAL,OS_EXIT_CRITICAL
**
** 作 者: 陈明计
** 日 期: 2003年8月3日
**-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年10月26日
**-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年12月30日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
#if EN_OS_SEM_POST > 0
uint8 OSSemPost(uint8 Index)
{
uint8 temp,i;
#if EN_OS_SEM_CHK > 0
if (Index >= OS_MAX_SEMS)
{
return NOT_OK;
}
#endif
OS_ENTER_CRITICAL();
#if OS_MAX_TASKS < 9
/* 信号量计数器加一 */
if (OsSemBuf[Index * 2] <255)
{
OsSemBuf[Index * 2]++;
}
/* 察看信号量的等待任务队列 */
temp = OsSemBuf[Index * 2 + 1];
for (i = 0; i < OS_MAX_TASKS; i++)
{
if ((temp & 0x01) != 0)
{
break;
}
temp = temp >> 1;
}
/* 有任务等待信号,使其中优先级最高的进入就绪状态,并将其从等待队列中清除 */
if (i < OS_MAX_TASKS)
{
OsSemBuf[Index * 2 + 1] &= ~OSMapTbl[i];
OSTaskResume(i);
}
#else
/* 信号量计数器加一 */
if (OsSemBuf[Index * 3] <255)
{
OsSemBuf[Index * 3]++;
}
temp = OsSemBuf[Index * 3 + 1];
for (i = 0; i < 8; i++)
{
if ((temp & 0x01) != 0)
{
break;
}
temp = temp >> 1;
}
if (i >= 8)
{
temp = OsSemBuf[Index * 3 + 2];
for ( ; i < OS_MAX_TASKS; i++)
{
if ((temp & 0x01) != 0)
{
break;
}
temp = temp >> 1;
}
}
/* 有任务等待信号,使其中优先级最高的进入就绪状态,并将其从等待队列中清除 */
if (i < OS_MAX_TASKS)
{
if (i < 8)
{
OsSemBuf[Index * 3 + 1] &= ~OSMapTbl[i];
}
else
{
OsSemBuf[Index * 3 + 2] &= ~OSMapTbl[i & 0x07];
}
OSTaskResume(i);
}
#endif
OS_EXIT_CRITICAL();
return OS_SEM_OK;
}
#endif
在中断服务子程序中请求信号量时,只能使用 OSSemAccept() 函数,而不能使用 OSSemPend(),因为中断中不能出现长时间的等待。
函数代码很简单,如果指定的信号量有效,则将信号量的计数值减 1,返回 OS_SEM_OK,否则直接返回 OS_SEM_NOT_OK。
/*********************************************************************************************************
** 函数名称: OSSemAccept
** 功能描述: 无等待请求信号量
** 输 入: Index:信号量索引
** 输 出: NOT_OK:参数错误
** OS_SEM_OK:得到信号量
** OS_SEM_TMO:超时到
** OS_SEM_NOT_OK:没有得到信号量
** 全局变量: 无
** 调用模块: OS_TaskSuspend,OSSched,OS_ENTER_CRITICAL,OS_EXIT_CRITICAL
**
** 作 者: 陈明计
** 日 期: 2002年9月1日
**-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年10月20日
***-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年10月26日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
#if EN_OS_SEM_ACCEPT > 0
uint8 OSSemAccept(uint8 Index)
{
#if EN_OS_SEM_CHK > 0
if (Index >= OS_MAX_SEMS)
{
return 0;
}
#endif
OS_ENTER_CRITICAL();
/* 判断信号量是否有效。有效,信号量计数器减一 */
#if OS_MAX_TASKS < 9
if (OsSemBuf[Index * 2] > 0)
{
OsSemBuf[Index * 2]--;
#else
if (OsSemBuf[Index * 3] > 0)
{
OsSemBuf[Index * 3]--;
#endif
OS_EXIT_CRITICAL();
return OS_SEM_OK;
}
else
{
/* 无信号返回信号无效 */
OS_EXIT_CRITICAL();
return OS_SEM_NOT_OK;
}
}
#endif
OSSemQuery() 可以用来查询指定信号量的计数值。
/*********************************************************************************************************
** 函数名称: OSSemQuery
** 功能描述: 查询信号量
** 输 入: Index:信号量索引
** 输 出: 信号量的值
** 全局变量: 无
** 调用模块: OS_ENTER_CRITICAL,OS_EXIT_CRITICAL
**
** 作 者: 陈明计
** 日 期: 2002年9月1日
**-------------------------------------------------------------------------------------------------------
** 修改人: 陈明计
** 日 期: 2002年10月26日
**-------------------------------------------------------------------------------------------------------
** 修改人:
** 日 期:
**-------------------------------------------------------------------------------------------------------
********************************************************************************************************/
#if EN_OS_SEM_QUERY > 0
uint8 OSSemQuery(uint8 Index)
{
if (Index >= OS_MAX_SEMS)
{
return 0;
}
#if OS_MAX_TASKS < 9
return OsSemBuf[2*Index];
#else
return OsSemBuf[3*Index];
#endif
}
#endif