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