MEDIUM: listener: add support for linux's accept4() syscall
On Linux, accept4() does the same as accept() except that it allows
the caller to specify some flags to set on the resulting socket. We
use this to set the O_NONBLOCK flag and thus to save one fcntl()
call in each connection. The effect is a small performance gain of
around 1%.
The option is automatically enabled when target linux2628 is set, or
when the USE_ACCEPT4 Makefile variable is set. If the libc is too old
to provide the equivalent function, this is automatically detected and
our own function is used instead. In any case it is possible to force
the use of our implementation with USE_MY_ACCEPT4.
diff --git a/Makefile b/Makefile
index 653c083..5652d59 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,8 @@
# USE_GETADDRINFO : use getaddrinfo() to resolve IPv6 host names.
# USE_OPENSSL : enable use of OpenSSL. Recommended, but see below.
# USE_FUTEX : enable use of futex on kernel 2.6. Automatic.
+# USE_ACCEPT4 : enable use of accept4() on linux. Automatic.
+# USE_MY_ACCEPT4 : use own implemention of accept4() if glibc < 2.10.
#
# Options can be forced by specifying "USE_xxx=1" or can be disabled by using
# "USE_xxx=" (empty string).
@@ -240,6 +242,7 @@
USE_LIBCRYPT = implicit
USE_LINUX_SPLICE= implicit
USE_LINUX_TPROXY= implicit
+ USE_ACCEPT4 = implicit
USE_FUTEX = implicit
else
ifeq ($(TARGET),solaris)
@@ -439,6 +442,16 @@
BUILD_OPTIONS += $(call ignore_implicit,USE_MY_SPLICE)
endif
+ifneq ($(USE_ACCEPT4),)
+OPTIONS_CFLAGS += -DUSE_ACCEPT4
+BUILD_OPTIONS += $(call ignore_implicit,USE_ACCEPT4)
+endif
+
+ifneq ($(USE_MY_ACCEPT4),)
+OPTIONS_CFLAGS += -DUSE_MY_ACCEPT4
+BUILD_OPTIONS += $(call ignore_implicit,USE_MY_ACCEPT4)
+endif
+
ifneq ($(USE_NETFILTER),)
OPTIONS_CFLAGS += -DNETFILTER
BUILD_OPTIONS += $(call ignore_implicit,USE_NETFILTER)
diff --git a/include/common/accept4.h b/include/common/accept4.h
new file mode 100644
index 0000000..9c81b80
--- /dev/null
+++ b/include/common/accept4.h
@@ -0,0 +1,72 @@
+/*
+ * include/common/accept4.h
+ * Definition of the accept4 system call for older Linux libc.
+ *
+ * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef _COMMON_ACCEPT4_H
+#define _COMMON_ACCEPT4_H
+
+#if defined (__linux__) && defined(USE_ACCEPT4)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <common/syscall.h>
+
+/* On recent Linux kernels, the accept4() syscall may be used to avoid an fcntl()
+ * call to set O_NONBLOCK on the resulting socket. It was introduced in Linux
+ * 2.6.28 and is not present in older libcs.
+ */
+#ifndef SOCK_NONBLOCK
+#define SOCK_NONBLOCK O_NONBLOCK
+#endif
+
+#if defined(USE_MY_ACCEPT4) || !defined(SYS_ACCEPT4)
+#if defined(CONFIG_HAP_LINUX_VSYSCALL) && defined(__linux__) && defined(__i386__)
+/* The syscall is redefined somewhere else */
+extern int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
+#elif ACCEPT4_USE_SOCKETCALL
+static int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
+{
+ unsigned long args[4];
+ static _syscall2(int, socketcall, int, call, unsigned long *, args);
+
+ args[0] = (unsigned long)sockfd;
+ args[1] = (unsigned long)addr;
+ args[2] = (unsigned long)addrlen;
+ args[3] = (unsigned long)flags;
+ return socketcall(SYS_ACCEPT4, args);
+}
+#else
+static _syscall4(int, accept4, int, sockfd, struct sockaddr *, addr, socklen_t *, addrlen, int, flags);
+#endif /* VSYSCALL etc... */
+#endif /* USE_MY_ACCEPT4 */
+#endif /* __linux__ && USE_ACCEPT4 */
+#endif /* _COMMON_ACCEPT4_H */
+
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/include/common/syscall.h b/include/common/syscall.h
index a3b27eb..3ccbf5a 100644
--- a/include/common/syscall.h
+++ b/include/common/syscall.h
@@ -2,7 +2,7 @@
* include/common/syscall.h
* Redefinition of some missing OS-specific system calls.
*
- * Copyright 2000-2011 Willy Tarreau <w@1wt.eu>
+ * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -124,6 +124,21 @@
#endif /* $arch */
#endif /* __NR_splice */
+/* accept4() appeared in Linux 2.6.28, but it might not be in all libcs. Some
+ * archs have it as a native syscall, other ones use the socketcall instead.
+ */
+#ifndef __NR_accept4
+#if defined(__x86_64__)
+#define __NR_accept4 288
+#elif defined(__sparc__) || defined(__sparc64__)
+#define __NR_splice 323
+#else
+#define ACCEPT4_USE_SOCKETCALL 1
+#ifndef SYS_ACCEPT4
+#define SYS_ACCEPT4 18
+#endif /* SYS_ACCEPT4 */
+#endif /* $arch */
+#endif /* __NR_accept4 */
#endif /* __linux__ */
#endif /* _COMMON_SYSCALL_H */
diff --git a/src/i386-linux-vsys.c b/src/i386-linux-vsys.c
index 1bdd9e9..9d30334 100644
--- a/src/i386-linux-vsys.c
+++ b/src/i386-linux-vsys.c
@@ -120,6 +120,10 @@
" mov $0x05, %eax\n"
" jmp socketcall\n"
+ "accept4: .GLOBL accept4\n"
+ " mov $0x12, %eax\n"
+ " jmp socketcall\n"
+
"getsockname: .GLOBL getsockname\n"
" mov $0x06, %eax\n"
" jmp socketcall\n"
diff --git a/src/listener.c b/src/listener.c
index 7c21b9d..c4259bc 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -14,6 +14,7 @@
#include <stdio.h>
#include <string.h>
+#include <common/accept4.h>
#include <common/config.h>
#include <common/errors.h>
#include <common/mini-clist.h>
@@ -308,7 +309,11 @@
return;
}
+#ifdef USE_ACCEPT4
+ cfd = accept4(fd, (struct sockaddr *)&addr, &laddr, SOCK_NONBLOCK);
+#else
cfd = accept(fd, (struct sockaddr *)&addr, &laddr);
+#endif
if (unlikely(cfd == -1)) {
switch (errno) {
case EAGAIN:
diff --git a/src/session.c b/src/session.c
index b5dfd2b..3fcce84 100644
--- a/src/session.c
+++ b/src/session.c
@@ -126,9 +126,13 @@
goto out_free_session;
}
- /* Adjust some socket options */
+#ifndef USE_ACCEPT4
+ /* Adjust some socket options if the connection was accepted by a plain
+ * accept() syscall.
+ */
if (unlikely(fcntl(cfd, F_SETFL, O_NONBLOCK) == -1))
goto out_free_session;
+#endif
/* monitor-net and health mode are processed immediately after TCP
* connection rules. This way it's possible to block them, but they