64位与Mac OS X Tiger

 

64 位与Mac OS X Tiger
——只在需要时迁移到64位
 
 
       Mac OS X是Apple Macintosh操作系统中第一个支持64位计算的版本,其可充分利用64位PowerPC G5处理器的能力,然而,这并不意味着必须把每一个应用程序都迁移到64位平台上去。大多数的OS X程序不必移植为64位,实际上,作为32位程序,它们还能执行得更快一些。把一个程序移植为64位,其最主要的原因还是因为想访问超过4GB的内存,此类的程序包括工程与科学计算、实时渲染、大型数据库等等。一个比较好的做法是,在决定是否需要移植到64位之前,先检查一下程序所运行的环境——也许它不需要程序移植呢?
 
 
Ø 64位数学库。在运行于PowerPC G5处理器之上的OS X中,你不必为了要进行64位算术运算而移植到64位;PowerPC可在32位模式中支持64位算术指令。在GCC中使用-mcpu=G5编译选项以打开对G5的特定优化,也可使用-mpowerpc64选项允许64位指令,同时使用以上两个选项可在32位程序中提高性能。
Ø Apple公司已经宣布Mac平台将会转换为Intel CPU。而Intel的处理器,如64位的至强(Xeon),要求程序必须为64位,以利用那些64位通用寄存器(此处与PowerPC不同)。因此,在Mac架构完全变为Intel处理器之后,你需要重新评价是否应该移植到64位上——特别当你的程序是整数密集型时。
Ø 64位的数据类型。你不必仅是为了访问64位数据而移植到64位,例如,long long与int64_t都是64位的,但却能在32位程序中使用。
Ø 需要更快的代码?如果你的程序对性能要求敏感并且已为32位高度优化过了,那就不必移植到64位了。64位指针所带来的尺寸上的增长,会增加缓存压力,同样也会消耗更多的磁盘空间、占用更多的内存、更高的网络占用率,这些都会导致程序性能降低。
 
 
“干净”的64
一旦决定把一个程序移植到64位,那么首先要保证代码对64位而言是“干净”的——也就是说,没有受任何其他的“杂质污染”。Mac OS X(包括其他任何从Unix派生来的操作系统)使用的64位C语言数据模型通常被称为“LP64”,在LP64数据模型中,int是32位,而long与指针是64位;相对而言,32位的数据模型被称为“ILP32”,即int、long、指针全部都是32位的。
在ILP32与LP64之间,因为long与指针大小存在差异,当假定它们与int大小一样时,将会导致数值截断。大多数的64位移植问题能在GCC中通过-Wall -Wformat -Wmissing-prototypes -Wconversion -Wsign-compare -Wpointer这些编译选项可发现。(有关更多信息,请参考《向64位进军》一文。)
这里要澄清一点:在OS X Tiger操作系统中,并不是所有的OS X API都支持64位计算,例如,像Cocoa与Carbon之类的Framework就不支持64位开发。这意味着,不能只是简单地重新编译一下32位图形界面(GUI)程序,就可以把它们当作OS X上的64位程序使用了,只有命令行程序能被重新编译为64位。然而,这也不是说GUI程序就不能搭上64位计算的顺风车了,下文将要说明,怎样做才能移植一个32位的OS X GUI程序到64位平台上。
 
 
       示例程序
       此处要移植为64位的32位示例程序,是一个简单的数组查找程序,用户输入一个数组的下标号,程序返回数组中此下标号代表的数值,如图1所示。我们将把此程序移植到64位,以访问超过4GB大小的数组。
 
 
       此示例程序用Qt 4编写(http://www.trolltech.com/),其为一个开源的C++应用框架,可非常简单地编写跨平台的本地GUI程序(如OS X上的Carbon)。在Absoft,所有的跨平台开发工具均用Qt编写,易于维护,并且可在所有支持的平台上(Windows、Liunx、OS X)达到本地运行速度。如果你的应用程序不是基于Qt的,并且使用了本地 OS X API,那么文中阐述的策略将是适用的。
 
 
       移植的方法论
       为把此32位示例程序转换为64位,作者把它分成两部分,只有命令行版本的程序在OS X中是64位的。
 
Ø 64位命令行版本的程序提供服务,其进行必要的64位操作,如数组分配及管理。
Ø 一个32位的GUI程序显示结果,并与用户交互。而现有的GUI程序被重构,以便与服务程序通讯。
 
对大多数图形界面程序来说,把一个程序重构为64位的后台服务端及32位的GUI,是一个艰难的任务。一旦选定了一种移植64位程序的策略,就必须制定64位后台服务端与32位GUI客户端程序相通讯的方式,以下是可采用的几种机制:
 
Ø 在64位程序的标准输入与标准输出之间,通过消息传递来通讯。
Ø 在同一主机上通讯,采用Unix域套接字
Ø 采用TCP/IP 客户端、服务端机制
Ø 采用共享内存或其他IPC机制
 
       上述可供所选择的方法依赖于具体的应用程序,以下的实现基于Unix域套接字。Unix域套接字是一个轻量级、但却高效的套接字,可用于在同一主机上的不同进程之间的通讯。如果你对标准TCP套接字非常熟悉,那么Unix域套接字也会易于掌握,并且,Unix域套接字也会使你的代码更容易地升级到重量级的TCP套接字,举例来说,也许在下一个版本中,程序的服务端运行在基于PowerPC的Mac上,而GUI客户端程序则运行在基于Intel CPU的Mac上。
 
 
       创建服务端
       程序的服务端用来分配数组空间,以便能访问超过4GB的内存,它同时也提供了一个接口,以使客户端能从数组中查找数值。服务端与GUI能分别测试,这可在重构GUI之前,不必忙于测试两者的相交互性。
       可在ILP32 与LP64 共享数据时,采用定宽的数据类型。例1(server.c)是服务端源代码,在第18至19行,使用了uint64_t来代替unsigned long long,当在ILP32与LP64之间通过套接字或磁盘共享数据时,这是一个非常好的办法,它保证了在两个不同的数据模型之间通讯时,数据大小不会发生变化。定宽数据类型最初是由C99引入,可在头文件<stdint.h>中找到。虽然这个C99的特性不是C++标准的一个部分,但大多数C++编译器也都支持它(如Absoft 10.0 a++ 和GNU g++)。
 
例1:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 #include <sys/un.h>
8 #include <inttypes.h>
9 #include <unistd.h>
10 #include "absoft.h"
11
12 int main(int argc, char *argv[])
13 {
14     int listenfd,/* 监听套接字变量*/
15     clientfd,    /*连接进来的套接字变量*/
16     i;
17     int32_t x;   /*客户端数组下标*/
18     uint64_t result; /*返回到客户端的结果*/
19     static uint64_t bigarray_[ARRAY_SIZE];
20     socklen_t clientlen;
21     struct sockaddr_un server, client;
22     /*用随机值初始化数组*/
23     for ( i = 0 ; i < ARRAY_SIZE ; i++ ) {
24         bigarray_[i] = 10000000000000000000ULL + i;
25     }
26     /* AF_LOCAL为Unix域套接字*/
27     if ((listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
28         perror("socket");
29         exit(1);
30     }
31     /*设置套接字*/
32     bzero((char *) &server, sizeof(server));
33     server.sun_family = AF_LOCAL;
34     strncpy(server.sun_path, SOCK_ADDR, sizeof(server.sun_path));
35
36     /*释放路径名以确保绑定成功,此处忽略了错误。*/
37     unlink(SOCK_ADDR);
38
39     /*绑定到套接字*/
40     if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) < 0 ) {
41         perror("bind");
42         exit(2);
43     }
44     /*监听套接字*/
45     if (listen(listenfd, LISTENQ) < 0 ) {
46         perror("listen");
47         exit(3);
48     }
49     for(;;) {
50         printf("Waiting for a connection.../n");
51         clientlen = sizeof(client);
52         if ((clientfd =
53             accept(listenfd, (struct sockaddr *)&client, &clientlen)) < 0) {
54             perror("accept");
55             exit(4);
56        }
57         /*读入客户端所需的数组下标值*/
58         readn(clientfd, &x, sizeof(x));
59         printf("Read in request for array element %d/n", x);
60         if ( x > ARRAY_SIZE || x < 0 ) {
61             /*发生错误*/
62             result = 0;
63         } else {
64             result = bigarray_[x];
65         }
66         /*输出指定的unsigned 64位整数*/
67         printf ("Server sending back to client: %llu/n", result);
68         if (writen(clientfd, &result, sizeof(result)) < 0 ) {
69             exit(5);
70         }
71         close(clientfd);
72     }
73     exit(0);
74}
 
       使用 __LP64__ 宏有条件地编译特定的64 位代码。当维护32位与64位的同一基代码时,在这个例子里,定义的ARRAY_SIZE必须足够大,以充分利用64位上的内存寻址优势,例2演示了如何在OS X上使用 __LP64__ 宏。
 
