Tại sao không có tòa nhà chung chung trong Linux / BSD?


17

Lý lịch:

Chi phí cuộc gọi hệ thống lớn hơn nhiều so với chi phí cuộc gọi chức năng (phạm vi ước tính từ 20-100x) chủ yếu là do chuyển ngữ cảnh từ không gian người dùng sang không gian kernel và ngược lại. Thông thường các chức năng nội tuyến để lưu chi phí cuộc gọi chức năng và các cuộc gọi chức năng rẻ hơn nhiều so với các tòa nhà cao tầng. Lý do là các nhà phát triển muốn tránh một số cuộc gọi trên hệ thống bằng cách chăm sóc càng nhiều hoạt động trong nhân trong một tòa nhà càng tốt.

Vấn đề:

Điều này đã tạo ra rất nhiều cuộc gọi hệ thống (không cần thiết?) Như sendmmsg () , recvmmsg () cũng như chdir, mở, lseek và / hoặc sự kết hợp liên kết tượng trưng như: 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, pwritevv ...

Bây giờ Linux đã thêm vào copy_file_range()mà dường như kết hợp đọc lseek và viết các tòa nhà. Vấn đề chỉ còn là thời gian trước khi điều này trở thành fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () và lcopy_file_rangeat () ... nhưng vì có 2 tệp liên quan đến X hơn. OK, Linus và các nhà phát triển BSD khác nhau sẽ không đi xa đến thế, nhưng quan điểm của tôi là nếu có một tòa nhà theo lô, tất cả (hầu hết?) Có thể được thực hiện trong không gian người dùng và giảm độ phức tạp của hạt nhân mà không cần thêm nhiều nếu bất kỳ chi phí ở phía libc.

Nhiều giải pháp phức tạp đã được đề xuất bao gồm một số dạng chuỗi tòa nhà đặc biệt cho các tòa nhà không chặn cho các tòa nhà quy trình hàng loạt; tuy nhiên các phương thức này thêm độ phức tạp đáng kể cho cả kernel và không gian người dùng theo cách tương tự như libxcb so với libX11 (các cuộc gọi không đồng bộ yêu cầu thiết lập nhiều hơn)

Giải pháp?:

Một tòa nhà chung chung. Điều này sẽ giảm bớt chi phí lớn nhất (nhiều công tắc chuyển đổi chế độ) mà không có sự phức tạp liên quan đến việc có luồng nhân chuyên biệt (mặc dù chức năng đó có thể được thêm vào sau).

Về cơ bản đã có một cơ sở tốt cho một nguyên mẫu trong tòa nhà của socketcall (). Chỉ cần mở rộng nó từ việc lấy một mảng các đối số để thay vào đó là một mảng trả về, con trỏ tới các mảng của các đối số (bao gồm số s chọc trời), số lượng các tòa nhà và một đối số cờ ... đại loại như:

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

Một điểm khác biệt lớn là tất cả các đối số có thể cần phải là con trỏ cho đơn giản để kết quả của các tòa nhà trước có thể được sử dụng bởi các tòa nhà tiếp theo (ví dụ: bộ mô tả tệp open()để sử dụng trong read()/ write())

Một số lợi thế có thể:

  • ít không gian người dùng -> không gian kernel -> chuyển đổi không gian người dùng
  • trình biên dịch có thể chuyển đổi -fcombine-s tòa nhà để cố gắng tự động hàng loạt
  • cờ tùy chọn cho hoạt động không đồng bộ (trả về fd để xem ngay)
  • khả năng thực hiện các chức năng tòa nhà kết hợp trong tương lai trong không gian người dùng

Câu hỏi:

Có khả thi để thực hiện một tòa nhà cao tầng không?

  • Tôi có thiếu một số vấn đề rõ ràng không?
  • Tôi có đánh giá quá cao lợi ích không?

Có đáng để tôi bận tâm triển khai một tòa nhà cao tầng không (tôi không làm việc tại Intel, Google hay Redhat)?

  • Tôi đã vá kernel của chính mình trước đây, nhưng sợ xử lý với LKML.
  • Lịch sử đã chỉ ra rằng ngay cả khi một cái gì đó hữu ích rộng rãi cho người dùng "bình thường" (người dùng cuối không phải là công ty không có quyền truy cập git write), nó có thể không bao giờ được chấp nhận ngược dòng (unionfs, aufs, cryptodev, tuxonice, v.v ...)

