io_uring
è una nuova interfaccia introdotta nel kernel Linux 5.1 nel 2019. Questa interfaccia permette di chiamare systems calls che possono essere eseguite in maniera asincrona. Inizialmente è stato introdotto supportando le system call più più semplici come read e write, ma la quantità di syscalls supportate è in constante aumento.
Perchè viene usato?
Principalmente per motivi di performance.
Il mese scorso, il lead developer Jens Axboe è riuscito ad avere 13M per core peak IOPS. Ci sono alcuni punti chiave del suo design che permettono di ridurre l’overhead e aumentare le performance.
Innanzitutto con io_uring, le system calls possono essere eseguite in maniera del tutto asincrona. Questo significa che il thread della nostra applicazione non deve rimanere bloccato mentre aspetta che il kernel completi la system call. Può semplicemente inviare una system call e ritrovarne il risultato quando è disponibile: senza bisogno di bloccare l’esecuzione e sprecare tempo in attesa della risposta.
Inoltre, è possibile sottomettere *batch* di system calls tutte insieme. Un task che normalmente richiederebbe molteplici system call, può ridurre le chiamate a solo 1! Esiste anche un modo per ridurre le system calls a zero, e si basa sull’avere una queue condivisa col kernel.
Queste operazioni permettono di ridurre di molto il numero di context switch da user space a kernel space e indietro. Ogni context switch infatti aggiunge overhead che andrà a ridurre le performance.
Con l’interfaccia io_uring, la comunicazione fra kernel e user space avviene tramite ring buffers condivisi. Questo riduce di molto la quantità di overhead quando si eseguono system calls che trasferiscono dati da kernel a userspace o viceversa. Per questo motivo, si può considerare io_uring un sistema a zero-copy.
Chiaramente l’overhead causato da operazioni bloccanti, il context switching o copiare bytes fra kernel e user space potrebbero passare inosservati; ma per sistemi ad alte performance invece possono iniziare a pesare.
Come viene usato?
Come detto sopra, applicazioni che richiedono alte performance possono beneficiare dall’utilizzo di io_uring. Può essere particolarmente utile per applicazioni che sono relative al server/backend, dove una parte significante del tempo applicativo viene speso in attesa di operazioni I/O bloccanti.
Come posso usarlo?
Il modo più facile è quello di utilizzare una delle librerie disponibili che offrono una api utente più facile da usare. In particolare, liburing è una libreria user space mantenuta dallo stesso lead developer che segue lo sviluppo dell’interfaccia nel lato kernel, quindi la libreria viene mantenuta aggiornata con le api disponibili.
Una cosa importante da ricordare è che io_uring non utilizza il versionamento delle strutture dati – quindi dovremo assicurarci che una feature è disponibile sul sistema che andrà ad eseguire il programma. Per farlo, la syscall io_uring_setup
ci viene in aiuto.
Vista la velocità con cui l’api e la libreria vengono sviluppate, la documentazione risulta a volte mancante o obsoleta, come per altro gli esempi che si possono trovare online. .
Il modo più semplice è probabilmente leggere un po’ di codice di esempio direttamente dalla libreria liburing, su github.
Per approfondire:
- Liburing: https://github.com/axboe/liburing
- Put an io_uring on it: Exploiting the Linux Kernel: https://www.graplsecurity.com/post/iou-ring-exploiting-the-linux-kernel
- Efficient IO with uring: https://kernel.dk/io_uring.pdf