鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作 原创 精华

zhushangyuan_
发布于 2021-10-14 08:38
浏览
2收藏

鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作

【本文正在参与优质创作者激励】

ExcHook异常钩子模块是OpenHarmony LiteOS-M内核的一个可选组件,提供注册钩子函数、解除注册钩子函数的核心API,支持保存异常上下文、任务信息、队列信息、中断寄存器状态、任务切换信息、内存分配信息等。由于异常钩子模块内容较多,我们分为几篇进行分析源码,包含如何调用钩子函数、如何注册如何调用,如何收集异常信息等。本篇介绍下异常钩子模块对外提供异常钩子函数的注册和解除注册等操作接口API。

本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。鸿蒙轻内核异常钩子模块代码主要在components\exchook目录下。异常钩子函数的注册、解注册、异常钩子类型定义在utils\los_debug.h|.c


1、异常钩子函数节点结构体和异常钩子函数节点数组

在文件components\exchook\los_exchook.c定义异常钩子的一些宏、函数节点结构体和一些全局变量函数节点数组。⑴处定义的宏设置当前系统支持的钩子函数的个数。⑵处定义的钩子函数节点结构体,每个节点除了异常钩子函数定义还有一个指向下一个节点的指针。⑶处定义的全局变量数组g_excNodes,注册的每一个异常钩子函数都使用一个节点来维护。⑷处定义异常钩子函数节点指针数组,数组的每个元素指针指向对应类型的异常钩子节点单向链表,而最后一个元素g_excHeads[EXC_TYPE_END]指向空闲的钩子函数节点链表。

    #ifndef LOSCFG_BASE_EXC_HOOK_LIMIT
⑴  #define LOSCFG_BASE_EXC_HOOK_LIMIT  16
    #endif

⑵  struct Node {
        ExcHookFn excHookFn;
        struct Node *next;
    };

⑶  STATIC struct Node g_excNodes[LOSCFG_BASE_EXC_HOOK_LIMIT];
⑷  STATIC struct Node *g_excHeads[EXC_TYPE_END + 1]; /* EXC_TYPE_END is used for the free list. */

2、异常钩子函数的注册操作

文件components\exchook\los_exchook.c中主要定义了异常钩子函数的注册LOS_RegExcHook和解除注册LOS_UnRegExcHook对外接口函数。接下来,我们分析注册操作的源代码。

2.1 注册LOS_RegExcHook

在分析注册钩子函数的函数之前,我们先分析下如何通过调用GetFreeNode()获取钩子函数空闲节点,代码如下。g_excHeads[EXC_TYPE_END]指向钩子函数空闲节点单向链表的第一个节点。⑴处如果为空说明未初始化或者空闲节点使用完毕。⑵处如果第1个空闲节点g_excNodes[0]被使用,说明已经没有空闲节点,返回NULL即可。否则执行⑶初始化空闲节点链表,初始化后g_excHeads[EXC_TYPE_END]执行g_excNodes数组的最后一个元素,然后数组的每个元素执行前一个元素,g_excNodes[0]是最后一个空闲节点,如下图所示。然后执行⑷处把函数DoExcHook注册为全局异常钩子函数g_excHook

鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作-鸿蒙开发者社区鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作-鸿蒙开发者社区

如果空闲节点链表不为空,执行⑸获取第一个空闲节点,然后g_excHeads[EXC_TYPE_END]指向下一个空闲节点。

STATIC struct Node *GetFreeNode(VOID)
{
    struct Node *node = NULL;
    int i;
⑴  if (g_excHeads[EXC_TYPE_END] == NULL) {
⑵      if (g_excNodes[0].excHookFn != NULL) {
            /* no free node now */
            return NULL;
        } else {
            /* Initialize the free list */
⑶          for (i = 0; i < LOSCFG_BASE_EXC_HOOK_LIMIT; ++i) {
                g_excNodes[i].next = g_excHeads[EXC_TYPE_END];
                g_excHeads[EXC_TYPE_END] = &g_excNodes[i];
            }
⑷          OsExcHookRegister(DoExcHook);
        }
    }

⑸  node = g_excHeads[EXC_TYPE_END];
    g_excHeads[EXC_TYPE_END] = node->next;
    return node;
}

下面我们接着看注册异常钩子函数的LOS_RegExcHook函数的源代码。⑴处先判断传入参数的合法性,⑵处获取空闲钩子函数节点,如果获取节点为空,则无法注册钩子函数。⑶处设置节点的钩子函数成员变量为传入的钩子函数,然后设置其下一个节点为该异常类型对应的钩子函数链表的第一个节点,然后把最后注册的钩子函数节点设置为第一个节点。因此,同一个异常类型可以注册多个钩子函数,他们维护为单向链表,第一个节点为g_excHeads[excType]。后注册的在链表的靠近链表头部。如下图所示。

鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作-鸿蒙开发者社区鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作-鸿蒙开发者社区

UINT32 LOS_RegExcHook(EXC_TYPE excType, ExcHookFn excHookFn)
{
    UINT32 intSave;
    struct Node *node = NULL;
⑴  if (excType >= EXC_TYPE_END || excHookFn == NULL) {
        return LOS_ERRNO_SYS_PTR_NULL;
    }

    intSave = LOS_IntLock();
⑵  node = GetFreeNode();
    if (node == NULL) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_SYS_HOOK_IS_FULL;
    }

⑶  node->excHookFn = excHookFn;
    node->next = g_excHeads[excType];
    g_excHeads[excType] = node;
    LOS_IntRestore(intSave);
    return LOS_OK;
}

2.2 解除注册LOS_UnRegExcHook

我们再看看解除注册钩子函数的LOS_UnRegExcHook函数的源码,⑴处先判断传入参数的合法性。我们已经知道,同一个类型的异常钩子函数节点使用单向链表维护,我们需要执行⑵处的循环进行遍历。遍历时,判断是否遍历到了要解除注册的函数,如果遍历到了,⑶成立,执行后续的代码。如果⑷成立,说明遍历到的节点不是第一个,把之前的节点执行当前要解除注册的节点的下一个节点,这样把要解除注册的节点从链表中进行删除。否则说明遍历到的节点为第一个,则执行⑸,把遍历到的下一个节点作为第一个节点,这样把要解除注册的节点从链表中进行删除。⑹处置空解除注册的节点,把该节点释放后作为空闲节点插入到钩子函数空闲节点链表g_excHeads[EXC_TYPE_END]的第一个位置上。

UINT32 LOS_UnRegExcHook(EXC_TYPE excType, ExcHookFn excHookFn)
{
    UINT32 intSave;
    struct Node *node = NULL;
    struct Node *preNode = NULL;
⑴  if (excType >= EXC_TYPE_END || excHookFn == NULL) {
        return LOS_ERRNO_SYS_PTR_NULL;
    }

    intSave = LOS_IntLock();
⑵  for (node = g_excHeads[excType]; node != NULL; node = node->next) {
⑶      if (node->excHookFn == excHookFn) {
⑷          if (preNode) {
                preNode->next = node->next;
            } else {
⑸              g_excHeads[excType] = node->next;
            }
⑹          node->excHookFn = NULL;
            node->next = g_excHeads[EXC_TYPE_END];
            g_excHeads[EXC_TYPE_END] = node;
        }
        preNode = node;
    }
    LOS_IntRestore(intSave);
    return LOS_OK;
}

3、异常钩子函数的执行

从上文中,我们了解到初始化钩子函数空闲链表后,通过语句OsExcHookRegister(DoExcHook)注册了异常钩子函数。现在我们就来分析下该函数的源码。DoExcHook函数会调用DoExcHookInRegOrder函数递归运行钩子函数链表上的所有节点维护的钩子函数。⑴处的代码调用函数DoExcHookInRegOrder,除了传入异常类型,还传入该异常类型对应的钩子函数节点链表的第一个节点。⑵处如果链表节点不为空,则递归调用该节点的下一个节点。⑶处执行异常钩子函数。因此,可以看得出,注册早的钩子函数在链表的尾部最先执行,注册晚的函数在链表的头部,最后执行。

STATIC VOID DoExcHookInRegOrder(EXC_TYPE excType, struct Node *node)
{
    if (node != NULL) {
⑵      DoExcHookInRegOrder(excType, node->next);
⑶      node->excHookFn(excType);
    }
}

STATIC VOID DoExcHook(EXC_TYPE excType)
{
    UINT32 intSave;
    if (excType >= EXC_TYPE_END) {
        return;
    }
    intSave = LOS_IntLock();
⑴  DoExcHookInRegOrder(excType, g_excHeads[excType]);
    LOS_IntRestore(intSave);
}

小结

本文介绍了异常钩子模块的对外注册函数LOS_RegExcHook和解除注册函数LOS_UnRegExcHook,对内部维护的钩子函数节点链表有了更深的理解。感谢阅读,如有任何问题、建议,都可以博客下留言给我,谢谢。

【本文正在参与优质创作者激励】

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-10-14 08:44:10修改
2
收藏 2
回复
举报
回复
    相关推荐