In-Depth Analysis of Linux Zero-Copy Technology

In-Depth Analysis of Linux Zero-Copy Technology

Overview

Zero-copy is an efficient data transfer technology designed to reduce CPU involvement in data copying operations, thereby improving system performance and reducing CPU load. In the Linux kernel, zero-copy technology is implemented through various mechanisms, including <span>sendfile()</span>, <span>splice()</span>, <span>copy_file_range()</span>, <span>mmap()</span>, and DMA (Direct Memory Access).

Working Principle

Traditional Data Copy Process

Network Card Network Stack File System Kernel Space Application Network Card Network Stack File System Kernel Space Application Total of 4 copies: 2 CPU copies + 2 DMA copies read() system call reads file data Data copied to kernel buffer Data copied to user space write() system call Data copied to socket buffer DMA transferred to network card

Zero-Copy Optimization Process

Network Card Network Stack File System Kernel Space Application Network Card Network Stack File System Kernel Space Application Total of 2 copies: Only 2 DMA copies, no CPU copies sendfile() system call reads file data DMA transferred to kernel buffer Zero-copy transferred to socket buffer DMA transferred to network card

Core Implementation Mechanisms

1. sendfile() Mechanism

<span>sendfile()</span> system call allows direct data transfer between two file descriptors, avoiding data copying between user space and kernel space.

Core Data Structures

/* include/linux/fs.h */
struct file_operations {
    ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    // ...
};

/* include/linux/splice.h */
struct splice_desc {
    unsigned int len;
    union {
        void __user *userptr;
        struct file *file;
        void *data;
    } u;
    loff_t pos;
    loff_t *opos;
    size_t num_spliced;
    unsigned int flags;
};

System Call Interface

/* fs/read_write.c - sendfile system call implementation */
SYSCALL_DEFINE4(sendfile, int, out_fd, int, in_fd, off_t __user *, offset, size_t, count)
{
    loff_t pos;
    off_t off;
    ssize_t ret;

    if (offset) {
        if (unlikely(get_user(off, offset)))
            return -EFAULT;
        pos = off;
        ret = do_sendfile(out_fd, in_fd, &amp;pos, count, MAX_NON_LFS);
        if (unlikely(put_user(pos, offset)))
            ret = -EFAULT;
        return ret;
    }

    return do_sendfile(out_fd, in_fd, NULL, count, MAX_NON_LFS);
}

static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos,
                          size_t count, loff_t max)
{
    struct fd in, out;
    struct inode *in_inode, *out_inode;
    ssize_t retval;

    in = fdget(in_fd);
    if (!in.file)
        return -EBADF;

    out = fdget(out_fd);
    if (!out.file)
        goto out;

    retval = -ESPIPE;
    if (!ppos) {
        pos = in.file-&gt;f_pos;
        ppos = &amp;pos;
    } else {
        if (!(in.file-&gt;f_mode &amp; FMODE_PREAD))
            goto fput_out;
    }

    count = min(count, MAX_RW_COUNT);
    retval = security_file_permission(in.file, MAY_READ);
    if (retval)
        goto fput_out;

    retval = rw_verify_area(WRITE, out.file, &amp;out_pos, count);
    if (retval &lt; 0)
        goto fput_out;

    if (!max)
        max = min(in_inode-&gt;i_sb-&gt;s_maxbytes, out_inode-&gt;i_sb-&gt;s_maxbytes);

    if (unlikely(pos + count &gt; max)) {
        retval = -EOVERFLOW;
        if (pos &gt;= max)
            goto fput_out;
        count = max - pos;
    }

    fl = 0;
    if (in.file-&gt;f_flags &amp; O_NONBLOCK)
        fl = SPLICE_F_NONBLOCK;

    retval = do_splice_direct(in.file, ppos, out.file, &amp;out_pos, count, fl);

    if (retval &gt; 0) {
        add_rchar(current, retval);
        add_wchar(current, retval);
        fsnotify_access(in.file);
        fsnotify_modify(out.file);
        out.file-&gt;f_pos = out_pos;
        if (ppos)
            in.file-&gt;f_pos = *ppos;
    }

    inc_syscr(current);
    inc_syscw(current);
    if (pos &gt; max)
        retval = -EOVERFLOW;

fput_out:
    fdput(out);
out:
    fdput(in);
    return retval;
}

2. splice() Mechanism

<span>splice()</span> system call uses pipes as intermediaries to move data between two file descriptors.

Core Data Structures

/* include/linux/pipe_fs_i.h */
#define PIPE_BUF_FLAG_LRU       0x01    /* page is on the LRU */
#define PIPE_BUF_FLAG_ATOMIC    0x02    /* was atomically mapped */
#define PIPE_BUF_FLAG_GIFT      0x04    /* page is a gift */
#define PIPE_BUF_FLAG_PACKET    0x08    /* read() as a packet */
#define PIPE_BUF_FLAG_CAN_MERGE 0x10    /* can merge buffers */
#define PIPE_BUF_FLAG_WHOLE     0x20    /* read() must return entire buffer or error */

struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};

struct pipe_inode_info {
    struct mutex mutex;
    wait_queue_head_t rd_wait, wr_wait;
    unsigned int head;
    unsigned int tail;
    unsigned int max_usage;
    unsigned int ring_size;
    unsigned int readers;
    unsigned int writers;
    unsigned int files;
    unsigned int r_counter;
    unsigned int w_counter;
    struct page *tmp_page;
    struct fasync_struct *fasync_readers;
    struct fasync_struct *fasync_writers;
    struct pipe_buffer *bufs;
    struct user_struct *user;
};

