首页
/ SerenityOS 内核中的 /dev/zero 设备:实现原理、设备号与实用示例

SerenityOS 内核中的 /dev/zero 设备:实现原理、设备号与实用示例

2026-09-10 11:08:30作者:裴锟轩Denise

/dev/zero 是 SerenityOS 提供的一个字符设备文件,读它永远得到全 \0 字节流,写它则被静默丢弃。本文以 zero(4) 手册页 为骨架,深入 ZeroDevice 内核实现 与设备号分配机制,帮助你理解数据汇(data sink)设备在内核中的工作方式,并掌握在 SerenityOS 中使用与手动创建 /dev/zero 的完整方法。

Name 与核心语义:什么是 /dev/zero

手册页 zero(4) 给出的定义非常精炼:

zero - data sink(数据汇)

/dev/zero 是一个字符设备文件,其行为可以概括为两条规则:

  • 写入即丢弃:任何写入 /dev/zero 的数据都会被直接抛弃,不做存储、不做处理;
  • 读取返回全零:从 /dev/zero 读取时返回 '\0' 字节(即值为 0 的字节),读取操作正常结束,进程退出状态为 0。

在 POSIX 风格系统中,/dev/zero 通常与 /dev/null(黑洞数据汇)和 /dev/full(恒满设备)并列为三类典型的“虚拟字符设备”。SerenityOS 的 null(4)full(4) 手册页与之互相引用,共同构成该主题的完整文档。

内核实现:ZeroDevice 的读取与写入

/dev/zero 的设备节点背后是内核类 ZeroDevice,其完整实现位于 Kernel/Devices/Generic/ZeroDevice.cpp,声明位于 Kernel/Devices/Generic/ZeroDevice.h

从类声明可以看出它是一个典型的字符设备:

class ZeroDevice final : public CharacterDevice {
    // ^CharacterDevice
    virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override;
    virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override;
    virtual bool can_read(OpenFileDescription const&, u64) const override;
    virtual bool can_write(OpenFileDescription const&, u64) const override { return true; }
    virtual StringView class_name() const override { return "ZeroDevice"sv; }
};

read():批量填充零字节

read 的实现是整个设备的核心,仅有三行:

ErrorOr<size_t> ZeroDevice::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size)
{
    TRY(buffer.memset(0, size));
    return size;
}

它直接调用 UserOrKernelBuffer::memset 将用户缓冲区的前 size 个字节全部清零,然后返回 size,表示“已读取”的字节数等于请求的字节数。由于 can_read() 恒返回 true,进程对 /dev/zero 的读取永远不会遇到 EOF,可以无限地读取零字节流。

write():数据汇,写入即成功丢弃

ErrorOr<size_t> ZeroDevice::write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t size)
{
    return size;
}

write 不去触碰传入的缓冲区内容,直接返回 size,表示“已写入”全部字节——这正是“data sink(数据汇)”一词的含义:数据被吸收、丢弃,且写操作总是成功。

设备号的来源:Generic 家族,minor 5

设备构造时指定了设备号:

ZeroDevice::ZeroDevice()
    : CharacterDevice(MajorAllocation::CharacterDeviceFamily::Generic, 5)
{
}

也就是说,/dev/zero 的主设备号(major)来自 Generic 字符设备家族,次设备号(minor)为 5。

设备号分配:MajorNumberAllocation 机制

主设备号的分配集中管理在 Kernel/API/MajorNumberAllocation.h 中。该文件定义了 CharacterDeviceFamily 枚举,并明确约定:

  • Generic = 1(通用字符设备家族,/dev/null/dev/zero/dev/full 均属于此家族);
  • 后续依次为 DeviceControl = 2Serial = 4Console = 5Mouse = 10GPURender = 28VirtualConsole = 35Keyboard = 85Audio = 116MasterPTY = 200SlavePTY = 201GPU = 226VirtIOConsole = 229 等。

代码中还有编译期 static_assert 保证这些家族编号按升序排列,避免主设备号冲突。同类“数据汇”设备在 Generic 家族中的次设备号分布如下(来自各设备构造代码):

