文件系统驱动编程基础篇之5——注册表与Inf (转)

注册表以树形方式存储配置信息,树节点称为键(key),键可以包含子键(subkey)和称为值(value)的数据项。

一)需要关注的几种键(注:硬件键、类键、设备接口类应是所列位置下的子键):

二)第3点中的服务键的写法与其他键有所不同,它以\REGISTRY打头,这是内核模式下根键的规定写法。

User-mode Handle

Corresponding Object Name

HKEY_LOCAL_MACHINE

\Registry\Machine

HKEY_USERS

\Registry\User

HKEY_CLASSES_ROOT

No kernel-mode equivalent

HKEY_CURRENT_USER

No simple kernel-mode equivalent, but see Registry Run-Time Library Routines

三)服务的启动类型,如Start为3表示按需启动,scm在基础篇四已经有所论述了。


启动类型

注释

SERVICE_AUTO_START
0x00000002

A service started automatically by the service control manager(scm) during system startup. For more information, seeAutomatically Starting Services.

SERVICE_BOOT_START
0x00000000

A device driver started by the system loader. This value is valid only for driver services.

SERVICE_DEMAND_START
0x00000003

A service started by the service control manager when a process calls the StartService function. For more information, see Starting Services on Demand.

SERVICE_DISABLED
0x00000004

A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED.

SERVICE_SYSTEM_START
0x00000001

A device driver started by the IoInitSystem function. This value is valid only for driver services.

四)Chap6\pnpevent示例驱动为例的具体键值,硬件键的命名问题详看资料1第十二章。

三、内核模式下注册表的访问

请先阅读资料1第三章五小节,本小节仅仅补充两个有删节的示例。
一)IoOpenDeviceRegistryKey与ZwSetValueKey的示例:

IoOpenDeviceRegistryKey的参数DevInstKeyType指示打开哪个注册表键,Msdn里的说明有些让人困惑。实践上的结果为:PLUGPLAY_REGKEY_DEVICE(打开硬件键的Device Parameters子键)、PLUGPLAY_REGKEY_DRIVER(打开类键)、PLUGPLAY_REGKEY_CURRENT_HWPROFILE(打开配置文件键)。

二)RtlQueryRegistryValues与RtlWriteRegistryValue的示例:

NTSTATUS SerialGetConfigDefaults(
IN PSERIAL_FIRMWARE_DATA DriverDefaultsPtr,
IN PUNICODE_STRING RegistryPath // 服务键
)
{
NTSTATUS Status = STATUS_SUCCESS; // return value

//
// We use this to query into the registry for defaults
//


RTL_QUERY_REGISTRY_TABLE paramTable[9]; // 注册表查询用到8个值,最后一个为0表示结束

PWCHAR path;
ULONG zero = 0;
ULONG DbgDefault = 0;//SER_DBG_DEFAULT;
ULONG DetectDefault = 0;
ULONG notThereDefault = SERIAL_UNINITIALIZED_DEFAULT;

PAGED_CODE();

//
// Since the registry path parameter is a "counted" UNICODE string, it
// might not be zero terminated. For a very short time allocate memory
// to hold the registry path zero terminated so that we can use it to
// delve into the registry.
//
// NOTE NOTE!!!! This is not an architected way of breaking into
// a driver. It happens to work for this driver because the author
// likes to do things this way.
//


path = ExAllocatePool (PagedPool, RegistryPath->Length+sizeof(WCHAR));

if (!path) {
Status = STATUS_INSUFFICIENT_RESOURCES;
return (Status);
}

RtlZeroMemory (DriverDefaultsPtr, sizeof(SERIAL_FIRMWARE_DATA));
RtlZeroMemory (?mTable[0], sizeof(paramTable));
RtlZeroMemory (path, RegistryPath->Length+sizeof(WCHAR));
RtlMoveMemory (path, RegistryPath->Buffer, RegistryPath->Length);

paramTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[0].Name = L"BreakOnEntry";
paramTable[0].EntryContext = &DriverDefaultsPtr->ShouldBreakOnEntry; // 保存查询结果
paramTable[0].DefaultType = REG_DWORD;
paramTable[0].DefaultData = &zero;
paramTable[0].DefaultLength = sizeof(ULONG);
。。。
paramTable[7].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[7].Name = L"UartRemovalDetect";
paramTable[7].EntryContext = &DriverDefaultsPtr->UartRemovalDetect; // 保存查询结果
paramTable[7].DefaultType = REG_DWORD;
paramTable[7].DefaultData = &DetectDefault;
paramTable[7].DefaultLength = sizeof(ULONG);

Status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
path, // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Serial
?mTable[0], // 以全0的结尾表结束,这样一次就可以查询多个值了
NULL,
NULL);
if (!NT_SUCCESS(Status)) {
DriverDefaultsPtr->ShouldBreakOnEntry = 0;
DriverDefaultsPtr->DebugLevel = 0;
DriverDefaultsPtr->UartRemovalDetect = 0;
}

