Linux Notifier Chains
1.    引言
Linux是单内核架构(monolithic kernel),大多数内核子系统和模块是相互独立的,它们被动态地加载或卸载,以使内核变得小巧和可扩展。然而,子系统或模块之间需要通信,或者说某个特定模块扑捉到的事件可能其它模块对此感兴趣,这就需要一种机制来满足子系统或模块之间交互的需求。
Linux使用通知链表来实现这一需求,它是一个简单的函数链表,当某件事件发生时,链表上的函数就会执行。这是一种发布-订阅(publish-subscribe)模式,当客户(订阅者)需要某个特定事件的通知时,会向主机(发布者)注册自己;接下来,只要感兴趣的事件一发生,主机便会通知客户。
2.    Notifier定义
在/include/linux/notifier.h文件中定义了通知链表节点
struct notifier_block {
    int (*notifier_call)(struct notifier_block *, unsigned long, void *);
    struct notifier_block *next;
    int priority;
};
其中,函数指针notifier_call注册了当某个事件发生时需要调用的函数;next指向下一个链表节点;priority设定链表节点的优先级,数值越大优先级越高,默认为0。因此,所有的通知链表节点组成了一个单链表,并以优先级(priority)排列。
3.    Notifier类型
内核提供了四种类型的通知链表,它们的分类是基于执行上下文和调用通知链所需的锁保护机制:
·Atomic notifier chains:该通知链表在中断或原子上下文执行,不能阻塞,链表事件对响应时间要求高,定义如下
    struct atomic_notifier_head {
        spinlock_t lock;
        struct notifier_block *head;
};
·Blocking notifier chains:在进程上下文执行,能够阻塞,链表事件对响应时间要求不高,定义如下
    struct blocking_notifier_head {
        struct rw_semaphore rwsem;
        struct notifier_block *head;
};
·Raw notifier chains:调用,注册或卸载链表通知时无限制,所需保护机制由调用者提供,定义如下
    struct raw_notifier_head {
        struct notifier_block *head;
};
·SRCU notifier chains:这是一种Sleepable Read Copy Update (SRCU)的链表通知,与block链表通知类似,不同在处理锁与保护上,SRCU在调用通知时的系统开销小,而从通知链表中去除通知调用的系统开销大,因此适合用在调用通知频繁,而移除调用通知少的情况中,定义如下
struct srcu_notifier_head {
    struct mutex mutex;
    struct srcu_struct srcu;
    struct notifier_block *head;
};
4.    Notifier注册
以blocking notifier chains为例,通常用一个宏来定义并初始化一个通知链表头,代码如下
#define BLOCKING_NOTIFIER_HEAD(name)                \
    struct blocking_notifier_head name =            \
        BLOCKING_NOTIFIER_INIT(name)
然后向通知链表中注册通知节点,代码如下
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
        struct notifier_block *n)
{
    int ret;

    /*
     * This code gets used during boot-up, when task switching is
     * not yet working and interrupts must remain disabled.  At
     * such times we must not call down_write().
     */
    if (unlikely(system_state == SYSTEM_BOOTING))
        return notifier_chain_register(&nh->head, n);

    down_write(&nh->rwsem);
    ret = notifier_chain_register(&nh->head, n);
    up_write(&nh->rwsem);
    return ret;
}
第一个参数为通知链表头指针,而第二参数是要注册到该通知链表中的链表节点,调用函数notifier_chain_register( )实现了真正的注册,代码如下
static int notifier_chain_register(struct notifier_block **nl,
        struct notifier_block *n)
{
    while ((*nl) != NULL) {
        if (n->priority > (*nl)->priority)
            break;
        nl = &((*nl)->next);
    }
    n->next = *nl;
    rcu_assign_pointer(*nl, n);
    return 0;
}
当blocking notifier chains的头指针head为NULL时,将所要注册的notifier block
赋给head,而该链表节点的next设为NULL;当blocking notifier chains的头指针head为非空时,若所要注册的notifier block的优先级比头节点的高,则将该链表节点的next指向头节点,而将该链表节点作为新的头节点;当blocking notifier chains的头指针head为非空时,且所要注册的notifier block的优先级比头节点的低,则将依据优先级高低遍历该通知链表上的节点,找到链表上第一个比自身优先级低的节点,将所要注册的链表节点插入到该节点之前。
5.    Notifier发送
仍以blocking notifier chains为例,当一个通知链表上注册了链表节点后,需要一个函数去按优先级去激活链表上注册的函数,代码如下
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
        unsigned long val, void *v)
{
    return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}

int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
                   unsigned long val, void *v,
                   int nr_to_call, int *nr_calls)
{
    int ret = NOTIFY_DONE;

    /*
     * We check the head outside the lock, but if this access is
     * racy then it does not matter what the result of the test
     * is, we re-check the list after having taken the lock anyway:
     */
    if (rcu_dereference(nh->head)) {
        down_read(&nh->rwsem);
        ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
                    nr_calls);
        up_read(&nh->rwsem);
    }
    return ret;
}
我们发现调用的底层函数是notifier_call_chain( ),代码如下
static int __kprobes notifier_call_chain(struct notifier_block **nl,
                    unsigned long val, void *v,
                    int nr_to_call,    int *nr_calls)
{
    int ret = NOTIFY_DONE;
    struct notifier_block *nb, *next_nb;

    nb = rcu_dereference(*nl);

    while (nb && nr_to_call) {
        next_nb = rcu_dereference(nb->next);

#ifdef CONFIG_DEBUG_NOTIFIERS
        if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
            WARN(1, "Invalid notifier called!");
            nb = next_nb;
            continue;
        }
#endif
        ret = nb->notifier_call(nb, val, v);

        if (nr_calls)
            (*nr_calls)++;

        if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
            break;
        nb = next_nb;
        nr_to_call--;
    }
    return ret;
}
第一个参数传入的是所要通知的链表头指针,第二个和第三个参数是要传递给通知函数的整型和指针参数,第四个参数nr_to_call限定了通知链表上响应的函数个数,为-1时无限制,第五个参数nr_call记录了实际调用的通知链表上的函数个数,为NULL时不记录。
6.    Notifier实例应用
以framebuffer子系统为例,简单介绍通知链表的实现。
在/drivers/video/fb_notify.c中初始化了一个名为fb_notifier_list的通知链表头,代码如下
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
在/drivers/video/console/fbcon.c文件中,函数fb_console_init( )在初始化framebuffer控制台时调用了函数fb_register_client(&fbcon_event_notifier)向通知链表头fb_notifier_list注册了一个名为fbcon_event_notifier的通知节点,定义如下
static struct notifier_block fbcon_event_notifier = {
    .notifier_call    = fbcon_event_notify,
};
其中fbcon_event_notify就是通知回调函数;而fb_register_client最终调用的就是注册通知节点的底层函数notifier_chain_register( )。
在/drivers/video/fbmem.c文件中,函数register_framebuffer( )在初始化注册framebuffer驱动时,在所有初始化工作完成后调用了函数fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event)给通知链表fb_notifier_list发送事件FB_EVENT_FB_REGISTERED,表明framebuffer已注册成功了,该函数最终调用了底层的链表通知函数notifier_call_chain( ),此时将遍历通知链表上所用的通知节点,显然节点fbcon_event_notifier的通知函数fbcon_event_notify将最终被调用执行。
Logo

更多推荐