[转] 将程序移植到64位Windows

发布时间:2010-2-11 15:31
分类名称:windows


简介:本文对如何将32Windows程序平滑的支持和过渡到64Windows操作系统做出了一个简单而系统的介绍。包括对于64位操作系统的版本,编程模型,一些移植原则甚至包括对驱动程序的移植原则的介绍。

作者介绍:系统分析员,现在北京工作。作者的bloghttp://goooder.blogchina.com.

前言:

或许大家还对32位的CPU及操作系统使用还是觉得非常的合乎日常需要,但Windows 64位已经悄悄的来到了各位的身边。不少软件厂商也纷纷宣称自己的软件已经支持64位操作系统了。

笔者在几个月之前就成功的实施了一个软件的64位操作系统移植。大致方案是,上层应用程序仍然使用32位程序,但将该系统软件的驱动程序统统的变成了64位。这也是让应用程序支持64位的最小代价。整个过程前后不到一个月(两个人月),就解决了大部分问题。而一些难啃的骨头都是因为以前的编码不太规范而引起的。因为这不是本文的主题,所以就此打住。

下面的内容就是我在工作过程中整理各种资料所得到的成果。为了文章的系统性,中间转贴了一些MSDN中文网站的内容。希望能对大家的工作有所帮助。

.最有可能受益于64位的应用

l 需要大量的可寻址内存,因而系统总体内存需求超过4GB 的应用。例如那些采用大型数据集的应用(金融和科学建模软件)和基于主机的桌面应用(在不降低性能的情况下同时运行多个线程);

l 必须同时管理大量的用户或者应用线程,例如大规模的瘦客户端解决方案,大型数据库,以及用于客户关系管理(CRM)、供应链管理(SCM)、企业资源规划(ERP)和数字版权管理(DRM)系统中的解决方案的数据仓库应用;

l 需要通过实时加密和解密提高安全性的应用,包括电子商务应用和对专用或者分类数据的保护;

l 需要数学精度和浮点性能的应用,包括建模、模拟、统计和金融分析、图像/视频/信号处理、物理、医学研究、远程通信、加密和压缩;

l 需要大规模的、强大的数据库性能的应用,包括决策支持,搜索和索引,文档和内容管理,以及语音识别;

l 需要64 位计算的大内存寻址功能的应用,包括很多高性能计算(HPC)群集应用;

l 需要提供数字内容创建功能,例如计算机辅助设计、制造和工程(CADCAM CAE)、数字音乐制作和视频编辑,以及实时媒体流解决方案;

l 需要通过最大限度的性能实现逼真的影院级消费者体验,包括计算机游戏、数字视频和协作;

l 需要将以前只限于64 位工作站的功能移植到企业、消费者和计算机爱好者的台式机中,包括3D 建模、渲染、动画、模拟和软件开发。

二.Windows XP64位版分类

微软在2003328日发布了64位的Windows XP64位的Windows XPWindows XP 64-Bit Edition。其实就是64位版本的Windows XP Professional。根据不同的微处理器架构,它分为两个不同版本:

1.IA-64版的Windows XP

针对英特尔(Intel)IA-64架构的安腾2(Itanium2)64位微处理器的Windows XP 64-Bit Edition Version 2003 for Itanium-based Systems。它是拥有64位寻址能力的强大的操作系统,主要面向顶级的高端IA-64架构的工作站,用在高端的科学运算,石油探测工艺,立体绘图,复杂的动画制作等等,是一种用在高效能运算(High Performance Computing)的强大的操作系统。估计它可能会改名为Windows XP Professional Itanium-based Edition。支持双处理器;最低支持1GB的内存,最高支持16GB的内存。

2.x64版的Windows XP

针对超微(AMD)x64架构的(Opteron)与速龙64(Athlon64)所属的64位扩展微处理器的Windows XP 64-Bit Edition for 64-Bit Extended Systems。由于英特尔也发布了x64架构的Intel EM64T技术的至强(Xeon)与奔腾4(Pentium 4)64位扩展微处理器,故微软将该版本的Windows XP 64-Bit Edition改为Windows XP Professional x64 Edition,它支持AMDIntelx64架构。可以使用在一般x64架构的工作站,桌面电脑以及笔记本电脑,用途与32Windows XP Professional一样,但具有64位寻址能力。支持双处理器;最低支持256MB的内存,最高支持16GB的内存。

三.x64版的Windows操作系统的设计目标

x64版有5大特征,即:

1. 同时轻松支持32Win32程序及64位程序;

2. 64位运行的程序代码和32位运行的程序应该是同一份代码;

