linux驱动工程面试必问知识点

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 bus_type {
    const char        * name;/*总线类型名称*/
    struct module        * owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/

    struct kset        subsys;/*与该总线相关的子系统*/
    struct kset        drivers;/*总线驱动程序的kset*/
    struct kset        devices;/* 挂在该总线的所有设备的kset*/

    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; /*驱动程序属性*/
    struct bus_attribute drivers_autoprobe_attr;/*驱动自动探测属性*/
    struct bus_attribute drivers_probe_attr;/*驱动探测属性*/

    int        (*match)(struct device * dev, struct device_driver * drv);
    int        (*uevent)(struct device *dev, char **envp,
                 int num_envp, char *buffer, int buffer_size);
    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);
    nt (*resume)(struct device * dev);
/*处理热插拔、电源管理、探测和移除等事件的方法*/
    unsigned int drivers_autoprobe:1;
};

在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:

 

/*in Linux 2.6.26.5*/

struct bus_type {
    const char        *name;
    struct bus_attribute    *bus_attrs;
    struct device_attribute    *dev_attrs;
    struct driver_attribute    *drv_attrs;

    int (*match)(struct device *devstruct device_driver *drv);
    int (*uevent)(struct device *devstruct 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);

    struct bus_type_private *p;
};

struct bus_type_private {
    struct kset subsys;
    struct kset *drivers_kset;
    struct kset *devices_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;
};

总线的注册和删除

总线的主要注册步骤:

(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:

struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match,
    .uevent = ldd_uevent,
};

(2)调用bus_register函数注册总线。

int bus_register(struct bus_type * bus)

调用可能失败, 所以必须始终检查返回值。若成功,新的总线子系统将被添加进系统,并可在 sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。
例如:

ret = bus_register(&ldd_bus_type);
if (ret)
 return ret;

  
当必须从系统中删除一个总线时, 调用:

void bus_unregister(struct bus_type *bus);

总线方法

在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:

int (*match)(struct device * dev, struct device_driver * drv);
/*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/

int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
/*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/

lddbus的match和uevent方法:

static int ldd_match(struct device *dev, struct device_driver *driver)
{
 return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}/*仅简单比较驱动和设备的名字*/
/*当涉及实际硬件时, match 函数常常对设备提供的硬件 ID 和驱动所支持的 ID 做比较*/

static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
 envp[0] = buffer;
 if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",
 Version) >= buffer_size)
 return -ENOMEM;
 envp[1] = NULL;
 return 0;
}/*在环境变量中加入 lddbus 源码的当前版本号*/

对设备和驱动的迭代

若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));

/*这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/

总线属性

几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 <linux/device.h> 如下:

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);
};


可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。

内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:

BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构, 将 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/

/*总线的属性必须显式调用 bus_create_file 来创建:*/
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);

/*删除总线的属性调用:*/
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

例如创建一个包含源码版本号简单属性文件方法如下:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
 return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

/*在模块加载时创建属性文件:*/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
 printk(KERN_NOTICE "Unable to create version attribute\n");

/*这个调用创建一个包含 lddbus 代码的版本号的属性文件(/sys/bus/ldd/version)*/


设备

在最底层, Linux 系统中的每个设备由一个 struct 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;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */

    struct kobject kobj;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/
    char    bus_id[BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/
    struct device_type    *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;
    struct device_attribute uevent_attr;
    struct device_attribute *devt_attr;

    struct semaphore    sem;  /* semaphore to synchronize calls to its driver. */
    struct bus_type    * bus;     /*标识该设备连接在何种类型的总线上*/
    struct device_driver *driver;    /*管理该设备的驱动程序*/
    void        *driver_data;    /*该设备驱动使用的私有数据成员*/
    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);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/
};
/*在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员*/

设备注册

设备的注册和注销函数为:

int device_register(struct device *dev);
void device_unregister(struct device *dev);

一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:

static void ldd_bus_release(struct device *dev)
{
 printk(KERN_DEBUG "lddbus release\n");
}

struct device ldd_bus = {
 .bus_id = "ldd0",
 .release = ldd_bus_release

}; /*这是顶层总线,parent 和 bus 成员为 NULL*/

/*作为第一个(并且唯一)总线, 它的名字为 ldd0,这个总线设备的注册代码如下:*/
ret = device_register(&ldd_bus);
if (ret)
 printk(KERN_NOTICE "Unable to register ldd0\n");
/*一旦调用完成, 新总线会在 sysfs 中 /sys/devices 下显示,任何挂到这个总线的设备会在 /sys/devices/ldd0 下显示*/

设备属性

sysfs 中的设备入口可有属性,相关的结构是:

/* interface for exporting device attributes 这个结构体和《LDD3》中的不同,已经被更新过了,请特别注意!*/
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);
};

/*设备属性结构可在编译时建立, 使用以下宏:*/
DEVICE_ATTR(_name,_mode,_show,_store);
/*这个宏声明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性

/*属性文件的实际处理使用以下函数:*/
 int device_create_file(struct device *device,    struct device_attribute * entry);
 void device_remove_file(struct device * dev, struct device_attribute * attr);

设备结构的嵌入

device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。

lddbus 驱动创建了它自己的 device 类型,并期望每个设备驱动使用这个类型来注册它们的设备:

struct ldd_device {
 char *name;
 struct ldd_driver *driver;
 struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);

lddbus 导出的注册和注销接口如下:

/*
 * LDD devices.
 */

/*
 * For now, no references to LDDbus devices go out which are not
 * tracked via the module reference count, so we use a no-op
 * release function.
 */
static void ldd_dev_release(struct device *dev)
{ }

int register_ldd_device(struct ldd_device *ldddev)
{
    ldddev->dev.bus = &ldd_bus_type;
    ldddev->dev.parent = &ldd_bus;
    ldddev->dev.release = ldd_dev_release;
    strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
    return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);

void unregister_ldd_device(struct ldd_device *ldddev)
{
    device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);

 sculld 驱动添加一个自己的属性到它的设备入口,称为 dev, 仅包含关联的设备号,源码如下:

static ssize_t sculld_show_dev(struct device *ddev,struct device_attribute *attr, char *buf)
{
 struct sculld_dev *dev = ddev->driver_data;
 return print_dev_t(buf, dev->cdev.dev);
}

static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

/*接着, 在初始化时间, 设备被注册, 并且 dev 属性通过下面的函数被创建:*/
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
 sprintf(dev->devname, "sculld%d", index);
 dev->ldev.name = dev->devname;
 dev->ldev.driver = &sculld_driver;
 dev->ldev.dev.driver_data = dev;
 register_ldd_device(&dev->ldev);
 if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
    printk( "Unable to create dev attribute ! \n"); 
} /*注意:程序使用 driver_data 成员来存储指向我们自己的内部的设备结构的指针。请检查
device_create_file的返回值,否则编译时会有警告。*/


设备驱动程序

设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:

/*定义在<linux/device.h>*/
struct device_driver {
    const char        * name;/*驱动程序的名字( 在 sysfs 中出现 )*/
    struct bus_type        * bus;/*驱动程序所操作的总线类型*/

    struct kobject        kobj;/*内嵌的kobject对象*/
    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 结构的函数是:*/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

/*driver的属性结构在:*/
struct driver_attribute {
 struct attribute attr;
 ssize_t (*show)(struct device_driver *drv, char *buf);
 ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
};
DRIVER_ATTR(_name,_mode,_show,_store)

/*属性文件创建的方法:*/
int driver_create_file(struct device_driver * drv, struct driver_attribute * attr);
void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr);

/*bus_type 结构含有一个成员( drv_attrs ) 指向一组为属于该总线的所有设备创建的默认属性*/

在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:

/*in Linux 2.6.26.5*/

struct device_driver {
    const char        *name;
    struct bus_type        *bus;

    struct module        *owner;
    const char         *mod_name;    /* used for built-in modules */

    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);
    struct attribute_group **groups;

    struct driver_private *p;
};


struct driver_private {
    struct kobject kobj;
    struct klist klist_devices;
    struct klist_node knode_bus;
    struct module_kobject *mkobj;
    struct device_driver *driver;
};
#define to_driver(obj) container_of(objstruct driver_private, kobj)

 

