在学习 i2c-dev.c 文件时,有如下的一段函数一直不明所以

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,void *data)
{struct device *dev = data;switch (action) {case BUS_NOTIFY_ADD_DEVICE:return i2cdev_attach_adapter(dev, NULL);case BUS_NOTIFY_DEL_DEVICE:return i2cdev_detach_adapter(dev, NULL);}return 0;
}static struct notifier_block i2cdev_notifier = {.notifier_call = i2cdev_notifier_call,
};static int __init i2c_dev_init(void)
{...res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);...
}

后来进过搜索发现是内核通知链的相关知识。
一下内容代码参考源为: http://blog.chinaunix.net/uid-23069658-id-4360706.html

在阅读内核源码的时候,到处会看到通知链的身影。从技术上来讲,这并不是一个多么复杂、高深、难懂的部分,说白了就是一个单向链表的插入、删除和遍历等操作。但这部分是由协议栈头号大Boss—-Alan Cox亲自主刀,足以说明这个基础特性的重要性,也有很多值得我们学习的地方。内核中通知链的基础文件就两个,头文件include/linux/notifier.h,源文件kernel/notifier.c,头文件和源文件所有代码加起来不超过1000行,总体来说还是比较好懂。
刚才说过,通知链的原型就是一个单向链表,内核提供的通知链机制主要用于不同子系统之间通信,基于事件和优先级。往通俗里将,考虑这么一种场景:对于网卡驱动子系统来说,经常会发生的情况就是什么?网卡IP地址有变化,网卡状态有变化等等。那么如果有其他子系统,比如路由子系统最网卡IP地址变化这件事比较感兴趣,它该怎么去感知这件事儿呢?当然这种场景下,很多人第一直觉就是“订阅者-发布者”模型。不过确实是这样的,通知链机制可以算作是“订阅者-发布者”模型的一种。每个子系统都会有些一些重要事件,例如前面说的,网络驱动子系统网卡的事件,或者USB的状态事件等等,这些子系统都会提供一个自己的事件队列,这个队列都是其他函数提供的回调函数。当有事件发生时,子系统就会去遍历其事件队列上已经注册了的所有回调函数,这样就实现了“通知”的目的。说的云里雾里的,还是看图吧:

对系统A来说,它自己的通知队列上被被人注册了三个回调函数,那么当系统A的某个事件发生时,它必须去遍历自己的事件队列headA,然后依次去执行队列里每个回调函数(这么说不太准确,不一定每个函数都执行,后面解释)。对子系统B来说,情况是一样地。

内核里通知链队列里,每个元素都是一个通知块,原型如下:

/* include/linux/notifier.h*/
struct notifier_block {int (*notifier_call)(struct notifier_block *, unsigned long, void *);struct notifier_block *next;int priority;
};

notifier_call是回调函数的指针,指向的函数是当事件发生时要执行的函数;next指向下一个回调函数的通知块;priority是事件发生时本函数(由notifier_call所指向)执行的优先级,数字越小优先级越高,越会先被执行。我们看到这个通知块的结构并不复杂,甚至可以说是已经非常简单明了,每一个这样的通知块串起来就是我们所说的通知链了。
Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。下面我们分别看一下这三类通知链:

1、原子通知链(Atomic Notifier Chains)
原子通知链的链表头定义如下:

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

我们可以看到原子通知链采用的是自旋锁,通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,而且不允许阻塞。

2、可阻塞通知链(Blocking Notifier Chains)
可阻塞的通知链有两种类型,一种用信号量实现回调函数的加锁,另一种是采用互斥锁和叫做“可睡眠的读拷贝更新机制”(Sleepable Read-Copy UpdateSleepable Read-Copy Update),链表头的定义分别如下:

struct blocking_notifier_head {struct rw_semaphore rwsem;struct notifier_block *head;
};struct srcu_notifier_head {struct mutex mutex;struct srcu_struct srcu;struct notifier_block *head;
};

可阻塞型的通知链运行在进程空间的上下文环境里。

3、原始通知链(Raw Notifier Chains)
顾名思义,没有任何安保措施,对链表的加锁和保护全部由调用者自己实现,定义如下:

struct raw_notifier_head {struct notifier_block *head;
};

关于三大类通知链详细的描述在notifier.h文件头部已经有非常详细的描述和说明了,这里我就浪费笔墨了,大家看源代码里的英文注释完全足够了。

这三类通知链,我们该怎么用这才是我需要关心的问题。在定义自己的通知链的时候,心里必须明确,自己需要一个什么样类型的通知链,是原子的、可阻塞的还是一个原始通知链。内核中用于定义并初始化不同类通知链的函数分别是:

ATOMIC_NOTIFIER_HEAD(name)     //定义并初始化一个名为name的原子通知链
BLOCKING_NOTIFIER_HEAD(name)   //定义并初始化一个名为name的阻塞通知链
RAW_NOTIFIER_HEAD(name)        //定义并初始化一个名为name的原始通知链

这一组接口一般在下列格式的代码里见到的会比较多一点:

static struct atomic_notifier_head dock_notifier_list;
ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);

OK,有了通知链只是第一步,接下来我们还需要提供往通知链上注册通知块、卸载通知块、已经遍历执行通知链上每个通知块里回调函数的基本接口,说白了就是单向链表的插入、删除和遍历,这样理解就可以了。
内核提供最基本的通知链的常用接口如下:

static int notifier_chain_register(struct notifier_block **nl,  struct notifier_block *n);
static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);   

这最基本的三个接口分别实现了对通知链上通知块的注册、卸载和遍历操作,可以想象,原子通知链、可阻塞通知链和原始通知链一定会对基本通知链的操作函数再进行一次包装的,事实也确实如此:

