Linux 内核| 内核通知链机制


简介

Linux内核中存在不同的子系统,例如内存子系统、I/O子系统。每个子系统需要可能需要获取其他子系统的某些事件以进行相应的处理。

通知链(notification chains)机制允许子系统订阅某个事件,当该事件发生时就通知该子系统。这类似于MQTT[2]订阅/发布的方式。

通知机制Linux源码实现主要在include/linux/notifier.hkernel/notifier.c文件中。

相关结构体

当一个事件发生时,该事件对应的一连串回调函数将被调用,从而实现事件的通知。在Linux中,回调函数的定义如下:

typedef    int (*notifier_fn_t)(struct notifier_block *nb,
            unsigned long action, void *data);

其接收 3 个参数:

  • nb:回调函数组成的函数指针链表,下面会详细解析该结构体
  • action: 事件的类型。通知链可能支持多个事件,因此我们需要此参数来将一个事件与其他事件区分开
  • data:保存事件的私有信息,用来提供有关事件的其他数据信息。

返回值标识通知执行的结果,分为

#define NOTIFY_DONE        0x0000        /* 订阅者对该事件不感兴趣 */
#define NOTIFY_OK        0x0001        /* 通知正常完成 */
#define NOTIFY_STOP_MASK    0x8000    /* 通知已完成,但不应为此事件调用进一步的回调。 */
#define NOTIFY_BAD        (NOTIFY_STOP_MASK|0x0002) /*通知失败*/

希望在某个事件上得到通知的Linux内核子系统都应提供自己的notifier_fn_t回调函数。 通知链机制的主要作用是在发生异步事件时调用某些回调。

struct notifier_block {
    notifier_fn_t notifier_call;       // 回调函数
    struct notifier_block __rcu *next; // 下一个回调块
    int priority;                      // 优先级
};

Linux通知链机制提供了 4 种通知类型,包括Blocking notifier chainsSRCU notifier chains
Atomic notifier chainsRaw notifier chains。每种类型的通知链都类似,主要区别在于通知链执行过程的保护机制不同,暂不展开。以阻塞通知链为例,阻塞通知链会在进程上下文中调用/执行回调函数, 这意味着通知链中的调用过程可能会被阻塞。

struct blocking_notifier_head {
    struct rw_semaphore rwsem;
    struct notifier_block __rcu *head;
};

实现

前面提到,通知机制类似一种订阅/发布的关系。订阅者向信息代理者注册自己的信息(例如,我们在报亭写下自己的地址),当信息代理者接收到信息发布者的信息后(如人民日报生成报纸并寄送给报亭),将就信息传递给该信息的所有订阅者(报亭将报纸分发给我们)。

“代理者”

通过以上例子,要实现事件的订阅与发布,首先需要建立一个代理者用来记录订阅者的“地址信息”。如下在文件mm/oom_kill.c中建立一个 oom_notify_list的链表,为需要关注内存不足的子系统提供一个记录点。

static BLOCKING_NOTIFIER_HEAD(oom_notify_list);

BLOCKING_NOTIFIER_HEAD宏定义在文件include/linux/notifier.h中,展开为定义一个blocking_notifier_head结构体,并进行简单初始化。

#define BLOCKING_NOTIFIER_HEAD(name)                \
    struct blocking_notifier_head name =            \
        BLOCKING_NOTIFIER_INIT(name)

为方便订阅者注册自身对OOM时间发生时的处理,代理者还提供相应API。register_xxxunregister_xxx分别将notifier_block *nb 插入到 list 中和从 list 中移除,从而建立/解除订阅者关系。当然订阅者也可以直接调用include/linux/notifier.h文件中提供的blocking_notifier_chain_register()函数也可。

int register_oom_notifier(struct notifier_block *nb)
{
    return blocking_notifier_chain_register(&oom_notify_list, nb);
}
EXPORT_SYMBOL_GPL(register_oom_notifier);

int unregister_oom_notifier(struct notifier_block *nb)
{
    return blocking_notifier_chain_unregister(&oom_notify_list, nb);
}
EXPORT_SYMBOL_GPL(unregister_oom_notifier);

“订阅者”

例如,RCU 子系统为接收 OOM 事件,其在kernel/rcu/rcutorture.c文件中定义好回调函数并将初始化struct notifier_block结构体,最后调用 API register_oom_notifier来将回调函数添加到 list 中。

static struct notifier_block rcutorture_oom_nb = {
    .notifier_call = rcutorture_oom_notify
};

register_oom_notifier(&rcutorture_oom_nb);

“发布者”

当事件产生时,系统只需要获取保存回调函数的 list,并一一调用相应的回调函数即可。事件订阅者回调函数的调用实现定义在kernel/notifier.c文件中。blocking_notifier_call_chain()接收 3 个参数:

  • nh - 通知链链表头
  • val - 通知的类型
  • v - 回调函数可能用到的参数
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
        unsigned long val, void *v)
{
    int ret = NOTIFY_DONE;
    if (rcu_access_pointer(nh->head)) {
        down_read(&nh->rwsem);
        ret = notifier_call_chain(&nh->head, val, v, -1, NULL);
        up_read(&nh->rwsem);
    }
    return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);

blocking_notifier_call_chain()函数调用notifier_call_chain,其接收额外两个参数nr_to_callnr_calls表示是要调用的通知程序函数的数量和已发送的通知的数量。nr_to_call 为 -1 以及nr_calls 为 NULL 时则忽略这两个参数。

static int notifier_call_chain(struct notifier_block **nl,
                   unsigned long val, void *v,
                   int nr_to_call, int *nr_calls)
{
    ...
}

同样以内存管理中的OOM事件为例。当内存不足时,定义在mm/oom_kill.c文件中的out_of_memory()函数将被调用。通知链中回调函数的调用将会进行内存回收,并通过 freed 返回是否回收了内存。

// mm/oom_kill.c
bool out_of_memory(struct oom_control *oc)
{
    ...
    if (!is_memcg_oom(oc)) {
        blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
        if (freed > 0)
            /* Got some memory back in the last second. */
            return true;
    }
}

参考链接

  1. 0xax. Notification Chains in Linux Kernel
  2. MQTT

声明:一丁点儿|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Linux 内核| 内核通知链机制


勿在浮沙筑高台,每天进步一丁点儿!