Condividi tramite


Procedure consigliate per lo sviluppo di driver team di Surface

Introduzione

Queste linee guida per lo sviluppo di driver sono state sviluppate in molti anni dagli sviluppatori di driver in Microsoft. Nel corso del tempo, quando i conducenti si comportavano male e le lezioni sono state apprese, queste lezioni sono state acquisite e sviluppate in modo da essere questo insieme di indicazioni. Queste procedure consigliate vengono usate dal team hardware di Microsoft Surface per sviluppare e gestire il codice del driver di dispositivo che supporta le esperienze hardware di Surface univoche.

Come qualsiasi set di linee guida, ci saranno eccezioni legittime e approcci alternativi che saranno ugualmente validi. È consigliabile incorporare queste linee guida nei propri standard di sviluppo o usarle per avviare linee guida specifiche del dominio per l'ambiente di sviluppo e i requisiti specifici.

Errori comuni eseguiti dagli sviluppatori di driver

Gestione delle operazioni di I/O

  1. Accesso ai buffer recuperati da IOCTLs senza convalidare la lunghezza. Vedere Errore durante la verifica delle dimensioni dei buffer.
  2. Esecuzione del blocco di I/O nel contesto di un thread utente o di un contesto di thread casuale. Vedere Introduzione agli oggetti Dispatcher kernel.
  3. Invio di operazioni di I/O sincrone a un altro driver senza timeout. Vedere Invio di richieste di I/O in modo sincrono.
  4. Uso di IOCTLs io senza comprendere le implicazioni per la sicurezza. Vedere Uso né di I/O diretto né memorizzato nel buffer.
  5. Non controllando lo stato restituito di WdfRequestForwardToIoQueue o non gestendo correttamente l'errore e causando l'abbandono di WDFREQUESTs.
  6. Mantenere WDFREQUEST all'esterno della coda in uno stato non annullabile. Vedere Gestione delle code di I/O, completamento delle richieste di I/O e annullamento delle richieste di I/O.
  7. Tentativo di gestire l'annullamento usando la funzione Mark/UnmarkCancelable invece di usare IoQueues. Vedere Oggetti coda framework.
  8. Non conoscendo la differenza tra le operazioni di pulizia e chiusura dell'handle di file. Vedere Errori nella gestione delle operazioni di pulizia e chiusura.
  9. Ignorare le potenziali ricorsioni con il completamento di I/O e la reinviazione dalla routine di completamento.
  10. Non essendo espliciti sugli attributi di risparmio energia di WDFQUEUEs. Non documentando chiaramente la scelta del risparmio energia. Questa è la causa principale del controllo dei bug 0x9F: DRIVER_POWER_STATE_FAILURE nei driver WDF. Quando il dispositivo viene rimosso, il framework elimina I/O dalla coda gestita dal risparmio energia e dalla coda gestita senza alimentazione in diverse fasi del processo di rimozione. Le code non gestite dall'alimentazione vengono eliminate quando viene ricevuto il IRP_MN_REMOVE_DEVICE finale. Pertanto, se si mantiene l'I/O in una coda non gestita dall'alimentazione, è consigliabile eliminare esplicitamente l'I/O nel contesto di EvtDeviceSelfManagedIoFlush per evitare deadlock.
  11. Non seguendo le regole di gestione dei runtime di integrazione. Vedere Errori nella gestione delle operazioni di pulizia e chiusura.

Sincronizzazione

  1. Blocco per il codice che non richiede protezione. Non tenere premuto un blocco per un'intera funzione quando è necessario proteggere solo un numero ridotto di operazioni.
  2. Chiamate fuori dai conducenti con blocchi mantenuti. Si tratta delle cause principali dei deadlock.
  3. Uso di primitive interlocked per creare uno schema di blocco invece di usare primitive di blocco appropriate fornite dal sistema, ad esempio mutex, semaforo e spinlock. Vedere Introduzione agli oggetti Mutex, Oggetti semafori e Introduzione ai blocchi spin.
  4. Uso di uno spinlock in cui un tipo di blocco passivo sarebbe più appropriato. Vedere Mutex veloci e mutex sorvegliati e oggetti evento. Per altre prospettive sui blocchi, vedere l'articolo OSR - Stato della sincronizzazione.
  5. Acconsentire esplicitamente al modello a livello di sincronizzazione e esecuzione di WDF senza comprendere appieno le implicazioni. Vedere Uso dei blocchi del framework. A meno che il driver non sia monolitico driver di primo livello che interagisce direttamente con l'hardware, evitare di acconsentire esplicitamente alla sincronizzazione di WDF perché può causare deadlock a causa della ricorsione.
  6. Acquisizione di KEVENT, Semaforo, ERESOURCE, UnsafeFastMutex nel contesto di più thread senza immettere un'area critica. Questa operazione può causare un attacco DOS perché un thread che contiene uno di questi blocchi può essere sospeso. Vedere Introduzione agli oggetti Dispatcher kernel.
  7. Allocazione di KEVENT nello stack di thread e restituzione al chiamante mentre l'EVENTO è ancora in uso. In genere eseguita quando viene usata con IoBuildSyncronousFsdRequest o IoBuildDeviceIoControlRequest. Il chiamante di queste chiamate deve assicurarsi di non rimuovere dallo stack fino a quando il gestore di I/O non ha segnalato l'evento al termine dell'IRP.
  8. Indefinitamente in attesa nelle routine dispatch. In generale, qualsiasi tipo di attesa nella routine dispatch è una cattiva pratica.
  9. Controllo inappropriato della validità di un oggetto (se blah == NULL) prima di eliminarlo. Ciò significa in genere che l'autore non ha una conoscenza completa del codice che controlla la durata dell'oggetto.