例2:
1 #ifndef ABSOFT_H
2 #define ABSOFT_H
3 #include <stdint.h>
4 #include <stdlib.h>
5 #define SOCK_ADDR "/tmp/sock"
6 #define LISTENQ 5
 
7 /* 当编译为64位时,可使用更大的数组
8 *此处演示的只是比32位上的大1
9 */
10 #ifdef __LP64__
11 #define ARRAY_SIZE 1001
12 #else
13 #define ARRAY_SIZE 1000
14 #endif /* __LP64__ */
15 /*函数原型*/
16 ssize_t readn(int fd, void *vptr, size_t n);
17 ssize_t writen(int fd, const void *vptr, size_t n);
18 uint64_t lookupValue(int32_t x);
19 #endif
 
       在Unix域套接字中,客户端与服务端通过路径名(如:/tmp/foo)进行通讯,此处的文件名不是一个通常意义上可读写的文件名,所以程序必须先把文件名与套接字联系起来,以便进行通讯。在Unix中,可对此文件使用命令ls –laF,以确认这个特殊的套接字,会看到文件名后附加了一个“=”号。
 
% ls -laF /tmp/sock
srwxr-xr-x 1 rwm wheel 0 Oct 29 21:51
/tmp/sock=
 
       再回头看一下例1中服务端代码,服务端必须准备接受连接,它也是通过套接字监听完成的,在例1中的第27行,调用套接字创建了一个通讯端点,返回一个未命名的套接字。此套接字调用接受三个参数:
 
