9.4 devfs和sysfs
devfs和sysfs它们来了,真的来了,一前一后来的,来得是那么突然,来得是那么悄无声息。一个脸色苍白,苍白得让人不寒而栗;一个眼光深邃,深邃的让人顿觉谦卑。人们一直在谈论着它们,据说先来的已经死了,死的很透彻,是被它的门人杀死的,而且居然是后来者收买了它的门人,后来的现在还在收买其他门派的门人,正在觊觎“武林盟主”的地位。所有的事情就这么潜移默化的变化着,轮替着。一切看似那么平静,平静得已经让让很多人开始摩拳擦掌。在这平静之中不知何时又要到来一场可怕的血雨腥风。
故事是这样开始的……
9.4.1 devfs的由来
Linux,或者说类UNIX系统最“酷”的地方是,设备不是简单地隐藏在晦涩的API之后,而是真正的与普通文件、目录或符号连接一样,存在于文件系统之上(还记得我们前面说过的9号计划嘛?正是发源于此)。因为字符设备和块设备是映射到普通文件系统名称空间的,这样人们就可以通过很简单的文件读写方式与硬件交互。很多时候仅使用标准的Linux命令,如cat或dd,就足够了。这些映射设备的文件被合理的组织在了/dev目录下。
devfs,也叫设备文件系统,它的唯一目的就是提供一个新的,更合理的方式管理那些位于/dev目录下的所有块设备和字符设备。因为典型的Linux系统以一种不太理想,而且麻烦的方式管理这些特殊文件。
时至今日,Linux支持的硬件种类越来越多,也就意味着在/dev中的文件数量也越来越多,用数以万计来说的确很夸张,但是要说数以千计、数以百计是绝对不过份的。只是这还不是问题的根本,最根本的是这些特殊文件是写死的,而且大多数根本不会映射到系统中,因为再复杂的服务器,撑死也就配备几十个设备。显然是使用99.9%的努力,只是为了解决0.1%的问题。况且谁也不敢保证用户以后不添置什么设备,所以这些文件一个都不能动。
不过devfs诞生之际,情况没有上面说的那么糟糕。就是因为devfs的生辰问题,导致了它日后的结局,我们祖先发明的生辰八字有些时候想想还是蛮有“科学”道理的。devfs诞生的太早了,它虽然对上面的问题做了一定的处理,但是有些不是很合理,具体我们后面还会说。现在要说的是,它解决了一个更要命的问题。什么问题呢?设备号的问题。传统的Linux设备驱动程序,要向系统提供一个文件映射,需要提供一个主设备号,而且这个主设备号必须保证唯一。由于历史原因,早些年内存比黄金还贵,这个主设备号被设计的只有8位,显然这是稀缺资源啊,在它面前,黄金都只能汗颜了。既然这样,开发人员自然不能凭空臆造一个主设备号了,只能联系Linux内核的开发人员来申请,如果人家正忙着呢,那您就只能等,还不能歇,一歇就麻烦了,因为等待申请的人多了去了。所以,您就甘心的在那儿耗着吧。直到人家看你是个虔诚的主儿,偶发恻隐之心,给您分配了一个“正式”的主设备号,您才算万事大吉收工交差。其实后面的事情远没有这么简单,只是那已经是历史,我就不多叨唠了。至于这种策略的后果是什么,我不说,谁都知道。反正很难想象,早年的Linux用户真是有够虔诚,要不然现在还有谁会知道有Linux这个破玩意儿呢?
不管devfs的命运如何,但就仅仅是把这个滥问题给解决了,就可以称之为伟大,何况这只是其中的一个部分呢?
9.4.2 进入devfs
devfs是怎么解决这个滥问题的呢?它给驱动开发人员提供了一个叫devfs_register()的内核API,这个API可以接受一个设备名称作为参数。调用成功后,在/dev目录下就会出现与设备名相同的文件名。而且devfs_register仍然支持主设备号的策略,这样可以保持向下兼容性,降低早期的设备驱动程序移植的复杂性。
一旦所有设备驱动程序启动并向内核注册适当的设备,内核就启动/sbin/init进程,系统初始化脚本开始执行。在启动过程初期,rc脚本将devfs文件系统安装在/dev中,这样/dev中就包含了devfs所表达的所有设备映射关系,所有注册的设备依然可以通过/dev目录进行访问,用户应用程序不用做任何修改。
这种设计的最大优点就是:所有需要的设备映射关系都由内核自动创建,因此也就不用写死设备文件了,那么/dev目录下就不会充斥着大量的无用的设备文件了。在实际应用中,只要查看一下devfs,就能够知道这个系统上有什么设备了。
devfs让一切变得容易了许多。最典型的就是当你编写一个显示实时系统信息的程序时,不用做依次轮询哪些设备是“活跃的”这样费时的工作。因为只要读取/dev下得所有信息就可以搞定。即便用户只想查看某一个类型设备的信息,比如光驱,根据devfs的约定,只需要读取/dev/cdroms下的所有文件即可。
在实际操作中,比如你想访问一个特定的块设备,还有很多不同的途径。例如:一个服务器上,只有一个SCSI光驱;使用devfs后,就可用通过/dev/cdroms/cdrom0访问;还通过使用/dev/scsi/host0/bus0/target4/lun0/cd访问它。这两种都映射了同一个设备,你可以选择一个你认为最方便的途径。如果你愿意,还可以使用一种老式的设备名称/dev/sr0访问光驱,这都是因为有一个非常便捷的叫devfsd的小程序在幕后完成的工作。这个程序虽然小,但是功能很多。它负责创建老式的“兼容性”设备映射文件,允许你一很多方式自定义/dev。
9.4.3 sysfs的由来
sysfs是后来的,收买了devfs的门徒,杀死了devfs,它用的不是钱和刀,是udev。还发表声明公开了devfs该杀的四大罪状。但是马上就有人不服了:才四大罪状,好多贪官100条大罪都犯下了,也没判死刑不是?Linux是一个崇尚简单的世界,只要有一条能够说明你很麻烦,就有理由杀掉你,况四条大罪呼?那么这四条大罪是什么呢?
第一,不确定的设备映射,有时一个设备映射的设备文件可能不同,例如我的U盘,可能对应sda也可能对应sdb。
第二,没有足够的主/辅设备号,当设备过多的时候,这就是一个问题。前面说过,虽然devfs已经意识到了将来的设备会很多,但是没处理好,没有给主/辅设备号太多的扩展余地。
第三,dev目录下文件太多而且不能表示当前系统上的实际设备(这个罪状在我看来是有点牵强的,不过欲加之罪嘛)。
第四,命名不够灵活,不能任意指定。
于是devfs死了,sysfs成为了新的“帮主”。那么sysfs究竟是什么来头呢?系出名门,出身高贵啊。
最初,当人们已经开始意识到procfs的复杂度之后,就开始想将procfs中有关设备的部分独立出来。最开始采用ramfs(这个可以看作是RamDisk和tmpfs的中间产品)作为基础,名曰ddfs,后来发现driverfs更为贴切。这些都是在2.5版本中内核鼓捣的。按照那个时候的内核版本号的规则,所有第二位为奇数的版本都是实验版,所以2.5这个内核版本对于大众来说是不多见的。driverfs把实际连接到系统上的设备和总线组织成一个分级的文件,和devfs相同,用户空间的程序同样可以利用这些信息以实现和内核的交互(这个思路来自procfs),该系统是当前实际设备树的一个直观反映。到了2.6内核,也就是2.5的最终成型版本,新设计了一个kobject子系统,它就改变了实现策略抛弃了ramfs,利用kobject子系统来建立这些信息。当一个kobject被创建的时候,对应的文件和目录也就被创建了,位于/sys下的相关目录下。因此更名为sysfs。因为本身就源自于procfs的设计思路,因此它所提供的也是用户空间与系统空间交换信息的接口。用户空间工具udev就是利用了sysfs提供的信息在用户空间实现了与devfs完全相同的功能。既然功能相同,而且是在用户空间实现,显然比在内核空间实现的devfs要简单的多,安全得多,也会稳定的多。devfs被杀,也许这就是它的宿命。
9.4.4 小结
其实sysfs和devfs做比拼已经没有实际的意义了,因为现在显然已经没有任何争端了。只是我想展现给大家还是开篇的一个主题:新的系统并不是只为了做同样的事情比老的系统快一点,还应该允许我们用以前完全不可能的方法来处理事情。
显然sysfs能够实现全部devfs的功能,而且是在用户空间完成的。不单单是这样。当一部并不存在的/dev节点被打开的时候,devfs会很负责的去加载这个驱动程序,sysfs却不会做这种傻事。不过也不能说devfs傻,应该说它敬业,要很负责的告诉用户,这个设备不存在,但是它没有好的机制去做,只能用笨方法,让驱动程序实际去监测设备来报告这个结果。sysfs如果只是做到这些,应该还是不能足以收买devfs的门徒的,sysfs还能给内核产生的设备名增加别名,好处就是用户可以用自己喜欢的名字,显然对用户很友好。sysfs真正的彻底解决了devfs遇到的所有问题。
devfs和sysfs的故事讲到这里也该结束了。在如今的Linux发行版中,你再也找不到devfs的影子了,但是procfs和sysfs还在。sysfs如今大红大紫,procfs的命运如何,还需要你我共同的期待。