Gestione oggetti

  1. Non padre in modo esplicito di oggetti WDF. Vedere Introduzione agli oggetti framework.
  2. Parenting WDF object to WDFDRIVER instead of parenting to an object that provides better lifetime management and optimizes memory usage.Ing WDF object to WDFDRIVER instead of parenting to an object that provides better lifetime management and optimizes memory usage. Ad esempio, l'elemento WDFREQUEST viene padre di un WDFDEVICE invece di IOTARGET. Vedere Uso di oggetti framework generali, ciclo di vita degli oggetti framework e riepilogo degli oggetti framework.
  3. Non eseguendo la protezione di rundown delle risorse di memoria condivisa a cui si accede tra i driver. Vedere Funzione ExInitializeRundownProtection.
  4. Accodamento errato dello stesso elemento di lavoro mentre quello precedente si trova già nella coda o già in esecuzione. Questo può essere un problema se il client presuppone che ogni elemento di lavoro in coda venga eseguito. Vedere Uso di Framework WorkItems. Per altre informazioni sull'accodamento di WorkItems, vedere il modulo DMF_QueuedWorkitem nel progetto DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
  5. Timer di accodamento prima di pubblicare il messaggio che il timer deve elaborare. Vedere Uso dei timer.
  6. Esecuzione di un'operazione in un elemento di lavoro che può bloccare o richiedere tempo illimitato per il completamento.
  7. Progettazione di una soluzione che comporta la coda di un'inondazione di elementi di lavoro. Può causare un attacco DOS o di sistema non risponde se il cattivo può controllare l'azione (ad esempio pompando I/O in un driver che accoda un nuovo elemento di lavoro per ogni I/O). Vedere Uso degli elementi di lavoro del framework.
  8. Non dopo che i callback DPC dell'elemento di lavoro sono stati eseguiti fino al completamento prima di eliminare l'oggetto. Vedere Linee guida per la scrittura di routine DPC e la funzione WdfDpcCancel.
  9. Creazione di thread invece di usare elementi di lavoro per attività di breve durata/non polling. Vedere Thread di lavoro di sistema.
  10. Non assicurarsi che i thread siano stati eseguiti fino al completamento prima di eliminare o scaricare il driver. Per altre informazioni sulla sincronizzazione del rundown dei thread, vedere il codice associato all'analisi del codice associato al modulo DMF_Thread nel progetto DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
  11. Uso di un singolo driver per gestire i dispositivi diversi ma interdipendenti e l'uso di variabili globali per condividere le informazioni.

Memory

  1. Non contrassegnare il codice di esecuzione passiva come PAGEABLE, quando possibile. Il codice del driver di paging può ridurre le dimensioni del footprint del codice del driver, liberando così lo spazio di sistema per altri usi. È consigliabile contrassegnare con cautela la tabella codici che genera IRQL >= DISPATCH_LEVEL o può essere chiamato in IRQL generato. Vedere Quando il codice e i dati devono essere pageable e rendere pageable i driver e rilevare il codice che può essere paging.
  2. Dichiarazione di strutture di grandi dimensioni nello stack, usare invece l'heap/pool. Vedere Uso di KernelStack e allocazione della memoria dello spazio di sistema.
  3. Inutilmente zero il contesto dell'oggetto WDF. Ciò può indicare una mancanza di chiarezza su quando la memoria verrà azzerato automaticamente.