//原子通知链
int atomic_notifier_chain_register(struct atomic_notifier_head *nh,  struct notifier_block *nb);
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);//可阻塞通知链
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb);int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb);//原始通知链
int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);

上述这三类通知链的基本API又构成了内核中其他子系统定义、操作自己通知链的基础。例如,Netlink定义了一个原子通知链,所以,它对原子通知链的基本API又封装了一层,以形成自己的特色:

/*net/netlink/af_netlink.c*/
...
static ATOMIC_NOTIFIER_HEAD(netlink_chain);
...
int netlink_register_notifier(struct notifier_block *nb)
{return atomic_notifier_chain_register(&netlink_chain, nb);
}
...int netlink_unregister_notifier(struct notifier_block *nb)
{return atomic_notifier_chain_unregister(&netlink_chain, nb);
}

网络事件也有一个原子通知链:

/*net/core/netevent.c*/
/**    Network event notifiers**    Authors:* Tom Tucker <tom@opengridcomputing.com>* Steve Wise <swise@opengridcomputing.com>**    This program is free software; you can redistribute it and/or* modify it under the terms of the GNU General Public License* as published by the Free Software Foundation; either version* 2 of the License, or (at your option) any later version.**    Fixes:*/#include <linux/rtnetlink.h>
#include <linux/notifier.h>
#include <net/netevent.h>static ATOMIC_NOTIFIER_HEAD(netevent_notif_chain);/***    register_netevent_notifier - register a netevent notifier block*    @nb: notifier**    Register a notifier to be called when a netevent occurs.*    The notifier passed is linked into the kernel structures and must*    not be reused until it has been unregistered. A negative errno code*    is returned on a failure.*/
int register_netevent_notifier(struct notifier_block *nb)
{int err;err = atomic_notifier_chain_register(&netevent_notif_chain, nb);
    return err;
}/***    netevent_unregister_notifier - unregister a netevent notifier block*    @nb: notifier**    Unregister a notifier previously registered by*    register_neigh_notifier(). The notifier is unlinked into the*    kernel structures and may then be reused. A negative errno code*    is returned on a failure.*/int unregister_netevent_notifier(struct notifier_block *nb)
{
    return atomic_notifier_chain_unregister(&netevent_notif_chain, nb);
}/***    call_netevent_notifiers - call all netevent notifier blocks* @val: value passed unmodified to notifier function* @v: pointer passed unmodified to notifier function**    Call all neighbour notifier blocks. Parameters and return value*    are as for notifier_call_chain().*/int call_netevent_notifiers(unsigned long val, void *v)
{
    return atomic_notifier_call_chain(&netevent_notif_chain, val, v);
}EXPORT_SYMBOL_GPL(register_netevent_notifier);
EXPORT_SYMBOL_GPL(unregister_netevent_notifier);
EXPORT_SYMBOL_GPL(call_netevent_notifiers)

可阻塞通知链里的SRCU通知链,由于使用条件较苛刻,限制条件较多,所以使用的机会不是很多,除非你特别清楚这种类型的通知链的适用场合,在2.6.32的内核里只有cpufreq.c在用这种类型的通知链。

关于内核通知链不像Netlink那样,既可以用于内核与用户空间的通信,还能用于内核不同子系统之间的通信,通知链只能用于内核不同子系统之间的通信。

以前有个女神,超凡脱俗、出水芙蓉,不过在怎么滴也是人,是人就会有各种各样的需求,女神的所有需求都放在她的需求链表里requirment_chain,比如物质需求,精神需求等等。然后女神首先需要做的事情就是将自己的需求链给实例化了:

/* Godness.c */
/* 我们假设女神需求链的类型是原始通知链(PS:不要和原始需求挂钩理解 -_-||)*/
static RAW_NOTIFIER_HEAD(requirment_chain);

当需求被定义出来后,还需要向外提供两个接口:一个是别人用于满足她需求的接口,另一个是别人需要和她break out的接口(虽然在现实生活中这种情况比较令人sadness,但女神所在的虚拟世界里这个是必须的)。于是女神提供了别人往其需求链注册响应函数的接口和卸载响应函数的接口:

/* Godness.c*/int register_godness_notifier(struct notifier_block *nb)
{return raw_notifier_chain_register(&requirment_chain, nb);
}
EXPORT_SYMBOL(register_godness_notifier); //注册函数实现了之后必须将其公布出去,不然别人怎么看得到呢int unregister_godness_notifier(struct notifier_block *nb)
{return raw_notifier_chain_unregister(&requirment_chain, nb);
}
EXPORT_SYMBOL(unregister_godness_notifier); //同上

然后,女神要做的就是提需求,并看看哪些屌丝、土豪或高富帅来追求自己:

