当前位置:首页>Linux>Linux文件操作命令内存占用深度分析

Linux文件操作命令内存占用深度分析

  • 2026-09-02 15:54:30
Linux文件操作命令内存占用深度分析
Hello,大家好,我是程序媛MM。

本文约3400字,今天继续在深度优化ota升级中对临时文件的操作处理,现在除了最开始传输的升级包后,后续一切只操作内存中存放了升级包数据的buffer,代码中有一处mv操作,有点疑惑,在mv操作过程中到底是会有两份内存还是只有一份,于是基于这个疑惑整理了所有对文件操作的命令中对内存占用的情况,一起来学习,看是否有颠覆认知的知识。

关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。


你是否遇到过一个 cp 命令让设备内存飙升?跨分区 mv 大文件导致业务 OOM?别再把 buff/cache 当成内存泄漏了。本文将深度分析高频文件命令的底层内存行为,一起来学习内存占用的真相。


📌 摘要

Linux 文件操作的内存占用分为 用户进程私有内存 和 内核页缓存(Page Cache) 两部分。绝大多数运维与开发人员混淆二者,常误判内存泄漏、缓存占用、大文件拷贝 OOM 等问题。

本文深入分析p、mv、rm、cat、dd、tar 等高频文件命令的底层系统调用、页缓存行为及内存峰值特征,区分同分区/跨分区操作差异,并附上故障排查与内存优化方案,让你从此不再“背锅”。


🧱 前置基础概念

1. 两类内存的隔离边界

类型
归属
生命周期
观测方式
用户态内存
(进程堆/栈)
命令程序自身申请的缓冲区
进程退出即释放
top
/ps 中的 RSS/VIRT
内核页缓存
(Page Cache)
内核统一管理的全局文件缓存
内存不足时由内核 LRU 自动回收
free 命令中的buff/cache

⚠️ 页缓存不属于任何进程,它是 Linux 设计的高速 I/O 中转站,不会触发 OOM,是“空闲内存”的优雅复用。


2. 核心底层对象

  • inode:存储文件元数据(大小、权限、磁盘数据块指针),不包含文件名。
  • dentry(目录项):文件名 + inode 指针,存在于目录文件中。

🔑 关键规则:

  • 同文件系统(同分区):inode 全局唯一,移动文件仅修改 dentry,不拷贝数据。
  • 跨文件系统(跨分区/磁盘):inode 隔离,必须完整拷贝文件数据块。

一 mv 命令:两种场景,内存行为天壤之别

📍 场景1:同分区/同文件系统移动(mv src dst)

底层仅调用 rename() 系统调用,全程无文件数据读写。

  • 用户进程内存:仅少量栈内存存储路径字符串,无文件数据缓冲区,内存峰值恒定 几 KB,与文件大小无关。
  • 内核页缓存:不新增任何缓存副本。源、目标文件名指向同一个 inode,页缓存内仅保留一份文件数据,无内存翻倍;仅更新目录项元数据缓存,增量极小。

✅ 结论:同分区mv不会产生双份内存,毫秒级完成,超大文件也毫无压力。


📍 场景2:跨分区/跨磁盘移动(如 /home → /mnt/usb)

底层等价于 cp + rm,流程:copy_file_range 全量拷贝 → 写入目标分区 → unlink 删除源文件。

  • 用户进程内存:mv 自带固定大小读写缓冲区(默认数十 KB),不会一次性加载整个文件到用户堆。
  • 内核页缓存(关键:短暂双份缓存):
    • 读源文件 → 数据加载至缓存 A
    • 写新文件 → 相同数据写入目标分区缓存 B
    • 操作中途,页缓存同时存在两份完全相同的数据,buff/cache 临时上涨。
    • 拷贝完成执行 unlink:源文件 inode 引用计数清零,内核后台逐步回收缓存 A,最终仅保留目标文件缓存。

⚠️ 风险点:跨分区移动 TB 级大文件会短时吃掉大量缓存,挤压业务可用内存,需格外注意。


