




已閱讀5頁,還剩3頁未讀, 繼續(xù)免費閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
PCI設(shè)備的WDM驅(qū)動程序設(shè)計柳泉 羅耀華 柳華偉摘要:本文詳細地討論了利用DDK開發(fā)PCI設(shè)備的WDM驅(qū)動程序的設(shè)計原理、方法及在設(shè)計中注意事項,實現(xiàn)了以芯片PCI9052開發(fā)的PCI卡的具有內(nèi)存和I/O讀寫及中斷處理的WDM驅(qū)動程序。關(guān)鍵字:PCI,WDM,驅(qū)動程序,DDK在Windows操作系統(tǒng)中,為了保證系統(tǒng)的安全性和可移植性,對應(yīng)用程序?qū)τ布牟僮鬟M行了限制,尤其Windows 2000和Windows XP,不支持直接對系統(tǒng)的硬件資源的操作。因而在設(shè)計開發(fā)PCI設(shè)備時,需要開發(fā)相應(yīng)的驅(qū)動程序來實現(xiàn)對PCI設(shè)備的操作,用戶應(yīng)用程序通過驅(qū)動程序來訪問PCI設(shè)備。由于計算機硬件設(shè)備都存在不同的特點,因此各種設(shè)備的驅(qū)動程序也都有自己的特點,比如PCI設(shè)備、USB設(shè)備等等。盡管在整體框架中基本相同,但設(shè)備功能上不同,因此本文以PCI橋芯片PCI9052開發(fā)的PCI卡為硬件設(shè)備,來探討PCI設(shè)備的驅(qū)動程序的開發(fā)。1 驅(qū)動程序類型和開發(fā)工具的選擇在WINDOWS操作系統(tǒng)下,支持PCI總線及其設(shè)備的驅(qū)動程序類型有支持Windows 98/95的VxD、支持Windows NT的NT式驅(qū)動程序和支持Windows 2000、Windows XP和Windows 98的WDM(Windows Driver Model)。前兩種驅(qū)動程序類型由于其支持的操作系統(tǒng)的逐漸淘汰而淘汰?,F(xiàn)在主流的操作系統(tǒng)是Windows 2000和Windows XP,因此開發(fā)PCI設(shè)備的驅(qū)動程序最好的方案是WDM驅(qū)動程序。在一個系統(tǒng)中開發(fā)出WDM驅(qū)動程序,稍加修改即可在其他系統(tǒng)中編譯運行。WDM是在Windows NT驅(qū)動程序體系的基礎(chǔ)上發(fā)展而來的,修改或增加了即插即用、電源管理等功能,使之適應(yīng)硬件和用戶的要求。開發(fā)WDM驅(qū)動程序的主要工具是微軟為各操作系統(tǒng)提供的開發(fā)軟件包 Device Driver Kits(DDK) ,該軟件包為驅(qū)動程序開發(fā)者提供了用于驅(qū)動程序開發(fā)的資源文件、編譯連接程序、開發(fā)技術(shù)文檔等。還有第三方提供的開發(fā)工具:NuMega公司的DriverStudio和Jungo公司的WinDriver,這些工具是在DDK的基礎(chǔ)上為方便開發(fā)用戶而進行開發(fā)的工具。在使用中,雖然利用DDK開發(fā)驅(qū)動程序難度較大,但是代碼非常簡潔,結(jié)構(gòu)清晰,效率也高。利用第三方開發(fā)工具使用簡單,開發(fā)速度較快,但對于驅(qū)動程序的理解和深入開發(fā)不如DDK。因此選擇DDK開發(fā)PCI設(shè)備驅(qū)動程序,雖然開始會覺得非常復(fù)雜,但從執(zhí)行效率和功能上會更有利。2 PCI設(shè)備驅(qū)動程序的特點在開發(fā)驅(qū)動程序之前對PCI總線和硬件設(shè)備進行了解是十分必要的,而且還要詳細地掌握PCI設(shè)備的特性以及PCI設(shè)備驅(qū)動程序在設(shè)備程序棧的關(guān)系等,以便進行WDM驅(qū)動程序的設(shè)計。PCI總線是一種高性能、與CPU無關(guān)的32/64位地址數(shù)據(jù)復(fù)用的總線,它支持突發(fā)傳輸、即插即用、電源管理等功能,不但能滿足現(xiàn)在的應(yīng)用需要,而且能夠適應(yīng)未來的需求。PCI總線支持硬件資源動態(tài)自動配置,以支持即插即用。在PCI設(shè)備插入PCI插槽或上電后,PCI總線配置機構(gòu)自動根據(jù)PCI設(shè)備的要求實現(xiàn)配置。PCI總線支持內(nèi)存讀寫、I/O端口讀寫、中斷機制和DMA功能。由于這些硬件特點使PCI設(shè)備的WDM驅(qū)動程序的設(shè)計變得很復(fù)雜。在開發(fā)WDM驅(qū)動程序之前,還有必須掌握PCI設(shè)備的需要分配的資源等配置信息以及PCI設(shè)備的功能和操作方法。在WDM中,采用了分層的驅(qū)動程序體系結(jié)構(gòu),總線驅(qū)動程序或類驅(qū)動程序在最底層直接與設(shè)備打交道,設(shè)備功能驅(qū)動程序在上層通過與低層驅(qū)動程序打交道,實現(xiàn)設(shè)備的功能,中間還可以有類過濾驅(qū)動程序或設(shè)備過濾驅(qū)動程序用于數(shù)據(jù)的過濾或轉(zhuǎn)換。在PCI總線的驅(qū)動程序?qū)又?,其層次圖如圖4:更多的PCI設(shè)備的功能驅(qū)動程序和過濾驅(qū)動程序上層PCI類過濾驅(qū)動程序上層PCI設(shè)備過濾驅(qū)動程序PCI設(shè)備功能驅(qū)動程序低層PCI類過濾驅(qū)動過濾程序低層PCI設(shè)備過濾驅(qū)動程序PCI總線過濾驅(qū)動程序PCI總線驅(qū)動程序圖1 通用PCI總線的WDM驅(qū)動程序棧在實際開發(fā)中,一般無需分很多層次,只需要開發(fā)一個設(shè)備驅(qū)動程序即可。設(shè)備驅(qū)動程序直接與PCI總線驅(qū)動程序打交道,進行硬件操作,以實現(xiàn)PCI設(shè)備的功能。3 WDM驅(qū)動程序的設(shè)計在PCI設(shè)備的WDM驅(qū)動程序中,一般是編寫功能驅(qū)動程序。PCI總線驅(qū)動程序由操作系統(tǒng)實現(xiàn),過濾驅(qū)動程序一般在特殊的情況下需要編寫。因此本文只討論PCI設(shè)備功能驅(qū)動程序的設(shè)計。在PCI設(shè)備功能驅(qū)動程序中,需要處理PCI設(shè)備的內(nèi)存、端口的讀寫、中斷處理和DMA數(shù)據(jù)傳輸,實現(xiàn)PCI設(shè)備的功能,因此,PCI設(shè)備功能驅(qū)動程序是很標(biāo)準(zhǔn)的WDM設(shè)備驅(qū)動程序。PCI設(shè)備驅(qū)動程序在框架上與其他類型的設(shè)備驅(qū)動程序基本相同,包括初始化、創(chuàng)建設(shè)備、卸載和刪除設(shè)備、即插即用處理、分發(fā)例程處理、電源管理、WMI等部分,限于篇幅,在此只討論PCI設(shè)備的特別之處。(1)PCI設(shè)備資源的獲得PCI設(shè)備的硬件資源是由PCI配置機構(gòu)動態(tài)分配的,由PCI設(shè)備實現(xiàn)PCI配置寄存器,提出需要分配的硬件資源,由PCI配置機構(gòu)分配資源。驅(qū)動程序需要取得這些資源,才能操作硬件。因此,PCI設(shè)備的硬件資源分配與管理是驅(qū)動程序中很重要的部分。硬件資源主要包括映射內(nèi)存空間、I/O空間、中斷。在WDM體系中,取得這些資源有四種方法:讀寫PCI配置寄存器、調(diào)用硬件抽象層(HAL)函數(shù)、向PCI總線驅(qū)動程序發(fā)送讀寫配置IRP和向PCI總線驅(qū)動程序傳遞開啟設(shè)備IRP。第一種方法通過讀寫PCI總線配置I/O寄存器,來取得PCI設(shè)備的配置信息,其中包括資源的分配。這種方法需要將幾乎所有的PCI設(shè)備枚舉一遍,考慮到這種方法是對公共寄存器的讀寫,不利于系統(tǒng)的安全性,最好不使用這種方法,但是在調(diào)試PCI設(shè)備硬件時是個很好的方法。第二種方法通過調(diào)用函數(shù)HalGetBusData和HalGetBusDataByOffset來實現(xiàn)的,但是這種方法是為了能夠與Windows NT的驅(qū)動程序兼容,而保留下來的方法,不推薦使用,其功能被第三種方法取代。在WDM體系中,總線驅(qū)動程序必須實現(xiàn)總線上設(shè)備的管理功能。PCI總線驅(qū)動程序?qū)崿F(xiàn)了對PCI設(shè)備資源的枚舉,設(shè)備驅(qū)動程序通過向PCI總線驅(qū)動程序傳遞設(shè)備配置IRP_MJ_PNP,經(jīng)總線驅(qū)動程序的處理后,設(shè)備驅(qū)動程序得到PCI設(shè)備的資源信息。第四種方法是推薦的方法,當(dāng)系統(tǒng)的PNP管理器在取得設(shè)備的資源后會自動向驅(qū)動程序發(fā)出IRP_MN_START_DEVICE的IRP,在該IRP棧中包含了設(shè)備的資源信息。好的驅(qū)動程序都應(yīng)該使用這種方法,在此主要討論該方法。每個支持PNP功能的驅(qū)動程序,都應(yīng)實現(xiàn)IRP_MN_START_DEVICE處理。在該IRP處理中應(yīng)先交給低層驅(qū)動程序處理后,再根據(jù)IRP棧內(nèi)內(nèi)容進行資源分配。如下:NTSTATUS PnpStartDevice(IN PDEVICE_OBJECT fdo, IN PIRP pIrp ) NTSTATUS status;PIO_STACK_LOCATION stack;pIrp-IoStatus.Status = STATUS_SUCCESS;/先由低層驅(qū)動程序處理,并等待KeInitializeEvent(&event,NotificationEvent,FALSE);IoCopyCurrentIrpStackLocationToNext(pIrp);IoSetCompletionRoutine(pIrp,(PIO_COMPLETION_ROUTINE) OnRequestComplete,(PVOID) &event,TRUE,TRUE,TRUE);status=IoCallDriver(DEVICE_EXTENSION *)fdo-DeviceExtension) - pLowerDeviceObject ,pIrp);if (status = STATUS_PENDING) KeWaitForSingleObject(PVOID)&event,Executive,KernelMode,FALSE,NULL);if (!NT_SUCCESS(status)return CompleteRequest(pIrp, status);stack = IoGetCurrentIrpStackLocation(pIrp);ResourceRaw = stack-Parameters.StartDevice.AllocatedResources -List0.PartialResourceList-PartialDescriptors;Resource = stack-Parameters.StartDevice.AllocatedResourcesTranslated -List0.PartialResourceList-PartialDescriptors;for (i = 0; i Count; +i, +Resource, +ResourceRaw)switch (ResourceRaw-Type)case CmResourceTypeInterrupt:/中斷資源 IrqL = (KIRQL) Resource-u.Interrupt.Level;/中斷IRQL vector = Resource-u.Interrupt.Vector;/中斷向量 affinity = Resource-u.Interrupt.Affinity;/中斷分發(fā)的處理器集 /判斷中斷觸發(fā)的類型if (ResourceRaw-Flags = CM_RESOURCE_INTERRUPT_LATCHED) mode = Latched;/低電平觸發(fā) elsemode = LevelSensitive;/下降沿出發(fā)/是否共享,PCI中斷都是共享的irqshare = Resource-ShareDisposition = CmResourceShareShared;/連接中斷status = IoConnectInterrupt(&pdx-pInterruptObject, (PKSERVICE_ROUTINE)OnInterrupt,(PVOID)pdx,NULL,vector,IrqL,IrqL,mode, irqshare,affinity,FALSE); case CmResourceTypePort:/端口資源 pdx-PhysicalIOBase = ResourceRaw-u.Port.Start;/開始物理地址 pdx-IOCount = ResourceRaw-u.Port.Length;/地址數(shù)量pdx-IOBase = (ULONG *)MmMapIoSpace(pdx-PhysicalIOBase,pdx-IOCount,MmNonCached);/映射端口 break; case CmResourceTypeMemory:/內(nèi)存資源pdx-PhysicalMemBase = ResourceRaw-u.Memory.Start;/開始地址pdx-MemCount = ResourceRaw-u.Memory.Length;/地址數(shù)量pdx-MemBase = (ULONG *)MmMapIoSpace(pdx-PhysicalMemBase,pdx-MemCount,MmNonCached);/映射內(nèi)存if (pdx-MemBase = NULL)return STATUS_INSUFFICIENT_RESOURCES;/其他資源一般沒有,可默認處理default:break; return STATUS_SUCCESS;在以上的代碼中,限于篇幅,沒有增加錯誤處理代碼,在實際中應(yīng)用一定需要進行在調(diào)用系統(tǒng)函數(shù)之后,進行相應(yīng)的處理,如果不符合要求,立即退出,否則在其他例程中會發(fā)生錯誤,使系統(tǒng)崩潰。同時,在退出之前,一定要釋放已分配的資源。(2)內(nèi)存讀寫Windows工作在保護模式下,與實模式的區(qū)別在于CPU尋址方式不同,可以實現(xiàn)虛擬內(nèi)存。在Windows系統(tǒng)中對內(nèi)存又分為分頁和非分頁內(nèi)存。分頁內(nèi)存一般用于應(yīng)用程序,系統(tǒng)提供分頁和分段使用戶應(yīng)用程序使用的內(nèi)存可以在程序空閑的時候由系統(tǒng)將其從物理內(nèi)存調(diào)配到硬盤中,以節(jié)省物理內(nèi)存資源,當(dāng)程序重新運行的時候,再由系統(tǒng)將其調(diào)配到物理內(nèi)存,這樣,系統(tǒng)可以得到比物理內(nèi)存非常大的內(nèi)存量,允許更多得應(yīng)用程序保持運行。而非分頁內(nèi)存為系統(tǒng)常駐內(nèi)存,不可以從物理內(nèi)存調(diào)配到硬盤上,因此內(nèi)存無需分頁。在WDM驅(qū)動程序中,對于硬件的內(nèi)存映射一般需要用非分頁內(nèi)存,因為在一些運行在DISPATCH_LEVEL或更高得中斷級例程中,禁止使用分頁內(nèi)存,比如在中斷處理程序中就不可以使用分頁內(nèi)存。再者,使用非分頁內(nèi)存無需太多的轉(zhuǎn)換,非常安全,效率也高。如果使用分頁內(nèi)存,系統(tǒng)就有可能將其調(diào)配到硬盤上,容易產(chǎn)出錯誤。但是,不能過多地使用非分頁內(nèi)存。在PCI設(shè)備的驅(qū)動程序中,獲得的設(shè)備內(nèi)存是一段映射物理內(nèi)存,這是無法使用的,需要將其映射成系統(tǒng)可以訪問的非分頁內(nèi)存。函數(shù)MmMapIoSpace完成該功能。該函數(shù)的原型為:PVOID MmMapIoSpace( IN PHYSICAL_ADDRESS PhysicalAddress, IN ULONG NumberOfBytes, IN MEMORY_CACHING_TYPE CacheEnable);參數(shù)PhysicalAddress為物理地址;NumberOfBytes為地址的數(shù)量;CacheEnable為內(nèi)存是否可以隱藏,取值可為MmNonCached,MmCached,MmWriteCombined,這里必須取為MmNonCached。其應(yīng)用實例見以上代碼中的“內(nèi)存資源”處理部分。當(dāng)訪問設(shè)備內(nèi)存時,使用函數(shù)UCHAR READ_REGISTER_UCHAR(IN PUCHAR Register);ULONG READ_REGISTER_ULONG(IN PULONG Register);USHORT READ_REGISTER_USHORT(IN PUSHORT Register);VOID READ_REGISTER_BUFFER_UCHAR(IN PUCHAR Register,IN PUCHAR Buffer,IN ULONG Count);VOID READ_REGISTER_BUFFER_UCHAR(IN PULONG Register,IN PULONG Buffer,IN ULONG Count);VOID READ_REGISTER_BUFFER_UCHAR(IN PUSHORT Register,IN PUSHORT Buffer,IN ULONG Count);VOID WRITE_REGISTER_UCHAR(IN PUCHAR Register,IN UCHAR Value);VOID WRITE_REGISTER_ULONG(IN PULONG Register,IN ULONG Value);VOID WRITE_REGISTER_USHORT(IN PUSHORT Register,IN USHORT Value);VOID WRITE_REGISTER_BUFFER_UCHAR(IN PUCHAR Register,IN PUCHAR Buffer,IN ULONG Count);VOID WRITE_REGISTER_BUFFER_UCHAR(IN PULONG Register,IN PULONG Buffer,IN ULONG Count);VOID WRITE_REGISTER_BUFFER_UCHAR(IN PUSHORT Register,IN PUSHORT Buffer,IN ULONG Count);以上函數(shù)對應(yīng)的分別是對PCI設(shè)備內(nèi)存的讀寫函數(shù),參數(shù)Register為映射后的內(nèi)存地址,在使用時,應(yīng)進行相應(yīng)的數(shù)據(jù)類型轉(zhuǎn)換。其他參數(shù)為數(shù)據(jù)參數(shù)。XXX_REGISTER_XXX讀寫單個地址的內(nèi)容;XXX_REGISTER_BUFFER_XXX讀寫一段內(nèi)存的內(nèi)容,這在PCI設(shè)備支持突發(fā)讀寫(Burst Transmission)時應(yīng)用。例如讀寫單個內(nèi)存的地址:WRITE_REGISTER_UCHAR(PUCHAR)pdx-MmBase,0x03C);(3)I/O讀寫在PC上,I/O空間是一個64K字節(jié)的尋址空間。I/O端口的尋址方式與內(nèi)存是不一樣的。但是在WDM驅(qū)動程序中,對其處理與內(nèi)存是一樣的,把其看作寄存器,映射為設(shè)備內(nèi)存。其映射方法和訪問函數(shù)的用法與內(nèi)存資源一樣,只不過函數(shù)XXX_REGISTER_XXX改為XXX_PORT_XXX。(4)中斷的處理在PCI總線中,很多設(shè)備共享一個中斷,這就需要在中斷處理函數(shù)要格外小心,處理不當(dāng),就會導(dǎo)致系統(tǒng)崩潰。驅(qū)動程序首先要在IRP_MN_START_DEVICE中獲得中斷資源,然后需要連接到中斷處理函數(shù)中,使其當(dāng)有中斷請求時,進入中斷服務(wù)例程。連接中斷的函數(shù)為IoConnectInterrupt,具體用法見上段程序中的“中斷資源”部分。十分需要注意的是在連接中斷之前,一定要確定PCI設(shè)備不會產(chǎn)生中斷請求,最好在PCI設(shè)備上電后,中斷為屏蔽狀態(tài)。在連接中斷后,調(diào)用開啟中斷請求的函數(shù)需要同步處理,以防在函數(shù)的執(zhí)行中,出現(xiàn)運行時間上的錯誤,而且在開啟中斷時,一定要在所有的硬件資源分配以后,否則如果有中斷產(chǎn)生,系統(tǒng)就會立即調(diào)用中斷處理例程,如果例程中使用了還沒有分配的資源,就會出現(xiàn)意想不到的結(jié)果。同步處理使用函數(shù):BOOLEAN KeSynchronizeExecution(IN PKINTERRUPT Interrupt,IN PKSYNCHRONIZE_ROUTINE SynchronizeRoutine,IN PVOID SynchronizeContext);參數(shù)Interrupt為IoConnectInterrupt返回的變量,SynchronizeRoutine為函數(shù)名稱,SynchronizeContext為函數(shù)的輸入?yún)?shù)。調(diào)用方式如下:KeSynchronizeExecution(pdx-pInterruptObject,(PKSYNCHRONIZE_ROUTINE)EnablePciInterrupt,pdx);在中斷服務(wù)例程中,首先必須根據(jù)硬件信息來判斷該中斷是否是自己的設(shè)備發(fā)出的。這是因為PCI總線共享中斷,系統(tǒng)在接收到中斷后,順序調(diào)用各個注冊該中斷資源的驅(qū)動程序的中斷處理例程,如果有返回TRUE的例程,就代表該中斷已處理,就不再調(diào)用其他例程,如果是返回FALSE的例程,則說明該中斷沒有處理,則繼續(xù)調(diào)用其他的例程。如果返回錯誤,就會擾亂系統(tǒng),造成系統(tǒng)崩潰。其框圖如圖3。進入中斷處理程序是否是自己的設(shè)備的中斷?處理中斷返回FALSE返回TRUE返回是否圖2 中斷服務(wù)例程框圖在中斷服務(wù)例程中,相應(yīng)的處理最好簡潔快速,因為中斷例程運行的級別很高,當(dāng)有中斷請求時,不但會打斷應(yīng)用程序的執(zhí)行,而且會打斷在硬件中斷級以下的所有運行程序。在WDM中,提供了DPC(Deferred Procedure Call)例程,將在中斷例程中耗時的但不需要立即處理的任務(wù)延時處理。比如,驅(qū)動程序接受應(yīng)用程序的寫PCI設(shè)備的數(shù)據(jù),當(dāng)寫完后,硬件產(chǎn)生中斷標(biāo)志執(zhí)行完畢,這時需要結(jié)束該IRP,就可以將結(jié)束IRP這個耗時的任務(wù)交給DPC完成。典型的用法示例如圖4:WriteFileDispatchWriteStartIoOnInterruptDPC應(yīng)用程序驅(qū)動程序圖3 中斷處理過程示例在該實例中,由應(yīng)用程序調(diào)用函數(shù)WriteF
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 現(xiàn)代林業(yè)造林方法及營林生產(chǎn)管理問題探尋
- 老年人護理中心
- 交通運輸采購法務(wù)支持與合同違約責(zé)任明確合同
- 車輛租賃行業(yè)風(fēng)險評估承包合同
- 高科技園區(qū)廠房場地租賃合同范本
- 槽棎施工與地基處理合同
- 礦山采礦權(quán)抵押貸款與礦山運營管理服務(wù)合同
- 叉車操作員健康管理與勞動合同
- 商業(yè)店鋪租賃合同含裝修補貼
- 特色餐飲店鋪租賃與裝修合同
- LY/T 2071-2024人造板類產(chǎn)品生產(chǎn)綜合能耗
- 帶狀皰疹預(yù)防接種健康宣教
- 探究大象耳朵秘密:2025年課堂新視角
- 《咸寧市政府投資房屋建筑和市政基礎(chǔ)設(shè)施工程施工范本招標(biāo)文件》2021版
- 固定矯治器護理查房
- 招生就業(yè)處2025年工作計劃
- 市場營銷學(xué)練習(xí)及答案(吳健安)
- 脊柱健康與中醫(yī)養(yǎng)生課件
- 2024馬克思主義發(fā)展史第2版配套題庫里面包含考研真題課后習(xí)題和章節(jié)題庫
- 急救車藥品管理制度
- 2024年職業(yè)技能:拍賣師專業(yè)知識考試題與答案
評論
0/150
提交評論