int call_godness_notifier_chain(unsigned long val, void *v)
{return raw_notifier_call_chain(&requirment_chain, val, v);
}
EXPORT_SYMBOL(call_godness_notifier_chain);
#define PHY_REQ 0 //物质需求
#define SPR_REQ 1 //精神需求#define REQ_MAX SPR_REQ+1static int make_requirment_thread(void *data)
{int i = 10;struct completion cmpl;unsigned int requirment_type = 0;printk("[Godness]requirements thread starting...\n");while((i--) > 0){init_completion(&cmpl);wait_for_completion_timeout(&cmpl, 3 * HZ);get_random_bytes(&requirment_type,sizeof(requirment_type));  //生成一个内核随机数requirment_type %= REQ_MAX;  //需求类型之可能是0或者1printk("[Godness]requirment type: %d \n",requirment_type);call_godness_notifier_chain(requirment_type,NULL);}printk("[Godness]requirements thread ended!\n");return 0;
}

女神的最终模型如下:

#include <asm/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL");#define PHY_REQ 0 //物质需求
#define SPR_REQ 1 //精神需求
#define REQ_MAX SPR_REQ+1extern void get_random_bytes(void* buf,int nbytes);
static struct task_struct *requirments_thread = NULL;
/*
* 女神所有的需求都会列在她的需求链里。这里我们定义了一个原始通知链,暂时没考虑锁的问题。
*/
static RAW_NOTIFIER_HEAD(requirment_chain);/*
* 如果谁想追求本女王,就来献殷勤吧
*/
int register_godness_notifier(struct notifier_block *nb)
{return raw_notifier_chain_register(&requirment_chain, nb);
}
EXPORT_SYMBOL(register_godness_notifier);/*
* 伺候不起的,赶紧Get out as soon as
*/
int unregister_godness_notifier(struct notifier_block *nb)
{return raw_notifier_chain_unregister(&requirment_chain, nb);
}
EXPORT_SYMBOL(unregister_godness_notifier);/*
* 本女王开始提需求了,看看谁能才是真心的。
*/
int call_godness_notifier_chain(unsigned long val, void *v)
{return raw_notifier_call_chain(&requirment_chain, val, v);
}
EXPORT_SYMBOL(call_godness_notifier_chain);static int make_requirment_thread(void *data)
{int i = 10;struct completion cmpl;unsigned int requirment_type = 0;printk("[Godness]requirements thread starting...\n");while((i--) > 0){init_completion(&cmpl);wait_for_completion_timeout(&cmpl, 3 * HZ);get_random_bytes(&requirment_type,sizeof(requirment_type));  //生成一个内核随机数requirment_type %= REQ_MAX;  //需求类型之可能是0或者1printk("[Godness]requirment type: %d \n",requirment_type);call_godness_notifier_chain(requirment_type,NULL);}printk("[Godness]requirements thread ended!\n");return 0;
}static int __init godness_init_notifier(void)
{printk("[Attention]The Godness coming into the world!\n");requirments_thread = kthread_run(make_requirment_thread,NULL,"Godness_requirments_thread");return 0;
}static void __exit godness_exit_notifier(void)
{printk("[Attention]The Godness leaving out!\n");
}
module_init(godness_init_notifier);
module_exit(godness_exit_notifier);

这个时候有个叫土豪的家伙,突然于茫茫人海中发现了女神,并且知道了女神有金钱需求的欲望,于是土豪向女神的需求链里注册了一个金钱的响应函数,这样一旦女神需要用钱的时候他第一时间就能收到通知,然后以迅雷下载不及掩耳盗铃之势加以满足:

/*Tuhao.c*/extern int register_godness_notifier(struct notifier_block*);
extern int unregister_godness_notifier(struct notifier_block*);static int baby_need_money(struct notifier_block *this, unsigned long event, void *ptr)
{if(event != 0)  //不是金钱需求关我鸟事{return NOTIFY_DONE; //Don't care}printk("[Tuhao]Hi Baby,$$$$$$$$ 么么哒 \n");return NOTIFY_OK;
}static struct notifier_block cash_notifier =
{.notifier_call = baby_need_money,.priority = 2,
};static int __init tuhao_register(void)
{int err;printk("[Tuhao]Tuhao register cash_requirment response to Godness...");err = register_godness_notifier(&cash_notifier);if (err){printk("Refused!\n");return -1;}printk("Accepted!\n");return err;
}static void __exit tuhao_unregister(void)
{unregister_godness_notifier(&cash_notifier);printk("[Tuhao]Tuhao is giving up Godness!(Son of bitch)\n");
}module_init(tuhao_register);
module_exit(tuhao_unregister);

这时,有一个屌丝,也于茫茫人海中发现了女神,他发现女神喜欢音乐,于是他开始响应女神的精神需求:

/*Diors.c*/extern int register_godness_notifier(struct notifier_block*);
extern int unregister_godness_notifier(struct notifier_block*);static int godness_need_music(struct notifier_block *this, unsigned long event, void *ptr)
{if(event != 1) //我又没钱,给不了你大房子、气派的车子...{return NOTIFY_DONE; //Don't care}printk("[Diors]Hi girl,This is a classic Music disk,take it. \n");return NOTIFY_OK;
}static struct notifier_block music_notifier =
{.notifier_call = godness_need_music,.priority = 2,
};static int __init diors_register(void)
{int err;printk("[Diors]Diors register music_requirment response to Godness...");err = register_godness_notifier(&music_notifier);if (err){printk("Refused!\n");return -1;}printk("Accepted!\n");return err;
}static void __exit diors_unregister(void)
{unregister_godness_notifier(&music_notifier);printk("[Diors]Tuhao is giving up Godness!(What a pity)\n");
}module_init(diors_register);
module_exit(diors_unregister);
#Makefile for funobj-m:=Goddess.o Tuhao.o Diors.oCURRENT_PATH := $(shell pwd)
KERNEL_VERSION := $(shell uname -r)
KERNEL_HEADER_DIR := /usr/src/kernels/$(LINUX_KERNELKERNEL_VERSION)
all:make -C $(KERNEL_HEADER_DIR) M=$(CURRENT_PATH) modules
clean:make -C $(KERNEL_HEADER_DIR) M=$(CURRENT_PATH) clean

K,让我们总结一下Linux内核通知链的应用场景。如果一个子系统需要向外通告事件时,它需要首先定义自己的通知链对象,然后向内核里其他子系统提供一个向自己的通知链注册消息响应函数的接口,当然也必须提供一个用于从自己从自己的通知链上卸载响应函数的接口。接下来,我们这个子系统要做的事情就是根据自己的实际运行情况,定期地产生一些消息,并调用自己通知链里别的系统已经注册好了消息响应函数,这样别的子系统就可以根据我们这个系统的的消息类型进行一些处理动作。
那么多个子系统对我们的同一种消息都挂有响应函数时该怎么处理?鉴于时间关系,下面。

书接上回,闲话不表。话说,女神无论是在土豪或者屌丝那里都找不到归属感,冥冥之中天上掉下来一个王子(PS:又名高富帅),既可以满足女神的物质需求还可以满足女神的精神需求:

/*GFS.c*/
#include <asm/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");/*
* 注册通知链
*/extern int register_godness_notifier(struct notifier_block*);
extern int unregister_godness_notifier(struct notifier_block*);static int sweet_heart_requirments(struct notifier_block *this, unsigned long event, void *ptr)
{switch(event){case 0:printk("[GFS]Hi honey,the VISA card is ready for you! \n");break;case 1:printk("[GFS]Hi honey,let me play the piano for you! \n");break;default:break;}return 0;
}static struct notifier_block honey_notifier =
{.notifier_call = sweet_heart_requirments,.priority = 2,
};static int __init GFS_register(void)
{int err;printk("[GFS]GFS register honey_requirment response to Godness...");err = register_godness_notifier(&honey_notifier);if (err){printk("Refused!\n");return -1;}printk("Accepted!\n");return err;
}/*
* 卸载刚刚注册了的通知链
*/
static void __exit GFS_unregister(void)
{unregister_godness_notifier(&honey_notifier);printk("[GFS]GFS broke up with Godness!(How sadness)\n");
}
module_init(GFS_register);
module_exit(GFS_unregister);

内核打印

2 00:39:41 localhost kernel: [Attention]The Godness coming into the world!
Jun  2 00:39:41 localhost kernel: [Godness]requirements thread starting...
Jun  2 00:39:44 localhost kernel: [Godness]requirment type: 1
Jun  2 00:39:45 localhost kernel: [Tuhao]Tuhao register Cash event  to Godness...Accepted!
Jun  2 00:39:47 localhost kernel: [Godness]requirment type: 0
Jun  2 00:39:47 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:39:49 localhost kernel: [Diors]Diors register music_requirment response to Godness...Accepted!
Jun  2 00:39:50 localhost kernel: [Godness]requirment type: 1
Jun  2 00:39:50 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:39:53 localhost kernel: [Godness]requirment type: 1
Jun  2 00:39:53 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:39:53 localhost kernel: [GFS]GFS register honey_requirment response to Godness...Accepted!
Jun  2 00:39:56 localhost kernel: [Godness]requirment type: 1
Jun  2 00:39:56 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:39:56 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:39:59 localhost kernel: [Godness]requirment type: 0
Jun  2 00:39:59 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:39:59 localhost kernel: [GFS]Hi honey,the VISA card is ready for you!
Jun  2 00:40:02 localhost kernel: [Godness]requirment type: 1
Jun  2 00:40:02 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:40:02 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:40:05 localhost kernel: [Godness]requirment type: 1
Jun  2 00:40:05 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:40:05 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:40:08 localhost kernel: [Godness]requirment type: 0
Jun  2 00:40:08 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:40:08 localhost kernel: [GFS]Hi honey,the VISA card is ready for you!
Jun  2 00:40:11 localhost kernel: [Godness]requirment type: 1
Jun  2 00:40:11 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:40:11 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:40:11 localhost kernel: [Godness]requirements thread ended!
Jun  2 00:40:52 localhost kernel: [GFS]GFS broke up with Godness!(How sadness)
Jun  2 00:40:52 localhost kernel: [Diors]Tuhao is giving up Godness!(What a pity)
Jun  2 00:40:52 localhost kernel: [Tuhao]Tuhao is giving up Godness!(Son of bitch)
Jun  2 00:40:52 localhost kernel: [Attention] The Godness leaving out!

我们可以看到,高富帅向女神需求通知链上注册的回调函数,优先级也是2,我们在测试的时候是先加载土豪、再加载屌丝最后加载高富帅,所以每当女神提出需求时,如果是金钱需求则最先被土豪响应,如果是精神需求则最先被屌丝给响应了。这完全不符合高富帅的特征啊,于是高富帅将自己的优先级提高到5,然后replay一次:

static struct notifier_block honey_notifier =
{.notifier_call = sweet_heart_requirments,.priority = 5,
};

内核打印

localhost kernel: [Attention]The Godness coming into the world!
Jun  2 00:50:29 localhost kernel: [Godness]requirements thread starting...
Jun  2 00:50:32 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:33 localhost kernel: [Tuhao]Tuhao register Cash event  to Godness...Accepted!
Jun  2 00:50:35 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:35 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:50:37 localhost kernel: [Diors]Diors register music_requirment response to Godness...Accepted!
Jun  2 00:50:38 localhost kernel: [Godness]requirment type: 1
Jun  2 00:50:38 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:50:41 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:41 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:50:41 localhost kernel: [GFS]GFS register honey_requirment response to Godness...Accepted!
Jun  2 00:50:44 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:44 localhost kernel: [GFS]Hi honey,the VISA card is ready for you!
Jun  2 00:50:44 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:50:47 localhost kernel: [Godness]requirment type: 1
Jun  2 00:50:47 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:50:47 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:50:50 localhost kernel: [Godness]requirment type: 1
Jun  2 00:50:50 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:50:50 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:50:53 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:53 localhost kernel: [GFS]Hi honey,the VISA card is ready for you!
Jun  2 00:50:53 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:50:56 localhost kernel: [Godness]requirment type: 0
Jun  2 00:50:56 localhost kernel: [GFS]Hi honey,the VISA card is ready for you!
Jun  2 00:50:56 localhost kernel: [Tuhao]Hi Baby,$$$$$$$$ 么么哒
Jun  2 00:50:59 localhost kernel: [Godness]requirment type: 1
Jun  2 00:50:59 localhost kernel: [GFS]Hi honey,let me play the piano for you!
Jun  2 00:50:59 localhost kernel: [Diors]Hi girl,This is a classic Music disk,take it.
Jun  2 00:50:59 localhost kernel: [Godness]requirements thread ended!
Jun  2 00:51:04 localhost kernel: [GFS]GFS broke up with Godness!(How sadness)
Jun  2 00:51:04 localhost kernel: [Diors]Tuhao is giving up Godness!(What a pity)
Jun  2 00:51:04 localhost kernel: [Tuhao]Tuhao is giving up Godness!(Son of bitch)
Jun  2 00:51:04 localhost kernel: [Attention] The Godness leaving out!

这次我们看到,高富帅终于如愿以偿地于第一时间响应到了女神的需求。再考虑一种情况,如果高富帅不想让女神的需求传递到土豪和屌丝那里,怎么办?其实也很简单,高富帅只要在自己的响应函数里向女神返回NOTIFY_STOP值就可以了:

static int sweet_heart_requirments(struct notifier_block *this, unsigned long event, void *ptr)
{switch(event){case 0:printk("[GFS]Hi honey,the VISA card is ready for you! \n");break;case 1:printk("[GFS]Hi honey,let me play the piano for you! \n");break;default:break;}return NOTIFY_STOP;
}

1、如果一个子系统A在运行过程中会产生一个实时的事件,而这些事件对其他子系统来说非常重要,那么系统A可以定义一个自己的通知链对象,根据需求可以选择原子通知链、非阻塞通知链或者原始通知链,并向外提供向这个通知链里注册、卸载执行事件的回调函数的接口;
2、如果子系统B对子系统A中的某(些)个事件感兴趣,或者说强依赖,就是说系统B需要根据系统A中某些事件来执行自己特定的操作,那么此时系统就需要实例化一个通知块struct notifier_block XXX{},然后编写通知块里的回调处理函数来响应A系统中响应的事件就可以了;
3、在我们这个示例里用到了struct notifier_block{}的优先级特性,其实在标准内核里每个实例化的通知块都没有用优先级字段。不用优先级字段的结果就是,先注册的通知块里的回调函数在事件发生时会先执行。注意这里所说的后注册指的是模块被动态加载内核的先后顺序,和哪个模块的代码先写完没有关系,注意区分。意思就是说,如果子系统B和C都对子系统A的up事件感兴趣,B和C在向A注册up事件的回调函数时并没有指定函数的优先级。无论是通过insmod手动加载模块B和C,还是系统boot时自动加载B和C,哪个模块先被加载,它的回调函数在A系统的up事件发生时会先被执行;
4、关于通知链的回调函数,正常情况下都需要返回NOTIFY_OK或者NOTIFY_DONE,这样通知链上后面挂载的其他函数可以继续执行。如果返回NOTIFY_STOP,则会使得本通知链上后续挂载的函数无法得到执行,除非你特别想这么做,否则在编写通知链的回调函数时一般最好不要返回这个值;
5、通知链上回调函数的原型int (notifier_call)(struct notifier_block , unsigned long, void *),其中第二个参数一般用于指明事件的类型,通常都是一个整数。而第三个参数是一个void类型的内存地址,在不同的子系统中用于表示不同的信息,例如在邻居子系统里,第三个参数可以是一个邻居信息结构体对象的地址struct neighbour *v;而在驱动框架里又可以是一个net_device{}结构体对象的地址等等;我们在设计自己的通知链系统可以用第三个入参实现在通知系统与被通知系统之间实现数据的传递,以便被通知系统的工作可以更加紧凑、高效;
6、如果以后再看到内核中某个子系统在调用通知链的注册函数时,不要心虚、不要怕。做到以下两点就没事儿了:
第一:心里首先要明确,这个注册通知链回调函数的系统一定和提供通知链的系统有某种连续,且本系统需要对那个系统的某些重要事件进行响应;
第二:看本系统注册的通知回调函数的实现,具体看它对那些事件感兴趣,并且是怎么处理的;
第三:看看原提供通知链对象的系统有哪些事件;
最后,心里也就明白了这个系统为什么要用通知链来感知别的系统的变化了,这样一来,对这两个系统从宏观到甚微观的层面就都有一个总体的认识和把握,后续再研究起来就顺风顺水了,而这也正是内核通知链的神奇所在。


i2c-dev.c 中的bus_register_notifier 的解析。

bus_register_notifier的具体形式

/* bus.c */
int bus_register_notifier(struct bus_type *bus, struct notifier_block *nb)
{return blocking_notifier_chain_register(&bus->p->bus_notifier, nb);
}

使用的是阻塞型的通知链表,用的已经是最底层的通知链函数了。

i2c 接口通过gpio模拟的方式驱动比较方便。一般使用的文件为 i2c-gpio.c、i2c-algo-bit.c、i2c-core、i2c-dev.c。

/*i2c-gpio.c*/
static int i2c_gpio_probe(struct platform_device *pdev)
{struct i2c_gpio_private_data *priv;struct i2c_gpio_platform_data *pdata;struct i2c_algo_bit_data *bit_data;struct i2c_adapter *adap;unsigned int sda_pin, scl_pin;int ret;/* First get the GPIO pins; if it fails, we'll defer the probe. */if (pdev->dev.of_node) {ret = of_i2c_gpio_get_pins(pdev->dev.of_node,&sda_pin, &scl_pin);if (ret)return ret;} else {if (!dev_get_platdata(&pdev->dev))return -ENXIO;pdata = dev_get_platdata(&pdev->dev);sda_pin = pdata->sda_pin;scl_pin = pdata->scl_pin;}ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");if (ret) {if (ret == -EINVAL)ret = -EPROBE_DEFER;    /* Try again later */return ret;}ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");if (ret) {if (ret == -EINVAL)ret = -EPROBE_DEFER;    /* Try again later */return ret;}priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);if (!priv)return -ENOMEM;adap = &priv->adap;bit_data = &priv->bit_data;pdata = &priv->pdata;if (pdev->dev.of_node) {pdata->sda_pin = sda_pin;pdata->scl_pin = scl_pin;of_i2c_gpio_get_props(pdev->dev.of_node, pdata);} else {memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));}if (pdata->sda_is_open_drain) {gpio_direction_output(pdata->sda_pin, 1);bit_data->setsda = i2c_gpio_setsda_val;} else {gpio_direction_input(pdata->sda_pin);bit_data->setsda = i2c_gpio_setsda_dir;}if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {gpio_direction_output(pdata->scl_pin, 1);bit_data->setscl = i2c_gpio_setscl_val;} else {gpio_direction_input(pdata->scl_pin);bit_data->setscl = i2c_gpio_setscl_dir;}if (!pdata->scl_is_output_only)bit_data->getscl = i2c_gpio_getscl;bit_data->getsda = i2c_gpio_getsda;if (pdata->udelay)bit_data->udelay = pdata->udelay;else if (pdata->scl_is_output_only)bit_data->udelay = 50;          /* 10 kHz */elsebit_data->udelay = 5;           /* 100 kHz */if (pdata->timeout)bit_data->timeout = pdata->timeout;elsebit_data->timeout = HZ / 10;        /* 100 ms */bit_data->data = pdata;adap->owner = THIS_MODULE;if (pdev->dev.of_node)strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));elsesnprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);adap->algo_data = bit_data;adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;adap->dev.parent = &pdev->dev;adap->dev.of_node = pdev->dev.of_node;adap->nr = pdev->id;**ret = i2c_bit_add_numbered_bus(adap);**if (ret)return ret;platform_set_drvdata(pdev, priv);dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",pdata->sda_pin, pdata->scl_pin,pdata->scl_is_output_only? ", no clock stretching" : "");return 0;
}

