信盈达—您身边的嵌入式&人工智能专家
全国免费咨询热线:400-8788-909

Linux设备驱动学习(4)-字符设备驱动

时间:2018-12-26 00:00:00 来源:信盈达 作者:信盈达

本篇文章记录的是我阅读《Essential Linux Device Drivers》-字符设备驱动的阅读笔记和思考纪录。

顺序存取设备数据。字符设备驱动驱动程序能从打印机、鼠标、看门狗、磁带、内存、实时时钟等几类设备获取原始数据,但它不适合管理硬盘、软盘和光盘等可随机访问的块设备中的数据。

从程序结构的角度看,字符设备驱动程序包括如下内容:

(2)入口函数集,如open()、read(),这些函数对应相应的I/O系统调用,由用户程序通过对应的/dev、节点调用。

(3)中断例程、底半部例程、定时器处理例程、内核辅助线程以及其他组成部分。

从数据流的角度看,包括如下关键的数据结构:

(3)struct file_operations

驱动程序初始化,init()函数是注册机制的基础。它负责完成如下工作:

(1)申请分配主设备号,alloc_chrdev_region();

(2)为特定设备相关的数据结构分配内存,file_operation

(3)将入口函数(open()、read()等)与字符驱动程序的cdev抽象相关联

(4)将主设备号与驱动程序的cdev相关联,cdev_init(),cdev_add()

(5)在/dev和/sys下创建节点,class_create(),device_create(),(这两个函数用于自动创建设备结点)

打开与释放,当应用程序打开设备节点时,内核调用相应驱动程序的open()函数,关闭时,内核调用release()函数。

数据交换,read()和write()负责在用户空间和设备之间交换数据的主要驱动函数。但是,不能从内核中直接访问用户空间的缓冲区,反之亦然。将数据复制到用户空间,调用copy_to_user()。调用copy_from_user()完成相反的工作。由于这两个函数可能会睡眠,所以在调用这两个函数的时候不能持有自旋锁。

如果一个字符驱动程序的write()成功返回,就表示驱动程序已经完成了将数据传送下去的任务。但这并不能保证数据已经成功地写到了设备中。可以调用fsync()函数,确保数据从驱动程序缓冲区中排出,并且写到设备。

如果用户程序有数据存储在多个缓冲区中并需要发送至设备,可以使用向量驱动函数aio_read()/aio_write()。

另一个数据访问函数是mmap(),他将设备内存和用户的虚拟内存关联在一起。

宏likely()和unlikely()负责将相关条件为真/假的可能性报告给GCC。GCC根据这一信息决定要执行的代码分支。

查找,内核使用内部指针跟踪当前文件访问的位置。应用程序使用lseek()系统调用去申请内部文件指针的重定位。字符驱动程序相对应的是llseek()函数。

控制,常见的字符驱动程序函数被称作I/O控制(ioctl)。

两个能够感知数据是否可获得的字符驱动程序方法:poll()和fasync()。前者是同步的,后者是异步的。

轮询,poll()驱动程序方法是select()系统调用的支柱。

fasync,fcntl(F_SETFL)调用导致fasync()驱动程序方法的调用。fasync()负责从接收SIGIO信号的进程列表里添加或删除条目。最后,fasync()利用内核库函数提供的服务调用了fasync_helper()。

字符驱动程序调用kill_fasync()发送SIGIO给注册的进程。为了通知一个读事件,将POLLIN作为kill_fasync()的参数。相应的写事件传递的参数是POLLOUT。

drivers/parport/目录包括IEEE1284并行端口通信的具体实现代码(称为parport)。parport有一个架构无关的模块和一个架构相关的模块。这两个模块为以并行端口为接口的设备驱动程序提供可编程接口。

新的设备模型将驱动程序和设备区分开来。调用parport_register_device()注册设备。

还可以使用sysfs控制并行端口。它使用了kobject,用于代表“控制”抽象。

内核中对RTC的支持分为两层:(1)硬件无关的顶层字符设备驱动程序,用于实现内核的RTC API;(2)硬件相关的底层驱动程序,用于和底层的总线通信。底层的RTC驱动程序由总线决定。

内核有一个专门的RTC子系统,提供了顶层的字符设备驱动程序,并给出了用于顶层和底层RTC驱动程序进行捆绑的核心基础结构。分散在不同的总线有关的目录下的底层RTC驱动程序通过此子系统统一在drivers/rtc/下。

RTC子系统使系统可以拥有不只一个RTC。

为了使能RTC子系统,在内核配置过程中需要选中CONFIG_RTC_CLASS配置选项。

有几个常用的内核工具没有和任何物理硬件相连接,它们被灵巧地实现伪字符设备。null设备、zero设备和内核随机数产生器被当作虚拟设备,并使用伪字符设备驱动程序来访问。

/dev/null字符设备接收你不想显示在屏幕上的数据。

/dev/zero驱动程序的read()方法中获取一串0。

/dev/random和/dev/urandom用于产生随机数,从/dev/random读取的随机数随机性高。

/dev/mem和/dev/kmem是典型的伪字符设备,它们提供了查看系统内存的工具。

上述几种字符设备拥有不同的设备号,但拥有静态分配的相同的主设备号1。还有其他的伪驱动程序属于同一个主设备号系列:其中/dev/full模拟一个总是处于满的设备,/dev/port查看系统的I/O端口。

混杂驱动程序是那些简单的字符设备驱动程序,它们拥有一些相同的特性。内核将这些共同性抽象至一个API中(具体实现见代码drivers/char/misc.c),这简化了这些驱动程序初始化的方式。

所有的混杂设备被分配一个主设备号10,但每个设备可选择一个单独的次设备号。

混杂驱动程序只需要调用misc_register()即可。每个混杂驱动程序自动出现在/sys/class/misc/文件中,而不必驱动程序编写者再编写了。

在drivers/char/目录下运行grep misc_register()命令可找到内核中其他的混杂设备。

(1)open()调用可能由于几个原因而失败。

(2)成功运行的read()和write()返回的字节数可能是1至请求的字节数之间的任意值,因此应用程序必须能处理这些情况。

(3)即使1字节的数据读或写就绪,select()也会返回成功。

(4)很多字符驱动程序方法是可选的,并不是所有的方法都提供。

另外,字符驱动程序不仅在drivers/char/目录下。下面是一些“超级”字符驱动程序:

(1)串行驱动程序,放在drivers/serial/目录下。

(2)输入驱动程序,放在drivers/input/目录下。

(3)帧缓存区(/dev/fb/*)提供对显存的访问,/dev/mem提供对系统内存的访问途径。

(4)一些设备类支持少量采用字符接口的硬件。

(5)一些子系统提供额外的字符接口,以向用户空间提供原始的设备模型。例如MTD子系统

(6)一些内核层提供钩子,通过导出相应的字符接口实现用户空间的设备驱动程序。

在drivers/目录下的register_chrdev上运行grep-r可了解内核中字符驱动程序的大致情况。