Linux / BSD'de neden genel yığın syscall yok?


17

Arka fon:

Sistem çağrısı yükü, çoğunlukla kullanıcı alanından çekirdek alanına ve geri içeriğe geçiş nedeniyle işlev çağrısı yükünden (tahminler 20-100x arasında değişir) çok daha büyüktür. İşlev çağrısı ek yükünü kaydetmek için satır içi işlevlerin yaygın olması ve işlev çağrıları sistem çağrılarından çok daha ucuzdur. Geliştiricilerin, bir sistem çağında mümkün olduğunca çok çekirdek içi işlemle ilgilenerek bazı sistem çağrısı yüklerinden kaçınmak istedikleri mantıklıdır.

Sorun:

Bu gibi (? Gereksiz) sistemi bir çok görüşme yarattı sendmmsg () , recvmmsg () kuyu gibi Chdir açık, lseek ve / veya sembolik bağı kombinasyonları gibi: 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, pwritevb ...

Şimdi Linux copy_file_range()okuma lseek ve yazma sistem çağrılarını birleştiren ekledi . Bunun fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () ve lcopy_file_rangeat () haline gelmesi sadece bir zaman meselesidir ... ancak X daha fazla çağrı yerine 2 dosya olduğu için X ^ 2 olabilir Daha. Tamam, Linus ve çeşitli BSD geliştiricileri, bu kadar ileri gitmesine izin vermedi, ancak benim açımdan, bir toplu sistem sistemi olsaydı, bunların hepsinin (en çok?) Kullanıcı alanına uygulanabileceği ve çok fazla eklemeden çekirdek karmaşıklığını azaltabileceği libc tarafında herhangi bir ek yük varsa.

Toplu işlem sistem çağrılarını bloke etmeyen sistem çağrıları için bazı özel sistem çağrısı ipliği içeren birçok karmaşık çözüm önerilmiştir; ancak bu yöntemler hem çekirdeğe hem de kullanıcı alanına libxcb ve libX11 ile aynı şekilde karmaşıklık katar (eşzamansız çağrılar çok daha fazla kurulum gerektirir)

Çözüm?:

Genel bir yığın sistem çağrısı. Bu, özel çekirdek iş parçacığına sahip olmayla ilgili karmaşıklıklar olmadan en büyük maliyeti (çoklu mod anahtarları) azaltacaktır (ancak bu işlevsellik daha sonra eklenebilir).

Temel olarak socketcall () sistem çağrısında bir prototip için zaten iyi bir temel vardır. Bunun yerine, bir dizi argüman almak, işaretçi argüman dizilerine (sistem çağrı numarasını içeren), sistem çağrılarının sayısını ve bayrak argümanını almak için bir dizi argüman almaktan uzatın ...

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