i2c_gpio_probe 驱动函数获取系统分配的gpio 资源,及gpio的电平设置

i2c_bit_add_numbered_bus(adap) 即调用i2c-algo-bit.c 中的函数

/*i2c-algo-bit.c*/ int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}static int __i2c_bit_add_bus(struct i2c_adapter *adap,int (*add_adapter)(struct i2c_adapter *))
{struct i2c_algo_bit_data *bit_adap = adap->algo_data;int ret;if (bit_test) {ret = test_bus(adap);if (bit_test >= 2 && ret < 0)return -ENODEV;}/* register new adapter to i2c module... */adap->algo = &i2c_bit_algo;adap->retries = 3;if (bit_adap->getscl == NULL)adap->quirks = &i2c_bit_quirk_no_clk_stretch;ret = add_adapter(adap);if (ret < 0)return ret;/* Complain if SCL can't be read */if (bit_adap->getscl == NULL) {dev_warn(&adap->dev, "Not I2C compliant: can't read SCL\n");dev_warn(&adap->dev, "Bus may be unreliable\n");}return 0;
}

i2c-algo-bit.c 主要通过i2c-gpio.c 中获取的gpio资源完成i2c 发送、接收数据核心算法
adap->algo = &i2c_bit_algo; 具体的可以查看 i2c-algo-bit.c 源文件