驱动程序结构的嵌入

对大多数驱动程序核心结构, device_driver 结构通常被嵌入到一个更高层的、总线相关的结构中。

以lddbus 子系统为例,它定义了ldd_driver 结构:

struct ldd_driver {
 char *version;
 struct module *module;
 struct device_driver driver;
 struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);

lddbus总线中相关的驱动注册和注销函数是:

/*
 * Crude driver interface.
 */
static ssize_t show_version(struct device_driver *driver, char *buf)
{
    struct ldd_driver *ldriver = to_ldd_driver(driver);
    sprintf(buf, "%s\n", ldriver->version);
    return strlen(buf);
}

int register_ldd_driver(struct ldd_driver *driver)
{
 int ret;
 driver->driver.bus = &ldd_bus_type;
 ret = driver_register(&driver->driver);/*注册底层的 device_driver 结构到核心*/
 if (ret)
 return ret;
 driver->version_attr.attr.name = "version";/* driver_attribute 结构必须手工填充*/
 driver->version_attr.attr.owner = driver->module;/*注意:设定 version 属性的拥有者为驱动模块, 不是 lddbus 模块!因为 show_version 函数是使用驱动模块所创建的 ldd_driver 结构,若 ldd_driver 结构在一个用户空间进程试图读取版本号时已经注销,就会出错*/
 driver->version_attr.attr.mode = S_IRUGO;
 driver->version_attr.show = show_version;
 driver->version_attr.store = NULL;
 return driver_create_file(&driver->driver, &driver->version_attr);/*建立版本属性,因为这个属性在运行时被创建,所以不能使用 DRIVER_ATTR 宏*/
}

void unregister_ldd_driver(struct ldd_driver *driver)
{
    driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);

在sculld 中创建的 ldd_driver 结构如下:

/* Device model stuff */
static struct ldd_driver sculld_driver = {
    .version = "$Revision: 1.21 $",
    .module = THIS_MODULE,
    .driver = {
        .name = "sculld",
    },
};/*只要一个简单的 register_ldd_driver 调用就可添加它到系统中。一旦完成初始化, 驱动信息可在 sysfs 中显示*/


类 子系统

类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。

几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况, 类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时, 它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。

为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。 类函数和结构与设备模型的其他部分遵循相同的模式,所以真正崭新的概念是很少的。

注意:class_simple 是老接口,在2.6.13中已被删除,这里不再研究。

管理类的接口

类由 struct class 的结构体来定义:

/*
 * device classes
 */
struct class {
    const char        * name;/*每个类需要一个唯一的名字, 它将显示在 /sys/class 中*/
    struct module        * owner;

    struct kset        subsys;
    struct list_head    children;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem;    /* locks both the children and interfaces lists */

    struct class_attribute        * class_attrs;/* 指向类属性的指针(以NULL结尾) */
    struct class_device_attribute    * class_dev_attrs;/* 指向类中每个设备的一组默认属性的指针 */
    struct device_attribute        * dev_attrs;

    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);/* 类热插拔产生时添加环境变量的函数 */
    int    (*dev_uevent)(struct device *dev, char **envp, int num_envp,
                char *buffer, int buffer_size);/* 类中的设备热插拔时添加环境变量的函数 */

    void    (*release)(struct class_device *dev);/* 把设备从类中删除的函数 */
    void    (*class_release)(struct class *class);/* 删除类本身的函数 */
    void    (*dev_release)(struct device *dev);

    int    (*suspend)(struct device *, pm_message_t state);
    int    (*resume)(struct device *);
};


/*类注册函数:*/
int class_register(struct class *cls);
void class_unregister(struct class *cls);

/*类属性的接口:*/
struct class_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class *cls, char *buf);
 ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};
CLASS_ATTR(_name,_mode,_show,_store);
int class_create_file(struct class *cls, const struct class_attribute *attr);void class_remove_file(struct class *cls, const struct class_attribute *attr);


在更新的内核里,这个结构体变得简洁了,删除了一些成员:

