1 .. include:: ../disclaimer-ita.rst 2 3 :Original: Documentation/process/botching-up-ioctls.rst 4 5 ========================================== 6 (Come evitare di) Raffazzonare delle ioctl 7 ========================================== 8 9 Preso da: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html 10 11 Scritto da : Daniel Vetter, Copyright © 2013 Intel Corporation 12 13 Una cosa che gli sviluppatori del sottosistema grafico del kernel Linux hanno 14 imparato negli ultimi anni è l'inutilità di cercare di creare un'interfaccia 15 unificata per gestire la memoria e le unità esecutive di diverse GPU. Dunque, 16 oggigiorno ogni driver ha il suo insieme di ioctl per allocare memoria ed 17 inviare dei programmi alla GPU. Il che è va bene dato che non c'è più un insano 18 sistema che finge di essere generico, ma al suo posto ci sono interfacce 19 dedicate. Ma al tempo stesso è più facile incasinare le cose. 20 21 Per evitare di ripetere gli stessi errori ho preso nota delle lezioni imparate 22 mentre raffazzonavo il driver drm/i915. La maggior parte di queste lezioni si 23 focalizzano sui tecnicismi e non sulla visione d'insieme, come le discussioni 24 riguardo al modo migliore per implementare una ioctl per inviare compiti alla 25 GPU. Probabilmente, ogni sviluppatore di driver per GPU dovrebbe imparare queste 26 lezioni in autonomia. 27 28 29 Prerequisiti 30 ------------ 31 32 Prima i prerequisiti. Seguite i seguenti suggerimenti se non volete fallire in 33 partenza e ritrovarvi ad aggiungere un livello di compatibilità a 32-bit. 34 35 * Usate solamente interi a lunghezza fissa. Per evitare i conflitti coi tipi 36 definiti nello spazio utente, il kernel definisce alcuni tipi speciali, come: 37 ``__u32``, ``__s64``. Usateli. 38 39 * Allineate tutto alla lunghezza naturale delle piattaforma in uso e riempite 40 esplicitamente i vuoti. Non necessariamente le piattaforme a 32-bit allineano 41 i valori a 64-bit rispettandone l'allineamento, ma le piattaforme a 64-bit lo 42 fanno. Dunque, per farlo correttamente in entrambe i casi dobbiamo sempre 43 riempire i vuoti. 44 45 * Se una struttura dati contiene valori a 64-bit, allora fate si che la sua 46 dimensione sia allineata a 64-bit, altrimenti la sua dimensione varierà su 47 sistemi a 32-bit e 64-bit. Avere una dimensione differente causa problemi 48 quando si passano vettori di strutture dati al kernel, o quando il kernel 49 effettua verifiche sulla dimensione (per esempio il sistema drm lo fa). 50 51 * I puntatori sono di tipo ``__u64``, con un *cast* da/a ``uintptr_t`` da lato 52 spazio utente e da/a ``void __user *`` nello spazio kernel. Sforzatevi il più 53 possibile per non ritardare la conversione, o peggio maneggiare ``__u64`` nel 54 vostro codice perché questo riduce le verifiche che strumenti come sparse 55 possono effettuare. La macro u64_to_user_ptr() può essere usata nel kernel 56 per evitare avvisi riguardo interi e puntatori di dimensioni differenti. 57 58 59 Le Basi 60 ------- 61 62 Con la gioia d'aver evitato un livello di compatibilità, possiamo ora dare uno 63 sguardo alle basi. Trascurare questi punti renderà difficile la gestione della 64 compatibilità all'indietro ed in avanti. E dato che sbagliare al primo colpo è 65 garantito, dovrete rivisitare il codice o estenderlo per ogni interfaccia. 66 67 * Abbiate un modo chiaro per capire dallo spazio utente se una nuova ioctl, o 68 l'estensione di una esistente, sia supportata dal kernel in esecuzione. Se non 69 potete fidarvi del fatto che un vecchio kernel possa rifiutare correttamente 70 un nuovo *flag*, modalità, o ioctl, (probabilmente perché avevate raffazzonato 71 qualcosa nel passato) allora dovrete implementare nel driver un meccanismo per 72 notificare quali funzionalità sono supportate, o in alternativa un numero di 73 versione. 74 75 * Abbiate un piano per estendere le ioctl con nuovi *flag* o campi alla fine di 76 una struttura dati. Il sistema drm verifica la dimensione di ogni ioctl in 77 arrivo, ed estende con zeri ogni incongruenza fra kernel e spazio utente. 78 Questo aiuta, ma non è una soluzione completa dato che uno spazio utente nuovo 79 su un kernel vecchio non noterebbe che i campi nuovi alla fine della struttura 80 vengono ignorati. Dunque, anche questo avrà bisogno di essere notificato dal 81 driver allo spazio utente. 82 83 * Verificate tutti i campi e *flag* inutilizzati ed i riempimenti siano a 0, 84 altrimenti rifiutare la ioctl. Se non lo fate il vostro bel piano per 85 estendere le ioctl andrà a rotoli dato che qualcuno userà delle ioctl con 86 strutture dati con valori casuali dallo stack nei campi inutilizzati. Il che 87 si traduce nell'avere questi campi nell'ABI, e la cui unica utilità sarà 88 quella di contenere spazzatura. Per questo dovrete esplicitamente riempire i 89 vuoti di tutte le vostre strutture dati, anche se non le userete in un 90 vettore. Il riempimento fatto dal compilatore potrebbe contenere valori 91 casuali. 92 93 * Abbiate un semplice codice di test per ognuno dei casi sopracitati. 94 95 96 Divertirsi coi percorsi d'errore 97 -------------------------------- 98 99 Oggigiorno non ci sono più scuse rimaste per permettere ai driver drm di essere 100 sfruttati per diventare root. Questo significa che dobbiamo avere una completa 101 validazione degli input e gestire in modo robusto i percorsi - tanto le GPU 102 moriranno comunque nel più strano dei casi particolari: 103 104 * Le ioctl devono verificare l'overflow dei vettori. Inoltre, per i valori 105 interi si devono verificare *overflow*, *underflow*, e *clamping*. Il 106 classico esempio è l'inserimento direttamente nell'hardware di valori di 107 posizionamento di un'immagine *sprite* quando l'hardware supporta giusto 12 108 bit, o qualcosa del genere. Tutto funzionerà finché qualche strano *display 109 server* non decide di preoccuparsi lui stesso del *clamping* e il cursore 110 farà il giro dello schermo. 111 112 * Avere un test semplice per ogni possibile fallimento della vostra ioctl. 113 Verificate che il codice di errore rispetti le aspettative. Ed infine, 114 assicuratevi che verifichiate un solo percorso sbagliato per ogni sotto-test 115 inviando comunque dati corretti. Senza questo, verifiche precedenti 116 potrebbero rigettare la ioctl troppo presto, impedendo l'esecuzione del 117 codice che si voleva effettivamente verificare, rischiando quindi di 118 mascherare bachi e regressioni. 119 120 * Fate si che tutte le vostre ioctl siano rieseguibili. Prima di tutto X adora 121 i segnali; secondo questo vi permetterà di verificare il 90% dei percorsi 122 d'errore interrompendo i vostri test con dei segnali. Grazie all'amore di X 123 per i segnali, otterrete gratuitamente un eccellente copertura di base per 124 tutti i vostri percorsi d'errore. Inoltre, siate consistenti sul modo di 125 gestire la riesecuzione delle ioctl - per esempio, drm ha una piccola 126 funzione di supporto `drmIoctl` nella sua librerie in spazio utente. Il 127 driver i915 l'abbozza con l'ioctl `set_tiling`, ed ora siamo inchiodati per 128 sempre con una semantica arcana sia nel kernel che nello spazio utente. 129 130 131 * Se non potete rendere un pezzo di codice rieseguibile, almeno rendete 132 possibile la sua interruzione. Le GPU moriranno e i vostri utenti non vi 133 apprezzeranno affatto se tenete in ostaggio il loro scatolotto (mediante un 134 processo X insopprimibile). Se anche recuperare lo stato è troppo complicato, 135 allora implementate una scadenza oppure come ultima spiaggia una rete di 136 sicurezza per rilevare situazioni di stallo quando l'hardware da di matto. 137 138 * Preparate dei test riguardo ai casi particolarmente estremi nel codice di 139 recupero del sistema - è troppo facile create uno stallo fra il vostro codice 140 anti-stallo e un processo scrittore. 141 142 143 Tempi, attese e mancate scadenze 144 -------------------------------- 145 146 Le GPU fanno quasi tutto in modo asincrono, dunque dobbiamo regolare le 147 operazioni ed attendere quelle in sospeso. Questo è davvero difficile; al 148 momento nessuna delle ioctl supportante dal driver drm/i915 riesce a farlo 149 perfettamente, il che significa che qui ci sono ancora una valanga di lezioni da 150 apprendere. 151 152 * Per fare riferimento al tempo usate sempre ``CLOCK_MONOTONIC``. Oggigiorno 153 questo è quello che viene usato di base da alsa, drm, e v4l. Tuttavia, 154 lasciate allo spazio utente la possibilità di capire quali *timestamp* 155 derivano da domini temporali diversi come il vostro orologio di sistema 156 (fornito dal kernel) oppure un contatore hardware indipendente da qualche 157 parte. Gli orologi divergeranno, ma con questa informazione gli strumenti di 158 analisi delle prestazioni possono compensare il problema. Se il vostro spazio 159 utente può ottenere i valori grezzi degli orologi, allora considerate di 160 esporre anch'essi. 161 162 * Per descrivere il tempo, usate ``__s64`` per i secondi e ``__u64`` per i 163 nanosecondi. Non è il modo migliore per specificare il tempo, ma è 164 praticamente uno standard. 165 166 * Verificate che gli input di valori temporali siano normalizzati, e se non lo 167 sono scartateli. Fate attenzione perché la struttura dati ``struct ktime`` 168 del kernel usa interi con segni sia per i secondi che per i nanosecondi. 169 170 * Per le scadenze (*timeout*) usate valori temporali assoluti. Se siete dei 171 bravi ragazzi e avete reso la vostra ioctl rieseguibile, allora i tempi 172 relativi tendono ad essere troppo grossolani e a causa degli arrotondamenti 173 potrebbero estendere in modo indefinito i tempi di attesa ad ogni 174 riesecuzione. Particolarmente vero se il vostro orologio di riferimento è 175 qualcosa di molto lento come il contatore di *frame*. Con la giacca da 176 avvocato delle specifiche diremmo che questo non è un baco perché tutte le 177 scadenze potrebbero essere estese - ma sicuramente gli utenti vi odieranno 178 quando le animazioni singhiozzano. 179 180 * Considerate l'idea di eliminare tutte le ioctl sincrone con scadenze, e di 181 sostituirle con una versione asincrona il cui stato può essere consultato 182 attraverso il descrittore di file mediante ``poll``. Questo approccio si 183 sposa meglio in un applicazione guidata dagli eventi. 184 185 * Sviluppate dei test per i casi estremi, specialmente verificate che i valori 186 di ritorno per gli eventi già completati, le attese terminate con successo, e 187 le attese scadute abbiano senso e servano ai vostri scopi. 188 189 190 Non perdere risorse 191 ------------------- 192 Nel suo piccolo il driver drm implementa un sistema operativo specializzato per 193 certe GPU. Questo significa che il driver deve esporre verso lo spazio 194 utente tonnellate di agganci per accedere ad oggetti e altre risorse. Farlo 195 correttamente porterà con se alcune insidie: 196 197 * Collegate sempre la vita di una risorsa creata dinamicamente, a quella del 198 descrittore di file. Considerate una mappatura 1:1 se la vostra risorsa 199 dev'essere condivisa fra processi - passarsi descrittori di file sul socket 200 unix semplifica la gestione anche per lo spazio utente. 201 202 * Dev'esserci sempre Il supporto ``O_CLOEXEC``. 203 204 * Assicuratevi di avere abbastanza isolamento fra utenti diversi. Di base 205 impostate uno spazio dei nomi riservato per ogni descrittore di file, il che 206 forzerà ogni condivisione ad essere esplicita. Usate uno spazio più globale 207 per dispositivo solo se gli oggetti sono effettivamente unici per quel 208 dispositivo. Un controesempio viene dall'interfaccia drm modeset, dove 209 oggetti specifici di dispositivo, come i connettori, condividono uno spazio 210 dei nomi con oggetti per il *framebuffer*, ma questi non sono per niente 211 condivisi. Uno spazio separato, privato di base, per i *framebuffer* sarebbe 212 stato meglio. 213 214 * Pensate all'identificazione univoca degli agganci verso lo spazio utente. Per 215 esempio, per la maggior parte dei driver drm, si considera fallace la doppia 216 sottomissione di un oggetto allo stesso comando ioctl. Ma per evitarlo, se 217 gli oggetti sono condivisibili, lo spazio utente ha bisogno di sapere se il 218 driver ha importato un oggetto da un altro processo. Non l'ho ancora provato, 219 ma considerate l'idea di usare il numero di inode come identificatore per i 220 descrittori di file condivisi - che poi è come si distinguono i veri file. 221 Sfortunatamente, questo richiederebbe lo sviluppo di un vero e proprio 222 filesystem virtuale nel kernel. 223 224 225 Ultimo, ma non meno importante 226 ------------------------------ 227 228 Non tutti i problemi si risolvono con una nuova ioctl: 229 230 * Pensateci su due o tre volte prima di implementare un'interfaccia privata per 231 un driver. Ovviamente è molto più veloce seguire questa via piuttosto che 232 buttarsi in lunghe discussioni alla ricerca di una soluzione più generica. Ed 233 a volte un'interfaccia privata è quello che serve per sviluppare un nuovo 234 concetto. Ma alla fine, una volta che c'è un'interfaccia generica a 235 disposizione finirete per mantenere due interfacce. Per sempre. 236 237 * Considerate interfacce alternative alle ioctl. Gli attributi sysfs sono molto 238 meglio per impostazioni che sono specifiche di un dispositivo, o per 239 sotto-oggetti con una vita piuttosto statica (come le uscite dei connettori in 240 drm con tutti gli attributi per la sovrascrittura delle rilevazioni). O magari 241 solo il vostro sistema di test ha bisogno di una certa interfaccia, e allora 242 debugfs (che non ha un'interfaccia stabile) sarà la soluzione migliore. 243 244 Per concludere. Questo gioco consiste nel fare le cose giuste fin da subito, 245 dato che se il vostro driver diventa popolare e la piattaforma hardware longeva 246 finirete per mantenere le vostre ioctl per sempre. Potrete tentare di deprecare 247 alcune orribili ioctl, ma ci vorranno anni per riuscirci effettivamente. E 248 ancora, altri anni prima che sparisca l'ultimo utente capace di lamentarsi per 249 una regressione.
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.