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