Linux内核通知链 使用和简析


1. 使用简述

通知链表是一个函数链表,链表上的每一个节点都注册了一个处理函数。当该chain对应的事件发生时(call chain),链表上所有节点对应的函数就会被执行。

定义

1、定义notifier head
通知链是一个链表,需要有一个链表头,后续的元素就可以陆续添加到这个链表中去

static RAW_NOTIFIER_HEAD(hello_chain);1

2、定义该通知链的注册方法
所谓注册就是根据优先级将被通知的元素添加到链表中去

static int register_hello_notifier(struct notifier_block *nb)
{
    int err;
    err = raw_notifier_chain_register(&hello_chain, nb);
    if(err)
        goto out;
out:
    return err;
}

EXPORT_SYMBOL(register_test_notifier);

3 定义该通知链的事件通知方法

static int call_hello_notifiers(unsigned long val, void *v)
{
    return raw_notifier_call_chain(&hello_chain, val, v);
}
EXPORT_SYMBOL(call_hello_notifiers);

通知事件注册

4 定义处理函数

int hello_notifier_event_handler(struct notifier_block *nb, unsigned long event, void *v)
{
    printk("event %lu\n", event);
    return NOTIFY_DONE;
}

/* define a notifier_block */
static struct notifier_block hello_init_notifier = {
    .notifier_call = hello_notifier_event_handler,
}


5 注册

extern int register_hello_notifier(struct notifier_block *nb);

register_test_notifier(&hello_init_notifier);

发出通知事件

6 发出通知事件

extern int call_test_notifiers(unsigned long val, void *v);
#define HEELO_EVENT 0x1612

call_test_notifiers(HEELO_EVENT, "no_use");

2. 详述

Linux 内核通知链有四种类型,根据回调函数可运行的上线文限制做出了下面的区分

原子通知链 Atomic notifier chains: Chain callbacks run in interrupt/atomic context. Callouts are not allowed to block.
可阻塞通知链 Blocking notifier chains: Chain callbacks run in process context. Callouts are allowed to block.
原始通知链 Raw notifier chains: There are no restrictions on callbacks, registration, or unregistration.  All locking and protection must be provided by the caller.
SRCU通知链 SRCU notifier chains: A variant of blocking notifier chains, with the same restrictions.

~/include/linux/notifier.h

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

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

struct atomic_notifier_head {
    spinlock_t lock;
    struct notifier_block __rcu *head;
};

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

struct raw_notifier_head {
    struct notifier_block __rcu *head;
};

struct srcu_notifier_head {
    struct mutex mutex;
    struct srcu_struct srcu;
    struct notifier_block __rcu *head;
};

为了初始化链表头方便,提供了宏定义

需要注意的是SRCU的链表头需要动态初始化

#define ATOMIC_INIT_NOTIFIER_HEAD(name) do {    \
        spin_lock_init(&(name)->lock);  \
        (name)->head = NULL;        \
    } while (0)
#define BLOCKING_INIT_NOTIFIER_HEAD(name) do {  \
        init_rwsem(&(name)->rwsem); \
        (name)->head = NULL;        \
    } while (0)
#define RAW_INIT_NOTIFIER_HEAD(name) do {  \
        (name)->head = NULL;        \
    } while (0)

/* srcu_notifier_heads must be initialized and cleaned up dynamically */
extern void srcu_init_notifier_head(struct srcu_notifier_head *nh);
#define srcu_cleanup_notifier_head(name)    \
        cleanup_srcu_struct(&(name)->srcu);

#define ATOMIC_NOTIFIER_INIT(name) {                \
        .lock = __SPIN_LOCK_UNLOCKED(name.lock),    \
        .head = NULL }
#define BLOCKING_NOTIFIER_INIT(name) {              \
        .rwsem = __RWSEM_INITIALIZER((name).rwsem), \
        .head = NULL }
#define RAW_NOTIFIER_INIT(name) {              \
        .head = NULL }
/* srcu_notifier_heads cannot be initialized statically */

#define ATOMIC_NOTIFIER_HEAD(name)              \
    struct atomic_notifier_head name =          \
        ATOMIC_NOTIFIER_INIT(name)
#define BLOCKING_NOTIFIER_HEAD(name)                \
    struct blocking_notifier_head name =            \
        BLOCKING_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_HEAD(name)                \
    struct raw_notifier_head name =            \
        RAW_NOTIFIER_INIT(name)

notifier_chain_register

无论是 ATOMIC BLOCKING RAW SRCU 的注册,都是对notifier_chain_register的封装, 以实现其特性
注册的过程就是讲新的链表元素按优先级添加到链表中去,大部分时候都是用的默认优先级

~/kernel/notifier.c

/*
 *  Notifier chain core routines.  The exported routines below
 *  are layered on top of these, with appropriate locking added.
 */

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

 

notifier_call_chain

所谓call chain 就是遍历链表,执行所有元素的notifier_call,并传递相关参数


/**
 * 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) == NOTIFY_STOP_MASK)
            break;
        nb = next_nb;
        nr_to_call--;
    }
    return ret;
}

linuxboy的RSS地址:https://www.linuxboy.net/rssFeed.aspx

本文永久更新链接地址:https://www.linuxboy.net/Linux/2019-07/159288.htm

相关内容

    暂无相关文章