Bir önemli fark argümanlar muhtemelen olacağını olacağını tüm önceki sistem çağrıları sonuçları (gelen örneğin dosya tanıtıcı müteakip sistem çağrıları tarafından kullanılabilir böylece ihtiyaç basitlik için işaretçileri olmak open()kullanılmak üzere read()/ ' write())

Bazı olası avantajlar:

  • daha az kullanıcı alanı -> çekirdek alanı -> kullanıcı alanı değiştirme
  • Otomatik derleme yapmaya çalışmak için olası derleyici anahtarı -fcombine-syscalls
  • eşzamansız çalışma için isteğe bağlı bayrak (hemen izlemek için fd döndür)
  • gelecekteki birleşik sistem çağrılarını kullanıcı alanında uygulama becerisi

Soru:

Yığınlı bir sistem çağrısı uygulamak mümkün müdür?

  • Bazı bariz yakaları kaçırıyor muyum?
  • Avantajları fazla tahmin ediyor muyum?

Toplu bir sistem çağrısı uygulama zahmetine girmem bana değer mi (Intel, Google veya Redhat'ta çalışmıyorum)?

  • Daha önce kendi çekirdeğimi yamaladım, ancak LKML ile uğraşmaktan korktum.
  • Geçmiş, "normal" kullanıcılar (git yazma erişimi olmayan kurumsal olmayan son kullanıcılar) için yaygın olarak yararlı bir şey olsa bile, asla yukarı akışa kabul edilmeyebileceğini (unionfs, aufs, cryptodev, smokin vb.)

Referanslar:


4
Gördüğüm oldukça açık bir problem, çekirdeğin bir sistem çağrısı için gereken zaman ve alanın yanı sıra tek bir sistem çağrısının işlemlerinin karmaşıklığı hakkında kontrolü bırakmasıdır. Temelde, keyfi, sınırsız miktarda çekirdek belleği ayırabilen, keyfi, sınırsız bir süre çalışabilen ve keyfi olarak karmaşık olabilen bir sistem çağrısı oluşturdunuz. batchSistem çağrılarını sistem batchçağrılarına yerleştirerek , keyfi sistem çağrılarının keyfi olarak derin bir çağrı ağacı oluşturabilirsiniz. Temel olarak, tüm uygulamanızı tek bir sisteme koyabilirsiniz.
Jörg W Mittag

@ JörgWMittag - Bunların paralel çalıştığını önermiyorum, bu nedenle kullanılan çekirdek belleği miktarı, toplu işteki en ağır sistem çağından daha fazla olmayacak ve çekirdekteki süre hala ncalls parametresi ile sınırlandırılacaktır ( bazı keyfi değerler). Yuvalanmış bir toplu iş syscall güçlü bir araç olmak konusunda haklısın, belki de o kadar çok engellenmelidir (statik bir dosya sunucusu durumunda yararlı olduğunu görebildim - kasıtlı olarak bir daemon'u işaretçiler kullanarak bir çekirdek döngüsüne yapıştırarak - temelde eski TUX sunucusunun kurulumu)
technosaurus

1
Sistem çağrıları bir ayrıcalık değişikliği içerir, ancak bu her zaman bir bağlam değişikliği olarak nitelendirilmez. en.wikipedia.org/wiki/…
Erik Eidt

1
biraz daha motivasyon ve arka plan sağlayan bu okuyun: matildah.github.io/posts/2016-01-30-unikernel-security.html
Tom

@ JörgWMettag yuvalaması, çekirdek yığını taşmasını önlemek için izin verilmiyor olabilir. Aksi takdirde, bireysel sistem çağrıları normalde olduğu gibi kendilerinden sonra serbest kalır. Bununla ilgili herhangi bir kaynak sarkması sorunu olmamalıdır. Linux çekirdeği dikkat çekicidir.
PSkocik

Yanıtlar:


5

Bunu x86_64'te denedim

94836ecf1e7378b64d37624fbb81fe48fbd4c772'ye karşı yama: (ayrıca burada 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);

Ve işe yarıyor gibi görünüyor - fd 1'e ve dünya fd 2'ye sadece bir sistem çağrısı ile merhaba yazabilirim:

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

Temelde kullanıyorum:

long a_syscall(long, long, long, long, long, long);

x86_64'te işlerin nasıl çalıştığı gibi görünen evrensel bir sistem çağrısı prototipi olarak, "süper" sistem çağrım:

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

O (denenmiş sistem çağrıları sayısını döndürür ==Nargseğer SUPERSYSCALL__continue_on_failureaksi halde, bayrak geçirilir >0 && <=Nargs) ve başarısızlıkları çekirdekleri uzay ve kullanıcı alanı yerine her zamanki ait çalışma sırasında parçalama arızası tarafından bildirilir arasında kopyalamak için -EFAULT.

Bilmediğim şey bunun diğer mimarilere nasıl yansıyacağı, ancak çekirdeğinde böyle bir şeyin olması iyi olurdu.

Bu tüm kemerlerde mümkün olsaydı, bazı sendikalar ve makrolar aracılığıyla tip güvenliği sağlayacak bir kullanıcı alanı sarmalayıcısı olabileceğini hayal ediyorum (sistem çağrısı adına dayalı bir sendika üyesi seçebilir ve tüm sendikalar 6 uzunluğa dönüştürülür) veya mimari de jour'un 6 uzunluğa eşdeğer olması ne olursa olsun).


1
Onun kavramının iyi bir kanıtı olsa da, uzun bir işaretçi yerine uzun bir işaretçi dizisi görmek istiyorum, böylece openin writeve dönüşünü kullanarak open-write-close gibi şeyler yapabiliyordunuz close. Bu get / put_user nedeniyle karmaşıklığı biraz artıracaktır, ancak muhtemelen buna değer. Taşınabilirlik IIRC ile ilgili olarak, bazı mimariler 5 veya 6 bağımsız bir sistem çağrısı toplu hale getirilirse, 5 ve 6 numaralı argümanlar için sistem çağrısı kayıtlarını tıkayabilir ... ileride kullanmak için 2 ek argüman eklemek bunu düzeltir ve gelecekte asenkron çağrı parametreleri için kullanılabilir bir SUPERSYSCALL__async işareti ayarlanır
technosaurus

1
Niyetim bir sys_memcpy de eklemekti. Kullanıcı daha sonra döndürülen fd'yi, modu kullanıcı alanına geri döndürmek zorunda kalmadan sys_write öğesinin ilk argümanına kopyalamak için sys_open ve sys_write arasına koyabilir.
PSkocik

3

Hemen akla gelen iki ana yaka:

  • Hata işleme: her bir sistem çağrısı, kullanıcı alanı kodunuz tarafından kontrol edilmesi ve işlenmesi gereken bir hatayla sonlanabilir. Bu nedenle, bir toplu iş aramasının, her bir ayrı ayrı çağrıdan sonra kullanıcı alanı kodunu çalıştırması gerekir; Ayrıca, API'nın çok karmaşık olması gerekir (eğer tasarlamak için mümkünse) - örneğin "üçüncü çağrı başarısız olursa, bir şey yapın ve dördüncü çağrıyı atlayın, ancak beşinci ile devam edin" gibi bir mantığı nasıl ifade edersiniz?

  • Gerçekte uygulanmakta olan birçok "birleşik" çağrı, kullanıcı ve çekirdek alanı arasında hareket etmek zorunda kalmanın yanı sıra ek avantajlar da sunar. Örneğin, genellikle bellek kopyalamaktan ve arabellekleri tamamen kullanmaktan kaçınırlar (örneğin, verileri ara arabellek yerine kopyalamak yerine doğrudan sayfa arabelleğindeki bir yerden başka bir yere aktarın). Tabii ki, bu sadece belirli çağrı kombinasyonları için anlamlıdır (örneğin, okuma-yazma-yazma), toplu çağrıların keyfi kombinasyonları için değil.


2
Re: hata işleme. Bunu düşündüm ve bu yüzden bayraklar argümanını önerdim (BATCH_RET_ON_FIRST_ERR) ... tüm çağrılar hatasız olarak tamamlanırsa başarılı bir sistem çağrısı veya biri başarısız olursa son başarılı olanı çağırmalıdır. Bu, hataları kontrol etmenize ve muhtemelen ilk başarısız çağrıdan başlayarak yalnızca 2 işaretçiyi arttırarak ve bir kaynak meşgulse veya çağrı kesilmişse dönüş değerlerine göre aramaları azaltarak yeniden denemenizi sağlar. ... bağlam dışı anahtarlama parçaları bunun kapsamı dışındadır, ancak Linux 4.2'den beri, splice () bunlara da yardımcı olabilir
technosaurus

2
Çekirdek, çeşitli işlemleri birleştirmek ve gereksiz işleri ortadan kaldırmak için çağrı listesini otomatik olarak optimize edebilir. Çekirdek, daha basit bir API ile büyük bir tasarrufla çoğu bireysel geliştiriciden daha iyi bir iş çıkarırdı.
Aleksandr Dubinsky

@technosaurus Technosaurus'un hangi işlemin başarısız olduğunu bildiren istisnalar fikriyle uyumlu olmaz (çünkü işlem sırası optimize edilir). Bu nedenle istisna normalde bu tür kesin bilgileri döndürmek için tasarlanmamıştır (ayrıca kod kafa karıştırıcı ve kırılgan hale geldiğinden). Neyse ki, çeşitli hata modlarını işleyen genel istisna işleyicileri yazmak zor değildir.
Aleksandr Dubinsky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.