Created
April 13, 2022 19:09
-
-
Save karthick18/e93d3031998b853c5a1d3a4e836888f7 to your computer and use it in GitHub Desktop.
Use splice with vmsplice to move user space buffers to output file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* An example using vmsplice to move user space buffers to pipe before using | |
* splice syscall which avoids copying to/from user space buffers to kernel space | |
* and uses the pipe buffers allocated in kernel space as an intermediate to directly xfer from one file to another | |
* | |
* gcc -o splice splice.c -g | |
*/ | |
#define _GNU_SOURCE | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <limits.h> | |
#include <sys/uio.h> | |
#define __IO_BUFSIZE (1<<20) /*reasonable buffer size based on pipe buffers */ | |
#ifdef NO_SPLICE | |
#define do_copy std_copy | |
#else | |
#define do_copy spliced_copy | |
#endif | |
/* | |
* Read from in and write to out without using user space buffers. | |
* Directly splice using the pipe buffer. | |
*/ | |
#ifndef NO_SPLICE | |
static int | |
spliced_copy (struct iovec *vec, int num_buffers, int out_fd) | |
{ | |
loff_t out_off = 0; | |
ssize_t len; | |
ssize_t actual_len = 0; | |
int i; | |
int filedes[2]; | |
int err = -1; | |
if (pipe (filedes) < 0) | |
{ | |
perror ("pipe:"); | |
goto out; | |
} | |
for (i = 0; i < num_buffers; i++) | |
{ | |
actual_len += vec[i].iov_len; | |
} | |
// if you want, you can change pipe buffer size from 64kb to the input buffers size | |
// needs admin privilege | |
#if 0 | |
if (fcntl (filedes[1], F_SETPIPE_SZ, actual_len) < 0) | |
{ | |
perror ("setpipe size:"); | |
} | |
#endif | |
// move from buffer to pipe with vmsplice gifting the buffer to kernel hinting | |
// no writes to our user space buffer | |
len = | |
vmsplice (filedes[1], vec, num_buffers, SPLICE_F_MOVE | SPLICE_F_GIFT); | |
if (len < 0) | |
{ | |
perror ("vmsplice:"); | |
goto out_close; | |
} | |
printf ("Transferring %ld out %ld bytes\n", len, actual_len); | |
while (len > 0) | |
{ | |
// move from pipe to out file | |
/* | |
* move from pipe buffer to out_fd | |
*/ | |
err = | |
splice (filedes[0], NULL, out_fd, &out_off, len, | |
SPLICE_F_MOVE | SPLICE_F_MORE); | |
if (err < 0) | |
{ | |
perror ("splice:"); | |
goto out_close; | |
} | |
len -= out_off; | |
} | |
err = 0; | |
out_close: | |
close (filedes[0]); | |
close (filedes[1]); | |
out: | |
return err; | |
} | |
#else | |
static int | |
std_copy (struct iovec *vec, int num_buffers, int out_fd) | |
{ | |
int err = -1; | |
ssize_t bytes; | |
if ((bytes = writev (out_fd, vec, num_buffers)) < 0) | |
{ | |
perror ("writev:"); | |
goto out; | |
} | |
printf ("Written %ld bytes to out file\n", bytes); | |
err = 0; | |
out: | |
return err; | |
} | |
#endif | |
// read from infile to a buffer | |
// this is just for testing splice from buffer to out file | |
static int | |
read_into_buffer (int in_fd, struct iovec **vecp, int *num_buffers_p) | |
{ | |
char buf[__IO_BUFSIZE]; | |
ssize_t bytes; | |
struct iovec *vec = NULL; | |
int num_buffers = 0; | |
int err = -1; | |
*vecp = NULL; | |
*num_buffers_p = 0; | |
while ((bytes = read (in_fd, buf, sizeof (buf))) > 0) | |
{ | |
vec = realloc (vec, sizeof (*vec) * (num_buffers + 1)); | |
if (vec == NULL) | |
{ | |
fprintf (stderr, "realloc failure\n"); | |
goto out; | |
} | |
char *seg = malloc (bytes); | |
if (seg == NULL) | |
{ | |
fprintf (stderr, "malloc failure\n"); | |
goto out; | |
} | |
memcpy (seg, buf, bytes); | |
vec[num_buffers].iov_base = seg; | |
vec[num_buffers].iov_len = bytes; | |
num_buffers++; | |
} | |
if (bytes < 0) | |
{ | |
fprintf (stderr, "error reading from infile\n"); | |
if (vec != NULL) | |
{ | |
free (vec); | |
} | |
goto out; | |
} | |
*vecp = vec; | |
*num_buffers_p = num_buffers; | |
err = 0; | |
out: | |
return err; | |
} | |
int | |
main (int argc, char **argv) | |
{ | |
char infile[0xff + 1], outfile[0xff + 1]; | |
int in_fd = -1, out_fd = -1; | |
int err = -1; | |
if (argc != 3) | |
{ | |
fprintf (stderr, "%s infile outfile\n", argv[0]); | |
goto out; | |
} | |
infile[0] = 0, outfile[0] = 0; | |
strncat (infile, argv[1], sizeof (infile) - 1); | |
strncat (outfile, argv[2], sizeof (outfile) - 1); | |
in_fd = open (infile, O_RDONLY); | |
if (in_fd < 0) | |
{ | |
perror ("open:"); | |
goto out; | |
} | |
out_fd = open (outfile, O_CREAT | O_WRONLY | O_TRUNC, 0777); | |
if (out_fd < 0) | |
{ | |
perror ("open2:"); | |
goto out_close; | |
} | |
struct iovec *vec = NULL; | |
int num_buffers = 0; | |
if ((err = read_into_buffer (in_fd, &vec, &num_buffers)) < 0) | |
{ | |
goto out_close2; | |
} | |
if (num_buffers > IOV_MAX) | |
{ | |
printf ("Num iov buffers exceeded. Actual %d, supported %d\n", | |
num_buffers, IOV_MAX); | |
goto out_close2; | |
} | |
if ((err = do_copy (vec, num_buffers, out_fd)) < 0) | |
{ | |
printf ("Error copying input file [%s] buffers to output file [%s]\n", | |
infile, outfile); | |
} | |
out_close2: | |
close (out_fd); | |
out_close: | |
close (in_fd); | |
out: | |
return err; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment