mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 07:26:40 -04:00
run-program: Use close_range syscall or libc wrapper before exec
FreeBSD and Linux have added the close_range syscall (in April 2020 and 5.9,
respectively). This allows for a range of fds to be closed using a single
syscall. The behavior of closefrom can be emulated using ~0U as the end fd.
FreeBSD immediately added it to their libc, but Linux libcs have lagged
behind. To support Linux systems running kernel 5.9+ without a libc wrapper for
close_range, we test for the presence of a wrapper at build time and use it if
it exists. Otherwise, fall back to the syscall directly (if the number of the
syscall can be determined at build time).
If close_range can't be found at build time, or the run time kernel does not
support it, fall back to close() in a for loop.
Linux tests were run on an x86-64 machine with ulimit -n 524288, using
(time (dotimes (i 100) (sb-ext:run-program "/bin/true" nil)))
Typical "slow" path (no use of close_range) results were:
Evaluation took:
11.630 seconds of real time
0.088556 seconds of total run time (0.013697 user, 0.074859 system)
0.77% CPU
1,491 forms interpreted
26,799,410,672 processor cycles
425,376 bytes consed
Typical "fast" path results were:
Evaluation took:
0.153 seconds of real time
0.055602 seconds of total run time (0.002995 user, 0.052607 system)
36.60% CPU
1,491 forms interpreted
351,242,436 processor cycles
424,192 bytes consed
This was tested with a 5.4 kernel and a 5.13 kernel, using Ubuntu 20.04, 21.04,
and 21.10 userspaces. The 5.4 kernel has no close_range support. Ubuntu 20.04
has neither a close_range wrapper (-os-provides-close-range-wrapper) nor a
version of Linux headers with __NR_close_range defined. Ubuntu 21.04 has no
wrapper, but does have __NR_close_range defined. Ubuntu 21.10 has both a
wrapper (+os-provides-close-range-wrapper) and __NR_close_range.
The results for which path was taken are summarized below. The top row of each
table states which kernel was used at build time, the left-most column states
which kernel was used at run time.
| 20.04 | 5.4 | 5.13 |
|-------|------|------|
| 5.4 | slow | slow |
| 5.13 | slow | slow |
| 21.04 | 5.4 | 5.13 |
|-------|------|------|
| 5.4 | slow | slow |
| 5.13 | fast | fast |
| 21.10 | 5.4 | 5.13 |
|-------|------|------|
| 5.4 | slow | slow |
| 5.13 | fast | fast |
This was additionally tested on FreeBSD amd64 13.0-RELEASE. All easily
available versions of FreeBSD have close_range support. So lack of close_range
was emulated by building with --without-os-provides-close-range-wrapper.
Additionally, FreeBSD has closefrom(2), so a slightly more complicated test was
needed to force the use of close_range.
(time (dotimes (i 100) (sb-ext:run-program "/bin/true" nil :preserve-fds (list 100000))))
As expected, the test took less time to run with
+os-provides-close-range-wrapper as opposed to
-os-provides-close-range-wrapper.
This commit is contained in:
parent
aa38e28e04
commit
4f4269f7e7
3
NEWS
3
NEWS
|
|
@ -12,6 +12,9 @@ changes relative to sbcl-2.2.3:
|
|||
longer save their entire lexical environment, reducing unnecessary
|
||||
memory retention.
|
||||
* optimization: faster (< integer fixnum) comparisons (ARM64 and x86-64).
|
||||
* platform support:
|
||||
** RUN-PROGRAM is faster on Linux and FreeBSD if close_range(2) is
|
||||
available.
|
||||
|
||||
changes in sbcl-2.2.3 relative to sbcl-2.2.2:
|
||||
* minor incompatible change: SB-THREAD:MUTEX-OWNER may return :THREAD-DEAD
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@
|
|||
;; The final batch of symbols is strictly for C. The LISP_FEATURE_
|
||||
;; prefix on the corresponding #define is unfortunate.
|
||||
:GCC-TLS :USE-SYS-MMAP
|
||||
:OS-PROVIDES-BLKSIZE-T)")) ; only for 'src/runtime/wrap.h'
|
||||
;; only for 'src/runtime/wrap.h'
|
||||
:OS-PROVIDES-BLKSIZE-T
|
||||
;; only for src/runtime/run-program.c
|
||||
:OS-PROVIDES-CLOSE-RANGE-WRAPPER)"))
|
||||
(public-features
|
||||
(cons
|
||||
sb-impl::!sbcl-architecture
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
* files for more information.
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
/* glibc won't give us close_range without this */
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "sbcl.h"
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
|
|
@ -27,6 +32,7 @@
|
|||
#include <termios.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "interr.h" // for lose()
|
||||
|
||||
#ifdef LISP_FEATURE_OPENBSD
|
||||
|
|
@ -96,20 +102,6 @@ set_pty(char *pty_name)
|
|||
|
||||
#endif /* !LISP_FEATURE_OPENBSD */
|
||||
|
||||
void closefrom_fallback(int lowfd)
|
||||
{
|
||||
int fd, maxfd;
|
||||
|
||||
#ifdef SVR4
|
||||
maxfd = sysconf(_SC_OPEN_MAX)-1;
|
||||
#else
|
||||
maxfd = getdtablesize()-1;
|
||||
#endif
|
||||
|
||||
for (fd = maxfd; fd >= lowfd; fd--)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int closefrom_fddir(char *dir, int lowfd)
|
||||
{
|
||||
DIR *d;
|
||||
|
|
@ -133,6 +125,38 @@ int closefrom_fddir(char *dir, int lowfd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void closefds_range(unsigned int first, unsigned int last)
|
||||
{
|
||||
int fds_closed = 0;
|
||||
// Try using close_range syscall first.
|
||||
#if defined(LISP_FEATURE_OS_PROVIDES_CLOSE_RANGE_WRAPPER)
|
||||
// Prefer the libc wrapper, if it exists at build time.
|
||||
fds_closed = !close_range(first, last, 0);
|
||||
#elif defined(LISP_FEATURE_LINUX) && defined(__NR_close_range)
|
||||
// Use syscall(2) if we could detect the syscall number at build time.
|
||||
fds_closed = !syscall(__NR_close_range, first, last, 0);
|
||||
#endif
|
||||
// Otherwise (if the syscall information isn't availble at build time or if
|
||||
// the run time kernel doesn't support the syscall), fall back to close()
|
||||
// in a for loop.
|
||||
if (!fds_closed)
|
||||
{
|
||||
unsigned int close_fd;
|
||||
if (last == ~0U)
|
||||
{
|
||||
#ifdef SVR4
|
||||
last = sysconf(_SC_OPEN_MAX)-1;
|
||||
#else
|
||||
last = getdtablesize()-1;
|
||||
#endif
|
||||
}
|
||||
for (close_fd = first; close_fd <= last; close_fd++)
|
||||
{
|
||||
close(close_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void closefds_from(int lowfd, int* dont_close)
|
||||
{
|
||||
if (dont_close) {
|
||||
|
|
@ -142,13 +166,7 @@ void closefds_from(int lowfd, int* dont_close)
|
|||
for (i = 0; i < length; i++)
|
||||
{
|
||||
int fd = dont_close[i];
|
||||
int close_fd;
|
||||
|
||||
/* Close the gaps between the fds */
|
||||
for (close_fd = lowfd; close_fd < fd; close_fd++)
|
||||
{
|
||||
close(close_fd);
|
||||
}
|
||||
closefds_range(lowfd, fd - 1);
|
||||
lowfd = fd+1;
|
||||
}
|
||||
}
|
||||
|
|
@ -172,7 +190,7 @@ void closefds_from(int lowfd, int* dont_close)
|
|||
*/
|
||||
|
||||
if (!fds_closed)
|
||||
closefrom_fallback(lowfd);
|
||||
closefds_range(lowfd, ~0U);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ featurep os-provides-getprotoby-r
|
|||
|
||||
featurep os-provides-poll
|
||||
|
||||
featurep os-provides-close-range-wrapper
|
||||
|
||||
if [ "$sbcl_arch" = arm ] ; then
|
||||
featurep arm-softfp
|
||||
fi
|
||||
|
|
|
|||
14
tools-for-build/os-provides-close-range-wrapper-test.c
Normal file
14
tools-for-build/os-provides-close-range-wrapper-test.c
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* We're really only interested in if this builds. If it does, the OS provides
|
||||
* close_range as a libc wrapper for the close_range syscall. If it doesn't
|
||||
* build, we fall back to syscall(2) if __NR_close_range is defined.
|
||||
*/
|
||||
|
||||
/* glibc won't give us close_range without this */
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
|
||||
int main ()
|
||||
{
|
||||
close_range(3, ~0U, 0);
|
||||
return 104;
|
||||
}
|
||||
Loading…
Reference in a new issue