/*in Linux 2.6.26.5*/

/*
 * device classes
 */
struct class {
    const char        *name;
    struct module        *owner;

    struct kset        subsys;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem/* locks children, devices, interfaces */
    struct class_attribute        *class_attrs;
    struct device_attribute        *dev_attrs;

    int (*dev_uevent)(struct device *devstruct kobj_uevent_env *env);

    void (*class_release)(struct class *class);
    void (*dev_release)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
};

类设备(在新内核中已被删除)

类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:

struct class_device {
    struct list_head    node;/*for internal use by the driver core only*/
    struct kobject        kobj;/*for internal use by the driver core only*/
    struct class        * class;    /* 指向该设备所属的类,必须*/
    dev_t            devt;        /* dev_t, creates the sysfs "dev" ,for internal use by the driver core only*/
    struct class_device_attribute *devt_attr;/*for internal use by the driver core only*/
    struct class_device_attribute uevent_attr;
    struct device        * dev;        /* 指向此设备相关的 device 结构体,可选。若不为NULL,应是一个从类入口到/sys/devices 下相应入口的符号连接,以便用户空间查找设备入口*/
    void            * class_data;    /* 私有数据指针 */
    struct class_device    *parent;    /* parent of this child device, if there is one */
    struct attribute_group ** groups;    /* optional groups */

    void    (*release)(struct class_device *dev);
    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);
    char    class_id[BUS_ID_SIZE];    /* 此类中的唯一的名字 */
};

/*类设备注册函数:*/
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);

/*重命名一个已经注册的类设备入口:*/
int class_device_rename(struct class_device *cd, char *new_name);

/*类设备入口属性:*/
struct class_device_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class_device *cls, char *buf);
 ssize_t (*store)(struct class_device *cls, const char *buf,
 size_t count);
};

CLASS_DEVICE_ATTR(_name, _mode, _show, _store);

/*创建和删除除struct class中设备默认属性外的属性*/
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);

类接口

类子系统有一个 Linux 设备模型的其他部分找不到的附加概念,称为“接口”, 可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:

struct class_interface {
    struct list_head    node;
    struct class        *class;/* 指向该接口所属的类*/

    int (*add(struct class_device *, struct class_interface *);

 /*当一个类设备被加入到在 class_interface 结构中指定的类时, 将调用接口的 add 函数,进行一些设备需要的额外设置,通常是添加更多属性或其他的一些工作*/
    void (*remove)    (struct class_device *, struct class_interface *);/*一个接口的功能是简单明了的. 当设备从类中删除, 将调用remove 方法来进行必要的清理*/
    int (*add_dev)     (struct device *, struct class_interface *);
    void (*remove_dev(struct device *, struct class_interface *);
};

/*注册或注销接口的函数:*/
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
/*一个类可注册多个接口*/


 

Linux设备驱动模型之platform总线

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包括通常意义的总线如usbpci,也包括虚拟的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是总线的名字,每个总线下都有自己的子系统,其中包含2ksetdeviecedriver,分别代表已知总线的驱动和插入总线的设备

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 *);

 

在说明下bussysfs里面的结构,刚才已经讲过,bus_type中有2kset结构对应于devicedriver,也就是说每个bus下面都会有devicedriver2个文件夹。

 

首先在总线上注册的驱动会得到一个文件夹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来跟踪所用的特定类型设备,platformpcii2c等,kset起到连接作用将设备模型和sysfs联系在一起。每个kset自身都包含一个kobject,这个kobject将作为很多其他的kobject的父类,从sys上看,某个kobject的父类是某个目录,那么它就是那个目录的子目录,parent指针可以代表目录层次,这样典型的设备模型层次就建立起来了,从面向对象的观点看,kset是顶层的容器类,kset继承他自己的kobject,并且可以当做kobject来处理

 

linux设备模型深探(2) - 784192422 - 嵌入式

如图:kset把它的子类kobject放在链表里面,kset子类链表里面那些kobjectkset指针指向上面的ksetparent指向父类。

 

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、同步和互斥

猜你喜欢

转载自www.cnblogs.com/alantu2018/p/8994674.html