设备节点 主设备号 次设备号 文件 行为
/dev/null NullDevice 1(Generic) 3 NullDevice.cpp 读取返回 0(EOF),写入全部丢弃
/dev/zero ZeroDevice 1(Generic) 5 ZeroDevice.cpp 读取返回 \0 流,写入全部丢弃
/dev/full FullDevice 1(Generic) 7 FullDevice.cpp 读取返回 \0 流,写入返回 ENOSPC

三者对比可见设计上的分工:/dev/null 读返回 EOF、/dev/zero 读返回零字节、/dev/full 写返回 ENOSPC(磁盘已满错误),覆盖了程序测试与错误处理演练的常见需求。

启动注册:设备在何时创建

ZeroDevice 实例在系统启动早期由 Kernel/Arch/init.cpp 创建:

(void)MemoryDevice::must_create().leak_ref();
(void)ZeroDevice::must_create().leak_ref();
(void)FullDevice::must_create().leak_ref();
(void)FUSEDevice::must_create().leak_ref();
(void)RandomDevice::must_create().leak_ref();

must_create() 内部通过 Device::try_create_device<ZeroDevice>() 完成实例化,配合 UNMAP_AFTER_INIT 标记,使该设备的初始化代码在引导完成后可从内核映像中释放。与 RandomDevice(随机数设备)、PTYMultiplexer(伪终端)等一同在存储管理初始化前被注册,保证用户态一启动即可访问 /dev/zero

另外值得注意:ZeroDevice::is_openable_by_jailed_processes() 返回 true,说明该设备允许被 jailed(受限)进程打开,属于内核信任的通用基础设备。

实际使用:从手册页示例到完整操作

手册页自带示例

zero(4) 中的示例展示了最基本的验证方式:

$ head -c 8 /dev/zero | hexdump
00 00 00 00 00 00 00 00

head -c 8/dev/zero 读取恰好 8 个字节,经管道交给 hexdump 后可见 8 个 00 字节——这正是 read()memset(0, size) 的直接体现。

生成固定大小的零填充文件

在 SerenityOS 的 Shell 中,可以用 dd 借助 /dev/zero 创建任意大小的零填充文件:

dd if=/dev/zero of=blank.bin bs=1024 count=1024

该命令会生成一个 1 MiB(1024 × 1024 字节)的全零文件,常用于:

  • 为后续格式化或文件系统操作准备空白镜像;
  • 测试磁盘与文件系统对“大文件预分配”的处理;
  • 抹除敏感数据前的占位填充(配合 /dev/random 等随机源交替使用)。

手动重建设备节点

full(4) 手册页给出的方式一致,若 /dev/zero 节点缺失或损坏,可以用 mknod 手动重建:

mknod /dev/zero c 1 5
chmod 666 /dev/zero

其中:

  • c 表示创建字符设备文件(字符设备在 SerenityOS 中也可写作 u);
  • 1 是主设备号,对应 MajorAllocation::CharacterDeviceFamily::Generic
  • 5 是次设备号,对应 ZeroDevice
  • chmod 666 保证所有用户可读可写。

mknod 是 Userland 的标准工具,实现见 Userland/Utilities/mknod.cpp,支持 -m mode 参数指定八进制或符号权限(默认 0666),支持 b(块设备)、c/u(字符设备)、p(FIFO)四种类型,并在创建 FIFO 时禁止指定设备号。例如显式指定权限的等价写法:

mknod -m 666 /dev/zero c 1 5

注意事项与常见误区

  • 读取不会遇到 EOFcan_read() 恒为 true,因此 cat /dev/zerodd if=/dev/zero 不指定 count 时会无限输出,务必配合 head -c Ndd bs=... count=... 等限定字节数的手段;
  • 写入恒成功:对 /dev/zero 写数据不会返回错误,与 /dev/fullENOSPC 行为形成鲜明对比,适合用来测试程序对“写入成功但数据被丢弃”场景的容错;
  • 设备号不要混淆/dev/null1 3/dev/zero1 5/dev/full1 7,三者同属 Generic 家族但次设备号不同,手动 mknod 时写错 minor 会导致行为不符合预期。

相关手册页

结合阅读这三份手册页与对应的 Generic 设备内核实现,即可完整掌握 SerenityOS 虚拟字符设备的全貌:从设备号分配、内核 read/write 语义,到用户态的实际使用与节点重建。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527