第二章:devfs

类Unix操作系统传统上将文件系统与硬件的接口放在/dev目录中。

/dev中的特殊文件称为设备节点。

在设备节点上运行命令会向设备发出指令。

一些常见的设备节点:

FreeBSD使用设备文件系统devfs和支持进程devd自动创建和配置这些设备节点,无需特殊配置,它们可以自动配置所有典型的系统通用特性和功能。

不过,如果你做了任何不同的事情,你可能需要重新配置devfs(5)。无法访问/dev的chrooted进程可能需要一个设备节点进行日志记录,但它当然不需要访问磁盘或终端等。jail需要访问设备节点进行登录和基本程序,但不应该看到主机的任何底层硬件。这些情况以及更多情况要求系统管理员重新配置devfs以提供所需的基础设施。

FreeBSD将设备节点管理分为三部分:配置启动时出现的设备,配置启动后出现的设备以及管理设备节点权限和访问控制。

启动时/dev 配置

系统管理员可以使用正常的系统工具更改设备节点,根据需要更改其所有权和权限。

但是假设一个无特权进程需要访问串口终端/dev/cuaa1。我们通常会将该用户添加到dialer组,但这会授予它对所有串口的访问权限。如果你需要更严格的控制,可以将/dev/cuaa1的所有者更改为无特权用户。或者,一个笨重的软件可能需要一个可以用不同名称访问的设备节点。

问题是,devfs是一个逻辑文件系统。它在系统内存中。当重启服务器时,对/dev的所有手动更改都会消失。内核和devfs创建了一个反映初始硬件配置的原始/dev。

虽然内核和devfs知道如何在没有你任何帮助的情况下完成它们的工作,但它们在启动时读取/etc/devfs.conf,看看你是否有任何特殊的说明。

devfs.conf中的规则格式为:

该操作描述了所需的更改。devicename条目是需要更改的设备节点,而desiredvalue是一个新值。使用这些规则,可以添加新的设备节点、更改其所有权和更改其权限。

devfs仅在启动时读取devfs.conf。要使更改立即生效,需要更改现有的/dev条目。

每次修改devfs.conf后,应重启以验证配置。

新设备节点

link操作在新设备节点和现有节点之间创建符号链接。这允许你为现有设备创建别名:

devfs创建了两个新的设备节点/dev/cdrom和/dev/acd0,它们指向/dev/cd0处的CD驱动器。一些软件希望CD驱动器位于/dev/cdrom,我们不想调整这些偏好。多年来,FreeBSD的ATA CD驱动器一直位于/dev/acd0。

设备所有者

使用own操作更改设备节点的所有者和组。以下示例让用户mwl完全控制CD驱动器:

许多在设备节点上运行的程序在执行任何操作之前都会进行内部检查,以验证它们是否以root身份运行。如果没有以root身份运行会自动关闭,甚至没有尝试访问目标设备节点。更改设备节点所有权不会使这些程序工作。

虽然您可以更改设备节点别名的所有者,但该更改仅适用于符号链接,而不适用于目标节点。始终更改实际设备节点的所有者。

设备权限

mode关键字更改设备节点上的权限:

这将授予/dev/cd0的所有者和组所有者读写权限

与所有权一样,只更改实际设备节点的权限,不更改任何别名。

动态硬件

devfs在插入新硬件时动态创建新的设备节点,并在移除硬件时删除设备节点。

许多可热插拔硬件非常简单——当出现新的USB键盘时,FreeBSD会将其连接到控制台。

其他硬件需要更复杂的配置。当硬件出现、消失和状态改变时,守护进程devd会自动运行用户区程序。例如当插入笔记本电脑以太网接口或更改USB到串口适配器的权限时,FreeBSD可以自动运行dhclient。

devd守护进程从/etc/devd.conf和/usr/local/etc/devd目录中以.conf结尾的任何文件读取其配置。由于/etc/devd.conf是一个系统文件,升级会覆盖它,强烈建议将本地规则放在/usr/local/etc/devd中。

可以使用单个文件设置规则,但如果规则变得越来越复杂,则建议为每种类型的设备使用单独的本地规则文件。

Devd 规则

devd监视四种不同类型的事件:attach(附加)、detach(分离)、nomatch(不匹配)、notify(通知)。

这些事件中的每一个都会触发该类型的规则。它还有一个options语句,控制devd本身的行为。

每个规则都有一个优先级,0是最低的;devd只处理最高的匹配规则,跳过任何较低优先级的匹配规则。

