Linux / BSD में जेनेरिक बैचिंग syscall क्यों नहीं है?


17

पृष्ठभूमि:

सिस्टम कॉल ओवरहेड, फ़ंक्शन कॉल ओवरहेड (अनुमान 20-100x से लेकर सीमा) की तुलना में बहुत बड़ा है, ज्यादातर संदर्भ के कारण उपयोगकर्ता स्थान से कर्नेल स्थान और वापस स्विच करने के लिए। फ़ंक्शन कॉल ओवरहेड को सहेजना सामान्य इनलाइन फ़ंक्शन है और syscalls की तुलना में फ़ंक्शन कॉल बहुत सस्ता है। यह इस कारण से है कि डेवलपर्स संभवतया एक syscall में ज्यादा से ज्यादा कर्नेल ऑपरेशन की देखभाल करके सिस्टम कॉल ओवरहेड से बचना चाहते हैं।

मुसीबत:

यह (? ज़रूरत से ज़्यादा) की तरह सिस्टम कॉल का एक बहुत पैदा कर दी है sendmmsg () , recvmmsg () : अच्छी तरह से chdir, खुले, lseek और / या की तरह सिमलिंक संयोजन के रूप में के रूप में 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आदि ...

अब लिनक्स ने जोड़ा है copy_file_range()जो जाहिरा तौर पर रीड लेसेक को जोड़ता है और syscalls लिखता है। इसके कुछ समय पहले ही यह fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () और lcopy_file_rangeat () ... हो जाता है, लेकिन चूंकि X अधिक कॉल के बजाय 2 फाइलें शामिल हैं, इसलिए यह X ^ 2 हो सकती है। अधिक। ठीक है, लिनुस और विभिन्न बीएसडी डेवलपर्स इसे दूर तक जाने नहीं देंगे, लेकिन मेरी बात यह है कि अगर कोई बैचिंग सिसकल होती, तो इनमें से सभी (अधिकांश?) को उपयोगकर्ता के स्थान पर लागू किया जा सकता था और बहुत कुछ जोड़े बिना कर्नेल जटिलता को कम किया जा सकता था? यदि कोई उपरि परिवाद पक्ष में है।

कई जटिल समाधानों का प्रस्ताव किया गया है जिसमें गैर-अवरुद्ध syscalls से बैच प्रक्रिया syscalls के लिए कुछ विशेष विशेष syscall धागा शामिल हैं; हालाँकि ये विधियाँ कर्नेल और उपयोगकर्ता स्थान दोनों के लिए महत्वपूर्ण जटिलता को एक ही तरह से जोड़ देती हैं जैसे कि libxcb बनाम libX11 (अतुल्यकालिक कॉल के लिए बहुत अधिक सेटअप की आवश्यकता होती है)

समाधान?:

एक सामान्य बैचिंग syscall। यह विशेष कर्नेल थ्रेड के साथ जुड़ी जटिलताओं के बिना सबसे बड़ी लागत (कई मोड स्विच) को कम कर देगा (हालांकि उस कार्यक्षमता को जोड़ा जा सकता है)।

सॉकेटस्कूल () syscall में एक प्रोटोटाइप के लिए मूल रूप से पहले से ही एक अच्छा आधार है। इसके बजाय इसे तर्कों की एक सरणी लेने से बढ़ाएं, बदले में सारणी लेने के लिए, तर्कों के सरणियों को इंगित करें (जिसमें syscall संख्या शामिल है), syscalls की संख्या और एक झंडे का तर्क ... कुछ इस तरह:

batch(void *returns, void *args, long ncalls, long flags);

एक बड़ा अंतर यह होगा कि तर्कों को शायद सभी को सादगी के लिए संकेत देने की आवश्यकता होगी ताकि पूर्व के syscalls के परिणामों का उपयोग बाद के syscalls (उदाहरण के open()लिए read()/ से उपयोग के लिए फ़ाइल विवरणकर्ता write()) द्वारा किया जा सके

कुछ संभावित लाभ:

  • कम उपयोगकर्ता स्थान -> कर्नेल स्थान -> उपयोगकर्ता स्थान स्विचिंग
  • स्वचालित रूप से बैच करने की कोशिश करने के लिए संभव संकलक स्विच -fcombine-syscalls
  • अतुल्यकालिक ऑपरेशन के लिए वैकल्पिक झंडा (तुरंत देखने के लिए वापसी)
  • यूजरस्पेस में भविष्य के संयुक्त syscall कार्यों को लागू करने की क्षमता

सवाल:

क्या बैचिंग सिसकल को लागू करना संभव है?

  • क्या मुझे कुछ स्पष्ट गोत्र याद आ रहे हैं?
  • क्या मैं लाभ को कम कर रहा हूं?

क्या मेरे लिए एक बैचिंग syscall (मैं Intel, Google या Redhat पर काम नहीं करता) को लागू करने के लिए परेशान करना उचित है?

  • मैंने पहले अपना कर्नेल पैच कर दिया है, लेकिन एलकेएमएल के साथ काम कर रहा है।
  • इतिहास से पता चला है कि भले ही कुछ "सामान्य" उपयोगकर्ताओं के लिए व्यापक रूप से उपयोगी हो (गैर-कॉरपोरेट एंड यूजर्स बिना गिट राइट एक्सेस), यह कभी स्वीकार नहीं किया जा सकता है अपस्ट्रीम (यूनियनफुट, एयूएफ, क्रिप्टोडेव, टक्सोनिस, आदि ...)