struct pipe_buf_operations {
    int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
    bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
    bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

splice System Call Implementation

/* fs/splice.c */
SYSCALL_DEFINE6(splice, int, fd_in, loff_t __user *, off_in,
                int, fd_out, loff_t __user *, off_out,
                size_t, len, unsigned int, flags)
{
    struct fd in, out;
    long error;

    if (unlikely(!len))
        return 0;

    if (unlikely(flags &amp; ~SPLICE_F_ALL))
        return -EINVAL;

    error = -EBADF;
    in = fdget(fd_in);
    if (in.file) {
        out = fdget(fd_out);
        if (out.file) {
            error = do_splice(in.file, off_in, out.file, off_out,
                            len, flags);
            fdput(out);
        }
        fdput(in);
    }
    return error;
}

static long do_splice(struct file *in, loff_t __user *off_in,
                     struct file *out, loff_t __user *off_out,
                     size_t len, unsigned int flags)
{
    struct pipe_inode_info *ipipe;
    struct pipe_inode_info *opipe;
    loff_t offset;
    long ret;

    ipipe = get_pipe_info(in, true);
    opipe = get_pipe_info(out, true);

    if (ipipe &amp;&amp; opipe) {
        if (off_in || off_out)
            return -ESPIPE;

        /* Splicing to self would be fun, but... */
        if (ipipe == opipe)
            return -EINVAL;

        if ((in-&gt;f_flags | out-&gt;f_flags) &amp; O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;

        return splice_pipe_to_pipe(ipipe, opipe, len, flags);
    }

    if (ipipe) {
        if (off_in)
            return -ESPIPE;
        if (off_out) {
            if (!(out-&gt;f_mode &amp; FMODE_PWRITE))
                return -EINVAL;
            if (copy_from_user(&amp;offset, off_out, sizeof(loff_t)))
                return -EFAULT;
        } else {
            offset = out-&gt;f_pos;
        }

        if (unlikely(out-&gt;f_flags &amp; O_APPEND))
            return -EINVAL;

        ret = rw_verify_area(WRITE, out, &amp;offset, len);
        if (unlikely(ret &lt; 0))
            return ret;

        if (in-&gt;f_flags &amp; O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;

        file_start_write(out);
        ret = do_splice_from(ipipe, out, &amp;offset, len, flags);
        file_end_write(out);

        if (!off_out)
            out-&gt;f_pos = offset;
        else if (copy_to_user(off_out, &amp;offset, sizeof(loff_t)))
            ret = -EFAULT;

        return ret;
    }

    if (opipe) {
        if (off_out)
            return -ESPIPE;
        if (off_in) {
            if (!(in-&gt;f_mode &amp; FMODE_PREAD))
                return -EINVAL;
            if (copy_from_user(&amp;offset, off_in, sizeof(loff_t)))
                return -EFAULT;
        } else {
            offset = in-&gt;f_pos;
        }

        if (in-&gt;f_flags &amp; O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;

        ret = splice_file_to_pipe(in, opipe, &amp;offset, len, flags);
        if (!off_in)
            in-&gt;f_pos = offset;
        else if (copy_to_user(off_in, &amp;offset, sizeof(loff_t)))
            ret = -EFAULT;

        return ret;
    }

    return -EINVAL;
}

3. mmap() Mechanism

Memory mapping allows processes to directly map file contents into virtual memory space, avoiding traditional read and write operations.

Core Data Structures

/* include/linux/mm_types.h */
struct vm_area_struct {
    unsigned long vm_start;         /* Our start address within vm_mm. */
    unsigned long vm_end;           /* The first byte after our end address */
    struct vm_area_struct *vm_next, *vm_prev;
    struct rb_node vm_rb;
    unsigned long rb_subtree_gap;
    struct mm_struct *vm_mm;        /* The address space we belong to. */
    pgprot_t vm_page_prot;          /* Access permissions of this VMA. */
    unsigned long vm_flags;         /* Flags, see mm.h. */
    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } shared;
        const char __user *anon_name;
    };
    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;
    const struct vm_operations_struct *vm_ops;
    unsigned long vm_pgoff;         /* Offset (within vm_file) in PAGE_SIZE units */
    struct file *vm_file;           /* File we map to (can be NULL). */
    void *vm_private_data;          /* was vm_pte (shared mem) */
};

struct vm_operations_struct {
    void (*open)(struct vm_area_struct *area);
    void (*close)(struct vm_area_struct *area);
    int (*split)(struct vm_area_struct *area, unsigned long addr);
    int (*mremap)(struct vm_area_struct *area);
    vm_fault_t (*fault)(struct vm_fault *vmf);
    vm_fault_t (*huge_fault)(struct vm_fault *vmf, enum page_entry_size pe_size);
    vm_fault_t (*map_pages)(struct vm_fault *vmf,
                           pgoff_t start_pgoff, pgoff_t end_pgoff);
    unsigned long (*pagesize)(struct vm_area_struct *area);
    int (*page_mkwrite)(struct vm_fault *vmf);
    int (*pfn_mkwrite)(struct vm_fault *vmf);
    int (*access)(struct vm_area_struct *vma, unsigned long addr,
                  void *buf, int len, int write);
    const char *(*name)(struct vm_area_struct *vma);
    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
                                   unsigned long addr);
    struct page *(*find_special_page)(struct vm_area_struct *vma,
                                     unsigned long addr);
};

mmap System Call Implementation

/* mm/mmap.c */
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
                unsigned long, prot, unsigned long, flags,
                unsigned long, fd, unsigned long, off)
{
    if (offset_in_page(off) != 0)
        return -EINVAL;

    return ksys_mmap_pgoff(addr, len, prot, flags, fd, off &gt;&gt; PAGE_SHIFT);
}

unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
                             unsigned long prot, unsigned long flags,
                             unsigned long fd, unsigned long pgoff)
{
    struct file *file = NULL;
    unsigned long retval;

    if (!(flags &amp; MAP_ANONYMOUS)) {
        audit_mmap_fd(fd, flags);
        file = fget(fd);
        if (!file)
            return -EBADF;
        if (is_file_hugepages(file)) {
            len = ALIGN(len, huge_page_size(hstate_file(file)));
        } else if (unlikely(flags &amp; MAP_HUGETLB)) {
            retval = -EINVAL;
            goto out_fput;
        }
    } else if (flags &amp; MAP_HUGETLB) {
        struct user_struct *user = NULL;
        struct hstate *hs;

        hs = hstate_sizelog((flags &gt;&gt; MAP_HUGE_SHIFT) &amp; MAP_HUGE_MASK);
        if (!hs)
            return -EINVAL;

        len = ALIGN(len, huge_page_size(hs));
        /*
         * VM_NORESERVE is used because the reservations will be
         * taken when vm_ops-&gt;mmap() is called
         * A dummy user value is used because we are not locking
         * memory so no accounting is necessary
         */
        file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
                                 VM_NORESERVE,
                                 &amp;user, HUGETLB_ANONHUGE_INODE,
                                 (flags &gt;&gt; MAP_HUGE_SHIFT) &amp; MAP_HUGE_MASK);
        if (IS_ERR(file))
            return PTR_ERR(file);
    }

    flags &amp;= ~(MAP_EXECUTABLE | MAP_DENYWRITE);

    retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:
    if (file)
        fput(file);
    return retval;
}

4. DMA Mechanism

DMA allows peripherals to access memory directly without CPU intervention for data transfer.

Core Data Structures

/* include/linux/dma-mapping.h */
enum dma_data_direction {
    DMA_BIDIRECTIONAL = 0,
    DMA_TO_DEVICE = 1,
    DMA_FROM_DEVICE = 2,
    DMA_NONE = 3,
};

struct dma_map_ops {
    void* (*alloc)(struct device *dev, size_t size,
                   dma_addr_t *dma_handle, gfp_t gfp,
                   unsigned long attrs);
    void (*free)(struct device *dev, size_t size,
                 void *vaddr, dma_addr_t dma_handle,
                 unsigned long attrs);
    struct page *(*alloc_pages)(struct device *dev, size_t size,
                               dma_addr_t *dma_handle, enum dma_data_direction dir,
                               gfp_t gfp);
    void (*free_pages)(struct device *dev, size_t size,
                       struct page *page, dma_addr_t dma_handle,
                       enum dma_data_direction dir);
    dma_addr_t (*map_page)(struct device *dev, struct page *page,
                          unsigned long offset, size_t size,
                          enum dma_data_direction dir,
                          unsigned long attrs);
    void (*unmap_page)(struct device *dev, dma_addr_t dma_handle,
                       size_t size, enum dma_data_direction dir,
                       unsigned long attrs);
    int (*map_sg)(struct device *dev, struct scatterlist *sg,
                  int nents, enum dma_data_direction dir,
                  unsigned long attrs);
    void (*unmap_sg)(struct device *dev, struct scatterlist *sg,
                     int nents, enum dma_data_direction dir,
                     unsigned long attrs);
    dma_addr_t (*map_resource)(struct device *dev, phys_addr_t phys_addr,
                              size_t size, enum dma_data_direction dir,
                              unsigned long attrs);
    void (*unmap_resource)(struct device *dev, dma_addr_t dma_handle,
                          size_t size, enum dma_data_direction dir,
                          unsigned long attrs);
    void (*sync_single_for_cpu)(struct device *dev, dma_addr_t dma_handle,
                               size_t size, enum dma_data_direction dir);
    void (*sync_single_for_device)(struct device *dev, dma_addr_t dma_handle,
                                  size_t size, enum dma_data_direction dir);
    void (*sync_sg_for_cpu)(struct device *dev, struct scatterlist *sg,
                           int nents, enum dma_data_direction dir);
    void (*sync_sg_for_device)(struct device *dev, struct scatterlist *sg,
                              int nents, enum dma_data_direction dir);
    void (*cache_sync)(struct device *dev, void *vaddr, size_t size,
                       enum dma_data_direction direction);
    int (*dma_supported)(struct device *dev, u64 mask);
    u64 (*get_required_mask)(struct device *dev);
    size_t (*max_mapping_size)(struct device *dev);
    unsigned long (*get_merge_boundary)(struct device *dev);
};

DMA API Implementation