最后,一条规则有一堆匹配的术语和要采取的行动。如果匹配的术语与设备匹配,规则类型与事件匹配,并且没有更高优先级的规则生效,devd将触发指定的操作。

下面是一个示例规则:

这是一个notify规则,因此当内核向用户区通知事件时,它会激活。但作为优先级为0的规则,它仅在没有更高优先级的规则符合指定标准时才会触发。

匹配的术语出现在括号内,要触发此规则,它必须与系统IFNET匹配。设备子系统必须与正则表达式 !usbus[0-9]+ 匹配。这归结为“设备驱动程序无法从usbus启动”,不包括USB设备。

此规则在ATTACHH上匹配,或者在内核通知用户硬件已连接到系统时匹配。说人话就是,当内核宣布插入了非USB网络设备时,此规则会触发。它适用于旧笔记本电脑上的可移动网卡。

此规则的最后一行给出了要采取的行动。此规则启动可移动网卡的网络配置脚本。

总的来说,这条规则说“当你插入可拆卸的非USB网卡时,请对其进行配置”。

匹配语句

您可以在devd(8)规则中匹配设备的几乎任何方面。我不会在这里列出所有可能的匹配项,因为那将是你几乎永远不会使用的一页又一页的东西。阅读devd.conf(5)以获取完整列表。不过,一般来说,您可以按制造商、供应商、型号、序列号、网络接口介质、父设备、硬件版本等匹配设备。

某些规则类型允许附加术语。例如,在附加和分离规则中,您可以使用设备名称作为match device-name的别名。这些是可选的。

我可以用更多的例子来填满整本书。理解devd.conf规则的最佳方法是阅读/etc/devd.conf中的示例。

创建devd(8) 规则

我强烈建议不要从头开始编写自己的devd(8)规则。查看/etc/devd.conf。找到一个符合您要求的规则。将该规则复制到/usr/local/etc/devd中的.conf文件中。编辑该规则,直到它达到您想要的效果。

在我看来,最容易编写的规则是notify规则。您可以阅读/var/log/messages或检查控制台,以准确查看内核传递给用户区的消息,并将其用作匹配项。

但是easy很无聊,所以我们将创建一个简单的attach规则。

Flash 驱动器

如果我插入闪存驱动器,我可能希望它安装在/media上。这并不总是正确的,但这很常见,我愿意让它成为默认设置。我宁愿每十次插入手动卸载/media一次,也不愿十次中有九次手动挂载。

在具有SATA驱动器的简单系统上,闪存驱动器可能显示为/dev/da0。在更复杂的系统中,它可能是任何设备节点。插入闪存驱动器,查看它在系统上的显示位置。

一些USB驱动器在插入时需要一两秒钟才能唤醒,所以我添加了sleep(1)命令。您也可以调用外部脚本。

FreeBSD不能阻止你移除已挂载的硬盘,但它可以抱怨。在这里,当devd(8)检测到设备被移除时,我们会自动强制卸载/media。

但是,您不必安装驱动器。我曾经不得不为大型FreeNAS部署准备40个闪存驱动器。一个devd.conf规则和一个简单的脚本让我告诉一个仆从“当灯停止闪烁时,把下一个放进去”,这样我就可以回到玩FreeCell(空当接龙)了。如果您的组织的操作或安全策略规定“不要将USB介质插入正在运行的生产服务器”,则调用newfs(8)的devd.conf规则可以强制执行。(当FreeBSD可以运行newfs时,USB固有的安全漏洞可能已经摧毁了你的服务器。断开USB端口比用强力胶填充端口更安全,也为未来的维护提供了更大的灵活性。)

CARP 故障转移

CARP——Common Address Redundancy Protocol,通用地址冗余协议。

CARP允许多个主机共享一个IP地址。当一个主机死亡时,另一个主机会接管该地址,使服务保持活力。

有些服务在故障转移时需要快速启动。通常,你需要告诉备份数据库,它曾经是学生,现在是大师(master)。

CARP事件生成内核通知,这意味着你可以适用devd告诉应用程序发生了什么。这是一个devd.conf配置,用于在主机接管CARP主机时运行脚本。

这是一个通知规则,这意味着当内核向用户空间发送通知消息时,它会触发。我们有更复杂的匹配项,因为我们只想在这个特定接口启动时运行脚本。如果一个接口成为CARP主节点,devd将运行脚本/usr/local/scripts/carp-up。

当接口关闭时,devd运行第二个命令通知主机现在是备份。

