学习Linux设备驱动开发的过程中自然会遇到字符设备驱动、平台设备驱动、设备驱动模型和sysfs等相关概念和技术。对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了。要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码。网络上有关这些技术的**不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关联的分析。对于开发者而言,能够熟悉某一点并分享出来已很难得,但对于专注传授技术和经验给学习者而言,横向比较关联各个驱动相关的知识点和纵向剖析Linux整个驱动软件层次是非常有必要的,也非常有意义。
本文依然是从需求的角度去理解以上知识点,存在即是合理,以上技术知识能够存在,即代表其有一定的作用。我们着重去理解每一个技术点的作用,并明确其在驱动开发中的角色。
一、设备驱动
Linux设备驱动分三种,包括字符设备驱动、块设备驱动和网络设备驱动。字符设备只能按字节流先后顺序访问设备内存,不能随机访问。鼠标、触摸屏、LCD等是字符设备的代表。块设备可以随机访问设备内存的任意地址,硬盘、SD卡、NAND FLASH是块设备的代表。网络设备指的是网卡一类使用socket套接字进行通信的设备。本文以字符设备为例讲述相关知识。
二、字符设备驱动
1. 字符设备驱动纵向关系
从< Linux字符设备驱动剖析>可以看出,应用层访问设备驱动非常简单,即是通过open接口来最终获得设备驱动的操作接口集struct file_opertions.而open接口传入的参数是/dev目录下的设备名。而从<Linux 设备文件的创建和mdev>可以知道,设备名对应的设备文件节点inode会存储设备号,而驱动框架中的全局数组cdev_map则维护设备号和file_opertions的关系。即应用层到底层的关系主要是(忽略VFS这一层):
设备名-->设备号-->file_opertions
Open函数返回的局部fd和file_opertions的关系(忽略进程数据结构)如下:
fdàfile(当前进程数据结构成员)-> file_opertions
这样,通过fd即可以获得file_opertions,即可以通过read、write等接口来调用驱动的读操作函数和写操作函数、ioctl函数等。
2. 字符设备驱动的任务
1)字符设备驱动最本质的任务应该是提供file_opertions的各个open、read、write、ioctl等接口的实现。
另外从以上的描述中,为了让应用层能够调用到底层的file_opertions还涉及到以下任务:
2)申请设备号,并将设备号和file_opertions注册(cdev_add接口)到驱动框架中的cdev_map数组。这点应该在字符设备驱动中负责,涉及到其主动向系统报备自己的存在。
3)在/dev目录中创建设备文件,内容包括设备号。这一点是否由字符设备驱动来负责商榷。字符设备驱动位于内核层,如果由其负责这个任务,那么驱动就得知道它要创建的设备名。简单的字符驱动还好,如果是USB等可插拔的设备,驱动怎么知道自己要创建什么设备名呢?有人说可以写明一套规则。确实如此,但如果把这套规则放到应用层,由应用程序开发人员去明确这个规则(mdev正是这样做的),会不会更好?因为是应用程序直接编程访问这个设备名对应的设备驱动的。所以字符设备驱动不应该直接负责设备文件的创建。
3. 谁来创建设备文件
总得有人出来做吧,否则应用层怎么访问啊?
一种方法就是用户在shell中使用mknod命令创建设备文件,同时传入设备名和设备号。这是人工的做法,很不科学。但它是一种演示的方法。
另外一种方法就是依赖设备模型来辅助创建设备文件。这也是设备模型的作用之一。
4. 字符设备驱动编程流程
1)定义struct file_opertions my_fops并实现其中的各个接口,如open、read、write、ioctl等接口。
2)实现驱动的入口函数,如chardev_init
static int __init chardev_init (void){
alloc_chrdev_region(&devno, …);//申请设备号
my_cdev=cdev_alloc();
cdev_init(my_cdev, &my_fops);
cdev_add(my_fops, devno, 1);//注册设备号和file_opertions
}
3)module_init(chardev_init);//宏定义该初始化入口函数。卸载流程不做解释。
4)insmod加载这个module后,可以人工在shell命令行利用mknod创建设备文件。
5)应用层即可以用open来打开设备文件来进行访问了
5. 总结
可以看出,字符设备驱动的核心框架跟设备模型、平台设备驱动没有直接关系,不用他们也一样能够正常工作。
三、设备驱动模型
我们主要谈及设备驱动模型在linux驱动中的作用和角色,有关设备模型的原理和实现我们另文再述。
1. 设备驱动模型的作用
1)设备驱动模型实现uevent机制,调用应用层的medv来自动创建设备文件。这在上面已经论述过。
2)设备驱动模型通过sysfs文件系统向用户层提供设备驱动视图,如下。
上图只是可视化的一种表达,有助于大家去理解设备模型,类似于windows的设备管理程序,在嵌入式linux里面并没有相关应用通过图形的方式来展现这种关系。但是用户可以通过命令窗口利用ls命名逐级访问/sys文件夹来获得各种总线、设备、驱动的信息和关系。可以看出,在/sys顶级目录,有三个关键的子目录,就是设备类、设备和总线。
设备是具体的一个个设备,在/sys/devices/是创建了实际的文件节点。而其他目录,如设备类和总线以下的子目录中出现的设备都是用符号链接指向/sys/devices/目录下的文件。
设备类是对/sys/devices/下的各种设备进行归类,以体现一类设备的公共属性,如鼠标和触摸屏都是属于input设备类。
总线目录是总线、设备、驱动模型的核心目录。因为设备和驱动都是依附在某种总线上的,如USB、PCI和平台总线等。设备和驱动正是依靠总线的管理功能才能找到对方,如设备注册到总线时去寻找驱动,而驱动注册的时候去寻找其能够支持的设备。
最重要的是,如果没有设备模型,那应用层很难知晓驱动和设备的关系,因为字符设备驱动并没有提供这些信息,那对于设备驱动的管理者而言会非常麻烦。
事实上,内核中的总线class、设备device和驱动device_driver都不会将所有的信息暴露给用户层,例如这三个数据结构都有对应的private数据结构,它用于内核对上下级总线设备驱动的链表关系维护。如果暴露给用户层,那容易被用户层修改而使系统混乱。实际上,用户层只关心三者的视图关联,至于他们的关联在底层怎么实现则不需要关心。
3)设备驱动模型提供统一的电源管理机制。很明显,我们在字符设备驱动的file_operations接口中并没有看到电源管理方面的接口。而对于操作系统来说,电源功耗管理必不可少。电源管理其实不应该由应用开发人员来负责,而是应该由系统来负责,例如手机很久没有触摸了,那会进入休眠状态。这种状态的改变应该由系统来完成,而各种设备进入睡眠模式也应该由系统来完成。因此file_operations不提供电源管理的接口给应用程序是合理的。而设备模型作为系统管理的一种机制,由它来提供电源管理是非常合理的。
4)设备驱动模型提供各种对象实例的引用计数,防止对象被应用层误删。设备模型的所有数据结构均是继承kobject而来,而kobject就提供基础的计数功能。
5)设备驱动模型提供多一种方式给应用层,用户和内核可以通过sysfs进行交互,如通过修改/sys目录下设备的文件内容,即可以直接修改设备对应的参数。
总结,设备驱动模型侧重于内核对总线、设备和驱动的管理,并向应用层暴露这些管理的信息,而字符设备驱动则侧重于设备驱动的功能实现。
2. 设备驱动模型的核心接口
设备驱动模型的核心接口
bus_register(struct bus_type *bus) 注册总线
device_add(struct device *dev) 注册设备
driver_register(struct device_driver *drv) 注册驱动
class_create(owner, name) 创建设备类
等等
3.设备驱动模型和字符设备驱动区别
设备驱动模型侧重于内核对总线、设备和驱动的管理,并向应用层暴露这些管理的信息,而字符设备驱动则侧重于设备驱动的功能实现。 |