/* kernel/dma/mapping.c */
dma_addr_t dma_map_page_attrs(struct device *dev, struct page *page,
                             size_t offset, size_t size,
                             enum dma_data_direction dir,
                             unsigned long attrs)
{
    const struct dma_map_ops *ops = get_dma_ops(dev);
    dma_addr_t addr;

    BUG_ON(!valid_dma_direction(dir));
    if (dma_is_direct(ops))
        addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);
    else
        addr = ops-&gt;map_page(dev, page, offset, size, dir, attrs);
    debug_dma_map_page(dev, page, offset, size, dir, addr, false);

    return addr;
}
EXPORT_SYMBOL(dma_map_page_attrs);

void dma_unmap_page_attrs(struct device *dev, dma_addr_t addr, size_t size,
                         enum dma_data_direction dir, unsigned long attrs)
{
    const struct dma_map_ops *ops = get_dma_ops(dev);

    BUG_ON(!valid_dma_direction(dir));
    if (dma_is_direct(ops))
        dma_direct_unmap_page(dev, addr, size, dir, attrs);
    else if (ops-&gt;unmap_page)
        ops-&gt;unmap_page(dev, addr, size, dir, attrs);
    debug_dma_unmap_page(dev, addr, size, dir, false);
}
EXPORT_SYMBOL(dma_unmap_page_attrs);

int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, int nents,
                    enum dma_data_direction dir, unsigned long attrs)
{
    const struct dma_map_ops *ops = get_dma_ops(dev);
    int ents;

    BUG_ON(!valid_dma_direction(dir));
    if (dma_is_direct(ops))
        ents = dma_direct_map_sg(dev, sg, nents, dir, attrs);
    else
        ents = ops-&gt;map_sg(dev, sg, nents, dir, attrs);
    if (ents &gt; 0)
        debug_dma_map_sg(dev, sg, nents, ents, dir, false);
    else if (WARN_ON_ONCE(ents != -EINVAL &amp;&amp; ents != -ENOMEM &amp;&amp;
                         ents != -EIO &amp;&amp; ents != -EREMOTEIO))
        return -EIO;

    return ents;
}
EXPORT_SYMBOL(dma_map_sg_attrs);

Zero-Copy Technology Comparative Analysis

Technology Applicable Scenarios Advantages Limitations Performance Improvement
sendfile() File to socket transfer Simple and easy to use, kernel optimizes automatically Only supports file to socket 50-80%
splice() Transfer between any file descriptors High flexibility, supports pipes Requires a pipe as an intermediary 40-70%
mmap() Random access to large files Reduces system calls, supports random access Virtual memory overhead 30-60%
copy_file_range() File-to-file copy File system-level optimization Requires file system support 60-90%
DMA Device to memory transfer Completely zero CPU involvement Requires hardware support 80-95%

Zero-Copy Architecture Diagram

Hardware Layer
Kernel Space
User Space
sendfile/splice/mmap

Zero-Copy

DMA
Application
User Buffer
System Call Layer
VFS Virtual File System
Page Cache
Socket Buffer
Network Protocol Stack
DMA Engine
Storage Device
Network Interface Card

Simple Implementation Examples

Below are several application examples of zero-copy technology:

1. sendfile Example – High-Performance File Server

#include &lt;sys/sendfile.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;errno.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;

#define PORT 8080
#define BUFFER_SIZE 4096

typedef struct {
    int sockfd;
    struct sockaddr_in addr;
} server_t;

int create_server(server_t *server) {
    server-&gt;sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server-&gt;sockfd &lt; 0) {
        perror("socket creation failed");
        return -1;
    }
    
    int opt = 1;
    if (setsockopt(server-&gt;sockfd, SOL_SOCKET, SO_REUSEADDR, 
                   &amp;opt, sizeof(opt)) &lt; 0) {
        perror("setsockopt failed");
        close(server-&gt;sockfd);
        return -1;
    }
    
    server-&gt;addr.sin_family = AF_INET;
    server-&gt;addr.sin_addr.s_addr = INADDR_ANY;
    server-&gt;addr.sin_port = htons(PORT);
    
    if (bind(server-&gt;sockfd, (struct sockaddr*)&amp;server-&gt;addr, 
             sizeof(server-&gt;addr)) &lt; 0) {
        perror("bind failed");
        close(server-&gt;sockfd);
        return -1;
    }
    
    if (listen(server-&gt;sockfd, 10) &lt; 0) {
        perror("listen failed");
        close(server-&gt;sockfd);
        return -1;
    }
    
    printf("Server listening on port %d\n", PORT);
    return 0;
}

ssize_t sendfile_transfer(int out_fd, int in_fd, off_t *offset, size_t count) {
    ssize_t total_sent = 0;
    ssize_t sent;
    
    while (total_sent &lt; count) {
        sent = sendfile(out_fd, in_fd, offset, count - total_sent);
        if (sent &lt; 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;  // Non-blocking mode, continue trying
            }
            perror("sendfile failed");
            return -1;
        }
        if (sent == 0) {
            break;  // EOF reached
        }
        total_sent += sent;
    }
    
    return total_sent;
}

