[保留] The Linux GCC HOWTO中譯版 | 作者: Daniel Barlow
譯者: 陳建勳(Frank J.S. Chen)
v1.17, 28 February 1996
_________________________________________________________________
本文闡述安裝GNU C編譯器和程式館的方法,同時概觀地說明程式的編譯、連結、
執行、除錯的過程以及可能面臨的諸多問題。寫作的參考資料泰半來自於Mitch
DSouza先生所收集的GCC-FAQ;而另一個來源是ELF-HOWTO。此份HOWTO可以說已
代替了GCC-FAQ,而且即將要永久替代ELF-HOWTO了。此乃GCC-HOWTO第一份公開發
行的版本(不須理會版本序號;那是RCS的傑作),有任何指正與建議的,本人都
很歡迎。
_________________________________________________________________
1. 行遠必自迩!
* 1.1 譯者的話
* 1.2 動與靜
* 1.3 作者的私語
* 1.4 印刷與排版
2. 東東在哪兒?
* 2.1 GCC-HOWTO在哪兒?
* 2.2 GCC相關的資料又在哪兒?
* 2.3 GCC
* 2.4 C程式館與標頭檔
* 2.5 有關聯的工具 (as, ld, ar, strings etc)
3. GCC的安裝與GCC的設定
* 3.1 GCC的版本
* 3.2 東東裝好後都到哪兒去了?
* 3.3 標頭檔ㄋㄟ?標頭檔ㄋㄟ?
* 3.4 建立交叉編譯器(Building cross compilers)
4. 移植程式與編譯程式
* 4.1 gcc自行定義的符號
* 4.2 線上求助說明
* 4.3 移植能力
5. 除錯與監管
* 5.1 預防重於治療(lint)
* 5.2 除錯
* 5.3 監管
6. 連結
* 6.1 共享程式庫 vs靜態程式庫
* 6.2 終極審判(‘sin() 在哪個程式庫裡?’)
* 6.3 X檔案?
* 6.4 建立你自己的程式庫
7. 動態載入
* 7.1 基本概念
* 7.2 錯誤訊息
* 7.3 控制動態載入器的運作
* 7.4 以動態載入撰寫程式
8. 與發展人士聯絡
* 8.1 Bug報表
* 8.2 協助發展
9. 結語
* 9.1 名人榜
* 9.2 翻譯
* 9.3 歡迎任何的回饋
* 9.4 合法的行迳規定
10. 索引
_________________________________________________________________
1. 行遠必自迩!
1.1 譯者的話
* 這份譯文為Linux document projects(LDP)中文翻譯計畫系列之一。目前之
網址為 [1]http://www.linux.org.tw/CLDP/,歡迎各位網友踴躍投入此一計
畫。
* 我並沒有完全按照原文逐字翻譯。為了力求譯文通暢可讀,我會稍稍的重組
一部份的文字,加油添醋,或是精簡原文;這樣做的話,可以彌補中英文間
語法結構的差異性,且語氣可以貫通無礙。
* 一些關鍵字與專業詞匯等,會附加上原文單字。
* 遇有轉譯困難,唯原文常見的字匯如bugs、shadow password、padding
、image之類的,則保留原文不變。若閣下對這些字匯有適當譯辭的,請不吝
指教。
* 內文中若遇有"[譯者注:**]"之標記,則為本人額外之注解。
* 對這篇譯文有任何建議與疑問的,請email至
[email protected]。
* WWW Home Page: [2]http://linux.ntcic.edu.tw/~jsfrank/。
* 此中譯文件之翻譯權已取得英籍之原作者Daniel Barlow 先生之同意;另,
陳建勳先生保有此份中譯版文件所有的權利,你可以任意的拷貝,以各種媒
體散布這份中譯文件,唯此節補充說明需原封不動附上,且不可任意更動譯
文。
* v0.1版的譯文相當粗糙,連文句的語法結構都嫌太過於松散,v0.2版針對中
文的用字習慣來修正,並將上一版譯的不妥當的地方修正過來,例如either
這個字,英國人常把either當名詞用,指兩者中任意一個;這份HOWTO就充份
反應出這個用字習性,跟美語有基本上的差異。
* 文中有幾句話沒有譯出來,一方面是看不懂,另方面是直譯也譯不出來,所
以只好保留原文了,要是閣下有新的領悟的,無論如何請告訴我。
* v0.1版翻譯起始日期為:11/7/97;截止日期為:11/19/97。
* v0.2版修正起始日期:5/13/98;截止日期為:6/3/98。
1.2 動與靜
目前Linux的發展正波濤洶湧的進行著。簡單一點講,Linux有兩種執行檔的格式
可用,取決於你的系統是怎麽整合起來的;你的Linux應該是其中一種吧!閱讀這
份文件,可以幫助你□清執行檔的類別。
要如何區別呢?執行公用程式‘file’(例如,file /bin/bash)就對了。
就ELF的程式而言,螢幕上顯示出來的訊息會含有ELF的字眼;如果說是a.out的,
訊息內會箝有 Linux/i386的字樣。
ELF與a.out格式的差異之處會在後續的章節中討論(很廣泛喔!)。ELF是比較新
的格式,一般而言,接受的程度較佳。
1.3 作者的私語
版權說明與合法的行迳規定,就擺在這份文件的尾端。除此之外,我還有一些不
得不提醒你的話要講。就算你□著沒事干,也不要在Usenet上丟一些呆瓜問的問
題;還有,不要老以為自己C語言的功力深厚,專門發表一些不是bugs的bugs出來
丟人現眼,這不就等於告訴別人你不學無術,在關老爺面前耍大刀了嗎?所以說
自以為是的英雄主義是得不償失的。
1.4 印刷與排版
如果你現在讀的是Postscript、dvi或是html格式,那麽你所看到的字型變化就會
比只讀純文字格式的人多一些。特別是檔案名稱、命令、命令的輸出與摘錄出來
的原始碼等,統統都是打字機的字型。這樣做的話,對於某些需要強調的‘變數
’還有那些沒有固定結果的□例而言,就可以達到強調的效果了。
讀這份文件的同時,你也會得到一套蠻有用的索引。假若是dvi、 postscript之
類的版本,索引的數字就是章節(section)的編號;如果是HTML的話,這些數字會
按順序排列,你可以用滑鼠左鍵去連結相對的索引內容;如果你看的是純文字版
本的話, 數字就只是數字,沒別的含意;建議你趕快升級為妙哩!
我用的shell是Bourne shell(不是C shell),舉的例子自然是Bourne shell的
語法。如果你用的是C shell的話,設定環境變數的語法會像下面這樣:
% setenv FOO bar
要是用Bourne shell的話,我會這樣子寫:
$ FOO=bar; export FOO
如果提示符號顯示的是井字符號#而不是錢字符號 $的話,很有可能這個命令是只
適用於root而已。當然啦,要是你試了這些□例,結果弄得你的系統發生災變,
我可是一點責任也不會負的喔!祝好運!:-)
11/8/97譯. 5/13/98修訂
2. 東東在哪兒?
2.1 GCC-HOWTO在哪兒?
這份文件是Linux HOWTO系列之一,換句話說,你可以在所有存放Linux HOWTO文
件的網站上面找到她的芳蹤,例如
[3]http://sunsite.unc.edu/pub/linux/docs/HOWTO/。HTML的版本(可能會是較
新的版本)可以從
[4]http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html上面抓下來。
2.2 GCC相關的資料又在哪兒?
標准的gcc說明文件是隨附在發行的原始碼(source distribution)內(往下看就
有了!),裡頭有textinfo與.info兩種檔案。要是你的網路連接速率夠快,或是
有一片cdrom,不然,有高度的耐心也成,你可以自己把它untar,然後再把相對
應的位元一一拷貝到/usr/info的目錄底下。假如你的條件與上述的不符,不妨到
[5]tsx-11站上去參觀參觀。不過,我想,沒有必要老是惦記著最新的版本吧!
libc的文件說明有兩種來源。一種是GNU libc,以.info的格式儲存,除了stdio
之外,其馀Linux libc的說明都相當的詳盡精確。另一種可以在Linux的archive
[6]manpages上找到系統呼叫(system call)(第2節)與libc函數(function)
(第3節)的文件說明。
2.3 GCC
解答有二:
(a)你可以在 [7]ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/的網站上找
到 正式的Linux GCC發行系統(distribution),而且是已經編譯好的可執行檔。
當我在寫這份文件時,2.7.2(gcc-2.7.2.bin.tar.gz)是最新的版本。
(b)自由軟體基金會(Free Software Foundation)所發布的GCC最新原始碼可以
從網站 [8]GNU archives上取得。沒有必要非得與上述的版本一致才行,不過這
個版本的確是目前最新的。Linux GCC的維護網友(maintainers)讓你可以很輕松
的自行編譯這個最新的版本。configure命令稿(script)會幫你自動設定好所有該
做的事情。建議你有空不妨到 [9]tsx-11看看,說不定會有修正的版本是你會想
要用的。
如果想要編寫出一些有用的軟體(不是我羅唆,還是有不少沒啥用途的軟體在網
路上四處流竄。),下面這一小節所談的也是你需要的:
2.4 C程式館與標頭檔
該選哪一套程式館是取決於(i)你的系統是ELF的或是a.out的;(ii)你希望你的系
統變成哪一種?如果你是從libc 4升級到libc 5,那麽給你一個良心的建議,先
去看看ELF-HOWTO再說。你一定會問,在ELF文件的哪兒呢?嘿!嘿!不偏不倚,
就差不多跟這份文件相同的位置。網站 [10]tsx-11上面可以找到你想要的。
libc-5.2.18.bin.tar.gz
--- ELF共享程式館(ELF shared library images),靜態程式館
(static libraries)與標頭檔(針對C語言與數學程式館)。
libc-5.2.18.tar.gz
---libc-5.2.18.bin.tar.gz的原始碼。這個檔案你也需要,因為.bin.套
件(package)含有必需的標頭檔。如果此時你正猶豫不決,不曉得是老身
親自下海,動手編譯C程式庫比較好;還是直接用人家編譯好的二進位
檔(binaries)就可以了。有這種困擾的人,來,看我的嘴形:用人家編譯
好的二進位檔不就解決了嘛!只有在你想要NYS或是shadow password的情
況下,才需要自己的手來推動搖籃。
libc-4.7.5.bin.tar.gz
--- 這個檔案是a.out的共享程式庫(shared library images)與靜態程式
庫,用途是為了與前述的libc 5套件共存共榮而設計的,不過除非你想要
繼續使用a.out的程式或是繼續發展a.out的程式,不然的話,是不需要它
的。
2.5 有關聯的工具 (as, ld, ar, strings etc)
到目前為止,與之前所談的都一樣,從網站 [11]tsx-11上,就可以找到這些工具
程式。目前的版本是binutils-2.6.0.2.bin.tar.gz。
需要注意的是binutils只適用於ELF,而且目前libc的版本也都是屬於ELF的;當
然啦,習慣a.out的人如果有個ELF的libc與a.out的libc聯合起來一起使用,這對
他們來講是再好不過的美事了。不可否認的,C程式館的發展正以堅決的腳步邁
向ELF的格式,除非你真的有很好的理由,需要a.out的東東,不然啊,大家都會
鼓勵你勇於突破,趁早加入銳不可擋的大潮流。
11/9/97譯
3. GCC的安裝與GCC的設定
3.1 GCC的版本
在shell的提示符號下鍵入gcc -v,螢幕上就會顯示出你目前正在使用的GCC的版
本。同時這也是一個相當可靠的方法,可以確定你現在所用的是ELF或是a.out。
在我的系統上,執行gcc -v的結果是:
$ gcc -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
上面的訊息指出了幾件重要的事情:
* i486 這是指明你現在正在用的gcc是為了486的微處理器而寫的---你的電腦
可能是386或是586。這3種微處理器的晶片所編譯而成的程式碼,彼此間是可
以相容使用的。差別之處是486的程式碼在某些地方有加上padding的功能,
所以可以在486上面跑得比較快。這對386的機器而言,執行程式的效能並不
會有什麽不良的影響,只不過真的會讓程式碼變得稍稍的大了一些。
* box 這可以說一點也不重要;不過也可能另有所指(像是slackware或者
是debian),或者根本什麽也不是(所以羅!完整的目錄名稱是i486-linux
)。假如你是實務派的佼佼者,親自動手建立屬於自己的gcc,那麽你可以在
建立的過程中設定這一項,以裝點門面。就像我做的一樣:-)。
* linux 其實這是指linuxelf或是linuxaout。這一項會令人引起不必要的困惑
,究竟是指哪一種會根據你所用的版本而異。
+ linux 意指ELF若版本序號是2.7.0.(或是更新的版本);否則的話,
就是a.out的了。
+ linuxaout 意指a.out的格式。當linux的定義從a.out更換到ELF時
,linuxaout就會順水推舟,搖身一變,成了一個目標物。因此,你不
會看到任何版本新於2.7.0.的gcc有linuxaout的。
+ linuxelf 已經過時了。通常這是指2.6.3版的gcc,而且這個版本也可
以用來產生ELF的可執行檔。要注意的是,gcc 2.6.3版在產生ELF程式
碼時會有bugs,所以如果你目前用的恰好是這個版本,建議你趕快升級
。
* 2.7.2 版本的序號。
所以,總結起來,我有2.7.2版的gcc,可以產生ELF格式的程式碼。就這麽簡單,
驚訝吧!eh?
3.2 東東裝好後都到哪兒去了?
如果安裝gcc時沒有仔細的看著螢幕,或者你是從一個完整的發行系統裡把gcc單
獨抓出來安裝的話,那麽也許你會想知道到底這些東東裝好後是住在整個檔案系
統的那些地方。幾個重點如下:
* /usr/lib/gcc-lib/target/version/ (與子目錄)大部份的編譯器就是住在
這個地方的。在這兒有可執行的程式,實際在做編譯的工作;另外,還有一
些特定版本的程式庫與標頭檔等也會儲存在此。
* /usr/bin/gcc 指的是編譯器的驅動程式---也就是你實際在命令列(command
line)上執行的程式。這個目錄可供各種版本的gcc使用,只要你用不同的編
譯器目錄(如上所述)來安裝就可以了。要知道內定的版本是那一個,
在shell提示符號下打gcc -v。要是想強迫執行某個版本,就換打gcc -V
version。例如:
# gcc -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
# gcc -V 2.6.3 -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specs
gcc driver version 2.7.2 executing gcc version 2.6.3
* /usr/target/(bin|lib|include)/ 如果你裝了數種的目標物件,例如a.out
與elf,或是某一種的交叉編譯器(cross-compiler)等等,那些屬於非主流目
標物件(non-native target(s))的程式庫,binutils(as、ld等等)工具
與標頭檔等都可以在這兒找到。即使你只安裝了一種gcc,還是可以在這兒找
到這些原本就是替它們准備的東東。如果不是在這兒,那麽就應該是
在/usr/(bin|lib|include)了。
* /lib/,/usr/lib 與其它的目錄等都是主流系統(native-system)的程式館
目錄。許多的應用程式都會用到/lib/cpp,因此你也需要它---作法上,不是
從/usr/lib/gcc-lib/target/version/ 目錄裡拷貝,就是弄個符號連結
(symlink)指向那兒。 [譯者注:所謂的native,是指目前你的系統是
以a.out或elf的格式為主,或者內定的gcc是哪一種版本等等。native的意思
是‘本土的’、‘本國的’與‘天生的’……等等;當你拿到一片CD-ROM重
頭至尾將Linux安裝完成,讓Linux出生,成為你個人特色濃厚的作業平台後
,如果再加裝一些不一樣的目標物件,自然就有‘本土’與‘外省’( 無關
政治),‘本國’與‘外國’、‘天生’與‘人為’等等的區別,同時也含
有內定(default)的意思在。假若再附加上你個人的價值觀判斷和喜好,我
想用主流(native)與非主流(non-native)來翻譯應該還算恰當。]
3.3 標頭檔ㄋㄟ?標頭檔ㄋㄟ?
假如把你自行安裝在/usr/local/include目錄底下的標頭檔排除在外的話
,Linux還有另外3種主要的標頭檔:
* /usr/include/與其子目錄底下的標頭檔,大部份都是由H.J.Lu發展的libc套
件(libc binary package)所提供的。我會只說‘大部份’的原因,是因為你
可能有其它來源的標頭檔(像是curses與dbm程式庫等等)擺在這兒;尤其是
,如果你現在用的是最新的libc發行系統的話(新版本不像舊版那樣,已經
不再支援curses或dbm了。),那東東之多是人人為之咋舌的! [譯者
注:libc binary package意指以二進位形式(machine code)儲存之套件,並
非原始碼(text),若要以中文全稱譯出,則成‘libc二進位檔套件’,似
有聱牙之嫌,故略去binary,以libc套件通稱。]
* 在核心原始碼的發行系統內(kernel source distribution)
,/usr/include/linux 與 /usr/include/asm (裡頭有這些檔案
: 與 )應該有符號連結(symbolic links)可以連
結至目錄linux/include/linux 與 linux/include/asm。如果你有鴻鹄之志
的話,安裝這些東東後,就不應該只是拿來編譯核心(kernel)而已。 把原
始碼解壓縮(unpacking)後,可能你也會發現,需要在核心的目錄
(kernel directory)底下做make config的動作。很多的檔案都會依
賴的幫忙,可是這個檔案卻有可能因版本不同而不存在
。若干核心版本裡,asm就只是它自己的一個符號連結,僅僅是在make
config時才建立出來而已。 [譯者注:原文提及autoconf.h時是‘Many
files depend on ,which otherwise may not exist,*
’。此處之otherwise之詞性應為形容詞(adj),指‘另一情況’、‘另一種
’、‘不同的’之意,將原文形容詞子句拆開來應為:(i) Many files
depend on . (ii) of other
condition may not exist. 與下一句互相比對,此處應同指在不同版本之情
況下。] 所以,當你在目錄/usr/src/linux底下,解開核心的程式碼時,就
照著下面指示的做吧!
$ cd /usr/src/linux
$ su
# make config
(回答接下來的問題。通常回答得正不正確並不重要,除非你打算繼續□造核心。)
# cd /usr/include
# ln -s ../src/linux/include/linux .
# ln -s ../src/linux/include/asm .
* 諸如、、、 與之類
的檔案,會隨著不同的編譯器版本而異,屬於你‘個人的’檔案,可以在
/usr/lib/gcc-lib/i486-box-linux/2.7.2/include/與其它相類似(相同)
的目錄名稱的地方找到。 11/11/97譯 5/14/98修正
3.4 建立交叉編譯器(Building cross compilers)
將Linux當作標的作業平台(target platform)
假設你已經拿到gcc的原始碼,通常你只要依循INSTALL檔的指示便可完成一切的
設定。 make後面再接configure --target=i486-linux --host=XXX on
platform XXX,就能幫你變把戲了。要注意的是,你會需要Linux還有核心的標頭
檔;同時也需要建立交叉組譯器(cross assembler)與交叉連結器(cross
linker),來源是 [12]ftp://tsx-11.mit.edu/pub/linux/packages/GCC/。
Linux當成原始作業平台(source platform)而MSDOS作為標的作業平台
Ugh。很明顯的,這個大概需要用到套件“emx”或是延伸套件“go”。請自行去
[13]ftp://sunsite.unc.edu/pub/Linux/devel/msdos看看。我並沒有測試過這些
個東西,所以沒有辦法保證什麽。
4. 移植程式與編譯程式
4.1 gcc自行定義的符號
只要執行gcc時,附加 -v這個參數,就能找出你所用的這版gcc,自動幫你定義了
什麽符號。例如,我的機器看起來會像這樣:
$ echo main(){printf("hello world\n");} | gcc -E -v -
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
/usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
-Amachine(i386) -D__i486__ -
假若你正在寫的程式碼會用到一些Linux獨有的特性,那麽把那些無法移植的程式
碼,以條件式編譯的前置命令封括起來,可是個不錯的主意呢!如下所示∶
#ifdef __linux__
#endif
用__linux__就可以達成目的;看仔細一點,不是linux喔。盡管linux也有定義,
畢竟,這個仍然不是POSIX的標准。
4.2 線上求助說明
gcc編譯器參數的說明文件是gcc info page(在Emacs內,按下C-h i,然後選
‘gcc’的選項)。要是弄不出來,不是賣你CD-ROM的人沒把這個東東壓給你,不
然就是你現在用的是舊版的。遇到這種情況,最好的方法是移動尊臀到archive
[14]ftp://prep.ai.mit.edu/pub/gnu或是它的mirrors站台,去把gcc的原始檔案
抓回家,重新烹饪一番。
gcc manual page(gcc.1) 可以說是已經過時了,要是你吃飽了撐著沒事干硬是
想看,它就會告訴你說別無聊了。
旗正飄飄
在命令列上執行gcc時,只要在它的屁股後面加上-On的選項,就能讓gcc乖乖的替
你生出最佳編碼的機器碼。這裡的n是一個可有可無的小整數,不同版本的gcc
,n的意義與其正確的功效都不一樣,不過,典型的□圍是從0(不要雞婆,我不
要最佳編碼。)變化到2(最佳編碼要多一點。),再升級到3(最佳編碼要再多
一點,多一點)。
gcc在其內部會將這些數字轉譯成一系列的-f與-m的選項。執行gcc時帶上旗號-v
與-Q,你就能很清楚的看出每一種等級的-O是對應到那些選項。好比說,就-O2來
講,我的gcc告訴會我說:
enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
-fexpensive-optimizations
-fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
-fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
-fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
-mno-386 -m486 -mieee-fp -mfp-ret-in-387
要是你用的最佳編碼等級高於你的編譯器所能支援的(e.g. -O6),那麽它的效
果就跟你用你的編譯器所能提供的最高等級的效果是一樣的。說實在的,發行出
去的gcc程式碼,用在編譯時竟是如此處理這等問題,真的不是什麽好的構想。日
後若是有更進步的最佳編碼方法具體整合到新的版本裡,而你(或是你的users)
還是試著這樣做的話,可能就會發現gcc會中斷你的程式了。
從gcc 2.7.0升級到2.7.2的users應該注意一點,使用-O2時會有一個bug。更糟糕
的是,強度折減參數(strength reduction)居然沒有用!要是你喜歡重新編
譯gcc的話,是有那麽一個修正的版本可以更正這項錯誤;不然的話,一定要確定
每次編譯時都有加上-fno-strength-reduce喔!
11/12/97譯
有個性的微處理器
有一些-m的旗號十分有用處,但是卻無法藉由各種等級的-O打開來使用。這之中
最重要的有是-m386和-m486這兩種,用來告訴gcc該把正在編譯的程式碼視作專
為386或是486機器所寫的。不論是用哪一種-m來編譯程式碼,都可以在彼此的機
器上執行,-m486編譯出來的碼會比較大,不過拿來在386的機器上跑也不會比較
慢就是了。
目前尚無-mpentium或是-m586的旗號。Linus建議我們可以用-m486
-malign-loops=2 -malign-jumps=2 -malign-functions=2來得到最佳編碼的486
程式碼,這樣做正好就可以避免alignment(Pentium並不需要)有過大的gaps發
生。Michael Meissner說:
我的第六感告訴我,-mno-strength-reduce(嘿!要曉得我可不是在談強度折
減參數的bug呀,那已經是另外一個爭論的戰場了。)一樣也可以在x86的機器
上產生較快的程式碼,這是因為x86的機器對暫存器有著不可磨滅的□渴在,
而且GCCs method of grouping registers into spill registers vs.
other registers doesnt help either。傳統上,強度折減的結果會使得編
譯器去利用加法暫存器以加法運算來取代乘法運算。事實上,我在懷
疑-fcaller-saves可能也只是個漏洞也說不定。
而我的第七感則再度的告訴我說,-fomit-frame-pointer可能會也可能不會有
任何的賺頭。從這點來看,就是意謂著有另一個暫存器可以用來處理記憶體分
配的問題。另方面,若純粹從x86的機器在轉換它的指令集成為機器碼的方法
上來看,便意謂著堆疊所用到的記憶體空間要比frame所用到的還要來得多;
換句話說,Icache對程式碼而言並沒有實質上的幫助,若是閣下用
了-fomit-frame-pointer的話,同時也是告訴編譯器在每次呼叫函數之後,就
必須修正堆疊的指標;然而,就frame來講,若呼叫的次數不多的話,則允許
堆疊暫時堆積起來。
有關這方面主題的最後一段話仍是來自於Linus:
要注意的是,如果你想要得到最佳狀況的執行效能,可千萬別相信我的話。無
論如何,一定要進行測試。gcc編譯器還有許多的參數可用,其中可能就有一
種最特別的組合,可以給你最佳編碼的結果。
11/14/97譯 5/15/98修正
Internal compiler error: cc1 got fatal signal 11
Signal 11是指 SIGSEGV,或者 ‘segmentation violation’。通常這是指
說gcc對自己所用的指標感到困惑,而且還嘗試著把資料寫入不屬於它的記憶體裡
。所以,這可能是一個gcc的bug。 然而,大體而言,gcc是一支經過嚴密測試且
可靠度良好的軟體佳作。它也用了大量復雜的資料結構與驚人的指標數量。簡言
之,若是要評選本世紀最挑惕與最一絲不□的RAM測試程式,gcc絕對可以一摘後
冠。假如你無法重新復制這只bug---當你重新開始編譯時,錯誤的訊息並沒有一
直出現在同一個地方---那幾乎可以確定,是你的硬體本身有問題(CPU,記憶體,主
機板或是快取記憶體).千萬不要因為你的電腦可以通過開機程序的測試、或
是Windows可以跑得很順、或者其它什麽的,就回過頭來大肆宣傳說這是gcc的一
個bug;你所做的這些測試動作,通常沒有什麽實際上的價值,這是很合理的結論
。另外,也不要因為編譯核心時,總是停留在‘make zImage’的階段,就要大罵
這是gcc的bug---當然它會停在那兒啊!做‘make zImage’時,需要編譯的檔案
可能就超過200檔案;我們正在研擬一個替代的方案。
如果你可以重覆產生這個bug,而且(最好是這樣啦!)可以寫一個短小的程式來
展示這只bug的話,你就可以把它做成bug報告,然後email給FSF,或者
是linux-gcc通信論壇。你可以去參考gcc的說明文件,看看有什麽詳細的資訊,是
他們所需要的。
4.3 移植能力
據報,近日來許多正面的消息指出,若是有某件東東到現在都還沒移植到Linux上
去,那麽可以肯定的是,它一定一點價值也沒有。:-)
嗯!正經一點。一般而言,原始碼只需要做一些局部的修改,就可以克服Linux
100%與POSIX相容的特質。如果你做了任何的修改,而將此部份傳回給原作者,會
是很有建設性的舉動。這樣日後就只需要用到‘make’,就能得到一個可執行的
檔案了。
BSD教徒 (有 bsd_ioctl、daemon 與 )
編譯程式時,可以配合-I/usr/include/bsd與連結-lbsd的程式庫。(例如:在你
的Makefile檔內,把-I/usr/include/bsd加到CFLAGS那一行;把-lbsd加
到LDFLAGS那一行)。如果你真的那麽想要BSD型態的信號行為,也不需要再加
上-D__USE_BSD_SIGNAL了。那是因為當你用了-I/usr/include/bsd與含括了標頭
檔之後,make時就會自動加入了。
失落的封印(SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS etc)
Linux與POSIX是完全相容的。不過,有些信號並不是POSIX定義的---ISO/IEC
9945-1:1990 (IEEE Std 1003.1-1990), paragraph B.3.3.1.1 sez:
“在POSIX.1中省略了SIGBUS、SIGEMT、SIGIOT、SIGTRAP與SIGSYS信號,那是
因為它們的行為與實作的方式息息相關,而且也無法進行適當的分類。確認實
作方式後,便可以發送這些信號,可是必須以文件說明它們是在什麽樣的環境
底下發送出來的,以及指出任何與它們的發展相關的限制。”
想要修正這個問題,最簡單也是最笨的方法就是用SIGUNUSED重新定義這些信號。
正確的方法應該是以條件式的編譯#ifdef來處理這些問題才對:
#ifdef SIGSYS
#endif
11/15/97譯 5/22/98修正
K & R
gcc是一個與ANSI相容的編譯器;奇怪的是,目前大多數的程式碼都不符合ANSI所
定的標准。如果你熱愛ANSI,喜歡用ANSI提供的標准來撰寫C程式,似乎除了加
上-traditional的旗號之外,就沒有其它什麽可以多談的了。There is a
certain amount of finer-grained control over which varieties of brain
damage to emulate;請自行查閱gcc info page。
要注意的是,盡管你用了-traditional來改變語言的特性,它的效果也僅局限
於gcc所能夠接受的□圍。例如, -traditional會打開-fwritable-strings,使得
字串常數移至資料記憶體空間內(從程式碼記憶體空間移出來,這個地方是不能任
意寫入的)。這樣做會讓程式碼的記憶體空間無形中增加的。
前置處理器的符號卯上函數原型宣告
最常見的問題是,如眾所皆知,Linux中有許多常用的函數都定義成巨集存放在標
頭檔內,此時若有相似的函數原型宣告出現在程式碼內,前置處理器會拒絕進行
語法分析的前置作業。常見的有atoi()與atol()。
sprintf()
在大部份的Unix系統上,sprintf(string, fmt, ...)傳回的是string的指標,然
而,這方面Linux(遵循ANSI)傳回的卻是放入string內的字元數目.進行移植時
,尤其是針對SunOS,需有警覺的心。
fcntl 與相關的函數;FD_*家族的定義到底擺在哪裡?
就在裡頭。 為了真正的原型宣告,當你用了fcntl,可能你也想含
括標頭檔進來。
一般而言,函數的manual page會在SYNOPSIS章節內列出需要的標頭檔。
select()的計時---程式執行時會處於忙碌-等待的狀態
很久很久以前,,select()的計時參數只有唯讀的性而已。即使到了最近
,manual pages仍然有下面這段的警告:
select()應該是藉由修正時間的數值(如果有的話),再傳回自原始計時開始
後所剩馀的時間。未來的版本可能會使這項功能實現。因此,就目前而言,若
以為呼叫select()之後,計時指標仍然不會被修正過,可是一種非常不明智的
想法喔!
未來就在我們的眼前了!至少,在這兒你絕對可以看到。函數select()傳回的,
是扣除等待尚未到達的資料所耗費的時間後,其剩馀的時間數值。如果在計時結
束時,都沒有資料傳送進來,計時引數便會設為0;如果接著還有任何
的select(),以同樣的計時structure來呼叫,那麽select()便會立刻結束。
若要修正這項問題,只要每次呼叫select()前,都把計時數值放到計時
structure內,就沒有問題了。把下面的程式碼,
struct timeval timeout;
timeout.tv_sec = 1; timeout.tv_usec = 0;
while (some_condition)
select(n,readfds,writefds,exceptfds,&timeout);
改成,
struct timeval timeout;
while (some_condition) {
timeout.tv_sec = 1; timeout.tv_usec = 0;
select(n,readfds,writefds,exceptfds,&timeout);
}
這個問題,在有些版本的Mosaic裡是相當著名的,只要一次的等待,Mosaic就掛
在那裡了。Mosaic的螢幕右上角,是不是有個圓圓的、會旋轉的地球動畫。那顆
球轉得愈快,就表示資料從網路上傳送過來的速率愈慢!
產生中斷的系統呼叫
特徵:
當一支程式以Ctrl-Z中止、然後再重新執行時□或者是其它可以產生Ctrl-C中斷
信號的情況,如子程序的終結等□系統就會抱怨說"interrupted system call"或
是"write: unknown error",或者諸如此類的訊息。
問題點:
POSIX的系統檢查信號的次數,比起一些舊版的Unix是要多那麽一點。如果
是Linux,可能就會執行signal handlers了□
* 非同步地(計時器的滴答聲)
* 系統呼叫的傳回值
* 在下列系統呼叫的執行期間∶ select(), pause(), connect(),accept(),
read() on terminals, sockets, pipes or files in /proc, write() on
terminals, sockets, pipes or the line printer, open() on FIFOs,
PTYs or serial lines,ioctl() on terminals, fcntl() with command
F_SETLKW, wait4(), syslog(), any TCP or NFS operations.
就其它的作業系統而言,你需要的可能就是下面這些系統呼叫了: creat(),
close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(),
wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() to this
list.
在系統呼叫期間,若有一信號(那支程式本身應准備好handler因應了)產生
,handler就會被呼叫。當handler將控制權轉移回系統呼叫時,它會偵測出它已
經產生中斷,而且傳回值會立刻設定成-1,而errno設定成EINTR。程式並沒有想
到會發生這種事,所以就掛了。
有兩種修正的方法可以選擇:
(1) 對每個你自行安裝的signal handler,都須在sigaction的旗號加
上SA_RESTART。例如,把下列的程式,
signal (sig_nr, my_signal_handler);
改成,
signal (sig_nr, my_signal_handler);
{ struct sigaction sa;
sigaction (sig_nr, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
sa.sa_flags &= ~ SA_INTERRUPT;
#endif
sigaction (sig_nr, &sa, (struct sigaction *)0);
}
要注意的是,當這部份的變更大量應用到系統呼叫之後,呼叫read()、write()
、ioctl()、 select()、 pause() 與 connect()時,你仍然得自行檢查EINTR。
如下所示:
(2) 你自己得很明確地檢查EINTR:
這裡有兩個針對read()與ioctl()的例子。
原始的程式片段,使用read():
int result;
while (len >; 0) {
result = read(fd,buffer,len);
if (result < 0) break;
buffer += result; len -= result;
}
修改成,
int result;
while (len >; 0) {
result = read(fd,buffer,len);
if (result < 0) { if (errno != EINTR) break; }
else { buffer += result; len -= result; }
}
原始的程式片段,使用ioctl():
int result;
result = ioctl(fd,cmd,addr);
修改成,
int result;
do { result = ioctl(fd,cmd,addr); }
while ((result == -1) && (errno == EINTR));
注意一點,有些版本的BSD Unix,其內定的行為是重新執行系統呼叫。若要讓系
統呼叫中斷,得使用 SV_INTERRUPT或SA_INTERRUPT旗號。
可以寫入的字串
gcc對其users總懷抱著樂觀的想法,相信當他們打算讓某個字串當作常數來用
時---那它就真的只是字串常數而已。因此,這種字串常數會儲存在程式碼的記憶
體區段內。這塊區域可以page到磁碟機的image上,避免耗掉swap的記憶體空間,
而且任何嘗試寫入的舉動都會造成分頁的錯誤(segmentation fault)。這可是一
種特色呢!
對老舊一點的程式而言,這可能會產生一個問題。例如,呼叫mktemp(),傳遞的
引數(arguments)是字串常數。 mktemp()會嘗試著在*適當的位置*重新寫入它的
引數。
原文鏈接:http://linux.chinaunix.net/bbs/viewthread.php?tid=232017
轉載請注明作者名及原文出處