3. 使现有程序具有企业级应用性能;(Enable existing applications to scale to enterprise capacities

4. 支持新的设计使之可以利用巨大地址空间及内存空间;

5. 支持32位既有程序。

x64 位平台并没有出现 Win64 API,它仍然是熟悉的 Win32 API(现在更合适的名称为 Windows API)。但它出现了一些新的兼容 64 位的数据类型,所以可能需要对代码进行少量的更改。这就意味着开发者可以从单个代码库构建代码的 32 位和 64 位版本,减少了由于维护两个代码库所带来的维护开销。

但是,在x64中,Microsoft 已经去除了一些旧的组件,如 Win16 子系统。所以Windows 64 位代码不支持16 位的Windows程序。也不支持 POSIX OS/2 子系统。又出现了一个新的子系统,称为 WOW64

四.x64新出现的子系统―WOW64

WOW64 Windows-32-on-Windows-64 的缩写。它为现有的 32 位应用程序提供了 32 位的模拟环境,可以使大多数 32 位应用程序在无需修改而直接运行在 Windows 64 位版本上。它类似于旧的 WOW32 子系统,负责在 Windows 32 位版本下运行 16 位的代码。

[转] 将程序移植到64位Windows - Dsliu - Dspace

尽管x64 CPU本身具有 32 位兼容性模式,可以处理 IA-32 指令的实际执行,但WOW 层仍然必不可少。WOW子系统负责诸如在32位和64位模式之间进程切换以及模拟32位系统的服务。例如,32位和64 位程序具有不同的注册表配置单元,还有一个用于 32 位二进制文件的不同的系统目录,而且64位二进制文件仍然使用 System32 目录。因此,当 32 位应用程序安装到系统中时,WOW层会确保将32位二进制文件置于一个新的目录 SysWOW64中。这是通过如下方式实现的:根据应用程序是否运行在 WOW 下,截获对 API 的调用(如 GetSystemDirectory)并返回适当的目录。类似的问题可能会存在于注册表中。因为 32 位和 64 位的 COM 服务器都可以安装在系统上,并位于相同的类标识符 (CLSID) 下,因此 WOW 层需要将对注册表的调用重定向到适当的32位或64位配置单元中。WOW 层也会处理注册表中某些区域之间的镜像更改,以便使其更简单地支持32位和64位代码之间的交互操作。

WOW64 非常重要,因为当不关注性能和伸缩性的问题时,它使开发者可以利用大多数现有的32位代码。它是两种方法的最佳结合。开发者可以将应用程序服务迁移到64位,同时将 Microsoft 管理控制台 (MMC) 配置管理单元保留为32位。Windows 64 位版本包括 MMC32位和64位的版本。当选择保留管理工具为32位时,进程间的通讯可能会遇到某些问题,但是只要接口设计正确,诸如远程过程调用 (RPC) 的协议应该可以在32位和64位进程之间运行。有关 WOW64 的另外一点需要牢记:它并不是为要求高性能的应用程序而设计的。至少,WOW64子系统需要将32位参数扩展到64位,并且需要将64位的返回值截断为32位。在最糟糕的情况下,WOW64 子系统将需要进行内核调用,涉及到的不仅仅是到内核的转换,还有从处理器的32位兼容性模式到其本机64位模式的转换。在 WOW64 下运行时,应用程序将无法妥当地进行调整。对于那些要将其保留为32位的应用程序而言,必须在WOW64下测试它们。如果性能不能满足期望,则需要考虑将应用程序迁移到64位。

WOW64是在用户模式下实现的,作为ntdll.dll和内核之间的层。WOW64及其支持的一些 DLL仅仅是可以加载到32位进程中的64位的 DLL。对于所有其他情况,进程保持为纯进程。32位的进程无法加载64位的DLL,反之亦然。所以,请检查代码中的所有LoadLibrary调用是否有效。

有关 WOW64 的详细信息,请参阅 Microsoft_ Platform SDK 中的“64-bit Windows Programming - Running 32-bit Applications”

五.64windows内存地址空间映射

默认情况下,Windows 32位版本的地址空间限制在4GB,其中一半是为内核保留的。这限制了普通的应用程序只能使用2GB的有效虚拟内存。2 GB看起来好像很多,但是由于错误的分配算法、大型文件映射甚至过多的使用 DLL,地址空间很容易在应用程序中变得零碎。看一下任务管理器中的“VM Size”列,就会发现普通应用程序消耗的虚拟内存量。当然,就想过去的 DOS 时期(利用 XMS/EMS)一样,有很多种方法可以使 32 位的应用程序访问多于 4 GB 的物理内存。进入物理地址扩展 (PAE) 和地址窗口扩展(Address Windowing ExtensionsAWE)。PAE 通过将地址位的数量从 32 扩展到 36 来工作,这样使应用程序可以寻址的空间达到 64 GBAWE 使应用程序可以将大于 4 GB 的物理内存范围映射到虚拟地址空间中。这两种方法都引入了开销并增加了代码的复杂性。

Windows 64 位版本提供 16 TB 的 有效寻址空间,其中一半可用于用户模式的应用程序。这意味着整个数据库可以移动到内存中,显著地提高了性能,或者整个网站可以缓存到内存中。它还可以使代 码保留并委托到巨型的邻近虚拟内存块中,无需实际地担心虚拟内存碎片问题。这也考虑到了巨型文件映射对象或共享的内存部分。

下面是64 位体系结构和 32 位体系结构的比较表格:

地址空间

64 Windows

32 Windows

虚拟内存

16 TB

4 GB

页面文件

512 TB

16 TB

页面缓冲池

128 GB

470 MB

非页面缓冲池

128 GB

256 MB

系统缓存

1 TB

1 GB


下面是Windows系列内存地址空间分配比较:

[转] 将程序移植到64位Windows - Dsliu - Dspace

七.将驱动移植到64Windows操作系统

x64位操作系统和x32位操作系统的最大区别就是内存寻址方式的不同。而64位操作系统不支持32位的驱动程序,因为驱动程序和windows内核同处于一个地址空间中。这是移植32位驱动到64位驱动的最大原因。当然,64位驱动程序可以使用更大的分页内存,非分页内存及系统缓存。而且,你的设备从此就支持64windows操作系统了。

1.在X64下的驱动程序安装

除了要把应用程序的32位驱动程序变成64位程序之外,驱动的安装程序和其它配置文件同样需要修改。也就是说,对于要在x64上运行的32位程序,它所依赖的驱动仍然需要是64位的。这些相关程序包括inf文件,device installers, class installersco-installers。相关资料可查看MSDN Libarary DDKPorting Your Driver to 64-Bit Windows

所以,要改造应用程序的安装程序。方法是,让32位版的驱动安装为缺省安装选项,即用户插入安装光盘之后,依然运行32位安装程序。但当程序调用UpdateDriverForPlugAndPlayDevices返回值为ERROR_IN_WOW64时,这说明该安装程序正运行在64Windows环境中。此时,这个安装程序应该调用CreateProcess函数来启动64位的安装进程。这个64位的安装进程通过调用64位驱动目录下的inf文件进行驱动安装。

2.驱动要支持32IOCTL

某些IOCTL可能包含含有指针的结构,所以,要特别小心的区别对待它,必须根据被调用者解析结构或者输出结构。

有三种办法可以解决这个问题:

1. 尽量避免使用IOCTL传递包含有指针的结构;

2. 通过API IoIs32bitProcess()来判断上层调用者的程序类型;

3. 64位程序中采用新的IOCTL命令;

例子:

IOCTL structure in header file

typedef struct _IOCTL_PARAMETERS {

PVOID Addr;

SIZE_T Length;

HANDLE Handle;

} IOCTL_PARAMETERS, *PIOCTL_PARAMETERS;

32-bit IOCTL structure

//

// 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;

32-Bit and 64-Bit IOCTL

#ifdef _WIN64

case IOCTL_REGISTER:

if (IoIs32bitProcess(Irp)) {
/* If this is a 32 bit process */
params32 = (PIOCTL_PARAMETERS_32)(Irp>AssociatedIrp.SystemBuffer);

) {

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 */

} 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;

364-Bit INF 文件要求

Windows Server 2003SP1之后,64位驱动的安装被提高了要求。这可以简化用户的操作及提高安全性。

Inf文件中必须含有NTAmd64或者NTIA64之类的修饰符才行。具体做法是在[Manufacturer]Models小节都需要添加此类的字段。

[Manufacturer]

%mycompany% = MyCompanyModels

[MyCompanyModels]

%MyDev% = mydevInstall,mydevHwid

[Manufacturer]

%mycompany% = MyCompanyModels,NTx86,NTAmd64

[MyCompanyModels.NTx86]

%MyDev% = mydevInstallx86,mydevHwid

[MyCompanyModels. NTAmd64]

%MyDev% = mydevInstallAmd64,mydevHwid

如果只需要在WindowsX64系统上安装,则只需要使用NTAmd64修饰符就可以了。更多请参考

http://www.microsoft.com/whdc/driver/install/64INF_reqs.mspx

4.编程中容易碰到的问题

1)指针的相关问题

