[PATCH] Use sendfile to copy data between file descriptors

Bartosz Golaszewski bartekgola at gmail.com
Mon Oct 20 11:36:45 UTC 2014


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:
user	0m0.470s
sys	0m16.300s

It doesn't add a lot of bloat either:

function                                             old     new   delta
bb_full_fd_action                                    233     311     +78
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/0 up/down: 78/0)               Total: 78 bytes
   text	   data	    bss	    dec	    hex	filename
 812839	   4123	   9552	 826514	  c9c92	busybox_old
 812917	   4123	   9552	 826592	  c9ce0	busybox_unstripped

This patch also moves USE_SENDFILE feature from httpd subconfiguration
to global settings.

Signed-off-by: Bartosz Golaszewski <bartekgola at gmail.com>
---
 Config.in             |  8 +++++++
 libbb/copyfd.c        | 59 ++++++++++++++++++++++++++++++++++++++++-----------
 networking/Config.src |  8 -------
 networking/httpd.c    |  6 +++---
 4 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/Config.in b/Config.in
index b83beb5..4b5ea69 100644
--- a/Config.in
+++ b/Config.in
@@ -264,6 +264,14 @@ config PAM
 	  Use PAM in some busybox applets (currently login and httpd) instead
 	  of direct access to password database.
 
+config FEATURE_USE_SENDFILE
+        bool "Use sendfile system call"
+        default y
+        depends on HTTPD
+        help
+          When enabled, busybox will use the kernel sendfile() function
+          instead of read/write loops where applicable.
+
 config LONG_OPTS
 	bool "Support for --long-options"
 	default y
diff --git a/libbb/copyfd.c b/libbb/copyfd.c
index eda2747..e10fe82 100644
--- a/libbb/copyfd.c
+++ b/libbb/copyfd.c
@@ -9,6 +9,28 @@
 
 #include "libbb.h"
 
+#if ENABLE_FEATURE_USE_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
+/*
+ * Returns 1 if all bytes have been copied, 0 otherwise.
+ */
+static int check_status(int *status, off_t *size, off_t *total, ssize_t rd)
+{
+	*total += rd;
+	if (*status < 0) { /* if we aren't copying till EOF... */
+		*size -= rd;
+		if (!(*size)) {
+			/* 'size' bytes copied - all done */
+			*status = 0;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 /* Used by NOFORK applets (e.g. cat) - must not use xmalloc.
  * size < 0 means "ignore write errors", used by tar --to-command
  * size = 0 means "copy till EOF"
@@ -18,6 +40,7 @@ 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;
+	ssize_t rd;
 #if CONFIG_FEATURE_COPYBUF_KB <= 4
 	char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024];
 	enum { buffer_size = sizeof(buffer) };
@@ -56,10 +79,29 @@ static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
 		status = 1; /* copy until eof */
 	}
 
-	while (1) {
-		ssize_t rd;
+#if ENABLE_FEATURE_USE_SENDFILE
+	{
+		ssize_t sz = size && (size < buffer_size)
+				? size : MAXINT(ssize_t) - 0xffff;
 
-		rd = safe_read(src_fd, buffer, size > buffer_size ? buffer_size : size);
+		while (1) {
+			rd = sendfile(dst_fd, src_fd, NULL, sz);
+			if (rd <= 0) {
+				/* Might be EOF, might be an error,
+				 * to make sure fall back to the read-write
+				 * loop.
+				 */
+				break;
+			} else if (check_status(&status, &size, &total, rd)) {
+				break;
+			}
+		}
+	}
+#endif
+
+	while (1) {
+		rd = safe_read(src_fd, buffer,
+				size > buffer_size ? buffer_size : size);
 
 		if (!rd) { /* eof - all done */
 			status = 0;
@@ -80,15 +122,8 @@ static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
 				dst_fd = -1;
 			}
 		}
-		total += rd;
-		if (status < 0) { /* if we aren't copying till EOF... */
-			size -= rd;
-			if (!size) {
-				/* 'size' bytes copied - all done */
-				status = 0;
-				break;
-			}
-		}
+		if (check_status(&status, &size, &total, rd))
+			break;
 	}
  out:
 
diff --git a/networking/Config.src b/networking/Config.src
index e566469..15a6968 100644
--- a/networking/Config.src
+++ b/networking/Config.src
@@ -181,14 +181,6 @@ config FEATURE_HTTPD_RANGES
 	  "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted
 	  downloads, seeking in multimedia players etc.
 
-config FEATURE_HTTPD_USE_SENDFILE
-	bool "Use sendfile system call"
-	default y
-	depends on HTTPD
-	help
-	  When enabled, httpd will use the kernel sendfile() function
-	  instead of read/write loop.
-
 config FEATURE_HTTPD_SETUID
 	bool "Enable -u <user> option"
 	default y
diff --git a/networking/httpd.c b/networking/httpd.c
index 621d9cd..9cf0804 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -133,7 +133,7 @@
 # include <security/pam_appl.h>
 # include <security/pam_misc.h>
 #endif
-#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+#if ENABLE_FEATURE_USE_SENDFILE
 # include <sys/sendfile.h>
 #endif
 /* amount of buffering in a pipe */
@@ -1624,7 +1624,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
 #endif
 	if (what & SEND_HEADERS)
 		send_headers(HTTP_OK);
-#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+#if ENABLE_FEATURE_USE_SENDFILE
 	{
 		off_t offset = range_start;
 		while (1) {
@@ -1654,7 +1654,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
 			break;
 	}
 	if (count < 0) {
- IF_FEATURE_HTTPD_USE_SENDFILE(fin:)
+ IF_FEATURE_USE_SENDFILE(fin:)
 		if (verbose > 1)
 			bb_perror_msg("error");
 	}
-- 
2.1.1



More information about the busybox mailing list