ret = add_adapter(adap);实际调用的是 i2c_add_numbered_adapter(adap)函数

i2c_add_numbered_adapter 的原型

/* i2c-core.c */
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{if (adap->nr == -1) /* -1 means dynamically assign bus id */return i2c_add_adapter(adap);return __i2c_add_numbered_adapter(adap);
}// 最终调用的是i2c_add_adapter() 函数
int i2c_add_adapter(struct i2c_adapter *adapter)
{struct device *dev = &adapter->dev;int id;if (dev->of_node) {id = of_alias_get_id(dev->of_node, "i2c");if (id >= 0) {adapter->nr = id;return __i2c_add_numbered_adapter(adapter);}}mutex_lock(&core_lock);id = idr_alloc(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);mutex_unlock(&core_lock);if (WARN(id < 0, "couldn't get idr"))return id;adapter->nr = id;return i2c_register_adapter(adapter);
}static int i2c_register_adapter(struct i2c_adapter *adap)
{int res = -EINVAL;/* Can't register until after driver model init */if (WARN_ON(!is_registered)) {res = -EAGAIN;goto out_list;}/* Sanity checks */if (WARN(!adap->name[0], "i2c adapter has no name"))goto out_list;if (!adap->algo) {pr_err("adapter '%s': no algo supplied!\n", adap->name);goto out_list;}if (!adap->lock_ops)adap->lock_ops = &i2c_adapter_lock_ops;rt_mutex_init(&adap->bus_lock);rt_mutex_init(&adap->mux_lock);mutex_init(&adap->userspace_clients_lock);INIT_LIST_HEAD(&adap->userspace_clients);/* Set default timeout to 1 second if not already set */if (adap->timeout == 0)adap->timeout = HZ;/* register soft irqs for Host Notify */res = i2c_setup_host_notify_irq_domain(adap);if (res) {pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",adap->name, res);goto out_list;}dev_set_name(&adap->dev, "i2c-%d", adap->nr);adap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);if (res) {pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);goto out_list;}dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);pm_runtime_no_callbacks(&adap->dev);pm_suspend_ignore_children(&adap->dev, true);pm_runtime_enable(&adap->dev);#ifdef CONFIG_I2C_COMPATres = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent);if (res)dev_warn(&adap->dev,"Failed to create compatibility class link\n");
#endifi2c_init_recovery(adap);/* create pre-declared device nodes */of_i2c_register_devices(adap);i2c_acpi_register_devices(adap);i2c_acpi_install_space_handler(adap);if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);/* Notify drivers */mutex_lock(&core_lock);bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);mutex_unlock(&core_lock);return 0;out_list:mutex_lock(&core_lock);idr_remove(&i2c_adapter_idr, adap->nr);mutex_unlock(&core_lock);return res;
}

i2c_register_adapter 完成对设备名的命名,绑定i2c_bus 总线,及device_register注册设备。

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);adap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);