如果原有项目的编程风格控制不严,指针类型混用,强制转换使用过多等等可能对移植是一个巨大的考验。另外,程序中存在结构之中根据具体数据类型来计算其它变量的位置此类的代码也需要重新检查。

使用指针的原则如下:

1. 不要将指针强制转换为int, long, ULONG, DWORD等类型,而应该使用UINT_PTRINT_PTR

2. 使用PtrToUlong()PtrToLong()来截断指针;

3. 永远不要将已经截断的存贮在int或者ULONG中指针地址的重新合成一个新的指针地址;

4. 小心的计算缓冲区的大小,说不定缓冲区的长度比ULONG所能存储的最大数都大!

5. 小心的调用那些传出指针的函数;

4可以举个例子:比如说有两个地址ptr2(高地址), ptr1(低地址),则len = ptr2 – ptr1 将有可能大于232次方。

2)结构的内存排列问题

64位的操作系统上,结构的内存排列(structure alignment)也需要小心审查。内存排列的齐整有利于处理器的执行效率。如果打开了一些编译选项,为了对齐内存地址,编译器可能会将某些位置填空。在移植过程中,对结构中的变量顺序需要仔细检查,特别是在同一个头文件中使用不同的pack选项。比如下面的代码:

#pragma pack (1) /* 也可以使用编译选项/Zp(结构成员对齐)*/