Người giới thiệu:


4
Một vấn đề khá rõ ràng mà tôi đang thấy là hạt nhân từ bỏ quyền kiểm soát về thời gian và không gian cần thiết cho một tòa nhà cao tầng cũng như sự phức tạp của các hoạt động của một tòa nhà đơn lẻ. Về cơ bản, bạn đã tạo ra một tòa nhà có thể phân bổ số lượng bộ nhớ kernel tùy ý, không giới hạn, chạy trong một khoảng thời gian tùy ý, không giới hạn và có thể phức tạp tùy ý. Bằng cách lồng các batchtòa nhà cao tầng vào các batchtòa nhà cao tầng, bạn có thể tạo ra một cây gọi sâu tùy ý của các tòa nhà cao tầng tùy ý. Về cơ bản, bạn có thể đặt toàn bộ ứng dụng của mình vào một tòa nhà duy nhất.
Jörg W Mittag

@ JörgWMittag - Tôi không gợi ý rằng những cái này chạy song song, do đó, bộ nhớ kernel được sử dụng sẽ không nhiều hơn tòa nhà nặng nhất trong lô và thời gian trong kernel vẫn bị giới hạn bởi tham số ncalls (có thể bị giới hạn bởi một số giá trị tùy ý). Bạn nói đúng về một tòa nhà hàng loạt lồng nhau là một công cụ mạnh mẽ, có thể bị ngăn chặn đến mức đó (mặc dù tôi có thể thấy nó hữu ích trong tình huống máy chủ tệp tĩnh - bằng cách cố tình dán một daemon vào vòng lặp kernel bằng con trỏ - về cơ bản triển khai máy chủ TUX cũ)
technosaurus

1
Các tòa nhà cao tầng liên quan đến một sự thay đổi đặc quyền nhưng điều này không phải lúc nào cũng được đặc trưng như một chuyển đổi bối cảnh. vi.wikipedia.org/wiki/ từ
Erik Eidt

1
đọc cái này ngày hôm qua để cung cấp thêm một số động lực và nền tảng: matildah.github.io/posts/2016-01-30-unikernel-security.html
Tom

@ JörgWMittag lồng nhau có thể không được phép để ngăn chặn tràn kernel stack. Nếu không, tòa nhà cá nhân sẽ tự do sau khi họ như thường lệ. Không nên có bất kỳ vấn đề nào về tài nguyên. Nhân Linux có thể được ưu tiên.
PSkocik

Câu trả lời:


5

Tôi đã thử điều này trên x86_64

Bản vá chống lại 94836ecf1e7378b64d37624fbb81fe48fbd4c772: (cũng ở đây 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);

Và nó xuất hiện để hoạt động - Tôi có thể viết lời chào đến fd 1 và thế giới đến fd 2 chỉ với một tòa nhà:

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

Về cơ bản tôi đang sử dụng:

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

như một nguyên mẫu tòa nhà chung, dường như là cách mọi thứ hoạt động trên x86_64, vì vậy tòa nhà "siêu" của tôi là:

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

Nó trả về số lượng các tòa nhà chọc trời đã thử ( ==Nargsnếu SUPERSYSCALL__continue_on_failurecờ được thông qua, ngược lại >0 && <=Nargs) và không thể sao chép giữa không gian hạt nhân và không gian người dùng được báo hiệu bằng segfaults thay vì thông thường -EFAULT.

Những gì tôi không biết là làm thế nào điều này sẽ chuyển đến các kiến ​​trúc khác, nhưng chắc chắn sẽ rất tốt nếu có một cái gì đó như thế này trong kernel.

Nếu điều này là có thể đối với tất cả các vòm, tôi tưởng tượng có thể có một trình bao bọc không gian người dùng sẽ cung cấp sự an toàn kiểu thông qua một số hiệp hội và macro (nó có thể chọn một thành viên công đoàn dựa trên tên tòa nhà và tất cả các hiệp hội sẽ được chuyển đổi thành 6 thời gian dài hoặc bất cứ điều gì kiến ​​trúc tương đương với 6 thời gian sẽ là).