在i2c-*.c 文件中并为发现 通知链的相关函数或者数据结构。
在内核根文件中搜索 BUS_NOTIFY_ADD_DEVICE

发现在 base/core.c 中存在,device_register() 是其中的函数,进入device_register() 中

/* drivers/base/core.c */int device_register(struct device *dev)
{device_initialize(dev);return device_add(dev);
}int device_add(struct device *dev)
{struct device *parent = NULL;struct kobject *kobj;struct class_interface *class_intf;int error = -EINVAL;struct kobject *glue_dir = NULL;dev = get_device(dev);if (!dev)goto done;if (!dev->p) {error = device_private_init(dev);if (error)goto done;}/** for statically allocated devices, which should all be converted* some day, we need to initialize the name. We prevent reading back* the name, and force the use of dev_name()*/if (dev->init_name) {dev_set_name(dev, "%s", dev->init_name);dev->init_name = NULL;}/* subsystems can specify simple device enumeration */if (!dev_name(dev) && dev->bus && dev->bus->dev_name)dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);if (!dev_name(dev)) {error = -EINVAL;goto name_error;}pr_debug("device: '%s': %s\n", dev_name(dev), __func__);parent = get_device(dev->parent);kobj = get_device_parent(dev, parent);if (kobj)dev->kobj.parent = kobj;/* use parent numa_node */if (parent && (dev_to_node(dev) == NUMA_NO_NODE))set_dev_node(dev, dev_to_node(parent));/* first, register with generic layer. *//* we require the name to be set before, and pass NULL */error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);if (error) {glue_dir = get_glue_dir(dev);goto Error;}/* notify platform of device entry */if (platform_notify)platform_notify(dev);error = device_create_file(dev, &dev_attr_uevent);if (error)goto attrError;error = device_add_class_symlinks(dev);if (error)goto SymlinkError;error = device_add_attrs(dev);if (error)goto AttrsError;error = bus_add_device(dev);if (error)goto BusError;error = dpm_sysfs_add(dev);if (error)goto DPMError;device_pm_add(dev);if (MAJOR(dev->devt)) {error = device_create_file(dev, &dev_attr_dev);if (error)goto DevAttrError;error = device_create_sys_dev_entry(dev);if (error)goto SysEntryError;devtmpfs_create_node(dev);}/* Notify clients of device addition.  This call must come* after dpm_sysfs_add() and before kobject_uevent().*/**if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);**kobject_uevent(&dev->kobj, KOBJ_ADD);bus_probe_device(dev);if (parent)klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);if (dev->class) {mutex_lock(&dev->class->p->mutex);/* tie the class to the device */klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);/* notify any interfaces that the device is here */list_for_each_entry(class_intf,&dev->class->p->interfaces, node)if (class_intf->add_dev)class_intf->add_dev(dev, class_intf);mutex_unlock(&dev->class->p->mutex);}
done:put_device(dev);return error;SysEntryError:if (MAJOR(dev->devt))device_remove_file(dev, &dev_attr_dev);DevAttrError:device_pm_remove(dev);dpm_sysfs_remove(dev);DPMError:bus_remove_device(dev);BusError:device_remove_attrs(dev);AttrsError:device_remove_class_symlinks(dev);SymlinkError:device_remove_file(dev, &dev_attr_uevent);attrError:kobject_uevent(&dev->kobj, KOBJ_REMOVE);glue_dir = get_glue_dir(dev);kobject_del(&dev->kobj);Error:cleanup_glue_dir(dev, glue_dir);put_device(parent);
name_error:kfree(dev->p);dev->p = NULL;goto done;
}

