背景:
システムコールのオーバーヘッドは、関数呼び出しのオーバーヘッドよりもはるかに大きく(推定範囲は20〜100倍)、主にユーザー空間からカーネル空間へのコンテキストの切り替えとその逆によるものです。関数呼び出しのオーバーヘッドを節約するためにインライン関数が一般的であり、関数呼び出しはsyscallよりはるかに安価です。開発者が1つのシステムコールでカーネル内の操作をできる限り処理することにより、システムコールのオーバーヘッドの一部を回避したいのは当然のことです。
問題:
これにより、 sendmmsg() 、 recvmmsg() のような多くの(余分な?)システムコールが作成され、さらに次のようなchdir、open、lseek、symlinkの組み合わせも作成されます。 openat
、mkdirat
、mknodat
、fchownat
、futimesat
、newfstatat
、unlinkat
、fchdir
、ftruncate
、fchmod
、renameat
、linkat
、symlinkat
、readlinkat
、fchmodat
、faccessat
、lsetxattr
、fsetxattr
、execveat
、lgetxattr
、llistxattr
、lremovexattr
、fremovexattr
、flistxattr
、fgetxattr
、pread
、pwrite
など...
Linuxにcopy_file_range()
が追加されました。これは、読み取りlseekと書き込みsyscallを組み合わせたようです。これがfcopy_file_range()、lcopy_file_range()、copy_file_rangeat()、fcopy_file_rangeat()、lcopy_file_rangeat()になるまでの時間の問題ですが、X回の呼び出しの代わりに2つのファイルが含まれているため、X ^ 2になる可能性がありますもっと。さて、LinusやさまざまなBSD開発者はそれをこれほど遠くに行かせませんでしたが、私のポイントは、バッチ処理システムコールがあった場合、これらのすべて(ほとんど?)がユーザー空間に実装され、あまり追加せずにカーネルの複雑さを軽減できるということです。 libc側にオーバーヘッドがある場合。
多くの複雑なソリューションが提案されており、syscallをバッチ処理するための非ブロッキングsyscallのための何らかの特別なsyscallスレッドが含まれています。ただし、これらのメソッドは、libxcbとlibX11の場合とほぼ同じ方法で、カーネルとユーザー空間の両方にかなりの複雑さを追加します(非同期呼び出しには、さらに多くの設定が必要です)。
ソリューション?:
一般的なバッチsyscall。これにより、特別なカーネルスレッドを使用することに伴う複雑さなしに、最大のコスト(マルチモードスイッチ)が軽減されます(ただし、この機能は後で追加できます)。
基本的に、socketcall()syscallのプロトタイプには、すでに適切な基礎があります。引数の配列を取ることから、代わりに戻り値の配列、引数の配列へのポインタ(syscall番号を含む)、syscallsの数、およびフラグ引数を取るように拡張するだけです。
_batch(void *returns, void *args, long ncalls, long flags);
_
1つの大きな違いは、引数がおそらくallを単純にするためにポインタである必要があるため、前のシステムコールの結果を後続のシステムコールで使用できるようにすることです(たとえば、[ open()
-read()
/write()
で使用)
いくつかの可能な利点:
質問:
バッチsyscallを実装することは可能ですか?
バッチsyscallを実装する手間をかける価値はありますか(私はIntel、Google、またはRedhatで働いていません)?
参考文献:
私はx86_64でこれを試しました
94836ecf1e7378b64d37624fbb81fe48fbd4c772に対するパッチ:(ここにも 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);
そして、それは機能しているようです-syscallを1つだけ使用して、hdをfd 1に、worldをfd 2に書き込むことができます。
#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);
}
基本的に私は使用しています:
long a_syscall(long, long, long, long, long, long);
ユニバーサルsyscallプロトタイプとして、x86_64での動作のように見えるので、私の「スーパー」syscallは次のとおりです。
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);
試行されたシステムコールの数(==Nargs
フラグが渡された場合はSUPERSYSCALL__continue_on_failure
、それ以外の場合は>0 && <=Nargs
)を返し、カーネルスペースとユーザースペース間でのコピーの失敗は、通常の-EFAULT
ではなくsegfaultによって通知されます。
私が知らないことは、これが他のアーキテクチャにどのように移植されるかですが、カーネルにこのようなものがあるといいでしょう。
これがすべてのアーチで可能である場合、いくつかのユニオンとマクロを介してタイプセーフを提供するユーザー空間ラッパーがあると思います(syscall名に基づいてユニオンメンバーを選択し、すべてのユニオンを6つのlongに変換することができます)または、アーキテクチャのde jourで6つのlongに相当するものは何でも)。
すぐに頭に浮かぶ2つの主な落とし穴は次のとおりです。
エラー処理:個々のsyscallはエラーで終了する可能性があり、ユーザー空間コードでチェックおよび処理する必要があります。したがって、バッチ呼び出しでは、個々の呼び出しの後にユーザー空間コードを実行する必要があるため、カーネル空間呼び出しのバッチ処理の利点は無効になります。さらに、APIは非常に複雑である必要があります(可能な場合は設計する)-たとえば、「3番目の呼び出しが失敗した場合、何かを実行して4番目の呼び出しをスキップして5番目の呼び出しを続行する」などのロジックをどのように表現しますか?
実際に実装される多くの「結合」呼び出しは、ユーザー空間とカーネル空間の間を移動する必要がないことを除いて、追加の利点を提供します。たとえば、メモリのコピーやバッファの使用を完全に回避することがよくあります(たとえば、中間バッファを介してコピーするのではなく、ページバッファのある場所から別の場所に直接データを転送します)。もちろん、これは呼び出しの特定の組み合わせ(読み取りと書き込みなど)に対してのみ意味があり、バッチ呼び出しの任意の組み合わせに対しては意味がありません。