Linux内核 / 基础组件 / 通知链快速入门

哈喽,周二愉快。我是老吴,继续记录我的学习心得。

一、实现目标的3种价值

对于实现目标来说,价值有三种来源:

  • 成就价值、内在价值和工具价值。

某个目标可能有1个或多个价值,以跑步为例:

  • 有的人跑步,是为了获得自律的标签,跑步里程累计达到成百上千公里,会让他感到骄傲和满足,这是成就价值。

  • 有的人跑步,是享受跑步这件事本身,早晨的阳光或者大汗淋漓都能让人心情愉悦,这是内在价值。

  • 有的人不喜欢跑步,但一直坚持跑步,是因为想要有个好身体以便努力挣钱,这是工具价值。

不同时间同一件事,三种价值的比例会不断变化:

  • 某段时间气候特别好,你觉得跑步特别舒服,则是内在价值在起主导作用。

思考一下:

  • 某些自己想做却一直没开始做的事,是否能通过为其附能上更多的价值以驱使自己开始行动呢?


二、通知链 ( notifier call chain ) 快速入门

目录:

1. 什么是通知链?
2. 通知链的内部实现
3. 如何使用通知链?

基于 Linux-4.14。

1. 什么是通知链?

通知链是 Linux 内核的一个基础组件,各种内核组件 (包括设备驱动、文件组件、网络组件等) 可以对向其感兴趣的一些内核事件进行注册,当该事件发生时,这些模块或者组件当初注册的回调函数将会被调用。

内核有哪些通知链?

$ grep " register_.*_notifier" ./* -nR

/* 平台架构相关 */
./arch/arm/kernel/kgdb.c:220: int ret = register_die_notifier(&kgdb_notifier);
./arch/arm/kernel/smp.c:793:static int __init register_cpufreq_notifier(void)
[...]

/* 设备驱动相关 */
./drivers/watchdog/smsc37b787_wdt.c:550: ret = register_reboot_notifier(&wb_smsc_wdt_notifier);
./drivers/watchdog/sc520_wdt.c:402: rc = register_reboot_notifier(&wdt_notifier);
[...]

/* 文件系统相关 */
./fs/nfsd/nfsctl.c:1275: retval = register_cld_notifier();

/* kernel basic 相关 */
./kernel/module.c:290:int register_module_notifier(struct notifier_block *nb)
./kernel/jump_label.c:687: return register_module_notifier(&jump_label_module_nb);
./kernel/events/uprobes.c:2050: return register_die_notifier(&uprobe_exception_nb);
./kernel/events/hw_breakpoint.c:626: return register_die_notifier(&hw_breakpoint_exceptions_nb);
./kernel/power/main.c:29:int register_pm_notifier(struct notifier_block *nb)
./kernel/tracepoint.c:340: * register_tracepoint_notifier - register tracepoint coming/going notifier
./kernel/tracepoint.c:348:int register_tracepoint_module_notifier(struct notifier_block *nb)
./kernel/tracepoint.c:497: ret = register_module_notifier(&tracepoint_module_nb);
./kernel/reboot.c:86:int register_reboot_notifier(struct notifier_block *nb)
./kernel/kprobes.c:2279:  err = register_die_notifier(&kprobe_exceptions_nb);
./kernel/kprobes.c:2281:  err = register_module_notifier(&kprobe_module_nb);
./kernel/trace/trace_printk.c:378: return register_module_notifier(&module_trace_bprintk_format_nb);
./kernel/trace/trace_events.c:3169: ret = register_module_notifier(&trace_module_nb);
./kernel/notifier.c:553:int register_die_notifier(struct notifier_block *nb)
./kernel/gcov/base.c:166: return register_module_notifier(&gcov_nb);
[...]

/* 内存管理相关 */
./mm/oom_kill.c:1002:int register_oom_notifier(struct notifier_block *nb)
./mm/vmalloc.c:545:int register_vmap_purge_notifier(struct notifier_block *nb)

