250 lines · plain
1.. include:: ../disclaimer-ita.rst2 3:Original: Documentation/process/botching-up-ioctls.rst4 5==========================================6(Come evitare di) Raffazzonare delle ioctl7==========================================8 9Preso da: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html10 11Scritto da : Daniel Vetter, Copyright © 2013 Intel Corporation12 13Una cosa che gli sviluppatori del sottosistema grafico del kernel Linux hanno14imparato negli ultimi anni è l'inutilità di cercare di creare un'interfaccia15unificata per gestire la memoria e le unità esecutive di diverse GPU. Dunque,16oggigiorno ogni driver ha il suo insieme di ioctl per allocare memoria ed17inviare dei programmi alla GPU. Il che è va bene dato che non c'è più un insano18sistema che finge di essere generico, ma al suo posto ci sono interfacce19dedicate. Ma al tempo stesso è più facile incasinare le cose.20 21Per evitare di ripetere gli stessi errori ho preso nota delle lezioni imparate22mentre raffazzonavo il driver drm/i915. La maggior parte di queste lezioni si23focalizzano sui tecnicismi e non sulla visione d'insieme, come le discussioni24riguardo al modo migliore per implementare una ioctl per inviare compiti alla25GPU. Probabilmente, ogni sviluppatore di driver per GPU dovrebbe imparare queste26lezioni in autonomia.27 28 29Prerequisiti30------------31 32Prima i prerequisiti. Seguite i seguenti suggerimenti se non volete fallire in33partenza e ritrovarvi ad aggiungere un livello di compatibilità a 32-bit.34 35* Usate solamente interi a lunghezza fissa. Per evitare i conflitti coi tipi36 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 riempite40 esplicitamente i vuoti. Non necessariamente le piattaforme a 32-bit allineano41 i valori a 64-bit rispettandone l'allineamento, ma le piattaforme a 64-bit lo42 fanno. Dunque, per farlo correttamente in entrambe i casi dobbiamo sempre43 riempire i vuoti.44 45* Se una struttura dati contiene valori a 64-bit, allora fate si che la sua46 dimensione sia allineata a 64-bit, altrimenti la sua dimensione varierà su47 sistemi a 32-bit e 64-bit. Avere una dimensione differente causa problemi48 quando si passano vettori di strutture dati al kernel, o quando il kernel49 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 lato52 spazio utente e da/a ``void __user *`` nello spazio kernel. Sforzatevi il più53 possibile per non ritardare la conversione, o peggio maneggiare ``__u64`` nel54 vostro codice perché questo riduce le verifiche che strumenti come sparse55 possono effettuare. La macro u64_to_user_ptr() può essere usata nel kernel56 per evitare avvisi riguardo interi e puntatori di dimensioni differenti.57 58 59Le Basi60-------61 62Con la gioia d'aver evitato un livello di compatibilità, possiamo ora dare uno63sguardo alle basi. Trascurare questi punti renderà difficile la gestione della64compatibilità all'indietro ed in avanti. E dato che sbagliare al primo colpo è65garantito, dovrete rivisitare il codice o estenderlo per ogni interfaccia.66 67* Abbiate un modo chiaro per capire dallo spazio utente se una nuova ioctl, o68 l'estensione di una esistente, sia supportata dal kernel in esecuzione. Se non69 potete fidarvi del fatto che un vecchio kernel possa rifiutare correttamente70 un nuovo *flag*, modalità, o ioctl, (probabilmente perché avevate raffazzonato71 qualcosa nel passato) allora dovrete implementare nel driver un meccanismo per72 notificare quali funzionalità sono supportate, o in alternativa un numero di73 versione.74 75* Abbiate un piano per estendere le ioctl con nuovi *flag* o campi alla fine di76 una struttura dati. Il sistema drm verifica la dimensione di ogni ioctl in77 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 nuovo79 su un kernel vecchio non noterebbe che i campi nuovi alla fine della struttura80 vengono ignorati. Dunque, anche questo avrà bisogno di essere notificato dal81 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 per85 estendere le ioctl andrà a rotoli dato che qualcuno userà delle ioctl con86 strutture dati con valori casuali dallo stack nei campi inutilizzati. Il che87 si traduce nell'avere questi campi nell'ABI, e la cui unica utilità sarà88 quella di contenere spazzatura. Per questo dovrete esplicitamente riempire i89 vuoti di tutte le vostre strutture dati, anche se non le userete in un90 vettore. Il riempimento fatto dal compilatore potrebbe contenere valori91 casuali.92 93* Abbiate un semplice codice di test per ognuno dei casi sopracitati.94 95 96Divertirsi coi percorsi d'errore97--------------------------------98 99Oggigiorno non ci sono più scuse rimaste per permettere ai driver drm di essere100sfruttati per diventare root. Questo significa che dobbiamo avere una completa101validazione degli input e gestire in modo robusto i percorsi - tanto le GPU102moriranno comunque nel più strano dei casi particolari:103 104 * Le ioctl devono verificare l'overflow dei vettori. Inoltre, per i valori105 interi si devono verificare *overflow*, *underflow*, e *clamping*. Il106 classico esempio è l'inserimento direttamente nell'hardware di valori di107 posizionamento di un'immagine *sprite* quando l'hardware supporta giusto 12108 bit, o qualcosa del genere. Tutto funzionerà finché qualche strano *display109 server* non decide di preoccuparsi lui stesso del *clamping* e il cursore110 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-test115 inviando comunque dati corretti. Senza questo, verifiche precedenti116 potrebbero rigettare la ioctl troppo presto, impedendo l'esecuzione del117 codice che si voleva effettivamente verificare, rischiando quindi di118 mascherare bachi e regressioni.119 120 * Fate si che tutte le vostre ioctl siano rieseguibili. Prima di tutto X adora121 i segnali; secondo questo vi permetterà di verificare il 90% dei percorsi122 d'errore interrompendo i vostri test con dei segnali. Grazie all'amore di X123 per i segnali, otterrete gratuitamente un eccellente copertura di base per124 tutti i vostri percorsi d'errore. Inoltre, siate consistenti sul modo di125 gestire la riesecuzione delle ioctl - per esempio, drm ha una piccola126 funzione di supporto `drmIoctl` nella sua librerie in spazio utente. Il127 driver i915 l'abbozza con l'ioctl `set_tiling`, ed ora siamo inchiodati per128 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 rendete132 possibile la sua interruzione. Le GPU moriranno e i vostri utenti non vi133 apprezzeranno affatto se tenete in ostaggio il loro scatolotto (mediante un134 processo X insopprimibile). Se anche recuperare lo stato è troppo complicato,135 allora implementate una scadenza oppure come ultima spiaggia una rete di136 sicurezza per rilevare situazioni di stallo quando l'hardware da di matto.137 138 * Preparate dei test riguardo ai casi particolarmente estremi nel codice di139 recupero del sistema - è troppo facile create uno stallo fra il vostro codice140 anti-stallo e un processo scrittore.141 142 143Tempi, attese e mancate scadenze144--------------------------------145 146Le GPU fanno quasi tutto in modo asincrono, dunque dobbiamo regolare le147operazioni ed attendere quelle in sospeso. Questo è davvero difficile; al148momento nessuna delle ioctl supportante dal driver drm/i915 riesce a farlo149perfettamente, il che significa che qui ci sono ancora una valanga di lezioni da150apprendere.151 152 * Per fare riferimento al tempo usate sempre ``CLOCK_MONOTONIC``. Oggigiorno153 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 sistema156 (fornito dal kernel) oppure un contatore hardware indipendente da qualche157 parte. Gli orologi divergeranno, ma con questa informazione gli strumenti di158 analisi delle prestazioni possono compensare il problema. Se il vostro spazio159 utente può ottenere i valori grezzi degli orologi, allora considerate di160 esporre anch'essi.161 162 * Per descrivere il tempo, usate ``__s64`` per i secondi e ``__u64`` per i163 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 lo167 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 dei171 bravi ragazzi e avete reso la vostra ioctl rieseguibile, allora i tempi172 relativi tendono ad essere troppo grossolani e a causa degli arrotondamenti173 potrebbero estendere in modo indefinito i tempi di attesa ad ogni174 riesecuzione. Particolarmente vero se il vostro orologio di riferimento è175 qualcosa di molto lento come il contatore di *frame*. Con la giacca da176 avvocato delle specifiche diremmo che questo non è un baco perché tutte le177 scadenze potrebbero essere estese - ma sicuramente gli utenti vi odieranno178 quando le animazioni singhiozzano.179 180 * Considerate l'idea di eliminare tutte le ioctl sincrone con scadenze, e di181 sostituirle con una versione asincrona il cui stato può essere consultato182 attraverso il descrittore di file mediante ``poll``. Questo approccio si183 sposa meglio in un applicazione guidata dagli eventi.184 185 * Sviluppate dei test per i casi estremi, specialmente verificate che i valori186 di ritorno per gli eventi già completati, le attese terminate con successo, e187 le attese scadute abbiano senso e servano ai vostri scopi.188 189 190Non perdere risorse191-------------------192Nel suo piccolo il driver drm implementa un sistema operativo specializzato per193certe GPU. Questo significa che il driver deve esporre verso lo spazio194utente tonnellate di agganci per accedere ad oggetti e altre risorse. Farlo195correttamente porterà con se alcune insidie:196 197 * Collegate sempre la vita di una risorsa creata dinamicamente, a quella del198 descrittore di file. Considerate una mappatura 1:1 se la vostra risorsa199 dev'essere condivisa fra processi - passarsi descrittori di file sul socket200 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 base205 impostate uno spazio dei nomi riservato per ogni descrittore di file, il che206 forzerà ogni condivisione ad essere esplicita. Usate uno spazio più globale207 per dispositivo solo se gli oggetti sono effettivamente unici per quel208 dispositivo. Un controesempio viene dall'interfaccia drm modeset, dove209 oggetti specifici di dispositivo, come i connettori, condividono uno spazio210 dei nomi con oggetti per il *framebuffer*, ma questi non sono per niente211 condivisi. Uno spazio separato, privato di base, per i *framebuffer* sarebbe212 stato meglio.213 214 * Pensate all'identificazione univoca degli agganci verso lo spazio utente. Per215 esempio, per la maggior parte dei driver drm, si considera fallace la doppia216 sottomissione di un oggetto allo stesso comando ioctl. Ma per evitarlo, se217 gli oggetti sono condivisibili, lo spazio utente ha bisogno di sapere se il218 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 i220 descrittori di file condivisi - che poi è come si distinguono i veri file.221 Sfortunatamente, questo richiederebbe lo sviluppo di un vero e proprio222 filesystem virtuale nel kernel.223 224 225Ultimo, ma non meno importante226------------------------------227 228Non tutti i problemi si risolvono con una nuova ioctl:229 230* Pensateci su due o tre volte prima di implementare un'interfaccia privata per231 un driver. Ovviamente è molto più veloce seguire questa via piuttosto che232 buttarsi in lunghe discussioni alla ricerca di una soluzione più generica. Ed233 a volte un'interfaccia privata è quello che serve per sviluppare un nuovo234 concetto. Ma alla fine, una volta che c'è un'interfaccia generica a235 disposizione finirete per mantenere due interfacce. Per sempre.236 237* Considerate interfacce alternative alle ioctl. Gli attributi sysfs sono molto238 meglio per impostazioni che sono specifiche di un dispositivo, o per239 sotto-oggetti con una vita piuttosto statica (come le uscite dei connettori in240 drm con tutti gli attributi per la sovrascrittura delle rilevazioni). O magari241 solo il vostro sistema di test ha bisogno di una certa interfaccia, e allora242 debugfs (che non ha un'interfaccia stabile) sarà la soluzione migliore.243 244Per concludere. Questo gioco consiste nel fare le cose giuste fin da subito,245dato che se il vostro driver diventa popolare e la piattaforma hardware longeva246finirete per mantenere le vostre ioctl per sempre. Potrete tentare di deprecare247alcune orribili ioctl, ma ci vorranno anni per riuscirci effettivamente. E248ancora, altri anni prima che sparisca l'ultimo utente capace di lamentarsi per249una regressione.250