Linee guida generali per i driver

  1. Combinazione di primitive WDM e WDF. Uso di primitive WDM in cui è possibile usare le primitive WDF. L'uso di primitive WDF consente di proteggere l'utente da gotchas, migliora il debug e rende più importante il driver portabile in modalità utente.
  2. Denominazione di fdO e creazione di collegamenti simbolici quando non necessario. Vedere Gestire il controllo di accesso dei driver.
  3. Copiare incollare e usare GUID e altri valori costanti dai driver di esempio.
  4. Prendere in considerazione l'uso del codice open source DMF (Driver Module Framework) nel progetto driver. DMF è un'estensione di WDF che consente funzionalità aggiuntive per uno sviluppatore di driver WDF. Vedere Introducing Driver Module Framework (Introduzione a Driver Module Framework).
  5. Uso del Registro di sistema come meccanismo di notifica tra processi o come cassetta postale. Per un'alternativa, vedere DMF_NotifyUserWithEvent e DMF_NotifyUserWithRequest moduli disponibili nel progetto DMF - https://github.com/Microsoft/DMF.
  6. Supponendo che tutte le parti del Registro di sistema siano disponibili per l'accesso durante la fase di avvio anticipato del sistema.
  7. Dipendenza dall'ordine di carico di un altro driver o servizio. Poiché l'ordine di carico può essere modificato al di fuori del controllo del driver, questo può comportare un driver che funziona inizialmente, ma successivamente non riesce in un modello imprevedibile.
  8. Ricreare le librerie di driver già disponibili, ad esempio WDF, per PnP descritto in Supporto di PnP e risparmio energia nel driver o in quelli forniti nell'interfaccia del bus, come descritto nell'articolo OSR Using Bus Interfaces for Driver to Driver Communication ( Uso delle interfacce del bus per la comunicazione da driver a driver).

PnP/Power

  1. Interfacciarsi con un altro driver in modo non compatibile con pnp- non eseguire la registrazione per le notifiche di modifica del dispositivo pnp. Vedere Registrazione per la notifica delle modifiche dell'interfaccia del dispositivo.
  2. Creazione di nodi ACPI per enumerare i dispositivi e creare dipendenze di alimentazione tra di esse anziché usare driver bus o interfacce di creazione di dispositivi software fornite dal sistema per PNP e dipendenze di alimentazione in modo elegante. Vedere Supporto di PnP e risparmio energia nei driver di funzione.
  3. Contrassegnare il dispositivo non disabilitabile: forzando un riavvio all'aggiornamento del driver.
  4. Nascondere il dispositivo in Gestione dispositivi. Vedere Nascondere i dispositivi da Gestione dispositivi.
  5. Presupponendo che il driver venga usato per una sola istanza del dispositivo.
  6. Facendo ipotesi che il conducente non venga mai scaricato. Vedere Routine di scaricamento del driver PnP.
  7. Non gestisce la notifica di arrivo dell'interfaccia spuriosa. Ciò può verificarsi e i driver devono gestire questa condizione in modo sicuro.
  8. Non implementando un criterio di alimentazione inattiva S0, importante per i dispositivi che sono vincoli DRIPS o figli. Vedere Supporto del risparmio di energia inattivo.
  9. Il mancato controllo dello stato restituito WdfDeviceStopIdle causa una perdita di riferimenti all'alimentazione a causa dello squilibrio WdfDeviceStopIdle/ResumeIdle e infine del controllo dei bug 9F.
  10. Non sapendo che PrepareHardware/ReleaseHardware può essere chiamato più volte a causa del ribilanciamento delle risorse. Questi callback devono essere limitati all'inizializzazione delle risorse hardware. Vedere EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Uso di PrepareHardware/ReleaseHardware per l'allocazione di risorse software. L'allocazione delle risorse software statica al dispositivo deve essere eseguita in AddDevice o in SelfManagedIoInit se l'allocazione delle risorse necessarie per interagire con l'hardware. Vedere EVT_WDF_DEVICE_edizione Standard LF_MANAGED_IO_INIT.

