| .. include:: ../disclaimer-ita.rst |
| |
| :Original: Documentation/process/botching-up-ioctls.rst |
| |
| ========================================== |
| (Come evitare di) Raffazzonare delle ioctl |
| ========================================== |
| |
| Preso da: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html |
| |
| Scritto da : Daniel Vetter, Copyright © 2013 Intel Corporation |
| |
| Una cosa che gli sviluppatori del sottosistema grafico del kernel Linux hanno |
| imparato negli ultimi anni è l'inutilità di cercare di creare un'interfaccia |
| unificata per gestire la memoria e le unità esecutive di diverse GPU. Dunque, |
| oggigiorno ogni driver ha il suo insieme di ioctl per allocare memoria ed |
| inviare dei programmi alla GPU. Il che è va bene dato che non c'è più un insano |
| sistema che finge di essere generico, ma al suo posto ci sono interfacce |
| dedicate. Ma al tempo stesso è più facile incasinare le cose. |
| |
| Per evitare di ripetere gli stessi errori ho preso nota delle lezioni imparate |
| mentre raffazzonavo il driver drm/i915. La maggior parte di queste lezioni si |
| focalizzano sui tecnicismi e non sulla visione d'insieme, come le discussioni |
| riguardo al modo migliore per implementare una ioctl per inviare compiti alla |
| GPU. Probabilmente, ogni sviluppatore di driver per GPU dovrebbe imparare queste |
| lezioni in autonomia. |
| |
| |
| Prerequisiti |
| ------------ |
| |
| Prima i prerequisiti. Seguite i seguenti suggerimenti se non volete fallire in |
| partenza e ritrovarvi ad aggiungere un livello di compatibilità a 32-bit. |
| |
| * Usate solamente interi a lunghezza fissa. Per evitare i conflitti coi tipi |
| definiti nello spazio utente, il kernel definisce alcuni tipi speciali, come: |
| ``__u32``, ``__s64``. Usateli. |
| |
| * Allineate tutto alla lunghezza naturale delle piattaforma in uso e riempite |
| esplicitamente i vuoti. Non necessariamente le piattaforme a 32-bit allineano |
| i valori a 64-bit rispettandone l'allineamento, ma le piattaforme a 64-bit lo |
| fanno. Dunque, per farlo correttamente in entrambe i casi dobbiamo sempre |
| riempire i vuoti. |
| |
| * Se una struttura dati contiene valori a 64-bit, allora fate si che la sua |
| dimensione sia allineata a 64-bit, altrimenti la sua dimensione varierà su |
| sistemi a 32-bit e 64-bit. Avere una dimensione differente causa problemi |
| quando si passano vettori di strutture dati al kernel, o quando il kernel |
| effettua verifiche sulla dimensione (per esempio il sistema drm lo fa). |
| |
| * I puntatori sono di tipo ``__u64``, con un *cast* da/a ``uintptr_t`` da lato |
| spazio utente e da/a ``void __user *`` nello spazio kernel. Sforzatevi il più |
| possibile per non ritardare la conversione, o peggio maneggiare ``__u64`` nel |
| vostro codice perché questo riduce le verifiche che strumenti come sparse |
| possono effettuare. La macro u64_to_user_ptr() può essere usata nel kernel |
| per evitare avvisi riguardo interi e puntatori di dimensioni differenti. |
| |
| |
| Le Basi |
| ------- |
| |
| Con la gioia d'aver evitato un livello di compatibilità, possiamo ora dare uno |
| sguardo alle basi. Trascurare questi punti renderà difficile la gestione della |
| compatibilità all'indietro ed in avanti. E dato che sbagliare al primo colpo è |
| garantito, dovrete rivisitare il codice o estenderlo per ogni interfaccia. |
| |
| * Abbiate un modo chiaro per capire dallo spazio utente se una nuova ioctl, o |
| l'estensione di una esistente, sia supportata dal kernel in esecuzione. Se non |
| potete fidarvi del fatto che un vecchio kernel possa rifiutare correttamente |
| un nuovo *flag*, modalità, o ioctl, (probabilmente perché avevate raffazzonato |
| qualcosa nel passato) allora dovrete implementare nel driver un meccanismo per |
| notificare quali funzionalità sono supportate, o in alternativa un numero di |
| versione. |
| |
| * Abbiate un piano per estendere le ioctl con nuovi *flag* o campi alla fine di |
| una struttura dati. Il sistema drm verifica la dimensione di ogni ioctl in |
| arrivo, ed estende con zeri ogni incongruenza fra kernel e spazio utente. |
| Questo aiuta, ma non è una soluzione completa dato che uno spazio utente nuovo |
| su un kernel vecchio non noterebbe che i campi nuovi alla fine della struttura |
| vengono ignorati. Dunque, anche questo avrà bisogno di essere notificato dal |
| driver allo spazio utente. |
| |
| * Verificate tutti i campi e *flag* inutilizzati ed i riempimenti siano a 0, |
| altrimenti rifiutare la ioctl. Se non lo fate il vostro bel piano per |
| estendere le ioctl andrà a rotoli dato che qualcuno userà delle ioctl con |
| strutture dati con valori casuali dallo stack nei campi inutilizzati. Il che |
| si traduce nell'avere questi campi nell'ABI, e la cui unica utilità sarà |
| quella di contenere spazzatura. Per questo dovrete esplicitamente riempire i |
| vuoti di tutte le vostre strutture dati, anche se non le userete in un |
| vettore. Il riempimento fatto dal compilatore potrebbe contenere valori |
| casuali. |
| |
| * Abbiate un semplice codice di test per ognuno dei casi sopracitati. |
| |
| |
| Divertirsi coi percorsi d'errore |
| -------------------------------- |
| |
| Oggigiorno non ci sono più scuse rimaste per permettere ai driver drm di essere |
| sfruttati per diventare root. Questo significa che dobbiamo avere una completa |
| validazione degli input e gestire in modo robusto i percorsi - tanto le GPU |
| moriranno comunque nel più strano dei casi particolari: |
| |
| * Le ioctl devono verificare l'overflow dei vettori. Inoltre, per i valori |
| interi si devono verificare *overflow*, *underflow*, e *clamping*. Il |
| classico esempio è l'inserimento direttamente nell'hardware di valori di |
| posizionamento di un'immagine *sprite* quando l'hardware supporta giusto 12 |
| bit, o qualcosa del genere. Tutto funzionerà finché qualche strano *display |
| server* non decide di preoccuparsi lui stesso del *clamping* e il cursore |
| farà il giro dello schermo. |
| |
| * Avere un test semplice per ogni possibile fallimento della vostra ioctl. |
| Verificate che il codice di errore rispetti le aspettative. Ed infine, |
| assicuratevi che verifichiate un solo percorso sbagliato per ogni sotto-test |
| inviando comunque dati corretti. Senza questo, verifiche precedenti |
| potrebbero rigettare la ioctl troppo presto, impedendo l'esecuzione del |
| codice che si voleva effettivamente verificare, rischiando quindi di |
| mascherare bachi e regressioni. |
| |
| * Fate si che tutte le vostre ioctl siano rieseguibili. Prima di tutto X adora |
| i segnali; secondo questo vi permetterà di verificare il 90% dei percorsi |
| d'errore interrompendo i vostri test con dei segnali. Grazie all'amore di X |
| per i segnali, otterrete gratuitamente un eccellente copertura di base per |
| tutti i vostri percorsi d'errore. Inoltre, siate consistenti sul modo di |
| gestire la riesecuzione delle ioctl - per esempio, drm ha una piccola |
| funzione di supporto `drmIoctl` nella sua librerie in spazio utente. Il |
| driver i915 l'abbozza con l'ioctl `set_tiling`, ed ora siamo inchiodati per |
| sempre con una semantica arcana sia nel kernel che nello spazio utente. |
| |
| |
| * Se non potete rendere un pezzo di codice rieseguibile, almeno rendete |
| possibile la sua interruzione. Le GPU moriranno e i vostri utenti non vi |
| apprezzeranno affatto se tenete in ostaggio il loro scatolotto (mediante un |
| processo X insopprimibile). Se anche recuperare lo stato è troppo complicato, |
| allora implementate una scadenza oppure come ultima spiaggia una rete di |
| sicurezza per rilevare situazioni di stallo quando l'hardware da di matto. |
| |
| * Preparate dei test riguardo ai casi particolarmente estremi nel codice di |
| recupero del sistema - è troppo facile create uno stallo fra il vostro codice |
| anti-stallo e un processo scrittore. |
| |
| |
| Tempi, attese e mancate scadenze |
| -------------------------------- |
| |
| Le GPU fanno quasi tutto in modo asincrono, dunque dobbiamo regolare le |
| operazioni ed attendere quelle in sospeso. Questo è davvero difficile; al |
| momento nessuna delle ioctl supportante dal driver drm/i915 riesce a farlo |
| perfettamente, il che significa che qui ci sono ancora una valanga di lezioni da |
| apprendere. |
| |
| * Per fare riferimento al tempo usate sempre ``CLOCK_MONOTONIC``. Oggigiorno |
| questo è quello che viene usato di base da alsa, drm, e v4l. Tuttavia, |
| lasciate allo spazio utente la possibilità di capire quali *timestamp* |
| derivano da domini temporali diversi come il vostro orologio di sistema |
| (fornito dal kernel) oppure un contatore hardware indipendente da qualche |
| parte. Gli orologi divergeranno, ma con questa informazione gli strumenti di |
| analisi delle prestazioni possono compensare il problema. Se il vostro spazio |
| utente può ottenere i valori grezzi degli orologi, allora considerate di |
| esporre anch'essi. |
| |
| * Per descrivere il tempo, usate ``__s64`` per i secondi e ``__u64`` per i |
| nanosecondi. Non è il modo migliore per specificare il tempo, ma è |
| praticamente uno standard. |
| |
| * Verificate che gli input di valori temporali siano normalizzati, e se non lo |
| sono scartateli. Fate attenzione perché la struttura dati ``struct ktime`` |
| del kernel usa interi con segni sia per i secondi che per i nanosecondi. |
| |
| * Per le scadenze (*timeout*) usate valori temporali assoluti. Se siete dei |
| bravi ragazzi e avete reso la vostra ioctl rieseguibile, allora i tempi |
| relativi tendono ad essere troppo grossolani e a causa degli arrotondamenti |
| potrebbero estendere in modo indefinito i tempi di attesa ad ogni |
| riesecuzione. Particolarmente vero se il vostro orologio di riferimento è |
| qualcosa di molto lento come il contatore di *frame*. Con la giacca da |
| avvocato delle specifiche diremmo che questo non è un baco perché tutte le |
| scadenze potrebbero essere estese - ma sicuramente gli utenti vi odieranno |
| quando le animazioni singhiozzano. |
| |
| * Considerate l'idea di eliminare tutte le ioctl sincrone con scadenze, e di |
| sostituirle con una versione asincrona il cui stato può essere consultato |
| attraverso il descrittore di file mediante ``poll``. Questo approccio si |
| sposa meglio in un applicazione guidata dagli eventi. |
| |
| * Sviluppate dei test per i casi estremi, specialmente verificate che i valori |
| di ritorno per gli eventi già completati, le attese terminate con successo, e |
| le attese scadute abbiano senso e servano ai vostri scopi. |
| |
| |
| Non perdere risorse |
| ------------------- |
| Nel suo piccolo il driver drm implementa un sistema operativo specializzato per |
| certe GPU. Questo significa che il driver deve esporre verso lo spazio |
| utente tonnellate di agganci per accedere ad oggetti e altre risorse. Farlo |
| correttamente porterà con se alcune insidie: |
| |
| * Collegate sempre la vita di una risorsa creata dinamicamente, a quella del |
| descrittore di file. Considerate una mappatura 1:1 se la vostra risorsa |
| dev'essere condivisa fra processi - passarsi descrittori di file sul socket |
| unix semplifica la gestione anche per lo spazio utente. |
| |
| * Dev'esserci sempre Il supporto ``O_CLOEXEC``. |
| |
| * Assicuratevi di avere abbastanza isolamento fra utenti diversi. Di base |
| impostate uno spazio dei nomi riservato per ogni descrittore di file, il che |
| forzerà ogni condivisione ad essere esplicita. Usate uno spazio più globale |
| per dispositivo solo se gli oggetti sono effettivamente unici per quel |
| dispositivo. Un controesempio viene dall'interfaccia drm modeset, dove |
| oggetti specifici di dispositivo, come i connettori, condividono uno spazio |
| dei nomi con oggetti per il *framebuffer*, ma questi non sono per niente |
| condivisi. Uno spazio separato, privato di base, per i *framebuffer* sarebbe |
| stato meglio. |
| |
| * Pensate all'identificazione univoca degli agganci verso lo spazio utente. Per |
| esempio, per la maggior parte dei driver drm, si considera fallace la doppia |
| sottomissione di un oggetto allo stesso comando ioctl. Ma per evitarlo, se |
| gli oggetti sono condivisibili, lo spazio utente ha bisogno di sapere se il |
| driver ha importato un oggetto da un altro processo. Non l'ho ancora provato, |
| ma considerate l'idea di usare il numero di inode come identificatore per i |
| descrittori di file condivisi - che poi è come si distinguono i veri file. |
| Sfortunatamente, questo richiederebbe lo sviluppo di un vero e proprio |
| filesystem virtuale nel kernel. |
| |
| |
| Ultimo, ma non meno importante |
| ------------------------------ |
| |
| Non tutti i problemi si risolvono con una nuova ioctl: |
| |
| * Pensateci su due o tre volte prima di implementare un'interfaccia privata per |
| un driver. Ovviamente è molto più veloce seguire questa via piuttosto che |
| buttarsi in lunghe discussioni alla ricerca di una soluzione più generica. Ed |
| a volte un'interfaccia privata è quello che serve per sviluppare un nuovo |
| concetto. Ma alla fine, una volta che c'è un'interfaccia generica a |
| disposizione finirete per mantenere due interfacce. Per sempre. |
| |
| * Considerate interfacce alternative alle ioctl. Gli attributi sysfs sono molto |
| meglio per impostazioni che sono specifiche di un dispositivo, o per |
| sotto-oggetti con una vita piuttosto statica (come le uscite dei connettori in |
| drm con tutti gli attributi per la sovrascrittura delle rilevazioni). O magari |
| solo il vostro sistema di test ha bisogno di una certa interfaccia, e allora |
| debugfs (che non ha un'interfaccia stabile) sarà la soluzione migliore. |
| |
| Per concludere. Questo gioco consiste nel fare le cose giuste fin da subito, |
| dato che se il vostro driver diventa popolare e la piattaforma hardware longeva |
| finirete per mantenere le vostre ioctl per sempre. Potrete tentare di deprecare |
| alcune orribili ioctl, ma ci vorranno anni per riuscirci effettivamente. E |
| ancora, altri anni prima che sparisca l'ultimo utente capace di lamentarsi per |
| una regressione. |