Ø 第一个参数代表族类型。在此例中,使用AF_LOCAL指定了一个Unix域族。
Ø 第二个参数SOCK_STREAM用于创建一个顺序、可靠、双向连接的字节流。
Ø 最后一个参数指示了协议,在此,零为默认值。
 
在例1的第31至34行,用文件名设置了sockaddr_un结构,请注意定义在absoft.h头文件中的SOCK_ADDR文件名,此处为一个Unix的路径名“/tmp/sock”,文件名是任意的,但必须在服务端与客户端同时定义,而且是绝对路径名。在37行中,删除了前一个程序实例可能留下的路径名,以确保绑定成功。
接下来,在40行中,设置好之前创建的未命名的套接字,最后,在45行,使用监听调用开始接受连接。
在49行,进入循环,等待客户端的连接。一旦接收到一个连接,在58行读入数组下标,并在68行中返回此数值。留意readn和written函数,普通的read/write操作并不保证在一次调用中可读写所有请求的字节,此处是两个包装函数,可一次读写所需的所有字节。
 
 
       创建客户端
       为了测试服务端,用C语言创建了一个客户端,并连接到服务端,请求一个数组下标,并取回相应的结果,可在重构GUI之前,用此来测试与服务端的交互性。客户端使用了socket与connect函数与服务端通讯,参见例3,其实现了客户端的lookup函数。客户端源代码很容易理解,因为它与服务端代码非常相似,但使用了connect函数以连接到服务端套接字。
 
例3:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 #include <sys/un.h>
8 #include <sys/uio.h>
9 #include <sys/fcntl.h>
10 #include <inttypes.h>
11 #include <stdint.h>
12 #include <unistd.h>
13 #include "absoft.h"
14  /*通过连接到Unix域套接字
15 *查找下标x的数组值
16 */
17 uint64_t lookupValue(int32_t x)
18 {
19      int s;
20      struct sockaddr_un remote;
21      uint64_t result;
22      if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0 ) {
23          perror("socket");
24          return(0);
25      }
26      bzero(&remote, sizeof(remote));
27      printf("Trying to connect.../n");
28      remote.sun_family = AF_LOCAL;
29      strcpy(remote.sun_path, SOCK_ADDR);
30     if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) < 0) {
31          perror("connect");
32          return(0);
33      }
34      printf("Connected and sending %d/n", x);
35      if (writen(s, &x, sizeof(x)) < 0 ) {
36          perror("send");
37          return(0);
38      }
39      readn(s, &result, sizeof(result));
40      printf ("Client received result from server = %llu/n", result);
41      close(s);
42      return result;
43 }
 
       读者可能会问,为什么不用C++语言来编写服务端与客户端代码呢?最主要的原因是可移植性,C语言套接字的实现可方便移植到多种平台上,而不需要第三方的库或手工编写套接字实现。如果非常想以C++代码编写客户与服务端代码,Qt提供了一个QSocket类,可扩展它以支持Unix域套接字。
 
 
       重构GUI
       至此,已经能让服务端分配数组空间,而客户端调用函数并取回数值,现在可以开始进行最棘手的部分了——重构GUI。为简化代码,此处屏蔽了所有的网络调用,所以,只在线程中进行调用,当通讯完成之后,由线程来通知程序的UI部分。参见例4中对fetchData函数的线程包装实现。
 
