百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | 掘金 | harmony >
读本篇之前建议先读鸿蒙内核源码分析(总目录)其余篇.
typedef VOID (*SWTMR_PROC_FUNC)(UINTPTR arg);//函数指针, 赋值给 SWTMR_CTRL_S->pfnHandler,回调处理
typedef struct tagSwTmrCtrl {
//软件定时器控制块
SortLinkList stSortList;//通过它挂到对应CPU核定时器链表上
UINT8 ucState; /**< Software timer state *///软件定时器的状态
UINT8 ucMode; /**< Software timer mode *///软件定时器的模式
UINT8 ucOverrun; /**< Times that a software timer repeats timing *///软件定时器重复计时的次数
UINT16 usTimerID; /**< Software timer ID *///软件定时器ID,唯一标识,由软件计时器池分配
UINT32 uwCount; /**< Times that a software timer works *///软件定时器工作的时间
UINT32 uwInterval; /**< Timeout interval of a periodic software timer *///周期性软件定时器的超时间隔
UINT32 uwExpiry; /**< Timeout interval of an one-off software timer *///一次性软件定时器的超时间隔
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 uwCpuid; /**< The cpu where the timer running on *///多核情况下,定时器运行的cpu
#endif
UINTPTR uwArg; /**< Parameter passed in when the callback function
that handles software timer timeout is called *///回调函数的参数
SWTMR_PROC_FUNC pfnHandler; /**< Callback function that handles software timer timeout */ //处理软件计时器超时的回调函数
UINT32 uwOwnerPid; /** Owner of this software timer *///软件定时器所属进程ID号
} SWTMR_CTRL_S;//变量前缀 uc:UINT8 us:UINT16 uw:UINT32
解读
在多CPU核情况下,定时器是跟着CPU走的,每个CPU核都维护着独立的定时任务链表,上面挂的都是CPU核要处理的定时器.
stSortList
的背后是双向链表,这对钩子在定时器创建的那一刻会钩到CPU的swtmrSortLink
上去.
pfnHandler
定时器时间到了的执行函数,由外界指定.uwArg
为回调函数的参数
ucMode
为定时器模式,软件定时器提供了三类模式
单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动删除。
周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动停止定时器,否则将永远持续执行下去。
单次触发定时器,但这类定时器超时触发后不会自动删除,需要调用定时器删除接口删除定时器。
ucState
定时器状态.
OS_SWTMR_STATUS_UNUSED(定时器未使用)
系统在定时器模块初始化时,会将系统中所有定时器资源初始化成该状态。
OS_SWTMR_STATUS_TICKING(定时器处于计数状态)
在定时器创建后调用LOS_SwtmrStart接口启动,定时器将变成该状态,是定时器运行时的状态。
OS_SWTMR_STATUS_CREATED(定时器创建后未启动,或已停止)
定时器创建后,不处于计数状态时,定时器将变成该状态。
定时器是指从指定的时刻开始,经过一定的指定时间后触发一个事件,例如定个时间提醒晚上9点准时秒杀。定时器有硬件定时器和软件定时器之分:
硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。
鸿蒙内核提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是 OS Tick 的整数倍,例如鸿蒙内核默认是10ms触发一次,那么上层软件定时器只能是 10ms,20ms,100ms 等,而不能定时为 15ms。
LITE_OS_SEC_BSS SWTMR_CTRL_S *g_swtmrCBArray = NULL; /* First address in Timer memory space *///定时器池
LITE_OS_SEC_BSS UINT8 *g_swtmrHandlerPool = NULL; /* Pool of Swtmr Handler *///用于注册软时钟的回调函数
LITE_OS_SEC_BSS LOS_DL_LIST g_swtmrFreeList; /* Free list of Software Timer *///空闲定时器链表
typedef struct {
//处理软件定时器超时的回调函数的结构体
SWTMR_PROC_FUNC handler; /**< Callback function that handles software timer timeout */ //处理软件定时器超时的回调函数
UINTPTR arg; /**< Parameter passed in when the callback function
that handles software timer timeout is called */ //调用处理软件计时器超时的回调函数时传入的参数
} SwtmrHandlerItem;
解读
三个全局变量可知,定时器是通过池来管理,在初始化阶段赋值.
g_swtmrCBArray
定时器池,初始化中一次性创建1024个定时器控制块供使用g_swtmrHandlerPool
回调函数池,回调函数也是统一管理的,申请了静态内存保存. 池中放的是 SwtmrHandlerItem
回调函数描述符.g_swtmrFreeList
空闲可供分配的定时器链表,鸿蒙的进程池,任务池,事件池都是这么处理的,没有印象的自行去 鸿蒙内核源码分析(总目录)< OSCHINA | CSDN > 翻看. g_swtmrFreeList
上挂的是一个个的 SWTMR_CTRL_S
SWTMR_CTRL_S
和 SwtmrHandlerItem
的关系,前者是一个定时器,后者是定时器时间到了去哪里干活.#define LOSCFG_BASE_CORE_SWTMR_LIMIT 1024 // 最大支持的软件定时器数
LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrInit(VOID)
{
UINT32 size;
UINT16 index;
UINT32 ret;
SWTMR_CTRL_S *swtmr = NULL;
UINT32 swtmrHandlePoolSize;
UINT32 cpuid = ArchCurrCpuid();
if (cpuid == 0) {
//确保以下代码块由一个CPU执行,g_swtmrCBArray和g_swtmrHandlerPool 是所有CPU共用的
size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT;//申请软时钟内存大小
swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); /* system resident resource */ //常驻内存
if (swtmr == NULL) {
return LOS_ERRNO_SWTMR_NO_MEMORY;
}
(VOID)memset_s(swtmr, size, 0, size);//清0
g_swtmrCBArray = swtmr;//软时钟
LOS_ListInit(&g_swtmrFreeList);//初始化空闲链表
for (index = 0; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) {
swtmr->usTimerID = index;//按顺序赋值
LOS_ListTailInsert(&g_swtmrFreeList, &swtmr->stSortList.sortLinkNode);//通过sortLinkNode将节点挂到空闲链表
}
//想要用静态内存池管理,就必须要使用LOS_MEMBOX_SIZE来计算申请的内存大小,因为需要点前缀内存承载头部信息.
swtmrHandlePoolSize = LOS_MEMBOX_SIZE(sizeof(SwtmrHandlerItem), OS_SWTMR_HANDLE_QUEUE_SIZE);//计算所有注册函数内存大小
//规划一片内存区域作为软时钟处理函数的静态内存池。
g_swtmrHandlerPool = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, swtmrHandlePoolSize); /* system resident resource *///常驻内存
if (g_swtmrHandlerPool == NULL) {
return LOS_ERRNO_SWTMR_NO_MEMORY;
}
ret = LOS_MemboxInit(g_swtmrHandlerPool, swtmrHandlePoolSize, sizeof(SwtmrHandlerItem));//初始化软时钟注册池
if (ret != LOS_OK) {
return LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM;
}
}
//每个CPU都会创建一个属于自己的 OS_SWTMR_HANDLE_QUEUE_SIZE 的队列
ret = LOS_QueueCreate(NULL, OS_SWTMR_HANDLE_QUEUE_SIZE, &g_percpu[cpuid].swtmrHandlerQueue, 0, sizeof(CHAR *));//为当前CPU core 创建软时钟队列 maxMsgSize:sizeof(CHAR *)
if (ret != LOS_OK) {
return LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED;
}
ret = OsSwtmrTaskCreate();//每个CPU独自创建属于自己的软时钟任务,统一处理队列
if (ret != LOS_OK) {
return LOS_ERRNO_SWTMR_TASK_CREATE_FAILED;
}
ret = OsSortLinkInit(&g_percpu[cpuid].swtmrSortLink);//每个CPU独自对自己软时钟链表排序初始化,为啥要排序因为每个定时器的时间不一样,鸿蒙把用时短的排在前面
if (ret != LOS_OK) {
return LOS_ERRNO_SWTMR_SORTLINK_CREATE_FAILED;
}
return LOS_OK;
}
代码解读:
OsSwtmrInit
是负责初始化各CPU核定时模块功能的,注意在多CPU核时,OsSwtmrInit
会被多次调用.cpuid == 0
代表主CPU核, 它最早执行这个函数,所以g_swtmrCBArray
和g_swtmrHandlerPool
是共用的,系统默认最多支持 1024 个定时器和回调函数.LOS_QueueCreate
队列和任务OsSwtmrTaskCreate
,并初始化了swtmrSortLink
链表,关于链表排序可前往系列篇总目录 排序链表篇查看.LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrTaskCreate(VOID)
{
UINT32 ret, swtmrTaskID;
TSK_INIT_PARAM_S swtmrTask;
UINT32 cpuid = ArchCurrCpuid();//获取当前CPU id
(VOID)memset_s(&swtmrTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));//清0
swtmrTask.pfnTaskEntry = (TSK_ENTRY_FUNC)OsSwtmrTask;//入口函数
swtmrTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K默认内核任务栈
swtmrTask.pcName = "Swt_Task";//任务名称
swtmrTask.usTaskPrio = 0;//哇塞! 逮到一个最高优先级的任务 @note_thinking 这里应该用 OS_TASK_PRIORITY_HIGHEST 表示
swtmrTask.uwResved = LOS_TASK_STATUS_DETACHED;//分离模式
#if (LOSCFG_KERNEL_SMP == YES)
swtmrTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(cpuid);//交给当前CPU执行这个任务
#endif
ret = LOS_TaskCreate(&swtmrTaskID, &swtmrTask);//创建任务并申请调度
if (ret == LOS_OK) {
g_percpu[cpuid].swtmrTaskID = swtmrTaskID;//全局变量记录 软时钟任务ID
OS_TCB_FROM_TID(swtmrTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//告知这是一个系统任务
}
return ret;
}
代码解读:
swtmrTask.usTaskPrio = 0;
0是最高优先级! 这并不多见! 内核会在第一时间响应软时钟任务.g_percpu[cpuid].swtmrTaskID = swtmrTaskID;
表示创建的任务和CPU具体核进行了捆绑.从此swtmrTaskID负责这个CPU的定时器处理.OsSwtmrTask
,是任务的执行体,类似于[Java 线程中的run()函数]usCpuAffiMask
代表这个任务只能由这个CPU核来跑//软时钟的入口函数,拥有任务的最高优先级 0 级!
LITE_OS_SEC_TEXT VOID OsSwtmrTask(VOID)
{
SwtmrHandlerItemPtr swtmrHandlePtr = NULL;
SwtmrHandlerItem swtmrHandle;
UINT32 ret, swtmrHandlerQueue;
swtmrHandlerQueue = OsPercpuGet()->swtmrHandlerQueue;//获取定时器超时队列
for (;;) {
//死循环获取队列item,一直读干净为止
ret = LOS_QueueRead(swtmrHandlerQueue, &swtmrHandlePtr, sizeof(CHAR *), LOS_WAIT_FOREVER);//一个一个读队列
if ((ret == LOS_OK) && (swtmrHandlePtr != NULL)) {
swtmrHandle.handler = swtmrHandlePtr->handler;//超时中断处理函数,也称回调函数
swtmrHandle.arg = swtmrHandlePtr->arg;//回调函数的参数
(VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandlePtr);//静态释放内存,注意在鸿蒙内核只有软时钟注册用到了静态内存
if (swtmrHandle.handler != NULL) {
swtmrHandle.handler(swtmrHandle.arg);//回调函数处理函数
}
}
}
}
代码解读:
OsSwtmrTask
只是在不断的消费队列,那生产者在哪里呢? 就是 OsSwtmrScan
LITE_OS_SEC_TEXT VOID OsSwtmrScan(VOID)//扫描定时器,如果碰到超时的,就放入超时队列
{
SortLinkList *sortList = NULL;
SWTMR_CTRL_S *swtmr = NULL;
SwtmrHandlerItemPtr swtmrHandler = NULL;
LOS_DL_LIST *listObject = NULL;
SortLinkAttribute* swtmrSortLink = &OsPercpuGet()->swtmrSortLink;//拿到当前CPU的定时器链表
swtmrSortLink->cursor = (swtmrSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
listObject = swtmrSortLink->sortLink + swtmrSortLink->cursor;
//由于swtmr是在特定的sortlink中,所以需要很小心的处理它,但其他CPU Core仍然有机会处理它,比如停止计时器
/*
* it needs to be carefully coped with, since the swtmr is in specific sortlink
* while other cores still has the chance to process it, like stop the timer.
*/
LOS_SpinLock(&g_swtmrSpin);
if (LOS_ListEmpty(listObject)) {
LOS_SpinUnlock(&g_swtmrSpin);
return;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
ROLLNUM_DEC(sortList->idxRollNum);
while (ROLLNUM(sortList->idxRollNum) == 0) {
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
LOS_ListDelete(&sortList->sortLinkNode);
swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList);
swtmrHandler = (SwtmrHandlerItemPtr)LOS_MemboxAlloc(g_swtmrHandlerPool);//取出一个可用的软时钟处理项
if (swtmrHandler != NULL) {
swtmrHandler->handler = swtmr->pfnHandler;
swtmrHandler->arg = swtmr->uwArg;
if (LOS_QueueWrite(OsPercpuGet()->swtmrHandlerQueue, swtmrHandler, sizeof(CHAR *), LOS_NO_WAIT)) {
(VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandler);
}
}
if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) {
OsSwtmrDelete(swtmr);
if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) {
swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT;
} else {
swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT;
}
} else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) {
swtmr->ucState = OS_SWTMR_STATUS_CREATED;
} else {
swtmr->ucOverrun++;
OsSwtmrStart(swtmr);
}
if (LOS_ListEmpty(listObject)) {
break;
}
sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
}
LOS_SpinUnlock(&g_swtmrSpin);
}
代码解读:
OsTickHandler
中调用的,它就干一件事,不停的比较定时器是否超时OsSwtmrTask
去消费.定时器池 g_swtmrCBArray
存储内核所有的定时器,默认1024个,各CPU共享这个池
定时器响应函数池g_swtmrHandlerPool
存储内核所有的定时器响应函数,默认1024个,各CPU也共享这个池
每个CPU核都有独立的任务(线程)来处理定时器, 这个任务叫定时任务
每个CPU核都有独立的响应函数队列swtmrHandlerQueue
,队列中存放该核时间到了的响应函数SwtmrHandlerItem
定时任务的优先级最高,循环读取队列swtmrHandlerQueue
, swtmrHandlerQueue
中存放是定时器时间到了的响应函数.并一一回调这些响应函数.
OsSwtmrScan负责扫描定时器的时间是否到了,到了就往队列swtmrHandlerQueue
中扔.
定时器有多种模式,包括单次,循环.所以循环类定时器的响应函数会多次出现在swtmrHandlerQueue
中.
v44.03 (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | harmony | 掘金 >
v43.03 (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | harmony | 掘金 >
v42.03 (中断切换篇) | 中断切换到底在切换什么? < csdn | harmony | 掘金 >
v41.03 (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | harmony | 掘金 >
v40.03 (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | harmony | 掘金 >
v39.03 (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony | 掘金 >
v38.03 (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | harmony | 掘金 >
v37.03 (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | harmony | 掘金 >
v36.03 (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | harmony | 掘金 >
v35.03 (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | harmony | 掘金 >
v34.03 (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | harmony | 掘金 >
v33.03 (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | harmony | 掘金 >
v32.03 (CPU篇) | 内核是如何描述CPU的? < csdn | harmony | 掘金 >
v31.03 (定时器篇) | 内核最高优先级任务是谁? < csdn | harmony | 掘金 >
v30.03 (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony | 掘金 >
v29.03 (信号量篇) | 信号量解决任务同步问题 < csdn | harmony | 掘金 >
v28.03 (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | harmony | 掘金 >
v27.03 (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | harmony | 掘金 >
v26.03 (自旋锁篇) | 想为自旋锁立贞节牌坊! < csdn | harmony | 掘金 >
v25.03 (并发并行篇) | 怎么记住并发并行的区别? < csdn | harmony | 掘金 >
v24.03 (进程概念篇) | 进程在管理哪些资源? < csdn | harmony | 掘金 >
v23.02 (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | harmony | 掘金 >
v22.02 (汇编基础篇) | CPU在哪里打卡上班? < csdn | harmony | 掘金 >
v21.02 (线程概念篇) | 是谁在不断的折腾CPU? < csdn | harmony | 掘金 >
v20.02 (用栈方式篇) | 栈是构建底层运行的基础 < csdn | harmony | 掘金 >
v19.02 (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | harmony | 掘金 >
v18.02 (源码结构篇) | 内核500问你能答对多少? < csdn | harmony | 掘金 >
v17.02 (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | harmony | 掘金 >
v16.02 (内存规则篇) | 内存管理到底在管什么? < csdn | harmony | 掘金 >
v15.02 (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | harmony | 掘金 >
v14.02 (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | harmony | 掘金 >
v13.02 (源码注释篇) | 热爱是所有的理由和答案 < csdn | harmony | 掘金 >
v12.02 (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | harmony | 掘金 >
v11.02 (内存分配篇) | 内存有哪些分配方式? < csdn | harmony | 掘金 >
v10.02 (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | harmony | 掘金 >
v09.02 (调度故事篇) | 用故事说内核调度 < csdn | harmony | 掘金 >
v08.02 (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony | 掘金 >
v07.02 (调度机制篇) | 任务是如何被调度执行的? < csdn | harmony | 掘金 >
v06.02 (调度队列篇) | 就绪队列对调度的作用 < csdn | harmony | 掘金 >
v05.02 (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | harmony | 掘金 >
v04.02 (任务调度篇) | 任务是内核调度的单元 < csdn | harmony | 掘金 >
v03.02 (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | harmony | 掘金 >
v02.02 (进程管理篇) | 进程是内核资源管理单元 < csdn | harmony | 掘金 >
v01.09 (双向链表篇) | 谁是内核最重要结构体? < csdn | harmony | 掘金 >
访问注解仓库地址
Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request
新建 Issue
关注「鸿蒙内核源码分析」公众号,百万汉字注解 + 百篇博客分析 => 深挖鸿蒙内核源码
各大站点搜 “鸿蒙内核源码分析” .欢迎转载,请注明出处.