Linee guida per la codifica

  1. Non si usano funzioni stringa e integer sicure. Vedere Uso di funzioni stringa Cassaforte e uso di funzioni integer Cassaforte.
  2. Non si usano typedef per la definizione delle costanti.
  3. Uso di variabili globali e statiche. Evitare l'archiviazione per ogni contesto di dispositivo in globals. Le organizzazioni globali sono progettate per la condivisione di informazioni tra più istanze di dispositivi. In alternativa, è consigliabile usare il contesto dell'oggetto WDFDRIVER per condividere informazioni tra più istanze di dispositivi.
  4. Non si usano nomi descrittivi per le variabili.
  5. Non è coerente nella denominazione delle variabili: coerenza tra maiuscole e minuscole. Non seguendo lo stile esistente di codifica quando si apportano aggiornamenti al codice esistente. Ad esempio, usando nomi di variabili diversi per strutture comuni in funzioni diverse.
  6. Non commentando scelte di progettazione importanti: risparmio energia, blocchi, gestione dello stato, uso di elementi di lavoro, CONTROLLER di dominio, timer, utilizzo globale delle risorse, pre-allocazione delle risorse, espressioni complesse/istruzioni condizionali.
  7. Commenti sugli elementi ovvi dal nome dell'API chiamata. Rendere il commento equivalente alla lingua inglese del nome della funzione ,ad esempio scrivendo il commento "Create the Device Object" quando si chiama WdfDeviceCreate.
  8. Non creare macro con una chiamata restituita. Vedere Funzioni (C++).
  9. Annotazioni del codice sorgente (SAL) o incomplete. Vedere Annotazioni SAL 2.0 per i driver di Windows.
  10. Uso di macro invece di funzioni inline.
  11. Uso di macro per le costanti al posto di constexpr quando si usa C++
  12. Compilazione del driver con il compilatore C, invece del compilatore C++ per assicurarsi di ottenere un controllo dei tipi sicuro.

Gestione errori

  1. Non segnala errori critici del driver e contrassegna correttamente il dispositivo non funzionante.
  2. Non restituendo lo stato di errore NT appropriato che si traduce in uno stato di errore WIN32 significativo. Vedere Uso dei valori NTSTATUS.
  3. Non si usano macro NTSTATUS per controllare lo stato restituito delle funzioni di sistema.
  4. Non asserzione su variabili di stato o flag, se necessario.
  5. Verifica se il puntatore è valido prima di accedervi per aggirare le race condition.
  6. AS edizione Standard RTING sui puntatori NULL. Se si tenta di usare un puntatore NULL per accedere alla memoria, Windows verificherà bug. I parametri del controllo dei bug forniranno le informazioni necessarie per correggere il puntatore Null. Nel tempo, quando al codice vengono aggiunte molte istruzioni AS edizione Standard RT non necessario, consumano memoria e rallentano il sistema.
  7. AS edizione Standard RTING sul puntatore al contesto dell'oggetto. Il framework del driver garantisce che l'oggetto venga sempre allocato con il contesto.

Traccia

  1. Non definire tipi personalizzati WPP e usarli nelle chiamate di traccia per ottenere messaggi di traccia leggibili. Vedere Aggiunta di traccia software WPP a un driver Windows.
  2. Non si usa la traccia IFR. Vedere Uso di IfR (Inflight Trace Recorder) nei driver KMDF e UMDF 2.
  3. Chiamata di nomi di funzione nelle chiamate di traccia WPP. WPP tiene già traccia dei nomi delle funzioni e dei numeri di riga.
  4. Non usare eventi ETW per misurare le prestazioni e altre esperienze utente critiche che influisce sugli eventi. Vedere Aggiunta di traccia eventi ai driver in modalità kernel.
  5. Non segnala errori critici nel log eventi e contrassegna normalmente il dispositivo non funzionante.

Verifica

  1. Verifica driver non in esecuzione con impostazioni standard e avanzate durante lo sviluppo e il test. Vedere Driver Verifier(Verifica driver). Nelle impostazioni avanzate è consigliabile abilitare tutte le regole, ad eccezione di quelle correlate alla simulazione di risorse basse. È preferibile eseguire i test di simulazione delle risorse scarsi in isolamento per semplificare il debug dei problemi.
  2. Non è in esecuzione il test DevFund sul driver o sulla classe di dispositivo di cui fa parte il driver con le impostazioni avanzate del verificatore abilitate. Vedere Come eseguire i test DevFund tramite la riga di comando.
  3. Non verificando che il driver sia conforme a HVCI. Vedere Implementare il codice compatibile HVCI.
  4. Non è in esecuzione AppVerifier in WUDFhost.exe durante lo sviluppo e il test dei driver in modalità utente. Vedere Application Verifier(Verifica applicazione).
  5. Non controllare l'utilizzo della memoria usando l'estensione del debugger !wdfpoolusage in fase di esecuzione per assicurarsi che gli oggetti WDF non vengano abbandonati. La memoria, le richieste e gli elementi di lavoro sono vittime comuni di questi problemi.
  6. Non usando l'estensione del debugger !wdfkd per controllare l'albero degli oggetti per assicurarsi che gli oggetti siano padre correttamente e controllare gli attributi degli oggetti principali, ad esempio WDFDRIVER, WDFDEVICE, I/O.