void handle_client(int client_fd) {
    char request[BUFFER_SIZE];
    char response[BUFFER_SIZE];
    char filepath[256];
    int file_fd;
    struct stat file_stat;
    
    // Read HTTP request
    ssize_t bytes_read = recv(client_fd, request, sizeof(request) - 1, 0);
    if (bytes_read &lt;= 0) {
        close(client_fd);
        return;
    }
    request[bytes_read] = '\0';
    
    // Simple GET request parsing
    if (strncmp(request, "GET ", 4) == 0) {
        char *path_start = request + 4;
        char *path_end = strchr(path_start, ' ');
        if (path_end) {
            *path_end = '\0';
            if (strcmp(path_start, "/") == 0) {
                strcpy(filepath, "./index.html");
            } else {
                snprintf(filepath, sizeof(filepath), ".%s", path_start);
            }
            
            file_fd = open(filepath, O_RDONLY);
            if (file_fd &gt;= 0 &amp;&amp; fstat(file_fd, &amp;file_stat) == 0) {
                // Send HTTP response header
                snprintf(response, sizeof(response),
                        "HTTP/1.1 200 OK\r\n"
                        "Content-Type: text/html\r\n"
                        "Content-Length: %ld\r\n"
                        "Connection: close\r\n\r\n",
                        file_stat.st_size);
                
                send(client_fd, response, strlen(response), 0);
                
                // Use sendfile for zero-copy file transfer
                off_t offset = 0;
                ssize_t sent = sendfile_transfer(client_fd, file_fd, 
                                               &amp;offset, file_stat.st_size);
                if (sent &lt; 0) {
                    printf("Failed to send file: %s\n", filepath);
                } else {
                    printf("Sent %ld bytes using sendfile: %s\n", sent, filepath);
                }
                
                close(file_fd);
            } else {
                // 404 Not Found
                const char *not_found = "HTTP/1.1 404 Not Found\r\n"
                                       "Content-Length: 13\r\n"
                                       "Connection: close\r\n\r\n"
                                       "404 Not Found";
                send(client_fd, not_found, strlen(not_found), 0);
            }
        }
    }
    
    close(client_fd);
}

int main() {
    server_t server;
    int client_fd;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    
    if (create_server(&amp;server) &lt; 0) {
        return EXIT_FAILURE;
    }
    
    printf("Zero-copy file server started on port %d\n", PORT);
    
    while (1) {
        client_fd = accept(server.sockfd, (struct sockaddr*)&amp;client_addr, 
                          &amp;client_len);
        if (client_fd &lt; 0) {
            perror("accept failed");
            continue;
        }
        
        printf("Client connected: %s:%d\n", 
               inet_ntoa(client_addr.sin_addr), 
               ntohs(client_addr.sin_port));
        
        // In actual applications, a new thread should be created or use asynchronous handling
        handle_client(client_fd);
    }
    
    close(server.sockfd);
    return EXIT_SUCCESS;
}

2. splice Example – Pipe Data Forwarder

#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;errno.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include &lt;sys/syscall.h&gt;

#define SPLICE_SIZE (64 * 1024)  // 64KB
#define PIPE_SIZE (16 * 4096)    // 64KB pipe buffer

// splice system call wrapper function
ssize_t splice_syscall(int fd_in, loff_t *off_in, int fd_out, 
                      loff_t *off_out, size_t len, unsigned int flags) {
    return syscall(SYS_splice, fd_in, off_in, fd_out, off_out, len, flags);
}

typedef struct {
    int pipe_fd[2];
    size_t buffer_size;
} splice_pipe_t;

int create_splice_pipe(splice_pipe_t *sp) {
    if (pipe(sp-&gt;pipe_fd) &lt; 0) {
        perror("pipe creation failed");
        return -1;
    }
    
    // Set pipe buffer size
    if (fcntl(sp-&gt;pipe_fd[1], F_SETPIPE_SZ, PIPE_SIZE) &lt; 0) {
        perror("fcntl F_SETPIPE_SZ failed");
        // Not a fatal error, continue execution
    }
    
    sp-&gt;buffer_size = SPLICE_SIZE;
    return 0;
}

void destroy_splice_pipe(splice_pipe_t *sp) {
    close(sp-&gt;pipe_fd[0]);
    close(sp-&gt;pipe_fd[1]);
}

