这个清单提供编码和安装注意事项的简要列表,主要针对正在创建将在 64 位版本 Windows 操作系统上运行的驱动程序开发人员。
此信息适用于以下操作系统:
Microsoft Windows Vista
Microsoft Windows Server 2008
64 位版本的 Microsoft Windows Server 2003
Microsoft Windows XP
| 64 位 Windows 驱动程序入门 | |
| 64 位 Windows 编程模型 | |
| 编码指南和移植问题 | |
| 64 位 Windows 的驱动程序安装 | |
| 开发 64 位驱动程序的参考资料 |
预期会有越来越多的领域采用 64 位系统,特别是在企业应用中。随着广泛的设备覆盖和针对 64 位系统的高质量驱动程序变得可用,这种应用将增长得更快。
设备支持 64 位系统的条件是:
| • | 确保设备寻址达到 64 位物理内存。 | ||||||
| • | 使用 64 位安全编程实践编写 Windows® 驱动程序,并使用 64 位编译器找出问题所属的领域:
|
注意,不支持 32 位驱动程序:
| • | 内核模式驱动程序运行在与 64 位 Windows 相同的地址空间中。 |
| • | 适应大型系统的驱动程序可以为分页/未分页池和系统缓存使用较大的地址空间。 |
Windows 64 位版本由 Windows 编程模型和 Microsoft Win32® API 发展而来。Windows 32 位和 64 位平台基于相同的源代码:
| • | 现有的应用程序可以扩展以满足企业需求。 |
| • | 新的设计可以使用巨大的地址空间和内存。 |
| • | 支持现有的 32 位应用程序。 |
此外:
| • | 64 位 Windows 编程模型使用相同的 Win32 DDI 和 API。 |
| • | 在 LLP64(LongLong 和指针)数据模型中,只有指针扩展到 64 位。所有其他的基本数据类型(整型和长整型)的长度保持在 32 位。虽然 64 位指针需要适应具有高达 16 TB 虚拟内存的系统,但是大部分数据仍然在 32 位整数范围内。对于大部分应用程序,将默认整型大小改成 64 位只会浪费空间。 |
| • | 64 位 Windows 编程模型添加大小明确的新数据类型,以及匹配指针精度的新整数类型。类型列表请参见“数据类型”表。 |
| • | 指针长度为 64 位,但是整型和长整型数据类型仍然是 32 位。 |
| • | 大部分内存分配都是 64 位。 |
| • | CPU 掩码扩展到 64 位。 |
| • | I/O 请求和 Unicode 字符串的长度仍然是 32 位。 |
数据类型
| 类型名称 | 类型含义 |
固定宽度数据类型 | |
LONG32、INT32 | 32 位有符号 |
LONG64、INT64 | 64 位有符号 |
ULONG32、UINT32、DWORD32 | 32 位无符号 |
ULONG64、UINT64、DWORD64 | 64 位无符号 |
指针精度数据类型 | |
INT_PTR、LONG_PTR | 有符号整型、 |
UINT_PTR、 DWORD_PTR | 无符号整型、 |
SIZE_T | 无符号计数、指针精度 |
SSIZE_T | 有符号计数、 |
内存布局
| 内存 | 基于 x64 | 基于 Intel Itanium |
用户地址范围 | 0x10000 – 0x000007FFFFFEFFFF | 0x10000 – 0x000006FBFFFEFFFF |
系统缓存 | 1 TB | 1 TB |
超出空间 | 512 GB | 16 GB |
系统地址空间(用于内核线程等) | 128 GB | 128 GB |
页大小 | 4K | 8K |
分页的池 | 128 GB | 128 GB |
未分页的池 | 在 Windows Vista 和早期系统上占物理内存的 40%,最高达 128 GB; | 在 Windows Vista 和早期系统上占物理内存的 40%,最高达 128 GB; |
物理内存地址 | 64 位 | 64 位 |
编码指南
| • | 使用 Windows 64 位和 32 位安全数据类型。 |
| • | 检查所有指针使用,特别是指针运算。 |
| • | 删除内联汇编代码(使用内部或本机汇编代码)。 |
| • | 对于特定于 x64 的代码,使用: |
| • | 对于特定于 Itanium 代码,使用: |
驱动程序的移植问题
| • | 驱动程序应该支持 IOCTL 命令的 32 位和 64 位版本。 | ||||||||
| • | 应该为只能寻址 32 位物理地址的硬件实现 DMA 支持。 | ||||||||
| • | 指针、多态使用和对齐问题适用于所有驱动程序。 | ||||||||
| • | 如有可能,所有驱动程序都应该通过使用 WDF 正确地支持即插即用和电源管理。
| ||||||||
| • | 对于 64 位微型端口:
|
构建环境和工具
| • | 用于 x64 的构建环境是 AMD64。 | ||||||||
| • | 64 位工具和开发过程与 32 位类似:
|
BIOS、ACPI 和补丁注意事项
| • | 基于 Itanium 的系统必须支持 ACPI 2.0 64 位表。 | ||||||||||
| • | 64 位 Windows 支持 GUID 分区表(GPT)磁盘。 | ||||||||||
| • | x64 系统上不允许 BIOS 回调。 | ||||||||||
| • | 要与基于 x64 的 Windows 操作系统兼容,驱动程序必须避免以下实践:
Windows 在 x64 平台上强制执行这些规则。任何这种修改都会导致错误检测。 |
IOCTL 支持
包含依赖于指针的类型的 IOCTL 结构在 64 位和 32 位应用程序中具有不同的大小。驱动程序在从 32 位线程发出的 IOCTL 和从 64 位线程发出的 IOCTL 之间必须有所不同。驱动程序基于发出方的位宽验证输入和输出缓冲区的长度。
驱动程序可以使用三种可能的解决方案中的任意一种来同时支持 32 位和 64 位调用者:
| • | 避免在 IOCTL 结构中使用指针类型。 |
| • | 使用 IoIs32bitProcess() API。 |
| • | 在 IOCTL 代码的 Function 代码字段中为 64 位调用者定义一个位。 |
要使用 IoIs32BitProcess(),在代码中定义一个 32 位 IOCTL 结构,然后确定发出进程是 32 位还是 64 位。
头文件中的 IOCTL 结构:
typedef struct _IOCTL_PARAMETERS { PVOID Addr; SIZE_T Length; HANDLE Handle; } IOCTL_PARAMETERS, *PIOCTL_PARAMETERS;
32 位 IOCTL 结构:
// // This structure is defined // inside the driver source code // typedef struct _IOCTL_PARAMETERS_32 { VOID*POINTER_32 Addr; INT32 Length; VOID*POINTER_32 Handle; } IOCTL_PARAMETERS_32, *PIOCTL_PARAMETERS_32;
使用 IoIs32BitProcess 的 32 位和 64 位 IOCTL 例子:
#ifdef _WIN64 case IOCTL_REGISTER:if (IoIs32bitProcess(Irp)) { /* If this is a 32 bit process */ params32 =
(PIOCTL_PARAMETERS_32)(Irp>AssociatedIrp.SystemBuffer); if(irpSp->Parameters.DeviceIoControl.InputBufferLength <
sizeof(IOCTL_PARAMETERS_32)) { status = STATUS_INVALID_PARAMETER; } else { LocalParam.Addr = params32->Addr; LocalParam.Handle = params32->Handle; LocalParam.Length = params32->Length; /* Handle the ioctl here */ status = STATUS_SUCCESS; Irp->IoStatus.Information = sizeof(IOCTL_PARAMETERS); } } else { /* 64bit process IOCTL */ params = (PIOCTL_PARAMETERS) (Irp->AssociatedIrp.SystemBuffer); if (irpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(IOCTL_PARAMETERS)) { status = STATUS_INVALID_PARAMETER;
} else { RtlCopyMemory(&LocalParam, params,
sizeof(IOCTL_PARAMETERS)); /* Handle the ioctl here */ status = STATUS_SUCCESS; } Irp->IoStatus.Information = sizeof(IOCTL_PARAMETERS); } break;
如果选择为 64 位调用者定义一个位,那么使用 Function 字段中的高位。
当前 IOCTL 码有 5 个字段,包括一个 11 位的 Function 字段:
Device Type (16) | Access (2) | Custom (1) | Function (11) | Method (2) |
新的 IOCTL 码使用 Function 字段中的高位来指示 64 位调用者:
Device Type (16) | Access (2) | Custom (1) | 64Bit (1) | Function (10) | Method (2) |
DMA 支持
| • | 使用 PHYSICAL_ADDRESS 类型定义来访问物理地址。PHYSICAL_ADDRESS 为 64 位长。 |
| • | 将所有 64 位视为一个有效的物理地址。不要忽略高端的 32 位。 |
| • | 使用内核模式驱动程序框架(KMDF)或 Windows DMA DDI 来确保在所有平台上正确运行。当使用 KMDF 或 Windows DMA 例程时,Windows 处理大小问题。 |
| • | 在合适的时候使用分散/聚集 DMA。 |
| • | 通过使用具备 64 位寻址能力的设备极大地提高性能。 |
指针、多态使用和对齐问题
指针大小在应用程序和驱动程序中一样。用于移植应用程序的指南在下列情况中也适用于驱动程序:
| • | 多态数据使用(与指针大小相关)。 |
| • | 对齐问题。 |
| • | 常量和宏运算。 |
本节讨论前两种情况。第三种情况在 MSDN® 文章“超越 Windows XP:现在准备好迎接 64 位版本 Windows 的到来”中讨论。
多态数据使用
| • | 不要将指针强制转换成 int、long、ULONG 或 DWORD。 使用 UINT_PTR、INT_PTR、ULONG_PTR 等。 例如: ImageBase = (PVOID) ((ULONG)ImageBase | 1); 应该改写成: ImageBase = (PVOID) ((ULONG_PTR)ImageBase | 1); | ||||
| • | 使用 PtrToUlong() 和 PtrToLong() 截断指针:
| ||||
| • | 当调用具有指针 OUT 参数的函数时,应小心地使用正确的大小: void GetBufferAddress(OUT PULONG *ptr); { *ptr=0x1000100010001000; } void foo() { ULONG bufAddress; // // this call causes memory corruption // GetBufferAddress((PULONG*)&bufAddress); } |
对齐问题
基于 Itanium 的系统需要自然对齐内存引用(也就是说,以 4 字节为边界访问 32 位)。错误对齐的内存引用在基于 Itanium 的系统上引发一个异常并对系统进行错误检测。
虽然 x64 更宽容,但要获取更好的性能,也需要对齐。
| • | 使用结构封装的指令时,应意识到对齐问题。 |
| • | 如果在单个头文件中使用不同的封装级别,应该格外小心。 |
| • | 为了获得最佳的结果,将 64 位值和指针放在结构的开始处。 |
| • | RtlCopyMemory() 和 memcpy() 不会发生错误。 |
| • | 使用 UNALIGNED 宏修复对齐问题: #pragma pack (1) /* also set by /Zp switch */ struct AlignSample { ULONG size; void *ptr; }; struct AlignSample s; void foo(void *p) { *p = p; // 导致对齐错误 ...} foo((PVOID)&s.ptr); void foo(void *p) { struct AlignSample s; *(UNALIGNED void *)&s.ptr = p; } |
有关对齐的更多信息,请参阅“ 内存管理:每个驱动程序作者都需要知道的技巧。”
更多编码问题
| • | 仔细地检查:
| ||||||||
| • | 截断执行控制代码为 32 位。 | ||||||||
| • | 使用 piecemeal-size 分配: struct foo { DWORD NumberOfPointers; PVOID Pointers[1]; } xx; Wrong:malloc(sizeof(DWORD)+100*sizeof(PVOID)); Correct:malloc(FIELD_OFFSET(struct foo,Pointers) +100*sizeof(PVOID)); | ||||||||
| • | 小心使用十六进制常量和无符号值。 | ||||||||
| • | 当您使用无符号数作为下标时,应小心操作:
|
其他注意事项
| • | -1 != 0xFFFFFFFF 0xFFFFFFFF != 无效句柄 |
| • | DWORD 始终是 32 位。不要使用 DWORD 变量来存储指针或句柄。 |
| • | 将指针强制转换成 PCHAR 用于指针运算: ptr = (PVOID)((PCHAR)ptr + pageSize); |
| • | 在调试语句中使用 %I 打印指针。 |
| • | 大于等于 0x80000000 的地址不一定是内核地址。 |
64 位驱动程序 INF 需求
Windows Server 2003 SP1 和更高的 Windows 版本不会在基于 x64 的系统上安装带有未修饰 INF 节的驱动程序包。
为了与 Intel Itanium 系统兼容,Windows Server 2003 SP1 将安装带有未修饰 INF 节的驱动程序包。但是,Windows 硬件徽标计划需要 INF 修饰,所以带有未修饰 INF 节的驱动程序包无法满足徽标的资格要求。
INF 的 [Manufacturer] 节条目和 [Models] 节名称必须针对将要安装在基于 x64 的系统上的驱动程序包添加修饰,而且应该针对基于 Intel Itanium 的系统添加修饰。原始发布版 Windows XP 之后的所有 Windows 版本都支持这些修饰;因此,通过这种方式修饰的非 x86 系统 INF 将适用于所有已发布的基于 NT 的 Windows 操作系统。
目标是防止安装错误的驱动程序二进制文件,因为客户不了解平台之间的差别。
| • | 使用 Windows XP 中引入的 TargetOsVersion。 |
| • | 在 Windows Server 2003 SP1 和以后的版本中,必须为 64 位平台修饰 [Manufacturer] 和 [Models] INF 节。例如: [Manufacturer] %mycompany% = MyCompanyModels, NTamd64 [MyCompanyModels.NTamd64] %MyDev% = mydevInstall, mydevHwid |
32 位和 64 位平台的驱动程序安装
为了简化从 32 位安装程序安装 64 位驱动程序的过程,Microsoft 在 WDK 中提供 Driver Install Frameworks (DIFx) 工具的 64 位版本。DIFx 工具包括:
| • | Driver Package Installer (DPInst)。 |
| • | Driver Installation Frameworks for Applications (DIFxApp)。 |
无需为 64 位平台编写单独的 64 位安装程序,可以从单个 32 位安装程序启动 DPInst 或 DIFxApp 的正确版本。
无法使用 32 位驱动程序安装包直接安装 64 位驱动程序,但是可以使用它们确定当前正在运行哪个体系结构,然后启动合适的安装程序。在 64 位平台上,32 位安装程序在 WOW64 下运行,WOW64 就是让基于 Win32 的应用程序在 64 位 Windows 上运行的 x64 模拟器。安装程序必须能够检测其运行的平台,从而能够为该平台安装正确的驱动程序包。
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • |