哈喽,周二愉快。我是老吴,继续记录我的学习心得。
一、实现目标的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人了,想加群的小朋友请先加我个人微信,我拉你进群: