十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

mknod命令详解:手动创建设备节点的原理与实战

mknod命令详解:手动创建设备节点的原理与实战 说实话很多用 Linux 用了几年的朋友天天跟/dev/null、/dev/ttyS0打交道但要问一句这些设备文件到底是怎么来的、能不能自己手动创建一个设备节点多半会愣一下。mknod这个命令平时用得少可一旦遇到嵌入式开发、容器权限、或者系统启动异常的场景它往往是唯一能救场的手段。这篇文章不打算做命令手册式的罗列而是结合我在实际运维和嵌入式项目里踩过的坑把这命令背后的设备管理逻辑讲透。先说个场景。之前有一块 ARM 板子根文件系统是用 BusyBox 精简过的用户跟我说ttyS0不见了串口调试直接没法用。我登上去一看/dev/下面确实没有 ttyS0 这个节点但内核模块已经加载了。这种时候你不能等 udev因为精简系统里压根没有 udev。如果当时不知道mknod /dev/ttyS0 c 4 64这么一行命令就得重新烧一版文件系统代价完全不同。这篇文章会把 mknod 从命令这一层往下拆设备文件在内核里到底是什么、主次设备号怎么查怎么选、哪些场景必须手动建节点、建错了会踩什么坑。适合嵌入式开发、系统运维、以及所有想真正理解 Linux 设备模型的朋友。1. 设备文件不是普通文件mknod 到底在创建什么很多人第一次在终端敲ls -l /dev/null时会奇怪为什么权限位那一段开头是c而不是-。这个c就表明它不是一个普通文件而是一个字符设备文件。类似的还有以b开头的块设备文件。mknod 这个命令的功能概括起来就是在文件系统里创建一个特殊文件把它和某个内核驱动程序关联起来。1.1 设备节点是门牌号不是房子本身理解 mknod 关键要绕开一个思维定式设备文件是不占磁盘数据空间的它不是驱动程序本身更不是设备本身。它更像是一个门牌号让用户态程序通过 open、read、write 这些标准系统调用能按图索骥找到内核里的某个驱动。举个生活化的例子。一栋写字楼里有很多公司你只知道公司名字比如科技公司 A但不知道它在几楼几号你就没法找过去。设备文件就相当于楼下的指示牌写着科技公司 A 在三楼 305。你照着指示牌走前台内核帮你叫对应的公司接待你。mknod 干的事就是在指示牌上添一笔。那内核怎么根据设备文件找到驱动呢秘密就在 ls -l 输出里。拿/dev/null举例$ ls -l /dev/null crw-rw-rw- 1 root root 1, 3 Jun 8 10:22 /dev/null结尾的1, 3就是关键信息。逗号前面是主设备号major number逗号后面是次设备号minor number。内核用主设备号在驱动表里查驱动用次设备号找到驱动内部具体管理哪个实例。后面我会专门讲这两个号怎么填。1.2 字符设备、块设备还有别的吗mknod 命令能创建的设备类型主要有两种c代表字符设备b代表块设备。字符设备按字节流读写常见的有串口、鼠标、声卡块设备按固定大小的块读写比如硬盘、U 盘。系统中还有一类伪设备比如/dev/null、/dev/zero严格来说是字符设备但不对应真实硬件它们由内核的虚拟驱动提供。早年间还有p类型用来创建命名管道FIFO。不过今天创建命名管道基本都用mkfifo了mknod 的p类型选项在很多发行版里仍然保留兼容但实际使用场景极少。还有s类型socket 文件在较老的 Unix 版本中存在Linux 的 mknod 命令并不支持创建 socket 文件。这里要划一个重点mknod 创建的是在文件系统里看得见、找得到的节点它不等于加载驱动程序。你建了一个节点但内核里根本没有对应主设备号的驱动open 的时候会报No such device or address。反过来驱动加载了但节点丢了也会出现同样的问题。2. 命令语法逐个拆解类型、主设备号、次设备号的抉择mknod 的基本语法非常简洁mknod [选项] 设备文件名 类型 [主设备号 次设备号]类型就三个取值b、c、p。如果类型是b或c必须跟主设备号和次设备号类型是p的话不需要任何编号。功能单一、参数少但越是看着简单的命令越容易在细节上翻车。2.1 主设备号和次设备号从哪来这是 mknod 最核心的问题。设备号不是随便编的它由内核里的驱动决定。两种常见方式静态分配驱动代码写死一个主设备号比如早年某些串口驱动把主设备号定为 4。这种情况下你查驱动源码、/proc/devices或文档就能确定。动态分配驱动加载时向内核申请一个可用的主设备号像现在大多数驱动用的方式。这类驱动加载后主设备号是多少得看内核分配结果。最可靠的查询途径是/proc/devices。这个文件会列出当前内核已注册的所有字符设备和块设备的主设备号和名字。$ cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 5 /dev/tty 5 /dev/console 10 misc 21 sg 89 i2c 116 alsa 128 ptm 136 pts 180 usb 189 usb_device 253 device-mapper 254 mtd Block devices: 7 loop 8 sd 9 md 11 sr 65 sd 66 sd 67 sd 68 sd 69 sd 70 sd 71 sd 72 sd 73 sd 74 sd 75 sd 76 sd 77 sd 78 sd 79 sd 179 mmc 253 device-mapper 254 mtd这段输出里4 tty表示主设备号为 4 的字符驱动叫 tty这就是串口终端这一类设备用的驱动。所以/dev/ttyS0的主设备号是 4至于为什么次设备号是 64这要看驱动内部怎么给次设备号编号。Linux 内核里 tty 驱动的惯例是从 64 开始给串口编次设备号所以 ttyS0 是 4, 64ttyS1 是 4, 65。还有个小技巧如果某个设备已经有节点了你可以用stat命令直接反查它的设备号$ stat -c %t %T /dev/sda 8 0注意这里的输出是十六进制%t是主设备号%T是次设备号。8 0对应十进制的主设备号也是 8次设备号是 0。但某些设备号比较大的时候比如主设备号为 253这里输出就会是fd不能直接拿去做 mknod得先转回十进制。怕算错的可以再用printf转一下$ printf %d %d\n 0xfd 0x00 253 02.2 动手实验十五秒钟建一个自己的 null 设备理解 mknod 最快的方法是亲手建一个来试试。找一个测试目录用 root 权限执行$ mknod /tmp/mynull c 1 3 $ ls -l /tmp/mynull crw-r--r-- 1 root root 1, 3 Jun 8 10:30 /tmp/mynull然后试一下$ echo hello /tmp/mynull $ cat /tmp/mynull没有报错数据确实写进去了读出来是空。自己 15 秒钟建了一个/dev/null。不过这种情况下权限是 mknod 默认的rw-r--r--真实系统里的/dev/null是crw-rw-rw-所以建完通常还要手动chmod一下$ chmod 666 /tmp/mynull如果你建的是块设备或串口等有实际硬件对应的设备别随便 chmod 成 666。很多人习惯性给设备文件灌 666 权限等于是把硬件操作权开放给所有用户非常危险。3. 什么时候必须手动 mknod不是考古是刚需有人会问现在主流发行版都跑 udev devtmpfs设备节点不是系统自动管的吗还学 mknod 干嘛这话对也不对。在标准服务器环境下确实极少需要手动建节点但有三类场景mknod 是唯一解。3.1 场景一嵌入式 / 精简系统嵌入式 Linux 里rootfs 经常用 BusyBox 拼装为了控制体积、减少启动时间很多系统不跑 udev而是提前把设备节点静态放到/dev目录下。问题来了某个驱动支持多个实例但出厂节点里只放了前两个实例的节点用到第三个实例时就得手动补。调试某个新硬件驱动时板子上没有自动创建节点的机制只能靠 mknod 临时建一个来验证驱动是否正确。老内核没有 devtmpfs 支持devtmpfs 在 2.6.32 才合入主线的这种情况在旧款开发板、老旧内核里很常见。当年我调试一块摄像头传感器驱动时驱动加载完想用应用层工具抓图结果工具报Failed to open /dev/video0。我cat /proc/devices一看主设备号 81 的 video 驱动已经在列表里了但/dev/下没有 video0 节点。于是$ mknod /dev/video0 c 81 0工具立马能跑起来。就这一行命令省掉了一次整包固件重刷的时间。嵌入式调试时这个习惯能救命驱动加载不上的问题要查驱动日志节点不存在的问题先看/proc/devices根据结果 mknod 试试别一上来就怀疑驱动代码写错了。3.2 场景二容器和命名空间环境容器技术普及之后mknod 在多了一个特殊空间里的角色。在容器里即使你想建节点也会经常遇到mknod: Operation not permitted。这是因为容器默认的 seccomp 规则会过滤掉mknod这个系统调用同时容器进程的 capabilities 里没有CAP_MKNOD。在 Docker 环境里正确做法是运行时给容器加--device参数把宿主机已有的设备节点映射进去$ docker run --device/dev/ttyUSB0 -it ubuntu bash不过这个方案有个限制它映射的是宿主机上已经存在的设备节点。如果某个设备驱动在宿主机加载了但节点还没被创建比如 devtmpfs 尚未覆盖某个特殊次设备号容器里也无从映射。这种情况下经常要先在宿主机做好 mknod再映射进容器。还有一种情况就是容器里跑特权应用比如 Docker 里跑 Dockerdind这类镜像通常会以特权模式跑内部才有 mknod 的权限。就算有权限你也要能回答设备号多少驱动在内核里注册了吗所以说 mknod 在容器场景虽然不常用但真到出问题时能帮你判断问题到底出在容器权限还是内核驱动。3.3 场景三系统启动异常或设备节点丢失还有一类场景大家都希望永远不要遇到/dev目录里的节点意外丢失或者系统进入紧急模式。万一 udev 数据库损坏、devtmpfs 没挂上会出现整个/dev全是空的或者一片混乱的状态。此时系统的响应就是设备不存在。从运维角度讲先别急着重装系统。可以先确认内核设备驱动在不在$ ls -l /proc/devices如果驱动在就用 mknod 把关键节点补上。如果驱动不在mknod 建了也没用要回头处理驱动加载的问题。这套排查思路在救援模式、Live CD 环境里一样有效。4. 踩过的坑与排查思路设备节点起不来的五种原因mknod 命令本身不会输出什么成功信息它静默执行完就算成功。但建完节点就万事大吉的想法往往要被打脸。真实世界里排障过程远比命令本身复杂。下面我把这些年踩过的坑逐一列出来每一条都对应着完全不同的失败表现。4.1 坑一设备号搞错Open 时报 No such device or address这是最高频的坑。你在/proc/devices里看到的驱动主设备号和实际要填的数字大多数情况可以直接用但有两个例外驱动代码里用alloc_chrdev_region动态分配主设备号/proc/devices显示的是当前这一轮启动分配到的号重启后可能变。某些驱动会注册多个主设备号比如在/proc/devices里你看到180 usb和189 usb_device它们各管一类功能不能混用。有一次我调 USB 转串口cat /proc/devices看到188 ttyUSB确定主设备号是 188但次设备号拿不准。当时想当然填了 0结果应用 open 一直报No such device or address。后来看了驱动源码发现这个驱动用次设备号 0 表示未分配状态实际 ttyUSB0 的次设备号应该从 16 开始。这里的关键是次设备号完全由驱动作者定义没有统一规律最可靠的方式是参考同型号设备已有的正常节点用stat -c %t %T /dev/ttyUSB0查。4.2 坑二权限不够mknod 直接拒绝mknod 需要CAP_MKNOD权限普通用户默认是没有的。在普调 Linux 桌面上你直接敲$ mknod /tmp/testnull c 1 3 mknod: /tmp/testnull: Operation not permitted有人想到 sudo。sudo 能成但注意 sudo 之后创建的节点属主是 root后续普通用户能不能访问还得看节点权限。标准做法是建完后用 chown/chmod 调$ sudo mknod /tmp/testnull c 1 3 $ sudo chown root:root /tmp/testnull $ sudo chmod 666 /tmp/testnull还有一个提示如果你的分区挂载时带了nodev选项即使在 root 下mknod 也会失败。很多安全加固过的系统会在/tmp分区挂nodev来防止普通用户创建设备节点。查挂载参数$ mount | grep /tmp tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec)看到nodev就知道不是权限问题而是挂载策略的问题。老老实实换个目录建或者改挂载参数。4.3 坑三驱动加载顺序导致设备号漂移动态分配主设备号的驱动设备号在不同启动批次之间可能不一样。如果系统里的设备节点是某些脚本在启动阶段静态创建并写死在某个位置的那么一旦驱动加载顺序变了节点和驱动之间就对不上号了。典型现象驱动起来了节点也在但 open 就是报错dmesg 里驱动日志提示注册成功了但应用层就是访问不了。这种问题在 mknod 的语境下是无解的因为在驱动注册——devtmpfs 或 udev 同步创建设备节点这条链路里节点已经建好了但里面的设备号是之前那一轮启动的号。真正的解法是确认系统里处理设备节点的机制如果用的是 devtmpfs内核在驱动注册时直接生成节点不会用旧号如果用的是 udev则靠 udev rules 根据 sysfs 信息生成节点也不会漂移。只有脱离这两者、完全靠静态节点 手动 mknod 的极简系统才需要专门写脚本在启动阶段等驱动注册完再根据/proc/devices动态生成节点。4.4 坑四设备节点建了但驱动没加载前面我提过mknod 创建的是门牌号不是公司。如果驱动没加载光有节点也不行。排查链路通常是这样的# 1. 看节点在不在 $ ls -l /dev/myled crw-r--r-- 1 root root 240, 0 Jun 8 10:40 /dev/myled # 2. 看驱动主设备号在不在 $ grep myled /proc/devices 241 myled # 3. 发现系统里根本没有 myled 这个驱动 $ modprobe myled modprobe: FATAL: Module not found.对第一步建好节点第二步发现主设备号对不上第三步查内核模块。有的人看到第一步成功了就以为万事大吉结果应用层调了半天其实驱动压根没挂。这类问题不能靠 mknod 解决要靠驱动加载解决。不过反向思考一下如果/proc/devices里驱动已经在了但/dev下没有节点这时候 mknod 才有真正价值。所以 mknod 前先查/proc/devices是一个必须养成的习惯。4.5 坑五次设备号够用但不对主设备号决定驱动次设备号决定实例。同一个驱动下次设备号不同可能对应完全不同的硬件语义。比如sd这个块设备驱动主设备号为 8次设备号 0 对应/dev/sda16 对应/dev/sdb32 对应/dev/sdc。规律是每块盘占 16 个次设备号其中第一个次设备号代表整块盘后续次设备号代表盘上的分区。如果不知道这个规律随手把次设备号填成 1创建出来的/dev/sdX节点指向的可能就不是一块盘而是某块盘上的某个分区。看ls -l /dev/sda*brw-rw---- 1 root disk 8, 0 Jun 8 09:00 /dev/sda brw-rw---- 1 root disk 8, 1 Jun 8 09:00 /dev/sda1 brw-rw---- 1 root disk 8, 2 Jun 8 09:00 /dev/sda2所以次设备号不是能对上就行而是要严格按照驱动约定的语义来填。最稳妥的方式永远是从已知正常的节点上stat拷设备号而不是自己推算。5. 从 mknod 到 udev设备节点管理的自然演进我讲了这么多 mknod 的使用场景你可能会疑惑现代 Linux 为什么几乎不用手动 mknod 了这背后其实是一条清晰的技术演进路线理解了它才算真正理解 mknod 的历史位置和适用边界。5.1 静态节点时代早期 Unix 和早期 Linux 的/dev目录里装着一大堆预生成的静态设备节点安装系统时就把所有可能用到的设备节点全部 build 出来。缺点很明显节点数量庞大、占 inode设备号与驱动对应关系写死一旦驱动改了次设备号规划节点就全废了。5.2 devfs 时代的短暂尝试Linux 2.4 时代出现过 devfs由内核动态生成设备节点节点不再需要在用户态静态创建。但由于它把设备命名策略和内核耦合在一起存在很多兼容性和命名争议最终被废弃。5.3 devtmpfs udev 的现代方案现代 Linux 采用 devtmpfs udev 的组合。devtmpfs 由内核挂载驱动注册的时候自动在内存中生成对应的设备节点保证/dev下至少有一个默认名字的节点udev 则在用户态监听内核事件根据自定义规则对设备节点进行重命名、创建符号链接、设置权限。在这种组合下驱动一加载节点立刻出现你根本碰不到需要 mknod 的场合。除非 devtmpfs 没挂载成功、udev 崩溃、或者系统极度精简到两者都没有。理解这层演进历史你在排障时就能快速定位问题到底出在链路哪一环是内核没生成节点还是用户态规则没执行。5.4 现代排查设备节点问题的新姿势既然主流是 devtmpfs udev如果/dev下少了某个节点正确排查顺序就变了dmesg | grep 驱动名确认驱动是否注册成功、有没有报错。cat /proc/devices确认驱动是否在设备号表里。ls -l /sys/class/设备类别/确认同类设备的 sysfs 是否生成了设备对象。udevadm monitor实时查看内核发出的 uevent 事件有没有被 udev 收到。udevadm info -a /sys/...检查这个设备在 udev 规则里能否正确匹配。这套步骤比直接 mknod 更接近问题根源。手动 mknod 更像绕过问题而上面这套是定位问题。实际排障时我一般两种思路并行先看问题严重程度如果只是想尽快恢复业务mknod 顶上去如果想知道以后怎么不再犯就走 udev 排查。5.5 写 udev 规则替代手动 mknod如果你经常遇到某个设备节点名不对得手动建一个固定名的节点与其每次开机都 mknod不如写一条 udev 规则。举个例子假设你有一个 USB 转串口设备它的内核名是 ttyUSB0但你希望它固定叫 /dev/myusbKERNELttyUSB[0-9]*, SUBSYSTEMtty, ATTRS{idVendor}1a86, ATTRS{idProduct}7523, SYMLINKmyusb把这条规则写到/etc/udev/rules.d/99-myusb.rules里然后$ udevadm control --reload-rules $ udevadm trigger这样 nodename 由 udev 接管比手动 mknod 好维护得多。嵌入式系统如果跑不了完整 udev也可以用 BusyBox 里的mdev配合mdev.conf实现类似功能本质也是节点名管理自动化。我在实际项目里对 mknod 的态度是掌握它、理解它、但尽量别依赖它。它是最底层、最朴素的设备节点创建方式也是理解 Linux 设备模型的一把钥匙。跑通了 mknod 再回头看 udev 规则、container 的设备映射很多概念都是相通的。下次再遇到/dev下缺节点的情况先别慌cat /proc/devices看一眼答案往往就在那一行输出里。
返回列表