Bagikan melalui


Praktik Terbaik Pengembangan Driver Tim Surface

Pengantar

Panduan pengembangan driver ini dikembangkan selama bertahun-tahun oleh pengembang driver di Microsoft. Seiring waktu ketika driver salah tingkah laku dan pelajaran dipelajari, pelajaran tersebut ditangkap dan berevolusi menjadi serangkaian panduan ini. Praktik terbaik ini digunakan oleh tim Microsoft Surface Hardware untuk mengembangkan dan memelihara kode driver perangkat yang mendukung pengalaman perangkat keras Surface yang unik.

Seperti seperangkat pedoman apa pun, akan ada pengecualian yang sah dan pendekatan alternatif yang akan sama-sama valid. Pertimbangkan untuk menggabungkan panduan ini ke dalam standar pengembangan Anda atau menggunakannya untuk memulai panduan khusus domain Anda untuk lingkungan pengembangan Dan persyaratan unik Anda.

Kesalahan umum yang dilakukan oleh pengembang driver

Menangani I/O

  1. Mengakses buffer yang diambil dari IOCTL tanpa memvalidasi panjangnya. Lihat Kegagalan untuk Memeriksa Ukuran Buffer.
  2. Melakukan pemblokiran I/O dalam konteks utas pengguna atau konteks utas acak. Lihat Pengantar Objek Dispatcher Kernel.
  3. Mengirim I/O sinkron ke driver lain tanpa batas waktu. Lihat Mengirim Permintaan I/O Secara Sinkron.
  4. Menggunakan IOCTL tidak ada io tanpa memahami implikasi keamanan. Lihat tidak menggunakan buffered atau I/O langsung.
  5. Tidak memeriksa status pengembalian WdfRequestForwardToIoQueue atau tidak menangani kegagalan dengan benar dan mengakibatkan WDFREQUESTs yang ditinggalkan.
  6. Menjaga WDFREQUEST di luar antrean dalam status tidak dapat dibatalkan. Lihat Mengelola Antrean I/O, Menyelesaikan Permintaan I/O, dan Membatalkan Permintaan I/O.
  7. Mencoba mengelola pembatalan menggunakan fungsi Mark/UnmarkCancelable alih-alih menggunakan IoQueues. Lihat Objek Antrean Kerangka Kerja.
  8. Tidak mengetahui perbedaan antara operasi Pembersihan handel file dan Tutup. Lihat Kesalahan dalam Menangani Operasi Pembersihan dan Penutupan.
  9. Mengabaikan potensi rekursi dengan penyelesaian I/O dan pengiriman ulang dari rutinitas penyelesaian.
  10. Tidak menjadi eksplisit tentang atribut manajemen daya WDFQUEUEs. Tidak mendokumenkan pilihan manajemen daya dengan jelas. Ini adalah penyebab utama Pemeriksaan Bug 0x9F: DRIVER_POWER_STATE_FAILURE di driver WDF. Ketika perangkat dihapus, kerangka kerja menghapus menyeluruh IO dari antrean yang dikelola daya dan antrean yang dikelola non-daya dalam berbagai tahap proses penghapusan. Antrean yang tidak dikelola daya dibersihkan saat IRP_MN_REMOVE_DEVICE akhir diterima. Jadi, jika Anda memegang I/O dalam antrean yang dikelola non-daya, adalah praktik yang baik untuk secara eksplisit membersihkan I/O dalam konteks EvtDeviceSelfManagedIoFlush untuk menghindari kebuntuan.
  11. Tidak mengikuti aturan penanganan RUN. Lihat Kesalahan dalam Menangani Operasi Pembersihan dan Penutupan.

