Antecedentes:
La sobrecarga de llamadas del sistema es mucho mayor que la sobrecarga de llamadas de función (el rango de estimaciones es de 20-100x) principalmente debido al cambio de contexto del espacio del usuario al espacio del kernel y viceversa. Es común que las funciones en línea ahorren sobrecarga de llamadas a funciones y las llamadas a funciones son mucho más baratas que las llamadas al sistema. Es lógico que los desarrolladores deseen evitar parte de la sobrecarga de llamadas del sistema al ocuparse de la mayor cantidad posible de operaciones en el núcleo en una llamada al sistema.
Problema:
Esto ha creado una gran cantidad de (superfluos?) Llamadas al sistema como sendmmsg () , recvmmsg () , así como la chdir, abierto, lseek y / o combinaciones de enlaces simbólicos como: 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
etc ...
Ahora se ha agregado Linux, copy_file_range()
que aparentemente combina lecturas de lseek y syscalls de escritura. Es solo una cuestión de tiempo antes de que esto se convierta en fcopy_file_range (), lcopy_file_range (), copy_file_rangeat (), fcopy_file_rangeat () y lcopy_file_rangeat () ... pero dado que hay 2 archivos involucrados en lugar de X llamadas más, podría convertirse en X ^ 2 más. De acuerdo, Linus y los diversos desarrolladores de BSD no lo dejarían ir tan lejos, pero mi punto es que si hubiera una syscall por lotes, todos (¿la mayoría?) Podrían implementarse en el espacio del usuario y reducir la complejidad del kernel sin agregar mucho si hay alguna sobrecarga en el lado de la biblioteca.
Se han propuesto muchas soluciones complejas que incluyen alguna hebra especial de syscall para llamadas syscall sin bloqueo para llamadas syscalls de proceso por lotes; sin embargo, estos métodos agregan una complejidad significativa tanto al kernel como al espacio del usuario de la misma manera que libxcb vs. libX11 (las llamadas asincrónicas requieren mucha más configuración)
¿Solución?:
Un syscall genérico por lotes. Esto aliviaría el mayor costo (conmutadores de modo múltiple) sin las complejidades asociadas con tener un hilo de kernel especializado (aunque esa funcionalidad podría agregarse más adelante).
Básicamente, ya existe una buena base para un prototipo en la llamada al sistema socketcall (). Simplemente extiéndalo de tomar una matriz de argumentos para tomar una matriz de retornos, puntero a matrices de argumentos (que incluye el número de syscall), el número de syscalls y un argumento de banderas ... algo como:
batch(void *returns, void *args, long ncalls, long flags);
Una diferencia importante sería que los argumentos probablemente toda necesidad de ser punteros para simplicidad de manera que los resultados de las llamadas al sistema anteriores podrían ser utilizados por llamadas al sistema posteriores (por ejemplo, el descriptor de archivo de open()
para su uso en read()
/ write()
)
Algunas posibles ventajas:
- menos espacio de usuario -> espacio de kernel -> cambio de espacio de usuario
- posible compilador conmutador -fcombine-syscalls para tratar de procesar automáticamente
- Indicador opcional para operación asincrónica (devuelva fd para mirar inmediatamente)
- capacidad para implementar futuras funciones combinadas de syscall en el espacio de usuario
Pregunta:
¿Es factible implementar una syscall por lotes?
- ¿Me estoy perdiendo algunas trampas obvias?
- ¿Estoy sobreestimando los beneficios?
¿Me vale la pena molestarme en implementar un syscall por lotes (no trabajo en Intel, Google o Redhat)?
- He parcheado mi propio kernel antes, pero temo tratar con el LKML.
- La historia ha demostrado que incluso si algo es ampliamente útil para los usuarios "normales" (usuarios finales no corporativos sin acceso de escritura git), es posible que nunca se acepte en sentido ascendente (unionfs, aufs, cryptodev, tuxonice, etc.)
Referencias
fuente
batch
syscalls enbatch
syscalls, puede crear un árbol de llamadas arbitrariamente profundas de syscalls arbitrarias. Básicamente, puede poner toda su aplicación en una sola llamada al sistema.Respuestas:
Probé esto en x86_64
Parche contra 94836ecf1e7378b64d37624fbb81fe48fbd4c772: (también aquí https://github.com/pskocik/linux/tree/supersyscall )
Y parece funcionar: puedo escribir hola a fd 1 y world a fd 2 con solo una llamada al sistema:
Básicamente estoy usando:
como un prototipo universal de syscall, que parece ser cómo funcionan las cosas en x86_64, por lo que mi "súper" syscall es:
Devuelve el número de llamadas al sistema intentadas (
==Nargs
siSUPERSYSCALL__continue_on_failure
se pasa el indicador, de lo contrario>0 && <=Nargs
) y las fallas para copiar entre el espacio del núcleo y el espacio del usuario se señalan mediante segfaults en lugar de lo habitual-EFAULT
.Lo que no sé es cómo esto se portaría a otras arquitecturas, pero seguramente sería bueno tener algo como esto en el núcleo.
Si esto fuera posible para todos los arcos, imagino que podría haber un contenedor de espacio de usuario que proporcionaría seguridad de tipo a través de algunos sindicatos y macros (podría seleccionar un miembro del sindicato basado en el nombre de syscall y todos los sindicatos se convertirían a los 6 largos o cualquiera que sea el equivalente de la arquitectura de jour de los 6 largos sería).
fuente
open
inwrite
yclose
. Eso aumentaría un poco la complejidad debido a get / put_user, pero probablemente valga la pena. En cuanto a la portabilidad IIRC, algunas arquitecturas pueden bloquear los registros de syscall para los args 5 y 6 si se combina un syscall de 5 o 6 arg ... agregar 2 args adicionales para uso futuro solucionaría eso y podría usarse en el futuro para parámetros de llamadas asíncronas si se establece una bandera SUPERSYSCALL__asyncDos problemas principales que vienen a la mente de inmediato son:
Manejo de errores: cada llamada al sistema individual puede terminar con un error que debe ser verificado y manejado por su código de espacio de usuario. Por lo tanto, una llamada de procesamiento por lotes tendría que ejecutar un código de espacio de usuario después de cada llamada individual de todos modos, por lo que los beneficios de las llamadas de espacio de kernel por lotes se negarían. Además, la API tendría que ser muy compleja (si es posible diseñarla), por ejemplo, ¿cómo expresaría una lógica como "si la tercera llamada falla, haga algo y salte la cuarta llamada pero continúe con la quinta"?
Muchas llamadas "combinadas" que realmente se implementan ofrecen beneficios adicionales además de no tener que moverse entre el espacio del usuario y del kernel. Por ejemplo, a menudo evitarán copiar memoria y usar memorias intermedias por completo (por ejemplo, transferir datos directamente de un lugar en el búfer de la página a otro en lugar de copiarlos a través de un búfer intermedio). Por supuesto, esto solo tiene sentido para combinaciones específicas de llamadas (por ejemplo, leer-luego-escribir), no para combinaciones arbitrarias de llamadas por lotes.
fuente