linux驱动——内核通知链(探究i2c-dev.c 中的bus_register_notifier函数所得)
来源:互联网 发布:上瘾网络剧未删减1 18 编辑:程序博客网 时间:2024/05/21 06:33
在学习 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或者1 printk("[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或者1 printk("[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) modulesclean: 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: 1Jun 2 00:39:45 localhost kernel: [Tuhao]Tuhao register Cash event to Godness...Accepted!Jun 2 00:39:47 localhost kernel: [Godness]requirment type: 0Jun 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: 1Jun 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: 1Jun 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: 1Jun 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: 0Jun 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: 1Jun 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: 1Jun 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: 0Jun 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: 1Jun 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: 0Jun 2 00:50:33 localhost kernel: [Tuhao]Tuhao register Cash event to Godness...Accepted!Jun 2 00:50:35 localhost kernel: [Godness]requirment type: 0Jun 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: 1Jun 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: 0Jun 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: 0Jun 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: 1Jun 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: 1Jun 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: 0Jun 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: 0Jun 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: 1Jun 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 */ else bit_data->udelay = 5; /* 100 kHz */ if (pdata->timeout) bit_data->timeout = pdata->timeout; else bit_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)); else snprintf(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_COMPAT res = 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");#endif i2c_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.cstatic int __init i2c_init(void){ ... bus_register(&i2c_bus_type); ...}// 初始化总线中的一个名为中的阻塞通知链 ** driver/base/bus.cint 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函数所得)
- 测试Linux 内核中的I2c-dev.c
- 浅谈linux内核中的I2c驱动(1)
- 浅谈linux内核中的I2c驱动(2)
- linux内核的I2C子系统详解4——i2c-s3c2410.c文件中的adapter、algorithm
- 使用Beaglebone Black的I2C (二)——使用C语言和i2c-dev驱动
- I2C-dev.c驱动代码分析
- 使用C语言和i2c-dev驱动
- uclinux内核中的I2C驱动
- linux内核的I2C子系统详解1——I2C总线概览、驱动框架概览
- Linux内核I2C子系统驱动
- Linux内核I2C子系统驱动
- i2c驱动中的传输函数
- linux的i2c驱动中的函数和数据结构的分析
- linux内核的I2C子系统详解3——i2c-core.c初步分析、I2C总线的匹配机制
- Linux内核通知链——notifier_call_chain
- notifier chain — linux内核通知链
- uclinux内核中的I2C驱动学习
- java.lang.IllegalStateException: The specified child already has a parent错误解决
- 树莓派docker
- CSS基础知识
- 51nod 1402 最大值问题
- pointer-like classes智能指针笔记----C++学习之路
- linux驱动——内核通知链(探究i2c-dev.c 中的bus_register_notifier函数所得)
- Annotation注释
- 数据一致性分类
- 20171103
- Eclipse 安装 SVN 插件的两种方法
- 一文看懂国内人工智能行业产业链全景(必收藏)
- git常用命令
- 一位资深程序员大牛给予Java初学者的学习路线建议
- java SE 乱记(一)