/* 网络相关 */
./net/ipv4/devinet.c:1367:int register_inetaddr_notifier(struct notifier_block *nb)
./net/ipv4/devinet.c:1379:int register_inetaddr_validator_notifier(struct notifier_block *nb)
[...]

2. 简单了解通知链的内部实现

通知链的实现机制:

通过链表的形式,内核将那些注册进来的关注同类事件的节点构成一个链表,当某一特定的内核事件发生时,事件所属的内核组件负责遍历该通知链上的所有节点,调用节点上的回调函数。

共有4 种类型的通知链:

链表头如下:

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

区别在于使用了不同的锁机制,抓住核心 ( struct notifier_block __rcu *head ) 就好。

通知链节点 ( 核心数据结构 struct notifier_block ):

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;
};
  • notifier_call: 通知节点中的回调函数;

  • next: 用来构成通知链;

  • priority: 通知节点的优先级,用来决定通知节点在通知链中的先后顺序,数值越大代表优先级越高;

2.1 以 module 通知链为例

内核模块机制中实现的 module 通知链 (module_notify_list) 是内核中众多通知链中的一条

内核通过 module 通知链向其他对内核模块事件感兴趣的组件发送通知,通知的事件类型是模块的当前加载状态:

enum module_state {
 MODULE_STATE_LIVE, /* Normal state. */
 MODULE_STATE_COMING, /* Full formed, running module_init. */
 MODULE_STATE_GOING, /* Going away. */
 MODULE_STATE_UNFORMED, /* Still setting it up. */
};

1) module 通知链的链表头:

static BLOCKING_NOTIFIER_HEAD(module_notify_list);

本质就是定义并初始化了: struct blocking_notifier_head module_notify_list,它用于管理所有对内核模块事件感兴趣的通知节点。

2) 关心模块状态的其他内核代码通过 blocking_notifier_chain_register() 添加节点 (struct notifier_block):

int register_module_notifier(struct notifier_block *nb)
{
 return blocking_notifier_chain_register(&module_notify_list, nb);
}

八卦一下,关心模块状态的内核代码有:

$ grep " register_module_notifier" ./* -nR
...
./kernel/tracepoint.c:497: ret = register_module_notifier(&tracepoint_module_nb);
./kernel/kprobes.c:2281:  err = register_module_notifier(&kprobe_module_nb);
./kernel/trace/trace_printk.c:378: return register_module_notifier(&module_trace_bprintk_format_nb);
./kernel/trace/trace_events.c:3169: ret = register_module_notifier(&trace_module_nb);

blocking_notifier_chain_register() 将通知节点加入 module_notify_list 管理的链表。在向一个通知链中加入新节点时,系统会把各节点的 priority 作为一个排序关键字进行简单排序,优先级越高的节点越靠近头节点,当有事件发生时,最先被通知:

3) 当相关事件发生时,使用 blocking_notifier_call_chain() 通知所有节点:

$ cd kernel
$ grep module_notify_list ./* -nR -A 1
./module.c:1012: blocking_notifier_call_chain(&module_notify_list,
./module.c-1013-         MODULE_STATE_GOING, mod);
--
./module.c:3466: blocking_notifier_call_chain(&module_notify_list,
./module.c-3467-         MODULE_STATE_LIVE, mod);
--
./module.c:3530: blocking_notifier_call_chain(&module_notify_list,
./module.c-3531-         MODULE_STATE_GOING, mod);
--
./module.c:3625: blocking_notifier_call_chain(&module_notify_list,
./module.c-3626-         MODULE_STATE_COMING, mod);
--
./module.c:3786: blocking_notifier_call_chain(&module_notify_list,
./module.c-3787-         MODULE_STATE_GOING, mod);

模块加载的不同阶段会发出不同的通知,通知链上各节点的回调函数会被触发。

以 MODULE_STATE_COMING 为例:

load_module()
    prepare_coming_module(mod);
        blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod);

blocking_notifier_call_chain() 将使通知链 module_notify_list 上的各节点的回调函数均被调用,其原理就是遍历 module_notify_list 上的各节点,依次调用各节点上的 notifier_call() :

blocking_notifier_call_chain()
 notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);

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

  // 调用节点上的回调函数
  ret = nb->notifier_call(nb, val, v);
  [...]
  nb = next_nb;
  nr_to_call--;
 }
 return ret;
}

遍历链表相关的知识,不是本文关注的重点。

2.2 一些可能用得上的通知链

1) reboot 通知链:

kernel/reboot.c:

// 通知链头:reboot_notifier_list

int register_reboot_notifier(struct notifier_block *nb)
{
 return blocking_notifier_chain_register(&reboot_notifier_list, nb);
}

如果你想在关机或者重启系统时做一些硬件关闭相关的事情,就需要用到这条通知链。

通知类型包括:

#define SYS_DOWN 0x0001 /* Notify of system down */
#define SYS_RESTART SYS_DOWN
#define SYS_HALT 0x0002 /* Notify of system halt */
#define SYS_POWER_OFF 0x0003 /* Notify of system power off */

