在本节,我们主要对Linux内核中notifier的实现机制进行分析,包括架构设计概览、模块功能详解、配置信息解析、主要数据结构、主要接口介绍以及内核使用场景等。
nofitier作为一个基础组件,使用的场景很多,这里我们以PM notifier在低功耗软件栈的位置为例,如图5-1所示。
这里之所以把notifier纳入我们讲解的范围,主要是因为低功耗框架基于notifier封装了PM notifier,所以在介绍PM notifier之前,我们先对基础notifier框架进行简单说明。
图5-1 PM notifier在低功耗软件栈中的位置
notifier也是一种通过链表实现的消息通知机制,任何模块都可以基于基础的notifier框架来封装自己的notifier,比如低功耗模块可以封装pm_notifier,调频模块可以封装freq_notifier,panic模块可以封装panic_notifier,die模块可以封装die_notifier,等等。
1)notifier函数声明在include\linux\notifier.h中,相关实现在kernel\notifier.c文件中。
2)notifier.c的实现为基础功能,没有受编译宏控制,默认参与编译。
notif ier的主要结构体如下:
为了更加清晰地对结构体做对比说明,我们以表格的形式来展示,如表5-1所示。
表5-1 notifier的4个结构体的对比说明
1)三类notifier的静态初始化接口,分别对应原子上下文、任务上下文、裸上下文的notifier节点的静态初始化方式。相关宏定义代码如下所示:
2)三类notifier的动态初始化接口,分别对应原子上下文、任务上下文、裸上下文的notifier节点的动态初始化方式。动态初始化有个限制,就是使用前一定要等初始化完成才行,否则无法操作对应的notifier;而静态初始化就没有这个限制,可以随时使用。相关定义代码段如下所示。
由结构体定义可知,notifier函数也同样可以分为三类——原子上下文类、任务上下文类、裸上下文类,如表5-2所示。
表5-2 notifier函数汇总
三类注册函数都是对notifier_chain_register的一层封装,只是分别加了各自的自旋锁、信号量控制。对于notifier_chain_register的说明如下:
该函数的主要功能是为三类注册函数提供最底层的通用功能,负责将notifier注册到对应的链表中。
函数原型如下:
入参**nl表示要插入的目标链表,入参*n表示要插入目标链表的节点。该函数返回0时表示执行成功,返回其他值时表示执行失败。
notifier_chain_register的具体实现代码如下:
可以看到,该函数返回0,表示插入成功。如果待插入节点已经在目标链表中,则不需要多余的操作,否则需要按照优先级从大到小的顺序插入链表中。priority越大,优先级越高。
三类去注册函数都是对notifier_chain_unregister的一层封装,只是分别加了各自的自旋锁、信号量控制。对于notifier_chain_unregister的说明如下:
该函数的主要功能是为三类去注册函数提供最底层的通用功能,负责从目标链表删除对应节点。
函数原型如下:
入参**nl表示要删除节点的目标链表,入参*n表示要从目标链表中删除的节点。该函数返回0时表示执行成功,返回其他值时表示执行失败。
notifier_chain_unregister的具体实现代码如下:
如果节点存在链表中,则删除之,并返回成功。如果不在链表中,则返回错误码。
三类notifier_call_chain函数(atomic_notifier_call_chain、blocking_notifier_call_chain、raw_notifier_call_chain)都是对notifier_call_chain的一层封装,只不过分别加了各自的自旋锁、信号量控制。对于notifier_call_chain的说明如下:
该函数的主要功能是回调链表中每个节点的notifier_call回调函数。
函数原型如下:
入参**nl表示要执行回调函数的目标链表;入参val是传递给notif ier回调函数的参数;入参*v是传递给notifier回调函数的指针;入参nr_to_call表示需要执行的回调函数节点的个数,如果是-1可以不用关注;出参*nr_calls表示执行了多少个回调函数节点。该函数返回0时表示执行成功,返回其他值时表示执行失败。
notifier_call_chain的具体实现代码如下:
函数会循环执行目标链表中每个节点的notifier_call回调函数,如果返回值与NOTIFY_STOP_MASK相与不为0,则停止执行并返回。
三类notifier_call_chain_robust函数(atomic_notifier_call_chain_robust、blocking_notifier_call_chain_robust、raw_notif ier_call_chain_robust)都是对notif ier_call_chain_robust的一层封装,只是分别加了各自的自旋锁、信号量控制。对于notif ier_call_chain_robust的说明如下:
该函数的主要功能,对于回调链表中每个节点的notifier_call回调函数,如果执行失败,则对已经执行过notifier_call回调的节点重新进行回调,但要把入参由val_up改为val_down。以pm_notifier为例,我们可以把这两个参数设为互反的两个值——suspend、resume,这样即使回调同一个回调函数,由于入参不同(即要执行的阶段不同),回调函数也可以用switch语句分别进行处理。
函数原型如下:
入参**nl表示要执行回调函数的目标链表;入参val_up和val_down都是传递给notifier回调函数的参数;入参*v是传递给notifier回调函数的指针;入参nr_to_call表示需要执行的回调函数节点的个数,如果是-1,则可以不关注;出参*nr_calls表示执行了多少个回调函数节点。该函数的返回值是最后一个notifier回调函数的返回值。
notifier_call_chain_robust的具体实现代码如下:
该函数是对notifier_call_chain的一层封装。循环执行目标链表中每个节点的notifier_call回调函数,如果返回值失败,则停止执行并对已执行的节点执行逆操作。
可以通过调用register_die_notifier注册die函数的notifier。当注册成功后,一旦die函数被调用,最终会调用到notify_die,从而调用到注册的notifier_call,这样当oops发生时,对这件事敏感的注册模块就可以执行自己的处理流程,比如保存死机或复位场景的“临终遗言”等。die notifier封装的是原子类函数,所有注册信息维护在die_chain中。相关实现代码如下所示:
可以通过调用atomic_notifier_chain_register注册到panic_notifier_list上,我们以内核的drivers\misc\ibmasm\heartbeat.c为例:通过ibmasm_register_panic_notifier注册对panic敏感的notifier回调函数,这样当panic函数被调用时,就能调用到注册的回调函数。相关实现代码如下所示:
当期望系统在reboot事件发生时回调到指定的回调函数时,可以调用register_reboot_notifier注册到reboot_notifier_list,这样当注册成功后,一旦系统发生reboot事件,就会回调到注册的回调函数中。相关实现代码如下所示:
当期望在低功耗睡眠/唤醒事件发生时执行指定的处理函数时,可以通过调用register_pm_notifier注册到pm_chain_head来实现。相关实现代码如下所示:
当注册成功后,一旦系统发生低功耗事件,就会调用注册的回调函数。我们以suspend_prepare为例看一下PM Core是如何处理PM notifier的:
本节只介绍了关于notifier的4个使用场景,内核中使用notifier的场景还有很多,在实际应用中可以根据需要对notifier.c中的实现进行封装。