基本概念
信号量(Semaphore) 是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。
一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:
0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。
正值,表示该信号量当前可被获取。
以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:
用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。
信号量运作原理
信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过 LOSCFG_BASE_IPC_SEM_LIMIT 宏实现),并把所有信号量初始化成未使用,加入到未使用链表中供系统使用。
- 信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。
- 信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,
等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。 - 信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。
- 信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。
信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。
当访问资源的任务数达到该资源允许的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
信号量长什么样?
typedef struct {
UINT8 semStat; /**< Semaphore state *///信号量的状态
UINT16 semCount; /**< Number of available semaphores *///有效信号量的数量
UINT16 maxSemCount; /**< Max number of available semaphores *///有效信号量的最大数量
UINT32 semID; /**< Semaphore control structure ID *///信号量索引号
LOS_DL_LIST semList; /**< Queue of tasks that are waiting on a semaphore *///等待信号量的任务队列,任务通过阻塞节点挂上去
} LosSemCB;
semList
,这又是一个双向链表, 双向链表是内核最重要的结构体, 查看双向链表篇, LOS_DL_LIST
像狗皮膏药一样牢牢的寄生在宿主结构体上semList
上挂的是未来所有等待这个信号量的任务.
初始化信号量模块
#ifndef LOSCFG_BASE_IPC_SEM_LIMIT
#define LOSCFG_BASE_IPC_SEM_LIMIT 1024 //信号量的最大个数
#endif
LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)//信号量初始化
{
LosSemCB *semNode = NULL;
UINT32 index;
LOS_ListInit(&g_unusedSemList);//初始
/* system resident memory, don't free */
g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));//分配信号池
if (g_allSem == NULL) {
return LOS_ERRNO_SEM_NO_MEMORY;
}
for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {
semNode = ((LosSemCB *)g_allSem) + index;//拿信号控制块, 可以直接g_allSem[index]来嘛
semNode->semID = SET_SEM_ID(0, index);//保存ID
semNode->semStat = OS_SEM_UNUSED;//标记未使用
LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);//通过semList把 信号块挂到空闲链表上
}
if (OsSemDbgInitHook() != LOS_OK) {
return LOS_ERRNO_SEM_NO_MEMORY;
}
return LOS_OK;
}
分析如下:
- 初始化创建了信号量池来统一管理信号量, 默认 1024 个信号量
- 信号ID范围从 [0,1023]
- 未分配使用的信号量都挂到了全局变量
g_unusedSemList
上.
小建议:鸿蒙内核其他池(如进程池,任务池)都采用free
来命名空闲链表,而此处使用unused
,命名风格不太严谨,有待改善.
创建信号量
LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{
unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用信号量池中取首个
LOS_ListDelete(unusedSe