2) netdevice 通知链:

net/core/dev.c:


// 通知链头:netdev_chain

int register_netdevice_notifier(struct notifier_block *nb)
{
    ...
}

用于关注网络设备相关的事件。

通知类型包括:

#define NETDEV_UP 0x0001 /* For now you can't veto a device up/down */
#define NETDEV_DOWN 0x0002
#define NETDEV_REBOOT 0x0003
#define NETDEV_CHANGE 0x0004 /* Notify device state change */
#define NETDEV_REGISTER 0x0005
#define NETDEV_UNREGISTER 0x0006
#define NETDEV_CHANGEMTU 0x0007 /* notify after mtu change happened */
#define NETDEV_CHANGEADDR 0x0008
#define NETDEV_GOING_DOWN 0x0009
#define NETDEV_CHANGENAME 0x000A
#define NETDEV_FEAT_CHANGE 0x000B
#define NETDEV_BONDING_FAILOVER 0x000C
#define NETDEV_PRE_UP  0x000D
#define NETDEV_PRE_TYPE_CHANGE 0x000E
#define NETDEV_POST_TYPE_CHANGE 0x000F
#define NETDEV_POST_INIT 0x0010
...

3. 如何使用通知链?

入门 demo

使用 module 通知链监测内核模块相关的事件:

static struct notifier_block *pnb = NULL;
static char *mstate[] = {"LIVE", "COMING", "GOING"};

// notifier callback
int get_notify(struct notifier_block *p, unsigned long v, void *m)
{
    printk("module <%s> is %s, p->priority=%d\n", ((struct module *)m)->name, mstate[v],
    p->priority);
    return 0;
}
static int hello_init(void)
{
    // alloc notifier node
    pnb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
    if(!pnb)
        return -1;
    
    pnb->notifier_call = get_notify;
    pnb->priority = 10;

    // register notifier node
    register_module_notifier(pnb);
    printk("A listening module is coming...\n");
    return 0;
}

static void hello_exit(void)
{
    unregister_module_notifier(pnb);
    kfree(pnb);
    printk("A listening module is going\n");
}
module_init(hello_init);
module_exit(hello_exit);

运行效果:

$ insmod notifier_mod.ko
$ dmesg | tail
A listening module is coming...
module <notifier_mod> is LIVE, p->priority=10  # 监测到自己被成功加载
$ rmmod exfat
$ dmesg | tail
module <exfat> is GOING, p->priority=10   # 监测到 exfat 模块被卸载

4. 相关参考

  • 深入Linux设备驱动程序内核机制,1.3

  • 精通linux设备驱动程序开发,3.2.4

  • Linux设备驱动程序,NULL


三、思考技术,也思考人生

要学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,关注公众号:嵌入式Hacker

觉得文章对你有价值,不妨点个 在看和点赞, 不点也行。

ps: 微信群满200人了,想加群的小朋友请先加我个人微信,我拉你进群:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值