深浅拷贝、COW及零拷贝

本文详细介绍了计算机中的拷贝技术,包括深拷贝、浅拷贝、写时复制(COW)和零拷贝。深拷贝是完全复制内存对象,浅拷贝仅复制指针。COW通过父子进程共享内存提高效率,而零拷贝则减少了内存和CPU在数据传输中的参与。文章提供了C++的深浅拷贝示例,并指出这些技术在资源有限和效率要求高的场景中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、拷贝

copy,中文翻译成拷贝,也算是拼音大法,拷贝其实就是复制,按照传统的思维方式来思考,复制就拥有了两份啊。映射到计算机上,如果也是如此,那么就没有什么可讨论的了。可话说回来,计算机世界中,资源太稀缺了。在现在,虽然家庭中使用计算机,相对来说资源的稀缺性明显少见了。但是在实际工程应用中,却比比皆是。
早期的计算机,不但资源缺少(内存按K,硬盘按M),为了节省资源,提高速度使用浅拷贝。同时,由于CPU速度低,为了提高效率,就搞出了COW(Copy On Write-写时复制)和零拷贝。通过简单的说明,应该明白为什么一个简单的复制,在计算机中搞得如此复杂,不外乎是节省资源,提高效率。
但万物都有两面性,拷贝有深浅,就会有掌握不深入时的数据异常。零拷贝掌握不透,就不明白数据复制的时机。而COW在LINUX中更是应用的广泛,是理解父子进程内存分布的一个重要环节。
存储介质的容量和访问速度的不匹配以及价格的巨大差异,是各种拷贝技术兴起的根本原因。

二、说明和解析

1、深拷贝
顾名思义,就是把内存对象完全复制了一份,或者说在内存中有两份一模一样的内存。
2、浅拷贝
只是增加了一个对同一块内存的操作句柄(指针)。
在这里插入图片描述

3、COW
写时复制,它通过父进程和子进程最初共享相同的页面(虚拟内存)来工作。共享页面标记为写时复制,即任何一个进程写入当前共享页面时,创建这些共享页面的副本。
在这里插入图片描述

4、零复制
零拷贝其实就是指在数据拷贝过程中全程尽量避免(当然完全避免最好,但实际还是有差距)内存和CPU的参与,直接由DMA来操作(比如网卡和磁盘的数据交互)。
在这里插入图片描述

三、内存的处理

所谓拷贝主要是在存储介质中的操作,在内存中,对于深拷贝和浅拷贝,只是对内存使用的一个不同的机制,在深拷贝中,需要完全的拷贝出来一份与当前内存相同大小的额外空间,而浅拷贝,则只是简单的增加一个指向(或者说可以得内存的句柄)句柄(指针)。换句话说,浅拷贝是多个指针操作相同的内存,那么结果可想而知,如果没有同步控制,数据一定会乱套。反之,深拷贝则不会有这种问题。不过,深拷贝增加了复制的时间开销和内存空间占用的开销,而浅拷贝则不会有(或者说只是一个指针的开销)。
COW的在内存中的处理有点类似于深浅拷贝的转换,为了提高效率,如果子进程没有写操作内存,只是读取的话,就共用一部分物理内存(fork出的子进程和父进程共用物理内存空间,但虚拟空间仍然不同),这也是和深浅拷贝细节不同之处。
而零拷贝更倾向于内存介质之间的传输,比如Cache和内存,内存和硬盘之类的,通过DMA(伪),mmap+write和sendfile。它其实的主要方式实现了介质之间的直接通信,而不是通过OS或者其它辅助的技术来多次拷贝和传输。在高版本的Linux中,当计算机中的网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术,就可以和sendfile直接融入,达到真正的零拷贝。但这些都基于PageCache即磁盘调整缓存技术。在实际项目中,Kafka和Nginx都有实际的工程应用,有兴趣可以看看。

四、示例

这里用c++给出一个深浅拷贝的例子,对于COW和零复制不好举例子,请大家自行查阅相关的技术源码:

//CopyData.h文件
#include <iostream>  
 