Sinkronisasi

  1. Menahan kunci untuk kode yang tidak memerlukan perlindungan. Jangan menahan kunci untuk seluruh fungsi ketika hanya sejumlah kecil operasi yang perlu dilindungi.
  2. Memanggil pengemudi dengan kunci ditahan. Ini adalah penyebab utama kebuntuan.
  3. Menggunakan primitif yang saling mengunci untuk membuat skema penguncian alih-alih menggunakan sistem yang sesuai yang disediakan kunci primitif seperti mutex, semaphore dan spinlock. Lihat Pengantar Objek Mutex, Objek Semaphore, dan Pengenalan Kunci Putar.
  4. Menggunakan spinlock di mana beberapa jenis kunci pasif akan lebih tepat. Lihat Mutex Cepat dan Mutex Yang Dijaga dan Objek Peristiwa. Untuk perspektif tambahan tentang kunci, tinjau Artikel OSR - Status Sinkronisasi.
  5. Memilih ke dalam model tingkat sinkronisasi dan eksekusi WDF tanpa pemahaman penuh tentang implikasi. Lihat Menggunakan Kunci Kerangka Kerja. Kecuali driver Anda adalah driver tingkat atas monolitik yang berinteraksi langsung dengan perangkat keras, hindari memilih ke sinkronisasi WDF karena dapat menyebabkan kebuntuan karena rekursi.
  6. Memperoleh KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex dalam konteks beberapa utas tanpa memasuki wilayah penting. Melakukan ini dapat menyebabkan serangan DOS karena utas yang memegang salah satu kunci ini dapat ditangguhkan. Lihat Pengantar Objek Dispatcher Kernel.
  7. Mengalokasikan KEVENT pada tumpukan utas dan kembali ke pemanggil saat PERISTIWA masih digunakan. Biasanya dilakukan ketika digunakan dengan IoBuildSyncronousFsdRequest atau IoBuildDeviceIoControlRequest. Pemanggil panggilan ini harus memastikan bahwa mereka tidak melepas kembar dari tumpukan sampai manajer I/O telah memberi sinyal peristiwa ketika IRP selesai.
  8. Tanpa batas waktu menunggu dalam rutinitas pengiriman. Secara umum, segala jenis menunggu dalam rutinitas pengiriman adalah praktik yang buruk.
  9. Secara tidak tepat memeriksa validitas objek (jika blah == NULL) sebelum menghapusnya. Ini biasanya berarti penulis tidak memiliki pemahaman penuh tentang kode yang mengontrol masa pakai objek.

Manajemen Objek

  1. Tidak secara eksplisit mengasuh objek WDF. Lihat Pengantar Objek Kerangka Kerja.
  2. Mengasuh objek WDF ke WDFDRIVER alih-alih mengasuh objek yang memberikan manajemen masa pakai yang lebih baik dan mengoptimalkan penggunaan memori. Misalnya, mengasuh WDFREQUEST ke WDFDEVICE alih-alih IOTARGET. Lihat Menggunakan Objek Kerangka Kerja Umum, Siklus Hidup Objek Kerangka Kerja, dan Ringkasan Objek Kerangka Kerja.
  3. Tidak melakukan perlindungan rundown sumber daya memori bersama yang diakses di seluruh driver. Lihat fungsi ExInitializeRundownProtection.
  4. Keliru mengantrekan item kerja yang sama saat item sebelumnya sudah dalam antrean atau sudah berjalan. Ini bisa menjadi masalah jika klien membuat asumsi bahwa setiap item kerja yang diantrekan akan dieksekusi. Lihat Menggunakan Framework WorkItems. Untuk informasi selengkapnya tentang antrean WorkItems, lihat modul DMF_QueuedWorkitem di proyek Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
  5. Timer antrean sebelum memposting pesan yang diperkirakan akan diproses oleh timer. Lihat Menggunakan Timer.
  6. Melakukan operasi di workitem yang dapat memblokir atau membutuhkan waktu yang lama untuk diselesaikan.
  7. Merancang solusi yang mengakibatkan banjir item kerja untuk diantrekan. Ini dapat menyebabkan sistem yang tidak responsif atau serangan DOS jika orang jahat dapat mengontrol tindakan (misalnya memompa I/O ke driver yang mengantre item kerja baru untuk setiap I/O). Lihat Menggunakan Item Kerja Kerangka Kerja.
  8. Tidak memperkirakan bahwa panggilan balik DPC item kerja telah berjalan hingga selesai sebelum menghapus objek. Lihat Panduan untuk Menulis Rutinitas DPC dan fungsi WdfDpcCancel.
  9. Membuat utas alih-alih menggunakan item kerja untuk tugas durasi pendek/non-polling. Lihat Utas Pekerja Sistem.
  10. Tidak memastikan utas telah berjalan hingga selesai sebelum menghapus atau membongkar driver. Untuk informasi selengkapnya tentang sinkronisasi rundown utas, lihat kode yang terkait dengan melihat kode yang terkait dengan modul DMF_Thread dalam proyek Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
  11. Menggunakan satu driver untuk mengelola perangkat yang berbeda tetapi saling bergantung dan menggunakan variabel global untuk berbagi informasi.

