linux内核原理面试必问(由易到难)
简单型
1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些?
2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化,高端内存概念?
3:linux中中断的实现机制,tasklet与workqueue的区别及底层实现区别?为什么要区分上半部和下半部?
4:linux中断的响应执行流程?中断的申请及何时执行(何时执行中断处理函数)?
5:linux中的同步机制?spinlock与信号量的区别?
6:linux中RCU原理?
7: linux中软中断的实现原理?(2014.03.11)
8:linux系统实现原子操作有哪些方法? (2014.03.22)
9:MIPS Cpu中空间地址是怎么划分的?如在uboot中如何操作设备的特定的寄存器? (2014.03.22)
复杂型:
1:linux中netfilter的实现机制?是如何实现对特定数据包进行处理(如过滤,NAT之类的)及HOOK点的注册?
2:linux中系统调用过程?如:应用程序中read()在linux中执行过程即从用户空间到内核空间?
3:linux内核的启动过程(源代码级)?
4:linux调度原理?
5:linux网络子系统的认识?
三: 笔试
1:二分法查找
2:大小端转化及判断
3: 二维数组最外边个元素之和?
4:特定比特位置0和1
5:字符串中的第一个和最后一个元素交换(字符串反转)?
1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些?
答:
-在32位架构cpu中,物理内存大小限制在4G。linux将4G内存分为两部分,0~1G为kernel使用,1~4G为用户使用;进程运行在kernel,就是运行在0-1G,进程运行在用户空间,就是运行在1-4G。
-用户空间和内核空间通信方式有那些?
1. 使用API:这是最常使用的一种方式了
A.get_user(x,ptr):在内核中被调用,获取用户空间指定地址的数值并保存到内核变量x中。
B.put_user(x,ptr):在内核中被调用,将内核空间的变量x的数值保存到到用户空间指定地址处。
C.Copy_from_user()/copy_to_user():主要应用于设备驱动读写函数中,通过系统调用触发。
2. 使用proc文件系统:和sysfs文件系统类似,也可以作为内核空间和用户空间交互的手段。
3. netlink
4. 使用mmap系统调用
5. 信号
内核空间和用户空间通信方式
2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化,高端内存概念?
1. 用户虚拟地址
这是在用户空间进程所能看到的常规地址。每个进程多有自己的虚拟地址,并且可以使用大于物理内存大小的空间。
2. 物理地址
该地址在处理器和系统内存之间使用,对应与真是物理地址。
3. 总线地址
没看懂,不说了。
4. 内核逻辑地址
内核逻辑地址组成了内核的常规地址空间。该地址映射了部分(或者全部)内存,并经常被视为物理地址。
逻辑地址使用硬件内建的指针大小,因此在安装了大量内存的32位系统中,它无法寻址全部的物理内存。
逻辑地址通常保存在unsigned long或者void *这样类型的变量中。kmalloc返回的内存就是内核逻辑地址。
(上面这段话很重要,一定要理解,建议自己使用记号笔标红)
5. 内核虚拟地址
内核虚拟地址与物理地址的映射不必是一对一的,而这是虚拟地址的特点。
所有逻辑地址都是内核虚拟地址,但是许多内核虚拟地址不是逻辑地址。vmalloc分配的内存就是一个虚拟地址。
可以参考下面的地址:
总结:高端内存的作用就是用于建立临时地址映射,用于kernel申请user空间内存
3: linux中中断的实现机制,tasklet与workqueue的区别及底层实现区别?为什么要区分上半部和下半部?
答:
tasklet和workqueue区别?
tasklet运行于中断上下文,不允许阻塞 、休眠,而workqueue运行与进程上下文,可以休眠和阻塞。
为什么要区分上半部和下半部?
中断服务程序异步执行,可能会中断其他的重要代码,包括其他中断服务程序。因此,为了避免被中断的代码延迟太长的时间,中断服务程序需要尽快运行,而且执行的时间越短越好,所以中断程序只作必须的工作,其他工作推迟到以后处理。所以Linux把中断处理切为两个部分:上半部和下半部。上半部就是中断处理程序,它需要完成的工作越少越好,执行得越快越好,一旦接收到一个中断,它就立即开始执行。像对时间敏感、与硬件相关、要求保证不被其他中断打断的任务往往放在中断处理程序中执行;而剩下的与中断有相关性但是可以延后的任务,如对数据的操作处理,则推迟一点由下半部完成。下半部分延后执行且执行期间可以相应所有中断,这样可使系统处于中断屏蔽状态的时间尽可能的短,提高了系统的响应能力。实现了程序运行快同时完成的工作量多的目标。
4:linux中断的响应执行流程?中断的申请及何时执行(何时执行中断处理函数)?
中断的响应流程:cpu接受终端->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。
中断的申请request_irq的正确位置:应该是在第一次打开 、硬件被告知终端之前。
5:linux中的同步机制?spinlock与信号量的区别?
linux中的同步机制:自旋锁/信号量/读取所/循环缓冲区
spinlock在得不到锁的时候,程序会循环访问锁,性能下降
信号量在得不到锁的时候会休眠,等到可以获得锁的时候,继续执行。
1、255.255.254.0网段最多能支持多少主机?(大概有5个备选项)
2、10M网卡传输过程中物理层采用什么编码?(SNAP?)(大概有4个备选项)
3、栈与队列的特点?(备选大概只有两个,A为FIFO,B为LIFO)
4、Cache的工作方式划分?(大概也有4个答案,大概是:write-none,write-all,write-through,write-back)。
5、什么叫NMI中断?(四个备选项)
6、RISC主要性能及特性?(大概有6个备选项)
7、在嵌入式系统中,所谓的北桥指的是什么?
(2)简答题:
1、说说轮巡任务调度与抢占式任务调度的区别?(大概为8分吧,记不清了)
2、什么叫存储器高速缓存技术,其主要目的?(大概6分)
3、画出计算机组成的最小逻辑框图。(哼,这道题竟然10分)
4、谈谈Volatile与Register修饰符的作用?
1、linux驱动分类
a. 字符设备
b. 块设备
c.网络设备
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。
块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。
字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如对于Flash设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。
网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket 机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系 统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。
2、信号量与自旋锁
自旋锁
自旋锁是专为防止多处理器并发而引入的一种锁,它应用于中断处理等部分。对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。
自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
事实上,自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。但是自旋锁节省了上下文切换的开销。
自旋锁的基本形式如下:
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);
因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。这点很好地满足了对称多处理机器需要的锁定服务。在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。
简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。递归使用一个自旋锁就会出现这种情况。
信号量
信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得*去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
信号量基本使用形式为:
static DECLARE_MUTEX(mr_sem);//声明互斥信号量
if(down_interruptible(&mr_sem))
//可被中断的睡眠,当信号来到,睡眠的任务被唤醒
//临界区
up(&mr_sem);
信号量和自旋锁区别
从严格意义上讲,信号量和自旋锁属于不同层次的互斥手段,前者的实现有赖于后者。
注意以下原则:
如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加简单一些。如果需要在自旋锁和信号量中作选择,应该取决于锁被持有的时间长短。理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选择。另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。
自旋锁对信号量
需求 建议的加锁方法
低开销加锁 优先使用自旋锁
短期锁定 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文中加锁 使用自旋锁
持有锁是需要睡眠、调度 使用信号量
3、platform总线设备及总线设备如何编写
Linux设备模型(总线、设备、驱动程序和类)【转】
文章的例子和实验使用《LDD3》所配的lddbus模块(稍作修改)。
提示:在学习这部分内容是一定要分析所有介绍的源代码,知道他们与上一部分内容(kobject、kset、attribute等等)的关系,最好要分析一个实际的“flatform device”设备,不然会只学到表象,到后面会不知所云的。
总线
总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是内部的虚拟"platform"总线。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
在 Linux 设备模型中, 总线由 bus_type 结构表示, 定义在 <linux/device.h> :
struct kset subsys;/*与该总线相关的子系统*/ struct klist klist_devices;/*与该总线相关的驱动程序链表*/ struct blocking_notifier_head bus_notifier; struct bus_attribute * bus_attrs; /*总线属性*/ int (*match)(struct device * dev, struct device_driver * drv); int (*suspend)(struct device * dev, pm_message_t state); |
在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:
/*in Linux 2.6.26.5*/
int (*match)(struct device *dev, struct device_driver *drv); int (*suspend)(struct device *dev, pm_message_t state); struct bus_type_private *p; struct bus_type_private { |
总线的注册和删除
总线的主要注册步骤:
(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:
|
(2)调用bus_register函数注册总线。
|
|
|
总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); |
lddbus的match和uevent方法:
static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) |
对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:
/*这两个函数迭代总线上的每个设备或驱动程序, |
总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 <linux/device.h> 如下:
|
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:
/*总线的属性必须显式调用 bus_create_file 来创建:*/ /*删除总线的属性调用:*/ |
例如创建一个包含源码版本号简单属性文件方法如下:
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); /*在模块加载时创建属性文件:*/ /*这个调用创建一个包含 lddbus 代码的版本号的属性文件(/sys/bus/ldd/version)*/ |
设备
在最底层, Linux 系统中的每个设备由一个 struct device 代表:
struct kobject kobj;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/ struct semaphore sem; /* semaphore to synchronize calls to its driver. */ #ifdef CONFIG_NUMA struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */ spinlock_t devres_lock; /* class_device migration path */ void (*release)(struct device * dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/ |
设备注册
|
一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:
struct device ldd_bus = { }; /*这是顶层总线,parent 和 bus 成员为 NULL*/ /*作为第一个(并且唯一)总线, 它的名字为 ldd0,这个总线设备的注册代码如下:*/ |
设备属性
sysfs 中的设备入口可有属性,相关的结构是:
/*设备属性结构可在编译时建立, 使用以下宏:*/ /*属性文件的实际处理使用以下函数:*/ |
设备结构的嵌入
device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。
lddbus 驱动创建了它自己的 device 类型,并期望每个设备驱动使用这个类型来注册它们的设备:
|
lddbus 导出的注册和注销接口如下:
/* int register_ldd_device(struct ldd_device *ldddev) void unregister_ldd_device(struct ldd_device *ldddev) |
sculld 驱动添加一个自己的属性到它的设备入口,称为 dev, 仅包含关联的设备号,源码如下:
static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL); /*接着, 在初始化时间, 设备被注册, 并且 dev 属性通过下面的函数被创建:*/ |
设备驱动程序
设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:
struct kobject kobj;/*内嵌的kobject对象*/ struct module * owner; int (*probe) (struct device * dev);/*查询一个特定设备是否存在及驱动是否可以使用它的函数*/ /*注册device_driver 结构的函数是:*/ /*driver的属性结构在:*/ /*属性文件创建的方法:*/ /*bus_type 结构含有一个成员( drv_attrs ) 指向一组为属于该总线的所有设备创建的默认属性*/ |
在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:
/*in Linux 2.6.26.5*/
struct module *owner; int (*probe) (struct device *dev); struct driver_private *p; struct driver_private { |
驱动程序结构的嵌入
对大多数驱动程序核心结构, device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。
以lddbus 子系统为例,它定义了ldd_driver 结构:
|
lddbus总线中相关的驱动注册和注销函数是:
int register_ldd_driver(struct ldd_driver *driver) void unregister_ldd_driver(struct ldd_driver *driver) |
在sculld 中创建的 ldd_driver 结构如下:
|
类 子系统
类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在 /sys/class
目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况,
类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时,
它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。 类函数和结构与设备模型的其他部分遵循相同的模式,所以真正崭新的概念是很少的。
注意:class_simple 是老接口,在2.6.13中已被删除,这里不再研究。
管理类的接口
类由 struct class 的结构体来定义:
struct kset subsys; struct class_attribute * class_attrs;/* 指向类属性的指针(以NULL结尾) */ int (*uevent)(struct class_device *dev, char **envp, void (*release)(struct class_device *dev);/* 把设备从类中删除的函数 */ int (*suspend)(struct device *, pm_message_t state); /*类注册函数:*/ /*类属性的接口:*/ |
/* struct kset subsys; int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); void (*class_release)(struct class *class); int (*suspend)(struct device *dev, pm_message_t state); |
类设备(在新内核中已被删除)
类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:
void (*release)(struct class_device *dev); /*类设备注册函数:*/ /*重命名一个已经注册的类设备入口:*/ /*类设备入口属性:*/ CLASS_DEVICE_ATTR(_name, _mode, _show, _store); /*创建和删除除struct class中设备默认属性外的属性*/ |
类接口
类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”, 可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
int (*add) (struct class_device *, struct class_interface *);
/*注册或注销接口的函数:*/ |
1 平台设备和驱动初识
platform是一个虚拟的地址总线,相比pci,usb,它主要用于描述SOC上的片上资源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的资源有一个共同点,就是在cpu的总线上直接取址。
平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源.
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
平台驱动遵循标准驱动模型的规范, 也就是说发现/列举(discovery/enumeration)在驱动之外处理, 而
由驱动提供probe()和remove方法. 平台驱动按标准规范对电源管理和关机通告提供支持
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
probe()总应该核实指定的设备硬件确实存在;平台设置代码有时不能确定这一点. 枚举(probing)可以使用的设备资源包括时钟及设备的platform_data.(译注: platform_data定义在device.txt中的"基本设备结构体"中.)
平台驱动通过普通的方法注册自身
int platform_driver_register(struct platform_driver *drv);
或者, 更常见的情况是已知设备不可热插拔, probe()过程便可以驻留在一个初始化区域(init section)
中,以便减少驱动的运行时内存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));
设备列举
按规定, 应由针对平台(也适用于针对板)的设置代码来注册平台设备
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)
一般的规则是只注册那些实际存在的设备, 但也有例外. 例如, 某外部网卡未必会装配在所有的板子上,
或者某集成控制器所在的板上可能没挂任何外设, 而内核却需要被配置来支持这些网卡和控制器
有些情况下, 启动固件(boot firmware)会导出一张装配到板上的设备的描述表. 如果没有这张表, 通常
就只能通过编译针对目标板的内核来让系统设置代码安装正确的设备了. 这种针对板的内核在嵌入式和自定
义的系统开发中是比较常见的.
多数情况下, 分给平台设备的内存和中断请求号资源是不足以让设备正常工作的. 板设置代码通常会用设备
的platform_data域来存放附加信息, 并向外提供它们.
嵌入式系统时常需要为平台设备提供一个或多个时钟信号. 除非被用到, 这些时钟一般处于静息状态以节电.
系统设置代码也负责为设备提供这些时钟, 以便设备能在它们需要是调用
clk_get(&pdev->dev, clock_name).
也可以用如下函数来一次性完成分配空间和注册设备的任务
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)
设备命名和驱动绑定
platform_device.dev.bus_id是设备的真名. 它由两部分组成:
*platform_device.name ... 这也被用来匹配驱动
*platform_device.id ... 设备实例号, 或者用"-1"表示只有一个设备.
连接这两项, 像"serial"/0就表示bus_id为"serial.0", "serial"/3表示bus_id为"serial.3";
上面二例都将使用名叫"serial"的平台驱动. 而"my_rtc"/-1的bus_id为"my_rtc"(无实例号), 它的
平台驱动为"my_rtc".
2 平台总线
下面我们看看与platform相关的操作
平台总线的初始化
int __init platform_bus_init(void)
{
int error;
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
这段初始化代码创建了一个platform设备,以后属于platform类型的设备就会以此为parent,增加的设备会出现在/sys/devices/platform目录下
[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth floppy.0 i8042 pcspkr power serial8250 uevent vesafb.0
紧接着注册名为platform的平台总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
int platform_device_add(struct platform_device *pdev)
{......
pdev->dev.parent = &platform_bus; //增加的platform设备以后都以platform_bus(platform设备)为父节点
pdev->dev.bus = &platform_bus_type; //platform类型设备都挂接在platform总线上 /sys/bus/platform/
......
}
3 platform device的注册
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
1)动态分配一个名为name的platform设备
struct platform_object {
struct platform_device pdev;
char name[1];
};
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa;
pa = kzalloc(sizeof(struct platform_object) + strlen(name),
GFP_KERNEL);//由于 platform_object内name只有一个字节,所以需要多分配strlen(name)长度
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
pa->pdev.id = id;
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release;
}
return pa ? &pa->pdev : NULL;
}
实际上就是分配一个platform_object 结构体(包含了一个platform device结构体)并初始化内部成员platform driver,然后返回platform driver结构体以完成动态分配一个platform设备
然后调用platform_add_devices()以追加一个platform 设备到platform bus上
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; //初始化设备的父节点所属类型为platform device(platform_bus)
pdev->dev.bus = &platform_bus_type; //初始化设备的总线为platform bus
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource;
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) { //插入资源到资源树上
printk(KERN_ERR
"%s: failed to claim resource %d\n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
pdev->dev.bus_id, pdev->dev.parent->bus_id);
ret = device_add(&pdev->dev); //注册特定的设备到platform bus上
if (ret == 0)
return ret;
failed:
while (--i >= 0)
if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
release_resource(&pdev->resource[i]);
return ret;
}
上面的操作我们看到另外一个陌生的结构 设备资源(struct resource)
关于资源的操作(从上面已经了解,平台设备会分到一系列诸如地址和中断请求号(IRQ)之类的资源.
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;// IORESOURCE_IO IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
struct resource *parent, *sibling, *child;
};
基于资源的分类(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分为2种类型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(内存映射)
这里说一下关于I/O端口:
CPU对外设IO端口物理地址的编址方式有2种:一种是IO映射方式(IO-mapped),
另一种是内存映射方式(Memory-mapped)。具体采用哪一种方式则取决于CPU的体系结构。
像X86体系对外设就专门实现了一个单独地址空间,并且有专门的I/O指令来访问I/O端口,像ARM体系结构通常只是实现一个物理地址空间,I/O端口就被映射到CPU的单一物理地址空间中,而成为内存的一部分,所以一般资源都采用(IORESOURCE_MEM)。
linux中对设备的资源按照资源树的结构来组织(其实就是一个链表结构的插入、删除、查找等操作),上面再添加设备(platform_device_add)的同时对相应的资源在资源树上进行插入操作int
insert_resource(struct resource *parent, struct resource *new)
关于platform resource有相关的函数进行对资源的操作。
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);
例如s3c24210的watchdog资源分配实例:
watchdog寄存器的基地址为0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //内存映射
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ, //IRQ
}
};
动态注册platform device例:
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
int ret, i;
if (nr_uarts > UART_NR)
nr_uarts = UART_NR;
printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
"%d ports, IRQ sharing %sabled\n", nr_uarts,
share_irqs ? "en" : "dis");
for (i = 0; i < NR_IRQS; i++)
spin_lock_init(&irq_lists[i].lock);
ret = uart_register_driver(&serial8250_reg);
if (ret)
goto out;
serial8250_isa_devs = platform_device_alloc("serial8250",
PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_uart_drv;
}
ret = platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
ret = platform_driver_register(&serial8250_isa_driver);
if (ret == 0)
goto out;
platform_device_del(serial8250_isa_devs);
put_dev:
platform_device_put(serial8250_isa_devs);
unreg_uart_drv:
uart_unregister_driver(&serial8250_reg);
out:
return ret;
}
也可以在编译的时候就确定设备的相关信息,调用 int platform_device_register(struct platform_device *);
/linux/arch/arm/mach-smdk2410/mach-smdk2410.c
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};
static void __init smdk2410_init(void)
{
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //静态增加一组soc设备,以便在加载驱动的时候匹配相关驱动
smdk_machine_init();
}
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]); //实际上是调用 platform_device_register追加platform device
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
int platform_device_register(struct platform_device * pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
从上面看出这和动态增加一个platform device所做的动作基本上是一样的(device_initialize,platform_device_add)
例 watchdog设备定义:
struct platform_device s3c_device_wdt = {
.name = "s3c2410-wdt",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
.resource = s3c_wdt_resource,
};
4 platform driver的注册
先看结构体,里面内嵌了一个
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
指定platform device所属总线,同时如果为platform_driver中各项指定了接口,则为struct device_driver中相应的接口赋值。
那么是如何赋值的呢?
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
从上面可以看出,是将struct device转换为struct platform_device和struct platform_driver.然后调用platform_driver中的相应接口函数来实现,
最后调用 driver_register()将platform driver注册到总线上。
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
......
serial8250_isa_devs = platform_device_alloc("serial8250",
PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_uart_drv;
}
ret = platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
ret = platform_driver_register(&serial8250_isa_driver);
if (ret == 0)
goto out;
......
}
在设备成功进行了注册后,调用platform_driver_register()进行驱动注册。
最后,总线上注册有设备和相应的驱动,就会进行设备和驱动的匹配。
在找到一个设备和驱动的配对后, 驱动绑定是通过调用probe()由驱动核心自动完成的. 如果probe()成功,
驱动和设备就正常绑定了. 有三种不同的方法来进行配对:
-设备一被注册, 就检查对应总线下的各驱动, 看是否匹配. 平台设备应在系统启动过程的早期被注册
-当驱动通过platform_driver_register()被注册时, 就检查对应总线上所有未绑定的设备.驱动通常在启动过程的后期被注册或通过装载模块来注册.
-用platform_driver_probe()来注册驱动的效果跟用platform_driver_register()几乎相同, 不同点仅在于,如果再有设备注册, 驱动就不会再被枚举了. (这无关紧要, 因为这种接口只用在不可热插拔的设备上.)
驱动和设备的匹配仅仅是通过名称来匹配的
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
小结:本节总结平台设备和驱动的模型,这部份知识可以作为我们深入了解具体平台设备驱动的基础。
BUS
在设备模型中,所有的device都是通过总线bus 连接,这里的bus包括通常意义的总线如usb,pci,也包括虚拟的platform总线。
[root@wangp bus]# pwd
/sys/bus
[root@wangp bus]# ls
ac97 acpi bluetooth gameport i2c ide pci pci_express pcmcia platform pnp scsi serio usb
[root@wangp platform]# pwd
/sys/bus/platform
[root@wangp platform]# ls
devices drivers
struct bus_type {
const char * name;
struct module * owner;
struct kset subsys;
struct kset drivers;
struct kset devices;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs;
struct device_attribute * dev_attrs;
struct driver_attribute * drv_attrs;
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device * dev);
int (*remove)(struct device * dev);
void (*shutdown)(struct device * dev);
int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);
unsigned int drivers_autoprobe:1;
};
name是总线的名字,每个总线下都有自己的子系统,其中包含2个kset,deviece和driver,分别代表已知总线的驱动和插入总线的设备
如platform总线的声明如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
只有很少的bus_type成员需要初始化,大部分交给kernel来处理
关于总线的操作常用的如下:
int bus_register(struct bus_type * bus);
void bus_unregister(struct bus_type * bus);
/* iterator helpers for buses */
列举总线上从start之后的每个设备,并进行fn操作,通常用途是对bus上的设备和驱动进行绑定
int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *));
int driver_attach(struct device_driver * drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
return 0;
}
几乎linux设备模型的每一层都提供了添加属性的函数,总线也不例外
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf); //显示属性
ssize_t (*store)(struct bus_type *, const char * buf, size_t count); //设置属性
};
创建属于一个总线的属性使用(在模块的加载时间完成)
int bus_create_file(struct bus_type *,struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);
在说明下bus在sysfs里面的结构,刚才已经讲过,bus_type中有2个kset结构对应于device和driver,也就是说每个bus下面都会有device何driver2个文件夹。
首先在总线上注册的驱动会得到一个文件夹driver,如platform驱动
[root@wangp platform]# pwd
/sys/bus/platform
[root@wangp platform]# ls
devices drivers
[root@wangp drivers]# pwd
/sys/bus/platform/drivers
[root@wangp drivers]# ls
i8042 pcspkr serial8250 vesafb
而任何在总线/sys/bus/xxx/上发现的设备会得到一个symlink(符号链接)即/sys/bus/xxx/device指向/sys/device/xxx下面的文件夹
[root@wangp devices]# pwd
/sys/bus/platform/devices
[root@wangp devices]# ls -l
total 0
lrwxrwxrwx 1 root root 0 Jun 6 10:37 bluetooth -> ../../../devices/platform/bluetooth
lrwxrwxrwx 1 root root 0 Jun 6 10:37 floppy.0 -> ../../../devices/platform/floppy.0
lrwxrwxrwx 1 root root 0 Jun 6 10:37 i8042 -> ../../../devices/platform/i8042
lrwxrwxrwx 1 root root 0 Jun 6 10:37 pcspkr -> ../../../devices/platform/pcspkr
lrwxrwxrwx 1 root root 0 Jun 6 10:37 serial8250 -> ../../../devices/platform/serial8250
lrwxrwxrwx 1 root root 0 Jun 6 10:37 vesafb.0 -> ../../../devices/platform/vesafb.0
DEVICE
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent; //该设备所属的设备
struct kobject kobj;
char bus_id[BUS_ID_SIZE]; /* position on parent bus */
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type * bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class;
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);
};
这个结构相当于一个基类,对于基于特定总线的设备,会派生出特定的device结构(linux的驱动模型有很多结构都可以基于类来看待)
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
一个总线设备用如下函数注册(注册总线类型)
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
以完成parent name bus_id bus几个成员的初始化,注册后可以在/sys/devices下面看到
void device_unregister(struct device *dev);
同时和其相关的属性为
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};
int device_create_file(struct device *device,struct device_attribute * entry);
device_remove_file(struct device * dev, struct device_attribute * attr);
以下完成一个总线设备的注册:
static void simple_bus_release(struct device *dev)
{
printk("simple bus release\n");
}
struct device simple_bus = {
.bus_id ="simple_bus",
.release = simple_bus_release
}
ret = device_register(&simple_bus);
if(ret)
pritnk("unable to register simple_bus\n");
完成注册后,simple_bus就可以再sysfs中/sys/devices下面看见,任何挂载这个bus上的device都会在/sys/devices/simple_bus下看到
DEVICE_DRIVER
struct device_driver {
const char * name;//在sysfs下显示
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
由于大多数驱动都会带有特有的针对某种特定总线的信息,因此一般都是基于device_driver来派生出自己
特有的驱动,如
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
比较xxx_driver 和device_driver我们可以发现,结构体所带的方法基本相同,在具体应该用的时候是可以转换的。
驱动的注册
int driver_register(struct device_driver * drv);
void driver_unregister(struct device_driver * drv);
而大多数驱动会调用针对特定总线的诸如platform_driver_register,pci_driver_register之类的函数去注册
总线(bus)可以挂接一类设备(device)
驱动(driver)可以驱动一类设备(device)
因此和bus一样,device_driver也有一个函数为某个驱动来遍历所有设备
int driver_for_each_dev(struct device_driver *drv, void *data,int (*callback)(struct device *dev,void *data);
所有device_driver完成注册后,会在/sys/bus/xxx/driver目录下看到驱动信息
同时相应的属性内容
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *, char * buf);
ssize_t (*store)(struct device_driver *, const char * buf, size_t count);
};
int driver_create_file(struct device_driver *,struct driver_attribute *);
void driver_remove_file(struct device_driver *, struct driver_attribute *);
说了这么多,现在来理一理kobject kset,subsys,sysfs,bus之间的关系
< XMLNAMESPACE PREFIX ="V" /><!--[if !vml]-->
<!--[endif]-->
上图反映了继承体系的一个基本结构,kset是一组相同的kobject的集合,kernel可以通过跟踪kset来跟踪所用的特定类型设备,platform、pci、i2c等,kset起到连接作用将设备模型和sysfs联系在一起。每个kset自身都包含一个kobject,这个kobject将作为很多其他的kobject的父类,从sys上看,某个kobject的父类是某个目录,那么它就是那个目录的子目录,parent指针可以代表目录层次,这样典型的设备模型层次就建立起来了,从面向对象的观点看,kset是顶层的容器类,kset继承他自己的kobject,并且可以当做kobject来处理
如图:kset把它的子类kobject放在链表里面,kset子类链表里面那些kobject的kset指针指向上面的kset,parent指向父类。
struct kobject {
const char * k_name;
struct kref kref;
struct list_head entry;
struct kobject * parent;
struct kset * kset;
struct kobj_type * ktype;
struct sysfs_dirent * sd;
};
struct kset {
struct kobj_type *ktype;
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
4、kmalloc和vmalloc的区别
kmalloc()
用于申请较小的、连续的物理内存
1. 以字节为单位进行分配,在<linux/slab.h>中
2. void *kmalloc(size_t size, int flags) 分配的内存物理地址上连续,虚拟地址上自然连续
3. gfp_mask标志:什么时候使用哪种标志?如下:
———————————————————————————————-
情形 相应标志
———————————————————————————————-
进程上下文,可以睡眠 GFP_KERNEL
进程上下文,不可以睡眠 GFP_ATOMIC
中断处理程序 GFP_ATOMIC
软中断 GFP_ATOMIC
Tasklet GFP_ATOMIC
用于DMA的内存,可以睡眠 GFP_DMA | GFP_KERNEL
用于DMA的内存,不可以睡眠 GFP_DMA | GFP_ATOMIC
———————————————————————————————-
4. void kfree(const void *ptr)
释放由kmalloc()分配出来的内存块
vmalloc()
用于申请较大的内存空间,虚拟内存是连续的
1. 以字节为单位进行分配,在<linux/vmalloc.h>中
2. void *vmalloc(unsigned long size) 分配的内存虚拟地址上连续,物理地址不连续
3.
一般情况下,只有硬件设备才需要物理地址连续的内存,因为硬件设备往往存在于MMU之外,根本不了解虚拟地址;但为了性能上的考虑,内核中一般使用
kmalloc(),而只有在需要获得大块内存时才使用vmalloc(),例如当模块被动态加载到内核当中时,就把模块装载到由vmalloc()分配
的内存上。
4.void vfree(void *addr),这个函数可以睡眠,因此不能从中断上下文调用。
malloc(), vmalloc()和kmalloc()区别
[*]kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存
[*]kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续,malloc不保证任何东西(这点是自己猜测的,不一定正确)
[*]kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大
[*]内存只有在要被DMA访问的时候才需要物理上连续
[*]vmalloc比kmalloc要慢
5、module_init的级别
在Linux底下写过driver模块的对这个宏一定不会陌生。module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj-y)。
1.在MODULE没有定义这种情况下,module_init定义如下:
#define module_init(x) __initcall(x);
因为
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
所以,module_init(x)最终展开为:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:
static initcall_t __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。
这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init)
*(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init)
*(.initcall2.init) *(.initcall2s.init) *(.initcall3.init)
*(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init)
*(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init)
*(.initcall6.init) *(.initcall6s.init) *(.initcall7.init)
*(.initcall7s.init)
__initcall_end = .;
}
以及do_initcalls:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
那么就不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。
在MODULE被定义的情况下(大部分可动态加载的driver模块都属于此, obj-m),module_init定义如下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void
gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用sys_init_module()去找到init_module函数的入口地址。
如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调用。
6、添加驱动
静态加载和动态加载:
静态加载是系统启动的时候由内核自动加载的,这个要事先将驱动编译进内核才行;
动态加载,也就是模块加载方式,这种方式下驱动以模块的形式存放在文件系统中,需要时动态载入内核,这种主要用在调试的时候,比较方便灵活。insmod module.ko
7、IIC原理,总线框架,设备编写方法,i2c_msg
我们要知道2440与at24c02之间属于主从连接,也就是说所有的会话必须由主设备发起,这里的主设备自然是2440。那么当2440的iic总线上挂载了多个存储设备时,2440如何去找到自己想要访问的存储设备呢?这就需要设备地址,那么当访问到具体的设备后,想要读写特定的存储空间,还需要知道存储地址!那么这些地址是如何组织的呢?我们可以看看下面这幅图:
首先发送设备地址并附带写信号,这样就找到了设备并告诉设备下一次是2440向设备发送
Linux设备驱动之I2C架构分析
一、前言
I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而
I2C 是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec阅读即可.
二、I2C架构概述
在linux中,I2C驱动架构如下所示:
如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.
这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于: linux-2.6.26.3/drivers/i2c/位置.
三、adapter注册
在 kernel中提供了两个adapter注册接口,分别为
int i2c_add_adapter(struct i2c_adapter *adapter);
和
int i2c_add_numbered_adapter(struct i2c_adapter *adapter);
由于在系统中可能存在多个adapter,因此将每一条I2C总线对应一个编号(下文中称为 I2C总线号)。这个总线号的PCI中的总线号不同,它和硬件无关,只是软件上便于区分而已。 (这句很重要)
对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分配一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.
分别来看一下这两个函数的代码:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, &id);
mutex_unlock(&core_lock);
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter);
}
在这里涉及到一个idr结构.idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存.避免在内存不够的时候出现问题.所在,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<<
linux文件系统之文件的读写>>中有关radix tree的分析.
注意一下 idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于 __i2c_first_dynamic_bus_num,然后将对应的id号存放在adapter->nr中.调用i2c_register_adapter(adapter)对这个adapter进行进一步注册.
看一下另外一人注册函数: i2c_add_numbered_adapter( ),如下所示:
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
int status;
if (adap->nr & ~MAX_ID_MASK)
return -EINVAL;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh;
* we need the "equal to" result to force the result
*/
status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
if (status == 0 && id != adap->nr) {
status = -EBUSY;
idr_remove(&i2c_adapter_idr, id);
}
mutex_unlock(&core_lock);
if (status == -EAGAIN)
goto retry;
if (status == 0)
status = i2c_register_adapter(adap);
return status;
}
对比一下就知道差别了,在这里它已经指定好了adapter->nr了.如果分配的id不和指定的相等,便返回错误.
过一步跟踪i2c_register_adapter().代码如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0, dummy;
mutex_init(&adap->bus_lock);
mutex_init(&adap->clist_lock);
INIT_LIST_HEAD(&adap->clients);
mutex_lock(&core_lock);
/* Add the adapter to the driver core.
* If the parent pointer is not set up,
* we add this adapter to the host bus.
*/
if (adap->dev.parent == NULL) {
adap->dev.parent = &platform_bus;
pr_debug("I2C adapter driver [%s] forgot to specify "
"physical device/n", adap->name);
}
sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
adap->dev.release = &i2c_adapter_dev_release;
adap->dev.class = &i2c_adapter_class;
res = device_register(&adap->dev);
if (res)
goto out_list;
dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);
/* create pre-declared device nodes for new-style drivers */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* let legacy drivers scan this bus for matching devices */
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);
out_unlock:
mutex_unlock(&core_lock);
return res;
out_list:
idr_remove(&i2c_adapter_idr, adap->nr);
goto out_unlock;
}
首先对adapter和adapter中内嵌的struct device结构进行必须的初始化.之后将adapter内嵌的struct device注册.
在这里注意一下adapter->dev的初始化.它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.
adapter->dev的名字为i2c + 总线号.
测试一下:
[eric@mochow i2c]$ cd /sys/class/i2c-adapter/
[eric@mochow i2c-adapter]$ ls
i2c-0
可以看到,在我的PC上,有一个I2C adapter,看下详细信息:
[eric@mochow i2c-adapter]$ tree
.
`-- i2c-0
|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0
|-- name
|-- subsystem -> ../../../class/i2c-adapter
`-- uevent
3 directories, 2 files
可以看到,该adapter是一个PCI设备.
继续往下看:
之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy drivers
New-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.
3.1、new-style 形式的adapter注册
对于第一种,也就是new-style drivers,将相关代码再次列出如下:
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().
结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:(adap->nr < __i2c_first_dynamic_bus_num)。
而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改 __i2c_first_dynamic_bus_num的值.在x86上没有使用i2c_register_board_info()。因此,x86平台上的分析可以忽略掉new-style driver的方式。不过,还是详细分析这种情况下.
首先看一下i2c_register_board_info(),如下:
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
int status;
mutex_lock(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!/n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
mutex_unlock(&__i2c_board_lock);
return status;
}
这个函数比较简单, struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.
跟踪i2c_scan_static_board_info():代码如下:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
mutex_lock(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
printk(KERN_ERR "i2c-core: can't create i2c%d-%04x/n",
i2c_adapter_id(adapter),
devinfo->board_info.addr);
}
mutex_unlock(&__i2c_board_lock);
}
该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c设备的信息.
如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap;
client->dev.platform_data = info->platform_data;
device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);
client->flags = info->flags & ~I2C_CLIENT_WAKE;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* a new style driver may be bound to this device when we
* return from this function, or any later moment (e.g. maybe
* hotplugging will load the driver module). and the device
* refcount model is the standard driver model one.
*/
status = i2c_attach_client(client);
if (status < 0) {
kfree(client);
client = NULL;
}
return client;
}
我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.
首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.特别的,clinet->name为info->name.也是指定好了的.
一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:
int i2c_attach_client(struct i2c_client *client)
{
struct i2c_adapter *adapter = client->adapter;
int res = 0;
//初始化client内嵌的dev结构
//父结点为所在的adapter,所在bus为i2c_bus_type
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
//如果client已经指定了driver,将driver和内嵌的dev关联起来
if (client->driver)
client->dev.driver = &client->driver->driver;
//指定了driver, 但不是newstyle的
if (client->driver && !is_newstyle_driver(client->driver)) {
client->dev.release = i2c_client_release;
client->dev.uevent_suppress = 1;
} else
client->dev.release = i2c_client_dev_release;
//clinet->dev的名称
snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
"%d-%04x", i2c_adapter_id(adapter), client->addr);
//将内嵌的dev注册
res = device_register(&client->dev);
if (res)
goto out_err;
//将clinet链到adapter->clients中
mutex_lock(&adapter->clist_lock);
list_add_tail(&client->list, &adapter->clients);
mutex_unlock(&adapter->clist_lock);
dev_dbg(&adapter->dev, "client [%s] registered with bus id %s/n",
client->name, client->dev.bus_id);
//如果adapter->cleinet_reqister存在,就调用它
if (adapter->client_register) {
if (adapter->client_register(client)) {
dev_dbg(&adapter->dev, "client_register "
"failed for client [%s] at 0x%02x/n",
client->name, client->addr);
}
}
return 0;
out_err:
dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
"(%d)/n", client->name, client->addr, res);
return res;
}
参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?
这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .以为只需要有bus为i2c_bus_type的driver注册,就会产生probe了.这个过程呆后面分析i2c driver的时候再来详细分析.
3.2、legacy形式的adapter注册
Legacy形式的adapter注册代码片段如下:
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);
这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().
代码如下:
static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
struct i2c_driver *driver = to_i2c_driver(d);
struct i2c_adapter *adap = data;
if (driver->attach_adapter) {
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);
}
return 0;
}
该函数很简单,就是调用driver的attach_adapter()接口.
到此为止,adapter的注册已经分析完了.
四、i2c driver注册
在分析i2c driver的时候,有必要先分析一下i2c架构的初始化
代码如下:
static int __init i2c_init(void)
{
int retval;
retval = bus_register(&i2c_bus_type);//注册总线
if (retval)
return retval;
retval = class_register(&i2c_adapter_class);//注册类
if (retval)
goto bus_err;
retval = i2c_add_driver(&dummy_driver);//添加驱动
if (retval)
goto class_err;
return 0;
class_err:
class_unregister(&i2c_adapter_class);
bus_err:
bus_unregister(&i2c_bus_type);
return retval;
}
subsys_initcall(i2c_init);
很明显,i2c_init()会在系统初始化的时候被调用.
在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.
I2c_bus_type结构如下:
static struct bus_type i2c_bus_type = {
.name = "i2c",
.dev_attrs = i2c_dev_attrs,
.match = i2c_device_match,
.uevent = i2c_device_uevent,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
这个结构先放在这里吧,以后还会用到里面的信息的.
从上面的初始化函数里也看到了,注册i2c driver的接口为i2c_add_driver().代码如下:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}
继续跟踪:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* new style driver methods can't mix with legacy ones */
//如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法
if (is_newstyle_driver(driver)) {
if (driver->attach_adapter || driver->detach_adapter
|| driver->detach_client) {
printk(KERN_WARNING
"i2c-core: driver [%s] is confused/n",
driver->driver.name);
return -EINVAL;
}
}
/* add the driver to the list of i2c drivers in the driver core */
//关联到i2c_bus_types
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* for new style drivers, when registration returns the driver core
* will have called probe() for all matching-but-unbound devices.
*/
//注册内嵌的driver
res = driver_register(&driver->driver);
if (res)
return res;
mutex_lock(&core_lock);
pr_debug("i2c-core: driver [%s] registered/n", driver->driver.name);
/* legacy drivers scan i2c busses directly */
//遍历所有的adapter,对其都调用driver->attach_adapter
if (driver->attach_adapter) {
struct i2c_adapter *adapter;
down(&i2c_adapter_class.sem);
list_for_each_entry(adapter, &i2c_adapter_class.devices,
dev.node) {
driver->attach_adapter(adapter);
}
up(&i2c_adapter_class.sem);
}
mutex_unlock(&core_lock);
return 0;
}
这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了。对于legacy的情况,对每一个adapter都调用driver->attach_adapter().
现在,我们可以将adapter和i2c driver关联起来考虑一下了:
1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client。struct client->dev->bus又指定了和i2c driver同一个bus.因为,它们可以发生probe.
2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().
五、i2c_bus_type的相关操作
I2c_bus_type的操作主要存在于new-style形式的驱动中.接下来分析一下对应的probe过程:
5.1:match过程分析
Match对应的操作函数为i2c_device_match().代码如下
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = to_i2c_client(dev);
struct i2c_driver *driver = to_i2c_driver(drv);
/* make legacy i2c drivers bypass driver model probing entirely;
* such drivers scan each i2c adapter/bus themselves.
*/
if (!is_newstyle_driver(driver))
return 0;
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
如果该驱动不是一个new-style形式的.或者driver没有定义匹配的id_table.都会匹配失败.
继续跟踪进i2c_match_id():
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
由此可见.如果client的名字和driver->id_table[]中的名称匹配即为成功.
5.2:probe过程分析
Probe对应的函数为: i2c_device_probe()
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct i2c_driver *driver = to_i2c_driver(dev->driver);
const struct i2c_device_id *id;
int status;
if (!driver->probe)
return -ENODEV;
client->driver = driver;
dev_dbg(dev, "probe/n");
if (driver->id_table)
id = i2c_match_id(driver->id_table, client);
else
id = NULL;
status = driver->probe(client, id);
if (status)
client->driver = NULL;
return status;
}
这个函数也很简单,就是将probe流程回溯到i2c driver的probe()
六、其它的扩展
分析完adapter和i2c driver的注册之后,好像整个架构也差不多了,其它,扩展的东西还有很多.
我们举一个legacy形式的例子,这个例子是在kernel中随便搜索出来的:
在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函数为:
static int __init ad7418_init(void)
{
return i2c_add_driver(&ad7418_driver);
}
i2c_driver ad7418_driver结构如下:
static struct i2c_driver ad7418_driver = {
.driver = {
.name = "ad7418",
},
.attach_adapter = ad7418_attach_adapter,
.detach_client = ad7418_detach_client,
};
该结构中没有probe()函数,可以断定是一个legacy形式的驱动.这类驱动注册的时候,会调用driver的attach_adapter函数.在这里也就是ad7418_attach_adapter.
这个函数代码如下:
static int ad7418_attach_adapter(struct i2c_adapter *adapter)
{
if (!(adapter->class & I2C_CLASS_HWMON))
return 0;
return i2c_probe(adapter, &addr_data, ad7418_detect);
}
在这里我们又遇到了一个i2c-core中的函数,i2c_probe().在分析这个函数之前,先来看下addr_data是什么?
#define I2C_CLIENT_MODULE_PARM(var,desc) /
static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; /
static unsigned int var##_num; /
module_param_array(var, short, &var##_num, 0); /
MODULE_PARM_DESC(var,desc)
#define I2C_CLIENT_MODULE_PARM_FORCE(name) /
I2C_CLIENT_MODULE_PARM(force_##name, /
"List of adapter,address pairs which are " /
"unquestionably assumed to contain a `" /
# name "' chip")
#define I2C_CLIENT_INSMOD_COMMON /
I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan " /
"additionally"); /
I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to " /
"scan"); /
static const struct i2c_client_address_data addr_data = { /
.normal_i2c = normal_i2c, /
.probe = probe, /
.ignore = ignore, /
.forces = forces, /
}
#define I2C_CLIENT_FORCE_TEXT /
"List of adapter,address pairs to boldly assume to be present"
由此可知道,addr_data中的三个成员都是模块参数.在加载模块的时候可以用参数的方式对其赋值.三个模块参数为别为probe,ignore,force.另外需要指出的是normal_i2c不能以模块参数的方式对其赋值,只能在驱动内部静态指定.
从模块参数的模述看来, probe是指"List of adapter,address pairs to scan additionally"
Ignore是指"List of adapter,address pairs not to scan "
Force是指"List of adapter,address pairs to boldly assume to be present"
事实上,它们里面的数据都是成对出现的.前面一部份表示所在的总线号,ANY_I2C_BUS表示任一总线.后一部份表示设备的地址.
addr_data是在 include/linux/i2c.h 中定义的或自己在自己驱动程序中定义的(注意,此处addr_data有两种定义方式)一个i2c_client_address_data结构:
若自己不定义,则用i2c.h中的默认定义。
/*
i2c_client_address_data is the struct for holding default
client addresses for a driver and for the parameters supplied on the
command line */
struct i2c_client_address_data {
unsigned short *normal_i2c;
unsigned short *probe;
unsigned short *ignore;
unsigned short **forces;
};
根据作者自行定义设备地址与否,有两种情形:
a. 采用默认定义,一般是不会work,毕竟大多数i2c-core中是不可能提前知道所接设备地址的,这样通过i2c_probe()探测肯定不可能找到,也不可能建立两者之间的联系;况且,i2c_probe()属于i2c-core中的函数,i2c-core中管理着所有注册过的设备和驱动列表,i2c_probe()中也不能随意传入地址,否则容易导致系统混乱或有潜在的风险,所以i2c-core也不允许这么做!
b. 作者自行定义地址结构
典型例子如下:
若自行定义,则参考如下:
/* Addresses to scan */
static unsigned short normal_i2c[] = {I2C_KS0127_ADDON>>1,
I2C_KS0127_ONBOARD>>1, I2C_CLIENT_END};/// 实际设备的地址List
static unsigned short probe[2] = {I2C_CLIENT_END, I2C_CLIENT_END};
static unsigned short ignore[2] = {I2C_CLIENT_END, I2C_CLIENT_END};
static struct i2c_client_address_data addr_data = {
normal_i2c,
probe,
ignore,
};
或者根本就不定义完整的i2c_client_address_data结构,只根据需要定义normal_i2c[],probe[],ignore[],forces[][],然后调用i2c_probe(adapter,&addr_data, &my_probe) 即可。
在my_probe()中把实际的地址赋于i2c_client,调用i2c_set_clientdata()设置i2c_client->dev->drv_data,并调用i2c_attach_client(client)向系统注册设备。
最后,i2c_probe()中探测时的地址优先级:forces[X][ X], probe[X ], normal_i2c[ X](其中忽略ignore[]中的项)。
I2c设备在实际使用中比较广泛,sensor,rtc,audio, codec,etc. 因设备复杂性不同,Linux中有些驱动中对地址的定义不在同一文件,这时多数情况都在arch中对设备作为platform_device进行初始化并注册的代码中。
现在可以来跟踪i2c_probe()的代码了.如下:
int
i2c_probe(struct i2c_adapter *adapter, const struct
i2c_client_address_data *address_data, int (*found_proc)
(struct i2c_adapter *, int, int))
{
int i, err;
int adap_id = i2c_adapter_id(adapter);
/* Force entries are done first, and are not affected by ignore
entries */
//先扫描force里面的信息,注意它是一个二级指针.ignore里的信息对它是无效的
if (address_data->forces) {
const unsigned short * const *forces = address_data->forces;//forces为常量指针,所指向的内容不能改变。
int kind;
for (kind = 0; forces[kind]; kind++) {
for (i = 0; forces[kind] != I2C_CLIENT_END;
i += 2) {
if (forces[kind] == adap_id
|| forces[kind] == ANY_I2C_BUS) {
dev_dbg(&adapter->dev, "found force "
"parameter for adapter %d, "
"addr 0x%02x, kind %d/n",
adap_id, forces[kind][i + 1],
kind);
err = i2c_probe_address(adapter,
forces[kind][i + 1],
kind, found_proc);
if (err)
return err;
}
}
}
}
/* Stop here if we can't use SMBUS_QUICK */
//如果adapter不支持quick.不能够遍历这个adapter上面的设备
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {
if (address_data->probe[0] == I2C_CLIENT_END
&& address_data->normal_i2c[0] == I2C_CLIENT_END)
return 0;
dev_warn(&adapter->dev, "SMBus Quick command not supported, "
"can't probe for chips/n");
return -1;
}
/* Probe entries are done second, and are not affected by ignore
entries either */
//遍历probe上面的信息.ignore上的信息也对它是没有影响的
for (i = 0; address_data->probe != I2C_CLIENT_END; i += 2) {
if (address_data->probe == adap_id
|| address_data->probe == ANY_I2C_BUS) {
dev_dbg(&adapter->dev, "found probe parameter for "
"adapter %d, addr 0x%02x/n", adap_id,
address_data->probe[i + 1]);
err = i2c_probe_address(adapter,
address_data->probe[i + 1],
-1, found_proc);
if (err)
return err;
}
}
/* Normal entries are done last, unless shadowed by an ignore entry */
//最后遍历normal_i2c上面的信息.它上面的信息不能在ignore中.
for (i = 0; address_data->normal_i2c != I2C_CLIENT_END; i += 1) {
int j, ignore;
ignore = 0;
for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;
j += 2) {
if ((address_data->ignore[j] == adap_id ||
address_data->ignore[j] == ANY_I2C_BUS)
&& address_data->ignore[j + 1]
== address_data->normal_i2c) {
dev_dbg(&adapter->dev, "found ignore "
"parameter for adapter %d, "
"addr 0x%02x/n", adap_id,
address_data->ignore[j + 1]);
ignore = 1;
break;
}
}
if (ignore)
continue;
dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
"addr 0x%02x/n", adap_id,
address_data->normal_i2c);
err = i2c_probe_address(adapter, address_data->normal_i2c,
-1, found_proc);
if (err)
return err;
}
return 0;
}
这段代码很简单,结合代码上面添加的注释应该很好理解.如果匹配成功,则会调用i2c_probe_address ().这个函数代码如下:
static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
int (*found_proc) (struct i2c_adapter *, int, int))
{
int err;
/* Make sure the address is valid */
//地址小于0x03或者大于0x77都是不合法的
if (addr < 0x03 || addr > 0x77) {
dev_warn(&adapter->dev, "Invalid probe address 0x%02x/n",
addr);
return -EINVAL;
}
/* Skip if already in use */
//adapter上已经有这个设备了
if (i2c_check_addr(adapter, addr))
return 0;
/* Make sure there is something at this address, unless forced */
//如果kind小于0.检查adapter上是否有这个设备
if (kind < 0) {
if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL) < 0)
return 0;
/* prevent 24RF08 corruption */
if ((addr & ~0x0f) == 0x50)
i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL);
}
/* Finally call the custom detection function */
//调用回调函数
err = found_proc(adapter, addr, kind);
/* -ENODEV can be returned if there is a chip at the given address
but it isn't supported by this chip driver. We catch it here as
this isn't an error. */
if (err == -ENODEV)
err = 0;
if (err)
dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)/n",
addr, err);
return err;
}
首先,对传入的参数进行一系列的合法性检查.另外,如果该adapter上已经有了这个地址的设备了.也会返回失败.所有adapter下面的设备都是以
adapter->dev为父结点的.因此只需要遍历adapter->dev下面的子设备就可以得到当前地址是不是被占用了.
如果kind < 0.还得要adapter检查该总线是否有这个地址的设备.方法是向这个地址发送一个Read的Quick请求.如果该地址有应答,则说明这个地址上有这个设备.另外还有一种情况是在24RF08设备的特例.
如果adapter上确实有这个设备,就会调用驱动调用时的回调函数.
在上面涉及到了IIC的传输方式,有疑问的可以参考intel ICH5手册的有关smbus部份.
跟踪i2c_smbus_xfer().代码如下:
s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data * data)
{
s32 res;
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
if (adapter->algo->smbus_xfer) {
mutex_lock(&adapter->bus_lock);
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
command,size,data);
mutex_unlock(&adapter->bus_lock);
} else
res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
command,size,data);
return res;
}
如果adapter有smbus_xfer()函数,则直接调用它发送,否则,也就是在adapter不支持smbus协议的情况下,调用i2c_smbus_xfer_emulated()继续处理.
跟进i2c_smbus_xfer_emulated().代码如下:
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data * data)
{
/* So we need to generate a series of msgs. In the case of writing, we
need to use only one message; when reading, we need two. We initialize
most things with sane defaults, to keep the code below somewhat
simpler. */
//写操作只会进行一次交互,而读操作,有时会有两次操作.
//因为有时候读操作要先写command,再从总线上读数据
//在这里为了代码的简洁.使用了两个缓存区,将两种情况统一起来.
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
//一般来说,读操作要交互两次.例外的情况我们在下面会接着分析
int num = read_write == I2C_SMBUS_READ?2:1;
//与设备交互的数据,一般在msg[0]存放写入设备的信息,在msb[1]里存放接收到的
//信息.不过也有例外的
//msg[2]的初始化,默认发送缓存区占一个字节,无接收缓存
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1 }
};
int i;
u8 partial_pec = 0;
//将要发送的信息copy到发送缓存区的第一字节
msgbuf0[0] = command;
switch(size) {
//quick类型的,其它并不传输有效数据,只是将地址写到总线上,等待应答即可
//所以将发送缓存区长度置为0 .再根据读/写操作,调整msg[0]的标志位
//这类传输只需要一次总线交互
case I2C_SMBUS_QUICK:
msg[0].len = 0;
/* Special case: The read/write field is used as data */
msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
num = 1;
break;
case I2C_SMBUS_BYTE:
//BYTE类型指一次写和读只有一个字节.这种情况下,读和写都只会交互一次
//这种类型的读有例外,它读取出来的数据不是放在msg[1]中的,而是存放在msg[0]
if (read_write == I2C_SMBUS_READ) {
/* Special case: only a read! */
msg[0].flags = I2C_M_RD | flags;
num = 1;
}
break;
case I2C_SMBUS_BYTE_DATA:
//Byte_Data是指命令+数据的传输形式.在这种情况下,写只需要一次交互,读却要两次
//第一次将command写到总线上,第二次要转换方向.要将设备地址和read标志写入总线.
//应回答之后再进行read操作
//写操作占两字节,分别是command+data.读操作的有效数据只有一个字节
//交互次数用初始化值就可以了
if (read_write == I2C_SMBUS_READ)
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
case I2C_SMBUS_WORD_DATA:
//Word_Data是指命令+双字节的形式.这种情况跟Byte_Data的情况类似
//两者相比只是交互的数据大小不同
if (read_write == I2C_SMBUS_READ)
msg[1].len = 2;
else {
msg[0].len=3;
msgbuf0[1] = data->word & 0xff;
msgbuf0[2] = data->word >> 8;
}
break;
case I2C_SMBUS_PROC_CALL:
//Proc_Call的方式与write 的Word_Data相似,只不过写完Word_Data之后,要等待它的应答
//应该它需要交互两次,一次写一次读
num = 2; /* Special case */
read_write = I2C_SMBUS_READ;
msg[0].len = 3;
msg[1].len = 2;
msgbuf0[1] = data->word & 0xff;
msgbuf0[2] = data->word >> 8;
break;
case I2C_SMBUS_BLOCK_DATA:
//Block_Data:指command+N段数据的情况.
//如果是读操作,它首先要写command到总线,然后再读N段数据.要写的command已经
//放在msg[0]了.现在只需要将msg[1]的标志置I2C_M_RECV_LEN位,msg[1]有效长度为1字节.因为
//adapter驱动会处理好的.现在现在还不知道要传多少段数据.
//对于写的情况:msg[1]照例不需要.将要写的数据全部都放到msb[0]中.相应的也要更新
//msg[0]中的缓存区长度
if (read_write == I2C_SMBUS_READ) {
msg[1].flags |= I2C_M_RECV_LEN;
msg[1].len = 1; /* block length will be added by
the underlying bus driver */
} else {
//data->block[0]表示后面有多少段数据.总长度要加2是因为command+count+N段数据
msg[0].len = data->block[0] + 2;
if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {
dev_err(&adapter->dev, "smbus_access called with "
"invalid block write size (%d)/n",
data->block[0]);
return -1;
}
for (i = 1; i < msg[0].len; i++)
msgbuf0 = data->block[i-1];
}
break;
case I2C_SMBUS_BLOCK_PROC_CALL:
//Proc_Call:表示写完Block_Data之后,要等它的应答消息它和Block_Data相比,只是多了一部份应答而已
num = 2; /* Another special case */
read_write = I2C_SMBUS_READ;
if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
dev_err(&adapter->dev, "%s called with invalid "
"block proc call size (%d)/n", __func__,
data->block[0]);
return -1;
}
msg[0].len = data->block[0] + 2;
for (i = 1; i < msg[0].len; i++)
msgbuf0 = data->block[i-1];
msg[1].flags |= I2C_M_RECV_LEN;
msg[1].len = 1; /* block length will be added by
the underlying bus driver */
break;
case I2C_SMBUS_I2C_BLOCK_DATA:
//I2c Block_Data与Block_Data相似,只不过read的时候,数据长度是预先定义好了的.另外
//与Block_Data相比,中间不需要传输Count字段.(Count表示数据段数目)
if (read_write == I2C_SMBUS_READ) {
msg[1].len = data->block[0];
} else {
msg[0].len = data->block[0] + 1;
if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {
dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with "
"invalid block write size (%d)/n",
data->block[0]);
return -1;
}
for (i = 1; i <= data->block[0]; i++)
msgbuf0 = data->block;
}
break;
default:
dev_err(&adapter->dev, "smbus_access called with invalid size (%d)/n",
size);
return -1;
}
//如果启用了PEC.Quick和I2c Block_Data是不支持PEC的
i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
&& size != I2C_SMBUS_I2C_BLOCK_DATA);
if (i) {
/* Compute PEC if first message is a write */
//如果第一个操作是写操作
if (!(msg[0].flags & I2C_M_RD)) {
//如果只是写操作
if (num == 1) /* Write only */
//如果只有写操作,写缓存区要扩充一个字节,用来存放计算出来的PEC
i2c_smbus_add_pec(&msg[0]);
else /* Write followed by read */
//如果后面还有读操作,先计算前面写部份的PEC(注意这种情况下不需要
//扩充写缓存区,因为不需要发送PEC.只会接收到PEC)
partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
}
/* Ask for PEC if last message is a read */
//如果最后一次是读消息.还要接收到来自slave的PEC.所以接收缓存区要扩充一个字节
if (msg[num-1].flags & I2C_M_RD)
msg[num-1].len++;
}
if (i2c_transfer(adapter, msg, num) < 0)
return -1;
/* Check PEC if last message is a read */
//操作完了之后,如果最后一个操作是PEC的读操作.检验后面的PEC是否正确
if (i && (msg[num-1].flags & I2C_M_RD)) {
if (i2c_smbus_check_pec(partial_pec, &msg[num-1]) < 0)
return -1;
}
//操作完了,现在可以将数据放到data部份返回了.
if (read_write == I2C_SMBUS_READ)
switch(size) {
case I2C_SMBUS_BYTE:
data->byte = msgbuf0[0];
break;
case I2C_SMBUS_BYTE_DATA:
data->byte = msgbuf1[0];
break;
case I2C_SMBUS_WORD_DATA:
case I2C_SMBUS_PROC_CALL:
data->word = msgbuf1[0] | (msgbuf1[1] << 8);
break;
case I2C_SMBUS_I2C_BLOCK_DATA:
for (i = 0; i < data->block[0]; i++)
data->block[i+1] = msgbuf1;
break;
case I2C_SMBUS_BLOCK_DATA:
case I2C_SMBUS_BLOCK_PROC_CALL:
for (i = 0; i < msgbuf1[0] + 1; i++)
data->block = msgbuf1;
break;
}
return 0;
}
在这个函数添上了很详细的注释,配和intel的datasheet,应该很容易看懂.在上面的交互过程中,调用了子函数i2c_transfer().它的代码如下所示:
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
int ret;
if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s/n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
if (in_atomic() || irqs_disabled()) {
ret = mutex_trylock(&adap->bus_lock);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
mutex_lock_nested(&adap->bus_lock, adap->level);
}
ret = adap->algo->master_xfer(adap,msgs,num);
mutex_unlock(&adap->bus_lock);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported/n");
return -ENOSYS;
}
}
因为在这里的同步用的是mutex.首先判断判断是否充许睡眠,如果不允许,尝试获锁.如果获锁失败,则返回,这样的操作是避免进入睡眠,我们在后面也可以看到,实际的传输工作交给了adap->algo->master_xfer()完成.
在这里,我们终于把i2c_probe_address()的执行分析完了,经过这个分析,我们也知道了数据是怎么样传输的.我们接着
i2c_probe()往下看.如果i2c_probe_address()成功.说明总线上确实有这样的设备.那么就会调用驱动中的回调函数.在
ad7148的驱动中,如下所示:
return i2c_probe(adapter, &addr_data, ad7418_detect);
也就是说,要调用的回调函数是ad7418_detect().这个函数中我们只分析和i2c框架相关的部份.代码片段如下所示:
static int ad7418_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *client;
……
……
client->addr = address;
client->adapter = adapter;
client->driver = &ad7418_driver;
i2c_set_clientdata(client, data);
……
……
if ((err = i2c_attach_client(client)))
goto exit_free;
……
……
}
结合上面关于new-style形式的驱动分析.发现这里走的是同一个套路,即初始化了client.然后调用
i2c_attach_client().后面的流程就跟上面分析的一样了.只不过,不相同的是,这里clinet已经指定了驱动为
ad7418_driver.应该在注册clinet->dev之后,就不会走bus->match和bus->probe的流程了.
七:i2c dev节点操作
现在来分析上面架构图中的i2c-dev.c中的部份.这个部份为用户空间提供了操作adapter的接口.这部份代码其实对应就晃一个模块.它的初始化函数为:
module_init(i2c_dev_init);
i2c_dev_init()代码如下:
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver/n");
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class))
goto out_unreg_chrdev;
res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed/n", __FILE__);
return res;
}
首先为主册了一个主设备号为I2C_MAJOR(89),操作集为i2cdev_fops的字符设备.
然后注册了一个名为”i2c-dev”的class.之后再注册了一个i2c的driver.如下所示:
res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
i2cdev_driver定义如下:
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.id = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
也就是说,当它注册或者有新的adapter注册后,就会它的attach_adapter()函数.该函数代码如下:
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
struct i2c_dev *i2c_dev;
int res;
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
/* register this i2c device with the driver core */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr),
"i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;
pr_debug("i2c-dev: adapter [%s] registered as minor %d/n",
adap->name, adap->nr);
return 0;
error_destroy:
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
return_i2c_dev(i2c_dev);
return res;
}
这个函数也很简单,首先调用get_free_i2c_dev()分配并初始化了一个struct i2c_dev结构,使i2c_dev->adap指向操作的adapter.之后,该i2c_dev会被链入链表i2c_dev_list中。再分别以I2C_MAJOR, adap->nr为主次设备号创建了一个device.如果此时系统配置了udev或者是hotplug,那么就么在/dev下自动创建相关的设备节点了.
刚才我们说过,所有主设备号为I2C_MAJOR的设备节点的操作函数是i2cdev_fops.它的定义如下所示:
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
7.1:i2c dev的open操作
Open操作对应的函数为i2cdev_open().代码如下:
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
//以次设备号从i2c_dev_list链表中取得i2c_dev
i2c_dev = i2c_dev_get_by_minor(minor);
if (!i2c_dev)
return -ENODEV;
//以apapter的总线号从i2c_adapter_idr中找到adapter
adap = i2c_get_adapter(i2c_dev->adap->nr);
if (!adap)
return -ENODEV;
/* This creates an anonymous i2c_client, which may later be
* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
*
* This client is ** NEVER REGISTERED ** with the driver model
* or I2C core code!! It just holds private copies of addressing
* information and maybe a PEC flag.
*/
//分配并初始化一个i2c_client结构
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->driver = &i2cdev_driver;
//clinet->adapter指向操作的adapter
client->adapter = adap;
//关联到file
file->private_data = client;
return 0;
}
注意这里分配并初始化了一个struct i2c_client结构.但是没有注册这个clinet.此外,这个函数中还有一个比较奇怪的操作.不是在前面已经将i2c_dev->adap
指向要操作的adapter么?为什么还要以adapter->nr为关键字从i2c_adapter_idr去找这个操作的adapter呢?注意了,调用i2c_get_adapter()从总线号nr找到操作的adapter的时候,还会增加module的引用计数.这样可以防止模块意外被释放掉.也许有人会有这样的疑问,那
i2c_dev->adap->nr操作,如果i2c_dev->adap被释放掉的话,不是一样会引起系统崩溃么?这里因为,在
i2cdev_attach_adapter()间接的增加了一次adapter的一次引用计数.如下:
tatic int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
......
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr),
"i2c-%d", adap->nr);
......
}
看到了么,i2c_dev内嵌的device是以adap->dev为父结点,在device_create()中会增次adap->dev的一次引用计数.
好了,open()操作到此就完成了.
7.2:read操作
Read操作对应的操作函数如下示:
static ssize_t i2cdev_read (struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;
int ret;
struct i2c_client *client = (struct i2c_client *)file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count,GFP_KERNEL);
if (tmp==NULL)
return -ENOMEM;
pr_debug("i2c-dev: i2c-%d reading %zd bytes./n",
iminor(file->f_path.dentry->d_inode), count);
ret = i2c_master_recv(client,tmp,count);
if (ret >= 0)
ret = copy_to_user(buf,tmp,count)?-EFAULT:ret;
kfree(tmp);
return ret;
}
首先从file结构中取得struct i2c_clinet.然后在kernel同分配相同长度的缓存区,随之调用i2c_master_recv()从设备中读取数据.再将读取出来的数据copy到用户空间中.
I2c_master_recv()代码如下:
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
struct i2c_adapter *adap=client->adapter;
struct i2c_msg msg;
int ret;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1);
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret;
}
看完前面的代码之后,这个函数应该很简单了,就是为读操作初始化了一个i2c_msg.然后调用i2c_tanster().代码中的
client->flags & I2C_M_TEN表示adapter是否采用10位寻址的方式.在这里就不再详细分析了.
另外,有人可能看出了一个问题.这里clinet->addr是从哪来的呢?对,在read之前应该还要有一步操作来设置
clinet->addr的值.这个过程是ioctl的操作.ioctl可以设置PEC标志,重试次数,超时时间,和发送接收数据等,我们在这里只看一下clinet->addr的设置.代码片段如下示:
static int i2cdev_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
......
......
switch ( cmd ) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/* NOTE: devices set up to work with "new style" drivers
* can't use I2C_SLAVE, even when the device node is not
* bound to a driver. Only I2C_SLAVE_FORCE will work.
*
* Setting the PEC flag here won't affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core. Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won't see
* (or use) this setting.
*/
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg;
return 0;
......
......
}
由此可见,调用I2C_SLAVE或者I2C_SLAVE_FORCE的Ioctl就会设置clinet->addr.另外,注释中也说得很清楚了.
如果是I2C_SLAVE的话,还会调用其所长i2cdev_check_addr().进行地址检查,如果adapter已经关联到这个地址的设备,就会检查失败.
7.2:write操作
Write操作如下所示:
static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count,
loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = (struct i2c_client *)file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count,GFP_KERNEL);
if (tmp==NULL)
return -ENOMEM;
if (copy_from_user(tmp,buf,count)) {
kfree(tmp);
return -EFAULT;
}
pr_debug("i2c-dev: i2c-%d writing %zd bytes./n",
iminor(file->f_path.dentry->d_inode), count);
ret = i2c_master_send(client,tmp,count);
kfree(tmp);
return ret;
}
该操作比较简单,就是将用户空间的数据发送到i2c 设备.
八:小结
在本节中,分析了i2c的框架设计.这个框架大体上沿用了Linux的设备驱动框架,不过之中又做了很多变通.在之后的分析中,会分别举一个adapter和i2c device的例子来详细描述一下有关i2c driver的设计.
IIC驱动程序分析
根据上一节课的分析,我们来解读这段代码:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
};
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");
return 0;
}
/* i2c_add_driver之后会调用到这个函数 */
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);//这个函数最终会调用适配器的发送函数来发送设备地址
}
/* i2c_del_driver时会调用这个函数,但前提是调用在at24cxx_detect函数中调用了i2c_attach_client函数将 设备、驱动、适配器三者联系起来了*/
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
return 0;
}
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
我们来分析一下这个程序的流程:
首先分配一个i2c_driver结构体,然后设置并注册这个结构体。设置的时候有来个函数比较重要:attach_adapter
和.detach_client
,前一个在注册的时候调用,后一个在卸载的时候调用。注册之后,这个结构体就被加入到总线驱动列表里面,然后它会调用at24cxx_attach函数,at24cxx_attach函数又会调用i2c_probe(adapter,
&addr_data, at24cxx_detect);函数,其中addr_data,代表设备地址,at24cxx_detect是找到设备后要执行的函数。i2c_probe函数最终会调用适配器里面的真正发送信号的函数把设备地址发送给从设备,一旦发现了相符合的从设备,就会调用at24cxx_detect函数,这个函数里面需要调用i2c_attach_client建立联系。这里建立联系的工作没有做,后来再来完善。
当调用 i2c_del_driver卸载i2c_driver这个结构体的时候,就会调用对应的驱动程序的 .detach_client = at24cxx_detach,函数来解除连接,但是前提是已经通过i2c_attach_client函数建立了联系。由于这里并没有建立联系,所以。。。。。。
我们可以总结出编写IIC总线驱动的大致流程:
1. 分配一个i2c_driver结构体
2. 设置
attach_adapter // 它直接调用 i2c_probe(adap, 设备地址, 发现这个设备后要调用的函数);
detach_client // 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理
3. 注册:i2c_add_driver
IIC驱动程序分析(二)
在上一节的实验中,我们采用的是normal_i2c 的方式,即:要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备。那么如果本身不存在这个设备当然啊不会给出应答信号,这是就不会调用i2c_probe(adapter, &addr_data, at24cxx_detect)函数中的at24cxx_detect函数。如果我们目前没有接上这个设备,但是我们今后打算把它安装上去,所以我们想要调用i2c_probe(adapter, &addr_data, at24cxx_detect)函数中的at24cxx_detect函数,那怎么办呢?这时就不能用normal_i2c方式,而应该采用forces方式。
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
/* 这里ANY_I2C_BUS表示在任意一条总线上查找该设备,0x60表示要发出的设备地址 */
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = ignore, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
.forces = forces, /* 强制认为存在这个设备 */
};
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");
return 0;
}
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
return 0;
}
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
在这个程序里面,即便不存在设备地址为0x60的设备,也会调用i2c_probe(adapter,
&addr_data,
at24cxx_detect)函数中的at24cxx_detect函数,因为这里采用的是强制的方式。具体原因等到以后在来研究,目前只是先有个轮廓!
IIC驱动程序分析(三)
上面两个程序我们主要实现了设备的识别,但是我们发现当卸载驱动的时候并没有相关的打印信息,这时怎么回事儿呢?其实原因我们之前已经提到过了,那是因为我们在i2c_probe(adapter, &addr_data, at24cxx_detect);的功能函数at24cxx_detect里面并没有建立设备、驱动、适配器的联系,因为没有建立联系,所以卸载的时候当然不会解除联系了!那么具体应该怎么做呢?我们来看代码:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,
.probe = ignore,
.ignore = ignore,
};
static struct i2c_driver at24cxx_driver;
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client;
printk("at24cxx_detect\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配
new_client->addr = address;//这是设备地址
new_client->adapter = adapter;这时适配器
new_client->driver = &at24cxx_driver;//这是驱动
strcpy(new_client->name, "at24cxx");//这是名字
i2c_attach_client(new_client);//添加联系
return 0;
}
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
}
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
这样的话卸载的时候就会有打印信息了!
IIC驱动程序之完善篇
下面我们来分析一个比较完整的IIC驱动程序:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};
static struct i2c_driver at24cxx_driver;
static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret; /* address = buf[0] * data = buf[1] */ if (size != 1) return -EINVAL; copy_from_user(&address, buf, 1);/ *将内核用户空间的数据(在buf中)拷贝到address里面 */
/* 数据传输三要素: 源,目的,长度 */
/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示写 */
/* 然后启动读操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 数据=1 byte */
msg[1].flags = I2C_M_RD; /* 表示读 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 2)
return -EINVAL;
copy_from_user(val, buf, 2);
/* 数据传输三要素: 源,目的,长度 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+数据=2 byte */
msg[0].flags = 0; /* 表示写 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx"); i2c_attach_client(at24cxx_client); major = register_chrdev(0, "at24cxx", &at24cxx_fops); cls = class_create(THIS_MODULE, "at24cxx"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */ return 0;}static int at24cxx_attach(struct i2c_adapter *adapter){ return i2c_probe(adapter, &addr_data, at24cxx_detect);}static int at24cxx_detach(struct i2c_client *client){ printk("at24cxx_detach\n"); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "at24cxx"); i2c_detach_client(client); kfree(i2c_get_clientdata(client)); return 0;}/* 1. 分配一个i2c_driver结构体 *//* 2. 设置i2c_driver结构体 */static struct i2c_driver at24cxx_driver = { .driver = { .name = "at24cxx", }, .attach_adapter = at24cxx_attach, .detach_client = at24cxx_detach,};static int at24cxx_init(void){ i2c_add_driver(&at24cxx_driver); return 0;}static void at24cxx_exit(void){ i2c_del_driver(&at24cxx_driver);}module_init(at24cxx_init);module_exit(at24cxx_exit);MODULE_LICENSE("GPL");
之前我们分析的IIC驱动程序主要是通过IIC总线机制完成了存储设备的识别,但是我们想对存储设备进行操作怎么办呢?那就要用到字符设备驱动程序的概念了!其实IIC总线机制就和平台总线机制是一个性质的,要完成具体的操作,还需要字符设备的支持。下面我们来详细分析一下:
首先通过IIC总线机制完成了存储设备的识别,接着注册字符设备以及创建设备节点,同时定义了操作函数,那么我们这里详细要分析的就是操作函数了,我们先来看看iic的消息传输函数:
测试程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* i2c_test r addr
* i2c_test w addr val
*/
void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
} else if (strcmp(argv[1], "w") == 0) { buf[0] = strtoul(argv[2], NULL, 0); buf[1] = strtoul(argv[3], NULL, 0); write(fd, buf, 2); } else { print_usage(argv[0]); return -1; } return 0;}
8、kernel panic
有两种主要类型kernel panic:
1.hard panic(也就是Aieee信息输出)
2.soft panic (也就是Oops信息输出)
9、USB总线,USB传输种类,urb等
USB总线:
USB总线属于一种轮询式总线,主机控制端口初始化所有的数据传输。每一总线动作最多传送三个数据包,包括令牌(Token)、数据(Data)、联络(HandShake)。按照传输前制定好的原则,在每次传送开始时,主机送一个描述传输动作的种类、方向、USB设备地址和终端号的USB数据包,这个数据包通常被称为令牌包(TokenPacket)。USB设备从解码后的数据包的适当位置取出属于自己的数据。数据传输方向不是从主机到设备就是从设备到主机。在传输开始时,由标志包来标志数据的传输方向,然后发送端开始发送包含信息的数据包或表明没有数据传送。接收端也要相应发送一个握手的数据包表明是否传送成功。发送端和接收端之间的USB数据传输,在主机和设备的端口之间,可视为一个通道。USB中有一个特殊的通道一缺省控制通道,它属于消息通道,设备一启动即存在,从而为设备的设置、状态查询和输入控制信息提供一个入口。
USB总线的四种传输类型:
1、中断传输:由OUT事务和IN事务构成,用于键盘、鼠标等HID设备的数据传输中 2、批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输。 3、同步传输:由OUT事务和IN事务构成,有两个特别地方,第一,在同步传输的IN和OUT事务中是没有返回包阶段的;第二,在数据包阶段任何的数据包都为DATA0 4、控制传输:最重要的也是最复杂的传输,控制传输由三个阶段构成(初始配置阶段、可选数据阶段、状态信息步骤),每一个阶段能够看成一个的传输,也就是说控制传输其实是由三个传输构成的,用来于USB设备初次加接到主机之后,主机通过控制传输来交换信息,设备地址和读取设备的描述符,使得主机识别设备,并安装相应的驱动程式,这是每一个USB研发者都要关心的问题。
URB:
USB请求块(USB request block,urb)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体,是USB主机与设备通信的“电波”。
20.3.2 USB请求块(URB)
20.3.2 USB请求块(URB)
1.urb结构体
USB请求块(USB request block,urb)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体,是USB主机与设备通信的“电波”。
代码清单20.13 urb结构体
1 struct urb |
当transfer_flags标志中的URB_NO_TRANSFER_DMA_MAP被置位时,USB核心将使用transfer_dma指向的缓冲区而非transfer_buffer指向的缓冲区,意味着即将传输DMA缓冲区。
当transfer_flags标志中的URB_NO_SETUP_DMA_MAP被置位时,对于有DMA缓冲区的控制urb而言,USB核心将使用setup_dma指向的缓冲区而非setup_packet指向的缓冲区。
2.urb处理流程
USB设备中的每个端点都处理一个urb队列,在队列被清空之前,一个urb的典型生命周期如下:
(1)被一个 USB 设备驱动创建。
创建urb结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags); |
iso_packets是这个urb应当包含的等时数据包的数目,若为0表示不创建等时数据包。 mem_flags参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb结构体指针,否则返回0。
urb结构体在驱动中不能静态创建,因为这可能破坏USB核心给urb使用的引用计数方法。
usb_alloc_urb()的“反函数”为:
void usb_free_urb(struct urb *urb); |
该函数用于释放由usb_alloc_urb()分配的urb结构体。
(2)初始化,被安排给一个特定USB设备的特定端点。
对于中断urb,使用usb_fill_int_urb()函数来初始化urb,如下所示:
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, |
urb参数指向要被初始化的urb的指针;dev指向这个urb要被发送到的USB设备;pipe是这个urb要被发送到的USB设备的特定端点;transfer_buffer是指向发送数据或接收数据的缓冲区的指针,和urb一样,它也不能是静态缓冲区,必须使用kmalloc()来分配;buffer_length是transfer_buffer指针所指向缓冲区的大小;complete指针指向当这个 urb完成时被调用的完成处理函数;context是完成处理函数的“上下文”;interval是这个urb应当被调度的间隔。
上述函数参数中的pipe使用usb_sndintpipe()或usb_rcvintpipe()创建。
对于批量urb,使用usb_fill_bulk_urb()函数来初始化urb,如下所示:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, |
除了没有对应于调度间隔的interval参数以外,该函数的参数和usb_fill_int_urb()函数的参数含义相同。
上述函数参数中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函数来创建。
对于控制 urb,使用usb_fill_control_urb()函数来初始化urb,如下所示:
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, |
除了增加了新的setup_packet参数以外,该函数的参数和usb_fill_bulk_urb()函数的参数含义相同。setup_packet参数指向即将被发送到端点的设置数据包。
上述函数参数中的pipe使用usb_sndctrlpipe()或usb_rcvictrlpipe()函数来创建。
等时urb没有像中断、控制和批量urb的初始化函数,我们只能手动地初始化urb,而后才能提交给USB核心。代码清单20.14给出了初始化等时urb的例子,它来自drivers/usb/media/usbvideo.c文件。
代码清单20.14 初始化等时urb
1 for (i = 0; i < USBVIDEO_NUMSBUF; i++) |
(3)被USB设备驱动提交给USB 核心。
在完成第(1)、(2)步的创建和初始化urb后,urb便可以提交给USB核心,通过usb_submit_urb()函数来完成,如下所示:
int usb_submit_urb(struct urb *urb, int mem_flags); |
urb参数是指向urb的指针,mem_flags参数与传递给kmalloc()函数参数的意义相同,它用于告知USB核心如何在此时分配内存缓冲区。
在提交urb到USB核心后,直到完成函数被调用之前,不要访问urb中的任何成员。
usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置,如下所示。
l GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current->state修改为非 TASK_ RUNNING时,应使用此标志。
l GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
l GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。
如果usb_submit_urb()调用成功,即urb的控制权被移交给USB核心,该函数返回0;否则,返回错误号。
(4)提交由USB核心指定的USB主机控制器驱动。
(5)被USB主机控制器处理,进行一次到USB设备的传送。
第(4)~(5)步由USB核心和主机控制器完成,不受USB设备驱动的控制。
(6)当urb完成,USB主机控制器驱动通知USB设备驱动。
在如下3种情况下,urb将结束,urb完成函数将被调用。
l urb 被成功发送给设备,并且设备返回正确的确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请求的数据被成功收到。
l 如果发送数据到设备或从设备接收数据时发生了错误,urb->status将记录错误值。
l urb 被从USB 核心“去除连接”,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或urb虽已提交,而USB设备被拔出的情况下。
usb_unlink_urb()和usb_kill_urb()这两个函数用于取消已提交的urb,其参数为要被取消的urb指针。对usb_unlink_urb()而言,如果urb结构体中的URB_ASYNC_UNLINK(即异步unlink)的标志被置位,则对该urb的usb_unlink_urb()调用将立即返回,具体的unlink动作将在后台进行。否则,此函数一直等到urb被解开链接或结束时才返回。usb_kill_urb()会彻底终止urb的生命周期,它通常在设备的disconnect()函数中被调用。
当urb生命结束时(处理完成或被解除链接),通过urb结构体的status成员可以获知其原因,如0表示传输成功,-ENOENT表示被usb_kill_urb()杀死,-ECONNRESET表示被usb_unlink_urb()杀死,-EPROTO表示传输中发生了bitstuff错误或者硬件未能及时收到响应数据包,-ENODEV表示USB设备已被移除,-EXDEV表示等时传输仅完成了一部分等。
对以上urb的处理步骤进行一个总结,图20.5给出了一个urb的整个处理流程,虚线框的usb_unlink_urb()和usb_kill_urb()并非一定会发生,它只是在urb正在被USB核心和主机控制器处理时,被驱动程序取消的情况下才发生。
3.简单的批量与控制URB
有时USB驱动程序只是从USB设备上接收或向USB设备发送一些简单的数据,这时候,没有必要将urb创建、初始化、提交、完成处理的整个流程走一遍,而可以使用两个更简单的函数,如下所示。
(1)usb_bulk_msg()。
usb_bulk_msg()函数创建一个USB批量urb 并将它发送到特定设备,这个函数是同步的,它一直等待urb完成后才返回。usb_bulk_msg()函数的原型为:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, |
图20.5 urb处理流程 |
usb_dev参数为批量消息要发送的USB 设备的指针,pipe为批量消息要发送到的USB设备的端点,data参数为指向要发送或接收的数据缓冲区的指针,len参数为data参数所指向的缓冲区的长度,actual_length用于返回实际发送或接收的字节数,timeout是发送超时,以jiffies为单位,0意味着永远等待。
如果函数调用成功,返回0;否则,返回1个负的错误值。
(2)usb_control_msg()函数。
usb_control_msg()函数与usb_bulk_msg()函数类似,不过它提供驱动发送和结束USB控制信息而非批量信息的能力,该函数的原型为:
int usb_control_msg(struct usb_device *dev, unsigned int pipe, _ _u8 request, |
dev指向控制消息发往的USB设备,pipe是控制消息要发往的USB设备的端点,request是这个控制消息的USB请求值,requesttype是这个控制消息的USB请求类型,value是这个控制消息的USB消息值,index是这个控制消息的USB消息索引值,data指向要发送或接收的数据缓冲区,size是data参数所指向的缓冲区的大小,timeout是发送超时,以jiffies为单位,0意味着永远等待。
参数request、requesttype、value和index与USB规范中定义的USB控制消息直接对应。
如果函数调用成功,该函数返回发送到设备或从设备接收到的字节数;否则,返回一个负的错误值。
对usb_bulk_msg()和usb_control_msg()函数的使用要特别慎重,由于它们是同步的,因此不能在中断上下文和持有自旋锁的情况下使用。而且,该函数也不能被任何其他函数取消,因此,务必要使得驱动程序的disconnect()函数掌握足够的信息,以判断和等待该调用的结束。
10、android boot 流程
android boot process from power on
1. Power on and boot ROM code execution
开机并执行 boot ROM 代码
At power on the CPU will be in a state where no initializations have been done. Internal clocks are not set up and the only memory available is the internal RAM.
When power supplies are stable the execution will start with the Boot ROM code. This is a small piece of code that is hardwired in the CPU ASIC.
才开机时,CPU 处于未初始化状态,还没有设定内部时钟,仅仅只有内部 RAM 可用。当电源稳定后会开始执行 BOOT ROM 代码,这是一小块代码被硬编码在 CPU ASIC 中。
A. The Boot ROM code will detect the boot media using a system register that maps to some physical balls on the asic.
This is to determine where to find the first stage of the boot loader.
Boot ROM 代码会引导 boot 媒介使用系统寄存器映射到 ASIC 中一些物理区域,这是为了确定哪里能找到 boot loader 的第一阶段B. Once the boot media sequence is established the boot ROM will try to load the first stage boot loader to internal RAM.
Once the boot loader is in place the boot ROM code will perform a jump and execution continues in the boot loader.
一旦 boot 媒介顺序确定,boot ROM 会试着装载 boot loader 的第一阶段到内部 RAM 中,一旦 boot loader 就位,boot ROM 代码会跳到并执行 boot loader
2. The boot loader
The boot loader is a special program separate from the Linux kernel that is used to set up initial memories and load the kernel to RAM.
On desktop systems the boot loaders are programs like GRUB and in embedded LinuxuBoot is often the boot loader of choice.
Device manufacturers often use their own proprietary boot loaders.
boot loader 是一个特殊的独立于 Linux 内核的程序,它用来初始化内存和装载内核到 RAM 中,桌面系统的 boot loader 程序有 GRUB,嵌入式系统常用 uBoot,
设备制造商常常使用自己专有的 boot loader 程序。
A. The first boot loader stage will detect and set up external RAM.
boot loader 第一阶段会检测和设置外部 RAMB. Once external RAM is available and the system is ready the to run something more significant the first stage will load the main boot loader and place it in external RAM.
一旦外部 RAM 可用,系统会准备装载主 boot loader,把它放到外部 RAM 中-
C. The second stage of the boot loader is the first major program that will run. This may contain code to set up file systems, additional memory,
network support and other things.On a mobile phone it may also be responsible for loading code for the modem CPU and setting up low level memory
protections and security options.boot loader 第二阶段是运行的第一个重要程序,它包含了设置文件系统,内存,网络支持和其他的代码。在一个移动电话上,
也可能是负责加载调制解调器的CPU代码和设定低级别的内存保护和安全选项 D. Once the boot loader is done with any special tasks it will look for a Linux kernel to boot. It will load this from the boot media
(or some other source depending on system configuration) and place it in the RAM.
It will also place some boot parameters in memory for the kernel to read when it starts up.
一旦 boot loader 完成这些特殊任务,开始寻找 linux 内核,它会从 boot 媒介上装载 linux 内核(或者其他地方,这取决于系统配置),把它放到 RAM 中,
它也会在内存中为内核替换一些在内核启动时读取的启动参数E. Once the boot loader is done it will perform a jump to the Linux kernel, usually some decompression routine, and the kernel assumes system responsibility.
一旦 boot loader 完成会跳到 linux 内核,通常通过解压程序解压内核文件,内核将取得系统权限
3. The Linux kernel
linux 内核
The Linux kernel starts up in a similar way on Android as on other systems. It will set up everything that is needed for the system to run. Initialize interrupt controllers,
set up memory protections, caches and scheduling.
linux 内核在 android 上跟在其他系统上的启动方式一样,它将设置系统运行需要的一切,初始化中断控制器,设定内存保护,高速缓存和调度
A. Once the memory management units and caches have been initialized the system will be able to use virtual memory and launch user space processes.
一旦内存管理单元和高速缓存初始化完成,系统将可以使用虚拟内存和启动用户空间进程B. The kernel will look in the root file system for the init process (found under system/core/init in the Android open source tree) and launch it as the initial user space process.
内核在根目录寻找初始化程序(代码对应 android source tree: /system/core/init ),启动它作为初始化用户空间进程
4. The init process
初始化进程
The init process is the "grandmother" of all system processes. Every other process in the system will be launched from this process or one of its descendants.
初始化进程是所有其他系统进程的 “祖母 ”,系统的每一个其他进程将从该进程中或者该进程的子进程中启动
A. The init process in Android will look for a file called init.rc. This is a script that describes the system services, file system and other parameters that need to be set up.
The init.rc script is placed in system/core/rootdir in the Android open source project.
初始化进程会寻找 init.rc 文件,init.rc 脚本文件描述了系统服务,文件系统和其他需要设定的参数,该文件在代码:system/core/rootdirB. The init process will parse the init script and launch the system service processes.
初始化进程解析 init 脚本,启动系统服务进程
5. Zygote and Dalvik
The Zygote is launched by the init process and will basically just start executing and and initialize the Dalvik VM.
Zygote 被初始化进程启动,开始运行和初始化 dalvik 虚拟机
6. The system server
系统服务
The system server is the first java component to run in the system. It will start all the Android services such as telephony manager and bluetooth.
Start up of each service is currently written directly into the run method of the system server. The system server source can be found in the file
:
frameworks/base/services/java/com/android/server/SystemServer.java in the open source project.
系统服务是在系统中运行的第一个 java 组件,它会启动所有的 android 服务,比如:电话服务,蓝牙服务,每个服务的启动被直接写在 SystemServer.java 这个类的 run 方法里面
代码: frameworks/base/services/java/com/android/server/SystemServer.java
7. Boot completed
启动完成
Once the System Server is up and running and the system boot has completed there is a standard broadcast action called ACTION_BOOT_COMPLETED.
一旦系统服务启动并运行,android 系统启动就完成了,同时发出 ACTION_BOOT_COMPLETED 广播
android boot 代码流程 1
之前这篇,从整体展示了 android 的整个启动流程,为了搞清楚 android 启动到底在代码层面上是如何调用的,将从源代码角度去分析,另所有代码基于 android 4.0 source tree
all story begin with the init process startup
故事从 init 进程启动开始
init 运行,代码:system/core/init ,入口:system/core/init/init.c main 函数:
1 int main(int argc, char **argv){
2
3 ...
4 // 初始化文件系统
5 mkdir("/dev", 0755);
6 mkdir("/proc", 0755);
7 mkdir("/sys", 0755);
8
9 mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
10 mkdir("/dev/pts", 0755);
11 mkdir("/dev/socket", 0755);
12 mount("devpts", "/dev/pts", "devpts", 0, NULL);
13 mount("proc", "/proc", "proc", 0, NULL);
14 mount("sysfs", "/sys", "sysfs", 0, NULL);
15
16 ...
17 // 解析 /init.rc 和 /init.$hardware.rc 脚本,其中 $hardware 参数从 /proc/cpuinfo 中读取,模拟器默认是 goldfish
18 INFO("reading config file\n");
19 init_parse_config_file("/init.rc");
20
21 /* pull the kernel commandline and ramdisk properties file in */
22 import_kernel_cmdline(0, import_kernel_nv);
23 /* don't expose the raw commandline to nonpriv processes */
24 chmod("/proc/cmdline", 0440);
25 get_hardware_name(hardware, &revision);
26 snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
27 init_parse_config_file(tmp);
28
29 ...
30 }
解析 init.rc 文件,主要是由 /system/core/init/init_parser.c 来完成,截取 init.rc 的部分内容如下:(具体 init.rc 文件规范,可参考:/system/core/init/readme.txt)
on early-init
start ueventd # create mountpoints
mkdir /mnt 0775 root system # setup the global environment
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin # Create cgroup mount points for process groups
mkdir /dev/cpuctl
mount cgroup none /dev/cpuctl cpu
chown system system /dev/cpuctl
chown system system /dev/cpuctl/tasks
chmod 0777 /dev/cpuctl/tasks
write /dev/cpuctl/cpu.shares 1024 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
init.rc 使用的是 android init language 规范,它支持4种语句,Actions,Commands,Services,Options:
- Action 定义方式:
on <trigger> #以 on 开头,后面是触发器名字,触发器有3种方式:
<command> #1.只是一个名字,如: on early-init;
<command> #2.name=value 对,如:on property:vold.decrypt=trigger_reset_main;
<command> #3.系统自带的,如:device-added-<path>,device-removed-<path>,service-exited-<name>
<command> #在触发器下一行就是在触发器触发的时候需要执行的命令,如:start...,mkdir,...
- Command 就是系统支持的一系列命令,如:export,hostname,mkdir,mount,等等,其中一部分是 linux 命令,还有一些是 android 添加的,如:class_start <serviceclass>: 启动服务,
class_stop <serviceclass>:关闭服务,等等。
- Service 定义方式:
service <name> <pathname> [ <argument> ]*
<option>
<option>
... #如:启动 android 最重要的服务 zygote
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
- Option 是针对 Service 的选项,如:
setenv <name> <value> 在启动服务时设置环境变量
user <username> 运行服务之前切换用户
oneshot 如果服务已经存在,将不再启动
class <classname> 为服务设置名字,具有相同名字的服务将一起启动或者关闭
socket <name> <type> <perm> [ <user> [ <group> ] ] 创建以<name>命名的 socket,并将该 socket 的文件描述符返回给启动的服务
onrestart <command> 在服务重新启动的时候执行<command>
在对 init.rc 和 init.$hardware.rc 2个文件按照 android init language 规范进行解析完成以后,会将所有将要执行的命令放到一个大的 action_list 当中,然后紧跟上面解析 init 文件下面:
// 寻找 early-init 触发器,加到 action_queue 中
action_for_each_trigger("early-init", action_add_queue_tail);
// 添加系统的一些触发器
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties"); /* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
} queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup"); if (!strcmp(bootmode, "charger")) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
} /* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
...
// 按顺序执行 action_queue 中的命令
for(;;) {
int nr, i, timeout = -1; execute_one_command();
restart_processes(); ...
}
从 action_list 中找到制定触发器,将触发器需要执行的命令添加到 action_queue 中,最后按顺序执行 action_queue 中的命令来完成初始化,初始化除了设置一些环境变量和创建文件夹以外,
更多的是关心 Service 的启动,init 文件里面的服务有2种,1种是 class core,还有1种是 class main,对 init.rc 的 service 按照 class <name> 分类如下:
class core: service ueventd /sbin/ueventd
service console /system/bin/sh
service adbd /sbin/adbd
service servicemanager /system/bin/servicemanager
service vold /system/bin/vold class main: service netd /system/bin/netd
service debuggerd /system/bin/debuggerd
service ril-daemon /system/bin/rild
service surfaceflinger /system/bin/surfaceflinger
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
service drm /system/bin/drmserver
service media /system/bin/mediaserver
service bootanim /system/bin/bootanimation
service dbus /system/bin/dbus-daemon --system --nofork
service bluetoothd /system/bin/bluetoothd -n
service installd /system/bin/installd
service flash_recovery /system/etc/install-recovery.sh
service racoon /system/bin/racoon
service mtpd /system/bin/mtpd
service keystore /system/bin/keystore /data/misc/keystore
service dumpstate /system/bin/dumpstate -s
service ueventd:会读取 /ueventd.rc 和 /ueventd.$hadrware.rc 文件,解析跟 init.rc 解析类似,主要是对文件系统的权限和用户进行设置:(代码:system/core/init/ueventd.c)
#目录 权限 user group /dev/null 0666 root root
/dev/zero 0666 root root
在 class core 服务启动以后, class main 开始启动,service zygote 是标志进入 android 最重要的一个服务,服务名字 zygote,实际上启动的是 app_process,(代码:frameworks/base/cmds/app_process/app_main.cpp)
int main(int argc, const char* const argv[])
{
// These are global variables in ProcessState.cpp
mArgC = argc;
mArgV = argv; mArgLen = 0; //读取参数,传递的参数就是 init.rc 中启动时传入的: -Xzygote /system/bin --zygote --start-system-server
for (int i=0; i<argc; i++) {
mArgLen += strlen(argv[i]) + 1;
}
mArgLen--; AppRuntime runtime;
const char* argv0 = argv[0]; //-Xzygote // Process command line arguments
// ignore argv[0]
argc--; //之前是 4, 现在是 3
argv++; //argv 指向 argv[1] // Everything up to '--' or first non '-' arg goes to the vm
// i = 0,代码:frameworks/base/core/jni/AndroidRuntime.cpp
int i = runtime.addVmArguments(argc, argv); // Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
const char* parentDir = NULL;
const char* niceName = NULL;
const char* className = NULL;
while (i < argc) {
const char* arg = argv[i++];
if (!parentDir) {
parentDir = arg; //parentDir = /system/bin
} else if (strcmp(arg, "--zygote") == 0) { //当 i = 2,arg = argv[1] 时,即:--zygote
zygote = true;
niceName = "zygote";
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName = arg + 12;
} else {
className = arg;
break;
}
} if (niceName && *niceName) {
setArgv0(argv0, niceName);
set_process_name(niceName);
} runtime.mParentDir = parentDir; if (zygote) {
// zygote = true,启动 com.android.internal.os.ZygoteInit,参数:startSystemServer
runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
} else if (className) {
// Remainder of args get passed to startup class main()
runtime.mClassName = className;
runtime.mArgC = argc - i;
runtime.mArgV = argv + i;
runtime.start("com.android.internal.os.RuntimeInit",
application ? "application" : "tool");
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
return 10;
}
}
检测传入参数,将调用 :
runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
runtime 的代码:frameworks/base/core/jni/AndroidRuntime.cpp,start 函数会启动虚拟机, 执行 com.android.internal.os.ZygoteInit 该类的 main 函数,并传入参数: start-system-server:
void AndroidRuntime::start(const char* className, const char* options)
{
LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
className != NULL ? className : "(unknown)");
...
/* start the virtual machine */
//设置 dalvik 虚拟机参数,创建并启动虚拟机
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
onVmCreated(env); /*
* Register android functions.
*/
if (startReg(env) < 0) {
LOGE("Unable to register all android natives\n");
return;
} ...
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
//将类名 com.xxx.xxx 转换成 com/xxx/xxx
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
// jni 调用 java 方法,获取对应类名的 class,然后调用静态 main 方法
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
LOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
free(slashClassName); LOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
LOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
LOGW("Warning: VM did not shut down cleanly\n");
}
ZygoteInit 类 main 函数:
public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();
//注册 socket server
registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis()); //预加载资源,有 preloadClasses() 和 preloadResources(),加载的开始和结束会被记录在 /system/etc/event-log-tags 文件中
preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup
gc(); // If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
if (argv[1].equals("start-system-server")) {
//在调用 Zygote 的 main 函数时,已经传入 start-system-server,调用 startSystemServer()
startSystemServer();
} else if (!argv[1].equals("")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
} Log.i(TAG, "Accepting command socket connections"); if (ZYGOTE_FORK_MODE) {
runForkMode();
} else {
//上面通过 registerZygoteSocket() 函数调用注册的 server scocket,会启动,开始监听 Zygote 连接
runSelectLoopMode();
}
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}
那 startSystemServer 到底又做了什么东东呢,以及最后系统如何发出 ACTION_BOOT_COMPLETED 广播的呢,且听下回分解。 -):
android boot 代码流程 2
上回 说到,开始调用 ZygoteInit main 函数,main 函数:
- registerZygoteServer:注册一个 zygote server socket,所有来自客户端的连接都通过 socket 方式连接;
- preload:预加载系统的类库和资源,这样其他程序启动将不再加载系统资源,只需加载自己程序的资源,这样就达到系统资源在程序之间共享;
- startSystemServer:
private static boolean startSystemServer()
throws MethodAndArgsCaller, RuntimeException {
/* Hardcoded command line to start the system server */
//命令行参数,包括:uid,gid,group,process_name,process class
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
"--capabilities=130104352,130104352",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null; int pid; try {
//解析命令行参数
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /* Request to fork the system server process */
//从 zygote 进程派生一个新的进程,fork 可参考:http://linux.die.net/man/2/fork ,不同的是该进程结束时,也会让 zygote 进程结束
//所以这里,会返回2次,一次返回的是 zygote 进程的 pid ,值大于0;一次返回的是子进程 pid,值等于0
// fork 返回在 zygote 进程返回的子进程 pid,非0,在子进程中返回0
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
} /* For child process */
//zygote 进程 pid 非0,直接返回,而子进程 pid = 0,对子进程进行设置
if (pid == 0) {
handleSystemServerProcess(parsedArgs);
} return true;
}
而 handleSystemServerProcess 将启动 com.android.server.SystemServer:
private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws ZygoteInit.MethodAndArgsCaller {
//因为有 zygote 监听 socket,所以 system server 不监听 socket 连接,此处关闭
closeServerSocket(); // set umask to 0077 so new files and directories will default to owner-only permissions.
FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
//设置进程名字,即从命令行参数获取的:system_server
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
} if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
null, parsedArgs.remainingArgs);
} else {
/*
* Pass the remaining arguments to SystemServer.
*/
/* zygoteInit -> applicationInit:设置 sdktarget 版本 -> invokeStaticMain:得到 com.android.server.SystemServer main 方法 -> ZygoteInit.MethodAndArgsCaller
* ZygoteInit.MethodAndArgsCaller 方法抛出异常 MethodAndArgsCaller,跳过了在 startSystemServer 下面的代码:
* if (ZYGOTE_FORK_MODE) {
* runForkMode();
* } else {
* runSelectLoopMode();
* }
*/
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs); } /* should never reach here */
}
在对 MethodAndArgsCaller 异常 catch 语句里,直接调用了 com.android.server.SystemServer main 方法,而 zygote 进程因为 pid 不为0,执行 runSelectLoopMode 方法:
private static void runSelectLoopMode() throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList();
ArrayList<ZygoteConnection> peers = new ArrayList();
FileDescriptor[] fdArray = new FileDescriptor[4]; fds.add(sServerSocket.getFileDescriptor());
peers.add(null); int loopCount = GC_LOOP_COUNT;
//一直循环
while (true) {
int index; /*
* Call gc() before we block in select().
* It's work that has to be done anyway, and it's better
* to avoid making every child do it. It will also
* madvise() any free memory as a side-effect.
*
* Don't call it every time, because walking the entire
* heap is a lot of overhead to free a few hundred bytes.
*/
if (loopCount <= 0) {
gc();
loopCount = GC_LOOP_COUNT;
} else {
loopCount--;
} //采用非阻塞方式,等待并取出 zygote 连接
try {
fdArray = fds.toArray(fdArray);
index = selectReadable(fdArray);
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
//selectReadable 返回值小于0 ,有错误发生;值等于0,有新的连接,加到 list 中;值大于0,处理当前连接
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {
ZygoteConnection newPeer = acceptCommandPeer();
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done;
done = peers.get(index).runOnce(); if (done) {
peers.remove(index);
fds.remove(index);
}
}
}
}
在 zygote 进程等待连接的同时,com.android.server.SystemServer 已经启动:
native public static void init1(String[] args); public static void main(String[] args) {
...
//加载 jni ,init1 是本地方法
System.loadLibrary("android_servers");
// init1 -> frameworks/base/services/jni/com_android_server_SystemServer.cpp :: android_server_SystemServer_init1 ->
// frameworks/base/cmds/system_server/library/system_init.cpp :: system_init
init1(args);
}
// init1 将回调 init2 方法
public static final void init2() {
Slog.i(TAG, "Entered the Android system server!");
Thread thr = new ServerThread();
thr.setName("android.server.ServerThread");
thr.start();
}
init1 方法最终调用的是 system_init 方法(代码:frameworks/base/cmds/system_server/library/system_init.cpp)
extern "C" status_t system_init()
{
LOGI("Entered system_init()"); sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
LOGI("ServiceManager: %p\n", sm.get()); sp<GrimReaper> grim = new GrimReaper();
sm->asBinder()->linkToDeath(grim, grim.get(), 0);
//初始化 SurfaceFlinger 和传感器
char propBuf[PROPERTY_VALUE_MAX];
property_get("system_init.startsurfaceflinger", propBuf, "1");
if (strcmp(propBuf, "1") == 0) {
// Start the SurfaceFlinger
SurfaceFlinger::instantiate();
} property_get("system_init.startsensorservice", propBuf, "1");
if (strcmp(propBuf, "1") == 0) {
// Start the sensor service
SensorService::instantiate();
} // And now start the Android runtime. We have to do this bit
// of nastiness because the Android runtime initialization requires
// some of the core system services to already be started.
// All other servers should just start the Android runtime at
// the beginning of their processes's main(), before calling
// the init function.
LOGI("System server: starting Android runtime.\n");
AndroidRuntime* runtime = AndroidRuntime::getRuntime();
//回调 com.android.server.SystemServer init2 方法
LOGI("System server: starting Android services.\n");
JNIEnv* env = runtime->getJNIEnv();
if (env == NULL) {
return UNKNOWN_ERROR;
}
jclass clazz = env->FindClass("com/android/server/SystemServer");
if (clazz == NULL) {
return UNKNOWN_ERROR;
}
jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V");
if (methodId == NULL) {
return UNKNOWN_ERROR;
}
env->CallStaticVoidMethod(clazz, methodId);
//启动线程池,为 binder 服务
LOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
LOGI("System server: exiting thread pool.\n"); return NO_ERROR;
}
init2 启动 ServerThread 线程,它会启动 android 系统所有的服务:
public void run() {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
SystemClock.uptimeMillis());
Looper.prepare(); android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND); BinderInternal.disableBackgroundScheduling(true);
android.os.Process.setCanSelfBackground(false); String factoryTestStr = SystemProperties.get("ro.factorytest");
int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
: Integer.parseInt(factoryTestStr);
//初始化服务,如:网络服务,Wifi服务,蓝牙,电源,等等,初始化完成以后,加到 ServiceManager 中,
//所以我们用 Context.getSystemService (String name) 才获取到相应的服务
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
WifiP2pService wifiP2p = null;
WifiService wifi = null;
IPackageManager pm = null;
Context context = null;
WindowManagerService wm = null;
BluetoothService bluetooth = null;
BluetoothA2dpService bluetoothA2dp = null;
DockObserver dock = null;
UsbService usb = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null; // Critical services...
try {
Slog.i(TAG, "Entropy Service");
ServiceManager.addService("entropy", new EntropyService());
Slog.i(TAG, "Package Manager");
// Only run "core" apps if we're encrypting the device.
......
//ActivityManagerService 是 android 系统最核心的服务之一
//1.系统 context 的初始化,设置默认主题 android.R.style.Theme_Holo
//2.设置进程名字为 system_process
//3.初始化 ActivityStack
context = ActivityManagerService.main(factoryTest);
//往 service manager 里面添加一些服务,如:activity,meminfo,cupinfo,permission
ActivityManagerService.setSystemProcess();
//安装系统 content provider
Slog.i(TAG, "System Content Providers");
ActivityManagerService.installSystemProviders();
//设置 windows manager
ActivityManagerService.self().setWindowManager(wm);
......
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
// where third party code can really run (but before it has actually
// started launching the initial applications), for us to complete our
// initialization.
//代码到这里,表明系统已经就绪,可以运行第3方代码
ActivityManagerService.self().systemReady(new Runnable() {
public void run() {
Slog.i(TAG, "Making services ready");
// systemui 是 3.0 以后添加的,因为没有物理键,提供虚拟键
startSystemUi(contextF); //诸多服务开始启动
try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
reportWtf("making Battery Service ready", e);
}
try {
if (networkManagementF != null) networkManagementF.systemReady();
} catch (Throwable e) {
reportWtf("making Network Managment Service ready", e);
}
......
}
}); // For debug builds, log event loop stalls to dropbox for analysis.
if (StrictMode.conditionallyEnableDebugLogging()) {
Slog.i(TAG, "Enabled StrictMode for system server main thread.");
} Looper.loop();
Slog.d(TAG, "System ServerThread is exiting!");
}
而要执行 ActivityManagerService.self().systemReady(new Runnable() ...) 参数里面 Runnable 的 run 方法,还必须等到 ActivityManagerService systemReady:
public void systemReady(final Runnable goingCallback) {
synchronized(this) {
//mSystemReady = false
if (mSystemReady) {
if (goingCallback != null) goingCallback.run();
return;
} // Check to see if there are any update receivers to run.
if (!mDidUpdate) {
if (mWaitingUpdate) {
return;
}
//检测是否有 ACTION_PRE_BOOT_COMPLETED register,该广播在 ACTION_BOOT_COMPLETED 前发出
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
List<ResolveInfo> ris = null;
try {
ris = AppGlobals.getPackageManager().queryIntentReceivers(
intent, null, 0);
} catch (RemoteException e) {
}
if (ris != null) {
for (int i=ris.size()-1; i>=0; i--) {
//检测广播注册是否是系统程序
if ((ris.get(i).activityInfo.applicationInfo.flags
&ApplicationInfo.FLAG_SYSTEM) == 0) {
ris.remove(i);
}
}
intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers(); final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
for (int i=0; i<ris.size(); i++) {
ActivityInfo ai = ris.get(i).activityInfo;
ComponentName comp = new ComponentName(ai.packageName, ai.name);
if (lastDoneReceivers.contains(comp)) {
ris.remove(i);
i--;
}
} for (int i=0; i<ris.size(); i++) {
ActivityInfo ai = ris.get(i).activityInfo;
ComponentName comp = new ComponentName(ai.packageName, ai.name);
doneReceivers.add(comp);
intent.setComponent(comp);
IIntentReceiver finisher = null;
if (i == ris.size()-1) {
finisher = new IIntentReceiver.Stub() {
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered,
boolean sticky) {
// The raw IIntentReceiver interface is called
// with the AM lock held, so redispatch to
// execute our code without the lock.
mHandler.post(new Runnable() {
public void run() {
synchronized (ActivityManagerService.this) {
mDidUpdate = true;
}
writeLastDonePreBootReceivers(doneReceivers);
showBootMessage(mContext.getText(
R.string.android_upgrading_complete),
false);
//如果有 ACTION_PRE_BOOT_COMPLETED,在处理完广播 receive 以后 ,还会再次走 systemRead(goingCallback)
systemReady(goingCallback);
}
});
}
};
}
Slog.i(TAG, "Sending system update to: " + intent.getComponent());
broadcastIntentLocked(null, null, intent, null, finisher,
0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);
if (finisher != null) {
mWaitingUpdate = true;
}
}
}
if (mWaitingUpdate) {
return;
}
mDidUpdate = true;
} mSystemReady = true;
//mStartRunning 已经在 ActivityManagerService.main(int factoryTest) 设置成 true
if (!mStartRunning) {
return;
}
} ......
retrieveSettings();
//开始执行 runnable 的 run 方法,执行完成以后,系统就绪
if (goingCallback != null) goingCallback.run(); synchronized (this) {
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info
= (ApplicationInfo)apps.get(i);
if (info != null &&
!info.packageName.equals("android")) {
addAppLocked(info);
}
}
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
} // Start up initial activity.
mBooting = true; try {
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
Message msg = Message.obtain();
msg.what = SHOW_UID_ERROR_MSG;
mHandler.sendMessage(msg);
}
} catch (RemoteException e) {
}
//恢复 top activity,因为现在没有任何启动的 activity, 将会启动 startHomeActivityLocked,启动 HOME
mMainStack.resumeTopActivityLocked(null);
}
}
HOME 启动以后,ActivityManagerService 中 finishBooting 方法会发出 Intent.ACTION_BOOT_COMPLETED 广播,调用该方法的地方有很多,resume activity 的时候或者出错的时候,
调用一次以后就不再调用。
至此 android 就完成了整个启动工作,整个流程可以用下图简洁表示:
11、android init解析init.rc
Android初始化语言(Android Init Language
Android初始化脚本语言包含四种类型的语句:
- 动作(Actions)
- 指令(Commands)
- 服务(Services)
- 选项(Options)
该语言的语法包括下列约定:
- 所有类型的语句都是基于行(line-oriented)的, 一个语句包含若干个tokens,token之间通过空格字符分隔. 如果一个token中需要包含空格字符,则需要通过C语言风格的反斜线('\')来转义,或者使用双引号把整个token引起来。反斜线还可以出现在一行的末尾,表示下一行的内容仍然属于当前语句。
- 以'#'开始的行是注释行。
- 动作(Actions)和服务(Services)语句隐含表示一个新的段落(section)的开始。 所有的指令(commands)和选项(options)归属于上方最近的一个段落。在第一个段落之前的指令(commands)和选项(options)是无效的。
- 动作(Actions)和服务(Services)拥有唯一性的名字。如果出现重名,那么后出现的定义将被作为错误忽略掉。
动作(Actions)
动作(Actions)是一个有名字的指令(commands)序列。每个动作(Actions)都定义一个触发条件(trigger),用于指示什么时候执行这个动作。当与动作的触发器匹配的事件发生时,该动作将被添加到一个即将被执行的队列的队尾(除非它已经在队列中)。
队列中的每一个动作被依次取出执行,动作中的每一个指令也将依次执行。初始化程序(Init)在执行一个动作的各项指令的期间,还需要处理其它操作(比如,设备创建/销毁,属性设置,进程重启)。
一个动作定义的形式如下:
on <trigger>
<command>
<command>
<command>
服务(Services)
服务是初始化程序需要启动的一些程序,初始化程序还有可能会在这些程序退出之后重启它们。Services take 一个服务定义的形式如下:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
选项(Options)
选项将影响控制初始化程序运行服务的时机和方法。可能的选项如下表。
选项 | 说明 |
disabled |
This service will not automatically start with its class. It must be explicitly started by name. |
socket <name> <type> <perm> [ <user> [ <group> ] ]
|
Create a unix domain socket named /dev/socket/<name> and pass its fd to the launched process. Valid <type> values include dgram and stream . user and group default to 0. |
user <username> |
Change to username before exec'ing this service. Currently defaults to root. |
group <groupname> [ <groupname> ]* |
Change to groupname before exec'ing this service. Additional groupnames beyond the first, which is required, are used to set additional groups of the process (withsetgroups() ). Currently defaults to root. |
capability [ <capability> ]+ |
Set linux capability before exec'ing this service |
oneshot |
Do not restart the service when it exits. |
class <name> |
Specify a class name for the service. All services in a named class must start and stop together. A service is considered of class "default" if one is not specified via the class option. |
触发器(Triggers)
触发器是一个字符串,用于匹配特定的事件,这些事件将触发触发器所属动作(Actions)的执行。
触发器 | 说明 |
boot |
This is the first trigger that occurs when init starts (after /init.conf is loaded). |
<name>=<value> |
Triggers of this form occur when the property <name> is set to the specific value<value> . |
device-added-<path> |
Triggers of these forms occur when a device node is added or removed. |
service-exited-<name> |
Triggers of this form occur when the specified service exits. |
指令(Commands)
Command | Description |
exec <path> [ <argument> ]* |
Fork and execute a program (<path> ). This will block until the program completes execution. Try to avoid exec. Unlike the builtin commands, it runs the risk of getting init"stuck". |
export <name> <value> |
Set the environment variable <name> equal to <value> in the global environment (which will be inherited by all processes started after this command is executed). |
ifup <interface> |
Bring the network interface <interface> online. |
import <filename> |
Parse an init config file, extending the current configuration. |
hostname <name> |
Set the host name. |
class_start <serviceclass> |
Start all services of the specified class if they are not already running. |
class_stop <serviceclass> |
Stop all services of the specified class if they are currently running. |
domainname <name> |
Set the domain name. |
insmod <path> |
Install the module at <path> . |
mkdir <path> |
Make a directory at <path> . |
mount <type> <device> <dir> [ <mountoption> ]* |
Attempt to mount the named device at the directory <dir> <device> . This may be of the form mtd@name to specify a mtd block device by name. |
setkey |
- currenlty undefined - |
setprop <name> <value> |
Set system property <name> to <value> . |
setrlimit <resource> <cur> <max> |
Set the rlimit for a resource. |
start <service> |
Start a service running if it is not already running. |
stop <service> |
Stop a service from running if it is currently running. |
symlink <target> <path> |
Create a symbolic link at <path> with the value <target> . |
write <path> <string> [ <string> ]* |
Open the file at <path> and write one or more strings to it with write(2). |
属性(Properties)
初始化程序(Init)可以根据需要修改一些系统的属性。
属性 | 说明 |
init.action |
Equal to the name of the action currently being executed or "" if none. |
init.command |
Equal to the command being executed or "" if none. |
init.svc.<name> |
State of a named service ("stopped", "running", or "restarting"). |
init.rc文件示例
on boot
export PATH /sbin:/system/sbin:/system/bin
export LD_LIBRARY_PATH /system/lib mkdir /dev
mkdir /proc
mkdir /sys mount tmpfs tmpfs /dev
mkdir /dev/pts
mkdir /dev/socket
mount devpts devpts /dev/pts
mount proc proc /proc
mount sysfs sysfs /sys write /proc/cpu/alignment 4 ifup lo hostname localhost
domainname localhost mount yaffs2 mtd@system /system
mount yaffs2 mtd@userdata /data import /system/etc/init.conf class_start default service adbd /sbin/adbd
user adb
group adb service usbd /system/bin/usbd -r
user usbd
group usbd
socket usbd 666 service zygote /system/bin/app_process -Xzygote /system/bin --zygote
socket zygote 666 service runtime /system/bin/runtime
user system
group system on device-added-/dev/compass
start akmd on device-removed-/dev/compass
stop akmd service akmd /sbin/akmd
disabled
user akmd
group akmd
12、同步和互斥
同步和互斥
相交进程之间的关系主要有两种,同步与互斥。所谓互斥,是指散步在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!
总结:
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
linux驱动工程面试必问知识点的更多相关文章
-
Java面试题之HashMap阿里面试必问知识点,你会吗?
面试官Q1:你用过HashMap,你能跟我说说它的数据结构吗? HashMap作为一种容器类型,无论你是否了解过其内部的实现原理,它的大名已经频频出现在各种互联网Java面试题中了.从基本的使用角度来 ...
-
深入理解微服务架构spring的各个知识点(面试必问知识点)
什么是spring spring是一个开源框架,spring为简化企业级开发而生,使用spring可以使简单的java bean 实现以前只有EJG才能实现的功能. Spring是一个轻量级的控制反转 ...
-
一万三千字的HashMap面试必问知识点详解
目录 概论 Hasmap 的继承关系 hashmap 的原理 解决Hash冲突的方法 开放定址法 再哈希法 链地址法 建立公共溢出区 hashmap 最终的形态 Hashmap 的返回值 HashMa ...
-
互联网公司面试必问的Redis题目
Redis是一个非常火的非关系型数据库,火到什么程度呢?只要是一个互联网公司都会使用到.Redis相关的问题可以说是面试必问的,下面我从个人当面试官的经验,总结几个必须要掌握的知识点. 介绍:Redi ...
-
面试必问:JVM类加载机制详细解析
前言 在Java面试中,简历上有写JVM(Java虚拟机)相关的东西,JVM的类加载机制基本是面试必问的知识点. 类的加载和卸载 JVM是虚拟机的一种,它的指令集语言是字节码,字节码构成的文件是cla ...
-
【面试必问】python实例方法、类方法@classmethod、静态方法@staticmethod和属性方法@property区别
[面试必问]python实例方法.类方法@classmethod.静态方法@staticmethod和属性方法@property区别 1.#类方法@classmethod,只能访问类变量,不能访问实例 ...
-
互联网公司面试必问的mysql题目(下)
这是mysql系列的下篇,上篇文章地址我附在文末. 什么是数据库索引?索引有哪几种类型?什么是最左前缀原则?索引算法有哪些?有什么区别? 索引是对数据库表中一列或多列的值进行排序的一种结构.一个非常恰 ...
-
互联网公司面试必问的mysql题目(上)
又到了招聘的旺季,被要求准备些社招.校招的题库.(如果你是应届生,尤其是东北的某大学,绝对福利哦) 介绍:MySQL是一个关系型数据库管理系统,目前属于 Oracle 旗下产品.虽然单机性能比不上or ...
-
一线大厂Java面试必问的2大类Tomcat调优
一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...
随机推荐
-
15天玩转redis —— 第九篇 发布/订阅模式
本系列已经过半了,这一篇我们来看看redis好玩的发布订阅模式,其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果 ...
-
The specified system/compiler is not supported
之前安装了QT的4.5.3版本,现需要用到phonon库,因此卸载后想重新安装4.7版本,但当使用./configure编译时出现The specified system/compiler is no ...
-
SpringMVC4+thymeleaf3的一个简单实例(篇一:基本环境)
首语:用SpringMVC和thymeleaf实现一个简单的应用,包括基本环境搭建,SpringMVC4和thymeleaf3的整合,页面参数的获取,页面参数验证,以及用MySQL保存数据.我会把步骤 ...
-
c - static 变量
static变量和普通的局部变量不同,位于数据区中,在函数的外部初始化. ref: http://www.cnblogs.com/hustcat/archive/2009/06/30/1513755. ...
-
poj3358数论(欧拉定理)
http://poj.org/problem?id=3358 (初始状态为分数形式)小数点进制转换原理:n / m ; n /= gcd( n , m ) ; m/= gcd( n , m ) ; n ...
-
use vue vuex vue-router, not use webpack
vue,vuex,vue-router放在一起能做什么?不用webpack之类的打包工具使用他们是否可行?各位道友在初学vue时是否有这样的困惑.因为现代构建前端项目的一般模式是: 安装webapck ...
-
第一篇:使用Spark探索经典数据集MovieLens
前言 MovieLens数据集包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息. 这个数据集经常用来做推荐系统,机器学习算法的测试数据集.尤其在推荐系统领域,很多著名论文都是基于这 ...
-
iOS js oc相互调用(JavaScriptCore 下)
下来我们使用js调用iOS js调用iOS分两种情况 一,js里面直接调用方法 二,js里面通过对象调用方法 首先我们看第一种,直接调用方法. 其中用到了iOS的block 上代码 -(void)we ...
-
java集合常见面试题
1. Array和ArrayList的区别,什么时候更合适用Array a) Array是数组,可以容纳基本类型和对象,而ArrayList是集合,只能容纳对象 b) Array是 ...
-
android 圆角背景
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...