发现代码

if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);

说明了通知链的链表头、遍历的类型为BUS_NOTIFY_ADD_DEVICE、传递的数据为 struct device dev。

总结一下在i2c中通知链的流程

初始化

// 注册i2c总线 ** i2c-core.c
static int __init i2c_init(void)
{...bus_register(&i2c_bus_type);...
}// 初始化总线中的一个名为中的阻塞通知链 ** driver/base/bus.c
int bus_register(struct bus_type *bus)
{...struct subsys_private *priv;priv->bus = bus;bus->p = priv;BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);...
}

通知链回调函数注册

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{struct i2c_adapter *adap;struct i2c_dev *i2c_dev;int res;if (dev->type != &i2c_adapter_type)return 0;adap = to_i2c_adapter(dev);i2c_dev = get_free_i2c_dev(adap);if (IS_ERR(i2c_dev))return PTR_ERR(i2c_dev);//将i2c-gpio.c 中的设备与文件操作函数绑定。**cdev_init(&i2c_dev->cdev, &i2cdev_fops);**i2c_dev->cdev.owner = THIS_MODULE;res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);if (res)goto error_cdev;/* register this i2c device with the driver core */i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d", adap->nr);if (IS_ERR(i2c_dev->dev)) {res = PTR_ERR(i2c_dev->dev);goto error;}pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",adap->name, adap->nr);return 0;
error:cdev_del(&i2c_dev->cdev);
error_cdev:put_i2c_dev(i2c_dev);return res;
}/*i2c-dev.c*/
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,void *data)
{struct device *dev = data;switch (action) {case BUS_NOTIFY_ADD_DEVICE:return i2cdev_attach_adapter(dev, NULL);case BUS_NOTIFY_DEL_DEVICE:return i2cdev_detach_adapter(dev, NULL);}return 0;
}static struct notifier_block i2cdev_notifier = {.notifier_call = i2cdev_notifier_call,
};static int __init i2c_dev_init(void)
{...res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);...
}

