I tried this on x86_64
Patch against 94836ecf1e7378b64d37624fbb81fe48fbd4c772:
(also here https://github.com/pskocik/linux/tree/supersyscall )
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 5aef183e2f85..8df2e98eb403 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -339,6 +339,7 @@
330 common pkey_alloc sys_pkey_alloc
331 common pkey_free sys_pkey_free
332 common statx sys_statx
+333 common supersyscall sys_supersyscall
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 980c3c9b06f8..c61c14e3ff4e 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -905,5 +905,20 @@ asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val);
asmlinkage long sys_pkey_free(int pkey);
asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
unsigned mask, struct statx __user *buffer);
-
#endif
+
+struct supersyscall_args {
+ unsigned call_nr;
+ long args[6];
+};
+#define SUPERSYSCALL__abort_on_failure 0
+#define SUPERSYSCALL__continue_on_failure 1
+/*#define SUPERSYSCALL__lock_something 2?*/
+
+
+asmlinkage
+long
+sys_supersyscall(long* Rets,
+ struct supersyscall_args *Args,
+ int Nargs,
+ int Flags);
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index a076cf1a3a23..56184b84530f 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -732,9 +732,11 @@ __SYSCALL(__NR_pkey_alloc, sys_pkey_alloc)
__SYSCALL(__NR_pkey_free, sys_pkey_free)
#define __NR_statx 291
__SYSCALL(__NR_statx, sys_statx)
+#define __NR_supersyscall 292
+__SYSCALL(__NR_supersyscall, sys_supersyscall)
#undef __NR_syscalls
-#define __NR_syscalls 292
+#define __NR_syscalls (__NR_supersyscall+1)
/*
* All syscalls below here should go away really,
diff --git a/init/Kconfig b/init/Kconfig
index a92f27da4a27..25f30bf0ebbb 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2184,4 +2184,9 @@ config ASN1
inform it as to what tags are to be expected in a stream and what
functions to call on what tags.
+config SUPERSYSCALL
+ bool
+ help
+ System call for batching other system calls
+
source "kernel/Kconfig.locks"
diff --git a/kernel/Makefile b/kernel/Makefile
index b302b4731d16..4d86bcf90f90 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -9,7 +9,7 @@ obj-y = fork.o exec_domain.o panic.o \
extable.o params.o \
kthread.o sys_ni.o nsproxy.o \
notifier.o ksysfs.o cred.o reboot.o \
- async.o range.o smpboot.o ucount.o
+ async.o range.o smpboot.o ucount.o supersyscall.o
obj-$(CONFIG_MULTIUSER) += groups.o
diff --git a/kernel/supersyscall.c b/kernel/supersyscall.c
new file mode 100644
index 000000000000..d7fac5d3f970
--- /dev/null
+++ b/kernel/supersyscall.c
@@ -0,0 +1,83 @@
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+#include <linux/compiler.h>
+#include <linux/sched/signal.h>
+
+/*TODO: do this properly*/
+/*#include <uapi/asm-generic/unistd.h>*/
+#ifndef __NR_syscalls
+# define __NR_syscalls (__NR_supersyscall+1)
+#endif
+
+#define uif(Cond) if(unlikely(Cond))
+#define lif(Cond) if(likely(Cond))
+
+
+typedef asmlinkage long (*sys_call_ptr_t)(unsigned long, unsigned long,
+ unsigned long, unsigned long,
+ unsigned long, unsigned long);
+extern const sys_call_ptr_t sys_call_table[];
+
+static bool
+syscall__failed(unsigned long Ret)
+{
+ return (Ret > -4096UL);
+}
+
+
+static bool
+syscall(unsigned Nr, long A[6])
+{
+ uif (Nr >= __NR_syscalls )
+ return -ENOSYS;
+ return sys_call_table[Nr](A[0], A[1], A[2], A[3], A[4], A[5]);
+}
+
+
+static int
+segfault(void const *Addr)
+{
+ struct siginfo info[1];
+ info->si_signo = SIGSEGV;
+ info->si_errno = 0;
+ info->si_code = 0;
+ info->si_addr = (void*)Addr;
+ return send_sig_info(SIGSEGV, info, current);
+ //return force_sigsegv(SIGSEGV, current);
+}
+
+asmlinkage long /*Ntried*/
+sys_supersyscall(long* Rets,
+ struct supersyscall_args *Args,
+ int Nargs,
+ int Flags)
+{
+ int i = 0, nfinished = 0;
+ struct supersyscall_args args; /*7 * sizeof(long) */
+
+ for (i = 0; i<Nargs; i++){
+ long ret;
+
+ uif (0!=copy_from_user(&args, Args+i, sizeof(args))){
+ segfault(&Args+i);
+ return nfinished;
+ }
+
+ ret = syscall(args.call_nr, args.args);
+ nfinished++;
+
+ if ((Flags & 1) == SUPERSYSCALL__abort_on_failure
+ && syscall__failed(ret))
+ return nfinished;
+
+
+ uif (0!=put_user(ret, Rets+1)){
+ segfault(Rets+i);
+ return nfinished;
+ }
+ }
+ return nfinished;
+
+}
+
+
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 8acef8576ce9..c544883d7a13 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -258,3 +258,5 @@ cond_syscall(sys_membarrier);
cond_syscall(sys_pkey_mprotect);
cond_syscall(sys_pkey_alloc);
cond_syscall(sys_pkey_free);
+
+cond_syscall(sys_supersyscall);
And it appears to work -- I can write hello to fd 1 and world to fd 2 with just one syscall:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
struct supersyscall_args {
unsigned call_nr;
long args[6];
};
#define SUPERSYSCALL__abort_on_failure 0
#define SUPERSYSCALL__continue_on_failure 1
long
supersyscall(long* Rets,
struct supersyscall_args *Args,
int Nargs,
int Flags);
int main(int c, char**v)
{
puts("HELLO WORLD:");
long r=0;
struct supersyscall_args args[] = {
{SYS_write, {1, (long)"hello\n", 6 }},
{SYS_write, {2, (long)"world\n", 6 }},
};
long rets[sizeof args / sizeof args[0]];
r = supersyscall(rets,
args,
sizeof(rets)/sizeof(rets[0]),
0);
printf("r=%ld\n", r);
printf( 0>r ? "%m\n" : "\n");
puts("");
#if 1
#if SEGFAULT
r = supersyscall(0,
args,
sizeof(rets)/sizeof(rets[0]),
0);
printf("r=%ld\n", r);
printf( 0>r ? "%m\n" : "\n");
#endif
#endif
return 0;
}
long
supersyscall(long* Rets,
struct supersyscall_args *Args,
int Nargs,
int Flags)
{
return syscall(333, Rets, Args, Nargs, Flags);
}
Basically I'm using:
long a_syscall(long, long, long, long, long, long);
as a universal syscall prototype, which appears to be how things work on x86_64, so my "super" syscall is:
struct supersyscall_args {
unsigned call_nr;
long args[6];
};
#define SUPERSYSCALL__abort_on_failure 0
#define SUPERSYSCALL__continue_on_failure 1
/*#define SUPERSYSCALL__lock_something 2?*/
asmlinkage
long
sys_supersyscall(long* Rets,
struct supersyscall_args *Args,
int Nargs,
int Flags);
It returns the number of syscalls tried (==Nargs
if the SUPERSYSCALL__continue_on_failure
flag is passed, otherwise >0 && <=Nargs
) and failures to copy between kernels space and user space are signalled by segfaults instead of the usual -EFAULT
.
What I don't know is how this would port to other architectures, but it would sure be nice to have something like this in the kernel.
If this were possible for all archs, I imagine there could be a userspace wrapper that would provide type safety through some unions and macros (it could select a union member based on the syscall name and all the unions would then get converted to the 6 longs or whatever the architecture de jour's equivalent of the 6 longs would be).
What's the difference between new CustomerReceiptCreator().createReceipt()
and CustomerReceiptCreator.createReceipt()
? Pretty much none. The only significant difference is that the first case has considerably more awkward syntax. If you follow the first in the belief that somehow avoiding static methods makes your code better OO, you are gravely mistaken. Creating a object just to call a single method on it is a static method by obtuse syntax.
Where things do get different is when you inject CustomerReceiptCreator
instead of new
ing it. Let's consider an example:
class OrderProcessor {
@Inject CustomerReceiptCreator customerReceiptCreator;
void processOrder(Order order) {
...
CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
...
}
}
Let's compare this a static method version:
void processOrder(Order order) {
...
CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
...
}
The advantage of the static version is that I can readily tell you how it interacts with the rest of the system. It doesn't. If I haven't used global variables, I know that that the rest of the system hasn't somehow been changed. I know that no other part of the system might be affecting the receipt here If I've used immutable objects, I know that order hasn't changed, and the createReceipt is a pure function. In that case, I can freely move/remove/etc this call without worrying about random unpredictable effects elsewhere.
I cannot make the same guarantees if I've injected the CustomerReceiptCreator
. It might have internal state that is changed by the call, I might be affected by or change other state. There might be unpredictable relationships between statements in my function such that changing the order will introduce surprising bugs.
On the other hand, what happens if CustomerReceiptCreator
suddenly needs a new dependency? Let's say it needs to check a feature flag. If we were injecting, we could do something like:
public class CustomerReceiptCreator {
@Injected FeatureFlags featureFlags;
public CustomerReceipt createReceipt(Order order) {
CustomerReceipt receipt = new CustomerReceipt();
// creation logic
if (featureFlags.isFlagSet(Flags::FOOBAR)) {
...
}
return receipt;
}
}
Then we are done, because the calling code will be injected with a CustomerReceiptCreator
which will automatically get injected a FeatureFlags
.
What if we were using a static method?
public class CustomerReceiptCreator {
public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
CustomerReceipt receipt = new CustomerReceipt();
// creation logic
if (featureFlags.isFlagSet(Flags::FOOBAR)) {
...
}
return receipt;
}
}
But wait! the calling code also needs to be updated:
void processOrder(Order order) {
...
CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
...
}
Of course, this still leaves the question of where processOrder
gets its FeatureFlags
from. If we're lucky, the trail ends here, if not, the necessity of passing through FeatureFlags gets pushed further up the stack.
There is a trade-off here. Static methods requiring explicitly passing around the dependencies which results in more work. Injected method reduce the work, but make the dependencies implicit and thus hidden making code harder to reason about.
Best Answer
Yes, it is irrelevant.
Computers are tireless, near-perfect execution engines working at speeds totally un-comparable to brains. While there is a measurable amount of time that a function call adds to the execution time of a program, this is as nothing compared to the additional time needed by the brain of the next person involved with the code when they have to disentangle the unreadable routine to even begin to understand how to work with it. You can try the calculation out for a joke - assume that your code has to be maintained only once, and it only adds half an hour to the time needed for someone to come to terms with the code. Take your processor clock speed and calculate: how many times would the code have to run to even dream of offsetting that?
In short, taking pity on the CPU is completely, utterly misguided 99.99% of the time. For the rare remaining cases, use profilers. Do not assume that you can spot those cases - you can't.