本章,我们将学习字符设备使用、字符设备驱动相关的概念,理解字符设备驱动程序的基本框架,并从源码上分析字符设备驱动实现和管理等。 主要分为下面五部分:
linux是文件型系统,所有硬件都会在对应的目录(/dev)下面用相应的文件表示。 在windows系统中,设备大家很好理解,像硬盘,磁盘指的是实实在在硬件。 而在文件系统的linux下面,都有对于文件与这些设备关联的,访问这些文件就可以访问实际硬件。 像访问文件那样去操作硬件设备,一切都会简单很多,不需要再调用以前com,prt等接口了。 直接读文件,写文件就可以向设备发送、接收数据。 按照读写存储数据方式,我们可以把设备分为以下几种:字符设备、块设备和网络设备。
字符设备:指应用程序按字节/字符来读写数据的设备。 这些设备节点通常为传真、虚拟终端和串口调制解调器、键盘之类设备提供流通信服务, 它通常不支持随机存取数据。字符设备在实现时,大多不使用缓存器。系统直接从设备读取/写入每一个字符。 例如,键盘这种设备提供的就是一个数据流,当你敲入“cnblogs”这个字 符串时, 键盘驱动程序会按照和输入完全相同的顺序返回这个由七个字符组成的数据流。它们是顺序的,先返回c,最后是s。
块设备:通常支持随机存取和寻址,并使用缓存器。 操作系统为输入输出分配了缓存以存储一块数据。当程序向设备发送了读取或者写入数据的请求时, 系统把数据中的每一个字符存储在适当的缓存中。当缓存被填满时,会采取适当的操作(把数据传走), 而后系统清空缓存。它与字符设备不同之处就是,是否支持随机存储。字符型是流形式,逐一存储。 典型的块设备有硬盘、SD卡、闪存等,应用程序可以寻址磁盘上的任何位置,并由此读取数据。 此外,数据的读写只能以块的倍数进行。
网络设备:是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。
Linux内核中处处体现面向对象的设计思想,为了统一形形**的设备,Linux系统将设备分别抽象为struct cdev, struct block_device,struct net_devce三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作, 并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作
字符设备驱动程序适合于大多数简单的硬件设备,而且比起块设备或网络驱动更加容易理解, 因此我们选择从字符设备开始,从最初的模仿,到慢慢熟悉,最终成长为驱动界的高手。
Linux内核中将字符设备抽象成一个具体的数据结构(struct cdev),我们可以理解为字符设备对象, cdev记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations), 在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点)绑定对象的cdev, 当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。
C语言中没有面向对象语言的继承的语法,但是我们可以通过结构体的包含来实现继承,这种抽象提取了设备的共性, 为上层提供了统一接口,使得管理和操作设备变得很容易。
在硬件层,我们可以通过查看硬件的原理图、芯片的数据手册,确定底层需要配置的寄存器,这类似于裸机开发, 将对底层寄存器的配置,读写操作放在文件操作接口里面,也就是实现file_operations结构体; 在驱动层,我们将文件操作接口注册到内核,内核通过内部散列表来登记记录主次设备号; 在文件系统层,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件的文件操作接口来设置底层寄存器。
实际上,在Linux上写驱动程序,都是做一些“填空题”。因为Linux给我们提供了一个基本的框架, 我们只需要按照这个框架来写驱动,内核就能很好的接收并且按我们所要求的那样工作。有句成语工欲善其事,必先利其器, 在理解这个框架之前我们得花点时间来学习字符设备驱动相关概念及数据结构。
在linux中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。 cdev结构体被内核用来记录设备号,而在使用设备时,我们通常会打开设备节点,通过设备节点的inode结构体、 file结构体最终找到file_operations结构体,并从file_operations结构体中得到操作设备的具体方法。
对于字符的访问是通过文件系统的名称进行的,这些名称被称为特殊文件、设备文件,或者简单称为文件系统树的节点, Linux根目录下有/dev这个文件夹,专门用来存放设备中的驱动程序,我们可以使用ls -l 以列表的形式列出系统中的所有设备。 其中,每一行表示一个设备,每一行的第一个字符表示设备的类型。
文件类型的字符及其意义如下:
如下图:ashmem 是一个字符设备c, 它的主设备号是10,次设备号是124;initctl是一个符号链接,随后的权限字符 表示该链接的所有者、同组用户和其他用户均有读(r)、写(w)和执行(x)的权限;loop0 是一个块设备,它的主设备号是7,次设备号为0,同时可以看到loop0-loop3共用一个主设备号,次设备号由0开始递增。

一般来说,主设备号指向设备的驱动程序,次设备号指向某个具体的设备。如上图,I2C-0,I2C-1属于不同设备但是共用一套驱动程序
在内核中,dev_t用来表示设备编号,dev_t是一个32位的数,其中,高12位表示主设备号,低20位表示次设备号。 也就是理论上主设备号取值范围:0-2^12,次设备号0-2^20。 实际上在内核源码中__register_chrdev_region(…)函数中,major被限定在0-CHRDEV_MAJOR_MAX,CHRDEV_MAJOR_MAX是一个宏,值是512。 在kdev_t中,设备编号通过移位操作最终得到主/次设备号码,同样主/次设备号也可以通过位运算变成dev_t类型的设备编号, 具体实现参看下面代码MAJOR(dev)、MINOR(dev)和MKDEV(ma,mi)。
dev_t定义 (内核源码/include/types.h)
设备号相关宏 (内核源码/include/linux/kdev_t.h)
完整版本查看:https://docs.armsom.org/zh/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/198131.html