Memori

  1. Tidak menandai kode eksekusi pasif sebagai PAGEABLE, jika memungkinkan. Kode driver halaman dapat mengurangi ukuran jejak kode driver, sehingga membebaskan ruang sistem untuk kegunaan lain. Berhati-hatilah menandai kode pageable yang meningkatkan IRQL >= DISPATCH_LEVEL atau dapat dipanggil di IRQL yang dinaikkan. Lihat Kapan Kode dan Data Harus Dapat Di-pageable dan Membuat Driver Dapat Di-pageable dan Mendeteksi Kode yang Dapat Di-pageable.
  2. Mendeklarasikan struktur besar pada tumpukan, Harus menggunakan heap/poolinstead. Lihat Menggunakan KernelStack dan Mengalokasikan Memori Ruang Sistem.
  3. Konteks Objek WDF nol yang tidak perlu. Ini dapat menunjukkan kurangnya kejelasan tentang kapan memori akan ditolak secara otomatis.

Pedoman Driver Umum

  1. Mencampur primitif WDM dan WDF. Menggunakan primitif WDM di mana primitif WDF dapat digunakan. Menggunakan primitif WDF melindungi Anda dari gotcha, meningkatkan penelusuran kesalahan, dan yang lebih penting membuat driver Anda portabel ke usermode.
  2. Penamaan FDO dan membuat tautan simbolis saat tidak diperlukan. Lihat Mengelola kontrol akses driver.
  3. Salin menempelkan dan menggunakan GUID dan nilai konstan lainnya dari driver sampel.
  4. Pertimbangkan penggunaan kode sumber terbuka Driver Module Framework (DMF) dalam proyek driver Anda. DMF adalah ekstensi untuk WDF yang memungkinkan fungsionalitas tambahan untuk pengembang driver WDF. Lihat Memperkenalkan Kerangka Kerja Modul Driver.
  5. Menggunakan registri sebagai mekanisme pemberitahuan antar-proses atau sebagai kotak surat. Untuk alternatif, lihat modul DMF_NotifyUserWithEvent dan DMF_NotifyUserWithRequest tersedia di proyek DMF - https://github.com/Microsoft/DMF.
  6. Dengan asumsi semua bagian registri akan tersedia untuk akses selama fase boot awal sistem.
  7. Mengambil dependensi pada urutan beban driver atau layanan lain. Karena urutan beban dapat diubah di luar kontrol driver Anda, ini dapat mengakibatkan driver yang bekerja pada awalnya, tetapi kemudian gagal dalam pola yang tidak dapat diprediksi.
  8. Membuat ulang pustaka driver yang sudah tersedia, seperti yang disediakan WDF untuk PnP yang dijelaskan dalam Mendukung PnP dan Manajemen Daya di Driver Anda atau yang disediakan di antarmuka bus seperti yang dijelaskan dalam artikel OSR Menggunakan Antarmuka Bus untuk Komunikasi Driver ke Driver.

