鸿蒙的运行机制,浅谈鸿蒙内核源码的信号量运作原理

基本概念

信号量(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(unusedSem);//从空闲链表上摘除

semCreated = GET_SEM_LIST(unusedSem);//通过semList挂到链表上的,这里也要通过它把LosSemCB头查到. 进程,线程等结构体也都是这么干的.

semCreated->semCount = count;//设置数量

semCreated->semStat = OS_SEM_USED;//设置可用状态

semCreated->maxSemCount = maxCount;//设置最大信号数量

LOS_ListInit(&semCreated->semList);//初始化链表,后续阻塞任务通过task->pendList挂到semList链表上,就知道哪些任务在等它了.

*semHandle = semCreated->semID;//参数带走 semID

OsSemDbgUpdateHook(semCreated->semID, OsCurrTaskGet()->taskEntry, count);

return LOS_OK;

ERR_HANDLER:

OS_RETURN_ERROR_P2(errLine, errNo);

}

分析如下:

从未使用的空闲链表中拿首个信号量供分配使用.

信号量的最大数量和信号量个数都由参数指定.

信号量状态由OS_SEM_UNUSED变成了OS_SEM_USED

semHandle带走信号量ID,外部由此知道成功创建了一个编号为*semHandle的信号量

申请信号量

LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)

{

UINT32 intSave;

LosSemCB *semPended = GET_SEM(semHandle);//通过ID拿到信号体

UINT32 retErr = LOS_OK;

LosTaskCB *runTask = NULL;

if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {

OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);

}

if (OS_INT_ACTIVE) {

PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n");

OsBackTrace();

return LOS_ERRNO_SEM_PEND_INTERR;

}

runTask = OsCurrTaskGet();//获取当前任务

if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {

OsBackTrace();

return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK;

}

SCHEDULER_LOCK(intSave);

if ((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID != semHandle)) {

retErr = LOS_ERRNO_SEM_INVALID;

goto OUT;

}

/* Update the operate time, no matter the actual Pend success or not */

OsSemDbgTimeUpdateHook(semHandle);

if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了

semPended->semCount--;//资源少了一个

goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的

} else if (!timeout) {

retErr = LOS_ERRNO_SEM_UNAVAILABLE;

goto OUT;

}

if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)

PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n");

OsBackTrace();

retErr = LOS_ERRNO_SEM_PEND_IN_LOCK;

goto OUT;

}

runTask->taskSem = (VOID *)semPended;//标记当前任务在等这个信号量

retErr = OsTaskWait(&semPended->semList, timeout, TRUE);//任务进入等待状态,当前任务会挂到semList上,并在其中切换任务上下文

if (retErr == LOS_ERRNO_TSK_TIMEOUT) {//注意:这里是涉及到task切换的,把自己挂起,唤醒其他task

runTask->taskSem = NULL;

retErr = LOS_ERRNO_SEM_TIMEOUT;

}

OUT:

SCHEDULER_UNLOCK(intSave);

return retErr;

}

分析如下: 这个函数有点复杂,大量的goto,但别被它绕晕了,盯着返回值看. 先说结果只有一种情况下申请信号量能成功(即retErr == LOS_OK)

if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了

semPended->semCount--;//资源少了一个

goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的

}

其余申请失败的原因有:

信号量ID超出范围(默认1024)

中断发生期间

系统任务

信号量状态不对,信号量ID不匹配

以上都是异常的判断,再说正常情况下semPended->semCount = 0时的情况,没有资源了怎么办? 任务进入OsTaskWait睡眠状态,怎么睡,睡多久,由参数timeout定timeout值分以下三种模式:

无阻塞模式:即任务申请信号量时,入参timeout等于0。若当前信号量计数值不为0,则申请成功,否则立即返回申请失败。

永久阻塞模式:即任务申请信号量时,入参timeout等于0xFFFFFFFF。若当前信号量计数值不为0,则申请成功。 否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行。

定时阻塞模式:即任务申请信号量时,0在OsTaskWait中,任务将被挂入semList链表,semList上挂的都是等待这个信号量的任务.

释放信号量

LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)

