linux 通知链(notifier chain)

  • 了解通知链(notifier chain)。

1.通知链(notifier chain)

  Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain)。

  通知链只能用在各个子系统之间,而不能在内核态和用户态之间进行事件通知。内核的核心代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。

  事件通知链是一个事件处理函数的列表,每个通知链都与某个或某些事件有关。当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
在这里插入图片描述

  四种不同的notifier chain类型:

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

2.数据结构

  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_call: 代表当事件发生之后调用的回调函数。
  • next: 用来链接同一个类型的notifier。
  • priority: notifier chain的优先级。对应的数字越大优先级越高,就优先执行。

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

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

3.notifier chain初始化

  内核提供了一套宏用来初始化各个类型的通知链。

3.1.动态初始化各个类型的通知链

#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)  

3.2.静态初始化

 113 #define ATOMIC_NOTIFIER_HEAD(name)              \
  114     struct atomic_notifier_head name =          \
  115         ATOMIC_NOTIFIER_INIT(name)
  116 #define BLOCKING_NOTIFIER_HEAD(name)                \
  117     struct blocking_notifier_head name =            \                                                  
  118         BLOCKING_NOTIFIER_INIT(name)
  119 #define RAW_NOTIFIER_HEAD(name)                 \
  120     struct raw_notifier_head name =             \
  121         RAW_NOTIFIER_INIT(name)
  122 
  123 #ifdef CONFIG_TREE_SRCU
  124 #define _SRCU_NOTIFIER_HEAD(name, mod)              \
  125     static DEFINE_PER_CPU(struct srcu_data, name##_head_srcu_data); \
  126     mod struct srcu_notifier_head name =            \
  127             SRCU_NOTIFIER_INIT(name, name##_head_srcu_data)
  128 
  129 #else
  130 #define _SRCU_NOTIFIER_HEAD(name, mod)              \
  131     mod struct srcu_notifier_head name =            \
  132             SRCU_NOTIFIER_INIT(name, name)                                                             
  133 
  134 #endif

4.注册/注销通知链

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

include/linux/notifier.h:
 144 extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
  145         struct notifier_block *nb);
  146 extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
  147         struct notifier_block *nb);
  148 extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
  149         struct notifier_block *nb);
  150 extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
  151         struct notifier_block *nb);

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

  • notifier_chain_register根据notifier_block的优先级,将新注册的notifier_block插入到单向链表中
  • 移除的时候,同理将notifier_block从链表中移除。

5.通知函数

  当某种事件需要发生的时候,就需要调用内核提供的通知函数notifier call函数,来通知注册过相应时间的子系统。同样,不同类型的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 chain为我们提供了一种简单方便的不同子系统之间的通信方法。根据使用场景不同,使用不同类型的notifier chain可以获得更高的效率。此外这种方法不同于IPC通信,回调函数和消息发出会在同一个内核进程中执行,同样适用于一些对实时性要求较高的场合。

refer to:

  • http://liujunming.top/2019/08/06/Linux-kernel-notifier-chain/
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/103581368