libbb: use sendfile() to copy data between file descriptors

Busybox already uses sendfile in httpd. This patch proposes to use it
globally to copy data between file descriptors.

It speeds up the copying on slow systems a lot - below are the times needed
to copy a 450Mb file with and without this option enabled on a BeagleBone
Black:

sendfile:
user    0m0.000s
sys     0m8.170s

read/write 4k:
user    0m0.470s
sys     0m16.300s

function                                             old     new   delta
bb_full_fd_action                                    394     474     +80

Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Bartosz Golaszewski
2014-11-27 13:20:24 +01:00
committed by Denys Vlasenko
parent ee41094b80
commit 8d75d794ea
4 changed files with 71 additions and 42 deletions

View File

@@ -8,6 +8,20 @@
*/
#include "libbb.h"
#if ENABLE_FEATURE_USE_SENDFILE
# include <sys/sendfile.h>
#else
# define sendfile(a,b,c,d) (-1)
#endif
/*
* We were using 0x7fff0000 as sendfile chunk size, but it
* was seen to cause largish delays when user tries to ^C a file copy.
* Let's use a saner size.
* Note: needs to be >= max(CONFIG_FEATURE_COPYBUF_KB),
* or else "copy to eof" code will use neddlesly short reads.
*/
#define SENDFILE_BIGBUF (16*1024*1024)
/* Used by NOFORK applets (e.g. cat) - must not use xmalloc.
* size < 0 means "ignore write errors", used by tar --to-command
@@ -18,12 +32,13 @@ static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
int status = -1;
off_t total = 0;
bool continue_on_write_error = 0;
#if CONFIG_FEATURE_COPYBUF_KB <= 4
ssize_t sendfile_sz;
#if CONFIG_FEATURE_COPYBUF_KB > 4
char *buffer = buffer; /* for compiler */
int buffer_size = 0;
#else
char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024];
enum { buffer_size = sizeof(buffer) };
#else
char *buffer;
int buffer_size;
#endif
if (size < 0) {
@@ -31,46 +46,58 @@ static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
continue_on_write_error = 1;
}
#if CONFIG_FEATURE_COPYBUF_KB > 4
if (size > 0 && size <= 4 * 1024)
goto use_small_buf;
/* We want page-aligned buffer, just in case kernel is clever
* and can do page-aligned io more efficiently */
buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
/* ignored: */ -1, 0);
buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024;
if (buffer == MAP_FAILED) {
use_small_buf:
buffer = alloca(4 * 1024);
buffer_size = 4 * 1024;
}
#endif
if (src_fd < 0)
goto out;
sendfile_sz = !ENABLE_FEATURE_USE_SENDFILE
? 0
: SENDFILE_BIGBUF;
if (!size) {
size = buffer_size;
size = SENDFILE_BIGBUF;
status = 1; /* copy until eof */
}
while (1) {
ssize_t rd;
rd = safe_read(src_fd, buffer, size > buffer_size ? buffer_size : size);
if (!rd) { /* eof - all done */
status = 0;
break;
if (sendfile_sz) {
rd = sendfile(dst_fd, src_fd, NULL,
size > sendfile_sz ? sendfile_sz : size);
if (rd >= 0)
goto read_ok;
sendfile_sz = 0; /* do not try sendfile anymore */
}
#if CONFIG_FEATURE_COPYBUF_KB > 4
if (buffer_size == 0) {
if (size > 0 && size <= 4 * 1024)
goto use_small_buf;
/* We want page-aligned buffer, just in case kernel is clever
* and can do page-aligned io more efficiently */
buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
/* ignored: */ -1, 0);
buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024;
if (buffer == MAP_FAILED) {
use_small_buf:
buffer = alloca(4 * 1024);
buffer_size = 4 * 1024;
}
}
#endif
rd = safe_read(src_fd, buffer,
size > buffer_size ? buffer_size : size);
if (rd < 0) {
bb_perror_msg(bb_msg_read_error);
break;
}
read_ok:
if (!rd) { /* eof - all done */
status = 0;
break;
}
/* dst_fd == -1 is a fake, else... */
if (dst_fd >= 0) {
if (dst_fd >= 0 && !sendfile_sz) {
ssize_t wr = full_write(dst_fd, buffer, rd);
if (wr < rd) {
if (!continue_on_write_error) {
@@ -92,10 +119,8 @@ static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
}
out:
#if CONFIG_FEATURE_COPYBUF_KB > 4
if (buffer_size != 4 * 1024)
if (buffer_size > 4 * 1024)
munmap(buffer, buffer_size);
#endif
return status ? -1 : total;
}