Ubuntu---Native POSIX線程庫
在2.6版本以前發布的Linux內核中,Linux線程庫叫做LinuxThreads,為glibc2.0以后的GNU C庫所支持。該庫雖然使用了POSIX API,但是并不真正遵循POSIX標準。從2.6內核開始,Linux引入了NPTL。它比LinuxThreads在性能上有了很大的提高,也更遵循POSIX標準。但是,僅僅使用2.6內核并不等于使用了NPTL。盡管有些發行版會同時攜帶NPTL和LinuxThreads, 但所有現代的Linux發行版都缺省攜帶NPTL。
用下面的命令可以查看你的系統上正在使用的POSIX實現:
編輯請注意,該代碼中需要翻譯的內容為:
(1)“This was returned from SUSE 9.1 installation”翻譯成“這是SUSE 9.1返回的結果”
(2)“This was returned from Fedora 2.6.9-1.667 Instatllation”翻譯成“翻譯成“這是Fedora 2.6.9-1.667返回的結果”
(3)“This was returned from an old RedHat installation” “翻譯成“這是一個老版本的RedHat返回的結果”
$ getconf GNU_LIBPATHREAD_VERSION
$ getconf GNU_LIBPATHREAD_VERSION
$ getconf GNU_LIBPATHREAD_VERSION
用下面的方法可以查看正在使用的Linux發行版是用什么編譯工具編譯鏈接的。
要找到/bin/ls鏈接的libpthreads庫,如下:
(代碼)(P81倒數第14行)
$ ldd /bin/ls |grep libc.so.6
從上面的輸出內容可以看到,libc.so.6是和“Native POSIX Threads Library by Ulrich Drepper”一起鏈接的。
NPTL實現了一對一的線程模型;也就是說,一個用戶線程對應一個內核線程。NPTL也實現了POSIX進程間的同步原語,而且線程選項PTHREAD_PROCESS_SHARED也被明確支持了。
3.9.1 最大線程數
在Linux上一個應用程序能夠創建的線程最大數量在不同的發行版上是不同的。運行在2個CPU 2GB內存上的SUSE9.1在pthread_create返回EAGAIN錯誤前允許創建16317個線程。EAGAIN錯誤的意思是說應用程序可能超過了系統的某些限制。該應用程序創建線程的棧大小為16384,這是創建線程時允許的最大棧大小。在把棧大小設置為16384前,該應用程序會在創建第1021個線程時失敗,并返回錯誤碼ENOMEM。任何情況下,8000到16000個線程已經足以滿足任何應用程序的需求。
要在Linux上創建大數量的線程,需要先做下面的事情:
1. 創建正確的棧大小(注釋17);
2. 檢查ulimit(可以輸出內存、棧大小限制等的工具),修改對應項或編輯/etc/security/limits.conf文件。
第4、5、6章更詳細的講述了POSIX線程的內容,以及它與NPTL的區別。
#p#
3.10 國際化(I18N)(注釋18)和本地化
I18N對項目移植會有多大的影響在很大程度上取決于待移植的應用程序。如果一個應用程序僅需要一些簡單的消息目錄(message catalogue)轉換、時期和時間顯示,或使用正則表達式進行簡單的文本串查找,那么把這些功能從UNIX平臺移植到Linux上還是比較容易的。但是,如果該應用程序進行了復雜的文本分析,就像有時在文本編輯器理使用的那樣,移植這些內容將會是比較困難的,見下一段的分析。
Linux遵循ISO對標準locale名稱的命名規范,[locale]_[territory].[codeset]。其中,locale由兩個字符組成,代表言語;territory由兩字符組成,代表國別。例如en_US.iso885915和zh_CN.gb18030。但是,每個系統上可用的locale和locale的內容是各不相同的。移植使用了locale復雜應用的應用程序可能需要學習具體的語言規范和翻譯規則,甚至需要修改Linux上已有的locale才能使移植后的應用程序像在源系統上那樣運行。
表3-4列出了支持的GNU libc國際化函數。linux.ctocio.com.cn/imagelist/2009/168/5bb91ws241x2.pdf" target=_blank>表3-4.pdf
在Linux上,可以使用locale –a命令察看系統上安裝的locale,解碼文件可以在/usr/share/locale/locale.alias里找到。
3.10.1 iconv支持
Linux提供了iconv(1)工具把傳統的字符串編碼轉換統一碼(unicode)??梢杂胕conv –list命令得到當前Linux上實現的字符串編碼列表。
有些時候,僅使用iconv工具轉換字符串編碼是不夠的。有些應用程序,例如郵件發送器、網頁接口,需要在兩種不同的編碼間互相轉換。GNU libc提供了內部字符串編碼(unicode,統一碼)和外部字符串編碼(傳統編碼)間互相轉換的功能。
程序3-4給出了如何使用libicon API的示例。
(代碼)(P85-87)
編譯代碼:
(代碼)(P87倒數第6行)
$ gcc iconv_samp.c –o iconv_samp
使用生成的程序來轉換一個輸入文件:
(代碼)(P87倒數第4行)
$ ./iconv_samp WINDOWS-1256 ISO_8859-16 < input-file
3.10.2 如何創建消息目錄(message catalog)(注釋19)
消息目錄是一個文件,用來把應用程序語言相關的輸出內容轉換成系統locale設置的語言。程序3-5給出了一個在Linux上如何用GNU xgettext和msgfmt工具創建消息目錄的示例。
(代碼)(P88第3行)
該例中,我們要以西班牙語輸出本書的書名和作者。首先,我們對示例程序運行xgettext:
(代碼)(P88第22行)
$ xgettext hello.c
這會生成一個叫message.po的文件:
(代碼)(P88第24行)
$ cat message.po
編輯文件message.po,對每個要翻譯的消息修改msgstr,并編輯charset(如果在運行xgettext前沒有設置的話)。然后對message.po文件運行msgfmt命令。文件my_messages.mo指向的是bindtextdomain()設置的域。
(代碼)(P89第16行)
$ msgfmt –v –o my_message.mo message.po
為運行示例程序,我們在本地目錄中為西班牙語(哥斯達黎加)創建一個目錄(不使用缺省的/usr/lib/locale目錄,因為我們沒有root權限):
(代碼)(P89第20行)
$ mkdir –p locale/es_CR/LC_MESSAGES
然后把my_message.mo移到該目錄下:
(代碼)(P89第22行)
$ mv my_message.mo locale/es_CR/LC_MESSAGES
為環境變量LC_MESSAGES輸出正確的值,并運行示例程序:
(代碼)(P89第25行)
$ export LC_MESSAGES=es_CR
$ ./hello
至此,我們成功地在Linux上創建了一個消息目錄文件。
#p#
3.11 大小端(Big/Little-Endian,也叫字節序)環境
Linux最初是在Intel平臺上開發的,而Intel平臺主要是一個小端(little-endian)環境,但是,現在Linux已經被移植到了很多支持大端(big-endian)的硬件平臺上。大小端,或者字節序,指的是一個數據元素及其每個單獨的字節是如何存放的。在big-endian環境中,最低地址放在多字節的最高位(或者最左側位);在little-endian環境中,最低地址放在多字節的最低位(或者最右側位)。通常來說,第0位在big-endian環境中是最高位,但是在little-endian環境中是最低位。
程序3-6給出了一個打印數據字節內容的示例。分別在big-endian和little-endian環境中編譯運行時會有不同的輸出。
(代碼)(P90第13行)
在Intel服務器(LE環境)上編譯運行時,輸出的內容是:
(代碼)(P90倒數第6行)
在IBM Power服務器(BE環境)上編譯運行時,輸出的內容是:
(代碼)(P90倒數第2行)
需要注意的是,前面的示例是使用gcc編譯的,缺省情況下生成的是32位的應用程序。
大多數基于RISC的計算機(包括IBM PowerPC服務器等)和網絡協議(Internet Protocol,IP)使用的都是BE結構,但是Intel和Alpha體系使用的是LE結構。移植軟件時,需要特別小心大小端(字節序)問題,因為這些問題通常不易發現,而且一旦發生又很難定位。
移植后的軟件通常會出現的問題包括:
- 不統一的數據引用(注釋20)
- 在BE和LE之間共享數據
- 在網絡設備(例如,IP和PCI)(注釋21)間交換數據
不統一的數據引用較多發生在用戶空間的應用程序中,而后兩種問題常常在底層代碼中遇到(例如,設備驅動程序)。不統一的數據引用往往是因為不正確的引用與大小端相關的數據類型,通常是在處理聯合或指針類型時。大小端處理得當的代碼應該包含一些定義來判斷平臺是BE或LE。一個好的編程習慣是,不要把指針強制轉換成int,并且需要時在轉換過程中明確地引用數據類型和各字節的值。
3.12 從32位移植到64位
64位Linux平臺正在逐步取代32位系統。64位的程序環境能夠非常明顯地提高內存尋址的性能和操作超大數據結構時應用程序的吞吐量。運行在IBM PowerPC和AMD 的64位體系結構上的Linux可以同時運行32位和64位應用程序,而且沒有任何性能損失。當32位環境不能為應用程序提供足夠的內存地址空間時可以把應用程序編譯成64位的,從而有效地利用Linux的上述優點。
用-m64標志可以讓gcc生成64位的目標文件,如下例:
(代碼)(P92第8行)
$ gcc –m64 sample.c –o sample.o
需要注意的是,在有些平臺上,例如IBM PowerPC,gcc編譯器缺省生成的是32位目標文件,即使運行的是64位Linux。而對于運行在AMD 64位體系上的64位Linux,gcc缺省生成的是64位目標文件。和UNIX平臺類似,64位的目標代碼只能和其它64位的目標代碼一起運行。因為地址沖突,32位的代碼不能和64位的代碼在同一個應用程序空間中運行。
32位的數據類型模型和64位的數據類型模型是不同的。32位應用程序的C語言數據類型模型是ILP32模型,其中,I代表int,L代表long,P代表指針,32表示這些數據類型都是32位的。64位應用程序的數據類型模型是LP64模型。除了int類型外,long(L)和指針(P)類型都變成了64位的。C語言的int和符點類型在兩種數據類型模型中是相同的。
#p#
3.12.1 常見的移植錯誤
在代碼不兼容的問題中,數據類型不匹配是較為常見的。這常常是因為大小端和32位到64位的問題。我們通常會遇到,32位的應用程序會假設int、long和指針類型具有同樣的字節大小。但是,long和指針類型的字節大小在LP64數據模型中變成了64位的,這個變化本身是導致ILP32到LP64問題的主因。在分析階段,要留出時間盡早找到這些不兼容的代碼。
3.12.1.1 假設LP64中int和指針具有同樣的字節大小
LP64中,指針類型(ptr)是64位的。如果沒有注意到這個區別至少會導致編譯器警告(或者更壞的情況,導致應用程序出現未定義的行為)。
來看下面的例子:
(代碼)(P93第2行)
要解決這個問題,可以把int改稱long,或者更好的方法,使用stdint.h中定義的uintptr_t。
3.12.1.2 忽略了int和long類型字節大小的不同
在ILP32環境中,int和long具有同樣的字節大小,這也很容易讓編程人員錯誤地以為在LP64中int和long也是同樣大小。
來看示例3-7。
(代碼)(p93倒數第15行)
編譯成32位應用程序運行:
(代碼)(P93倒數第7行)
gcc bad_1.c –o foo
$ ./foo
80000000
編譯成64位應用程序運行:
(代碼)(P93倒數第3行)
gcc –m64 bad_1.c –o foo
$ ./foo
ffffffff80000000
調用sizeof返回一個size_t類型的整數。因為size_t類型在LP64中變成了64位的,所以注意不要把sizeof的返回值傳給期望int類型參數的函數。否則,會被截短。
3.12.1.3 忽略符號位的擴展
示例3-7同時也演示了轉換到LP64時符號位的擴展問題。ISO C整型進位(promotion)規則表明,字符、短整數或整數位,所有有符號的或無符號的,或枚舉類型的對象,都可能和整數一起出現在某個表達式中。這種情況下,如果整形能夠表示上述所有源類型的值,則這些類型的值都會轉換成整數;否則,轉換成無符號整數。
要解決該問題,可以把1 << 31 改成 1L<<31。
3.12.1.4 字符串轉換時缺少必要的檢查
字符串函數,例如printf,sprintf,scanf,以及sscanf,用的是格式化的字符串,這些字符串需要遵循long類型規范,pencentl用于long類型參數,percentp用于指針參數。在LP64環境中不使用這些規范將導致不可預測的格式化結果。
3.12.2 最優方法
一個移植32位應用程序到64位環境的最優方案建議把移植工作分兩個步進行。第一步先把應用程序從源系統(AIX、Solaris,或HP-UX)移植到Linux上;第二步再把移植后的32位程序改成64位的。
3.13 小結
在真正的移植開始之前,對應用程序的分析可以說是最重要的工作。如果分析做得好的話,可以發現一些隱藏的陷阱,并以此來進一步完善整個項目計劃。極少數情況下,待移植的應用程序可能使用了一些平臺相關的特性,而這些特性又是Linux所不支持的。此時,需要移植人員來找到一個繞開或替代該特性的方法。幸運的是,現在的各種Linux版本都支持最常用的API標準,例如POSIX線程、大頁面、異步I/O、消息隊列、64位結構等。在Linux上找到源系統的一些替代方法從來沒像現在這么容易。
下面列出了本章講述的一些重點內容:
- 文檔“Conflicts between ISO/IEC 9945(POSIX) AND THE Linux Standard Base”詳細描述了Linux支持的標準。(注釋22)
- Linux提供了支持庫版本化的三種方法:內部版本化、外部版本化,以及符號版本化。
- 通過Native POSIX線程庫,Linux現在更完整地實現了對POSIX線程的支持,這使得移植多線程應用程序到Linux變得更加容易了。
- Linux對UNIX平臺上使用大頁面支持的應用程序也提供了大頁面支持功能。
- 針對具體情況,大小端環境可能對待移植的應用程序產生影響。大小端問題只有在待移植的應用程序所在的源平臺和目標Linux平臺使用的字節序不同時才會有影響。
- 從32位到64位的移植應該當成一個完全獨立的移植過程。如果待移植的應用程序是32位的而且要移植成64位的程序在Linux上運行,那么應該把該過程當成兩個獨立的移植項目:第一個是把32位程序移植到Linux上,第二個是把32位程序移植成64位的。
本章對Linux2.6的功能只介紹了一個大致的輪廓,具體的移植章節(移植Solaris、AIX,和HP-UX應用程序)通過列舉Linux和各UNIX平臺之間的區別及相似性更詳細地講述了這些技術特性。接下來我們就進入各移植章節。
【編輯推薦】