{

LosSemCB *semPosted = NULL;

LosTaskCB *resumedTask = NULL;

if (GET_SEM_INDEX(semHandle) >= LOSCFG_BASE_IPC_SEM_LIMIT) {

return LOS_ERRNO_SEM_INVALID;

}

semPosted = GET_SEM(semHandle);

if ((semPosted->semID != semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) {

return LOS_ERRNO_SEM_INVALID;

}

/* Update the operate time, no matter the actual Post success or not */

OsSemDbgTimeUpdateHook(semHandle);

if (semPosted->semCount == OS_SEM_COUNT_MAX) {//当前信号资源不能大于最大资源量

return LOS_ERRNO_SEM_OVERFLOW;

}

if (!LOS_ListEmpty(&semPosted->semList)) {//当前有任务挂在semList上,要去唤醒任务

resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));//semList上面挂的都是task->pendlist节点,取第一个task下来唤醒

resumedTask->taskSem = NULL;//任务不用等信号了,重新变成NULL值

OsTaskWake(resumedTask);//唤醒任务,注意resumedTask一定不是当前任务,OsTaskWake里面并不会自己切换任务上下文,只是设置状态

if (needSched != NULL) {//参数不为空,就返回需要调度的标签

*needSched = TRUE;//TRUE代表需要调度

}

} else {//当前没有任务挂在semList上,

semPosted->semCount++;//信号资源多一个

}

return LOS_OK;

}

LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)

{

UINT32 intSave;

UINT32 ret;

BOOL needSched = FALSE;

SCHEDULER_LOCK(intSave);

ret = OsSemPostUnsafe(semHandle, &needSched);

SCHEDULER_UNLOCK(intSave);

if (needSched) {//需要调度的情况

LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令

LOS_Schedule();发起调度

}

return ret;

}

分析如下:

注意看在什么情况下semPosted->semCount才会 ++ ,是在LOS_ListEmpty为真的时候,semList是等待这个信号量的任务.semList上的任务是在OsTaskWait中挂入的.都在等这个信号.

每次OsSemPost都会唤醒semList链表上一个任务,直到semList为空.

掌握信号量的核心是理解LOS_SemPend和LOS_SemPost

编程示例

本实例实现如下功能:

测试任务Example_TaskEntry创建一个信号量,锁任务调度,创建两个任务Example_SemTask1、Example_SemTask2,Example_SemTask2优先级高于Example_SemTask1,两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务Example_TaskEntry释放信号量。

Example_SemTask2得到信号量,被调度,然后任务休眠20Tick,Example_SemTask2延迟,Example_SemTask1被唤醒。

Example_SemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被Example_SemTask2持有,Example_SemTask1挂起,10Tick后仍未得到信号量, Example_SemTask1被唤醒,试图以永久阻塞模式申请信号量,Example_SemTask1挂起。

20Tick后Example_SemTask2唤醒, 释放信号量后,Example_SemTask1得到信号量被调度运行,最后释放信号量。

Example_SemTask1执行完,40Tick后任务Example_TaskEntry被唤醒,执行删除信号量,删除两个任务。

/* 任务ID */

static UINT32 g_testTaskId01;

static UINT32 g_testTaskId02;

/* 测试任务优先级 */

#define TASK_PRIO_TEST 5

/* 信号量结构体id */

static UINT32 g_semId;

VOID Example_SemTask1(VOID)

{

UINT32 ret;

printf("Example_SemTask1 try get sem g_semId ,timeout 10 ticks.\n");

/* 定时阻塞模式申请信号量,定时时间为10ticks */

ret = LOS_SemPend(g_semId, 10);

/*申请到信号量*/

if (ret == LOS_OK) {

LOS_SemPost(g_semId);

return;

}

/* 定时时间到,未申请到信号量 */

if (ret == LOS_ERRNO_SEM_TIMEOUT) {

printf("Example_SemTask1 timeout and try get sem g_semId wait forever.\n");

/*永久阻塞模式申请信号量*/

ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);

printf("Example_SemTask1 wait_forever and get sem g_semId .\n");

if (ret == LOS_OK) {

LOS_SemPost(g_semId);

return;

}

}

}

VOID Example_SemTask2(VOID)

{

UINT32 ret;

printf("Example_SemTask2 try get sem g_semId wait forever.\n");

/* 永久阻塞模式申请信号量 */

ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);

if (ret == LOS_OK) {

printf("Example_SemTask2 get sem g_semId and then delay 20ticks .\n");

}