void TestCopy();
class CopyData
{
private:
	int num = 0;
	char *name = nullptr;
public:
	CopyData();
	~CopyData();
	CopyData(const CopyData &s);//拷贝构造函数 
};

//CopyData.cpp文件
CopyData::CopyData()
{
	name = new char(100);
	std::cout << "CopyData construct!" << std::endl;

}
CopyData::~CopyData()
{
	std::cout << "~CopyData destory! "  << std::endl;
	delete name;
	name = nullptr;
}
CopyData::CopyData(const CopyData &s)
{
	name = new char(100);
	strcmp(name,s.name);
	std::cout << " CopyData copy construst!" << std::endl;
}

void TestCopy()
{
	CopyData c;
	CopyData c1(c);// 复制 
}

运行结果:

CopyData construct!
 CopyData copy construst!
~CopyData destory!

深浅拷贝也是所有语言必考的一个面试题,在新的js技术中,也有这项技术的存在,这是没想到的。照这种发展方向来看,未来可能所有的前端也要认真学习操作系统和计算机原理这两门课了。

五、总结

只要计算机世界中存储的数量和存储的速度一直不匹配,那么,拷贝技术就不可能会停止下来,它会发展出更好更快的方法和算法。反过来,如果存储的容量和速度都是无限的,那么拷贝就自然回归到了本质,只是一份简单的复制罢了。
学习一项技术的本质,就是要弄清楚这项技术的起源、发展和目的。弄明白为什么这么做?未来要怎么做?
知其然,然后知其所以然!
在这里插入图片描述

