本文约3400字,今天继续在深度优化ota升级中对临时文件的操作处理,现在除了最开始传输的升级包后,后续一切只操作内存中存放了升级包数据的buffer,代码中有一处mv操作,有点疑惑,在mv操作过程中到底是会有两份内存还是只有一份,于是基于这个疑惑整理了所有对文件操作的命令中对内存占用的情况,一起来学习,看是否有颠覆认知的知识。
关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。
你是否遇到过一个
cp命令让设备内存飙升?跨分区mv大文件导致业务 OOM?别再把buff/cache当成内存泄漏了。本文将深度分析高频文件命令的底层内存行为,一起来学习内存占用的真相。
Linux 文件操作的内存占用分为 用户进程私有内存 和 内核页缓存(Page Cache) 两部分。绝大多数运维与开发人员混淆二者,常误判内存泄漏、缓存占用、大文件拷贝 OOM 等问题。
本文深入分析p、mv、rm、cat、dd、tar 等高频文件命令的底层系统调用、页缓存行为及内存峰值特征,区分同分区/跨分区操作差异,并附上故障排查与内存优化方案,让你从此不再“背锅”。
| 用户态内存 | topps 中的 RSS/VIRT | ||
| 内核页缓存 | free 命令中的 |
⚠️ 页缓存不属于任何进程,它是 Linux 设计的高速 I/O 中转站,不会触发 OOM,是“空闲内存”的优雅复用。
inode:存储文件元数据(大小、权限、磁盘数据块指针),不包含文件名。dentry(目录项):文件名 + inode 指针,存在于目录文件中。🔑 关键规则:
mv 命令:两种场景,内存行为天壤之别mv src dst)底层仅调用 rename() 系统调用,全程无文件数据读写。
✅ 结论:同分区mv不会产生双份内存,毫秒级完成,超大文件也毫无压力。
/home → /mnt/usb)底层等价于 cp + rm,流程:copy_file_range 全量拷贝 → 写入目标分区 → unlink 删除源文件。
mv 自带固定大小读写缓冲区(默认数十 KB),不会一次性加载整个文件到用户堆。buff/cache 临时上涨。unlink:源文件 inode 引用计数清零,内核后台逐步回收缓存 A,最终仅保留目标文件缓存。⚠️ 风险点:跨分区移动 TB 级大文件会短时吃掉大量缓存,挤压业务可用内存,需格外注意。
cp 命令:必然产生双份页缓存副本cp 逻辑为循环 read + write,GNU cp 默认使用 copy_file_range 内核零拷贝。
采用固定分块缓冲区(默认 64KB/128KB),不会一次性 malloc 整个文件,用户态内存稳定,不会因大文件撑爆进程内存。
源文件数据进入页缓存 A,写入目标文件生成全新缓存 B,两份缓存永久共存(除非手动删除源文件)。
buff/cache直接上涨 10GB;多次复制会持续叠加缓存副本。copy_file_range 零拷贝优化,也仅消除用户态拷贝,无法避免内核生成两份独立页缓存,缓存占用无优化。cp --sparse 稀疏文件仅跳过空洞零块写入,缓存占用小幅降低,但非空洞数据依旧双份缓存。
rm 删除命令:几乎不占用内存底层调用 unlink() 系统调用,仅做两件事:
rm 执行后,旧文件页缓存不会立刻释放,内核等待内存压力触发 LRU 回收;若删除后立即读取该文件,缓存依然可用。rm 文件(如日志截断)进程持有文件 fd 时,inode 引用计数不为 0,磁盘数据与页缓存全部保留;进程 close(fd) 后才会逐步回收缓存。
cat 读取命令:仅单份缓存,无副本逻辑:循环 read 输出至 stdout,无写入新文件操作。
cat 只会命中已有缓存,buff/cache 不上涨。衍生命令(less、tac、head、tail)内存行为与 cat 完全一致。
dd 裸读写命令:两种 I/O 模式内存差异极大O_DIRECT)dd(如 dd if=/dev/sda of=/mnt/usb.img):同时存在源、目标两份缓存,和 cp 行为一致。dd oflag=direct / iflag=direct(Direct I/O)绕过内核页缓存,数据直接在用户态缓冲区与硬件 DMA 交互:
buff/cache无增长。bs/count 块大小决定,若设置 bs=10G 会直接 malloc 10GB 用户内存,极易触发 OOM。tar 打包解压:多层缓存叠加tar czf 打包文件zstd/gzip 会额外占用进程堆内存做压缩窗口缓存。tar xzf 解压读取压缩包单份缓存,写入零散文件生成多份独立缓存,缓存持续上涨。
mv | ||||
mv | ||||
cp | 永久双份 | |||
rm | ||||
cathead | ||||
dd | ||||
dd direct | bs | |||
tar |
buff/cache 占用高 = 内存泄漏✅ 错误。页缓存是内核空闲内存的复用,available 指标包含可回收缓存内存;内存不足时 kswapd 后台自动清理缓存分配给业务进程,不会 OOM。
mv 大文件会占满内存✅ 错误。同分区 mv 仅修改目录项,不读写任何文件数据,缓存、进程内存均无上涨,100GB 文件也瞬间完成。
copy_file_range 能节省页缓存✅ 错误。零拷贝仅消除用户态 → 内核态的数据拷贝,源与目标分属不同 inode,内核必须维护两份独立页缓存,缓存占用无优化。
rm 文件后缓存立刻释放✅ 错误。unlink 仅标记磁盘块空闲,页缓存保留至内存压力触发回收;频繁删大文件不会立刻释放 buff/cache。
不直接 mv,分步执行:
# 1. 拷贝完成并校验 md5,确认完整性
cp src /mnt/target && md5sum src /mnt/target
# 2. 校验通过再删除源文件,缓存分阶段上涨,峰值减半
rm -f src
使用 Direct I/O,不占用全局缓存:
dd if=big.file of=/mnt/usb/big.file oflag=direct bs=1M
# 同步脏页落盘,回收页缓存
sync && echo 3 > /proc/sys/vm/drop_caches
# 查看可用内存(含可回收缓存)
free -h
# 监控页缓存回收、读写命中
cat /proc/vmstat | grep pgscan
# 追踪文件缓存占用工具
vmtouch big.file
结合嵌入式、软总线开发场景,建议如下:
mv 逻辑。判断文件操作内存压力,核心先区分两点:
仅修改目录项(同分区 mv)→ 零缓存新增、零内存压力。新建文件( cp/跨分区mv/tar/dd)→ 内核页缓存必然产生多份数据副本,大文件场景需提前控制内存峰值。
运维排查内存占用请优先看 available 而非 free;开发自研文件传输工具需控制分块缓冲区大小,规避用户态 OOM 与内核缓存抢占业务资源。
往期文章(欢迎订阅技术分享栏目全部文章):

这里是女程序员的笔记本
15年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助