Tło:
Narzut wywołania systemowego jest znacznie większy niż narzut wywołania funkcji (szacowany zakres od 20-100x), głównie z powodu przełączania kontekstu z przestrzeni użytkownika na przestrzeń jądra iz powrotem. Zazwyczaj funkcje wbudowane oszczędzają narzutu wywołania funkcji, a wywołania funkcji są znacznie tańsze niż wywołania systemowe. Jest oczywiste, że programiści chcieliby uniknąć narzutu wywołania systemowego, dbając o jak najwięcej operacji wewnątrz jądra w jednym wywołaniu systemowym.
Problem:
W ten sposób powstało wiele (zbędne?) Wywołań systemowych, takich jak sendmmsg () , recvmmsg () jak również CHDIR, otwarty, lseek i / lub kombinacji dowiązań symbolicznych, takich jak: 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
itd ...
Teraz Linux dodał, copy_file_range()
który najwyraźniej łączy odczyt lseek i pisanie wywołań systemowych. To tylko kwestia czasu, zanim zmieni się w fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () i lcopy_file_rangeat () ... ale ponieważ są dwa pliki zaangażowane zamiast X więcej wywołań, może stać się X ^ 2 więcej. OK, Linus i różni programiści BSD nie pozwolili by posunąć się tak daleko, ale chodzi mi o to, że gdyby istniało wsadowe wywołanie systemowe, wszystkie (większość?) Można by zaimplementować w przestrzeni użytkownika i zmniejszyć złożoność jądra bez dodawania dużej ilości jeśli narzut po stronie libc.
Zaproponowano wiele złożonych rozwiązań, które obejmują niektóre specjalne wątki systemowe do nie wywoływania bloków systemowych do przetwarzania wsadowego; jednak metody te znacznie zwiększają złożoność przestrzeni jądra i użytkownika, podobnie jak libxcb vs. libX11 (wywołania asynchroniczne wymagają znacznie większej konfiguracji)
Rozwiązanie?:
Ogólny system wsadowy. Zmniejszyłoby to największy koszt (przełączniki wielu trybów) bez złożoności związanej z posiadaniem specjalistycznego wątku jądra (chociaż tę funkcjonalność można by dodać później).
Zasadniczo istnieje już dobra podstawa dla prototypu w funkcji systemowej socketcall (). Po prostu rozszerz go od pobierania tablicy argumentów, aby zamiast tego pobierał tablicę zwrotów, wskaźnik do tablic argumentów (w tym numer syscall), liczbę syscall i argument flagi ... coś w stylu:
batch(void *returns, void *args, long ncalls, long flags);
Jedną z głównych różnic jest to, że argumenty prawdopodobnie wszystko trzeba być wskaźniki dla uproszczenia tak, że wyniki poprzednich syscalli mogłyby zostać wykorzystane przez kolejnych syscalli (na przykład deskryptor pliku z open()
do zastosowania w read()
/ write()
)
Niektóre możliwe zalety:
- mniejsza przestrzeń użytkownika -> przestrzeń jądra -> przełączanie przestrzeni użytkownika
- możliwy przełącznik kompilatora -fcombine-syscalls, aby spróbować wsadowo zautomatyzować
- opcjonalna flaga dla operacji asynchronicznej (zwróć fd, aby obejrzeć natychmiast)
- możliwość implementacji przyszłych połączonych funkcji syscall w przestrzeni użytkownika
Pytanie:
Czy można wdrożyć syscall wsadowy?
- Czy brakuje mi oczywistych problemów?
- Czy przeceniam korzyści?
Czy warto zawracać sobie głowę wdrażaniem systemu wsadowego (nie pracuję w Intelu, Google ani Redhat)?
- Już wcześniej załatałem własne jądro, ale boję się radzenia sobie z LKML.
- Historia pokazała, że nawet jeśli coś jest bardzo przydatne dla „zwykłych” użytkowników (nie-korporacyjnych użytkowników końcowych bez dostępu do zapisu git), może nigdy nie zostać zaakceptowane powyżej (unionfs, aufs, cryptodev, tuxonice itp.)
Bibliografia:
źródło
batch
syscall wbatch
syscall, możesz stworzyć dowolnie głębokie drzewo wywołań arbitralnych syscall. Zasadniczo możesz umieścić całą aplikację w jednym wywołaniu systemowym.Odpowiedzi:
Próbowałem tego na x86_64
Łatka przeciwko 94836ecf1e7378b64d37624fbb81fe48fbd4c772: (także tutaj https://github.com/pskocik/linux/tree/supersyscall )
I wydaje się, że działa - mogę napisać cześć na fd 1 i world na fd 2 za pomocą tylko jednego połączenia systemowego:
Zasadniczo używam:
jako uniwersalny prototyp syscall, który wygląda jak działa na x86_64, więc mój „super” syscall to:
Zwraca liczbę próbnych wywołań systemowych (
==Nargs
jeśliSUPERSYSCALL__continue_on_failure
flaga zostanie przekazana, w przeciwnym razie>0 && <=Nargs
), a niepowodzenia kopiowania między przestrzenią jądra a przestrzenią użytkownika są sygnalizowane przez segfault zamiast zwykłych-EFAULT
.Nie wiem, w jaki sposób można by to przenieść na inne architektury, ale na pewno byłoby miło mieć coś takiego w jądrze.
Gdyby było to możliwe dla wszystkich łuków, wyobrażam sobie, że może istnieć opakowanie przestrzeni użytkownika, które zapewniłoby bezpieczeństwo typu przez niektóre związki i makra (mogłoby wybrać członka związku na podstawie nazwy syscall, a wszystkie związki przekształciłyby się w 6 długości) lub cokolwiek by to było równoważne 6 desek architektury de Jour).
źródło
open
wwrite
iclose
. Zwiększy to nieco złożoność z powodu get / put_user, ale prawdopodobnie warto. Jeśli chodzi o przenośność IIRC, niektóre architektury mogą zablokować rejestry syscall dla argumentów 5 i 6, jeśli syscall 5 lub 6 arg jest wsadowy ... dodanie 2 dodatkowych argumentów do wykorzystania w przyszłości naprawiłoby to i mogłoby być użyte w przyszłości dla asynchronicznych parametrów wywołania, jeśli ustawiona jest flaga SUPERSYSCALL__asyncDwie główne wpadki, które natychmiast przychodzą mi do głowy:
Obsługa błędów: każde indywidualne wywołanie systemowe może zakończyć się błędem, który należy sprawdzić i obsłużyć za pomocą kodu przestrzeni użytkownika. W związku z tym wywołanie wsadowe musiałoby uruchamiać kod przestrzeni użytkownika po każdym indywidualnym wywołaniu, więc korzyści płynące z pakietowania wywołań w przestrzeni jądra zostałyby zanegowane. Ponadto interfejs API musiałby być bardzo złożony (jeśli w ogóle możliwe zaprojektowanie) - na przykład, jak wyraziłbyś logikę, np. „Jeśli trzecie połączenie się nie powiedzie, zrób coś i pomiń czwarte połączenie, ale kontynuuj piąte”)?
Wiele „połączonych” wywołań, które faktycznie się implementują, oferuje dodatkowe korzyści oprócz konieczności przechodzenia między przestrzenią użytkownika a jądrem. Na przykład często unikają kopiowania pamięci i całkowitego używania buforów (np. Przenoszą dane bezpośrednio z jednego miejsca w buforze strony do innego zamiast kopiować je przez bufor pośredni). Oczywiście ma to sens tylko w przypadku określonych kombinacji wywołań (np. Odczyt i zapis), a nie w przypadku dowolnych kombinacji wywołań pakietowych.
źródło