PnP/Daya

  1. Berinteraksi dengan driver lain dengan cara yang tidak ramah pnp - tidak mendaftar untuk pemberitahuan perubahan perangkat pnp. Lihat Mendaftar untuk Pemberitahuan Perubahan Antarmuka Perangkat.
  2. Membuat simpul ACPI untuk menghitung perangkat dan membuat dependensi daya di antaranya alih-alih menggunakan driver bus atau sistem yang menyediakan antarmuka pembuatan perangkat lunak ke PNP dan dependensi daya dengan cara yang elegan. Lihat Mendukung PnP dan Manajemen Daya di Driver Fungsi.
  3. Menandai perangkat yang tidak dapat dinonaktifkan - memaksa boot ulang pada pembaruan driver.
  4. Menyembunyikan perangkat di manajer perangkat. Lihat Menyembunyikan Perangkat dari Manajer Perangkat.
  5. Membuat asumsi bahwa driver hanya akan digunakan untuk satu instans perangkat.
  6. Membuat asumsi bahwa driver tidak akan pernah dibongkar. Lihat Rutinitas Pembongkaran Driver PnP.
  7. Tidak menangani pemberitahuan kedatangan antarmuka yang memacu. Hal ini dapat terjadi dan driver diharapkan dapat menangani kondisi ini dengan aman.
  8. Tidak menerapkan kebijakan daya Diam S0, yang penting untuk perangkat yang merupakan batasan DRIPS atau anak-anak daripadanya. Lihat Mendukung Power-Down Menganggur.
  9. Tidak memeriksa status pengembalian WdfDeviceStopIdle menyebabkan kebocoran referensi daya karena ketidakseimbangan WdfDeviceStopIdle/ResumeIdle dan akhirnya pemeriksaan bug 9F.
  10. Tidak mengetahui bahwa PrepareHardware/ReleaseHardware dapat dipanggil lebih dari sekali karena penyeimbangan ulang sumber daya. Panggilan balik ini harus dibatasi untuk menginisialisasi sumber daya perangkat keras. Lihat EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Menggunakan PrepareHardware/ReleaseHardware untuk mengalokasikan sumber daya perangkat lunak. Alokasi sumber daya perangkat lunak statis ke perangkat harus dilakukan baik di AddDevice atau di SelfManagedIoInit jika alokasi sumber daya yang diperlukan berinteraksi dengan perangkat keras. Lihat EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.

Pedoman Pengkodian

  1. Tidak menggunakan fungsi string dan bilangan bulat yang aman. Lihat Menggunakan Fungsi String Brankas dan Menggunakan Fungsi Bilangan Bulat Brankas.
  2. Tidak menggunakan typedefs untuk menentukan konstanta.
  3. Menggunakan variabel global dan statis. Hindari menyimpan per konteks perangkat di global. Global dimaksudkan untuk berbagi informasi di beberapa instans perangkat. Sebagai alternatif, pertimbangkan untuk menggunakan konteks objek WDFDRIVER untuk berbagi informasi di beberapa instans perangkat.
  4. Tidak menggunakan nama deskriptif untuk variabel.
  5. Tidak konsisten dalam penamaan variabel - konsistensi kasus. Tidak mengikuti gaya pengkodean yang ada saat membuat pembaruan pada kode yang ada. Misalnya, menggunakan nama variabel yang berbeda untuk struktur umum dalam fungsi yang berbeda.
  6. Tidak mengomentari pilihan desain penting - manajemen daya, kunci, manajemen status, penggunaan workitem, DPC, timer, penggunaan sumber daya global, pra-alokasi sumber daya, ekspresi kompleks/pernyataan bersyariah.
  7. Mengomentari hal-hal yang jelas dari nama API yang dipanggil. Membuat komentar Anda bahasa Inggris setara dengan nama fungsi (seperti menulis komentar "Buat Objek Perangkat" saat memanggil WdfDeviceCreate).
  8. Jangan membuat makro yang memiliki panggilan kembali. Lihat Fungsi (C++).
  9. Anotasi Kode Sumber (SAL) tidak ada atau tidak lengkap. Lihat Anotasi SAL 2.0 untuk Driver Windows.
  10. Menggunakan makro alih-alih fungsi sebaris.
  11. Menggunakan makro untuk konstanta sebagai pengganti constexpr saat menggunakan C++
  12. Mengkompilasi driver Anda dengan pengkompilasi C, alih-alih pengkompilasi C++ untuk memastikan Anda mendapatkan pemeriksaan jenis yang kuat.