संदर्भ:


4
एक स्पष्ट रूप से स्पष्ट समस्या जो मैं देख रहा हूं, वह यह है कि कर्नेल एक syscall के लिए आवश्यक समय और स्थान के साथ-साथ एक एकल syscall के संचालन की जटिलता के बारे में नियंत्रण छोड़ देता है। आपने मूल रूप से एक syscall बनाया है जो अनियंत्रित, कर्नेल मेमोरी के अनबाउंड मात्रा को आवंटित कर सकता है, एक अनियंत्रित, अनबाउंड राशि के लिए चला सकता है, और मनमाने ढंग से जटिल हो सकता है। घोंसले बनाने batchमें syscalls batchsyscalls, आप मनमाने ढंग से syscalls के मनमाने ढंग से गहरी कॉल पेड़ बना सकते हैं। मूल रूप से, आप अपने पूरे आवेदन को एक एकल syscall में डाल सकते हैं।
जॉर्ग डब्ल्यू मित्तग

@ JörgWMittag - मैं सुझाव नहीं दे रहा हूं कि ये समानांतर रूप से चलते हैं, इसलिए उपयोग की जाने वाली कर्नेल मेमोरी की मात्रा बैच में सबसे भारी syscall से अधिक नहीं होगी और कर्नेल में समय अभी भी nallalls पैरामीटर (जो सीमित हो सकता है) से घिरा हुआ है कुछ मनमाना मूल्य)। नेस्टेड बैच syscall के बारे में आपका अधिकार एक शक्तिशाली उपकरण होने के नाते है, शायद इतना ही कि इसे पहले से ही रखा जाना चाहिए (हालाँकि मैं देख सकता था कि यह स्थैतिक फ़ाइल सर्वर स्थिति में उपयोगी है - जानबूझकर बिंदु के साथ कर्नेल लूप में एक डेमन चिपकाकर - मूल रूप से पुराने TUX सर्वर को लागू करना)
टेक्नोसॉरस

1
Syscalls में एक विशेषाधिकार परिवर्तन शामिल है लेकिन यह हमेशा संदर्भ स्विच के रूप में विशेषता नहीं है। en.wikipedia.org/wiki/…
एरिक Eidt

1
कल इसे पढ़ें जो कुछ और प्रेरणा और पृष्ठभूमि प्रदान करता है: matildah.github.io/posts/2016-01-30-unikernel-security.html
टॉम

@ JörgWMittag नेस्टिंग को कर्नेल स्टैक ओवरफ्लो से रोकने के लिए अस्वीकृत किया जा सकता है। अन्यथा, व्यक्तिगत syscall खुद के बाद मुक्त हो जाएगा जैसे वे सामान्य रूप से करते हैं। इसके साथ कोई संसाधन-समस्या-विरोधी समस्या नहीं होनी चाहिए। लिनक्स कर्नेल प्रीमेचेबल है।
PSkocik

जवाबों:


5

मैंने यह x86_64 पर आजमाया

94836ecf1e7378b64d37624fb81fe48fbd4c772 के विरुद्ध पैच: (यहां भी 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);

और यह काम करने के लिए प्रतीत होता है - मैं हैलो को लिख सकता हूँ 1 fd और दुनिया को fd 2 को सिर्फ एक 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);
}

मूल रूप से मैं उपयोग कर रहा हूँ:

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);

यह syscalls की संख्या की कोशिश करता है ( ==Nargsयदि SUPERSYSCALL__continue_on_failureध्वज पारित किया गया है, अन्यथा >0 && <=Nargs) और विफलताओं के बीच की गुठली अंतरिक्ष और उपयोगकर्ता के स्थान के बीच सामान्य रूप से segfaults द्वारा संकेतित हैं -EFAULT

मुझे नहीं पता कि यह अन्य आर्किटेक्चर के लिए कैसे पोर्ट होगा, लेकिन यह निश्चित रूप से अच्छा होगा कि कर्नेल में ऐसा कुछ हो।

यदि यह सभी अभिलेखों के लिए संभव था, तो मुझे लगता है कि एक उपयोगकर्ता रैपर हो सकता है जो कुछ यूनियनों और मैक्रोज़ के माध्यम से टाइप सुरक्षा प्रदान करेगा (यह syscall नाम के आधार पर एक यूनियन सदस्य का चयन कर सकता है और सभी यूनियन फिर 6 लॉन्ग में परिवर्तित हो जाएंगे। या जो भी आर्किटेक्चर डे पत्रिकाओं के 6 लोंगो के बराबर होगा)।