二 cp 命令:必然产生双份页缓存副本

cp 逻辑为循环 read + write,GNU cp 默认使用 copy_file_range 内核零拷贝。

1. 用户进程内存

采用固定分块缓冲区(默认 64KB/128KB),不会一次性 malloc 整个文件,用户态内存稳定,不会因大文件撑爆进程内存。

2. 内核页缓存(核心痛点)

源文件数据进入页缓存 A,写入目标文件生成全新缓存 B,两份缓存永久共存(除非手动删除源文件)。

  • 📌 示例:复制一个 10GB 文件后,buff/cache直接上涨 10GB;多次复制会持续叠加缓存副本。
  • 🔍 即便使用 copy_file_range 零拷贝优化,也仅消除用户态拷贝,无法避免内核生成两份独立页缓存,缓存占用无优化。

补充:cp --sparse 稀疏文件

仅跳过空洞零块写入,缓存占用小幅降低,但非空洞数据依旧双份缓存。


三 rm 删除命令:几乎不占用内存

底层调用 unlink() 系统调用,仅做两件事:

  1. 将文件 inode 引用计数减 1;
  2. 引用计数为 0 时,标记磁盘数据块为空闲,但不主动清空页缓存。

内存行为

  • 用户态:仅操作路径字符串,内存可忽略。
  • 内核缓存:rm 执行后,旧文件页缓存不会立刻释放,内核等待内存压力触发 LRU 回收;若删除后立即读取该文件,缓存依然可用。

特殊场景:进程打开状态下 rm 文件(如日志截断)

进程持有文件 fd 时,inode 引用计数不为 0,磁盘数据与页缓存全部保留;进程 close(fd) 后才会逐步回收缓存。


四 cat 读取命令:仅单份缓存,无副本

逻辑:循环 read 输出至 stdout,无写入新文件操作。

  • 用户内存:极小缓冲区(4KB)。
  • 页缓存:文件数据加载至缓存单副本,无新增重复缓存;重复 cat 只会命中已有缓存,buff/cache 不上涨。

衍生命令(less、tac、head、tail)内存行为与 cat 完全一致。


五 dd 裸读写命令:两种 I/O 模式内存差异极大

模式1:默认 Buffered I/O(无 O_DIRECT)

  • 读磁盘 → 数据存入 Buffer Cache;写入文件 → 数据存入 Page Cache。
  • 跨介质 dd(如 dd if=/dev/sda of=/mnt/usb.img):同时存在源、目标两份缓存,和 cp 行为一致。

模式2:dd oflag=direct / iflag=direct(Direct I/O)

绕过内核页缓存,数据直接在用户态缓冲区与硬件 DMA 交互:

  • 内核 buff/cache无增长。
  • 用户进程内存占用由 bs/count 块大小决定,若设置 bs=10G 会直接 malloc 10GB 用户内存,极易触发 OOM。
  • ✅ 适合数据库、超大镜像备份等场景,可规避缓存抢占业务内存。

六 tar 打包解压:多层缓存叠加

tar czf 打包文件

  1. 读取大量零散文件:每个文件生成独立页缓存,缓存总量等于所有源文件总大小。
  2. 写入压缩包:新增压缩包页缓存,缓存总占用 ≈ 源文件大小 + 压缩包大小,峰值内存压力极高。
  3. 压缩运算在用户态,zstd/gzip 会额外占用进程堆内存做压缩窗口缓存。

tar xzf 解压

读取压缩包单份缓存,写入零散文件生成多份独立缓存,缓存持续上涨。


七 各命令内存行为对比总表

命令
操作场景
用户进程内存峰值
内核页缓存特征
是否产生双份数据缓存
mv
同分区移动
几 KB,恒定
单份缓存,无新增
❌ 否
mv
跨分区移动
几十 KB 缓冲区
临时双份,完成后回收源缓存
⚠️ 操作中途是,结束后否
cp
任意分区复制
固定小块缓冲区
永久双份
缓存共存
✅ 永久是
rm
删除文件
几 KB
缓存延迟回收
❌ 无新增副本
cat
/head
只读输出
4 KB
单份缓存
❌ 否
dd
 默认