ssize_t splice_transfer(int fd_in, int fd_out, size_t count) {
    splice_pipe_t sp;
    ssize_t total_transferred = 0;
    ssize_t bytes_in_pipe = 0;
    
    if (create_splice_pipe(&amp;sp) &lt; 0) {
        return -1;
    }
    
    printf("Starting splice transfer of %zu bytes\n", count);
    
    while (total_transferred &lt; count) {
        size_t remaining = count - total_transferred;
        size_t to_splice = (remaining &lt; sp.buffer_size) ? remaining : sp.buffer_size;
        
        // Step 1: splice from input file descriptor to pipe
        ssize_t bytes_to_pipe = splice_syscall(fd_in, NULL, sp.pipe_fd[1], 
                                              NULL, to_splice, SPLICE_F_MOVE);
        
        if (bytes_to_pipe &lt; 0) {
            if (errno == EAGAIN) {
                continue;
            }
            perror("splice to pipe failed");
            break;
        }
        
        if (bytes_to_pipe == 0) {
            printf("EOF reached on input\n");
            break;  // EOF
        }
        
        bytes_in_pipe = bytes_to_pipe;
        
        // Step 2: splice from pipe to output file descriptor
        while (bytes_in_pipe &gt; 0) {
            ssize_t bytes_from_pipe = splice_syscall(sp.pipe_fd[0], NULL, 
                                                    fd_out, NULL, 
                                                    bytes_in_pipe, SPLICE_F_MOVE);
            
            if (bytes_from_pipe &lt; 0) {
                if (errno == EAGAIN) {
                    continue;
                }
                perror("splice from pipe failed");
                goto cleanup;
            }
            
            if (bytes_from_pipe == 0) {
                printf("Unexpected EOF on output\n");
                goto cleanup;
            }
            
            bytes_in_pipe -= bytes_from_pipe;
            total_transferred += bytes_from_pipe;
        }
        
        printf("Transferred %ld bytes, total: %ld\n", 
               bytes_to_pipe, total_transferred);
    }
    
cleanup:
    destroy_splice_pipe(&amp;sp);
    return total_transferred;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s &lt;input_file&gt; &lt;output_file&gt;\n", argv[0]);
        return EXIT_FAILURE;
    }
    
    const char *input_file = argv[1];
    const char *output_file = argv[2];
    
    int fd_in = open(input_file, O_RDONLY);
    if (fd_in &lt; 0) {
        perror("Failed to open input file");
        return EXIT_FAILURE;
    }
    
    int fd_out = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd_out &lt; 0) {
        perror("Failed to open output file");
        close(fd_in);
        return EXIT_FAILURE;
    }
    
    // Get input file size
    struct stat st;
    if (fstat(fd_in, &amp;st) &lt; 0) {
        perror("fstat failed");
        close(fd_in);
        close(fd_out);
        return EXIT_FAILURE;
    }
    
    printf("Input file size: %ld bytes\n", st.st_size);
    
    // Execute splice transfer
    ssize_t transferred = splice_transfer(fd_in, fd_out, st.st_size);
    
    if (transferred &gt;= 0) {
        printf("Successfully transferred %ld bytes using splice\n", transferred);
    } else {
        printf("Transfer failed\n");
    }
    
    close(fd_in);
    close(fd_out);
    
    return (transferred &gt;= 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

3. mmap Example – Memory-Mapped File Processor

#include &lt;sys/mman.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;errno.h&gt;
#include &lt;sys/time.h&gt;

typedef struct {
    void *addr;          // Mapped address
    size_t length;       // Mapped length
    int fd;              // File descriptor
    int prot;            // Protection flags
    int flags;           // Mapping flags
} mmap_region_t;

int create_mmap_region(mmap_region_t *region, const char *filename, 
                      int prot, int flags) {
    struct stat st;
    
    // Open file
    int open_flags = O_RDONLY;
    if (prot &amp; PROT_WRITE) {
        open_flags = O_RDWR;
    }
    
    region-&gt;fd = open(filename, open_flags);
    if (region-&gt;fd &lt; 0) {
        perror("Failed to open file");
        return -1;
    }
    
    // Get file size
    if (fstat(region-&gt;fd, &amp;st) &lt; 0) {
        perror("fstat failed");
        close(region-&gt;fd);
        return -1;
    }
    
    region-&gt;length = st.st_size;
    region-&gt;prot = prot;
    region-&gt;flags = flags;
    
    // Create memory mapping
    region-&gt;addr = mmap(NULL, region-&gt;length, prot, flags, region-&gt;fd, 0);
    if (region-&gt;addr == MAP_FAILED) {
        perror("mmap failed");
        close(region-&gt;fd);
        return -1;
    }
    
    printf("Successfully mapped %zu bytes at address %p\n", 
           region-&gt;length, region-&gt;addr);
    return 0;
}

void destroy_mmap_region(mmap_region_t *region) {
    if (region-&gt;addr != MAP_FAILED) {
        if (munmap(region-&gt;addr, region-&gt;length) &lt; 0) {
            perror("munmap failed");
        }
    }
    if (region-&gt;fd &gt;= 0) {
        close(region-&gt;fd);
    }
}

// Use mmap for file search
size_t mmap_search_pattern(mmap_region_t *region, const char *pattern) {
    size_t pattern_len = strlen(pattern);
    size_t count = 0;
    char *data = (char *)region-&gt;addr;
    
    printf("Searching for pattern '%s' in %zu bytes\n", pattern, region-&gt;length);
    
    struct timeval start, end;
    gettimeofday(&amp;start, NULL);
    
    for (size_t i = 0; i &lt;= region-&gt;length - pattern_len; i++) {
        if (memcmp(data + i, pattern, pattern_len) == 0) {
            count++;
            printf("Found pattern at offset %zu\n", i);
        }
    }
    
    gettimeofday(&amp;end, NULL);
    
    double elapsed = (end.tv_sec - start.tv_sec) + 
                    (end.tv_usec - start.tv_usec) / 1000000.0;
    
    printf("Search completed in %.6f seconds\n", elapsed);
    printf("Found %zu occurrences of pattern '%s'\n", count, pattern);
    
    return count;
}

// Use mmap for file copy
int mmap_copy_file(const char *src_file, const char *dst_file) {
    mmap_region_t src_region;
    int dst_fd;
    struct stat st;
    
    // Map source file
    if (create_mmap_region(&amp;src_region, src_file, PROT_READ, MAP_PRIVATE) &lt; 0) {
        return -1;
    }
    
    // Create destination file
    dst_fd = open(dst_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dst_fd &lt; 0) {
        perror("Failed to create destination file");
        destroy_mmap_region(&amp;src_region);
        return -1;
    }
    
    // Extend destination file size
    if (ftruncate(dst_fd, src_region.length) &lt; 0) {
        perror("ftruncate failed");
        close(dst_fd);
        destroy_mmap_region(&amp;src_region);
        return -1;
    }
    
    // Map destination file
    void *dst_addr = mmap(NULL, src_region.length, PROT_WRITE, 
                          MAP_SHARED, dst_fd, 0);
    if (dst_addr == MAP_FAILED) {
        perror("mmap destination failed");
        close(dst_fd);
        destroy_mmap_region(&amp;src_region);
        return -1;
    }
    
    printf("Copying %zu bytes using mmap\n", src_region.length);
    
    struct timeval start, end;
    gettimeofday(&amp;start, NULL);
    
    // Memory copy (zero-copy)
    memcpy(dst_addr, src_region.addr, src_region.length);
    
    // Ensure data is written to disk
    if (msync(dst_addr, src_region.length, MS_SYNC) &lt; 0) {
        perror("msync failed");
    }
    
    gettimeofday(&amp;end, NULL);
    
    double elapsed = (end.tv_sec - start.tv_sec) + 
                    (end.tv_usec - start.tv_usec) / 1000000.0;
    
    printf("Copy completed in %.6f seconds\n", elapsed);
    
    // Clean up resources
    munmap(dst_addr, src_region.length);
    close(dst_fd);
    destroy_mmap_region(&amp;src_region);
    
    return 0;
}

// Demonstrate memory mapping prefetch optimization
void mmap_prefetch_demo(mmap_region_t *region) {
    printf("Demonstrating madvise prefetch optimization\n");
    
    // Advise kernel to prefetch entire file
    if (madvise(region-&gt;addr, region-&gt;length, MADV_WILLNEED) &lt; 0) {
        perror("madvise MADV_WILLNEED failed");
    } else {
        printf("Advised kernel to prefetch %zu bytes\n", region-&gt;length);
    }
    
    // Advise for sequential access
    if (madvise(region-&gt;addr, region-&gt;length, MADV_SEQUENTIAL) &lt; 0) {
        perror("madvise MADV_SEQUENTIAL failed");
    } else {
        printf("Advised kernel for sequential access pattern\n");
    }
}

int main(int argc, char *argv[]) {
    if (argc &lt; 3) {
        fprintf(stderr, "Usage: %s &lt;command&gt; &lt;file&gt; [pattern/destination]\n", argv[0]);
        fprintf(stderr, "Commands:\n");
        fprintf(stderr, "  search &lt;file&gt; &lt;pattern&gt;     - Search for pattern in file\n");
        fprintf(stderr, "  copy &lt;src_file&gt; &lt;dst_file&gt;  - Copy file using mmap\n");
        fprintf(stderr, "  prefetch &lt;file&gt;             - Demonstrate prefetch\n");
        return EXIT_FAILURE;
    }
    
    const char *command = argv[1];
    const char *filename = argv[2];
    
    if (strcmp(command, "search") == 0) {
        if (argc &lt; 4) {
            fprintf(stderr, "Search command requires a pattern\n");
            return EXIT_FAILURE;
        }
        
        const char *pattern = argv[3];
        mmap_region_t region;
        
        if (create_mmap_region(&amp;region, filename, PROT_READ, MAP_PRIVATE) &lt; 0) {
            return EXIT_FAILURE;
        }
        
        mmap_search_pattern(&amp;region, pattern);
        destroy_mmap_region(&amp;region);
        
    } else if (strcmp(command, "copy") == 0) {
        if (argc &lt; 4) {
            fprintf(stderr, "Copy command requires destination file\n");
            return EXIT_FAILURE;
        }
        
        const char *dst_file = argv[3];
        
        if (mmap_copy_file(filename, dst_file) &lt; 0) {
            return EXIT_FAILURE;
        }
        
    } else if (strcmp(command, "prefetch") == 0) {
        mmap_region_t region;
        
        if (create_mmap_region(&amp;region, filename, PROT_READ, MAP_PRIVATE) &lt; 0) {
            return EXIT_FAILURE;
        }
        
        mmap_prefetch_demo(&amp;region);
        destroy_mmap_region(&amp;region);
        
    } else {
        fprintf(stderr, "Unknown command: %s\n", command);
        return EXIT_FAILURE;
    }
    
    return EXIT_SUCCESS;
}

Compilation and Testing

Compilation Script

#!/bin/bash

# Create build directory
mkdir -p build

# Compile sendfile example
echo "Compiling sendfile server..."
gcc -o build/sendfile_server sendfile_server.c -D_GNU_SOURCE

# Compile splice example
echo "Compiling splice transfer..."
gcc -o build/splice_transfer splice_transfer.c -D_GNU_SOURCE

# Compile mmap example
echo "Compiling mmap processor..."
gcc -o build/mmap_processor mmap_processor.c -D_GNU_SOURCE

echo "All examples compiled successfully!"
echo "Executables are in the build/ directory:"
echo "  - sendfile_server: Zero-copy file server"
echo "  - splice_transfer: Splice-based file transfer"
echo "  - mmap_processor: Memory-mapped file operations"

Testing Script

#!/bin/bash

BUILD_DIR="build"
TEST_DIR="test_data"

# Create test directory and files
mkdir -p $TEST_DIR

# Create test files
echo "Creating test files..."
dd if=/dev/urandom of=$TEST_DIR/test_file_1MB bs=1M count=1 2&gt;/dev/null
dd if=/dev/urandom of=$TEST_DIR/test_file_10MB bs=1M count=10 2&gt;/dev/null
echo "Hello World! This is a test pattern for zero-copy demonstration." &gt; $TEST_DIR/test_text.txt

# Test splice transfer
echo "Testing splice transfer..."
time $BUILD_DIR/splice_transfer $TEST_DIR/test_file_1MB $TEST_DIR/splice_output_1MB
echo "Splice test completed."

# Test mmap search
echo "Testing mmap pattern search..."
$BUILD_DIR/mmap_processor search $TEST_DIR/test_text.txt "test"

# Test mmap copy
echo "Testing mmap file copy..."
time $BUILD_DIR/mmap_processor copy $TEST_DIR/test_file_1MB $TEST_DIR/mmap_copy_1MB

# Test mmap prefetch
echo "Testing mmap prefetch..."
$BUILD_DIR/mmap_processor prefetch $TEST_DIR/test_file_1MB

# Verify copy results
echo "Verifying copy results..."
if cmp -s $TEST_DIR/test_file_1MB $TEST_DIR/splice_output_1MB; then
    echo "✓ Splice copy verification passed"
else
    echo "✗ Splice copy verification failed"
fi

if cmp -s $TEST_DIR/test_file_1MB $TEST_DIR/mmap_copy_1MB; then
    echo "✓ Mmap copy verification passed"
else
    echo "✗ Mmap copy verification failed"
fi

echo "All tests completed!"

Performance Analysis and Debugging Tools

1. System Call Tracing

# Trace sendfile system calls
strace -e trace=sendfile,splice,mmap,munmap ./sendfile_server

# Trace file operations
strace -e trace=file ./splice_transfer input.txt output.txt

# Trace memory operations
strace -e trace=memory ./mmap_processor search large_file.txt pattern

2. Performance Analysis

# Use perf to analyze zero-copy program performance
perf record -g ./splice_transfer large_file.txt output.txt
perf report

# Analyze system call performance
perf trace ./sendfile_server

# Analyze memory access patterns
perf record -e cache-misses,page-faults ./mmap_processor copy src.txt dst.txt

3. Memory Mapping Debugging

# View process memory mappings
cat /proc/[PID]/maps

# Monitor page faults
perf stat -e page-faults,minor-faults,major-faults ./mmap_processor

# View virtual memory statistics
cat /proc/[PID]/status | grep -E "Vm|Rss"

4. Network Performance Testing

# Test sendfile server performance
ab -n 1000 -c 10 http://localhost:8080/test_file.html

# Use iperf to test network transfer
iperf3 -s &amp;  # Server
iperf3 -c localhost -t 30  # Client tests for 30 seconds

5. I/O Performance Monitoring

# Monitor disk I/O
iostat -x 1

# Monitor file system cache
cat /proc/meminfo | grep -E "Cached|Buffers|Dirty"

# Use iotop to monitor I/O activity
iotop -o -d 1

Debugging Tips and Best Practices

1. Error Handling Patterns

// Standard error checking pattern
ssize_t result = sendfile(out_fd, in_fd, &amp;offset, count);
if (result &lt; 0) {
    switch (errno) {
        case EAGAIN:
        case EWOULDBLOCK:
            // Non-blocking mode, retry later
            break;
        case EINVAL:
            // Invalid parameters, check file descriptor types
            fprintf(stderr, "Invalid parameters for sendfile\n");
            break;
        case ENOSYS:
            // System not supported, fallback to traditional methods
            fprintf(stderr, "sendfile not supported, falling back to read/write\n");
            break;
        default:
            perror("sendfile failed");
            break;
    }
}

2. Performance Optimization Suggestions

Optimization Direction Specific Measures Performance Improvement
Buffer Size Use 64KB-1MB buffers 10-30%
File Prefetch madvise(MADV_WILLNEED) 20-40%
CPU Affinity Bind to specific CPU cores 5-15%
Memory Alignment Use page-aligned buffers 5-10%
Batch Operations Combine multiple small requests 30-50%

3. Kernel Parameter Tuning

# Adjust network buffer sizes
echo 'net.core.rmem_max = 16777216' &gt;&gt; /etc/sysctl.conf
echo 'net.core.wmem_max = 16777216' &gt;&gt; /etc/sysctl.conf

# Adjust TCP buffers
echo 'net.ipv4.tcp_rmem = 4096 65536 16777216' &gt;&gt; /etc/sysctl.conf
echo 'net.ipv4.tcp_wmem = 4096 65536 16777216' &gt;&gt; /etc/sysctl.conf

# Apply settings
sysctl -p

4. Compilation Optimization Options

# High-performance compilation options
gcc -O3 -march=native -mtune=native -flto \
    -ffast-math -funroll-loops \
    -D_GNU_SOURCE -o program source.c

Conclusion

Linux zero-copy technology significantly enhances system performance by reducing data copying between user space and kernel space. The main technologies include:

  1. 1. sendfile(): Suitable for file to socket transfers, simple and efficient
  2. 2. splice(): Provides greater flexibility, supports transfers between any file descriptors
  3. 3. mmap(): Allows direct memory mapping, suitable for large files and random access
  4. 4. copy_file_range(): File system-level optimized copy
  5. 5. DMA: Hardware-level zero-copy, completely avoids CPU involvement

By appropriately selecting and combining these technologies, optimal performance can be achieved in various scenarios. In practical applications, optimizations should also consider specific hardware characteristics, file system types, and application requirements.

Leave a Comment