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> :
|
在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:
/*in Linux 2.6.26.5*/
|
总线的注册和删除
总线的主要注册步骤:
(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:
|
(2)调用bus_register函数注册总线。
|
|
|
总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
|
lddbus的match和uevent方法:
|
对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:
|
总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 <linux/device.h> 如下:
|
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:
|
例如创建一个包含源码版本号简单属性文件方法如下:
|
设备
在最底层, Linux 系统中的每个设备由一个 struct device 代表:
|
设备注册
|
一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:
|
设备属性
sysfs 中的设备入口可有属性,相关的结构是:
|
设备结构的嵌入
device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。
lddbus 驱动创建了它自己的 device 类型,并期望每个设备驱动使用这个类型来注册它们的设备:
|
lddbus 导出的注册和注销接口如下:
|
sculld 驱动添加一个自己的属性到它的设备入口,称为 dev, 仅包含关联的设备号,源码如下:
|
设备驱动程序
设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:
|
在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:
/*in Linux 2.6.26.5*/
|
驱动程序结构的嵌入
对大多数驱动程序核心结构, device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。
以lddbus 子系统为例,它定义了ldd_driver 结构:
|
lddbus总线中相关的驱动注册和注销函数是:
|
在sculld 中创建的 ldd_driver 结构如下:
|
类 子系统
类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况, 类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时, 它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。 类函数和结构与设备模型的其他部分遵循相同的模式,所以真正崭新的概念是很少的。
注意:class_simple 是老接口,在2.6.13中已被删除,这里不再研究。
管理类的接口
类由 struct class 的结构体来定义:
|
|
类设备(在新内核中已被删除)
类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:
|
类接口
类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”, 可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
|
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的级别
6、添加驱动
7、IIC原理,总线框架,设备编写方法,i2c_msg
8、kernel panic
9、USB总线,USB传输种类,urb等
10、android boot 流程
11、android init解析init.rc
12、同步和互斥