场景: 从本地磁盘读取数据,然后将这些数据通过socket发送到远端。
read(file, user_buf, len); // read data from disk
write(socket, tmp_buf, len); // write data into NIC.
完整过程如下:
1: 将文件从磁盘读到kernel
2: 将kernel中的数据copy到user_buffer // read done.
3: 将user_buffer中数据复制到kernel
4: 将kernel中数据复制到NIC。
上述过程4 次 context switch, 4次copy。
引入zero-copy 技术
1: 将文件从磁盘读到kernel。
2: 将kernel中的数据发到NIC。
上述过程1次context switch, 2次copy。
system interface
ssize_t sendfile( int out_fd, int in_fd, off_t *offset, size_t count );
sendfile() copies data between one file descriptor and another. Because this copying is done within the kernel, sendfile() is more efficient than the combination of read and write , which would require transferring data to and from user space.
in_fd : should be a file descriptor opened for reading and out_fd should be a descriptor opened for writing. If offset is not NULL, then it points to a variable holding the file offset from which sendfile() will start reading data from in_fd. When sendfile() returns, this variable will be set to the offset of the byte following the last byte that was read. If offset is not NULL, then sendfile() does not modify the file offset of in_fd; otherwise the file offset is adjusted to reflect the number of bytes read fromi n_fd. If offset is NULL, then data will be read from in_fd starting at the file offset, and the file offset will be updated by the call.count is the number of bytes to copy between the file descriptors. The in_fd argument must correspond to a file which supports mmap(2)-like operations (i.e., it cannot be a socket). In Linux kernels before 2.6.33,out_fd must refer to a socket. Since Linux 2.6.33 it can be any file. If it is a regular file, then sendfile() changes the file offset appropriately.
=============================================================================
场景:
对于一个流对象,当从这个流对象读取数据的时候,需要传递给流对象一个buffer, 因此,此处存在一次memory copy。例如:在asio中socket的读写操作。
ZeroCopyInputStream and ZeroCopyOutputStream interfaces, which represent abstract I/O streams to and from which protocol buffers can be read and written.
For a few simple implementations of these interfaces, see zero_copy_stream_impl.h.
These interfaces are different from classic I/O streams in that they try to minimize the amount of data copying that needs to be done. To accomplish this, responsibility for allocating buffers is moved to the stream object, rather than being the responsibility of the caller. So, the stream can return a buffer which actually points directly into the final data structure where the bytes are to be stored, and the caller can interact directly with that buffer, eliminating an intermediate copy operation.
As an example, consider the common case in which you are reading bytes from an array that is already in memory (or perhaps an mmap()ed file). With classic I/O streams, you would do something like:
char buffer[BUFFER_SIZE];
input->Read(buffer, BUFFER_SIZE);
DoSomething(buffer, BUFFER_SIZE);
Then, the stream basically just calls memcpy() to copy the data from the array into your buffer. With a ZeroCopyInputStream, you would do this instead:
const void* buffer;
int size;
input->Next(&buffer, &size);
DoSomething(buffer, size);
Here, no copy is performed. The input stream returns a pointer directly into the backing array, and the caller ends up reading directly from it.