For the complete documentation index, see llms.txt. This page is also available as Markdown.

第 9 章 编写 FreeBSD 设备驱动程序

9.1. 介绍

本章简要介绍了如何为 FreeBSD 编写设备驱动程序。在这个上下文中,“设备”通常指的是与硬件相关的系统组件,例如磁盘、打印机或带有键盘的图形显示器。设备驱动程序是操作系统中控制特定设备的软件组件。还有所谓的伪设备,设备驱动程序通过软件模拟设备的行为,而没有任何特定的底层硬件。设备驱动程序可以静态地编译进系统,或者通过动态内核链接器设施 kld 按需加载。

在类 UNIX® 操作系统中,大多数设备通过设备节点进行访问,这些节点有时也称为特殊文件。这些文件通常位于文件系统层次结构中的 /dev 目录下。

设备驱动程序大致可以分为两类:字符设备驱动程序和网络设备驱动程序。

9.2. 动态内核链接器设施 - KLD

kld 接口允许系统管理员动态地为运行中的系统添加和移除功能。这使得设备驱动程序的编写者无需不断重启来测试更改,就能将新的更改加载到运行中的内核中。

通过以下命令使用 kld 接口:

  • kldload - 加载新的内核模块

  • kldunload - 卸载内核模块

  • kldstat - 列出已加载的模块

内核模块的框架布局

/*
 * KLD 框架
 * 灵感来源于 Andrew Reiter 的 Daemonnews 文章
 */

#include <sys/types.h>
#include <sys/systm.h>  /* uprintf */
#include <sys/errno.h>
#include <sys/param.h>  /* kernel.h 中使用的定义 */
#include <sys/module.h>
#include <sys/kernel.h> /* 模块初始化中使用的类型 */

/*
 * 处理 KLD 加载和卸载的加载处理函数。
 */

static int
skel_loader(struct module *m, int what, void *arg)
{
	int err = 0;

	switch (what) {
	case MOD_LOAD:                /* kldload */
		uprintf("Skeleton KLD 已加载。\n");
		break;
	case MOD_UNLOAD:
		uprintf("Skeleton KLD 已卸载。\n");
		break;
	default:
		err = EOPNOTSUPP;
		break;
	}
	return(err);
}

/* 向内核声明此模块 */

static moduledata_t skel_mod = {
	"skel",
	skel_loader,
	NULL
};

DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);

9.2.1. Makefile

FreeBSD 提供了系统 Makefile 来简化编译内核模块。

使用此 Makefile 运行 make 将生成文件 skeleton.ko,可以通过以下命令将其加载到内核中:

9.3. 字符设备

字符设备驱动程序是直接与用户进程交换数据的驱动程序。这是最常见的设备驱动程序类型,源代码树中有许多简单的示例。

这个简单的伪设备示例记住写入它的所有值,并在读取时将其回显出来。

FreeBSD 10.X - 12.X 的 Echo 伪设备驱动程序示例

加载此驱动程序后,尝试执行:

真实硬件设备将在下一章中描述。

9.4. 块设备(已废除)

其他 UNIX® 系统可能支持另一种磁盘设备,称为块设备。块设备是内核提供缓存的磁盘设备。这种缓存使得块设备几乎无法使用,或者至少是危险且不可靠的。缓存会重新排序写操作的顺序,剥夺了应用程序在任何时刻知道磁盘确切内容的能力。

这使得磁盘上数据结构(文件系统、数据库等)的可预测和可靠的崩溃恢复变得不可能。由于写操作可能被延迟,内核无法向应用程序报告哪个特定的写操作遇到了写入错误,这进一步加剧了一致性问题。

因此,没有重要的应用程序依赖于块设备,事实上,几乎所有直接访问磁盘的应用程序都非常注意指定应始终使用字符(或“原始”)设备。由于每个磁盘(分区)别名为两个具有不同语义的设备的实现显著复杂了相关内核代码,FreeBSD 在磁盘 I/O 基础设施现代化过程中放弃了对缓存磁盘设备的支持。

9.5. 网络驱动程序

网络设备的驱动程序不通过设备节点进行访问。它们的选择基于内核中的其他决策,且通常通过系统调用 socket(2) 来使用网络设备,而不是调用 open()

有关更多信息,请参见 ifnet(9) 以及环回设备的源代码。

最后更新于