那么,一堆静态文件到底出了什么问题/dev
?显然,开发人员已经重新发明了这个轮子,据我统计,已经 3 次了 ( devfs
-> udev + HAL
-> udev
),现在显然它还要进入 Grand Unified Init Program,所以是四次。
我记得几年前我第一次开始使用 Linux 时,我很惊讶,尽管声称“一切都是文件”,但却没有/dev/eth0
(后来才明白过来,因为它不是字符或块设备——尽管“数据包”设备类型会很有趣……)。既然如此,为什么处理字符和块设备文件树的程序也负责网络设备?我看到过对“灵活性”的模糊提及,但这与 ifconfig(8) 仅通过查看所做的相比有什么增加/proc/net/dev
?例如,我知道 NetworkManager 不会很快出现在 Net 或 OpenBSD 中,因为它依赖于udev
,而两个团队都不想编写;我不明白的是,为什么一个至少名义上用来管理树的程序/dev
显然是公开网络设备的唯一方法内核已经通过多种方式暴露了(并且没有一个在/dev
!)。
仅仅是因为热插拔吗?内核是否在监听物理总线并在出现“设备已添加”消息时加载相应模块时存在问题?或者,但愿不会,实际管理员这样做?我确实记得在 2000 年代初,我的服务器有时会以意想不到的顺序初始化其网卡,我想在用户空间中决定这种命名是有意义的(尽管当时修复起来并不难),但这似乎是对蟑螂的致命打击。(或者也许这个问题比机架式服务器或 PC 更难影响我没有考虑的用例,这是我的经验。)
因此,让我的问题简单明了一点:udev 究竟解决了哪些问题?为什么 devfs、HAL 和/或普通的旧文件无法解决这些问题?将这么多不同的东西(热插拔、常规设备管理、网络设备管理、设备命名、驱动程序优先级等)全部放在一个程序中,是否有特别的原因?
答案1
还有两件事:Linux 进入企业和其他大型服务器领域,暴露了静态文件/dev
可能被破坏的现象。无论是在消费者领域还是在企业领域,技术的发展都暴露了静态文件 /dev 可能是一个笑话。[这个答案补充了更多背景故事,但并不特别说明为什么 devfs 被 udev 取代]。
主编号和次编号空间耗尽
/dev
在内核中,文件通过其主编号和次编号来标识。内核实际上从来不关心文件名称(例如,你可以关心文件名称,mv /dev/sda /dev/disk-1
它仍会继续工作 — — 尽管程序当然不知道在哪里找到它)。
使用静态时/dev
,您需要为每个可能存在的设备分配一个主/次设备号。这些数字必须是全局唯一的,因为它们是作为发行版的一部分提供的,而不是按需创建的。问题是它们每个都是 8 位数字 - 范围是 0-255。
例如,最初 Linux 以 8,0 为 sda、8,1 为 sda1、8,16 为 sdb 等开始。但人们不断向机器添加越来越多的磁盘,尤其是考虑到光纤通道等因素。因此,在某个时候,添加了主编号 65–71 以容纳更多磁盘。后来,主编号为 128–135。然而,人们一直想要更多磁盘……
分区表格式(如 GPT)也出现了,支持每个磁盘有更多分区。当然,其他设备也在占用数字空间:各种 RAID 控制器、逻辑卷管理等。
最终结果可以在LANANA Linux 设备列表。如果你看一下 2.6 列表(唯一还存在的列表),你会发现很多块主编号(最大 200 到 255)都被使用了。显然,这些数字已经用完了。
改成更大的数字并不容易。它会改变内核 ABI。根据文件系统的不同,它会改变磁盘布局。但是,当然,最多这些设备在任何一个系统上都不存在,即使是(例如)用完 SCSI 磁盘的系统也可能有很多可用的东西 - 例如,它可能不需要 IBM XT 硬盘。
有了动态版本/dev
,发行版就不必发送设备编号。它们不再需要是全局唯一的。它们甚至不必在启动过程中都是唯一的。
设备名称不可预测
以前给所有东西分配一个数字是很容易的。一个主板有两个 IDE 通道;每个 IDE 通道支持一个主设备和一个从设备。您可以按通道顺序和主从顺序分配。因此,hda
第一个通道为主;hdb
第一个通道为从设备;hdc
第二个通道为主;等等。这些都是可预测且稳定的。如果您添加新驱动器或移除驱动器,它们可能会发生变化,但如果没有硬件变化,它们是静态的。
您可以/dev/hda1
放心/etc/fstab
,它会继续工作,至少在没有硬件变化的情况下。
IDE 就是这样工作的。之后就什么都没有了。
SATA 看似简单:一个端口,一个磁盘。但事实并非如此;它允许端口倍增器。并且允许热插拔。尽管如此,如果没有硬件更改,您实际上仍然可以保持映射工作。
USB 就差多了。它不仅允许热插拔,而且很常见。人们总是在插入 USB 闪存盘。此外,设备可能需要一段时间才能探测到,而且实际上可以随时更改(例如,在手机上打开或关闭 USB 存储模式时)。Firewire 也类似。使用这两种方式,您都无法真正实现稳定的映射。
网络附加磁盘没有任何固有的端口顺序。内核使用的唯一顺序是它们出现的顺序。逻辑卷也是如此。
对启动速度的追求也使情况变得更糟。最初,内核会很乐意地等待相当长的时间,例如,所有 USB 设备初始化。为了全面探测所有 SCSI 总线等。这些探测被做成了后台任务;启动不再等待它们。探测完成后,设备就会被添加。
因此,内核或多或少只能“按其出现的顺序排列”。这意味着许多类型的设备每次启动时都会改变顺序 — 一次启动时是的,/dev/sdb
另一次启动时也是/dev/sdc
。这使得静态的想法变成了/dev
笑话。
概括
当你考虑到静态组合/dev
由于不可预测的设备探测顺序而变得越来越没有意义,并且继续分配静态主/次号码导致大量工作无法完成时,就会明白为什么 Linux 的开发人员选择切换到动态/dev
。
答案2
好问题。
在某种程度上,这个论点可以反过来:由于内核 2.6.13 引入了新版本uevent
,因此必然devfs
需要重写才能利用界面的新功能。因此,在某种程度上,问题应该是内核为什么要进行更改。
然而,从表面上看,你的问题的答案是这篇维基百科文章:
与传统的 Unix 系统不同,在传统的 Unix 系统中,/dev 目录中的设备节点是一组静态文件,而 Linux udev 设备管理器则动态地仅提供系统中实际存在的设备的节点。尽管 devfs 曾经提供过类似的功能,但 Greg Kroah-Hartman 列举了一些理由来说明为什么更喜欢它的实现而不是 devfs:
1) udev 支持持久设备命名,这不依赖于设备插入系统的顺序等。默认的 udev 设置会为存储设备提供持久名称。任何硬盘都可以通过其唯一的文件系统 ID、磁盘名称以及其所连接的硬件上的物理位置来识别。
2) udev 完全在用户空间中执行,而不是在 devfs 的内核空间中执行。一个结果是 udev 将命名策略移出了内核,并且可以在创建节点之前运行任意程序,根据设备的属性为设备编写名称;这样,整个过程也是可中断的,并且以较低的优先级运行。
我可能应该补充一点,使用 udev 可以避免出现 的可能性race condition
,这基本上破坏了 devfs 和热插拔中的设备命名。换句话说:使用 devfs 无法确保最左边的以太网端口将被调用eth0
,而最右边的端口将被eth1
调用,这使得(仅举一个例子)路由器的设置(一个端口连接到 WAN,一个端口连接到 LAN)难以实现。
采用基于 GUID 的磁盘命名方案是另一个优点,将整个过程转移到用户空间是一个更大的优点:您是否搜索过此站点以查看有多少人编写自己的 udev 规则?