资讯动态

Linux mknod详解:手把手教你创建设备文件与恢复设备节点

发布时间:2026/10/17 3:43:25 来源:尧图企业网站定制
你可能永远用不到mknod但真正需要它的那天你会发现自己根本绕不开它。在Linux系统里设备节点正常情况下都由系统自动管理插上U盘、接入硬盘对应的设备文件就会自己出现在/dev目录下。但一旦你进入容器环境、系统救援模式或者遇到驱动静态注册设备号的情况会发现/dev目录里连个影子都没有这时候mknod就是你唯一能依赖的工具。它专门用来创建字符设备文件、块设备文件以及FIFO命名管道简单来说就是给一个真实的硬件设备在系统里“建档案”让内核知道这个设备节点对应哪个驱动、由谁处理。这篇文章会把mknod讲透从设备文件原理到主次设备号规则再到四个实战场景和常见坑的排查方法看完你就能在实际工作中稳稳上手。适合正在搞嵌入式开发、容器运维、设备驱动调试的朋友也适合所有想真正理解Linux设备管理机制的人。1. 先搞懂mknod背后的设备文件设计逻辑1.1 设备文件到底是什么为什么它不占磁盘空间Linux有一个经典的设计哲学一切皆文件。设备也不例外但这里的“文件”和你在/home目录下看到的普通文件完全不是一回事。设备文件实际上是一个访问入口存放在/dev目录下它不占用任何磁盘空间而是由内核在内存中维护的一种虚拟文件对象内部记录着这个设备节点对应的主设备号、次设备号以及设备类型。当你对设备文件执行open、read、write、close这些操作时内核并不会像处理普通文件那样去磁盘上找数据而是根据设备文件里记录的主次设备号去查找对应的设备驱动程序然后把你的读写请求转交给驱动由驱动和真实硬件交互。整个链路看起来就是在操作一个文件但底层的机制完全不同。初学Linux的时候我有一阵子没搞明白为什么ls -l /dev/sda显示出来的大小不是容量大小而是两个逗号隔开的数字。后来才知道那两个数就是主设备号和次设备号。理解了这一点后面再学mknod就会非常顺——它做的事情本质上就是替你创建这样一个“设备档案”。1.2 字符设备与块设备的本质区别mknod可以创建两种硬件设备文件字符设备文件c或u和块设备文件b。两者最核心的区别在于数据传输方式。字符设备按字节流传输数据一次一个字节按顺序读写没有缓冲机制数据量小、但时序敏感。典型的字符设备有键盘、鼠标、串口、终端、声卡等。你在终端上敲下的按键就是一个字符一个字符送到系统里的。字符设备的优点是实现简单、响应直接但吞吐能力有限不适合大规模数据传输。块设备则不同它以固定大小的数据块block为单位进行传输每次读写都涉及一批字节而且内核为块设备提供了页缓存机制读写数据会先在内存里做缓冲和调度再批量写入磁盘。典型的块设备包括硬盘、U盘、SSD、光盘等。块设备追求的是高吞吐量适合存储类场景。打个比方字符设备就像便利店你来买一瓶水我直接递给你一瓶交易简单但每次的量就那么大块设备就像大型仓储超市进货出货都是整托盘、整集装箱来追求的是批量效率。这个区别直接决定了你用mknod时必须给对类型。如果你把一个字符设备错误地创建成块设备文件那么这个节点即使创建成功打开它访问数据时驱动根本没法处理你的请求报错几乎是必然的。所以动手之前先确认你要操作的那类设备到底属于什么类型。1.3 主设备号与次设备号的作用创建设备文件时必须指定主设备号和次设备号而这两个数字就是设备文件最核心的属性。主设备号用来标识设备对应的驱动程序。内核中每个已注册的驱动都会被分配一个主设备号当系统访问设备文件时内核凭借主设备号找到对应的驱动。次设备号则用来区分同一个驱动管理下的不同设备实例。举例来说一块SCSI磁盘被分成多个分区它们的驱动都是同一套SCSI驱动主设备号都是8但每个分区的次设备号不同内核才能区分你操作的是哪块磁盘的哪个分区。用生活里的例子类比主设备号相当于公司的总机电话号码次设备号是总机转接各分机的分机号。你拨通总机找到驱动再拨分机号找到具体设备才能建立联系。设备文件创建过程就是在告诉内核“这个设备由你管具体管哪一个实例看次设备号。”在/dev目录里执行ls -l你会看到类似这样的输出brw-rw---- 1 root disk 8, 1 Jun 22 10:11 sda1 crw-rw-rw- 1 root root 1, 3 Jun 22 10:11 null第一列开头是b说明sda1是块设备是c则代表null是字符设备。在后面用逗号隔开的8, 1和1, 3就是这对主次设备号。1.4 何时需要手动创建几个真实应用场景有人会问现代Linux都有udev它会自动管理/dev目录还有必要手动执行mknod吗答案是有而且一些场景只有mknod能救场。第一系统进入救援模式或单用户模式时/dev目录可能不完整关键节点缺失。如果连/dev/null、/dev/console都没有很多系统工具直接罢工你需要手动创建基础节点才能继续排查。第二容器和chroot环境。大多数容器不会运行udev/dev目录的内容是创建容器时预先放进去的。一旦某个镜像精简过头缺少程序运行所需的设备节点你就得自己mknod补上。第三嵌入式开发和驱动调试。设备驱动如果采用静态设备号分配内核加载时不会自动生成节点。驱动加载成功了但/dev下没有对应文件应用层完全无法访问这时候mknod就是连接驱动和应用的桥梁。第四批量导出同一驱动的多个实例节点。比如一个驱动管理了4路ADC或8个LED你以为系统会帮你生成/dev/myled0到/dev/myled7实际上如果不配置udev规则它们根本不会出现。写一个for循环配合mknod一秒就把所有节点建好了。第五自定义设备节点路径。你想在某个特定的目录下建一个指向现有设备的节点或者临时做一个和设备模型无关的节点用mknod是最直接的方式。2. 掌握设备号规则mknod语法与分配方式2.1 命令语法与参数详解mknod的语法非常简洁整个命令的所有参数都是一次性给的mknod [选项] 设备名 类型 [主设备号 次设备号]设备名指的是要创建的节点路径比如/dev/mychar。类型有三种b块设备文件c或u字符设备文件u是历史遗留写法兼容旧系统pFIFO命名管道创建时不需要指定主次设备号常用选项主要有两个-m 权限创建设备文件的同时指定权限例如-m 666会直接生成rw-rw-rw-的权限省去后续chmod的步骤。-v显示执行过程的详细信息确认命令执行结果。举个例子来说明。创建字符设备文件sudo mknod /dev/mychar c 240 0意思是创建一个名叫/dev/mychar的字符设备节点主设备号240次设备号0。创建块设备文件sudo mknod /dev/myblock b 7 200意思是创建块设备节点/dev/myblock主设备号7次设备号200。7号主设备对应的是loop设备驱动所以这个节点可以当成一个loop设备来使用。创建FIFO更简单mknod /tmp/myfifo pFIFO不关联真实硬件只做进程间通信用普通用户权限就能创建前提是对目标目录有写权限。它在语义上比匿名管道更方便因为所有进程都能通过路径找到它。2.2 三种方法精确确定主次设备号手动创建设备节点最让人头疼的问题就是主设备号和次设备号填什么填错了就算节点建出来也是废的。我整理了三种可靠的方法。方法一查看/proc/devices。这个文件记录了当前内核里已经注册的所有字符设备和块设备的主设备号输出内容大致如下Character devices: 1 mem 4 /dev/vc/0 5 /dev/tty 7 vcs 10 misc 13 input 21 sg ... Block devices: 7 loop 8 sd 9 md 11 sr 254 device-mapper左边是主设备号右边是驱动名称。看到某个设备的驱动在这个列表里你就知道它的主设备号是多少了。次设备号则需要根据你要访问的具体实例来确定一般从0开始递增。方法二参考现有的同类设备节点。比如你想在另一个目录创建一个和/dev/sda1指向相同设备的节点直接执行ls -l /dev/sda1从输出里抄下主设备号8和次设备号1就行。这个方法最稳妥因为照着已有节点抄设备号绝对不会错。要注意的是设备名和类型也要和实际一致别抄着抄着把sda写成了sdb。方法三查内核源码或驱动文档。如果你在编写或调试自己的驱动驱动代码里会明确指定静态分配的主设备号如果是动态分配驱动加载后通过/proc/devices就能查到分配结果然后按需创建节点。嵌入式开发里这个场景非常常见驱动作者通常会把建议的创建命令写在README里。2.3 常用设备号速查表为了在实操中节省时间我把Linux中常见的设备主设备号整理成了速查表。注意不同内核版本和架构下数值可能有差异最终以/proc/devices为准这张表作为参考足够了。主设备号对应驱动类型典型节点示例节点类型1内存设备/dev/null、/dev/zero、/dev/mem字符设备3传统IDE硬盘/dev/hda新系统较少见块设备4终端设备/dev/tty0、/dev/tty1字符设备5替代设备/dev/tty、/dev/console、/dev/ptmx字符设备7loop设备/dev/loop0、/dev/loop1块设备8SCSI/SAS/SATA/USB存储/dev/sda、/dev/sdb块设备9软件RAID设备/dev/md0块设备10杂项设备/dev/misc字符设备13输入设备/dev/input/mice、/dev/input/event0字符设备21SCSI磁带机/dev/st0字符设备29帧缓冲设备/dev/fb0字符设备90音频设备/dev/dsp老旧系统字符设备179SD/MMC设备/dev/mmcblk0块设备180USB字符设备/dev/ttyUSB0、/dev/hidraw0字符设备188USB串口设备/dev/ttyUSB0字符设备254Device Mapper设备/dev/dm-0LVM逻辑卷块设备这张表里有几行容易混淆。首先是USB串口设备的180和188我在某些系统上看到过不同的分配方式所以最靠谱的方法是查/proc/devices确认不要硬记。其次是ttyUSB0有的发行版主设备号用的188有的用180建错直接用不了。3. 四个实战场景带你上手mknod3.1 场景一在容器环境中创建基础设备节点先讲一个最常见的实战需求在容器或chroot环境中补全设备节点。很多精简镜像里/dev目录几乎是空的程序跑起来缺设备节点报出一堆看不懂的错误。其实mknod几分钟就能搞定。假设我在宿主机上模拟一个chroot环境目录是/tmp/chroot-test在/dev为空的情况下先创建最基础的那几个节点mkdir -p /tmp/chroot-test/dev sudo mknod /tmp/chroot-test/dev/null c 1 3 sudo mknod /tmp/chroot-test/dev/zero c 1 5 sudo mknod /tmp/chroot-test/dev/random c 1 8 sudo mknod /tmp/chroot-test/dev/urandom c 1 9 sudo mknod /tmp/chroot-test/dev/tty c 5 0这些主次设备号是从哪来的我并没有凭记忆硬填而是先在自己的系统上执行ls -l /dev/null确认了null的主设备号是1、次设备号是3然后照搬到目标目录。不同系统的设备号可能有细微差异这是一个非常稳妥的习惯先参考现有节点再创建新节点。节点创建出来后还要设置权限。因为mknod默认创建出来的节点权限受umask影响不一定是程序需要的权限所以应该主动调整sudo chmod 666 /tmp/chroot-test/dev/null sudo chmod 666 /tmp/chroot-test/dev/zero sudo chmod 666 /tmp/chroot-test/dev/random sudo chmod 666 /tmp/chroot-test/dev/urandom sudo chmod 666 /tmp/chroot-test/dev/tty也可以直接在mknod时用-m参数一步到位比如sudo mknod -m 666 /tmp/chroot-test/dev/null c 1 3效果一样。验证环节很简单。写入null设备测试echo hello /tmp/chroot-test/dev/null没有报错说明null设备正常。再用dd从zero设备读4个字节用xxd查看内容全是00说明zero设备也正常dd if/tmp/chroot-test/dev/zero of/tmp/test_zero.bin bs1 count4 xxd /tmp/test_zero.bin注意 在容器环境里即使你手动创建了设备节点容器本身还必须具备访问对应设备的权限。如果容器不是运行在privileged模式下也没有通过cgroup规则放宽设备访问限制那么操作设备时依然会被内核拒绝。mknod只是创建了节点不代表权限也同时打通了。这个坑很多人踩过排查的时候先看容器启动参数和cgroup规则。3.2 场景二创建一个可用的loop设备loop设备在Linux里用途广泛挂载镜像文件全靠它。正常情况下udev会自动生成/dev/loop0、/dev/loop1等节点但如果你处在一个精简的容器环境里或者系统启动时某些节点没被创建就需要手动补齐。先检查当前系统里loop设备的情况ls /sys/block/ | grep loop如果没有任何输出说明loop节点完全缺失。创建方法sudo mknod /dev/loop0 b 7 0 sudo mknod /dev/loop1 b 7 1 sudo mknod /dev/loop2 b 7 2主设备号7是所有loop设备驱动的统一编号次设备号从0开始递增对应loop0、loop1、loop2。权限建议设置为660sudo chmod 660 /dev/loop0 /dev/loop1 /dev/loop2创建完别急着收工验证一下这个节点能不能真正工作。先制作一个最小镜像dd if/dev/zero of/tmp/test.img bs1M count1 mkfs.ext4 /tmp/test.img然后关联到loop设备sudo losetup /dev/loop0 /tmp/test.img sudo fdisk -l /dev/loop0如果fdisk能识别出这个设备哪怕提示没有分区表也说明loop设备节点工作正常。这个验证流程能帮你确认自己使用mknod创建出来的节点真的有在跟内核驱动通信而不只是存在一个空文件。3.3 场景三创建节点后正确设置权限并验证有效性设备文件创建出来之后权限设置是很关键的一步。因为默认创建的节点根据你当时的umask权限可能很保守。如果你的应用需要任意用户都能访问就必须主动设置权限。我创建一个测试节点然后查看它的初始状态sudo mknod /tmp/testdev c 240 0 ls -l /tmp/testdev像这样的输出很常见crw-r--r-- 1 root root 240, 0 Apr 8 20:10 /tmp/testdev第一列的c代表字符设备后面的240, 0就是设备号。权限是crw-r--r--意思是root用户可读写其他用户只能读而很多设备需要读写双向操作因此要执行sudo chmod 666 /tmp/testdev ls -l /tmp/testdev此时权限就变成crw-rw-rw-了。为了减少步骤也可以创建时直接指定sudo mknod -m 666 /tmp/testdev c 240 0验证节点有没有真正连上驱动可以执行stat /tmp/testdev输出里会明确显示设备类型是字符设备、主设备号240、次设备号0。然后配合cat /proc/devices确认240对应的驱动确实存在grep 240 /proc/devices有输出说明驱动在线这个节点是活的。如果没有输出说明内核里根本没注册这个设备号对应的驱动节点建得再标准也无法工作。这个排查思路很重要能帮你快速区分“节点问题”和“驱动问题”。3.4 场景四用for循环批量创建同主设备号的节点在驱动开发或嵌入式调试中经常需要一次性创建一批设备节点。比如某个驱动管理了4路ADC、8个LED如果一个个手敲mknod效率太低还容易出错。用for循环配合变量就可以完美解决。假设这个ADC驱动的主设备号是240次设备号从0开始递增共4路设备for i in 0 1 2 3; do sudo mknod -m 666 /dev/adc$i c 240 $i done执行完/dev/adc0到/dev/adc3四个节点全部创建完成。想更灵活一点次设备号按某个步长递增也行比如每路设备间隔两个次设备号for i in 0 1 2 3; do minor$((i * 2)) sudo mknod -m 660 /dev/ctl$i c 240 $minor done这种批量创建方式也适合给多个loop设备建块设备节点。如果同时需要两类节点字符和块可以在循环里加条件判断。注意一点批量创建时要确认设备号范围不与其他已存在的节点冲突否则会出现两个文件名指向同一设备的尴尬情况。4. 避坑指南常见错误与排查方法4.1 权限不够怎么办Operation not permitted使用mknod时最常见的报错就是Operation not permitted。普通用户默认无法创建块设备或字符设备文件必须使用root权限。解决办法很简单加上sudo或者切换到root用户操作。但还有两个隐蔽原因会触发同样的报错。第一文件系统挂载了nodev选项。如果你在一个nodev挂载点的目录里执行mknod即使你有root权限系统也会拒绝创建设备文件。检查方法mount | grep /tmp如果输出里带有nodev字样说明该目录不允许创建设备文件。此时换个挂载选项正常的目录就可以。第二安全模块限制。某些系统配置了SELinux或AppArmor策略即使root用户也可能没有创建设备节点的能力。排查时先检查SELinux状态getenforce如果是Enforcing状态可以临时用setenforce 0排查问题确认后就调整策略或路径。生产环境不建议长期关闭安全模块找到合适的策略配置才是正道。还有一个低级但常见的错误在已有同名普通文件或目录的路径上执行mknod会提示File exists。这时候先确认旧文件真的没用了再删除rm -f /tmp/testdev sudo mknod /tmp/testdev c 240 0千万别在/dev目录里随手rm文件如果不确定一个设备节点是否被系统引用先查询、再确认、最后才动手。4.2 设备号对不上No such device or address设备文件创建成功了一访问就报No such device or address这种情况非常有意思——节点本身没毛病但主设备号对应的驱动不存在。说白了就是设备号填错了。拿ttyUSB0举例。我想为USB转串口设备创建节点sudo mknod /dev/ttyUSB0 c 188 0标准Linux里USB串口的主设备号是188大多数系统上这么建是对的。但如果你的系统里这个驱动的主设备号实际是180或者已经被其他驱动占用那么创建出来的节点打开时就会报错。解决办法是先用ls /sys/class/tty/看看有没有ttyUSB0这个设备。如果sysfs里能看到设备但/dev下没有节点说明硬件确实存在只是节点缺失。然后参考/proc/devices确认设备号再创建grep usb /proc/devices输出里会列出实际的主设备号按这个数来创建节点就不会错。还有一种情况需要留意内核里有两个不同的驱动使用了相同的设备号这会导致设备节点被错误的驱动接管。这种冲突很难从用户空间排查通常需要检查内核日志。多了解一些设备和驱动的对应关系对排查这类问题有帮助。4.3 mknod创建的设备在重启后失效怎么办不少朋友遇到过这样的疑问我明明用mknod创建了设备节点当时工作正常重启之后节点就不见了。这其实是正常现象不是命令出了问题。现代Linux发行版在启动过程中会启动udevudev会按照规则重新构建整个/dev目录。你在udev规则之外手动添加的“野节点”重启后自然会被清除。udev不认这些节点因为它不知道它们该不该存在。如果你希望某个设备节点永久保留有几种做法。最推荐的是写udev规则。在/etc/udev/rules.d/目录下新建一个规则文件比如99-custom-device.rules内容可以是这样KERNELmydev, MODE0666这样udev在遇到名为mydev的内核设备时会自动创建节点并设置权限。如果你的场景是驱动开发更彻底的做法是在驱动内部调用device_create()函数创建设备接口让udev自动接管节点生命周期。mknod适合应急和排障但产品级交付还是应该交给设备模型自动管理。4.4 FIFO使用的三个隐性陷阱FIFO创建本身非常简单mknod /tmp/myfifo p一行命令就搞定没有主次设备号的烦恼。但在实际使用中FIFO有几个非常容易踩的坑我在这里单独拎出来说。第一个坑是打开FIFO时的阻塞行为。默认情况下以只读方式打开FIFO会一直阻塞直到有另一个进程以写方式打开同一个FIFO。反过来以只写方式打开也会阻塞直到有进程以只读方式打开。这个设计是为了保证管道两端同步但如果你只开了一端进程就会卡住看起来像死锁一样。解决办法是使用非阻塞标志O_NONBLOCK打开FIFO或者以O_RDWR方式打开后者不阻塞但破坏了FIFO的语义平时不建议。写多进程通信代码时一定要考虑清楚两端的打开顺序。第二个坑是写端关闭后读端会收到EOF。比如一个进程在持续读取FIFO如果写端进程意外退出读端下一次read操作会返回0表示数据流结束。没有经验的人会把这个EOF误当成正常的数据结束直接退出循环导致后续逻辑错误。所以在设计协议时要明确区分正常结束和异常断开。第三个坑是权限问题。FIFO创建时权限受umask影响如果两个不同用户的进程需要通信创建FIFO时就要赋予双方都能读写权限。可以用mknod -m 666 /tmp/shared_fifo p或者用mkfifo -m 666命令效果一样。这种细节放到生产环境里可能就是一次线上事故的源头提前想到能省很多麻烦。4.5 mkfifo vs mknod如何选择mknod和mkfifo都支持创建FIFO但使用习惯上多数人更推荐mkfifo。原因很简单mkfifo专门做FIFO创建这件事语法更直观默认行为也更贴近常规工具的习惯。mkfifo -m 666 /tmp/myfifo一行命令就完成了而mknod需要你记得用p类型参数mknod -m 666 /tmp/myfifo p功能上是等价的但mkfifo更容易读、不容易误解。如果你只是创建FIFO直接用mkfifo就好如果是在脚本里统一处理多种设备文件类型字符、块、FIFO用一个mknod搞定所有需求也完全可以。工具选型的核心原则是“在合适的场景用合适的工具”没有一个命令在所有场景里都是最优解。最后再分享一点经验mknod的语法简单到只有一条命令、几个参数但它背后连接的是一整套Linux设备模型的设计逻辑。用了这些年我最深的体会是真正难的不是敲命令而是搞清楚设备号怎么对、udev什么时候会接管、权限怎么给才安全、以及异常情况下怎么快速恢复。这几个层面每个都能写一篇长文mknod只是设备管理体系里的冰山一角。建议大家平时在虚拟机里做一个小练习手动用mknod创建/dev/null、/dev/zero、/dev/random、/dev/tty这几个基础节点然后分别尝试读写直观感受一下设备文件到底是怎么和内核驱动交互的。另外动手改动/sys和/dev目录之前先收集好完整的现场信息别急着删和建。真到了系统救援、容器排障、驱动调试的关键时刻扎实的mknod功底能帮你省下宝贵时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