在Linux设备驱动模型中,字符设备驱动确实是最基本和最常见的一类设备驱动。字符设备是以字节流的方式进行数据读写的设备,通常没有复杂的寻址方式,也不支持随机访问。
字符设备驱动的主要特点包括:
-
字节流访问:字符设备是以字节为单位进行读写的,每次读写操作都是按照字节流进行的。
-
顺序访问:对字符设备的读写操作是有顺序的,通常不支持随机访问。
-
设备文件:在Linux中,每个字符设备都会在/dev目录下有一个对应的设备文件,通过这个设备文件,用户空间程序可以访问设备。
-
文件操作:字符设备驱动通过实现一系列的文件操作接口(open(), read(), write(), close()等)来与用户空间进行交互。
下面正式开始,从第一字符设备驱动开始:
在Linux系统中,字符设备驱动通过创建位于/dev目录下的设备文件来提供对硬件的访问。这些设备文件通常具有特定的命名规则,/dev/xxx,其中xxx是具体的驱动文件名字,它代表了某个特定的字符设备。
应用程序通过打开、读取、写入和关闭这些设备文件来与硬件进行交互。这些操作在应用程序中通常使用标准的文件I/O函数(如open(), read(), write(), close()等)来完成。当应用程序对这些设备文件进行操作时,Linux内核会将这些操作转发给相应的字符设备驱动。
字符设备驱动会实现一系列的文件操作函数,这些函数定义了如何对硬件进行具体的操作。当应用程序调用write()函数向设备文件写入数据时,内核会调用字符设备驱动中实现的write文件操作函数,该函数会负责将数据发送给硬件。同样地,当应用程序调用read()函数从设备文件读取数据时,内核会调用字符设备驱动中实现的read文件操作函数,该函数会从硬件中读取数据并返回给应用程序。
点灯我是最喜欢的了,因为从开始学就是点灯,到最后的结束或者调试都可以用灯来进行判断,所以我特意贴了点灯篇。
Linux驱动开发涉及到对硬件的直接操作,包括配置硬件寄存器,而这些操作通常是通过内存映射来完成的。下面我会对您的描述进行一些补充和解释。
地址映射在Linux驱动开发中的作用
在Linux系统中,内核空间和用户空间是隔离的,每个进程都有自己独立的虚拟地址空间。为了访问物理硬件,如GPIO端口,驱动程序需要在内核空间中进行操作。然而,直接操作物理地址是不安全的,因此Linux使用了虚拟地址来替代物理地址。
MMU(内存管理单元)负责将虚拟地址转换为物理地址。在Linux内核启动时,它会初始化MMU并设置一系列的内存映射,包括将物理内存映射到虚拟地址空间,以及将特定的硬件设备地址映射到内核的虚拟地址空间。
Linux驱动中的地址映射
在编写Linux驱动时,对于需要直接访问的硬件寄存器,驱动程序通常会使用ioremap()函数(或其替代函数,如devm_ioremap_resource())来获取一个指向这些寄存器的虚拟地址。一旦获得了这个虚拟地址,驱动程序就可以像操作普通内存一样来操作这些寄存器了。
例如,如果您要操作一个连接到GPIO端口的LED灯,您可能需要找到该GPIO端口的基地址,并使用ioremap()将其映射到内核的虚拟地址空间。然后,您就可以通过这个虚拟地址来读写GPIO寄存器,从而控制LED灯的开关状态。
注意事项
- 权限:只有内核空间的代码才有权限直接访问硬件。用户空间的程序不能直接访问硬件寄存器,它们需要通过系统调用来请求内核空间的驱动程序来完成这些操作。
- 平台依赖性:不同的处理器和硬件平台可能有不同的内存映射方式和寄存器布局。因此,在编写驱动程序时,需要仔细查阅相关的硬件文档,以确保正确地配置和访问硬件寄存器。
- 资源管理:使用ioremap()获取的虚拟地址在驱动卸载时需要使用iounmap()释放,以避免内存泄漏和其他潜在问题。
这个我也贴上来,因为之前一直不知道含义是什么,看到这里时就突然清楚了。
cdev_del函数:
使用的是基于 cdev
的现代字符设备驱动接口,应该在驱动卸载时调用 cdev_del()
来删除字符设备,并调用 unregister_chrdev_region()
来释放设备号范围。这两个函数合起来的功能相当于旧式接口中的 unregister_chrdev()
函数。这样做可以确保你的驱动在卸载时能够正确地清理其分配的资源。
cdev_del()
cdev_del() 函数用于从内核中删除一个字符设备结构体(struct cdev)。当你使用 cdev_init() 或 cdev_alloc() 初始化或分配一个字符设备结构体,并且使用 cdev_add() 将其添加到系统中后,在驱动卸载时,你需要调用 cdev_del() 来从系统中移除它。
创建和删除类
在Linux中,class是一个用于组织和管理设备节点的结构体,定义在include/linux/device.h头文件中。每个class都对应一个设备类别,例如“tty”对应终端设备,“sound”对应声音设备等。
class_create()
class_create()函数用于创建一个新的class。它的原型通常如下:
- owner:通常是THIS_MODULE宏,表示这个类属于当前加载的模块。
- name:类的名称,通常与设备类型相关。
class_create()返回一个指向新创建的class结构体的指针。如果创建成功,这个指针可以用于后续的设备创建操作。
class_destroy()
当驱动卸载时,需要调用class_destroy()来删除之前创建的类:
调用class_destroy()会释放与类相关的资源,并删除该类。
创建设备节点
创建了类之后,还需要在类下创建具体的设备节点,这样用户空间程序才能通过/dev目录下的设备文件与内核驱动进行交互。
device_create()
device_create()函数用于在指定的类下创建一个新的设备:
- class:设备所属的类。
- parent:设备的父设备,通常为NULL。
- devt:设备的设备号,通常由MKDEV()宏生成。
- drvdata:传递给设备的私有数据,通常为NULL。
- fmt:设备名称的格式字符串,以及后续的变参。
这个函数返回一个指向新创建设备的device结构体的指针。如果创建成功,这个设备会在/dev目录下生成一个对应的设备文件。