在上一节中我们考察了磁盘的类驱动,包括由类驱动所创建的FDO和PDO两种设备对象,以及这两种设备对象之间的关系。
在“类(Class)”驱动的下面本应该是“端口(Port)”驱动,但是实际的设计和实现中常常把端口驱动也分成两个子层,把其中与具体产品密切相关的成分分离出来,成为“小端口(Miniport)”驱动。特别地,对于块存储设备,Windows采用的就是Class/Port/Miniport的结构方案,并由微软提供块存储设备的端口驱动,这样具体设备的厂家就只要为自己的产品开发小端口驱动就可以了。
我们现在要考察的就是这样一个分成两层的端口驱动,主要是由DDK提供的一个小端口驱动aha154x.sys,其代码在DDK的src/storage/miniport/aha154x中。Aha154x是由Adaptec公司提供的SCSI总线适配器。
ULONG //src/storage/miniport/aha154x
DriverEntry(IN PVOID DriverObject, IN PVOID Argument2)
{
HW_INITIALIZATION_DATA hwInitializationData;
......
// Zero out structure.
for (i=0; i<sizeof(HW_INITIALIZATION_DATA); i++)
((PUCHAR)&hwInitializationData)[i] = 0;
// Set size of hwInitializationData.
hwInitializationData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_ DATA);
// Set entry points.
hwInitializationData.HwInitialize = A154xHwInitialize;
hwInitializationData.HwResetBus = A154xResetBus;
hwInitializationData.HwStartIo = A154xStartIo;
hwInitializationData.HwInterrupt = A154xInterrupt;
hwInitializationData.HwFindAdapter = A154xFindAdapter;
hwInitializationData.HwAdapterState = A154xAdapterState;
hwInitializationData.HwAdapterControl = A154xAdapterControl;
// Indicate no buffer mapping but will need physical addresses.
hwInitializationData.NeedPhysicalAddresses = TRUE;
// Specify size of extensions.
hwInitializationData.DeviceExtensionSize = sizeof(HW_DEVICE_EXTENSION);
hwInitializationData.SpecificLuExtensionSize = sizeof(HW_LU_EXTENSION);
// Specifiy the bus type.
hwInitializationData.AdapterInterfaceType = Isa; //这是ISA总线上的SCSI磁盘适配器
hwInitializationData.NumberOfAccessRanges = 2;
// Ask for SRB extensions for CCBs.
hwInitializationData.SrbExtensionSize = sizeof(CCB);
// The adapter count is used by the find adapter routine to track how
// which adapter addresses have been tested.
context.adapterCount = 0;
context.biosScanStart = 0;
isaStatus = ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &context);
// Now try to configure for the Mca bus. Specifiy the bus type.
hwInitializationData.AdapterInterfaceType = MicroChannel;
context.adapterCount = 0;
context.biosScanStart = 0;
mcaStatus = ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &context);
return(mcaStatus < isaStatus ? mcaStatus : isaStatus);
} // end A154xEntry()
这个模块的DriverEntry()与前面Disk的DriverEntry()有了形式上的不同,注意这里的函数指针都在一个HW_INITIALIZATION_DATA数据结构hwInitializationData中,而不是主功能函数数组中。这里没有直接设置主功能函数,而且既不直接创建设备对象,也不提供AddDevice函数。事实上,这些都是由ScsiPortInitialize()实现的。
那么这里的ScsiPortInitialize()又是从哪来的呢?DDK甚至不说是谁提供了这个函数,而只是在srb.h中提供了这个函数的声明:
SCSIPORT_API ULONG
ScsiPortInitialize(IN PVOID Argument1, IN PVOID Argument2,
IN struct _HW_INITIALIZATION_DATA *HwInitializationData, IN PVOID HwContext);
意思是:“你别管是谁提供的,反正到时候有个什么东西会向你提供这个函数就是了”。
幸好我们有工具depends.exe,可以看到这个函数是由scsiport.sys导出的,可惜DDK中却不提供这个模块的源代码(要不然就不像是微软了)。幸好,我们还有ReactOS,那里也实现了scsiport.sys,从那里我们可以看到其DriverEntry()的源代码:
NTSTATUS STDCALL //ReactOS-0.3.0/drivers/storage/scsiport
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DPRINT("ScsiPort Driver %s/n", VERSION);
return(STATUS_SUCCESS);
}
原来如此。当然,我们也可以拿来微软的scsiport.sys,从其程序入口地址开始反汇编,读不了几条指令就应该可以发现这个秘密。
这个DriverEntry()揭示的事实是:SCSI磁盘的类驱动scsiport.sys并不创建自己的设备对象,因而是无形的,更无从出现在设备对象的堆叠中。换言之,scsiport.sys的作用仅仅是为小端口驱动提供库函数。由此也可见,“小端口驱动”乃是把端口驱动中的许多公共库函数剥离以后所剩下来的那一小部分。
前面的代码中调用了ScsiPortInitialize()两次,第一次是针对ISA总线(实际上包括PCI总线);第二次是针对“微通道”总线MicroChannel。后者现在已经很少用了。
如前所述,DDK中并不提供ScsiPortInitialize()及其所属类驱动的代码,所以下面属于类驱动的有关代码均取自ReactOS。我们先分段阅读ScsiPortInitialize()。
ULONG STDCALL
ScsiPortInitialize(IN PVOID Argument1, IN PVOID Argument2,
IN struct _HW_INITIALIZATION_DATA *HwInitializationData, IN PVOID HwContext)
{
PDRIVER_OBJECT DriverObject = (PDRIVER_OBJECT)Argument1;
......
if ((HwInitializationData->HwInitialize == NULL) ||
(HwInitializationData->HwStartIo == NULL) ||
(HwInitializationData->HwInterrupt == NULL) ||
(HwInitializationData->HwFindAdapter == NULL) ||
(HwInitializationData->HwResetBus == NULL))
return(STATUS_INVALID_PARAMETER);
DriverObject->DriverStartIo = NULL;
DriverObject->MajorFunction[IRP_MJ_CREATE] = ScsiPortCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = ScsiPortCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ScsiPortDeviceControl;
DriverObject->MajorFunction[IRP_MJ_SCSI] = ScsiPortDispatchScsi;
SystemConfig = IoGetConfigurationInformation();
DeviceExtensionSize = sizeof(SCSI_PORT_DEVICE_EXTENSION) +
HwInitializationData->DeviceExtensionSize;
PortConfigSize = sizeof(PORT_CONFIGURATION_INFORMATION) +
HwInitializationData->NumberOfAccessRanges * sizeof(ACCESS_RANGE);
MaxBus = (HwInitializationData->AdapterInterfaceType == PCIBus) ? 8 : 1;
PortDeviceObject = NULL;
BusNumber = 0;
SlotNumber.u.AsULONG = 0;
while (TRUE)
{ //为配备的每条SCSI总线都创建一个设备对象
/* Create a unicode device name */
swprintf (NameBuffer, L"//Device//ScsiPort%lu", SystemConfig->ScsiPortCount);
RtlInitUnicodeString (&DeviceName, NameBuffer);
DPRINT("Creating device: %wZ/n", &DeviceName);
/* Create the port device */
Status = IoCreateDevice (DriverObject, DeviceExtensionSize, &DeviceName,
FILE_DEVICE_CONTROLLER, 0, FALSE, &PortDeviceObject);
if (!NT_SUCCESS(Status)) ......
/* Increase the stacksize. We reenter our device on IOCTL_SCSI_MINIPORT */
PortDeviceObject->StackSize++; //使StackSize变成2
/* Set the buffering strategy here... */
PortDeviceObject->Flags |= DO_DIRECT_IO;
PortDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
DeviceExtension = PortDeviceObject->DeviceExtension;
RtlZeroMemory(DeviceExtension, DeviceExtensionSize);
DeviceExtension->Length = DeviceExtensionSize;
DeviceExtension->DeviceObject = PortDeviceObject;
DeviceExtension->PortNumber = SystemConfig->ScsiPortCount;
DeviceExtension->MiniPortExtensionSize = HwInitializationData->Device ExtensionSize;
DeviceExtension->LunExtensionSize = HwInitializationData->SpecificLu ExtensionSize;
DeviceExtension->SrbExtensionSize = HwInitializationData->SrbExtension Size;
DeviceExtension->HwStartIo = HwInitializationData->HwStartIo;
DeviceExtension->HwInterrupt = HwInitializationData->HwInterrupt;
DeviceExtension->AdapterObject = NULL;
DeviceExtension->MapRegisterCount = 0;
DeviceExtension->PhysicalAddress.QuadPart = 0ULL;
DeviceExtension->VirtualAddress = NULL;
DeviceExtension->CommonBufferLength = 0;
/* Initialize the device base list */
InitializeListHead (&DeviceExtension->DeviceBaseListHead);
/* Initialize the irp lists */
InitializeListHead (&DeviceExtension->PendingIrpListHead);
DeviceExtension->NextIrp = NULL;
DeviceExtension->PendingIrpCount = 0;
DeviceExtension->ActiveIrpCount = 0;
/* Initialize LUN-Extension list */
InitializeListHead (&DeviceExtension->LunExtensionListHead);
/* Initialize the spin lock in the controller extension */
KeInitializeSpinLock (&DeviceExtension->Lock);
/* Initialize the DPC object */
IoInitializeDpcRequest (PortDeviceObject, ScsiPortDpc); //初始化本设备对象的DPC
/* Initialize the device timer */
DeviceExtension->TimerState = IDETimerIdle;
DeviceExtension->TimerCount = 0;
IoInitializeTimer (PortDeviceObject, ScsiPortIoTimer, DeviceExtension);
/* Allocate and initialize port configuration info */
DeviceExtension->PortConfig = ExAllocatePool (NonPagedPool, PortConfigSize);
if (DeviceExtension->PortConfig == NULL) ......
RtlZeroMemory (DeviceExtension->PortConfig, PortConfigSize);
PortConfig = DeviceExtension->PortConfig;
PortConfig->Length = sizeof(PORT_CONFIGURATION_INFORMATION);
PortConfig->SystemIoBusNumber = BusNumber;
PortConfig->AdapterInterfaceType = HwInitializationData->AdapterInter faceType;
PortConfig->InterruptMode =
(PortConfig->AdapterInterfaceType == PCIBus) ? LevelSensitive : Latched;
PortConfig->MaximumTransferLength = SP_UNINITIALIZED_VALUE;
PortConfig->NumberOfPhysicalBreaks = SP_UNINITIALIZED_VALUE;
PortConfig->DmaChannel = SP_UNINITIALIZED_VALUE;
PortConfig->DmaPort = SP_UNINITIALIZED_VALUE;
PortConfig->DmaWidth = 0;
PortConfig->DmaSpeed = Compatible;
PortConfig->AlignmentMask = 0;
PortConfig->NumberOfAccessRanges = HwInitializationData->NumberOfAccess Ranges;
PortConfig->NumberOfBuses = 0;
for (i = 0; i < SCSI_MAXIMUM_BUSES; i++) //SCSI_MAXIMUM_BUSES定义为8
PortConfig->InitiatorBusId[i] = 255;
PortConfig->ScatterGather = FALSE;
PortConfig->Master = FALSE;
PortConfig->CachesData = FALSE;
PortConfig->AdapterScansDown = FALSE;
PortConfig->AtdiskPrimaryClaimed = SystemConfig->AtDiskPrimaryAddress Claimed;
PortConfig->AtdiskSecondaryClaimed =
SystemConfig->AtDiskSecondaryAddressClaimed;
PortConfig->Dma32BitAddresses = FALSE;
PortConfig->DemandMode = FALSE;
PortConfig->MapBuffers = HwInitializationData->MapBuffers;
PortConfig->NeedPhysicalAddresses = HwInitializationData->NeedPhysical Addresses;
PortConfig->TaggedQueuing = HwInitializationData->TaggedQueuing;
PortConfig->AutoRequestSense = HwInitializationData->AutoRequestSense;
PortConfig->MultipleRequestPerLu = HwInitializationData->MultipleRequest PerLu;
PortConfig->ReceiveEvent = HwInitializationData->ReceiveEvent;
PortConfig->RealModeInitialized = FALSE;
PortConfig->BufferAccessScsiPortControlled = FALSE;
PortConfig->MaximumNumberOfTargets = SCSI_MAXIMUM_TARGETS;
PortConfig->SrbExtensionSize = HwInitializationData->SrbExtensionSize;
PortConfig->SpecificLuExtensionSize = HwInitializationData->SpecificLu ExtensionSize;
PortConfig->AccessRanges = (ACCESS_RANGE(*)[])(PortConfig + 1);
/* Search for matching PCI device */
if ((HwInitializationData->AdapterInterfaceType == PCIBus) &&
(HwInitializationData->VendorIdLength > 0) &&
(HwInitializationData->VendorId != NULL) &&
(HwInitializationData->DeviceIdLength > 0) &&
(HwInitializationData->DeviceId != NULL))
{ //总线类型为PCI
/* Get PCI device data */
if (!SpiGetPciConfigData (HwInitializationData, PortConfig,
BusNumber, &SlotNumber))
{
Status = STATUS_UNSUCCESSFUL;
goto ByeBye;
}
}
/* Note: HwFindAdapter is called once for each bus */
Again = FALSE;
DPRINT("Calling HwFindAdapter() for Bus %lu/n", PortConfig->SystemIo BusNumber);
Result = (HwInitializationData->HwFindAdapter)(
&DeviceExtension->MiniPortDeviceExtension, HwContext,
0, /* BusInformation */ "", /* ArgumentString */
PortConfig, &Again);
if (Result == SP_RETURN_FOUND)
{
DPRINT("ScsiPortInitialize(): Found HBA! (%x)/n", PortConfig->Bus InterruptVector);
if (DeviceExtension->VirtualAddress == NULL &&
DeviceExtension->SrbExtensionSize)
{
ScsiPortGetUncachedExtension(&DeviceExtension->MiniPortDevice Extension,
PortConfig, 0);
}
/* Register an interrupt handler for this device */ //设置中断向量等
MappedIrq = HalGetInterruptVector(PortConfig->AdapterInterfaceType,
PortConfig->SystemIoBusNumber, PortConfig->BusInterruptLevel,
PortConfig->BusInterruptLevel, &Dirql, &Affinity);
Status = IoConnectInterrupt(&DeviceExtension->Interrupt, ScsiPortIsr,
DeviceExtension, NULL, MappedIrq, Dirql, Dirql,
PortConfig->InterruptMode, TRUE, Affinity, FALSE);
if (!NT_SUCCESS(Status)) ......
if (!(HwInitializationData->HwInitialize)(
&DeviceExtension->MiniPortDeviceExtension))
{ //硬件初始化失败
DbgPrint("HwInitialize() failed!");
Status = STATUS_UNSUCCESSFUL;
goto ByeBye;
}
/* Initialize port capabilities */
DeviceExtension->PortCapabilities = ExAllocatePool(NonPagedPool,
sizeof(IO_SCSI_CAPABILITIES));
if (DeviceExtension->PortCapabilities == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ByeBye;
}
PortCapabilities = DeviceExtension->PortCapabilities;
PortCapabilities->Length = sizeof(IO_SCSI_CAPABILITIES);
if (PortConfig->ScatterGather == FALSE ||
PortConfig->NumberOfPhysicalBreaks >= (0x100000000LL >> PAGE_SHIFT)||
PortConfig->MaximumTransferLength <
PortConfig->NumberOfPhysicalBreaks * PAGE_SIZE)
{
PortCapabilities->MaximumTransferLength =
PortConfig->MaximumTransferLength;
}
else
{
PortCapabilities->MaximumTransferLength =
PortConfig->NumberOfPhysicalBreaks * PAGE_SIZE;
}
PortCapabilities->MaximumPhysicalPages =
PortCapabilities->MaximumTransferLength / PAGE_SIZE;
PortCapabilities->SupportedAsynchronousEvents = 0; /* FIXME */
PortCapabilities->AlignmentMask = PortConfig->AlignmentMask;
PortCapabilities->TaggedQueuing = PortConfig->TaggedQueuing;
PortCapabilities->AdapterScansDown = PortConfig->AdapterScansDown;
PortCapabilities->AdapterUsesPio = TRUE; /* FIXME */
/* Scan the adapter for devices */
SpiScanAdapter(DeviceExtension); //扫描同一条SCSI总线上的磁盘
/* Build the registry device map */ //把扫描的发现记录在注册表中
SpiBuildDeviceMap (DeviceExtension, (PUNICODE_STRING)Argument2);
/* Create the dos device link */ //生成DOS设备名并建立符号连接
swprintf(DosNameBuffer, L"//??//Scsi%lu:", SystemConfig->ScsiPort Count);
RtlInitUnicodeString(&DosDeviceName, DosNameBuffer);
IoCreateSymbolicLink(&DosDeviceName, &DeviceName);
/* Update the system configuration info */
if (PortConfig->AtdiskPrimaryClaimed == TRUE)
SystemConfig->AtDiskPrimaryAddressClaimed = TRUE;
if (PortConfig->AtdiskSecondaryClaimed == TRUE)
SystemConfig->AtDiskSecondaryAddressClaimed = TRUE;
SystemConfig->ScsiPortCount++;
PortDeviceObject = NULL;
DeviceFound = TRUE;
} //end if (Result == SP_RETURN_FOUND)
else
{
DPRINT("HwFindAdapter() Result: %lu/n", Result);
ExFreePool (PortConfig);
IoDeleteDevice (PortDeviceObject);
PortDeviceObject = NULL;
}
DPRINT("Bus: %lu MaxBus: %lu/n", BusNumber, MaxBus);
if (BusNumber >= MaxBus)
{
Status = STATUS_SUCCESS;
goto ByeBye;
}
if (Again == FALSE)
{
BusNumber++;
SlotNumber.u.AsULONG = 0;
}
} //end while
ByeBye:
/* Clean up the mess */
if (PortDeviceObject != NULL)
{
DPRINT("Delete device: %p/n", PortDeviceObject);
DeviceExtension = PortDeviceObject->DeviceExtension;
if (DeviceExtension->PortCapabilities != NULL)
{
IoDisconnectInterrupt (DeviceExtension->Interrupt);
ExFreePool (DeviceExtension->PortCapabilities);
}
if (DeviceExtension->PortConfig != NULL)
{
ExFreePool (DeviceExtension->PortConfig);
}
IoDeleteDevice (PortDeviceObject);
}
return (DeviceFound == FALSE) ? Status : STATUS_SUCCESS;
}
我们关心的是设备驱动的框架,而不是SCSI磁盘的细节,这里的许多代码都与SCSI总线和磁盘的细节有关,要对这些细节做一介绍一方面说来话长,另一方面也不免离题,所以这里只对代码中那些框架性的东西做一说明。有关SCSI磁盘的详情则需要参考这方面的专门资料。
先看主功能函数的设置。注意这里没有提供针对IRP_MJ_READ、IRP_MJ_WRITE的主功能函数,这是因为对SCSI磁盘的操作是通过SCSI_REQUEST_BLOCK数据结构即“SCSI请求块”SRB的传递而完成的,这些操作都统一抽象为“SCSI操作”,在“SCSI操作”中又分出读、写等具体的操作,所以这里提供了针对IRP_MJ_SCSI的主功能函数。其实,IRP在某种意义上正是对SRB的模仿。
一台机器上可以接好几条“SCSI”总线,每条SCSI总线上则可以接好几个磁盘。这里通过一个while循环为每条SCSI总线都创建一个设备对象,并为其设置好中断向量和DPC对象。这些设备对象都指向同一个驱动对象,所以IRP无论进入哪一个设备对象都会执行相同的主功能函数。这些设备对象是由同一个驱动对象创建的,所以都挂在这个驱动对象的队列中。注意这里并没有为每个磁盘都创建一个设备对象,因为SCSI总线的控制是集中式的,在同一条总线上不同的磁盘具有不同的“逻辑单元号”LUN,对不同磁盘的操作只是体现在SRB中不同的逻辑单元号。
代码中没有提供AddDevice函数,这是因为所创建的设备对象不再堆叠到别的设备对象上,IRP一旦进入这些设备对象就不再下传了。如前所述,在某种意义上IRP是对SRB的模仿,所以也可以认为IRP进入这些设备对象以后就转化成SRB继续下传,不过此时是通过SCSI总线的硬件下传了。就IRP而言,这些设备对象已经是末梢、到了尽头了。
在这个例子中,端口驱动是无形的,它没有自己的设备对象,当然更谈不上出现在堆叠中。出现在堆叠中的是小端口驱动的设备对象,这些设备对象当然是指向小端口驱动对象,主功能函数指针的数组也在小端口驱动对象中。但是这些主功能函数却是由端口驱动模块提供的,所以端口驱动模块相当于一个程序库,也可以看做是对内核及其设备驱动框架的扩充。显然,在这里端口驱动模块的装载必须在小端口驱动模块之前。但是,由端口驱动模块提供的主功能函数却不知道怎样与具体设备的硬件接口打交道,于是小端口驱动模块又得反过来为其提供一组函数,这就是HW_INITIALIZATION_DATA结构中那些函数指针的作用。
相比之下,在前面鼠标器驱动的那个例子中,那里没有小端口驱动,而端口驱动却有个设备对象在堆叠中,这是因为没有把公共的库函数剥离出去。至于怎么剥离,剥离以后形成什么样的边界,那就是设计者的事了。所以,端口驱动和小端口驱动之间的关系只是二者之间的事,二者的开发者之间有个约定就行,在这方面并不存在某种统一的范式,更不遵循由IRP+IoCallDriver()所构成的标准界面。
最后还要说明,我们在这里所考察的小端口驱动aha154x.sys是用于SCSI磁盘的,所以其端口驱动是scsiport.sys。可是如果用的是IDE磁盘呢?IDE磁盘的小端口驱动是pciide.sys+pciidex.sys,与其相配的端口驱动则是atapi.sys。
此外,从Windows Server 2003开始又引入了另一个端口驱动storport.sys来取代scsiport.sys,以支持磁盘阵列RAID并提供“高性能计算”所需的其他支持。
总结一下SCSI磁盘的类驱动和端口驱动,我们看到:
— 驱动模块disk.sys的DriverEntry()通过由classpnp.sys提供的ClassInitialize()和ClassInitializeEx()进行初始化。disk.sys为此而准备的数据结构为CLASS_INIT_DATA。disk.sys有自己的设备对象,并出现在相关的设备对象堆叠中,所以是“有形”的;而classpnp.sys本身不创建自己的设备对象,所以是“无形”的,只是一个动态连接库。
— 驱动模块aha154x.sys的DriverEntry()通过由scsiport.sys提供的ScsiPortInitialize()进行初始化。aha154x.sys为此准备的数据结构为HW_INITIALIZATION_DATA。aha154x.sys有自己的设备对象,并出现在相关的设备对象堆叠中,所以是“有形”的;而scsiport.sys本身不创建自己的设备对象,所以是“无形”的,只是一个动态连接库。
数据结构CLASS_INIT_DATA和HW_INITIALIZATION_DATA的本质都是向上层模块提供下层模块中的函数指针和数据,只是两个数据结构所面向的层次不同。
就是因为数据结构所面向的层次不同,disk.sys称为“类驱动(Class Driver)”,而aha154x.sys称为“小端口驱动”。
在本书中我们所关心的主要是磁盘驱动的系统结构,而不是磁盘操作的算法、流程和细节。对于有关细节有兴趣的读者可以自己进一步研读代码,或者参考《Linux内核源代码情景分析》一书中Linux内核所实现的磁盘驱动。实际上,抛开系统结构上的不同,剩下来的就很接近了。具体的细节可能不同,但是大部分的算法和流程其实是很接近的。比方说,对于访问磁盘上扇区的先后需要进行调度,以提高访问的效率,为此Linux内核中实现了一个“电梯算法”,而Windows则在类驱动中实现了类似的算法,叫C-Look。当然,由于文件系统的结构不同,这些算法也不会完全相同,但是所体现的思路则大同小异。所以,两个系统之间有着“他山之石,可以攻玉”的关系,了解任何一方对于了解另一方都是大有帮助的。