1
Đó là một bằng chứng tốt về khái niệm, mặc dù tôi muốn thấy một loạt các con trỏ dài thay vì chỉ là một mảng dài, để bạn có thể làm những việc như open-write-close bằng cách sử dụng sự trở lại của openin writeclose. Điều đó sẽ tăng độ phức tạp một chút do get / put_user, nhưng có lẽ đáng giá. Đối với tính di động IIRC, một số kiến ​​trúc có thể ghi đè các thanh ghi tòa nhà cho các đối số 5 và 6 nếu một tòa nhà 5 hoặc 6 arg bị bó ... thêm 2 đối số để sử dụng trong tương lai sẽ khắc phục điều đó và có thể được sử dụng trong tương lai cho các tham số cuộc gọi không đồng bộ nếu một cờ SUPERSYSCALL__async được đặt
technosaurus

1
Ý định của tôi là cũng thêm một sys_memcpy. Sau đó, người dùng có thể đặt nó vào giữa sys_open và sys_write để sao chép fd được trả về vào đối số đầu tiên của sys_write mà không phải chuyển chế độ trở lại không gian người dùng.
PSkocik

3

Hai vấn đề chính xuất hiện trong tâm trí là:

  • Xử lý lỗi: mỗi tòa nhà riêng lẻ có thể kết thúc bằng một lỗi cần được kiểm tra và xử lý bằng mã không gian người dùng của bạn. Do đó, một cuộc gọi theo đợt sẽ phải chạy mã vùng người dùng sau mỗi cuộc gọi riêng lẻ vì vậy lợi ích của các cuộc gọi không gian kernel sẽ bị phủ nhận. Ngoài ra, API sẽ phải rất phức tạp (nếu có thể thiết kế hoàn toàn) - ví dụ: bạn sẽ thể hiện logic như "nếu cuộc gọi thứ ba thất bại, hãy làm gì đó và bỏ qua cuộc gọi thứ tư nhưng tiếp tục với cuộc gọi thứ năm")?

  • Nhiều cuộc gọi "kết hợp" thực sự được triển khai mang lại lợi ích bổ sung ngoài việc không phải di chuyển giữa không gian người dùng và nhân. Ví dụ, họ thường sẽ tránh sao chép bộ nhớ và sử dụng bộ đệm hoàn toàn (ví dụ: truyền dữ liệu trực tiếp từ một nơi trong bộ đệm trang sang nơi khác thay vì sao chép nó qua bộ đệm trung gian). Tất nhiên, điều này chỉ có ý nghĩa đối với các kết hợp cuộc gọi cụ thể (ví dụ đọc-ghi-viết), không phải cho các kết hợp cuộc gọi theo đợt tùy ý.


2
Re: xử lý lỗi. Tôi đã nghĩ về điều đó và đó là lý do tại sao tôi đề xuất đối số cờ (BATCH_RET_ON_FIRST_ERR) ... một tòa nhà thành công sẽ trả về ncalls nếu tất cả các cuộc gọi hoàn thành không có lỗi hoặc cuộc gọi thành công cuối cùng nếu một cuộc gọi thất bại. Điều này sẽ cho phép bạn kiểm tra lỗi và có thể thử lại bắt đầu từ cuộc gọi không thành công đầu tiên chỉ bằng cách tăng 2 con trỏ và giảm số ncalls theo giá trị trả về nếu tài nguyên chỉ bận hoặc cuộc gọi bị gián đoạn. ... các phần chuyển đổi không theo ngữ cảnh nằm ngoài phạm vi cho điều này, nhưng vì Linux 4.2, splice () cũng có thể giúp những phần đó
technosaurus

2
Nhân có thể tự động tối ưu hóa danh sách cuộc gọi để hợp nhất các hoạt động khác nhau và loại bỏ công việc dư thừa. Hạt nhân có thể sẽ làm một công việc tốt hơn so với hầu hết các nhà phát triển cá nhân với nỗ lực tiết kiệm lớn với API đơn giản hơn.
Alexanderr Dubinsky

@technosaurus Nó sẽ không tương thích với ý tưởng về các trường hợp ngoại lệ của Technosaurus, giao tiếp hoạt động nào thất bại (vì thứ tự các hoạt động được tối ưu hóa). Đây là lý do tại sao ngoại lệ thường không được thiết kế để trả về thông tin chính xác như vậy (cũng bởi vì mã trở nên khó hiểu và dễ vỡ). May mắn thay, không khó để viết các trình xử lý ngoại lệ chung xử lý các chế độ thất bại khác nhau.
Alexanderr Dubinsky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.