struct AlignSample {

ULONG size;

void *ptr;

};

struct AlignSample s;

void foo(void *p) {

*p = p; // 将会导致访问异常

...

}

foo((PVOID)&s.ptr);

补救办法就是使用宏UNALIGNED:

void foo(void *p) {

struct AlignSample s;

*(UNALIGNED void *)&s.ptr = p;

}

当然,更好的办法就是首先将那些64位长度的数据类型变量放在结构的前端。

3)小心使用十六进制的常量,无符号整数

小心使用十六进制的常量,无符号整数等等。比如说下面的一些断言在64位系统中是错误的:

~((UINT64)(PAGE_SIZE-1)) == (UINT64)~(PAGE_SIZE-1)

PAGE_SIZE = 0x1000UL // Unsigned Long - 32 bits

PAGE_SIZE - 1 = 0x00000fff

等式左边:

// 无符号转换

(UINT64)(PAGE_SIZE -1 ) = 0x0000000000000fff

~((UINT64)(PAGE_SIZE -1 ))= 0xfffffffffffff000

等式右边:

~(PAGE_SIZE-1) = 0xfffff000
(UINT64)(~(PAGE_SIZE-1))=0x00000000fffff000

所以:

~((UINT64)(PAGE_SIZE-1))!= (UINT64)(~(PAGE_SIZE-1))

还有:

DWORD index = 0;

CHAR *p;

If (p[index – 1] == ‘0’)

上面的代码将会在64位系统上出错!因为在32位系统上

p[index-1] == p[0xffffffff] == p[-1]

这是对的。但在64位系统上:

p[index-1] == p[0x00000000ffffffff] != p[-1]

再如:

-1 != 0xFFFFFFFF

0xFFFFFFFF != invalid handle

DWORD总是32,所以要查找原有程序中所有用DWORD存贮指针的代码。另外,别忘了使用 %I来打印指针地址,而且大于0x80000000的也未必是内核态地址了。

5.开发64位驱动的工具

64位的开发工具和32位的差不多,除了必须的Windows DDK 2003之外,WindbgDriver Verifier等都是拿手的好工具。最后,下载一份最新的WHQL测试包进行WHQL测试也是值得推荐的。

目前,学会使用Windbg来调试64位用户态和核心态程序的方法是尤为必要的。也可以购买支持64位的VC环境Visual Stdio 2005来进行64位程序开发。

参考资料

Windows XP Professional x64 Edition 概述http://www.microsoft.com/china/windowsxp/64bit/evaluation/overviews/overview.mspx

AMD64计算平台-带您进入计算的未来白皮书http://msdn.microsoft.com/library/default.asp?url=/library/en-us/kmarch/hh/kmarch/Other_f910e5d8-a732-4faa-a8d2-d4de021dc78d.xml.asp

64-bit DDK编程指导
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/kmarch/hh/kmarch/Other_f910e5d8-a732-4faa-a8d2-d4de021dc78d.xml.asp

硬件测试资料
http://www.microsoft.com/whdc/system/platform/64bit/default.mspx