| 级别: 初级 Nam Keung (), 高级程序员 Chakarat Skawratananond (), pSeries Linux 技术顾问
2005 年 1 月 06 日 本文的内容是 Win32 API(特别是进程、线程和共享内存服务)到 POWER 上 Linux 的映射。本文可以帮助您确定哪种映射服务最适合您的需要。作者向您详细介绍了他在移植 Win32 C/C++ 应用程序时遇到的 API 映射。
有很多方式可以将 Win32 C/C++ 应用程序移植和迁移到 pSeries 平台。您可以使用免费软件或者第三方工具来将
Win32 应用程序代码移到 Linux。在我们的方案中,我们决定使用一个可移植层来抽象系统 API 调用。可移植层将使我们
的应用程序具有以下优势:
- 与硬件无关。
- 与操作系统无关。
- 与操作系统上版本与版本间的变化无关。
- 与操作系统 API 风格及错误代码无关。
- 能够统一地在对 OS 的调用中置入性能和 RAS 钩子(hook)。
由于 Windows 环境与 pSeries Linux 环境有很大区别,所以进行跨 UNIX 平台的移植比进行从 Win32 平台到
UNIX 平台的移植要容易得多。这是可以想到的,因为很多 UNIX 系统都使用共同的设计理念,在应用程序层有
非常多的类似之处。不过,Win32 API 在移植到 Linux 时是受限的。本文剖析了由于 Linux 和 Win32 之间设计
的不同而引发的问题。
在 Win2K/NT 上,DLL 的初始化和终止入口点是 _DLL_InitTerm 函数。当每个新的进程获得对 DLL 的访问时,这个
函数初始化 DLL 所必需的环境。当每个新的进程释放其对 DLL 的访问时,这个函数为那个环境终止 DLL。当您链接
到那个 DLL 时,这个函数会自动地被调用。对应用程序而言,_DLL_InitTerm 函数中包含了另外一个初始化和终止例程。
在 Linux 上,GCC 有一个扩展,允许指定当可执行文件或者包含它的共享对象启动或停止时应该调用某个函数。语法是
__attribute__((constructor)) 或
__attribute__((destructor)) 。
这些基本上与构造函数及析构函数相同,可以替代 glibc 库中的 _init 和 _fini 函数。
这些函数的 C 原型是:
void __attribute__ ((constructor)) app_init(void); void __attribute__ ((destructor)) app_fini(void); |
Win32 sample _DLL_InitTerm(HMODULE modhandle, DWORD fdwReason, LPVOID lpvReserved) { WSADATA Data; switch (fdwReason) { case DLL_PROCESS_ATTACH: if (_CRT_init() == -1) return 0L; /* start with initialization code */ app_init(); break; case DLL_PROCESS_DETACH: /* Start with termination code*/ app_fini(); _CRT_term(); break; ….. default: /* unexpected flag value - return error indication */ return 0UL; } return 1UL; /* success */ } |
Win32 进程模型没有与
fork() 和
exec() 直接相当
的函数。在 Linux 中使用
fork() 调用总是会继承所有内容,与此不同,
CreateProcess() 接收用于控制进程创建方面的显式参数,比如文件句柄继承。
CreateProcess API 创建一个包含有一个或多个在此进程的上下文中运行的线程的新进程,子进程与父进程之间
没有关系。在 Windows NT/2000/XP 上,返回的进程 ID 是 Win32 进程 ID。在 Windows ME 上,返回的进程 ID 是
除去了高位(high-order bit)的 Win32 进程 ID。当创建的进程终止时,所有与此进程相关的数据都从内存中删除。
为了在 Linux 中创建一个新的进程,
fork() 系统调用会复制那个进程。新进程创建后,
父进程和子进程的关系就会自动建立,子进程默认继承父进程的所有属性。Linux 使用一个不带任何参数的调用创建新的
进程。
fork() 将子进程的进程 ID 返回给父进程,而不返回给子进程任何内容。
Win32 进程同时使用句柄和进程 ID 来标识,而 Linux 没有进程句柄。
Win32
|
Linux
| CreateProcess | fork()
execv()
| TerminateProcess | kill | ExitProcess() | exit() | GetCommandLine | argv[] | GetCurrentProcessId | getpid | KillTimer | alarm(0) | SetEnvironmentVariable | putenv | GetEnvironmentVariable | getenv | GetExitCodeProcess | waitpid |
在 Win32 中,
CreateProcess() 的第一个参数指定要运行的程序,第二个参数给出命令
行参数。CreateProcess 将其他进程参数作为参数。倒数第二个参数是一个指向某个 STARTUPINFORMATION 结构体的指针,
它为进程指定了标准的设备以及其他关于进程环境的启动信息。在将 STARTUPINFORMATION 结构体的地址传给
CreateProcess 以重定向进程的标准输入、标准输出和标准错误之前,您需要设置这个结构体的 hStdin、hStdout
和 hStderr 成员。最后一个参数是一个指向某个 PROCESSINFORMATION 结构体的指针,由被创建的进程为其添加内容。
进程一旦启动,它将包含创建它的进程的句柄以及其他内容。
Win32 example PROCESS_INFORMATION procInfo; STARTUPINFO startupInfo; typedef DWORD processId; char *exec_path_name char *_cmd_line; GetStartupInfo( &startupInfo ); // You must fill in this structure if( CreateProcess( exec_path_name, // specify the executable program _cmd_line, // the command line arguments NULL, // ignored in Linux NULL, // ignored in Linux TRUE, // ignored in Linux DETACHED_PROCESS | HIGH_PRIORITY_CLASS, NULL, // ignored in Linux NULL, // ignored in Linux &startupInfo, &procInfo)) *processId = procInfo.dwProcessId; else { *processId = 0; return RC_PROCESS_NOT_CREATED; } |
在 Linux 中,进程 ID 是一个整数。Linux 中的搜索目录由 PATH 环境变量(exec_path_name)决定。
fork() 函数建立父进程的一个副本,包括父进程的数据空间、堆和栈。
execv() 子例程使用 exec_path_name 将调用进程当前环境传递给新的进程。
这个函数用一个由 exec_path_name 指定的新的进程映像替换当前的进程映像。新的映像构造自一个由 exec_path_name
指定的正规的、可执行的文件。由于调用的进程映像被新的进程映像所替换,所以没有任何返回。
Equivalent Linux code #include #include int processId; char *exec_path_name; char * cmd_line ; cmd_line = (char *) malloc(strlen(_cmd_line ) + 1 ); if(cmd_line == NULL) return RC_NOT_ENOUGH_MEMORY; strcpy(cmd_line, _cmd_line); if( ( *processId = fork() ) == 0 ) // Create child { char *pArg, *pPtr; char *argv[WR_MAX_ARG + 1]; int argc; if( ( pArg = strrchr( exec_path_name, '/' ) ) != NULL ) pArg++; else pArg = exec_path_name; argv[0] = pArg; argc = 1; if( cmd_line != NULL && *cmd_line != '\0' ) { pArg = strtok_r(cmd_line, " ", &pPtr); while( pArg != NULL ) { argv[argc] = pArg; argc++; if( argc >= WR_MAX_ARG ) break; pArg = strtok_r(NULL, " ", &pPtr); } } argv[argc] = NULL; execv(exec_path_name, argv); free(cmd_line); exit( -1 ); } else if( *processId == -1 ) { *processId = 0; free(cmd_line); return RC_PROCESS_NOT_CREATED; } |
在 Win32 进程中,父进程和子进程可能需要单独访问子进程所继承的由某个句柄标识的对象。父进程可以
创建一个可访问而且可继承的副本句柄。Win32 示例代码使用下面的方法终止进程:
- 使用 OpenProcess 来获得指定进程的句柄。
- 使用 GetCurrentProcess 获得其自己的句柄。
- 使用 DuplicateHandle 来获得一个来自同一对象的句柄作为原始句柄。
如果函数成功,则使用 TerminateThread 函数来释放同一进程上的主线程。然后使用 TerminateThread 函数
来无条件地使一个进程退出。它启动终止并立即返回。
Win32 sample code if( thread != (HANDLE) NULL ) { HANDLE thread_dup; if( DuplicateHandle( OpenProcess(PROCESS_ALL_ACCESS, TRUE, processId), thread, GetCurrentProcess(), &thread_dup, //Output 0, FALSE, DUPLICATE_SAME_ACCESS )) { TerminateThread( thread_dup, 0); } } TerminateProcess( OpenProcess(PROCESS_ALL_ACCESS, TRUE, processId), (UINT)0 ); |
在 Linux 中,使用 kill 子例程发送 SIGTERM 信号来终止特定进程(processId)。
然后调用设置 WNOHANG 位的 waitpid 子例程。这将检查特定的进程并终止。
Equivalent Linux code pid_t nRet; int status; kill( processId, SIGTERM ); nRet = waitpid( processId, &status, WNOHANG); //Check specified process is terminated |
Win32 OpenProcess 返回特定进程(processId)的句柄。如果函数成功,则
GetExitCodeProcess 将获得特定进程的状态,并检查进程的状态是否是 STILL_ACTIVE。
Win 32 sample HANDLE nProc; DWORD dwExitCode; nProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, processId); if ( nProc != NULL) { GetExitCodeProcess( nProc, &dwExitCode ); if (dwExitCode == STILL_ACTIVE ) return RC_PROCESS_EXIST; else return RC_PROCESS_NOT_EXIST; } else return RC_PROCESS_NOT_EXIST; |
在 Linux 中,使用 kill 子例程发送通过
Signal 参数指定的信号给
由
Process 参数(processId)指定的特定进程。Signal 参数是一个
null 值,会执行错误检查,但不发送信号。
Equivalent Linux code if ( kill ( processId, 0 ) == -1 && errno == ESRCH ) // No process can be found // corresponding to processId return RC_PROCESS_NOT_EXIST; else return RC_PROCESS_EXIST; |
线程 是系统分配 CPU 时间的基本单位;当等待调度时,每个线程保持信息来保存它的“上下文”。每个
线程都可以执行程序代码的任何部分,并共享进程的全局变量。
构建于
clone() 系统调用之上的 LinuxThreads 是一个 pthreads 兼容线程系统。
因为线程由内核来调度,所以 LinuxThreads 支持阻塞的 I/O 操作和多处理器。不过,每个线程实际上是一个
Linux 进程,所以一个程序可以拥有的线程数目受内核所允许的进程总数的限制。Linux 内核没有为线程同步提供
系统调用。Linux Threads 库提供了另外的代码来支持对互斥和条件变量的操作(使用管道来阻塞线程)。
对有外加 LinuxThreads 的信号处理来说,每个线程都会继承信号处理器(如果派生这个线程的父进程
注册了一个信号处理器的话。只有在 Linux Kernel 2.6 和更高版本中支持的新特性才会包含 POSIX 线程支持,比如
用于 Linux 的 Native POSIX Thread Library(NPTL)。
线程同步、等待函数、线程本地存储以及初始化和终止抽象是线程模型的重要部分。在这些之下,线程服务只
负责:
- 新线程被创建,threadId 被返回。
- 通过调用 pthread_exit 函数可以终止当前的新线程。
Win32
|
Linux
| _beginthread | pthread_attr_init
pthread_attr_setstacksize
pthread_create
| _endthread | pthread_exit | TerminateThread | pthread_cancel | GetCurrentThreadId | pthread_self |
Win32 应用程序使用 C 运行期库,而不使用 Create_Thread API。使用了 _beginthread 和 _endthread 例程。
这些例程会考虑任何可重入性(reentrancy)和内存不足问题、线程本地存储、初始化和终止抽象。
Linux 使用 pthread 库调用
pthread_create() 来派生一个线程。
threadId 作为一个输出参数返回。为创建一个新线程,要传递一组参数。当新线程被创建时,这些参数会执行一个
函数。stacksize 用作新线程的栈的大小(以字节为单位),当新线程开始执行时,实际的参数被传递给函数。
进行创建的线程必须指定要执行的新线程的启动函数的代码。启动地址是 threadproc 函数(带有一个单独的参数,
即 threadparam)的名称。如果调用成功地创建了一个新线程,则返回 threadId。Win32 threadId 的类型定义是
HANDLE。Linux threadId 的类型定义是 pthread_t。
- threadproc
- 要执行的线程程序(函数)。它接收一个单独的 void 参数。
- threadparam
- 线程开始执行时传递给它的参数。
在 Win32 中,线程的栈由进程的内存空间自动分配。系统根据需要增加栈的大小,并在线程终止时释放它。
在 Linux 中,栈的大小在 pthread 属性对象中设置;pthread_attr_t 传递给库调用
pthread_create() 。
Win32 sample int hThrd; DWORD dwIDThread; unsigned stacksize; void *thrdparam; //parameter to be passed to the thread when it //begins execution HANDLE *threadId; if( stacksize < 8192 ) stacksize = 8192; else stacksize = (stacksize/4096+1)*4096; hThrd = _beginthread( thrdproc, // Definition of a thread entry //point NULL, stacksize, thrdparam); if (hThrd == -1) return RC_THREAD_NOT_CREATED); *threadId = (HANDLE) hThrd; __________________________________________________________________ Equivalent Linux code #include pthread_t *threadId; void thrdproc (void *data); //the thread procedure (function) to //be executed. //It receives a single void parameter void *thrdparam; //parameter to be passed to the thread when it //begins execution pthread_attr_t attr; int rc = 0; if (thrdproc == NULL || threadId == NULL) return RC_INVALID_PARAM); if (rc = pthread_attr_init(&attr)) return RC_THREAD_NOT_CREATED); // EINVAL, ENOMEM if (rc = pthread_attr_setstacksize(&attr, stacksize)) return RC_THREAD_NOT_CREATED); // EINVAL, ENOSYS if (rc = pthread_create(threadId, &attr, (void*(*)(void*))thrdproc, thrdparam)) return RC_THREAD_NOT_CREATED); // EINVAL, EAGAIN |
在 Win32 中,一个线程可以使用 TerminateThread 函数终止另一个线程。不过,线程的栈和其他资源将不会被收回。
如果线程终止自己,则这样是可取的。在 Linux 中,pthread_cancel 可以终止由具体的 threadId
所标识的线程的执行。
Win32
|
Linux
|
TerminateThread((HANDLE *) threadId, 0);
|
pthread_cancel(threadId);
|
在 Linux 中,线程默认创建为可合并(joinable)状态。另一个线程可以使用
pthread_join() 同步线程的终止并重新获得终止代码。
可合并线程的线程资源只有在其被合并后才被释放。
Win32 使用
WaitForSingleObject() 来等待线程终止。
Linux 使用 pthread_join 完成同样的事情。
Win32
|
Linux
| unsigned long rc;
rc = (unsigned long)
WaitForSingleObject (threadId, INIFITE);
| unsigned long rc=0;
rc =
pthread_join(threadId,
void **status);
|
在 Win32 中,使用
_endthread() 来结束当前线程的执行。在 Linux 中,推荐使用
pthread_exit() 来退出一个线程,以避免显式地调用 exit 例程。在 Linux 中,线程
的返回值是 retval,可以由另一个线程调用
pthread_join() 来获得它。
Win32
|
Linux
|
_endthread();
|
pthread_exit(0);
|
在 Win32 进程中,GetCurrentThreadId 函数获得进行调用的线程的线程标识符。Linux 使用
pthread_self() 函数来返回进行调用的线程的 ID。
Win32
|
Linux
|
GetCurrentThreadId()
|
pthread_self()
|
用于 Win32 Sleep 函数的时间段的单位是毫秒,可以是 INFINITE,在这种情况下线程将永远不会再重新开始。
Linux sleep 函数类似于 Sleep,但是时间段以秒来计。要获得毫秒级的精度,则使用 nanosleep 函数来提供同样的服务。
Win32
|
Equivalent Linux code
|
Sleep (50)
| struct timespec timeOut,remains;
timeOut.tv_sec = 0;
timeOut.tv_nsec = 500000000; /* 50 milliseconds */
nanosleep(&timeOut, &remains);
|
Win32 SleepEx 函数挂起
当前线程,直到下面事件之一发生:
- 一个 I/O 完成回调函数被调用。
- 一个异步过程调用(asynchronous procedure call,APC)排队到此线程。
- 最小超时时间间隔已经过去。
Linux 使用 sched_yield 完成同样的事情。
Win32
|
Linux
|
SleepEx (0,0)
|
sched_yield()
|
共享内存允许多个进程将它们的部分虚地址映射到一个公用的内存区域。任何进程都可以向共享内存区域写入数据,
并且数据可以由其他进程读取或修改。共享内存用于实现进程间通信媒介。不过,共享内存不为使用它的进程提供
任何访问控制。使用共享内存时通常会同时使用“锁”。
一个典型的使用情形是:
- 某个服务器创建了一个共享内存区域,并建立了一个共享的锁对象。
- 某个客户机连接到服务器所创建的共享内存区域。
- 客户机和服务器双方都可以使用共享的锁对象来获得对共享内存区域的访问。
- 客户机和服务器可以查询共享内存区域的位置。
Win32
|
Linux
| CreateFileMaping,
OpenFileMapping
| mmap
shmget
| UnmapViewOfFile | munmap
shmdt
| MapViewOfFile | mmap
shmat
|
Win32 通过共享的内存映射文件来创建共享内存资源。Linux 使用 shmget/mmap 函数通过直接将文件数据
合并入内存来访问文件。内存区域是已知的作为共享内存的段。
文件和数据也可以在多个进程和线程之间共享。不过,这需要进程或线程之间同步,由应用程序来处理。
如果资源已经存在,则
CreateFileMapping() 重新初始化共享资源对于进程的约定。
如果没有足够的空闲内存来处理错误的共享资源,此调用可能会失败。
OpenFileMapping()
需要共享资源必须已经存在;这个调用只是请求对它的访问。
在 Win32 中,CreateFileMapping 不允许您增加文件大小,但是在 Linux 中不是这样。在 Linux 中,如果资源
已经存在,它将被重新初始化。它可能被销毁并重新创建。Linux 创建可以通过名称访问的共享内存。
open() 系统调用确定映射是否可读或可写。传递给
mmap()
的参数必须不能与
open() 时请求的访问相冲突。
mmap()
需要为映射提供文件的大小(字节数)。
对 32-位内核而言,有 4GB 虚地址空间。最前的 1 GB 用于设备驱动程序。最后 1 GB 用于内核数据结构。中间的 2GB
可以用于共享内存。当前,POWER 上的 Linux 允许内核使用 4GB 虚地址空间,允许用户应用程序使用最多 4GB 虚
地址空间。
Win32
|
Linux
|
PAGE_READONLY
|
PROT_READ
|
PAGE_READWRITE
|
(PROT_READ | PROT_WRITE)
|
PAGE_NOACCESS
|
PROT_NONE
|
PAGE_EXECUTE
|
PROT_EXEC
|
PAGE_EXECUTE_READ
|
(PROT_EXEC |PROT_READ)
|
PAGE_EXECUTE_READWRITE
|
(PROT_EXEC | PROT_READ | PROT_WRITE)
|
要获得 Linux 共享内存的分配,您可以查看 /proc/sys/kernel 目录下的 shmmax、shmmin 和 shmall。
在 Linux 上增加共享内存的一个示例:
echo 524288000 > /proc/sys/kernel/shmmax |
最大共享内存增加到 500 MB。
下面是创建共享内存资源的 Win32 示例代码,以及相对应的 Linux nmap 实现。
Win32 sample code typedef struct { // This receives a pointer within the current process at which the // shared memory is located. // The same shared memory may reside at different addresses in other // processes which share it. void * location; HANDLE hFileMapping; }mem_shared_struct, *mem_shared, *token; mem_shared_struct *token; if ((*token = (mem_shared) malloc(sizeof(mem_shared_struct))) == NULL) return RC_NOT_ENOUGH_MEMORY; if (newmode == new_shared_create) (*token)->hFileMapping = CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL, PAGE_READWRITE, 0, (DWORD) size, (LPSTR) name); else (*token)->hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, (LPSTR) name); if ((*token)->hFileMapping == NULL) { free( *token ); return RC_SHM_NOT_CREATED ); } (*token)->location = MapViewOfFile((*token)->hFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); if ((*token)->location == NULL) { CloseHandle((*token)->hFileMapping); free(*token); return RC_OBJECT_NOT_CREATED; } ____________________________________________________________________ Equivalent Linux code typedef struct { void *location; int nFileDes; cs_size nSize; char *pFileName; }mem_shared_struct, *mem_shared, token; mode_t mode=0; int flag=0; int i, ch='\0'; char name_buff[128]; if (newmode == new_shared_create) flag = O_CREAT; else if (newmode != new_shared_attach) return RC_INVALID_PARAM; if ((*token = (mem_shared) malloc(sizeof(mem_shared_struct))) == NULL) return RC_NOT_ENOUGH_MEMORY; strcpy(name_buff, "/tmp/" ); strcat(name_buff, name ); if(((*token)->pFileName = malloc(strlen(name_buff)+1)) == NULL ) { free(*token); return RC_NOT_ENOUGH_MEMORY; } mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; flag |= O_RDWR; if(newmode == new_shared_create) remove(name_buff); if(((*token)->nFileDes = open(name_buff, flag, mode)) < 0) { free((*token)->pFileName); free(*token); return RC_OBJECT_NOT_CREATED; } if(newmode == new_shared_create) { lseek((*token)->nFileDes, size - 1, SEEK_SET); write((*token)->nFileDes, &ch, 1); } if(lseek((*token)->nFileDes, 0, SEEK_END) < size) { free((*token)->pFileName); free(*token); return RC_MEMSIZE_ERROR; } (*token)->location = mmap( 0, size, PROT_READ | PROT_WRITE, MAP_VARIABLE | MAP_SHARED, (*token)->nFileDes, 0); if((int)((*token)->location) == -1) { free((*token)->pFileName); free(*token); return RC_OBJECT_NOT_CREATED; } (*token)->nSize = size;strcpy((*token)->pFileName, name_buff); |
为销毁共享内存资源,munmap 子例程要取消被映射文件区域的映射。munmap 子例程只是取消
对 mmap 子例程的调用而创建的区域的映射。如果某个区域内的一个地址被 mmap 子例程取消映射,
并且那个区域后来未被再次映射,那么任何对那个地址的引用将导致给进程发出一个 SIGSEGV 信号。
Win32
|
等价的 Linux 代码
|
UnmapViewOfFile(token->location);
CloseHandle(token->hFileMapping);
|
munmap(token->location, token->nSize);
close(token->nFileDes);
remove(token->pFileName);
free(token->pFileName);
|
本文介绍了关于初始化和终止、进程、线程及共享内存服务从 Win32 API 到 POWER 上 Linux 的映射。
这绝对没有涵盖所有的 API 映射,而且读者只能将此信息用作将 Win32 C/C++ 应用程序迁移到 POWER Linux
的一个参考。
IBM、eServer 和 pSeries 是 IBM Corporation 在美国和/或其它国家或地区的商标。
UNIX 是 The Open Group 在美国和其它国家或地区的注册商标。
Microsoft 和 Windows 是 Microsoft Corporation 在美国和/或其它国家或地区的商标或注册商标。
所有其他商标和注册商标是它们相应公司的财产。
此出版物/说明是在美国完成的。IBM 可能不在其他国家或地区提供在此讨论的产品、程序、
服务或特性,而且信息可能会不加声明地加以修改。有关您当前所在区域的产品、程序、服务和特性的信息,
请向您当地的 IBM 代表咨询。任何对 IBM 产品、程序、服务或者特性的引用并非意在明示或暗示只能使用 IBM
的产品、程序、服务或者特性。只要不侵犯 IBM 的知识产权,任何同等功能的产品、程序、服务或特性,
都可以代替 IBM 产品、程序、服务或特性。
涉及非 IBM 产品的信息可从这些产品的供应商、其出版说明或其他可公开获得的资料中获取,并不构成 IBM 对
此产品的认可。非 IBM 价目及性能数字资源取自可公开获得的信息,包括供应商的声明和供应商的全球主页。
IBM 没有对这些产品进行测试,也无法确认其性能的精确性、兼容性或任何其他关于非 IBM 产品的声明。
有关非 IBM 产品性能的问题应当向这些产品的供应商提出。
有关非 IBM 产品性能的问题应当向这些产品的供应商提出。IBM 公司可能已拥有或正在申请与本说明中描述的
内容有关的各项专利。提供本说明并未授予用户使用这些专利的任何许可。您可以用书面方式将许可查询寄往:
IBM Director of Licensing
IBM Corporation
North Castle Drive
Armonk, NY 10504-1785 U.S.A。
所有关于 IBM 未来方向或意向的声明都可随时更改或收回,而不另行通知,它们仅仅表示了目标和意愿而已。
联系您本地的 IBM 办公人员或者 IBM 授权的转销商,以获得特定的 Statement of General Direction 的全文。
本说明中所包含的信息没有提交给任何正式的 IBM 测试,而只是“按原样”发布。虽然 IBM 可能为了其在特定条件下的
精确性而已经对每个条目进行了检查,但不保证在其他地方可以获得相同的或者类似的结果。使用此信息或者实现这里
所描述的任何技术是客户的责任,取决于客户评价并集成它们到客户的操作环境的能力。尝试为他们自己的环境而修改
这些技术的客户,这样做所带来的风险由他们自行承担。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文。
|