Penanganan Kesalahan

  1. Tidak melaporkan kesalahan driver penting dan menandai perangkat secara anggun tidak berfungsi.
  2. Tidak mengembalikan status kesalahan NT yang sesuai yang diterjemahkan ke status kesalahan WIN32 yang bermakna. Lihat Menggunakan nilai NTSTATUS.
  3. Tidak menggunakan makro NTSTATUS untuk memeriksa status fungsi sistem yang dikembalikan.
  4. Tidak menegaskan pada variabel status atau bendera jika diperlukan.
  5. Memeriksa untuk melihat apakah pointer valid sebelum mengaksesnya untuk mengatasi kondisi balapan.
  6. MENEGASKAN pada pointer NULL. Jika Anda mencoba menggunakan penunjuk NULL untuk mengakses memori, Windows akan memeriksa bug. Parameter pemeriksaan bug akan memberikan informasi yang diperlukan untuk memperbaiki pointer null. Lembur, ketika banyak pernyataan ASSERT yang tidak diperlukan ditambahkan ke kode, mereka menggunakan memori dan memperlambat sistem.
  7. MENEGASKAN pada penunjuk konteks objek. Kerangka kerja driver menjamin bahwa objek akan selalu dialokasikan dengan konteks.

Menelusuri

  1. Tidak menentukan jenis kustom WPP dan menggunakannya dalam panggilan pelacakan untuk mendapatkan pesan jejak yang dapat dibaca manusia. Lihat Menambahkan Pelacakan Perangkat Lunak WPP ke Driver Windows.
  2. Tidak menggunakan pelacakan IFR. Lihat Menggunakan Inflight Trace Recorder (IFR) di Driver KMDF dan UMDF 2.
  3. Memanggil nama fungsi dalam panggilan jejak WPP. WPP sudah melacak nama fungsi dan nomor baris.
  4. Tidak menggunakan peristiwa ETW untuk mengukur performa dan pengalaman pengguna penting lainnya yang memengaruhi peristiwa. Lihat Menambahkan Pelacakan Peristiwa ke Driver Mode Kernel.
  5. Tidak melaporkan kesalahan penting dalam eventlog dan menandai perangkat secara anggun tidak berfungsi.

Verifikasi

  1. Tidak menjalankan pemverifikasi driver dengan pengaturan standar dan tingkat lanjut selama pengembangan dan pengujian. Lihat Pemverifikasi Driver. Dalam pengaturan tingkat lanjut, disarankan untuk mengaktifkan semua aturan, kecuali aturan yang terkait dengan simulasi sumber daya rendah. Lebih baik menjalankan pengujian simulasi sumber daya yang rendah dalam isolasi untuk mempermudah debug masalah.
  2. Tidak menjalankan pengujian DevFund pada driver atau kelas perangkat yang difungsikan driver dengan pengaturan pemverifikasi tingkat lanjut diaktifkan. Lihat Cara menjalankan DevFund Tests melalui baris perintah.
  3. Tidak memverifikasi bahwa driver mematuhi HVCI. Lihat Menerapkan kode kompatibile HVCI.
  4. Tidak menjalankan AppVerifier pada WUDFhost.exe selama pengembangan dan pengujian driver mode pengguna. Lihat Pemverifikasi Aplikasi.
  5. Tidak memeriksa penggunaan memori menggunakan ekstensi debugger !wdfpoolusage pada runtime untuk memastikan objek WDF tidak ditinggalkan. Memori, permintaan, dan lokasi kerja adalah korban umum dari masalah ini.
  6. Tidak menggunakan ekstensi debugger !wdfkd untuk memeriksa pohon objek untuk memastikan objek diinduk dengan benar dan memeriksa atribut objek utama seperti WDFDRIVER, WDFDEVICE, IO.