内核设备驱动开发是复杂操作系统最重要的部分之一,这就是 Linux。对于在工业、家庭或医疗应用等真实环境中使用计算机作为监控或管理机器的开发人员来说,设备驱动非常重要。事实上,即使现在到处都广泛支持 Linux,每天都在创建新的外围设备,这些设备需要驱动才能在 GNU/Linux 机器上高效使用。
这本书将介绍一个完整的字符驱动(通常称为字符驱动)的实现,通过介绍所有必要的技术在内核和用户空间之间交换数据,实现与外围设备中断的进程同步,访问映射到(内部或外部)设备的输入/输出内存,并有效地管理内核中的时间。
本书介绍的所有代码都与 Linux 4.18+版本兼容(也就是说,与最新的 5.x 内核兼容)。该代码可以在 Marvell ESPRESSObin 上进行测试,该设备具有板载 ARM 64 位 CPU,但也可以使用任何其他类似的 GNU/Linux 嵌入式设备。通过这种方式,读者可以验证他们所阅读的内容是否被正确理解。
如果你想了解如何在 Linux 机器上实现一个完整的角色驱动,或者了解几种内核机制是如何工作的(比如工作队列、完成和内核定时器等等),以便更好地理解通用驱动是如何工作的,那么这本书就是为你准备的。
如果你需要知道如何编写一个定制的内核模块,如何向它传递参数,或者如何读取和更好地管理内核消息,甚至如何向内核源代码中添加定制代码,那么这本书就是为你而写的。
如果你需要更好地理解一个设备树,如何修改它,甚至如何编写一个新的设备树来满足你的需求,并学习如何管理你的新设备驱动,那么你也会从这本书中受益。
第一章、安装开发系统,介绍了如何安装基于 Ubuntu 18.04.1 LTS 的完整开发系统,以及基于 Marvell ESPRESSObin 板的完整测试系统。本章还将介绍如何使用串行控制台以及如何从头开始重新编译内核,并将教您一些执行交叉编译和软件仿真的技巧。
第 2 章、内核内部一瞥,讨论如何创建自定义内核模块,以及如何读取和管理内核消息。这两种技能对于帮助开发人员理解内核内部发生的事情都非常有用。
第三章使用字符驱动,研究如何实现一个真正简单的字符驱动,以及如何在它和用户空间之间交换数据。本章最后提出了一些例子来强调一切都是针对设备驱动的文件抽象。
第四章使用设备树,呈现设备树。读者将学习如何阅读和理解它,如何编写自定义设备树,然后如何编译它,以便获得可以传递给内核的二进制形式。本章以下载固件(在外围设备中)和如何使用引脚多路复用工具配置中央处理器引脚的一节结束。使用 Armada 3720、i.Mx 7Dual 和 SAMA5D3 处理器提供了示例。
第 5 章、管理中断和并发,介绍如何在 Linux 内核中管理中断和并发。它展示了如何安装中断处理程序,如何将作业推迟到以后,以及如何管理内核定时器。在这一章的最后,读者将学习如何等待一个事件(例如等待一些数据被读取)以及如何保护他们的数据免受竞争条件的影响。
第 6 章、杂项内核内部,讨论了如何在内核内部动态分配内存,以及如何使用几个对日常编程操作有用的助手函数(如字符串操作、列表和哈希表操作)。本章还将介绍如何进行输入/输出内存访问,以及如何安全地在内核中花费时间来创建定义明确的繁忙循环延迟。
第 7 章、高级字符驱动操作,介绍了字符驱动可用的所有高级操作:ioctl()
、mmap()
、lseek()
、poll()
/ select()
系统调用实现,以及通过SIGIO
信号的异步输入/输出。
附录 A 、附加信息:使用字符驱动,这包含第 3 章的附加信息。
附录 B 、附加信息:使用设备树,这包含第 4 章的附加信息。
附录 C 、附加信息:管理中断和并发,这包含第 5 章的附加信息。
附录 D 、附加信息:其他内核内部组件,包含第 6 章的附加信息。
附录 E 、附加信息:高级字符驱动操作,包含第 7 章的附加信息。
- 你应该对非图形文本编辑器有一点了解,比如
vi
、emacs
或者nano
。你不能将液晶显示器、键盘和鼠标直接连接到嵌入式工具包来对文本文件进行小的修改,所以你应该对这些工具有工作知识来远程进行这样的修改。 - 你应该知道如何管理一个 Ubuntu 系统,或者至少是一个通用的基于 GNU/Linux 的系统。我的主机运行在 Ubuntu 18.04.1 LTS 上,但是你也可以使用一个更新的 Ubuntu LTS 版本,或者一个基于 Debian 的系统,只需要做一些修改。您也可以使用另一个 GNU/Linux 发行版,但是这需要您付出一点努力,主要是关于交叉编译工具、库依赖项和包管理的安装。 国外的系统,比如 Windows、macOS 等,不在本书的涵盖范围之内,因为你不应该用低技术的系统去开发高技术系统的代码!
- C 编程语言的工作知识,C 编译器如何工作,以及如何管理 makefile 都是强制性要求。
你可以从你在www.packt.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packt.com/support并注册将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
- 登录或注册www.packt.com。
- 选择“支持”选项卡。
- 点击代码下载和勘误表。
- 在搜索框中输入图书的名称,并按照屏幕指示进行操作。
下载文件后,请确保使用最新版本的解压缩文件夹:
- 视窗系统的 WinRAR/7-Zip
- zipeg/izp/un ARX for MAC
- 适用于 Linux 的 7-Zip/PeaZip
这本书的代码包托管在 GitHub 上https://GitHub . com/giometti/Linux _ device _ driver _ development _ cook book。如果代码有更新,它将在现有的 GitHub 存储库中更新。
这本书的代码包也托管在 GitHub 上https://GitHub . com/PacktPublishing/Linux-设备-驱动-开发-Cookbook 。如果代码有更新,它将在现有的 GitHub 存储库中更新。
我们还有来自丰富的图书和视频目录的其他代码包,可在**【https://github.com/PacktPublishing/】**获得。看看他们!
我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。可以在这里下载:https://www . packtpub . com/sites/default/files/downloads/9781838558802 _ color images . pdf。
本书通篇使用了许多文本约定。
文本文件夹名称、文件名、文件扩展名、路径名、伪 URL 和用户输入中的代码如下所示:“要获取前面的内核消息,我们可以同时使用dmesg
和tail -f /var/log/kern.log
命令。”
代码块设置如下:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
您应该注意到,本书中的大多数代码都有 4 个空格的缩进,而您可以在 GitHub 或 Packt 网站上随本书提供的文件中找到的示例代码使用了 8 个空格的缩进。因此,前面的代码如下所示:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
显然,它们在实践中是完全等价的!
本书中使用的嵌入式工具包的任何命令行输入或输出如下所示:
# make CFLAGS="-Wall -O2" helloworld
cc -Wall -O2 helloworld.c -o helloworld
命令以粗体显示,而它们的输出是普通文本。您还应该注意到,由于空间限制,提示字符串已被删除;事实上,在您的终端上,完整的提示应该如下所示:
root@espressobin:~# make CFLAGS="-Wall -O2" helloworld
cc -Wall -O2 helloworld.c -o helloworld
还要注意,由于书中的空间限制,您可能会遇到非常长的命令行,如下所示:
$ make CFLAGS="-Wall -O2" \
CC=aarch64-linux-gnu-gcc \
chrdev_test
aarch64-linux-gnu-gcc -Wall -O2 chrdev_test.c -o chrdev_test
否则,我不得不中断命令行。但是,在某些特殊情况下,您会发现中断的输出行(尤其是在内核消息上),如下所示:
[ 526.318674] mem_alloc:mem_alloc_init: kmalloc(..., GFP_KERNEL) =ffff80007982f
000
[ 526.325210] mem_alloc:mem_alloc_init: kmalloc(..., GFP_ATOMIC) =ffff80007982f
000
不幸的是,这些行不容易在印刷的书中复制,但你应该把它们视为一行。
作为非特权用户,在我的主机上给出的任何命令行输入或输出如下所示:
$ tail -f /var/log/kern.log
当我需要在我的主机上以特权用户(root)的身份给出命令时,命令行输入或输出将如下所示:
# insmod mem_alloc.ko
您应该注意到,所有特权命令也可以由普通用户通过使用以下格式的sudo
命令来执行:
$ sudo <command>
因此,正常用户可以执行上述命令,如下所示:
$ sudo /insmod mem_alloc.ko
在几个 GNU/Linux 发行版上,内核消息有这种常见的形式:
[ 3.421397] mvneta d0030000.ethernet eth0: Using random mac address 3e:a1:6b:
f5:c3:2f
对于这本书来说,这是相当长的一行,所以这就是为什么我们从每一行的开头到真正的信息开始的地方都去掉了字符。因此,在前面的示例中,行输出将报告如下:
mvneta d0030000.ethernet eth0: Using random mac address 3e:a1:6b:f5:c3:2f
不过,前面已经说了,如果线还是太长,反正是会断的。
长输出,或终端中重复或不太重要的行,通过用三个点...
替换它们来丢弃,如下所示:
output begin
output line 1
output line 2
...
output line 10
output end
当这三个点在一行的末尾时,这意味着输出继续,但出于空间原因,我决定剪切它。
当您应该修改文本文件时,我将使用统一上下文差异格式,因为这是表示文本修改的一种非常高效和紧凑的方式。这种格式可以通过使用带有-u
选项参数的diff
命令获得,或者通过使用git
存储库中的git diff
命令获得。
举个简单的例子,让我们考虑一下file1.old
中的以下文本:
This is first line
This is the second line
This is the third line
...
...
This is the last line
假设我们必须修改第三行,如下面的代码片段所示:
This is first line
This is the second line
This is the new third line modified by me
...
...
This is the last line
您很容易理解,每次报告整个文件进行简单的修改是不必要的,并且会占用空间;但是,通过使用统一上下文 diff 格式,前面的修改可以写成如下:
$ diff -u file1.old file1.new
--- file1.old 2019-05-18 14:49:04.354377460 +0100
+++ file1.new 2019-05-18 14:51:57.450373836 +0100
@@ -1,6 +1,6 @@
This is first line
This is the second line
-This is the third line
+This is the new third line modified by me
...
...
This is the last line
现在修改的很清晰,写的很紧凑!它以两行标题开始,其中原始文件以---
开头,新文件以+++
开头。然后,它跟随文件中包含行差的一个或多个变更块。前面的例子只有一个大块,其中未更改的行前面有一个空格字符,而要添加的行前面有一个+
字符,要删除的行前面有一个-
字符。
尽管如此,由于篇幅的原因,本书复制的大多数补丁都减少了缩进,以适应打印页面的宽度;然而,它们仍然是完全可读的。对于完整的补丁,您应该参考 GitHub 或 Packt 网站上提供的文件。
在本书中,我将主要使用两种不同的连接来与嵌入式工具包交互:串行控制台,以及 SSH 终端和以太网连接。
串行控制台通过 USB 连接实现,主要用于从命令行管理系统。它主要用于监控系统,尤其是控制内核消息。
SSH 终端与串行控制台非常相似,即使不完全相同(例如,内核消息不会自动出现在终端上),但它可以以与串行控制台相同的方式使用,从命令行提供命令和编辑文件。
在这几章中,我将在串行控制台上或通过 SSH 连接使用终端来提供实现本书中解释的所有原型所需的大多数命令和配置设置。
要从主机访问串行控制台,您可以使用minicon
命令,如下所示:
$ minicom -o -D /dev/ttyUSB0
但是在第一章、安装开发系统中,这些方面都有说明,大家不用担心。还要注意,在某些系统上,您可能需要 root 权限才能访问/dev/ttyUSB0
设备。在这种情况下,您可以通过使用sudo
命令来解决此问题,或者更好的方法是使用以下命令将您系统的用户正确添加到正确的组中:
$ sudo adduser $LOGNAME dialout
然后注销并再次登录,您应该可以毫无问题地访问串行设备。
要访问 SSH 终端,您可以使用以太网连接。它主要用于从主机或互联网下载文件,可以通过将以太网电缆连接到嵌入式套件的以太网端口,然后根据读者的局域网设置配置相应的端口来建立(参见第 1 章、安装开发系统中的所有说明)。
粗体:表示一个新的术语、一个重要的单词或者你在屏幕上看到的单词。例如,菜单或对话框中的单词像这样出现在文本中。下面是一个示例:“从管理面板中选择系统信息。”
Warnings or important notes appear like this. Tips and tricks appear like this.
在这本书里,你会发现几个经常出现的标题(准备,怎么做...、它是如何工作的...、还有更多...和参见。
要给出如何完成配方的明确说明,请使用以下章节:
本节告诉您配方中的预期内容,并描述如何设置配方所需的任何软件或任何初步设置。
本节包含遵循配方所需的步骤。
这一部分通常包括对前一部分发生的事情的详细解释。
本节包含关于配方的附加信息,以便您更好地了解配方。
本节提供了该配方的其他有用信息的有用链接。
我们随时欢迎读者的反馈。
一般反馈:如果你对这本书的任何方面有疑问,在你的信息主题中提到书名,发邮件给我们[email protected]
。
勘误表:虽然我们已经尽了最大的努力来保证内容的准确性,但是错误还是会发生。如果你在这本书里发现了一个错误,如果你能向我们报告,我们将不胜感激。请访问www.packt.com/submit-errata,选择您的图书,点击勘误表提交链接,并输入详细信息。
盗版:如果您在互联网上遇到任何形式的我们作品的非法拷贝,如果您能提供我们的位置地址或网站名称,我们将不胜感激。请通过[email protected]
联系我们,并提供材料链接。
如果你有兴趣成为一名作者:如果有一个你有专长的话题,你有兴趣写或者投稿一本书,请访问authors.packtpub.com。
请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?然后,潜在的读者可以看到并使用您不带偏见的意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢大家!
更多关于 Packt 的信息,请访问packt.com。