//
// Check to see if there was a forcefifo or an rxfifo size.
// If there isn't then write out values so that they could
// be adjusted later.
//


if (DriverDefaultsPtr->ForceFifoEnableDefault == notThereDefault) {

DriverDefaultsPtr->ForceFifoEnableDefault = SERIAL_FORCE_FIFO_DEFAULT;
// 本文转自 C++Builder研究 - http://www.ccrun.com/article.asp?i=1058&d=i35urg
RtlWriteRegistryValue(
RTL_REGISTRY_ABSOLUTE,
path,
L"ForceFifoEnable",
REG_DWORD,
&DriverDefaultsPtr->ForceFifoEnableDefault,
sizeof(ULONG)
);
}
。。。
//
// We don't need that path anymore.
//


if (path) {
ExFreePool(path);
}

//
// Set the defaults for other values
//

DriverDefaultsPtr->PermitSystemWideShare = FALSE;

return (Status);
}

四、设备接口

设备创建类( Device Setup Classes )和设备接口类( Device Interface Classes )是驱动编程中经常碰到的两类键。设备创建类区分了驱动的类别,它的标准类定义于devguid.h,我们将在下一节详述。Winodow2000以前的驱动需要自定义设备对象的名字,同时提供和该名字关联的符号链接(如C:,COM1)以使应用程序可以访问该设备,使用了设备接口类可以避免这种显式的命名。它在接口类键下创建了设备实例子键(##?…),子键下产生了符号链接名(#{…)。同设备创建类一样,也存在预定义的接口类,如usbiodef.h定义了GUID_DEVINTERFACE_USB_HUB,mountmgr.h定义了MOUNTDEV_MOUNTED_DEVICE_GUID。




* 键下存放相应内容的值,请自行查看注册表。

调用IoRegisterDeviceInterface函数,功能或过滤驱动程序的AddDevice函数可以注册一个或多个设备接口,SymbolicLinkName参数返回了如\??\Root#SYSTEM#0000#{2eb07ea0-7e70-11d0-a5d6-28db04c10000}\{8c07dd50-7a8d-11d2-8f8c-00c04fbf8fef}&dmusic的符号链接串,将来它可以被其他组件用来打开设备句柄。
设备接口在注册后需调用IoSetDeviceInterfaceState函数开放接口,并等到驱动完成IRP_MN_START_DEVICE后,其他组件才可以访问该设备。I/O管理器将查询注册表,如果Linked值为0 且引用计数ReferenceCount值不大于0,说明还未有设备引用该接口,它将创建上述符号链接,否则只是简单的增加引用计数。以后,驱动程序会执行一个功能相反的调用禁止该接口(用FALSE做参数调用IoSetDeviceInterfaceState)。最后,I/O管理器删除符号连接对象,但它保留了注册表项,即这个名字将总与设备的这个实例关联;但符号连接对象与硬件一同到来或消失。注意,该符号链接是创建于该设备的PDO上的,所以PDO的安全描述符将最终控制设备的访问权限,并非应用程序获得该符号链接就一定获准访问该设备。
示例代码Chap6\pnpevent\sys创建了两个设备接口,其中一个是由Generic.sys的InitializeGenericExtension 调用RegisterInterface(pdx, &GUID_GENERIC_POWER)注册了一个电源管理接口,符号链接是\??\ROOT#SAMPLE#0001#{894a7461-a033-11d2-821e-444553540000},另一个就是GUID_INTERFACE_PNPEVENT,符号链接为??\Root#SAMPLE#0000#{6a061783-e697-11d2-81b5-00c04fa330a6}。
在用户模式下,设备接口也有着重要的应用,为此系统提供了SetupDixxx函数——它是设备安装函数集(Device Installation Functions)的一部分——简化了设备接口的使用。为了解该部分内容,请先阅读资料1第二章三小节及示例代码Chap2\InterfaceEnum。

  • 枚举设备接口

根据示例Chap2\InterfaceEnum,我们可以总结出枚举设备接口的一个可行步骤:

函数

注释

1

SetupDiGetClassDevs

returns a device information set that contains all devices of a specified class. 返回设备集

2

SetupDiEnumDeviceInterfaces

returns a context structure for a device interface element of a device information set. Each call returns information about one device interface; the function can be called repeatedly to get information about several interfaces exposed by one or more devices. 返回“设备接口数据”结构

3

SetupDiGetDeviceInterfaceDetail

returns details about a particular device interface. 一般分两次调用来获得“设备信息数据”

SetupDiOpenDeviceInterfaceRegKey*

opens the registry subkey that is used by applications and drivers to store information specific to a device interface instance and returns a handle to the key. 设备接口类下的符号链接下的Device Parameters子键

4

SetupDiGetDeviceRegistryProperty

retrieves the specified Plug and Play device property. 设备在硬件键的以及相关的属性(如枚举器等)

5

通过设备信息集完成你感兴趣的工作

注:关键的三步:SetupDiGetClassDevs -> SetupDiEnumDeviceInterfaces -> SetupDiGetDeviceInterfaceDetail

* 设备硬件键下的Device Parameters可以用SetupDiOpenDevRegKey来访问

此外可能用到函数还有SetupDiCreateDeviceInfoList、SetupDiDestroyDeviceInfoList、SetupDiCreateDeviceInterface 、SetupDiOpenDeviceInterface等。
示例通过两重循环枚举了系统中设备接口类包含的所有设备,输出结果为:

{2accfe60-c130-11d2-b082-00a0c91efb8b} (StoragePortClassGuid):
AH4453JX IDE Controller
Win XP Promise FastTrak TX4000/S150 TX Series (tm) Controller

如果掌握了设备接口的基础知识,SetupDixxx函数并不显得难以使用,但我们希望大家不要就此满足,对于SetupDixxx涉及到的设备信息集(Device Information Sets),我们有必要了解得更深入一些。
?

Msdn对SetupDixxx函数的描述里,多次提及了设备信息集,但是没有明确说明它的具体结构,尽管我们不难猜测到是相当复杂的。从原理上说,设备信息集可看作是设备信息元组成的链表,可以用SetupDiGetClassDevs(...DIGCF_INTERFACEDEVICE...)来获取符合定义条件的设备信息集。每个信息元代表着一个设备,包含了设备实例的句柄(Devnode,PnP管理器为了管理系统里所有的设备,使用它构造了分层的设备树)和关联该设备的设备接口链表。
当调用SetupDiEnumDeviceInterfaces来枚举这个设备信息集包含的设备接口时,如果DeviceInfoData参数为NULL,将会遍历所有的信息元。对于每个信息元,都将发生如下的操作:从本信息元的设备接口链表中筛选出满足InterfaceClassGuid参数的节点组成临时链表B,如果MemberIndex参数小于B的节点总数,则说明满足条件的节点就在B中,从中取出返回即可。否则MemberIndex = MemberIndex – B节点总数,继续遍历下一个信息元。举例来说,MemberIndex = 2(C++一般以0为起点,因此实际上是查找第3个),信息元1的临时链表有2个节点,信息元2的临时链表有3个节点,则我们可以在信息元2找到该节点。
如果DeviceInfoData不为空,则根据它定位到合适的信息元,从中取出满足MemberIndex要求的设备接口。

  • 枚举设备

既然知道了如何枚举设备接口,枚举设备就是一件轻而易举的事情了。利用先前获得的设备信息集,使用SetupDiEnumDeviceInfo函数来遍历每一个设备。

  • 本站的《用SetupAPI结合注册表获取USB优盘序列号》

因为该文缺少原理上的说明,所以我们将之作为设备接口类的一个例子来学习。mountmgr.h里有如下一段话:

//
// Devices that wish to be mounted should report this GUID in
// IoRegisterDeviceInterface.
//
DEFINE_GUID(MOUNTDEV_MOUNTED_DEVICE_GUID, 0x53f5630d, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);

意即需要绑定的设备都应该使用这个GUID来注册设备接口,U盘当然也不例外,因此代码中调用SetupDiGetClassDevs建立了设备信息集后,可以使用这个GUID来枚举设备。枚举循环中,SetupDiGetDeviceInterfaceDetail获取设备路径,构造出该设备的硬件键,用硬件键下的“ParentIdPrefix”值,与最初取得的HKLM\SYSTEM\MountedDevices键下的“\DosDevices\U盘符:”值比较,如果相等,则说明此设备就是想找的U盘。

1. LpUSBKeyData-> \??\STORAGE#RemovableMedia#7&1633246c&0&RM#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}
2. 截取后的LpUSBKeyData-> 7&1633246c&0
3.设备路径-> \\?\usbstor#disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
4.截取后的设备路径-> usbstor#disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0
5.硬件键-> SYSTEM\CurrentControlSet\Enum\usbstor\disk&ven_kingston&prod_datatraveler_2.0&rev_pmap\5b8504002c22&0
6.硬件键下的ParentIdPrefix-> 7&1633246c&0
7.lpPathTemp-> disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
8.lpPos1-> 5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
9.lpPos2-> #{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
10.截取后的lpPathTemp-> 5b8504002c22&0
11.ID-> 5B8504002C22

ID号藏于何处呢,原来包含在设备路径(来自SP_DEVICE_INTERFACE_DETAIL_DATA结构)里。第7步起即为从设备路径中取出ID的字符串处理过程。
这个例子之所以有价值,是因为它用设备路径构造出了硬件键——将设备路径截头去尾,替换掉#,再连上硬件键的固有开始部分。设备路径代表了什么呢,原来是一个符号链接:

接下来思考一下能否改进这个程序。大家可以看到,获取了设备路径后,不再使用SetupDixxx,操作从流水线转换到了手工作坊,代码也开始变得复杂起来,放弃的原因可能是SetupDiGetDeviceRegistryProperty无法获取ParentIdPrefix,而在用户模式下似乎也没有其他更好的标志了。有兴趣的读者可以资料6、10为线索,寻找是否存在更有效的方法。

五、Inf文件

资料1第十二章已经清晰的描述了Inf文件,笔者仅仅打算叙述一下测试示例驱动时遇到的问题。比较有意思的一个是,用DriverStudio的EzDriverInstaller安装Inf,提示“…Inf找不到 [ClassInstall32] 段…”,这是因为缺少必需的设备创建类,虽然我们可以在Inf里补全该段,但既然示例驱动都使用了这个设备创建类,我们也可以编写一个文本格式的注册表文件setup.reg注册一下,内容为:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{894A7460-A033-11d2-821E-444553540000}]
@="WDM Book Samples"
"Class"="Sample"
"EnumPropPages32"="samclass.dll"
"Icon"="-5"