通知链的遍历函数

/*i2c-gpio.c*/
i2c_bit_add_numbered_bus(adap);/*i2c-algo-bit.c*/
i2c_add_numbered_adapter(adap);/*i2c-core*/
i2c_add_numbered_adapter()->i2c_add_adapter(struct i2c_adapter *adapter)\->i2c_register_adapter(adapter)->device_register(&adap->dev)

通过gpio 模拟i2c 设备、各文件的作用
i2c-gpio.c : 获取gpio 资源,完成gpio 高低电平设置、
i2c-algo-bit.c : 完成设备发送、接收的核心数据结构i2c_algorithm
i2c-core.c : 完成设备的注册
i2c-dev.c : 完成设备文件相关函数(read、write 、ioctl等)

联系: 通过内核通知链,将i2c-core.c 注册的设备,通过通知链回调函数,将i2c-dev.c 中的文件操作函数进行绑定。

linux驱动——内核通知链(探究i2c-dev.c 中的bus_register_notifier函数所得)相关推荐

  1. linux 内核通知,[Linux] 内核通知链 notifier

    Linux 内核中每个模块之间都是独立的,如果模块需要感知其他模块的事件,就需要用到内核通知链. 最典型的通知链应用就是 LCD 和 TP 之间,TP 需要根据 LCD 的亮灭来控制是否打开关闭触摸功 ...

  2. linux 通知链,Linux内核通知链notifier

    当有事件发生时,通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历n1指向的通知链中所有的元素,然后依次调用每一个的回调函数,完成通知动作. static int ...

  3. Linux内核通知链机制的原理及实现【转】

    转自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html 一.概念: 大多数内核子系统都是相互独立的,因此某个子系统可能对其 ...

  4. Linux 内核通知链和例程代码

    概念 大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制.通知链表只 ...

  5. 深入理解Linux内核通知链(Notifier)

    数据结构 内核使用struct notifier_block结构代表一个notifier typedef int (*notifier_fn_t)(struct notifier_block *nb, ...

  6. Linux 内核通知链随笔【中】

        关于内核通知链不像Netlink那样,既可以用于内核与用户空间的通信,还能用于内核不同子系统之间的通信,通知链只能用于内核不同子系统之间的通信.那么内核通知链到底是怎么工作的?我们如何才能用好 ...

  7. notifier_chain 内核通知链的学习与使用

    linux 内核通知链 通知链用于将状态改变信息发送给请求这些改变的代码段,通知链可以用于内核将特定的事件传递给感兴趣的内核组件中. 内核定义了主要的事件类型: 死亡通知 网络设备通知 cpu 频率通 ...

  8. 内核通知链(网络子系统为例)

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

  9. Linux内核通知链(notifier chain)

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

  10. Linux驱动入门学习(三):I2C架构全面理解

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

最新文章

  1. 微信的通讯录首字母滑动的实现
  2. 第十六周博客作业西北师范大学|李晓婷
  3. 数据结构常见算法原理讲解100篇(一)-递归和分治算法原理及案例应用
  4. Ubuntu下配置D-Link路由器进行联网
  5. 三维计算机视觉(七)--Spin image
  6. 常见批处理作业调度算法
  7. PHP api接口开发
  8. 数字电路与逻辑设计之集成触发器
  9. JavaScript应用程序开发(游娱平台)
  10. java excel 密码_Java 加密Excel文件(打开时需输入密码)
  11. edge怎么开启沉浸式阅读_《幻塔》首测今日开启 探索沉浸式开放世界_网络游戏新闻...
  12. ios不能保存png_AnyTrans for iOS for Mac (强大的ios设备管理工具)
  13. 【漏洞复现】MS17-010漏洞(永恒之蓝)复现
  14. 你与心中“小仙女”的距离 只差一个海马体照相馆
  15. 小米手机拦截返回音设置不了_为了让自己的手机更好用,我利用了MIUI 10的这些功能...
  16. SSM+停车管理系统 毕业设计-附源码171046
  17. 【测试视频】信号源单边带相位噪声SSB测试
  18. C++day01 const和define之间的区别
  19. win7下笔记本玩游戏不能全屏的解决
  20. 古训:六不合,七不交,四深交

热门文章

  1. 前端js中文转拼音(例:张三转为ZhangSan)
  2. 速腾聚创16线雷达离线和实时跑Lego-loam
  3. CF898D. Alarm Clock(贪心 双指针)
  4. Docker参数 -i -t 的作用
  5. openlayers 地图上加图标_OpenLayers学习笔记中级篇(四、地图图标操作)
  6. 中英文常用标点符号统一清洗为英文格式
  7. gulp-tinypng-nokey [error]: xxx Too many files uploaded at once
  8. 超算优化重在存储,DAOS助力瑞金打造先进的生信大数据平台
  9. Django自学——笔记7(数据库、图书管理系统)
  10. 简单的python爬虫程序:爬取斗鱼直播人气前五十的主播