例4:
1 #include "FetchDataThread.h"
2 FetchDataThread::FetchDataThread(QObject *parent)
3          : QThread(parent)
4 {
5 }
6 FetchDataThread::~FetchDataThread()
7 {
8      cond.wakeOne();
9      wait();
10 }
11 void FetchDataThread::fetchData(const int32_t x)
12 {
13      //保持mutex直到函数退出
14      QMutexLocker locker(&mutex);
15      this->x = x;
16      if (!isRunning())
17          start();
18      else
19          cond.wakeOne();
20 }
21 void FetchDataThread::run()
22 {
23      QMutexLocker locker(&mutex);
24      int32_t xv = x;
25      // 调用网络通讯功能
26     uint64_t result = lookupValue(xv);
27      /*简化了错误检查,只在发生错误时返回0 */
28      if ( result == 0 ) {
29          emit errorOccured("Error looking up value");
30          return;
31      } else {
32          QString str;
33          emit fetchedData( str.setNum(result) );
34      }
35 }
 
       例4中的run()方法调用了例3中定义的lookupValue函数,并且锁住一个互斥体(mutex),以保证关键数据线程安全。
       此处使用了Qt中的emit关键字来发送从服务端得到的结果,GUI收到之后就会显示出来,并使程序中的Lookup按钮可用。
 
 
       启动和停止服务端
       最后一部分是使GUI可自动地启动服务端,就好像两者是一个文件一样。UI线程中的main()函数使用了Qt的QProcess类来运行服务端,并在程序退出前关闭服务端。
 
 
       创建通用的二进制文件
       也许会想到发售32位 GUI与64位服务端程序,因此,程序有可能运行在世界上各处的Macintosh上,为了避免发售程序的不同版本,可以创建一个通用的二进制安装文件(或称为“肥胖的二进制文件”),它同时包含了32位与64位版本。通用二进制文件无需额外的代码或用户干预,会根据用户的系统自动选择对应的程序。
       用Xcode可非常简单地创建通用二进制文件,或使用OS X中的lipo工具。Lipo可把32位与64位程序“粘”成一个二进制文件。例5是一个makefile的示例,创建了名为server的通用二进制文件,可用Unix的file命令来验证。
 
% file server
server: Mach-O fat file with 2 architectures
server (for architecture ppc):
Mach-O executable ppc
server (for architecture ppc64):
Mach-O 64-bit executable ppc64
 
例5:
CFLAGS= -Wall -Wformat -Wmissing-prototypes -Wconversion
                                    -Wsign-compare -Wpointer-arith
      all: server
      server32: util.c server.c
          gcc $(CFLAGS) -m32 util.c server.c -o server32
      server64: util.c server.c
          gcc $(CFLAGS) -m64 util.c server.c -o server64
      server: server32 server64
          lipo -create server32 server64 -output server
      clean:
          rm -rf server32 server64 server
 
 
       构建和运行程序
       在安装了Qt之后,可构建生成程序,如下:
 
% qmake ; make ; make -f Makefile.server
 
       在命令行中,qmake生成了一个makefile,以构建GUI,请见例6,并生成一个通用二进制文件。生成之后,可在命令行中执行:
 
%./Viewer.app/Contents/MacOS/Viewer
 
例6:
# Use The Qt utility "qmake" to build
# a Makefile from this file
TEMPLATE = app
CONFIG += qt release
TARGET +=
DEPENDPATH += .
INCLUDEPATH += .
DEFINES += DIVORCE_UI
HEADERS += Viewer.h
HEADERS += absoft.h
HEADERS += FetchDataThread.h
SOURCES += client.c
SOURCES += util.c
SOURCES += Viewer.cpp
SOURCES += FetchDataThread.cpp
 
 
       最后
       有着Unix传统、并有如通用二进制文件这样的创新,OS X是一个非常好的可用来开发64位应用的64位平台。移植命令行程序到64位是非常容易的,而以上所述的策略将会有助于在64位,使你的GUI程序充分利用Mac OS X Tiger的强大功能。想一下OS X Tiger的漂亮界面,是不是有点迫不急待想动手了?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值