首頁 > 軟體

Linux核心通知鏈 使用和簡析

2020-06-16 16:34:58

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 %lun", 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;
}


IT145.com E-mail:sddin#qq.com