linux kernel — 通知链(notifier chain)简析

notifier chain在Linux kernel中被用来实现不同的subsystem之间的通信。简单的来说,notifier chain是一个存储了某个事件对应的回调函数的列表。每个notifier chain都和特定的事件相关,当发出事件通知的时候,会触发对应的回调函数来处理该事件。

kernel中共设计了四种不同的notifier chain类型:

  • atomic notifier:回调函数在原子上下文中执行,不可阻塞
  • blocking notifier:回调函数在进程上下文中执行,可以阻塞
  • raw notifier:回调函数没有任何限制,但加锁、保护等操作需要由调用者自行完成
  • SRCU notifier: 回调函数同样是在进程上下文中执行的,是blocking notifier的一种变体

数据结构

Notifier chain的数据结构有notifier list和notifier block两个部分构成。notifier list就是用来存储notifier block的链表。

notifier chain根据类型不同分为四种notifier list结构:

  • atomic_notifier_head: 使用spin lock保护链表,发送通知时使用RCU进行同步
struct atomic_notifier_head {
    spinlock_t lock;
    struct notifier_block __rcu *head;
};
  • blocking_notifier_head: 使用读写信号量保护链表,发送通知使用RCU进行同步
struct blocking_notifier_head {
    struct rw_semaphore rwsem;
    struct notifier_block __rcu *head;
};
  • raw_notifier_head: 没有保护链表的措施,发送通知使用RCU进行同步
struct raw_notifier_head {
    struct notifier_block __rcu *head;
};
  • srcu_notifier_head: 使用互斥锁保护链表,消息发送使用SRCU进行同步(没有使用锁机制),相比起传统的blocking notifier来说,在发送通知的时候由于保护chain所造成的开销更小
struct srcu_notifier_head {
    struct mutex mutex;
    struct srcu_struct srcu;
    struct notifier_block __rcu *head;
};

事件对应的回调函数存储在notifier_block的notifier_call中,priority表示事件到来是回调执行的优先级,优先级越高的越早执行:

struct notifier_block {
    notifier_fn_t notifier_call;
    struct notifier_block __rcu *next;
    int priority;
};

其中回调函数是一个notifier_fn_t类型,可以接收两个额外参数—action和data,可以用来指定操作类型、传递数据指针。返回值则可以表明执行有无错误。

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

接口

notifier list定义和初始化

我们可以使用以下的宏来定义并初始化某种类型的list,这些宏在定义变量的同时也会初始化结构体中相应的成员:

ATOMIC_NOTIFIER_HEAD(name)
BLOCKING_NOTIFIER_HEAD(name)
RAW_NOTIFIER_HEAD(name)
SRCU_NOTIFIER_HEAD(name)
SRCU_NOTIFIER_HEAD_STATIC(name)

注册和移除block

注册和移除block的时候,由于不同类型使用了不同的方法来保护链表,所以我们需要调用相应类型的注册函数:

  • atomic notifier在注册和移除的时候会使用spin lock来保护链表结构。
int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
                                   struct notifier_block *n)
{
    unsigned long flags;
    int ret;

    spin_lock_irqsave(&nh->lock, flags);
    ret = notifier_chain_register(&nh->head, n);
    spin_unlock_irqrestore(&nh->lock, flags);
    return ret;
}
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
                                     struct notifier_block *n)
{
    unsigned long flags;
    int ret;

    spin_lock_irqsave(&nh->lock, flags);
    ret = notifier_chain_unregister(&nh->head, n);
    spin_unlock_irqrestore(&nh->lock, flags);
    synchronize_rcu();
    return ret;
}
  • blocking notifier在注册和移除的时候使用读写锁保护链表结构。
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;
}
int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh,
                                          struct notifier_block *n)
{
    int ret;

    down_write(&nh->rwsem);
    ret = notifier_chain_cond_register(&nh->head, n);
    up_write(&nh->rwsem);
    return ret;
}
  • raw notifier在注册时需要用户自己解决链表结构保护的问题。
int raw_notifier_chain_register(struct raw_notifier_head *nh,
                                struct notifier_block *n)
{
    return notifier_chain_register(&nh->head, n);
}
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
                                  struct notifier_block *n)
{
    return notifier_chain_unregister(&nh->head, n);
}
  • srcu在注册和移除的时候使用互斥锁解决链表结构的保护问题。
int srcu_notifier_chain_register(struct srcu_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 mutex_lock().
         */
    if (unlikely(system_state == SYSTEM_BOOTING))
        return notifier_chain_register(&nh->head, n);

    mutex_lock(&nh->mutex);
    ret = notifier_chain_register(&nh->head, n);
    mutex_unlock(&nh->mutex);
    return ret;
}
int srcu_notifier_chain_unregister(struct srcu_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 mutex_lock().
         */
    if (unlikely(system_state == SYSTEM_BOOTING))
        return notifier_chain_unregister(&nh->head, n);

    mutex_lock(&nh->mutex);
    ret = notifier_chain_unregister(&nh->head, n);
    mutex_unlock(&nh->mutex);
    synchronize_srcu(&nh->srcu);
    return ret;
}