除了事件的类型和我们运行的脚本之外,这个规则几乎与第一个规则相同。

调试devd.conf

调试devd(8)规则可能很困难。我强烈建议在操作规则中使用logger(1),这样您运行的命令就会被发送到系统日志中。您可能需要在/etc/syslog.conf中启用系统调试日志/var/log/all.log来捕获正在发生的一切,或者查看系统控制台。

任何时候,当硬件状态发生变化时,您都需要执行操作,请考虑devd.conf。不过,许多可以进入脚本的函数也可以通过devfs规则访问。

devfs(5) 规则

除了使用devfs.conf更改devfs,以及使用devd(8)在动态设备上运行命令外,您还可以使用/etc/devfs.rules中的devfs规则。所有设备节点,无论是在启动时存在还是动态添加,都受devfs.rules的约束。规则允许您设置设备节点的所有权和权限,并使设备节点可见或不可见。除了配置文件,您还可以在命令行添加devfs规则。

就像rc.conf和periodic.conf一样,FreeBSD在/etc/defaults/中有一个默认的devfs.rules文件。升级会覆盖此文件。将您自己的devfs规则放在/etc/devfs.rules中。/etc/devfs.rules中的条目通过规则编号覆盖默认文件中的规则,也就是说,/etc/devfs.reles中的规则5覆盖/etc/defaults/devfs.rule中的规则5。

devfs 规则设计

每个devfs规则都以一个唯一的名称和规则编号开头,在方括号内给出。您可以在devfs.rules的其他地方按名称引用此规则集。该数字只是一个唯一的标识符,devfs规则在规则之外没有优先级或处理顺序。然后,该规则有一个语句,添加此规则集采取的操作。

下面是默认devfs.rules中完整的devfs规则:

此规则名为devfsrules_hide_all,规则编号为1。它向此规则集添加了一个操作:hide。它隐藏了每个设备节点,给你一个空的/dev。

规则内容

所有devfs规则都以单词add开头,向规则集中添加规则。然后path关键字和设备名称的正则表达式,或者tpye关键字和设备类型。在规则的末尾,需要一个动作。下面是一个完整的devfs规则:

此规则声明用户mwl拥有名称以cuau开头的所有设备节点。这些设备节点连接到串行端口。

在多用户系统中,这将是个坏主意。

由路径指定的设备使用shell正则表达式。要匹配各种设备,请使用星号作为通配符,如果上面示例中的cuau所示。可以在字符串中间使用通配符,或者给出确切的设备名称。比如,如果只想更改机器上的第二个串口,可以指定cuau1。

type关键字指定此规则适用的设备类型。有效类型包括:

如果既不包含路径也不包含类型关键字,devfs会将该规则应用于所有设备节点。但强烈不建议这样做。

规则的操作可以是group、user、mode、hide和unhide中的任何一个。

嵌套规则

一个规则集可以包含其他规则集。看看下面的默认规则集:

此规则集是为jail设计的。它被命名为devfs.rules_jail,并被分配了规则集编号4.

add include语句引入了其他规则集,分别命名为devfs.rules_hide_all、devfs.rules_unhide_basic和devfsrules_unhide_login。这些规则在默认规则中已在前面定义。它还显式取消隐藏/dev/zfs下的设备节点。

对于特定的应用程序,可能需要稍微不同的规则集。在chroot中运行的一些应用程序需要一个syslog设备套接字或/dev/log。可以创建自己的规则集,引用hide_all规则,然后取消隐藏日志设备:

同样,你可能有一个需要访问特定设备的jail。可以参考jail devfs规则,并添加所需的设备。以下是一个用于串口连接的jail:

嵌套规则允许你创建所需的任何变体,同时让更改渗透到规则集中。

使用规则挂载 /dev

如果不想更改/dev的devfs规则。主系统应该可以访问系统上的所有设备。像jail这样的应用程序会适当地挂载自己的/dev文件系统。但是如果需要为应用程序或chroot挂载设备文件系统,请在/etc/fstab中创建一个条目:

要使用规则集,请使用ruleset挂载选项和规则集号码。确保逗号和ruleset之间没有空格。

命令行中设置devfs

可以使用devfs命令在命令行填加、删除和更改devfs规则。比如运行devfs rule apply hide讲移除所有设备节点。试试看,这个功能很少用,如果要用,建议仔细阅读devfs手册。

如果决定使用此功能,一定要使用-m选项为非默认设备文件系统指定一个挂载点,否则,将在默认系统/dev上工作,可能需要重启才能恢复。