/* 任务休眠20 ticks */

LOS_TaskDelay(20);

printf("Example_SemTask2 post sem g_semId .\n");

/* 释放信号量 */

LOS_SemPost(g_semId);

return;

}

UINT32 ExampleTaskEntry(VOID)

{

UINT32 ret;

TSK_INIT_PARAM_S task1;

TSK_INIT_PARAM_S task2;

/* 创建信号量 */

LOS_SemCreate(0,&g_semId);

/* 锁任务调度 */

LOS_TaskLock();

/*创建任务1*/

(VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));

task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;

task1.pcName = "TestTsk1";

task1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;

task1.usTaskPrio = TASK_PRIO_TEST;

ret = LOS_TaskCreate(&g_testTaskId01, &task1);

if (ret != LOS_OK) {

printf("task1 create failed .\n");

return LOS_NOK;

}

/* 创建任务2 */

(VOID)memset_s(&task2, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));

task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;

task2.pcName = "TestTsk2";

task2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;

task2.usTaskPrio = (TASK_PRIO_TEST - 1);

ret = LOS_TaskCreate(&g_testTaskId02, &task2);

if (ret != LOS_OK) {

printf("task2 create failed .\n");

return LOS_NOK;

}

/* 解锁任务调度 */

LOS_TaskUnlock();

ret = LOS_SemPost(g_semId);

/* 任务休眠40 ticks */

LOS_TaskDelay(40);

/* 删除信号量 */

LOS_SemDelete(g_semId);

/* 删除任务1 */

ret = LOS_TaskDelete(g_testTaskId01);

if (ret != LOS_OK) {

printf("task1 delete failed .\n");

return LOS_NOK;

}

/* 删除任务2 */

ret = LOS_TaskDelete(g_testTaskId02);

if (ret != LOS_OK) {

printf("task2 delete failed .\n");

return LOS_NOK;

}

return LOS_OK;

}

实例运行结果:

Example_SemTask2 try get sem g_semId wait forever.

Example_SemTask1 try get sem g_semId ,timeout 10 ticks.

Example_SemTask2 get sem g_semId and then delay 20ticks .

Example_SemTask1 timeout and try get sem g_semId wait forever.

Example_SemTask2 post sem g_semId .

Example_SemTask1 wait_forever and get sem g_semId .

编辑:hfy

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HarmonyOS是华为公司自主研发的操作系统,其内核是实现系统各项功能的核心部分。对于HarmonyOS内核码的分析,可以从以下几个方面进行讨论。 首先,HarmonyOS内核码的分析可以关注其整体架构。HarmonyOS内核采用微内核架构,将各个功能模块拆分成独立的服务,通过消息传递进行通信,实现了更高的灵活性和可靠性。通过分析内核的整体架构,可以深入了解到HarmonyOS是如何进行进程管理、内存管理、文件系统等各个方面的功能实现。 其次,可以重点关注HarmonyOS内核的调度机制。调度机制是操作系统内核决定进程、线程执行顺序和时间分配的重要部分。HarmonyOS内核采用了全局时钟中断驱动的抢占式调度机制,能够确保不同任务的公平和高效执行。通过对调度机制的分析可以了解到HarmonyOS内核是如何进行多任务切换、时间片轮转以及任务优先级管理的。 此外,HarmonyOS内核码分析还可以关注线程同步和通信机制。线程同步和通信是多线程协作的基础,也是操作系统内核重要的功能之一。HarmonyOS内核通过互斥锁、条件变信号机制实现了线程之间的同步和通信。了解这些机制可以更好地理解HarmonyOS是如何处理多线程并发访问共享资和协调线程之间的执行顺序的。 最后,分析HarmonyOS内核码还可以关注其安全性。安全性是一个操作系统内核不能忽视的重要问题。HarmonyOS内核采用了多种安全机制,如安全IPC、安全网卡等,确保系统资和用户数据的安全。通过分析内核码中的安全措施可以了解到HarmonyOS是如何保障系统的安全性并防止恶意攻击。 综上所述,对于HarmonyOS内核码的分析需要关注整体架构、调度机制、线程同步和通信机制以及安全性等方面。通过深入分析内核码,可以更好地了解操作系统的具体实现细节和原理,为开发者提供更好的参考和指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值