他们在对于链表的保护方式上有所不同,但是真正操作链表的函数都是相同的,都是notifier_chain_register和notifier_chain_unregister。

notifier_chain_register根据notifier_block的优先级,将新注册的notifier_block插入到单向链表中。

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

移除的时候同理,将notifier_block从链表中移除。

static int notifier_chain_unregister(struct notifier_block **nl,
        struct notifier_block *n)
{
    while ((*nl) != NULL) {
        if ((*nl) == n) {
            rcu_assign_pointer(*nl, n->next);
            return 0;
        }
        nl = &((*nl)->next);
    }
    return -ENOENT;
}

需要注意的一个小细节是在修改notifier_block结构体,把新的block加入链表的时候,使用的rcu的发布订阅机制。

发送通知

同样,不同类型的notifier chain也需要使用不同的发送通知的函数。

atomic notifier的回调函数需要在原子上下文中执行,所以回调函数不能够阻塞。内核采用RCU来保护notifier call back。

int __atomic_notifier_call_chain(struct atomic_notifier_head *nh,
                 unsigned long val, void *v,
                 int nr_to_call, int *nr_calls)
{
    int ret;

    rcu_read_lock();
    ret = notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
    rcu_read_unlock();
    return ret;
}
int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
                   unsigned long val, void *v)
{
    return __atomic_notifier_call_chain(nh, val, v, -1, NULL);
}

blocking notifier的回调函数可以允许阻塞。使用读写锁保护call back。

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_access_pointer(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;
}
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);
}

raw notifier没有提供任何的锁机制,所以相关的锁操作必须由用户自己完成。

int raw_notifier_chain_register(struct raw_notifier_head *nh,
        struct notifier_block *n)
{
    return notifier_chain_register(&nh->head, n);
}

srcu nitifier使用SRCU的方法来保护call back,这样在执行call chain的时候所造成开销是非常小的。然而相对的在register或者unregister的时候就会造成很大的开销。所以通常来说适合经常发送消息,但是很少会把notifier blocks移除的场景。

int __srcu_notifier_call_chain(struct srcu_notifier_head *nh,
                   unsigned long val, void *v,
                   int nr_to_call, int *nr_calls)
{
    int ret;
    int idx;

    idx = srcu_read_lock(&nh->srcu);
    ret = notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
    srcu_read_unlock(&nh->srcu, idx);
    return ret;
}
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
        unsigned long val, void *v)
{
    return __srcu_notifier_call_chain(nh, val, v, -1, NULL);
}

他们最终调用的是notifier_call_chain函数,需要注意一下几个参数:

  • nr_to_call参数表示调用的回调函数的数量,如果是-1的话这个参数无效
  • nr_calls可以记录下发出了多少次消息,也就是执行了多少个回调函数

该函数的功能十分简单,遍历链表,根据设定的nr_to_call数量来执行回调,并且进行nr_calls的累加。当回调函数返回了NOTIFY_STOP的时候停止执行回调。最后该函数的返回值等于最后一次执行的回调函数的返回值。如果没有注册任何notifier block,返回NOTIFY_DONE.

/**
 * notifier_call_chain - Informs the registered notifiers about an event.
 *  @nl:        Pointer to head of the blocking notifier chain
 *  @val:       Value passed unmodified to notifier function
 *  @v:     Pointer passed unmodified to notifier function
 *  @nr_to_call:    Number of notifier functions to be called. Don't care
 *          value of this parameter is -1.
 *  @nr_calls:  Records the number of notifications sent. Don't care
 *          value of this field is NULL.
 *  @returns:   notifier_call_chain returns the value returned by the
 *          last notifier function called.
 */
static int 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_raw(*nl);

    while (nb && nr_to_call) {
        next_nb = rcu_dereference_raw(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)
            break;
        nb = next_nb;
        nr_to_call--;
    }
    return ret;
}

此外call back的返回值共有以下情况:

#define NOTIFY_DONE        0x0000      /* Don't care */
#define NOTIFY_OK      0x0001      /* Suits me */
#define NOTIFY_STOP_MASK   0x8000      /* Don't call further */
#define NOTIFY_BAD     (NOTIFY_STOP_MASK|0x0002)
                        /* Bad/Veto action */
/*
 * Clean way to return from the notifier and stop further calls.
 */
#define NOTIFY_STOP        (NOTIFY_OK|NOTIFY_STOP_MASK

总结时间

notifier chain为我们提供了一种简单方便的不同子系统之间的通信方法。根据使用场景不同,使用不同类型的notifier chain可以获得更高的效率。此外这种方法不同于IPC通信,回调函数和消息发出会在同一个内核进程中执行,同样适用于一些对实时性要求较高的场合。