Linux Notifier Chains

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;
};
View Code

  其中,函数指针notifier_call注册了当某个事件发生时需要调用的函数;next指向下一个链表节点;priority设定链表节点的优先级,数值越大优先级越高,默认为0。因此,所有的通知链表节点组成了一个单链表,并以优先级(priority)排列。


3.Notifier类型
  内核提供了四种类型的通知链表,它们的分类是基于执行上下文和调用通知链所需的锁保护机制:
  Atomic notifier chains:该通知链表在中断或原子上下文执行,不能阻塞,链表事件对响应时间要求高,定义如下

struct atomic_notifier_head {
    spinlock_t lock;
    struct notifier_block *head;
};
View Code

  Blocking notifier chains:在进程上下文执行,能够阻塞,链表事件对响应时间要求不高,定义如下

struct blocking_notifier_head {
    struct rw_semaphore rwsem;
    struct notifier_block *head;
};
View Code

  Raw notifier chains:调用,注册或卸载链表通知时无限制,所需保护机制由调用者提供,定义如下

struct raw_notifier_head {
    struct notifier_block *head;
};
View Code

  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;
};
View Code

 

4.Notifier注册
  以blocking notifier chains为例,通常用一个宏来定义并初始化一个通知链表头,代码如下'

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

  然后向通知链表中注册通知节点,代码如下

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;
}
View Code

  第一个参数为通知链表头指针,而第二参数是要注册到该通知链表中的链表节点,调用函数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;
}
View Code

  当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;
}
View Code

  我们发现调用的底层函数是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;
}
View Code

  第一个参数传入的是所要通知的链表头指针,第二个和第三个参数是要传递给通知函数的整型和指针参数,第四个参数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将最终被调用执行。

posted @ 2016-02-21 13:43  zer-o  阅读(178)  评论(0编辑  收藏  举报