將 Linux 應用程式移植到 64 位系統上
Linux 是可以使用 64 位處理器的跨平臺作業系統之一,現在 64 位的系統在伺服器和桌面端都已經非常常見了。很多開發人員現在都面臨著需要將自己的應用程式從 32 位環境移植到 64 位環境中。隨著 Intel® Itanium® 和其他 64 位處理器的引入,使軟體針對 64 位環境做好準備變得日益重要了。
與 UNIX® 和其他類 UNIX 作業系統一樣,Linux 使用了 LP64 標準,其中指標和長整數都是 64 位的,而普通的整數則依然是 32 位的。儘管有些高階語言並不會受到這種型別大小不同的影響,但是另外一些語言(例如 C 語言)卻的確會受到這種影響。
將應用程式從 32 位系統移植到 64 位系統上的工作可能會非常簡單,也可能會非常困難,這取決於這些應用程式是如何編寫和維護的。很多瑣碎的問題都可能導致產生問題,即使在一個編寫得非常好的高度可移植的應用程式中也是如此,因此本文將對這些問題進行歸納總結,並給出解決這些問題的一些方法建議。
32 位平臺有很多限制,這些限制正在阻礙大型應用程式(例如資料庫)開發人員的工作進展,尤其對那些希望充分利用計算機硬體優點的開發人員來說更是如此。科學計算通常要依賴於浮點計算,而有些應用程式(例如金融計算)則需要一個比較狹窄的數字範圍,但是卻要求更高的精度,其精度高於浮點數所提供的精度。64 位數學運算提供了這種更高精度的定點數學計算,同時還提供了足夠的數字範圍。現在在計算機業界中有很多關於 32 位地址空間所表示的地址空間的討論。32 位指標只能定址 4GB 的虛擬地址空間。我們可以克服這種限制,但是應用程式開發就變得非常複雜了,其效能也會顯著降低。
在語言實現方面,目前的 C 語言標準要求 “long long” 資料型別至少是 64 位的。然而,其實現可能會將其定義為更大。
另外一個需要改進的地方是日期。在 Linux 中,日期是使用 32 位整數來表示的,該值所表示的是從 1970 年 1 月 1 日至今所經過的秒數。這在 2038 年就會失效。但是在 64 位的系統中,日期是使用有符號的 64 位整數表示的,這可以極大地擴充其可用範圍。
總之,64 位具有以下優點:
- 64 位的應用程式可以直接訪問 4EB 的虛擬記憶體,Intel Itanium 處理器提供了連續的線性地址空間。
- 64 位的 Linux 允許檔案大小最大達到 4 EB(2 的 63 次冪),其重要的優點之一就是可以處理對大型資料庫的訪問。
不幸的是,C 程式語言並沒有提供一種機制來新增新的基本資料型別。因此,提供 64 位的定址和整數運算能力必須要修改現有資料型別的繫結或對映,或者向 C 語言中新增新的資料型別。
表 1. 32 位和 64 位資料模型
ILP32 | LP64 | LLP64 | ILP64 | |
---|---|---|---|---|
char | 8 | 8 | 8 | 8 |
short | 16 | 16 | 16 | 16 |
int | 32 | 32 | 32 | 64 |
long | 32 | 64 | 32 | 64 |
long long | 64 | 64 | 64 | 64 |
指標 | 32 | 64 | 64 | 64 |
這 3 個 64 位模型(LP64、LLP64 和 ILP64)之間的區別在於非浮點資料型別。當一個或多個 C 資料型別的寬度從一種模型變換成另外一種模型時,應用程式可能會受到很多方面的影響。這些影響主要可以分為兩類:
-
資料物件的大小。編譯器按照自然邊界對資料型別進行對齊;換而言之,32 位的資料型別在 64 位系統上要按照 32 位邊界進行對齊,而 64 位的資料型別在 64 位系統上則要按照 64 位邊界進行對齊。這意味著諸如結構或聯合之類的資料物件的大小在 32 位和 64 位系統上是不同的。
-
基本資料型別的大小。通常關於基本資料型別之間關係的假設在 64 位資料模型上都已經無效了。依賴於這些關係的應用程式在 64 位平臺上編譯也會失敗。例如,
sizeof (int) = sizeof (long) = sizeof (pointer)
的假設對於 ILP32 資料模型有效,但是對於其他資料模型就無效了。
總之,編譯器要按照自然邊界對資料型別進行對齊,這意味著編譯器會進行 “填充”,從而強制進行這種方式的對齊,就像是在 C 結構和聯合中所做的一樣。結構或聯合的成員是根據最寬的成員進行對齊的。清單 1 對這個結構進行了解釋。
清單 1. C 結構
struct test { int i1; double d; int i2; long l; } |
表 2 給出了這個結構中每個成員的大小,以及這個結構在 32 位系統和 64 位系統上的大小。
表 2. 結構和結構成員的大小
結構成員 | 在 32 位系統上的大小 | 在 64 位系統上的大小 |
---|---|---|
struct test { | ||
int i1; | 32 位 | 32 位 |
32 位填充 | ||
double d; | 64 位 | 64 位 |
int i2; | 32 位 | 32 位 |
32 位填充 | ||
long l; | 32 位 | 64 位 |
}; | 結構大小為 20 位元組 | 結構大小為 32 位元組 |
注意,在一個 32 位的系統上,編譯器可能並沒有對變數 d
進行對齊,儘管它是一個 64 位的物件,這是因為硬體會將其當作兩個 32 位的物件進行處理。然而,64 位的系統會對 d
和 l
都進行對齊,這樣會新增兩個 4 位元組的填充。
本節介紹如何解決一些常見的問題:
- 宣告
- 表示式
- 賦值
- 數字常數
- Endianism
- 型別定義
- 位移
- 字串格式化
- 函式引數
要想讓您的程式碼在 32 位和 64 位系統上都可以工作,請注意以下有關宣告的用法:
- 根據需要適當地使用 “L” 或 “U” 來宣告整型常量。
- 確保使用無符號整數來防止符號擴充套件的問題。
- 如果有些變數在這兩個平臺上都需要是 32 位的,請將其型別定義為 int。
- 如果有些變數在 32 位系統上是 32 位的,在 64 位系統上是 64 位的,請將其型別定義為 long。
- 為了對齊和效能的需要,請將數字變數宣告為 int 或 long 型別。不要試圖使用 char 或 short 型別來儲存位元組。
- 將字元指標和字元位元組宣告為無符號型別的,這樣可以防止 8 位字元的符號擴充套件問題。
在 C/C++ 中,表示式是基於結合律、操作符的優先順序和一組數學計算規則的。要想讓表示式在 32 位和 64 位系統上都可以正確工作,請注意以下規則:
- 兩個有符號整數相加的結果是一個有符號整數。
- int 和 long 型別的兩個數相加,結果是一個 long 型別的數。
- 如果一個運算元是無符號整數,另外一個運算元是有符號整數,那麼表示式的結果就是無符號整數。
- int 和 doubule 型別的兩個數相加,結果是一個 double 型別的數。此處 int 型別的數在執行加法運算之前轉換成 double 型別。
由於指標、int 和 long 在 64 位系統上大小不再相同了,因此根據這些變數是如何賦值和在應用程式中使用的,可能會出現問題。下面是有關賦值的一些技巧:
-
不要交換使用 int 和 long 型別,因為這可能會導致高位數字被截斷。例如,不要做下面的事情:
int i; long l; i = l;
-
不要使用 int 型別來儲存指標。下面這個例子在 32 位系統上可以很好地工作,但是在 64 位系統上會失敗,這是因為 32 位整數無法存放 64 位的指標。例如,不要做下面的事情:
unsigned int i, *ptr; i = (unsigned) ptr;
-
不要使用指標來存放 int 型別的值。例如,不要做下面的事情;
int *ptr; int i; ptr = (int *) i;
-
如果在表示式中混合使用無符號和有符號的 32 位整數,並將其賦值給一個有符號的 long 型別,那麼將其中一個運算元轉換成 64 位的型別。這會導致其他運算元也被轉換成 64 位的型別,這樣在對錶達式進行賦值時就不需要再進行轉換了。另外一種解決方案是對整個表示式進行轉換,這樣就可以在賦值時進行符號擴充套件。例如,考慮下面這種用法可能會出現的問題:
long n; int i = -2; unsigned k = 1; n = i + k;
從數學計算上來說,上面這個黑體顯示的表示式的結果應該是 -1 。但是由於表示式是無符號的,因此不會進行符號擴充套件。解決方案是將一個運算元轉換成 64 位型別(下面的第一行就是這樣),或者對整個表示式進行轉換(下面第二行):
n = (long) i + k; n = (int) (i + k);
16 進位制的常量通常都用作掩碼或特殊位的值。如果一個沒有字尾的 16 進位制的常量是 32 位的,並且其高位被置位了,那麼它就可以作為無符號整型進行定義。
例如,常數 OxFFFFFFFFL 是一個有符號的 long 型別。在 32 位系統上,這會將所有位都置位(每位全為 1),但是在 64 位系統上,只有低 32 位被置位了,結果是這個值是 0x00000000FFFFFFFF。
如果我們希望所有位全部置位,那麼一種可移植的方法是定義一個有符號的常數,其值為 -1。這會將所有位全部置位,因為它採用了二進位制補碼演算法。
long x = -1L; |
可能產生的另外一個問題是最高位的設定。在 32 位系統上,我們使用的是常量 0x80000000。但是可移植性更好的方法是使用一個位移表示式:
1L << ((sizeof(long) * 8) - 1); |
Endianism 是指用來儲存資料的方法,它定義了整數和浮點資料型別中是如何對位元組進行定址的。
Little-endian 是將低位位元組儲存在記憶體的低地址中,將高位位元組儲存在記憶體的高地址中。
Big-endian 是將高位位元組儲存在記憶體的低地址中,將低位位元組儲存在記憶體的高地址中。
表 3 給出了一個 64 位長整數的佈局示例。
表 3. 64 位 long int 型別的佈局
低地址 | 高地址 | |||||||
---|---|---|---|---|---|---|---|---|
Little endian | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
Big endian | Byte 7 | Byte 6 | Byte 5 | Byte 4 | Byte 3 | Byte 2 | Byte 1 | Byte 0 |
例如,32 位的字 0x12345678 在 big endian 機器上的佈局如下:
表 4. 0x12345678 在 big-endian 系統上的佈局
記憶體偏移量 | 0 | 1 | 2 | 3 |
記憶體內容 | 0x12 | 0x34 | 0x56 | 0x78 |
如果將 0x12345678 當作兩個半字來看待,分別是 0x1234 和 0x5678,那麼就會看到在 big endian 機器上是下面的情況:
表 5. 0x12345678 在 big-endian 系統上當作兩個半字來看待的情況
記憶體偏移量 | 0 | 2 |
記憶體內容 | 0x1234 | 0x5678 |
然而,在 little endian 機器上,字 0x12345678 的佈局如下所示:
表 6. 0x12345678 在 little-endian 系統上的佈局
記憶體偏移量 | 0 | 1 | 2 | 3 |
記憶體內容 | 0x78 | 0x56 | 0x34 | 0x12 |
類似地,兩個半字 0x1234 和 0x5678 如下所示:
表 7. 0x12345678 在 little-endian 系統上作為兩個半字看到的情況
記憶體偏移量 | 0 | 2 |
記憶體內容 | 0x3412 | 0x7856 |
下面這個例子解釋了 big endian 和 little endian 機器上位元組順序之間的區別。
下面的 C 程式在一臺 big endian 機器上進行編譯和執行時會列印 “Big endian”,在一臺 little endian 機器上進行編譯和執行時會列印 “Little endian”。
清單 2. big endian 與 little endian
#include <stdio.h> main () { int i = 0x12345678; if (*(char *)&i == 0x12) printf ("Big endian\n"); else if (*(char *)&i == 0x78) printf ("Little endian\n"); } |
Endianism 在以下情況中非常重要:
- 使用位掩碼時
- 物件的間接指標地址部分
在 C 和 C++ 中有位域來幫助處理 endian 的問題。我建議使用位域,而不要使用掩碼域或 16 進位制的常量。有幾個函式可以用來將 16 位和 32 位資料從 “主機位元組順序” 轉換成 “網路位元組順序”。例如,htonl (3)
、ntohl (3)
用來轉換 32 位整數。類似地,htons
(3)
、ntohs (3)
用來轉換 16 位整數。然而,對於 64 位整數來說,並沒有標準的函式集。但是在 big endian 和 little endian 系統上,Linux 都提供了下面的幾個巨集:
- bswap_16
- bswap_32
- bswap_64
建議您不要使用 C/C++ 中那些在 64 位系統上會改變大小的資料型別來編寫應用程式,而是使用一些型別定義或巨集來顯式地說明變數中所包含的資料的大小和型別。有些定義可以使程式碼的可移植性更好。
-
ptrdiff_t
:
這是一個有符號整型,是兩個指標相減後的結果。 -
size_t
:
這是一個無符號整型,是執行sizeof
操作的結果。這在向一些函式(例如malloc (3)
)傳遞引數時使用,也可以從一些函式(比如fred (2)
)中返回。 -
int32_t
、uint32_t
等:
定義具有預定義寬度的整型。 -
intptr_t
和uintptr_t
:
定義整型型別,任何有效指標都可以轉換成這個型別。
例 1:
在下面這條語句中,在對 bufferSize
進行賦值時,從 sizeof
返回的 64 位值被截斷成了 32 位。
int bufferSize = (int) sizeof (something);
解決方案是使用 size_t
對返回值進行型別轉換,並將其賦給宣告為 size_t
型別的 bufferSize,如下所示:
size_t bufferSize = (size_t) sizeof (something);
例 2:
在 32 位系統上,int 和 long 大小相同。由於這一點,有些開發人員會交換使用這兩種型別。這可能會導致指標被賦值給 int 型別,或者反之。但是在 64 位的系統上,將指標賦值給 int 型別會導致截斷高 32 位的值。
解決方案是將指標作為指標型別或為此而定義的特殊型別進行儲存,例如 intptr_t
和 uintptr_t
。
無型別的整數常量就是 (unsigned) int 型別的。這可能會導致在位移時出現被截斷的問題。
例如,在下面的程式碼中,a
的最大值可以是 31。這是因為 1 << a
是 int 型別的。
long t = 1 << a;
要在 64 位系統上進行位移,應該使用 1L
,如下所示:
long t = 1L << a;
函式 printf (3)
及其相關函式都可能成為問題的根源。例如,在 32 位系統上,使用 %d
來列印 int 或 long 型別的值都可以,但是在 64 位平臺上,這會導致將 long 型別的值截斷成低 32 位的值。對於 long 型別的變數來說,正確的用法是 %ld
。
類似地,當一個小整數(char、short、int)被傳遞給 printf (3)
時,它會擴充套件成 64 位的,符號會適當地進行擴充套件。在下面的例子中,printf (3)
假設指標是 32 位的。
char *ptr = &something;
printf (%x\n", ptr);
上面的程式碼在 64 位系統上會失敗,它只會顯示低 4 位元組的內容。
這個問題的解決方案是使用 %p
,如下所示;這在 32 位和 64 位系統上都可以很好地工作:
char *ptr = &something;
printf (%p\n", ptr);
在向函式傳遞引數時需要記住幾件事情:
- 在引數的資料型別是由函式原型定義的情況中,引數應該根據標準規則轉換成這種型別。
- 在引數型別沒有指定的情況中,引數會被轉換成更大的型別。
- 在 64 位系統上,整型被轉換成 64 位的整型值,單精度的浮點型別被轉換成雙精度的浮點型別。
- 如果返回值沒有指定,那麼函式的預設返回值是 int 型別的。
在將有符號整型和無符號整型的和作為 long 型別傳遞時就會出現問題。考慮下面的情況:
清單 3. 將有符號整型和無符號整型的和作為 long 型別傳遞
long function (long l); int main () { int i = -2; unsigned k = 1U; long n = function (i + k); } |
上面這段程式碼在 64 位系統上會失敗,因為表示式 (i + k)
是一個無符號的 32 位表示式,在將其轉換成 long 型別時,符號並沒有得到擴充套件。解決方案是將一個運算元強制轉換成 64 位的型別。
在基於暫存器的系統上還有一個問題:系統採用暫存器而不是堆疊來向函式傳遞引數。考慮下面的例子:
float f = 1.25;
printf ("The hex value of %f is %x", f, f);
在基於堆疊的系統中,這會列印對應的 16 進位制值。但是在基於暫存器的系統中,這個 16 進位制的值會從一個整數暫存器中讀取,而不是從浮點暫存器中讀取。
解決方案是將浮點變數的地址強制轉換成一個指向整型型別的指標,如下所示:
printf ("The hex value of %f is %x", f, *(int *)&f);
主流的硬體供應商最近都在擴充自己的 64 位產品,這是因為 64 位平臺可以提供更好的效能、價值和可伸縮性。32 位系統的限制,特別是 4GB 的虛擬記憶體上限,已經極大地刺激很多公司開始考慮遷移到 64 位平臺上。瞭解如何將應用程式移植到 64 位體系結構上可以幫助我們編寫可移植性更好且效率更高的程式碼。
學習
-
您可以參閱本文在 developerWorks 全球站點上的 英文原文。
-
64-Bit Programming Models: Why LP64? 介紹了有關各種 64 位程式設計模型的更多細節知識和有關 LP64 的爭論。
-
請在 Wikipedia 上了解 32 位系統所具有的 2038 年問題。
-
請閱讀 “將企業應用程式從 UNIX 移植到 Linux”(developerWorks,2005 年 2 月),瞭解有關將大型多執行緒應用程式移植到 Linux 上的技巧和內幕。
-
“Porting Intel applications to 64 bit Linux PowerPC” 討論了在將 Linux 從 IA32 移植到 PowerPC 上時所要考慮的一些問題。
-
Linux Online(linux.org) Linux distributions site 提供了有關發行版的豐富資訊,包括 64 位系統上的發行版。
-
developerWorks Linux on Power Architecture developer's corner 是為在基於 POWER 硬體的 Linux 上執行的應用程式的程式設計師和開發人員準備的參考資料。
-
penguinppc.org 是專為 PowerPC 系統上的 Linux 使用者準備的一個社群站點。
-
在 developerWorks Linux 專區 中可以找到為 Linux 開發人員準備的更多參考資料。
-
隨時關注 developerWorks 技術事件和網路廣播。
獲得產品和技術
-
在您的下一個 Linux 開發專案中採用 IBM 試用版軟體,這可以從 developerWorks 上直接下載。
討論
-
通過參與 developerWorks blogs 來加入 developerWorks 社群。
相關文章
- 如何移植32位程式到64位系統薦
- 如何在64位Linux系統上用gcc編譯32位程式LinuxGC編譯
- 將 Win32 程式移植到 LinuxWin32Linux
- 倪光南院士建議將Windows應用移植到國產作業系統Windows作業系統
- 如何將 Google Auto Draw 移植到小程式上Go
- Solaris到Linux應用的移植(轉)Linux
- Windows是如何將64位Ntdll對映到32位程式的Windows
- 使用visual c++ 2005遠端除錯64位系統上32位與64位程式C++除錯
- <摘錄>如何在64位linux強制編譯32位應用程式Linux編譯
- Win10 32位系統是否可以直接升級到64位|windows10 32位系統怎麼升級64位系統Win10Windows
- 未來蘋果裝置將移植 Linux 系統蘋果Linux
- VMWare在32位INTER CPU上虛擬64位系統
- 啟動64位 IIS 32位應用程式的支援
- 在Linux 64位系統下使用hugepageLinux
- 解決windows server2003 64位作業系統上不能載入32位應用程式dll 的問題WindowsServer作業系統
- 上海一公司需要將原系統移植到Websphere平臺Web
- 把Iptables移植到嵌入式Linux系統(轉)Linux
- 【HugePage】在Linux 64位系統下使用hugepageLinux
- 微軟透露Windows 10 on ARM將不會支援X86 64位應用程式微軟Windows
- 在Linux作業系統上執行Windows應用程式(轉)Linux作業系統Windows
- Linux中如何檢視系統是32位還是64位?Linux
- 嵌入式linux應用程式移植方法總結Linux
- 【原創】EtherCAT主站IgH解析(二)-- 如何將Igh移植到Linux/Windows/RTOS等多作業系統移植指南LinuxWindows作業系統
- linux檢視核心版本、系統版本、系統位數(32or64)Linux
- 在64位作業系統上安裝ArcGISServer9.3.1作業系統Server
- Linux 根檔案系統的移植(從入門到精通)Linux
- 把 Linux 移植到蘋果 M1 Mac 上Linux蘋果Mac
- mysql++在64位作業系統上的安裝指南MySql作業系統
- win10系統32位和64位區別 win10系統32位和64位哪個好Win10
- 64位系統如何裝pythonPython
- Socket相關程式:從Windows移植到LinuxWindowsLinux
- 【日常分享】Linux系統檢視是32位還是64位的方法Linux
- Linux GCC 64位程式設計技巧LinuxGC程式設計
- 手機移植平板技術 電視裝上 Linux 系統(轉)Linux
- 如何移植uCOS-III到Linux系統 How to Port uCOS-III on Linux OSLinux
- liunx核心移植(三)——核心、驅動、應用程式、根檔案系統的關係
- 32位程式注入64位程式
- 如何將複雜的應用邏輯從儲存過程移植到業務層儲存過程