<think>好的,用户现在让我介绍一下Linux中的零拷贝技术。之前他们问过splice()、vmsplice()和sendfile()的区别,现在可能想更全面地了解零拷贝的概念和原理。我需要先回顾一下之前的回答,确保内容一致,同时补充更多基础知识和应用场景。 用户可能是开发者或者系统管理员,对性能优化感兴趣。他们可能已经知道这些系统调用,但想深入理解背后的机制。需要解释清楚零拷贝解决的问题,传统拷贝方式的不足,以及各种零拷贝技术的实现方法。 要注意避免重复之前的内容,但可以适当引用之前的例子。可能需要比较不同的技术,比如sendfile、splice、mmap等,以及它们的适用场景。还要考虑用户可能想知道零拷贝的实际应用,比如在高性能服务器、大数据处理中的应用。 需要检查技术细节的准确性,比如DMA、内核缓冲区、用户空间和内核空间的交互。确保术语正确,比如零拷贝并非完全无拷贝,而是减少不必要的拷贝次数。此外,可能需要提到一些工具或命令来验证零拷贝的效果,比如perf或者strace。 最后,结构要清晰,分点说明,帮助用户逐步理解。可能需要用例子或比喻来形象地说明传统拷贝和零拷贝区别。还要注意语言简洁,避免过于技术化,让不同层次的读者都能理解。</think>在Linux系统中,**零拷贝(Zero-Copy)**是一种通过减少数据在内核空间和用户空间之间复制次数来提升I/O性能的核心技术。它避免了传统数据拷贝中CPU的冗余操作,尤其在高吞吐量场景(如网络传输、大文件处理)中效果显著。 --- ### 一、传统数据拷贝的瓶颈 #### 典型场景(以读取文件并发送到网络为例): 1. **磁盘 → 内核缓冲区**:通过DMA(直接内存访问)将磁盘数据拷贝到内核缓冲区(无需CPU参与)。 2. **内核缓冲区 → 用户缓冲区**:CPU将数据从内核空间拷贝到用户空间。 3. **用户缓冲区 → Socket缓冲区**:CPU再次将数据拷贝到内核的Socket缓冲区。 4. **Socket缓冲区 → 网卡**:通过DMA将数据发送到网卡。 **问题**:步骤2和3涉及两次**CPU拷贝**和四次**上下文切换**,消耗大量资源。 --- ### 二、零拷贝的核心实现方式 #### 1. **`sendfile()`** - **原理**:直接在内核中完成文件到Socket的数据传输,无需用户空间参与。 - **步骤**: 1. 磁盘 → 内核缓冲区(DMA) 2. 内核缓冲区 → Socket缓冲区(CPU拷贝) 3. Socket缓冲区 → 网卡(DMA) - **拷贝次数**:仅1次CPU拷贝(传统方式为2次) - **系统调用**:`sendfile(out_fd, in_fd, offset, count)` #### 2. **结合`mmap()`和`write()`** - **原理**:通过内存映射(`mmap`)将文件映射到用户空间,减少一次拷贝。 - **步骤**: 1. 磁盘 → 内核缓冲区(DMA) 2. 内核缓冲区映射到用户空间(零拷贝) 3. 用户空间直接调用`write()`发送到Socket - **拷贝次数**:1次CPU拷贝(内核到Socket缓冲区) - **系统调用**:`mmap()` + `write()` #### 3. **`splice()` + 管道** - **原理**:利用管道作为内核缓冲区的中转,连接两个文件描述符。 - **步骤**(文件 → 管道 → Socket): 1. `splice()`将文件数据移动到管道(内核完成) 2. `splice()`将管道数据移动到Socket - **拷贝次数**:0次CPU拷贝(仅DMA操作) - **系统调用**:`splice(fd_in, fd_out, len, flags)` #### 4. **`vmsplice()`(用户空间优化)** - **原理**:将用户空间的内存页面直接绑定到内核管道缓冲区。 - **步骤**: 1. 用户分配页对齐内存(如`posix_memalign`) 2. `vmsplice()`将内存“挂载”到管道写端 3. `splice()`将管道数据发送到目标(如Socket) - **拷贝次数**:0次CPU拷贝(但需用户内存锁定) --- ### 三、零拷贝技术的对比 | 技术 | 适用场景 | CPU拷贝次数 | 用户空间访问数据 | 限制条件 | |--------------------|----------------------------|-------------|------------------|----------------------------| | `sendfile()` | 文件→Socket | 1次 | 否 | 目标必须为Socket或支持O_DIRECT的文件 | | `mmap()`+`write()` | 文件随机读写 | 1次 | 是 | 小文件可能不划算 | | `splice()` | 任意fd间传输(需管道中转) | 0次 | 否 | 至少一端必须是管道 | | `vmsplice()` | 用户内存→内核传输 | 0次 | 是(但需锁定) | 内存必须页对齐 | --- ### 四、零拷贝的实际应用 1. **Web服务器**(如Nginx): - 使用`sendfile()`高效发送静态文件。 - 大文件下载时吞吐量提升可达2-3倍。 2. **数据库系统**(如Kafka): - 通过`mmap()`快速加载日志文件到内存。 - 减少数据从磁盘到网络的处理延迟。 3. **视频流媒体**: - 使用`splice()`将视频文件从存储直接推送到网络接口。 - 支持高并发实时流传输。 4. **分布式存储**: - 利用零拷贝加速节点间的数据同步。 --- ### 五、验证零拷贝效果的工具 1. **`strace`**: ```bash strace -e trace=sendfile,splice,vmsplice <应用程序> ``` 观察是否实际调用了零拷贝系统调用。 2. **`perf`性能分析**: ```bash perf stat -e cpu-cycles,instructions,cache-references,cache-misses <应用程序> ``` 对比传统拷贝和零拷贝的CPU周期和缓存命中率。 3. **`vmstat`监控上下文切换**: ```bash vmstat 1 # 查看cs(context switch)次数变化 ``` 零拷贝技术会显著减少上下文切换。 --- ### 六、注意事项 1. **内核版本**: - `sendfile()`在2.2+内核中支持文件→Socket,2.6.33+支持文件→文件。 - `splice()`和`vmsplice()`需要2.6.17+内核。 2. **数据修改需求**: - 零拷贝技术通常**不允许修改数据**(如加密、压缩),需结合其他方案(如COW复制)。 3. **内存对齐**: - `vmsplice()`要求用户内存按页对齐(通常4KB),否则退化为普通拷贝。 4. **文件类型限制**: - 某些技术不支持特殊文件(如`/proc`下的虚拟文件)。 > **扩展阅读**:Linux零拷贝的底层依赖于**Page Cache**和**DMA**机制,可进一步研究内核的`io_uring`(新一代异步I/O框架)对零拷贝的优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值