news 2026/9/9 23:55:44

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 [选项] 设备文件名 类型 [主设备号 次设备号]

类型就三个取值:bcp。如果类型是bc,必须跟主设备号和次设备号;类型是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, 64,ttyS1 是 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 0

2.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/nullcrw-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 里跑 Docker(dind),这类镜像通常会以特权模式跑,内部才有 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 usb189 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/sda,16 对应/dev/sdb,32 对应/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下少了某个节点,正确排查顺序就变了:

  1. dmesg | grep <驱动名>:确认驱动是否注册成功、有没有报错。
  2. cat /proc/devices:确认驱动是否在设备号表里。
  3. ls -l /sys/class/<设备类别>/:确认同类设备的 sysfs 是否生成了设备对象。
  4. udevadm monitor:实时查看内核发出的 uevent 事件有没有被 udev 收到。
  5. udevadm info -a /sys/...:检查这个设备在 udev 规则里能否正确匹配。

这套步骤比直接 mknod 更接近问题根源。手动 mknod 更像"绕过问题",而上面这套是"定位问题"。实际排障时我一般两种思路并行:先看问题严重程度,如果只是想尽快恢复业务,mknod 顶上去;如果想知道以后怎么不再犯,就走 udev 排查。

5.5 写 udev 规则替代手动 mknod

如果你经常遇到"某个设备节点名不对,得手动建一个固定名的节点",与其每次开机都 mknod,不如写一条 udev 规则。举个例子,假设你有一个 USB 转串口设备,它的内核名是 ttyUSB0,但你希望它固定叫 /dev/myusb:

KERNEL=="ttyUSB[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="myusb"

把这条规则写到/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看一眼,答案往往就在那一行输出里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 23:55:15

WLED 灯带控制指南:让 ESP32 三步变身智能照明控制器

WLED 灯带控制指南&#xff1a;让 ESP32 三步变身智能照明控制器 【免费下载链接】WLED Control WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi! 项目地址: https://gitcode.com/GitHub_Trending/wl/WLED 想让灯带跟着音乐变色、在手机上随…

作者头像 李华
网站建设 2026/9/9 23:53:19

混合流水车间多目标调度:NSGA-II与多种启发式解码

做排产调度的人应该都有过这种体验&#xff1a;流水车间&#xff08;Flow Shop&#xff09;问题本身还能靠经典规则硬解&#xff0c;一旦换成混合流水车间&#xff08;Hybrid Flow Shop&#xff09;&#xff0c;阶段里塞进多台并行机&#xff0c;解空间立刻膨胀。要是再叠加一道…

作者头像 李华
网站建设 2026/9/9 23:52:18

3D视觉引导的柔性智能喷涂方案:核心架构与落地实践

1. 核心能力速览能力项说明方案定位面向非标多品种工件的智能喷涂整体解决方案核心技术3D视觉感知 机器人轨迹规划 离线工艺管理适配工件非标件、多品种、小批量、乱序上料的工件主要功能3D识别定位、型号自动区分、喷涂轨迹自适应、快速换产部署方式产线集成 / 机器人工作站…

作者头像 李华
网站建设 2026/9/9 23:51:48

结构化并发:从goto到并发任务的生命周期管理

从 “goto” 到 “结构化并发”&#xff0c;一个编程术语的诞生记学编程的人大多听过“结构化编程”这个词&#xff0c;它说的是用顺序、分支、循环三种基本结构来组织代码&#xff0c;替代当年让人头疼的“goto 满天飞”。但近几年&#xff0c;你在看 Kotlin、Java、Swift 这些…

作者头像 李华