资讯详情

BusyBox根文件系统/dev目录创建:静态mknod、devtmpfs、mdev三方案详解

📅 2026/9/18 22:06:38 | 华诺云谱 👁 阅读
BusyBox根文件系统/dev目录创建:静态mknod、devtmpfs、mdev三方案详解
做嵌入式Linux的兄弟应该都干过这事往板子上烧完内核手搓了一个BusyBox根文件系统结果启动到一半卡在“Creating 5 entries in /dev”或者挂载根文件系统之后VFS报一堆节点不存在console登录不了串口一片死寂。这种问题十有八九是dev目录没处理好——不是忘了建就是建了但方式不对设备节点在用户空间看不到内核也不认。这期就专门聊聊BusyBox环境下根文件系统里dev目录的正确创建方式。我把静态mknod、devtmpfs、mdev这三种主流方案从原理到实操全拆开讲附带设备号速查和排错经验。无论你是刚入坑的应届生还是被设备节点折磨过好几轮的老兵这篇都值得花十分钟看完。1. 为什么dev目录在BusyBox里这么特殊1.1 /dev不是普通目录它是设备访问的入口先搞清一个概念Linux下一切皆文件硬件设备在用户空间就是/dev目录下的一个文件节点。你在应用层open(/dev/ttyS0)内核VFS就会根据这个路径找到对应的设备驱动然后把你的读写请求转给硬件。没有这个节点驱动写得再好应用程序也够不着设备。在完整的发行版系统里dev目录由udev或者systemd-udevd接管插个U盘、加载个驱动设备节点会自动出现。但嵌入式设备用BusyBox图的就是体积小、依赖少绝大多数场景不会把完整的udev机制搬进去。BusyBox只提供了一个轻量级的mdev还经常有人不配置。这时候dev目录里的设备节点从哪来就得你自己决定。1.2 内核挂载根文件系统的先后顺序决定了问题从哪来这里要理解内核启动流程中的一个关键点。内核启动后期会向initrd/initramfs里的init进程交接或者在直接挂载根文件系统后执行其中的/sbin/init。无论哪条路径根文件系统上挂载时VFS希望里面已经有足够的基础设备节点尤其是/dev/console和/dev/null。因为init进程起来之前内核要把控制台输出重定向到某个设备上如果没有console节点printk回显会出问题甚至init直接崩溃。所以你的根文件系统里dev目录从一开始就得存在且至少要放console和null两个节点。这两个节点不是“建议有”而是“没有大概率起不来”。我在课上带学生做实验时第一个坑必踩在这。1.3 没有守护进程也没有设备管理器谁来补位发行版有udev在后台实时响应内核的uevent创建设备节点。BusyBox环境通常没有这个后台机制。就算你用mdev也得在rcS里显式启动mdev并设置热插拔处理函数。否则内核注册了一个新设备sysfs里能看到dev目录里却什么都没有。这就是很多人遇到的怪现象/sys/class/xxx里有设备/dev/xxx就是找不到。所以你要么在制作文件系统时就把常用的节点静态创建好要么让内核帮忙把devtmpfs挂到/dev上要么配置mdev让它扫描sysfs生成节点。三种思路各有利弊下一节详细对比。2. 三种主流方案对比与选型2.1 方案一静态mknod最简单的板级方案这是古老但确实可行的方式。你在构建根文件系统时用mknod命令把目标板子上需要的所有设备节点预先创建出来随文件系统一起烧录。优点很直接不需要任何运行时机制节点一直都在依赖SPI Flash、NAND这类介质上也不会因为用户态程序没起来而缺失。缺点也明显不灵活。换内核、换驱动设备主次设备号变了你的静态节点就废了。而且嵌入式设备外设相对固定所以这方案在小批量、硬件固定的产品里依然有生命力。2.2 方案二devtmpfs内核帮你维护节点devtmpfs从内核2.6.33开始引入是一个由内核维护的虚拟文件系统。简单说内核每注册一个驱动、发现一个设备就自动在这个文件系统里创建设备节点设备移除时自动删除。你不需要任何用户态守护进程。使用上只需要内核开启CONFIG_DEVTMPFS和CONFIG_DEVTMPFS_MOUNT然后在启动时把devtmpfs挂载到/dev。如果开启了自动挂载内核会自己挂连命令都省了。这是目前我见过最适合BusyBox轻量系统的方案兼顾了动态性和零守护进程的开销。不过要注意devtmpfs只会创建内核已知的设备节点像某些纯粹的虚拟设备节点可能还是需要手动补。2.3 方案三mdevBusyBox自带的用户态热插拔mdev是BusyBox里udev的简化替代品。它不像devtmpfs那样由内核直接生成节点而是通过解析sysfs和uevent由用户态程序负责创建。配置相对繁琐需要编写/etc/mdev.conf需要在rcS里启动mdev还需要把/sys挂载好以及设置内核的uevent helper或者直接在rcS里执行mdev -s来扫描已有设备。它能实现比较细粒度的规则控制比如给特定设备指定权限、属主这是devtmpfs裸用做不到的。但因为依赖用户态程序如果根文件系统阶段、rcS还没跑起来时设备节点就已经被需要了那就晚了。因此通常把mdev和devtmpfs结合使用这是比较专业的做法。2.4 方案选型表方案动态性实现复杂度有无守护进程适用场景静态mknod无低无硬件固定、批量产线devtmpfs高低无大多数BusyBox系统首选mdev高中有需要自定义节点权限/规则devtmpfs mdev高中有兼顾动态与定制专业推荐3. 正确创建dev目录的详细实操3.1 先搭好根文件系统的骨架在用BusyBox制作根文件系统时我习惯先用下面的命令把基础目录建好避免后面缺目录临时补mkdir -p rootfs/{bin,sbin,etc/init.d,dev,proc,sys,tmp,usr/bin,usr/sbin,lib,root,mnt} chmod 1777 rootfs/tmp sudo chown -R root:root rootfs这个骨架里dev目录必须要提前建不然后续根本没法往里面放节点。同时强调一下整个根文件系统的属主和权限也有讲究如果以非root用户创建了目录打包进去后init启动时可能遇到权限问题。3.2 把busybox编进去并补齐动态链接库BusyBox建议静态编译这样省去拷贝库的麻烦。但如果你想用动态编译节省一点空间就得用readelf -d busybox查它依赖的库然后从工具链的sysroot里把对应的ld、libc拷贝到rootfs/lib下。这一步如果有遗漏进入系统后执行任何命令都会报No such file or directory但不是文件不存在而是动态链接器缺失。3.3 静态节点方式的mknod完整操作选择静态方案时先把内核的文档或/proc/devices里列出的设备号确认一下然后rootfs/dev目录下执行cd rootfs/dev mknod console c 5 1 mknod null c 1 3 mknod zero c 1 5 mknod random c 1 8 mknod urandom c 1 9 mknod ttyS0 c 4 64 mknod ttyS1 c 4 65 mknod mtd0 c 90 0 mknod mtd0ro c 90 1这些节点里面console和null是必须的另外几个看你的板子。我拿一块全志的板子举例串口对应ttyS0Flash分区对应mtd0。如果你的平台串口名是ttyAMA0或者ttymxc0需要同步调整。设备节点的关键是类型和主次设备号不能拍脑袋。比如串口是字符设备c主设备号4次设备号64对应ttyS065对应ttyS1。只用linux源码目录下Documentation/admin-guide/devices.txt或者直接查内核源码的include/uapi/linux/major.h。3.4 devtmpfs方式的内核配置和挂载采用devtmpfs方案时先确认内核配置CONFIG_DEVTMPFSy CONFIG_DEVTMPFS_MOUNTy这两个配置打开后内核在初始化时会自动挂载devtmpfs到/dev不需要根文件系统里做任何特殊操作。但为了安全我习惯在rcS里再加一句挂载兜底#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev mkdir -p /dev/pts mount -t devpts none /dev/pts这里有个细节devpts和devtmpfs是两回事devpts专门给pty伪终端用的。很多人在rcS里漏了devpts结果ssh登录进去之后无法申请伪终端shell直接报错。3.5 mdev方案的配置细节如果用mdev建议在内核配置里确认CONFIG_DEVTMPFSy但不需要自动挂载然后rcS里做下面的操作mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev mkdir -p /dev/pts mount -t devpts none /dev/pts echo /sbin/mdev /proc/sys/kernel/hotplug mdev -s这里有一个很多人踩的坑mdev -s必须在/sys挂载之后执行因为它要扫描/sys下的设备信息。同时要注意devtmpfs已经挂载的情况下mdev -s的主要作用不是创建设备节点而是根据/etc/mdev.conf修正节点权限、建立软链接。如果你只想靠mdev完全接管不挂devtmpfs比较复杂我不推荐新手这么干。3.6 /etc/mdev.conf规则示例一份简化但实用的mdev.confconsole 0:0 660 null 0:0 666 zero 0:0 666 urandom 0:0 444 ttyS0 0:0 600 ttyS1 0:0 600 mtd.* root:root 660格式基本是“设备名正则 属主 权限”。匹配规则支持正则这就是mdev比devtmpfs灵活的点。比如监控一个U盘设备自动挂载也是通过mdev.conf的规则触发脚本完成的这个后面有机会单独讲。4. init进程与dev目录的先后顺序问题4.1 内核挂载devtmpfs的时机如果你开启了CONFIG_DEVTMPFS_MOUNT内核在初始化设备模型时会自动挂载devtmpfs这个时间点远早于用户空间init进程启动。也就是说不管你是用initramfs还是直接挂载根文件系统/dev在init跑起来之前就已经是可用的了。这也是devtmpfs方案比静态方案更“正确”的核心原因之一它卡在最合理的时机。但注意有一种情况会被坑如果你没有开启自动挂载也没有在initramfs阶段挂载devtmpfs而你的init进程又依赖某个/dev节点那就麻烦了。比如有些init脚本会在mount -a之前就要访问/dev/console这时可能出问题。所以要么内核自动挂载要么保证initramfs阶段正确挂载两条路至少走一条。4.2 rcS脚本里的挂载顺序命门就在这里rcS脚本是BusyBox系统里的第一个用户态初始化脚本。很多人习惯把一堆mount塞进去但顺序错了也会出诡异问题。我的经验是先挂proc和sysfs再挂devtmpfs再挂devpts最后再去执行其他依赖设备节点的动作。原因很简单mdev -s需要读取/sys里的设备信息所以sysfs必须先就绪而需要pty的应用程序必须等devpts就位。这套顺序经得起推敲不是随便排的。小型嵌入式系统常见的问题是把普通根文件系统的数据分区比如/etc或/var也放进rcS里挂载。这时如果该设备节点还没出现mount就会失败。如果用devtmpfs通常节点已经由内核创建问题不大如果你用静态节点而挂载的设备在静态列表里漏了那就只能干瞪眼。所以这里也能看出devtmpfs的优势。4.3 VFS层和sync和dev目录有什么关系先解释一下VFS和sync这两个热词和dev目录的关联。Linux一切皆文件靠的是VFS设备节点在VFS里是一种特殊inode。mknod、挂载devtmpfs、open(/dev/xxx)最终都会经VFS层转化成对具体文件系统或驱动框架的操作。而sync是把内存中变更的dirty数据刷回持久存储。对dev目录来说生产环境有一个非常实际的场景你在开发机上用mknod在rootfs目录里创建了一堆静态设备节点然后打包烧录。如果你在创建节点之后没有sync开发机断电或者直接拔掉SD卡节点可能因为page cache还没落盘而丢失。这个坑我朋友遇到过现场演示时设备节点时有时无查了半天最后发现就是没sync导致数据没真正写进镜像。所以我现在每次构建完根文件系统打包前都习惯性跑一下sync形成肌肉记忆了。5. 常见问题与排查技巧实录5.1 启动时报错“Creating 2 entries in /dev”看到这个错误说明内核的devtmpfs已经在工作了但根文件系统里的/dev是空的或者初始化脚本尝试往里面创建节点却缺少必要工具。这个报错实际上来自内核的devtmpfs初始化逻辑。我遇到时一般先检查内核配置确认CONFIG_DEVTMPFS_MOUNT是否开启以及启动参数里有没有“rootfstypexxx”把文件系统类型指定错。5.2 设备节点存在但open失败节点在/dev下能看到open却报Permission denied或者No such device。Permission denied一般是权限问题ls -l看一下节点的权限位和属主。No such device相对复杂表示VFS能找到这个inode但驱动层不认这个设备通常是主设备号对应的驱动没注册或次设备号超出驱动管理范围。先用ls -l /dev/xxx确认主次设备号然后到/proc/devices核对系统实际分配的设备号不一致就用mknod重新创建。5.3 内核配置了devtmpfs但/dev下什么都没有这种情况一般是没开启CONFIG_DEVTMPFS_MOUNT内核只提供了devtmpfs的代码但没人挂载它。此时需要你在initramfs阶段或者rcS脚本里手动执行mount -t devtmpfs none /dev。还有一种可能是文件系统里缺乏挂载点目录如果/dev都不存在mount当然失败。所以我在前面强调根文件系统骨架里的dev目录绝对不能省。5.4 使用mdev模块加载后节点不出现如果确认devtmpfs已经挂载模块加载后节点还是看不到。先确认sysfs有没有挂载再确认内核的hotplug机制里是否设置了uevent helper。手动执行一下mdev -s如果能扫出节点说明自动触发链路有问题。这种问题大概率是echo /sbin/mdev /proc/sys/kernel/hotplug没执行或者mdev.conf配置里把设备规则写成了不匹配的模式。5.5 设备号速查表常用设备节点类型主设备号次设备号/dev/console字符51/dev/null字符13/dev/zero字符15/dev/random字符18/dev/urandom字符19/dev/ttyS0字符464/dev/ttyS1字符465/dev/mtd0字符900/dev/fb0字符290这张表最好存一份开发时经常要翻。不同平台、不同内核版本可能会有差异最终以内核源码里的设备号分配表为准。5.6 常见排错速查表现象可能原因排查/解决启动后无法打开console/dev/console不存在或类型错误确认c 5 1节点存在设备节点有读写报错主次设备号不对或驱动未加载cat /proc/devices核对mdev后节点还是少/sys未挂载或uevent helper未设置mount -t sysfs none /sys设置hotplug/dev目录不存在导致挂载失败文件系统骨架漏建/devmkdir -p /dev并打包前syncinit无法执行动态链接库缺失或bin目录权限不对静态编译busybox或补齐运行时最后再分享两个实际操作中的经验每次构建根文件系统时无论用哪种设备节点方案我都坚持在打包前用find rootfs/dev -ls检查一遍关键节点是否在位然后执行一次sync。这个习惯帮我避免过好几次线下调试时的突发意外。另外提一点如果你打算长期维护一个嵌入式Linux产品建议优先走上devtmpfs的路线而不是在静态节点上堆mknod。开发初期看似省事但后期每升级一次内核设备号或多或少的变动都会让你头大。用内核的机制去管理内核维护的设备总比自己手写列表更靠谱。希望这篇东西能帮你少走我当年走过的弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。