1
इसकी अवधारणा का एक अच्छा सबूत है, हालांकि मैं अभी लंबे समय तक की एक सरणी की लंबी बजाय करने के लिए संकेत की एक सरणी को देखने के लिए, आप की तरह खुले लिखने-पास की वापसी का उपयोग कर बातें कर सकता है ताकि चाहते हैं openमें writeऔर close। यह / put_user के कारण जटिलता को थोड़ा बढ़ा देगा, लेकिन शायद इसके लायक है। पोर्टेबिलिटी IIRC के रूप में, कुछ आर्किटेक्चर 5 और 6 के लिए arys syscall रजिस्टरों को रोक सकते हैं यदि 5 या 6 arg syscall को बैच दिया गया है ... भविष्य में उपयोग के लिए 2 अतिरिक्त args जोड़ने से यह ठीक हो जाएगा और भविष्य में अतुल्यकालिक कॉल मापदंडों के लिए उपयोग किया जा सकता है। एक SUPERSYSCALL__async ध्वज सेट किया गया है
टेक्नोसॉरस

1
मेरा इरादा भी एक sys_memcpy जोड़ना था। उपयोगकर्ता तब sys_open और sys_write के बीच में लौटा सकता है ताकि वापस लौटे fd को कॉपी करने के लिए sys_write के पहले तर्क के बिना वापस मोडपेस पर जा सके।
PSkocik

3

दो मुख्य गोत्र जो तुरंत दिमाग में आते हैं:

  • त्रुटि से निपटने: प्रत्येक व्यक्ति syscall एक त्रुटि के साथ समाप्त हो सकता है जिसे आपके उपयोगकर्ता-स्थान कोड द्वारा जांचने और नियंत्रित करने की आवश्यकता होती है। इसलिए प्रत्येक व्यक्तिगत कॉल के बाद एक बैचिंग कॉल को उपयोगकर्ता-स्पेस कोड चलाना होगा, ताकि कर्नेल-स्पेस कॉल को बैचने के लाभों को नकार दिया जाएगा। इसके अतिरिक्त, API को बहुत ही जटिल होना होगा (यदि संभव हो तो डिज़ाइन करना संभव है) - उदाहरण के लिए आप तर्क को कैसे व्यक्त करेंगे जैसे कि "यदि तीसरा कॉल विफल हो गया, तो कुछ करें और चौथी कॉल को छोड़ दें लेकिन पांचवें के साथ जारी रखें")?

  • कई "संयुक्त" कॉल जो वास्तव में लागू होते हैं, उपयोगकर्ता और कर्नेल स्थान के बीच स्थानांतरित नहीं होने के अलावा अतिरिक्त लाभ प्रदान करते हैं। उदाहरण के लिए, वे अक्सर मेमोरी को कॉपी करने और पूरी तरह से बफ़र्स का उपयोग करने से बचते हैं (जैसे पृष्ठ बफर में सीधे डेटा को एक मध्यवर्ती बफर के माध्यम से कॉपी करने के बजाय दूसरे स्थान पर स्थानांतरित करें)। बेशक, यह केवल कॉल्स के विशिष्ट संयोजनों के लिए समझ में आता है (उदाहरण के लिए पढ़े-लिखो), न कि बैचेड कॉल्स के मनमाने संयोजन के लिए।


2
पुन: त्रुटि से निपटने। मैंने इस बारे में सोचा और इसीलिए मैंने झंडे के तर्क (BATCH_RET_ON_FIRST_ERR) का सुझाव दिया ... एक रसीला syscall ncalls को वापस करना चाहिए अगर सभी कॉल बिना त्रुटि के पूरा हो जाए या आखिरी succesful एक असफल हो जाए। यह आपको त्रुटियों की जांच करने की अनुमति देगा और संभवतया पहले असफल कॉल पर शुरू करने की कोशिश करेगा, यदि केवल एक संसाधन व्यस्त था या कॉल बाधित हो गया था, तो 2 पॉइंट बढ़ाकर और रिटर्न वैल्यू से एनक्लॉल्स को घटाकर। ... गैर-संदर्भ switiching भागों इसके लिए गुंजाइश से बाहर हैं, लेकिन चूंकि लिनक्स 4.2,
स्प्लिस

2
कर्नेल स्वचालित रूप से विभिन्न कार्यों को मर्ज करने और निरर्थक कार्यों को समाप्त करने के लिए कॉल सूची को अनुकूलित कर सकता है। कर्नेल शायद सरलतम एपीआई के प्रयास में एक बड़ी बचत में अधिकांश व्यक्तिगत डेवलपर्स की तुलना में बेहतर काम करेगा।
अलेक्सांद्र डबिन्सकी

@technosaurus यह अपवादों के टेक्नोसॉरस के विचार के अनुकूल नहीं होगा, जो यह बताता है कि कौन सा ऑपरेशन विफल हुआ (क्योंकि संचालन का क्रम अनुकूलित हो जाता है)। यही कारण है कि अपवाद सामान्य रूप से ऐसी सटीक जानकारी (भी, क्योंकि कोड भ्रमित और नाजुक हो जाता है) को वापस करने के लिए डिज़ाइन नहीं किया गया है। सौभाग्य से, यह सामान्य अपवाद संचालकों को लिखना मुश्किल नहीं है जो विभिन्न विफलता मोड को संभालते हैं।
Aleksandr Dubinsky
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.