本文的內容是 Win32 API(特別是進程、線程和共享內存服務)到 POWER 上 Linux 的映射。本文可以幫助您確定哪種映射服務最適合您的需要。作者向您詳細介紹了他在移植 Win32 C/C++ 應用程序時遇到的 API 映射。
概述
有
很多方式可以將 Win32 C/C++ 應用程序移植和遷移到 pSeries 平臺。您可以使用免費軟件或者第三方工具來將 Win32
應用程序代碼移到 Linux。在我們的方案中,我們決定使用一個可移植層來抽象系統(tǒng) API 調用??梢浦矊訉⑹刮覀兊膽贸绦蚓哂幸韵聝?yōu)勢:
- 與硬件無關。
- 與操作系統(tǒng)無關。
- 與操作系統(tǒng)上版本與版本間的變化無關。
- 與操作系統(tǒng) API 風格及錯誤代碼無關。
- 能夠統(tǒng)一地在對 OS 的調用中置入性能和 RAS 鉤子(hook)。
由 于 Windows 環(huán)境與 pSeries Linux 環(huán)境有很大區(qū)別,所以進行跨 UNIX 平臺的移植比進行從 Win32 平臺到 UNIX 平臺的移植要容易得多。這是可以想到的,因為很多 UNIX 系統(tǒng)都使用共同的設計理念,在應用程序層有非常多的類似之處。不過,Win32 API 在移植到 Linux 時是受限的。本文剖析了由于 Linux 和 Win32 之間設計的不同而引發(fā)的問題。
初始化和終止
在
Win2K/NT 上,DLL 的初始化和終止入口點是 _DLL_InitTerm 函數(shù)。當每個新的進程獲得對 DLL 的訪問時,這個函數(shù)初始化
DLL 所必需的環(huán)境。當每個新的進程釋放其對 DLL 的訪問時,這個函數(shù)為那個環(huán)境終止 DLL。當您鏈接到那個 DLL
時,這個函數(shù)會自動地被調用。對應用程序而言,_DLL_InitTerm 函數(shù)中包含了另外一個初始化和終止例程。
在 Linux 上,GCC 有一個擴展,允許指定當可執(zhí)行文件或者包含它的共享對象啟動或停止時應該調用某個函數(shù)。語法是 __attribute__((constructor))
或 __attribute__((destructor))
。這些基本上與構造函數(shù)及析構函數(shù)相同,可以替代 glibc 庫中的 _init 和 _fini 函數(shù)。
這些函數(shù)的 C 原型是:
|
|
進程服務
Win32 進程模型沒有與 fork()
和 exec()
直接相當?shù)暮瘮?shù)。在 Linux 中使用 fork()
調用總是會繼承所有內容,與此不同, CreateProcess()
接收用于控制進程創(chuàng)建方面的顯式參數(shù),比如文件句柄繼承。
CreateProcess API 創(chuàng)建一個包含有一個或多個在此進程的上下文中運行的線程的新進程,子進程與父進程之間沒有關系。在 Windows NT/2000/XP 上,返回的進程 ID 是 Win32 進程 ID。在 Windows ME 上,返回的進程 ID 是除去了高位(high-order bit)的 Win32 進程 ID。當創(chuàng)建的進程終止時,所有與此進程相關的數(shù)據(jù)都從內存中刪除。
為了在 Linux 中創(chuàng)建一個新的進程, fork()
系統(tǒng)調用會復制那個進程。新進程創(chuàng)建后,父進程和子進程的關系就會自動建立,子進程默認繼承父進程的所有屬性。Linux 使用一個不帶任何參數(shù)的調用創(chuàng)建新的進程。 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 |
創(chuàng)建進程服務
在 Win32 中, CreateProcess()
的第一個參數(shù)指定要運行的程序,第二個參數(shù)給出命令行參數(shù)。CreateProcess 將其他進程參數(shù)作為參數(shù)。倒數(shù)第二個參數(shù)是一個指向某個
STARTUPINFORMATION 結構體的指針,它為進程指定了標準的設備以及其他關于進程環(huán)境的啟動信息。在將
STARTUPINFORMATION 結構體的地址傳給 CreateProcess
以重定向進程的標準輸入、標準輸出和標準錯誤之前,您需要設置這個結構體的 hStdin、hStdout 和 hStderr
成員。最后一個參數(shù)是一個指向某個 PROCESSINFORMATION
結構體的指針,由被創(chuàng)建的進程為其添加內容。進程一旦啟動,它將包含創(chuàng)建它的進程的句柄以及其他內容。
|
在 Linux 中,進程 ID 是一個整數(shù)。Linux 中的搜索目錄由 PATH 環(huán)境變量(exec_path_name)決定。 fork()
函數(shù)建立父進程的一個副本,包括父進程的數(shù)據(jù)空間、堆和棧。 execv()
子例程使用 exec_path_name 將調用進程當前環(huán)境傳遞給新的進程。
這個函數(shù)用一個由 exec_path_name 指定的新的進程映像替換當前的進程映像。新的映像構造自一個由 exec_path_name 指定的正規(guī)的、可執(zhí)行的文件。由于調用的進程映像被新的進程映像所替換,所以沒有任何返回。
|
終止進程服務
在 Win32 進程中,父進程和子進程可能需要單獨訪問子進程所繼承的由某個句柄標識的對象。父進程可以創(chuàng)建一個可訪問而且可繼承的副本句柄。Win32 示例代碼使用下面的方法終止進程:
- 使用 OpenProcess 來獲得指定進程的句柄。
- 使用 GetCurrentProcess 獲得其自己的句柄。
- 使用 DuplicateHandle 來獲得一個來自同一對象的句柄作為原始句柄。
如果函數(shù)成功,則使用 TerminateThread 函數(shù)來釋放同一進程上的主線程。然后使用 TerminateThread 函數(shù)來無條件地使一個進程退出。它啟動終止并立即返回。
|
在 Linux 中,使用 kill 子例程發(fā)送 SIGTERM 信號來終止特定進程(processId)。然后調用設置 WNOHANG 位的 waitpid 子例程。這將檢查特定的進程并終止。
|
進程依然存在服務
Win32 OpenProcess 返回特定進程(processId)的句柄。如果函數(shù)成功,則 GetExitCodeProcess 將獲得特定進程的狀態(tài),并檢查進程的狀態(tài)是否是 STILL_ACTIVE。
|
在 Linux 中,使用 kill 子例程發(fā)送通過 Signal
參數(shù)指定的信號給由 Process
參數(shù)(processId)指定的特定進程。Signal 參數(shù)是一個 null 值,會執(zhí)行錯誤檢查,但不發(fā)送信號。
|
線程模型
線程 是系統(tǒng)分配 CPU 時間的基本單位;當?shù)却{度時,每個線程保持信息來保存它的“上下文”。每個線程都可以執(zhí)行程序代碼的任何部分,并共享進程的全局變量。
構建于 clone()
系統(tǒng)調用之上的 LinuxThreads 是一個 pthreads 兼容線程系統(tǒng)。因為線程由內核來調度,所以 LinuxThreads
支持阻塞的 I/O 操作和多處理器。不過,每個線程實際上是一個 Linux
進程,所以一個程序可以擁有的線程數(shù)目受內核所允許的進程總數(shù)的限制。Linux 內核沒有為線程同步提供系統(tǒng)調用。Linux Threads
庫提供了另外的代碼來支持對互斥和條件變量的操作(使用管道來阻塞線程)。
對有外加 LinuxThreads 的信號處理來說,每個線程都會繼承信號處理器(如果派生這個線程的父進程注冊了一個信號處理器的話。只有在 Linux Kernel 2.6 和更高版本中支持的新特性才會包含 POSIX 線程支持,比如 用于 Linux 的 Native POSIX Thread Library(NPTL)。
線程同步、等待函數(shù)、線程本地存儲以及初始化和終止抽象是線程模型的重要部分。在這些之下,線程服務只負責:
- 新線程被創(chuàng)建,threadId 被返回。
- 通過調用 pthread_exit 函數(shù)可以終止當前的新線程。
Win32 | Linux |
_beginthread | pthread_attr_init pthread_attr_setstacksize pthread_create |
_endthread | pthread_exit |
TerminateThread | pthread_cancel |
GetCurrentThreadId | pthread_self |
線程創(chuàng)建
Win32 應用程序使用 C 運行期庫,而不使用 Create_Thread API。使用了 _beginthread 和 _endthread 例程。這些例程會考慮任何可重入性(reentrancy)和內存不足問題、線程本地存儲、初始化和終止抽象。
Linux 使用 pthread 庫調用 pthread_create()
來派生一個線程。
threadId 作為一個輸出參數(shù)返回。為創(chuàng)建一個新線程,要傳遞一組參數(shù)。當新線程被創(chuàng)建時,這些參數(shù)會執(zhí)行一個函數(shù)。stacksize 用作新線程的棧的大?。ㄒ宰止?jié)為單位),當新線程開始執(zhí)行時,實際的參數(shù)被傳遞給函數(shù)。
指定線程程序(函數(shù))
進
行創(chuàng)建的線程必須指定要執(zhí)行的新線程的啟動函數(shù)的代碼。啟動地址是 threadproc 函數(shù)(帶有一個單獨的參數(shù),即
threadparam)的名稱。如果調用成功地創(chuàng)建了一個新線程,則返回 threadId。Win32 threadId 的類型定義是
HANDLE。Linux threadId 的類型定義是 pthread_t。
- threadproc
- 要執(zhí)行的線程程序(函數(shù))。它接收一個單獨的 void 參數(shù)。
- threadparam
- 線程開始執(zhí)行時傳遞給它的參數(shù)。
設置棧大小
在 Win32 中,線程的棧由進程的內存空間自動分配。系統(tǒng)根據(jù)需要增加棧的大小,并在線程終止時釋放它。在 Linux 中,棧的大小在 pthread 屬性對象中設置;pthread_attr_t 傳遞給庫調用 pthread_create()
。
|
終止線程服務
在
Win32 中,一個線程可以使用 TerminateThread
函數(shù)終止另一個線程。不過,線程的棧和其他資源將不會被收回。如果線程終止自己,則這樣是可取的。在 Linux 中,pthread_cancel
可以終止由具體的 threadId 所標識的線程的執(zhí)行。
Win32 | Linux |
TerminateThread((HANDLE *) threadId, 0); | pthread_cancel(threadId); |
線程狀態(tài)
在 Linux 中,線程默認創(chuàng)建為可合并(joinable)狀態(tài)。另一個線程可以使用 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); |
結束當前線程服務的執(zhí)行
在 Win32 中,使用 _endthread()
來結束當前線程的執(zhí)行。在 Linux 中,推薦使用 pthread_exit()
來退出一個線程,以避免顯式地調用 exit 例程。在 Linux 中,線程的返回值是 retval,可以由另一個線程調用 pthread_join()
來獲得它。
Win32 | Linux |
_endthread(); | pthread_exit(0); |
獲得當前線程 ID 服務
在 Win32 進程中,GetCurrentThreadId 函數(shù)獲得進行調用的線程的線程標識符。Linux 使用 pthread_self()
函數(shù)來返回進行調用的線程的 ID。
Win32 | Linux |
GetCurrentThreadId() | pthread_self() |
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 函數(shù)掛起 當前線程,直到下面事件之一發(fā)生:
- 一個 I/O 完成回調函數(shù)被調用。
- 一個異步過程調用(asynchronous procedure call,APC)排隊到此線程。
- 最小超時時間間隔已經過去。
Linux 使用 sched_yield 完成同樣的事情。
Win32 | Linux |
SleepEx (0,0) | sched_yield() |
共享內存服務
共享內存允許多個進程將它們的部分虛地址映射到一個公用的內存區(qū)域。任何進程都可以向共享內存區(qū)域寫入數(shù)據(jù),并且數(shù)據(jù)可以由其他進程讀取或修改。共享內存用于實現(xiàn)進程間通信媒介。不過,共享內存不為使用它的進程提供任何訪問控制。使用共享內存時通常會同時使用“鎖”。
一個典型的使用情形是:
- 某個服務器創(chuàng)建了一個共享內存區(qū)域,并建立了一個共享的鎖對象。
- 某個客戶機連接到服務器所創(chuàng)建的共享內存區(qū)域。
- 客戶機和服務器雙方都可以使用共享的鎖對象來獲得對共享內存區(qū)域的訪問。
- 客戶機和服務器可以查詢共享內存區(qū)域的位置。
Win32 | Linux |
CreateFileMaping, OpenFileMapping |
mmap shmget |
UnmapViewOfFile | munmap shmdt |
MapViewOfFile | mmap shmat |
創(chuàng)建共享內存資源
Win32 通過共享的內存映射文件來創(chuàng)建共享內存資源。Linux 使用 shmget/mmap 函數(shù)通過直接將文件數(shù)據(jù)合并入內存來訪問文件。內存區(qū)域是已知的作為共享內存的段。
文件和數(shù)據(jù)也可以在多個進程和線程之間共享。不過,這需要進程或線程之間同步,由應用程序來處理。
如果資源已經存在,則 CreateFileMapping()
重新初始化共享資源對于進程的約定。如果沒有足夠的空閑內存來處理錯誤的共享資源,此調用可能會失敗。 OpenFileMapping()
需要共享資源必須已經存在;這個調用只是請求對它的訪問。
在 Win32 中,CreateFileMapping 不允許您增加文件大小,但是在 Linux 中不是這樣。在 Linux 中,如果資源已經存在,它將被重新初始化。它可能被銷毀并重新創(chuàng)建。Linux 創(chuàng)建可以通過名稱訪問的共享內存。 open()
系統(tǒng)調用確定映射是否可讀或可寫。傳遞給 mmap()
的參數(shù)必須不能與 open()
時請求的訪問相沖突。 mmap()
需要為映射提供文件的大小(字節(jié)數(shù))。
對 32-位內核而言,有 4GB 虛地址空間。最前的 1 GB 用于設備驅動程序。最后 1 GB 用于內核數(shù)據(jù)結構。中間的 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 上增加共享內存的一個示例:
|
下面是創(chuàng)建共享內存資源的 Win32 示例代碼,以及相對應的 Linux nmap 實現(xiàn)。
|
刪除共享內存資源
為
銷毀共享內存資源,munmap 子例程要取消被映射文件區(qū)域的映射。munmap 子例程只是取消對 mmap
子例程的調用而創(chuàng)建的區(qū)域的映射。如果某個區(qū)域內的一個地址被 mmap
子例程取消映射,并且那個區(qū)域后來未被再次映射,那么任何對那個地址的引用將導致給進程發(fā)出一個 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 在美國和/或其它國家或地區(qū)的商標。
UNIX 是 The Open Group 在美國和其它國家或地區(qū)的注冊商標。
Microsoft 和 Windows 是 Microsoft Corporation 在美國和/或其它國家或地區(qū)的商標或注冊商標。
所有其他商標和注冊商標是它們相應公司的財產。
此 出版物/說明是在美國完成的。IBM 可能不在其他國家或地區(qū)提供在此討論的產品、程序、服務或特性,而且信息可能會不加聲明地加以修改。有關您當前所在區(qū)域的產品、程序、服務和特性的信息, 請向您當?shù)氐?IBM 代表咨詢。任何對 IBM 產品、程序、服務或者特性的引用并非意在明示或暗示只能使用 IBM 的產品、程序、服務或者特性。只要不侵犯 IBM 的知識產權,任何同等功能的產品、程序、服務或特性,都可以代替 IBM 產品、程序、服務或特性。
涉 及非 IBM 產品的信息可從這些產品的供應商、其出版說明或其他可公開獲得的資料中獲取,并不構成 IBM 對此產品的認可。非 IBM 價目及性能數(shù)字資源取自可公開獲得的信息,包括供應商的聲明和供應商的全球主頁。 IBM 沒有對這些產品進行測試,也無法確認其性能的精確性、兼容性或任何其他關于非 IBM 產品的聲明。有關非 IBM 產品性能的問題應當向這些產品的供應商提出。
有關非 IBM 產品性能的問題應當向這些產品的供應商提出。IBM 公司可能已擁有或正在申請與本說明中描述的內容有關的各項專利。提供本說明并未授予用戶使用這些專利的任何許可。您可以用書面方式將許可查詢寄往: IBM Director of Licensing IBM Corporation North Castle Drive Armonk, NY 10504-1785 U.S.A。所有關于 IBM 未來方向或意向的聲明都可隨時更改或收回,而不另行通知,它們僅僅表示了目標和意愿而已。聯(lián)系您本地的 IBM 辦公人員或者 IBM 授權的轉銷商,以獲得特定的 Statement of General Direction 的全文。
本說明中所包含的信息沒有提交 給任何正式的 IBM 測試,而只是“按原樣”發(fā)布。雖然 IBM 可能為了其在特定條件下的精確性而已經對每個條目進行了檢查,但不保證在其他地方可以獲得相同的或者類似的結果。使用此信息或者實現(xiàn)這里所描述的任何技術 是客戶的責任,取決于客戶評價并集成它們到客戶的操作環(huán)境的能力。嘗試為他們自己的環(huán)境而修改這些技術的客戶,這樣做所帶來的風險由他們自行承擔。
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。