嵌入式 Linux 设备驱动程序:了解它们的作用
编者注:嵌入式 Linux 一直是嵌入式系统设计中使用的顶级操作系统之一。随着人们对物联网 (IoT) 的兴趣迅速增长,嵌入式 Linux 服务多种角色的能力将证明在支持物联网应用程序层次结构每一层的各种需求方面至关重要。反过来,工程师掌握嵌入式 Linux 系统的能力将成为实现更复杂系统的快速、可靠开发的关键。在掌握嵌入式 Linux 编程 - 第二版中,作者 Chris Simmonds 带读者详细了解这个重要操作系统的广度和深度,并使用详细的示例来说明每个关键点。
在本书的第 9 章摘录中,作者描述了内核设备驱动程序如何与系统硬件交互,以及开发人员如何编写设备驱动程序并在他们的应用程序中使用它们。以下部分介绍了有关嵌入式 Linux 设备驱动程序的摘录:
• 了解它们的作用
• 在运行时读取驱动程序状态
• 用户空间中的设备驱动
• 编写内核设备驱动
• 发现硬件配置
改编自 掌握嵌入式 Linux 编程 – 第二版,Chris Simmonds。
第 9 章与设备驱动程序的接口
内核设备驱动程序是将底层硬件暴露给系统其余部分的机制。作为嵌入式系统的开发人员,您需要了解这些设备驱动程序如何适应整体架构以及如何从用户空间程序访问它们。您的系统可能会有一些新颖的硬件,您必须想出一种访问它们的方法。在很多情况下,你会发现有设备驱动提供给你,不用编写任何内核代码就可以实现你想要的一切。例如,您可以使用 sysfs 中的文件操作 GPIO 引脚和 LED,并且有一些库可以访问串行总线,包括 SPI(串行外设接口) 和我 2 C(内部集成电路)。
有很多地方可以找到如何编写设备驱动程序,但很少有人告诉您为什么要这样做以及您在这样做时有哪些选择。这就是我想在这里介绍的内容。但是,请记住,这不是一本致力于编写内核设备驱动程序的书,这里提供的信息是为了帮助您导航,但不一定是在那里安家。有很多好书和文章可以帮助您编写设备驱动程序,本章末尾列出了其中的一些。
在本章中,我们将涵盖以下主题:
设备驱动的作用
字符设备
阻止设备
网络设备
在运行时找出驱动程序
找到合适的设备驱动
用户空间中的设备驱动
编写内核设备驱动
发现硬件配置
设备驱动的作用
正如我在第 4 章中提到的,配置和构建内核 ,内核的功能之一就是将计算机系统的众多硬件接口封装起来,并以一致的方式呈现给用户空间的程序。内核具有旨在使编写设备驱动程序变得容易的框架,设备驱动程序是在上面的内核和下面的硬件之间进行调解的一段代码。可以编写设备驱动程序来控制物理设备,例如 UART 或 MMC 控制器,或者它可以表示虚拟设备,例如空设备 ( /dev/null ) 或 ramdisk。一个驱动可以控制多台同类设备。
内核设备驱动程序代码以高特权级别运行,内核的其余部分也是如此。它可以完全访问处理器地址空间和硬件寄存器。它可以处理中断和 DMA 传输。它可以利用复杂的内核基础结构进行同步和内存管理。但是,您应该意识到这有一个缺点。如果有问题的驱动程序出现问题,它可能会真正出错并导致系统崩溃。因此,有一个原则是设备驱动程序应该尽可能简单,只需向做出真正决策的应用程序提供信息。你经常听到这被表达为内核中没有策略 .用户空间有责任设置管理系统整体行为的策略。例如,响应外部事件(例如插入新 USB 设备)加载内核模块是用户空间程序 udev 的责任,而不是内核。内核只是提供了一种加载内核模块的方法。
在 Linux 中,设备驱动程序主要有以下三种类型:
字符 :这是针对具有丰富功能和应用程序代码和驱动程序之间的薄层的无缓冲 I/O。是实现自定义设备驱动程序的首选。
块 :它有一个专为块 I/O 进出大容量存储设备的接口。有一层厚厚的缓冲旨在使磁盘读写尽可能快,这使得它不适用于其他任何事情。
网络 :这类似于块设备,但用于传输和接收网络数据包而不是磁盘块。
还有第四种类型,它在一个伪文件系统中表现为一组文件。例如,您可以通过 /sys/class/gpio 中的一组文件访问 GPIO 驱动程序,我将在本章稍后介绍。让我们首先更详细地了解三种基本设备类型。
字符设备
字符设备在用户空间由一个称为设备节点的特殊文件标识 .该文件名使用与其关联的主要和次要编号映射到设备驱动程序。从广义上讲,主要数字 将设备节点映射到特定的设备驱动程序,以及次要编号 告诉驱动程序正在访问哪个接口。例如,ARM Versatile PB 上第一个串口的设备节点名为 /dev/ttyAMA0 ,其主编号为 204,次要编号为 64。第二个串口的设备节点具有相同的主编号,因为它由相同的设备驱动程序处理,但次要编号为 65。我们可以从此处列出的目录中看到所有四个串行端口的编号:
# ls -l /dev/ttyAMA*crw-rw---- 1 root root 204, 64 Jan 1 1970 /dev/ttyAMA0crw-rw---- 1 root root 204, 65 Jan 1 1970 /dev/ ttyAMA1crw-rw---- 1 根根 204, 66 Jan 1 1970 /dev/ttyAMA2crw-rw---- 1 根根 204, 1970 年 1 月 1 日 67 /dev/ttyAMA3
标准主要和次要编号的列表可以在 Documentation/devices.txt 的内核文档中找到。该列表不会经常更新,也不包括上一段中描述的 ttyAMA 设备。尽管如此,如果您查看 drivers/tty/serial/amba-pl011.c 中的内核源代码,您将看到主编号和次编号的声明位置:
#define SERIAL_AMBA_MAJOR 204 #define SERIAL_AMBA_MINOR 64
当设备有多个实例时,与 ttyAMA 驱动程序一样,形成设备节点名称的约定是采用基本名称 ttyAMA,并在此示例中附加从 0 到 3 的实例编号。
正如我在第 5 章中提到的,构建根文件系统 ,可以通过多种方式创建设备节点:
devtmpfs :设备节点在设备驱动程序使用驱动程序提供的基本名称 (ttyAMA) 注册新设备接口时创建,并且实例编号。
udev 或 mdev(没有 devtmpfs):本质上与 devtmpfs 相同,除了用户空间守护程序必须提取设备名称从 sysfs 并创建节点。我来谈谈sysfs
mknod :如果您使用静态设备节点,则使用 mknod 手动创建它们。
您可能从我上面使用的数字中得出的印象是,主数字和次数字都是 0 到 255 范围内的 8 位数字。实际上,从 Linux 2.6 开始,主数字是 12 位长,这给出了有效数字从1到4095,次要号为20位,从0到1048575。
嵌入式