镜像拷贝
小块缓冲
双份缓存
✅ 是
dd direct
裸设备直写
bs
 参数大小
不占用页缓存
❌ 无内核缓存
tar
 打包
多文件压缩
压缩窗口内存
源文件 + 压缩包双份缓存
✅ 是

🧠 八 常见误区澄清

❌ 误区1:buff/cache 占用高 = 内存泄漏

✅ 错误。页缓存是内核空闲内存的复用,available 指标包含可回收缓存内存;内存不足时 kswapd 后台自动清理缓存分配给业务进程,不会 OOM。

❌ 误区2:同分区 mv 大文件会占满内存

✅ 错误。同分区 mv 仅修改目录项,不读写任何文件数据,缓存、进程内存均无上涨,100GB 文件也瞬间完成。

❌ 误区3:零拷贝 copy_file_range 能节省页缓存

✅ 错误。零拷贝仅消除用户态 → 内核态的数据拷贝,源与目标分属不同 inode,内核必须维护两份独立页缓存,缓存占用无优化。

❌ 误区4:rm 文件后缓存立刻释放

✅ 错误。unlink 仅标记磁盘块空闲,页缓存保留至内存压力触发回收;频繁删大文件不会立刻释放 buff/cache。


🛠️ 九 线上故障优化方案

1. 跨分区大文件移动,规避缓存暴涨

不直接 mv,分步执行:

# 1. 拷贝完成并校验 md5,确认完整性
cp src /mnt/target && md5sum src /mnt/target
# 2. 校验通过再删除源文件,缓存分阶段上涨,峰值减半
rm -f src

2. 超大文件拷贝绕过页缓存,保护业务内存

使用 Direct I/O,不占用全局缓存:

dd if=big.file of=/mnt/usb/big.file oflag=direct bs=1M

3. 主动回收无用缓存(应急,不推荐常态化执行)

# 同步脏页落盘,回收页缓存
sync && echo 3 > /proc/sys/vm/drop_caches

4. 监控内存缓存指标

# 查看可用内存(含可回收缓存)
free -h
# 监控页缓存回收、读写命中
cat /proc/vmstat | grep pgscan
# 追踪文件缓存占用工具
vmtouch big.file

💡 十 开发引申:自研软总线/文件传输模块内存设计

结合嵌入式、软总线开发场景,建议如下:

  1. 本地同存储文件迁移:仅维护路径元数据结构体,不拷贝数据缓冲区,完全复刻同分区 mv 逻辑。
  2. 跨设备/跨存储传输:分块读写,限制单块缓冲区大小,避免一次性申请超大用户内存。
  3. 传输完成异步释放源文件缓存引用,防止长期双份内存占用。
  4. 支持 Direct I/O 模式开关,适配工业设备低缓存占用需求。

🎯 结语

判断文件操作内存压力,核心先区分两点:

  • 是否跨文件系统?
  • 是否新增独立 inode 文件?
  • 仅修改目录项(同分区 mv)→ 零缓存新增、零内存压力。
  • 新建文件(cp/跨分区 mv/tar/dd)→ 内核页缓存必然产生多份数据副本,大文件场景需提前控制内存峰值。

运维排查内存占用请优先看 available 而非 free;开发自研文件传输工具需控制分块缓冲区大小,规避用户态 OOM 与内核缓存抢占业务资源。


往期文章(欢迎订阅技术分享栏目全部文章):

【项目实战】Linux 下 OTA 升级系统成砖问题排查与解决 (第一次遇见的现象,不要错过)
【项目实战】SPI Nor Flash下OTA升级概率失败导致系统成砖问题的排查和解决方法
Linux文件读写原理、分片策略、异常处理及 OTA 低内存落地实践
【Linux面试题】: 在做OTA升级时双分区切换时你所做的可靠性保障措施有哪些?

“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章