导入注册表后,再将samclass.dll复制到system32目录下即可。
另外一个问题是使用Inf安装了驱动后,运行测试程序会提示驱动启动失败,原因是示例驱动使用了generic.sys,将它复制到system2\driver下即可。
在前面的章节,我们已经接触了sources、MOF,此次涉及了Inf,虽然语法各异,但对于我们来说,应该明确的是它们的出现是为了完整的表达自身模型的需要。对于资料1未详述的问题,可以查阅Msdn,如需要了解Inf中某节xxx的语法格式,可以输入Inf xxx来查询,又如文件复制、注册表配置的具体路径也可以查阅Msdn获知,再如Inf经常出现的点式名字,如DriverInstall.NT,DriverInstall.NT.Services,DriverInstall.nt.hw等,它们的区别在Msdn中也可以找到。
留给初学者的作业是完全理解显卡的Inf文件,找出显卡支持的分辨率声明位置,并尝试使用DDK的GenInf编写一个简单的Inf。

六、结语

注册表的许多操作都以API的形式被固定下来,虽然我们没有必要了解它们的内部实现,但有些特别重要的键仍然值得我们加以关注。Inf为驱动程序的正确安装与工作提供了很多有用的信息。硬件键名的形式,取决于不同类别设备的标识符的定义方式。
现在我们仍然不知道,在文件系统驱动加载之前,能否在内核模式下正常的访问注册表,你能够给出正确的答案来吗?
本篇的参考完成时间为不超过三星期。

你可能感兴趣的:(驱动(内核)编程)