第9章基于linux操作系統(tǒng)的arm編程_第1頁
已閱讀1頁,還剩47頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、<p>  第9章 基于ARM9和Linux嵌入式系統(tǒng)設(shè)計</p><p>  本章將學習如何進行嵌入式Linux系統(tǒng)的開發(fā)。讀完本章,讀者將了解以下內(nèi)容:</p><p>  ● 嵌入式Linux的開發(fā)環(huán)境。</p><p>  ● Linux開發(fā)工具GNU gcc的使用。</p><p>  ● GNU make命令和make

2、file 文件。</p><p><b>  ● GDB調(diào)試器。</b></p><p>  ● 嵌入式Linux下C語言編程。</p><p>  ● 嵌入式Linux引導程序。</p><p>  ●嵌入式Linux 下程序調(diào)試應(yīng)用舉例。</p><p>  9.1 嵌入式Linux的開發(fā)環(huán)

3、境</p><p>  嵌入式系統(tǒng)是以應(yīng)用為中心,以計算機技術(shù)為基礎(chǔ)、軟硬件均可裁剪、適應(yīng)應(yīng)用系統(tǒng)對功能、可靠性、成本、體積、功耗嚴格要求的專用計算機系統(tǒng)。其發(fā)展已有二十多年的歷史,國際上也出現(xiàn)了一些著名的嵌入式操作系統(tǒng),如VxWorks,Palm OS,Windows CE等,但這些操作系統(tǒng)均屬于商品化產(chǎn)品,價格昂貴且由于源代碼不公開導致了諸如對設(shè)備的支持,應(yīng)用軟件的移植等一系列的問題。而Linux作為一種優(yōu)秀

4、的免費操作系統(tǒng),近幾年在嵌入式領(lǐng)域異軍突起,成為了最有潛力的嵌入式操作系統(tǒng)。</p><p>  9.1.1 嵌入式Linux開發(fā)環(huán)境建立</p><p>  進行項目開發(fā)前,首先要做的是搭建一套基于Linux操作系統(tǒng)的應(yīng)用開發(fā)環(huán)境,一般由目標板和宿主機所構(gòu)成。目標板用于運行操作系統(tǒng)和系統(tǒng)應(yīng)用軟件,目標板所用到的操作系統(tǒng)的內(nèi)核編譯、應(yīng)用程序的開發(fā)和調(diào)試規(guī)則需要通過宿主機來完成。開發(fā)環(huán)境對硬

5、件沒有特殊的要求,但是為了雙方之間建立連接關(guān)系,關(guān)鍵的接口(包括串口、以太網(wǎng)口和USB口等)是必不可少的。</p><p>  嵌入式Linux 開發(fā)環(huán)境有幾個方案:</p><p> ?。?)在WINDOWS 下安裝Linux虛擬機后,目前大多情況下使用VWare軟件;</p><p> ?。?)直接安裝 Linux 操作系統(tǒng)。</p><p&

6、gt;  若使用純LINUX操作系統(tǒng)開發(fā)環(huán)境。多數(shù)使用的開發(fā)環(huán)境為RedHat 9,RedHat 9支持中文,并且包含了絕大部分的開發(fā)工具,不用擔心裝了Linux就不能使用WinDows的問題。一般的情況都是用戶已經(jīng)有了WINDOWS 操作系統(tǒng),再安裝Linux,Linux 會自動安裝一個叫作GRUB 的啟動引導軟件,可以選擇引導多個操作系統(tǒng)。可現(xiàn)在的微型計算機的CPU速度快,內(nèi)存容量大,因此,相當多的ARM嵌入式系統(tǒng)的開發(fā)人員使用在W

7、INDOWS 下安裝Linux虛擬機,這樣,開發(fā)人員就可在WINDOWS 和Linux兩種操作系統(tǒng)下任意切換使用。給開發(fā)人員帶來許多方便。</p><p>  9.1.2 嵌入式Linux開發(fā)的一般過程</p><p>  嵌入式Linux融合了嵌入式和Linux的特點,其開發(fā)與一般的應(yīng)用程序開發(fā)相比有著自己的特點,下面簡要的介紹一下嵌入式Linux開發(fā)的一般過程。</p>

8、<p>  了解硬件是首要的一步,這是嵌入式開發(fā)的特點決定的。了解硬件指的是了解整個硬件,判斷硬件對于當前的應(yīng)用來說是否合適。嵌入式系統(tǒng)中需要使用到CPU和各種外圍設(shè)備,由此需要收集相關(guān)硬件的資料,包括CPU,芯片手冊和各種外圍設(shè)備的手冊以及相關(guān)的各種電路圖等,并對整體系統(tǒng)有較深入的了解。</p><p>  了解硬件后,下一步就該準備需要使用的Linux工具以及其他工具,這些工具包括針對所用CPU的編

9、譯器/匯編器/連接器、相應(yīng)的庫工具、目標文件分析/管理工具、符號查看器等。由于Linux的開放性,針對不同目標平臺的Linux工具都可在網(wǎng)上免費得到,這些工具的絕大部分都由GNU提供。所需要的其他工具還包括硬件廠商提供給公司的工具,如編程器、下載工具和查錯器等。所有這些工具對以后的開發(fā)、調(diào)試等都可說是必不可少的。</p><p>  做好以上的準備工作后,就要進入實質(zhì)性的工作階段了。首先需要安排內(nèi)存地址,如SDR

10、AM的內(nèi)存地址,F(xiàn)lash的內(nèi)存地址等,這需要與實際應(yīng)用和硬件狀況相結(jié)合來考慮,要根據(jù)硬件的限制以及實際應(yīng)用的需要對內(nèi)存地址進行合理的安排,同時要注意內(nèi)存地址應(yīng)具有一定的伸縮性,以便于將來需要改動時所做的變動達到最小。一般來說,嵌入式Linux的內(nèi)存地址安排體現(xiàn)在連接腳本當中。</p><p>  接著就該進入編寫啟動代碼和機器相關(guān)代碼階段了。各種不同目標系統(tǒng),甚至相同目標系統(tǒng)的啟動代碼和機器相關(guān)代碼也是不相同的

11、。啟動代碼一般需要完成硬件初始化、裝載內(nèi)核、安裝根文件系統(tǒng)以及開始內(nèi)核執(zhí)行的工作,不同目標平臺的啟動代碼一般可通過參考Linux下已有的啟動代碼和相關(guān)CPU的手冊進行編寫。</p><p>  啟動代碼和機器相關(guān)代碼編程完成,并可以啟動系統(tǒng)后,下一步就可以開始驅(qū)動程序的編寫了。嵌入式Linux系統(tǒng)驅(qū)動程序開發(fā)與普通Linux開發(fā)并沒有太大的區(qū)別,都需要對相關(guān)的硬件作出了解,同時需要遵循Linux編寫驅(qū)動程序的一些

12、規(guī)則,編寫完一個驅(qū)動程序后,一般還要寫一個相應(yīng)的測試程序以便隨時進行測試。Linux下各種不同類型的設(shè)備都有相當多的驅(qū)動程序源碼可以參考,因此實際編寫時更多的時間是花在對特定硬件特性的熟悉上。</p><p>  除了以上提到的這些步驟外,進行實際開發(fā)時,很多時候還要進行庫(這里所提到的庫均指C庫)、GUI和系統(tǒng)程序的移植。這是因為嵌入式Linux中所用的庫一般不能直接使用標準庫,而需要進行精簡,雖然已有精簡的C

13、庫,如uClibc等可供使用,但還是需要經(jīng)常對其進行修改。嵌入式Linux常用的GUI有Microwindows、MiniGUI、QT/Embedded、TinyX等,各自均有其使用的場合,所針對的目標平臺和應(yīng)用層次也不一樣,必須根據(jù)實際需要進行選擇。系統(tǒng)程序如mount、ls等有些是應(yīng)用時所必需的,有些則是進行調(diào)試時所需要的,初始時則需要一些通用的系統(tǒng)程序。</p><p>  9.2 Linux開發(fā)工具的使

14、用</p><p>  9.2.1 Linux開發(fā)工具GNU gcc的使用</p><p>  在Linux平臺下,GNU gcc編譯器,即可以編譯Linux操作系統(tǒng)下運行的應(yīng)用程序,又可以編譯Linux內(nèi)核本身,甚至可以交叉編譯運行于其他CPU上的程序。下面介紹編譯程序GCC在編譯應(yīng)用程序的過程的具體用法、GCC的常用選項、模式和警告選項。</p><p>&l

15、t;b>  GCC簡介</b></p><p>  通常所說的GCC是GNU Compiler Collection的簡稱,除了編譯程序之外,它還含其他相關(guān)工具,所以它能把易于人類使用的高級語言編寫的源代碼構(gòu)建成計算機能夠直接執(zhí)行的二進制代碼。GCC是Linux平臺下最常用的編譯程序,是Linux平臺編譯器的事實標準。同時,在Linux平臺下的嵌入式開發(fā)領(lǐng)域,GCC也是用得最普遍的一種編譯器。G

16、CC之所以被廣泛采用,是因為它能支持各種不同的目標體系結(jié)構(gòu)。例如,它既支持基于宿主的開發(fā)(簡單講就是要為某平臺編譯程序,就在該平臺上編譯),也支持交叉編譯(即在A平臺上編譯的程序是供平臺B使用的)。目前,GCC支持的體系結(jié)構(gòu)有40余種,常見的有x86系列、Arm、PowerPC等。同時,GCC還能運行在不同的操作系統(tǒng)上,如Linux、Solaris、Windows等。除了上面講的之外,GCC除了支持C語言外,還支持多種其他語言,例如C+

17、+、Ada、Java、Objective-C、Fortram、Pascal等。</p><p>  GCC常用模式及選項</p><p>  gcc最基本的用法是:</p><p>  gcc [options] file... </p><p>  其中option是以“-”開始的各種選項,file是相關(guān)的文件名。在使用gcc的時,必須給出

18、必要的選項和文件名。gcc的整個編譯過程分別是:預處理、編譯,匯編和鏈接。</p><p>  表9.1 gcc編譯器中常用選項</p><p>  例如,$ gcc -o hello hello.c ,gcc編譯器就會生成一個hello的可執(zhí)行文件。在hello.c的當前目錄下執(zhí)行./hello。</p><p>  gcc編譯器生成的目標文件默認格式為elf(e

19、xecutive linked file)格式,這是Linux系統(tǒng)所采用的可執(zhí)行鏈接文件的通用文件格式。elf格式由若干個段(section)組成,如果沒有特別指明,由標準c源代碼生成的目標文件中包含以下段:</p><p>  ● .text(正文段)包含程序的指令代碼,</p><p>  ● .data(數(shù)據(jù)段)包含固定的數(shù)據(jù),如常量,字符串等,</p><p&g

20、t;  ● .bss(未初始化數(shù)據(jù)段)包含未初始化的變量和數(shù)組等。</p><p>  GCC常用兩種模式:編譯模式和編譯連接模式。下面以一些命令來說明各種模式的使用方法。為簡單起見,假設(shè)全部的源代碼都在一個文件test.c中。</p><p>  $ gcc -o test </p><p>  此命令是把源文件test.c直接編譯成可執(zhí)行程序test。&l

21、t;/p><p>  $ gcc -c test.c </p><p>  此命令是把源文件test.c編譯成不可執(zhí)行目標文件test.o。默認情況下,生成的目標文件名為test.o,但也可以為輸出文件指定名稱,如下所示:</p><p>  $ gcc -c test.c –o mytest.o</p><p>  此命令是把源文件test.

22、c編譯成不可執(zhí)行目標文件test.o。</p><p>  下面的命令將同時編譯3個源文件,即first.c、second.c和 third.c,然后將它們連接成一個可執(zhí)行程序test。命令如下:</p><p>  $ gcc -o test  first.c second.c third.c</p><p>  3.其他常用選項的使用</p>

23、;<p>  許多情況下,頭文件和源文件會單獨存放在不同的目錄中。例如,假設(shè)存放源文件的子目錄名為./src,而包含文件則放在層次的其他目錄下,如./inc。當在./src 目錄下進行編譯工作時,如何告訴GCC到哪里找頭文件呢?方法如下所示:</p><p>  $ gcc test.c –I../inc -o test</p><p>  上面的命令告訴GCC包含文件存放在

24、./inc 目錄下,在當前目錄的上一級。如果在編譯時需要的包含文件存放在多個目錄下,可以使用多個-I 來指定各個目錄。如:</p><p>  $ gcc test.c –I../inc –I../../inc2 -o test</p><p>  這里指出了另一個包含子目錄inc2,較之前目錄它還要在再上兩級才能找到。另外,還可以在編譯命令行中定義符號常量。為此,可以簡單的在命令行中使用

25、-D選項即可,如下例所示:</p><p>  $ gcc –D TEST_CONFIGURATION test.c -o test</p><p>  上面的命令與在源文件中加入下列命令是等效的:</p><p>  #define TEST_CONFIGURATION</p><p><b>  4. 警告功能</b>

26、;</p><p>  當GCC在編譯過程中檢查出錯誤時,它就會中止編譯;但檢測到警告時卻能繼續(xù)編譯生成可執(zhí)行程序,因為警告只是針對程序結(jié)構(gòu)的診斷信息,它不能說明程序一定有錯誤,而是存在風險,或者可能存在錯誤。雖然GCC提供了非常豐富的警告,但前提是已經(jīng)啟用了它們,否則它不會報告這些檢測到的警告。</p><p>  在眾多的警告選項之中,最常用的就是-Wall選項。該選項能發(fā)現(xiàn)程序中一系

27、列的常見錯誤警告,該選項用法舉例如下:</p><p>  $ gcc -Wall test.c -o test</p><p>  該選項相當于同時使用了下列所有的選項:</p><p>  ● unused-function:遇到僅聲明過但尚未定義的靜態(tài)函數(shù)時發(fā)出警告。</p><p>  ● unused-label:遇到聲明過但不使用

28、的標號的警告。</p><p>  ● unused-parameter:從未用過的函數(shù)參數(shù)的警告。</p><p>  ● unused-variable:在本地聲明但從未用過的變量的警告。</p><p>  ● unused-value:僅計算但從未用過的值的警告。</p><p>  ● format:檢查對printf和scanf等

29、函數(shù)的調(diào)用,確認各參數(shù)類型和格式串中的一致。</p><p>  ● implicit-int:警告沒有規(guī)定類型的聲明。</p><p>  ● implicit-function-:在函數(shù)在未經(jīng)聲明就使用時給予警告。</p><p>  ● char-subscripts:警告把char類型作為數(shù)組下標。</p><p>  ● missi

30、ng-braces:聚合初始化兩邊缺少大括號。</p><p>  ● Parentheses:在某些情況下如果忽略了括號,編譯器就發(fā)出警告。</p><p>  ● return-type:如果函數(shù)定義了返回類型,而默認類型是int型,編譯器就發(fā)出警告。同時警告那些不帶返回值的 return語句,如果他們所屬的函數(shù)并非void類型。</p><p>  ● seq

31、uence-point:出現(xiàn)可疑的代碼元素時,發(fā)出報警。</p><p>  ● Switch:如果某條switch語句的參數(shù)屬于枚舉類型,但是沒有對應(yīng)的case語句使用枚舉元素,編譯器就發(fā)出警告(在switch語句中使用default分支能夠防止這個警告)。超出枚舉范圍的case語句同樣會導致這個警告。</p><p>  ● strict-aliasing:對變量別名進行最嚴格的檢查。

32、</p><p>  ● unknown-pragmas:使用了不允許的#pragma。</p><p>  ● Uninitialized:在初始化之前就使用自動變量。</p><p>  需要注意的是,各警告選項既然能使之生效,當然也能使之關(guān)閉。比如假設(shè)我們想要使用-Wall來啟用個選項,同時又要關(guān)閉unused警告,利益通過下面的命令來達到目的:</p&

33、gt;<p>  $ gcc -Wall -Wno-unused test.c -o test</p><p>  下面是使用-Wall選項的時候沒有生效的一些警告項:</p><p>  ● cast-align:一旦某個指針類型強制轉(zhuǎn)換時,會導致目標所需的地址對齊邊界擴展,編譯器就發(fā)出警告。例如,某些機器上只能在2或4字節(jié)邊界上訪問整數(shù),如果在這種機型上把char *強制

34、轉(zhuǎn)換成int *類型, 編譯器就發(fā)出警告。</p><p>  ● sign-compare:將有符號類型和無符號類型數(shù)據(jù)進行比較時發(fā)出警告。</p><p>  ● missing-prototypes :如果沒有預先聲明函數(shù)原形就定義了全局函數(shù),編譯器就發(fā)出警告。即使函數(shù)定義自身提供了函數(shù)原形也會產(chǎn)生這個警告。這樣做的目的是檢查沒有在頭文件中聲明的全局函數(shù)。</p>&l

35、t;p>  ● packed:當結(jié)構(gòu)體帶有packed屬性但實際并沒有出現(xiàn)緊縮式給出警告。</p><p>  ● padded:如果結(jié)構(gòu)體通過充填進行對齊則給出警告。</p><p>  ● unreachable-code:如果發(fā)現(xiàn)從未執(zhí)行的代碼時給出警告。</p><p>  ● inline:如果某函數(shù)不能內(nèi)嵌(inline),無論是聲明為inline

36、或者是指定了-finline-functions 選項,編譯器都將發(fā)出警告。 </p><p>  ● disabled-optimization:當需要太長時間或過多資源導致不能完成某項優(yōu)化時給出警告。</p><p>  9.2.2 GDB調(diào)試器簡介 </p><p>  Linux系統(tǒng)中包含了GNU 調(diào)試程序gdb,它是一個用來調(diào)試C和 C++ 程序的調(diào)試器

37、??梢允钩绦蜷_發(fā)者在程序運行時觀察程序的內(nèi)部結(jié)構(gòu)和內(nèi)存的使用情況。gdb 提供如下功能:</p><p>  ● 運行程序,設(shè)置所有的能影響程序運行的參數(shù)和環(huán)境。</p><p>  ● 控制程序在指定的條件下停止運行。 </p><p>  ● 當程序停止時,可以檢查程序的狀態(tài)。</p><p>  ● 修改程序的錯誤,并重新運行程序。

38、</p><p>  ● 動態(tài)監(jiān)視程序中變量的值。 </p><p>  ● 可以單步執(zhí)行代碼,觀察程序的運行狀態(tài)。 </p><p>  gdb程序調(diào)試的對象是可執(zhí)行文件,而不是程序的源代碼文件。然而,并不是所有的可執(zhí)行文件都可以用gdb調(diào)試。如果要讓產(chǎn)生的可執(zhí)行文件可以用來調(diào)試,需在執(zhí)行g(shù)cc指令編譯程序時,加上-g參數(shù),指定程序在編譯時包含調(diào)試信息。調(diào)試信息包

39、含程序里的每個變量的類型和在可執(zhí)行文件里的地址映射以及源代碼的行號。gdb 利用這些信息使源代碼和機器碼相關(guān)聯(lián)。</p><p><b>  1.gdb的啟動</b></p><p>  在終端窗口中,有兩種方法運行g(shù)db,即在終端窗口的命令行中直接輸入gdb命令或gdb filename命令運行g(shù)db,下面分別介紹。</p><p><

40、b>  方法1:</b></p><p>  先啟動gdb后執(zhí)行file filename命令。即</p><p><b>  gdb</b></p><p>  file filename</p><p>  執(zhí)行上述兩條命令就可啟動gdb,并裝入可執(zhí)行的程序filename。</p>

41、<p><b>  方法2:</b></p><p>  啟動gdb的同時裝入可執(zhí)行的程序。即</p><p>  gdb filename</p><p>  其中,filename是要調(diào)試的可執(zhí)行文件。用這種方式運行g(shù)db可以直接指定想要調(diào)試的程序。這和啟動gdb后執(zhí)行file filename命令效果完全一樣。</p>

42、;<p>  啟動gdb后,就可以使用gdb的命令調(diào)試程序。</p><p>  2.gdb的基本命令</p><p>  gdb中的命令主要分為以下幾類:工作環(huán)境相關(guān)命令、設(shè)置斷點與恢復命令、源代碼查看命令、查看運行數(shù)據(jù)相關(guān)命令及修改運行參數(shù)命令。gdb的命令可以通過help命令進行查找命令所屬的種類(class),可以從相關(guān)class找到相應(yīng)命令。如下所示:</p&

43、gt;<p>  (gdb) help 此命令可列出命令的種類。</p><p>  (gdb) help data 此命令查找data類種的命令,并列出data類種的所有命令。</p><p>  (gdb) help call 此命令查找call命令。</p><p>  若用戶已知命令名,直接鍵入“help [comma

44、nd]”來查看命令。</p><p>  下面分別對這幾類的命令進行講解。</p><p> ?。?)工作環(huán)境相關(guān)命令</p><p>  gdb中不僅可以調(diào)試所運行的程序,而且還可以對程序相關(guān)的工作環(huán)境進行相應(yīng)的設(shè)定,甚至還可以使用shell中的命令進行相關(guān)的操作,其功能極其強大。表9.2所示列出了gdb常見工作環(huán)境相關(guān)命令。</p><p&g

45、t;  表9.2      gdb工作環(huán)境相關(guān)命令</p><p>  (2) 設(shè)置斷點與恢復命令</p><p>  gdb中設(shè)置斷點與恢復的常見命令如表9.3所示。</p><p>  表9.3      gdb設(shè)置斷點與恢復相關(guān)命令</

46、p><p> ?。?)gdb中源碼查看相關(guān)命令</p><p>  在gdb中可以查看源碼以方便其他操作,它的常見相關(guān)命令如表9.4所示:</p><p>  表9.4     gdb源碼查看相關(guān)相關(guān)命令</p><p>  (4) gdb中查看運行數(shù)據(jù)相關(guān)命令</p><p&

47、gt;  gdb中查看運行數(shù)據(jù)是指當程序處于“運行”或“暫?!睜顟B(tài)時,可以查看的變量及表達式的信息,其常見命令如表9.5所示:</p><p>  表9.5        gdb查看運行數(shù)據(jù)相關(guān)命令</p><p> ?。?)其他gdb命令</p><p>  ● run命令:執(zhí)行當前被

48、調(diào)試的程序。 </p><p>  ● kill命令:停止正在調(diào)試的應(yīng)用程序。 </p><p>  ● watch命令:設(shè)置監(jiān)視點,監(jiān)視表達式的變化。 </p><p>  ● awatch命令:設(shè)置讀寫監(jiān)視點。當要監(jiān)視的表達式被讀或?qū)憰r將應(yīng)用程序掛起。它的語法與watch命令相同。 </p><p>  ● rwatch命令:設(shè)置讀監(jiān)視

49、點,當監(jiān)視表達式被讀時將程序掛起,等侍調(diào)試。此命令的語法與watch相同。 </p><p>  ● info break命令:顯示當前斷點列表,包括每個斷點到達的次數(shù)。 </p><p>  ● info files命令:顯示調(diào)試文件的信息。 </p><p>  ● info func命令:顯示所有的函數(shù)名。 </p><p>  ●

50、info local命令:顯示當前函數(shù)的所有局部變量的信息。 </p><p>  ● info prog命令:顯示調(diào)試程序的執(zhí)行狀態(tài)。 </p><p>  ● Shell命令:執(zhí)行Linux Shell命令。 </p><p>  ● make命令:不退出gdb而重新編譯生成可執(zhí)行文件。 </p><p>  ● Quit命令:退出gdb

51、。</p><p>  (6) gdb中修改運行參數(shù)相關(guān)命令</p><p>  gdb還可以修改運行時的參數(shù),并使該變量按照用戶當前輸入的值繼續(xù)運行。它的設(shè)置方法為:在單步執(zhí)行的過程中,鍵入命令“set 變量=設(shè)定值”。這樣,在此之后,程序就會按照該設(shè)定的值運行了。 </p><p>  特別注意,在gcc編譯選項中一定要加入”-g”。只有在代碼處于“運行”或“暫

52、?!睜顟B(tài)時才能查看變量值,設(shè)置斷點后程序在指定行之前停止。</p><p>  9.3 GNU make命令和makefile 文件</p><p>  Makefile文件描述了目標文件之間的依賴關(guān)系,以及指定編譯過程中使用的工具。Makefile里主要包含了五個方面:顯式規(guī)則、隱晦規(guī)則、變量定義、文件指示和注釋。</p><p>  ● 顯式規(guī)則。顯式規(guī)則說明

53、了如何生成一個或多個目標文件。這是由Makefile的書寫者明顯指出,要生成的文件、文件的依賴文件、生成的命令。</p><p>  ● 隱晦規(guī)則。由于make有自動推導的功能,所以隱晦的規(guī)則可以讓我們比較簡略地書寫Makefile,這是由make所支持的。</p><p>  ● 變量的定義。在Makefile中可定義一系列的變量,變量一般是字符串,類似C語言中的宏,當Makefile被

54、執(zhí)行時,其中的變量都會被擴展到相應(yīng)的引用位置上。</p><p>  ● 文件指示。其包括3個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據(jù)某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣;還有就是定義一個多行的命令。</p><p>  ● 注釋。Makefile中只有行注釋,和UNIX的Shell

55、腳本一樣,其注釋是用“#”字符,如果在Makefile中使用“#”字符,可以用反斜框進行轉(zhuǎn)義,如:“\#”。</p><p>  最后,還值得一提的是,在Makefile中的命令,必須要以[Tab]鍵開始。</p><p>  Makefile定義了一系列的規(guī)則來指定哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復雜的功能操作。Makefile就像一個Shell

56、腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。Makefile帶來的好處就是“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大地提高了軟件開發(fā)的效率。GNU的make工作時的執(zhí)行步驟如下:</p><p>  讀入所有的Makefile。</p><p>  讀入被include的其它Makefile。</p><p>  初始化文件中的變量。&

57、lt;/p><p>  推導隱晦規(guī)則,并分析所有規(guī)則。</p><p>  為所有的目標文件創(chuàng)建依賴關(guān)系鏈。</p><p>  根據(jù)依賴關(guān)系,決定哪些目標要重新生成。</p><p><b>  執(zhí)行生成命令。</b></p><p>  1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果

58、定義的變量被使用了,那么,make會把其展開在使用的位置。但make并不會完全馬上展開。make程序利用Makefile中的數(shù)據(jù)和每個文件的最后修改時間來確定那個文件需要更新,對于需要更新的文件,make程序執(zhí)行Makefile數(shù)據(jù)中定義的命令來更新。 </p><p>  9.3.1 Makefile文件的規(guī)則 </p><p>  GNU make的主要功能是讀入一個文本文件make

59、file,并根據(jù)makefile的內(nèi)容執(zhí)行一系列的工作。Makefile的默認文件名為GNU makefile、makefile或Makefile,也可以在make的命令行中指定別的文件名。如果不特別指定,make命令在執(zhí)行時將按順序查找默認的Makefile文件。多數(shù)Linux程序員使用第三種文件名Makefile。因為第一個字母是大寫,通常被列在一個目錄的文件列表的最前面。 </p><p>  Makefi

60、le文件包含一些規(guī)則。這些規(guī)則主要是描述哪些文件(稱為target目標文件,注意,不是指編譯時產(chǎn)生的目標文件)是從哪些別的文件(稱為dependency依賴文件)中產(chǎn)生的,以及用什么命令(command)來執(zhí)行這個過程。</p><p>  依靠這些信息,make會對磁盤上的文件進行檢查,如果目標文件的生成或被改動時的時間(稱為該文件時間戳)至少比它的一個依賴文件還舊的話,make就執(zhí)行相應(yīng)的命令,以更新目標文件

61、。目標文件不一定是最后的可執(zhí)行文件,可以是任何一個中間文件并可以作為其他目標文件的依賴文件。</p><p>  1.Makefile書寫規(guī)則</p><p>  一個Makefile文件主要含有一系列的規(guī)則,每條規(guī)則包含以下內(nèi)容。</p><p>  ● 一個目標(target),即make最終需要創(chuàng)建的文件,如可執(zhí)行文件和目標文件;目標也可以是要執(zhí)行的動作,如c

62、lean。</p><p>  ● 一個或多個依賴文件(dependency)列表,通常是編譯目標文件所需要的其他文件。</p><p>  ● 一系列命今(command),是make執(zhí)行的動作,通常是把指定的相關(guān)文件編譯成目標文件的編譯命令,每個命令占一行,且每個命令行起始字符必須為TAB字符。 </p><p>  除非特別指定,否則make的工作目錄就是當前

63、目錄。target是需要創(chuàng)建的二進制文件或目標文件,dependency是在創(chuàng)建target時需要用到的一個或多個文件的列表,命令序列是創(chuàng)建target文件所需要執(zhí)行的步驟,比如編譯命令。</p><p>  Makefile規(guī)則的一般形式如下:</p><p>  target:dependency dependency</p><p>  (tab)<co

64、mmand> </p><p>  例如,有以下的Makefile文件:</p><p>  # 一個簡單的Makefile的例子</p><p>  test:prog.o code.o</p><p>  gcc –o test prog.o code.o</p><p>  prog.o:prog.c pr

65、og.h code.h</p><p>  gcc –c prog.c –o prog.o</p><p>  code.o:code.c code.h</p><p>  gcc –c code.c –o code.o</p><p><b>  clean:</b></p><p>  rm

66、–f *.o </p><p>  上面的Makefile文件中共定義了4個目標:test、prog.o、code.o和clean。目標從每行的最左邊開始寫,后面跟一個冒號(:),如果有與這個目標有依賴性的其他目標或文件,把它們列在冒號后面,并以空格隔開。然后另起一行開始寫實現(xiàn)這個目標的一組命令。在Makefile中,可使用續(xù)行號(\)將一個單獨的命令行延續(xù)成幾行。但要注意在續(xù)行號(\)后面不能跟任何字符(包括空

67、格和鍵)。</p><p>  一般情況下,調(diào)用make命令可輸入:</p><p>  # make target</p><p>  target是Makefile文件中定義的目標之一,如果省略target,make就將生成Makefile文件中定義的第一個目標。對于上面Makefile的例子,單獨的一個make命令等價于:</p><p&g

68、t;  # make test</p><p>  因為test是Makefile文件中定義的第一個目標,make首先將其讀入,然后從第一行開始執(zhí)行,把第一個目標test作為它的最終目標,所有后面的目標的更新都會影響到test的更新。第一條規(guī)則說明只要文件test的時間戳比文件prog.o或code.o中的任何一個舊,下一行的編譯命令將會被執(zhí)行。 </p><p>  內(nèi)核源代碼中Make

69、file被分布在目錄樹中,與Makefile直接相關(guān)的文件有配置文件.config和規(guī)則文件Rules.make。頂層Makefile是整個內(nèi)核配置、編譯的總體控制文件。在頂層Makefile中的語句:include arch/$(ARCH)/Makefile,包含了特定CPU體系結(jié)構(gòu)下的Makefile,這個Makefile中包含了平臺相關(guān)的信息。配置文件.config包含由用戶選擇項,用來存放內(nèi)核配置后的結(jié)果(如make confi

70、g)。位于各種CPU體系目錄下的Makefile,比如drivers/Makefile,負責所在子目錄下源代碼的管理。規(guī)則文件Rules.make,則被所有的Makefile使用。</p><p>  用戶通過make config配置后,產(chǎn)生了.config。頂層Makefile讀入.config中的配置選擇。頂層Makefile有兩個主要的任務(wù):產(chǎn)生壓縮的內(nèi)核鏡像vmlinux文件和內(nèi)核模塊module。為了

71、達到此目的,頂層Makefile遞歸的進入到內(nèi)核的各個子目錄中,分別調(diào)用位于子目錄中的Makefile。至于到底進入哪些子目錄,取決于內(nèi)核的配置。位于各個子目錄下的Makefile同樣也根據(jù).config給出的配置信息,構(gòu)造出當前配置下需要的源文件列表,并在文件的最后有include $(TOPDIR)/ Rules.make。</p><p>  2. 在規(guī)則中使用通配符</p><p>

72、;  如果定義一系列比較類似的文件,很自然地就想起使用通配符。make支持3種通配符:“*”,“?”和“[...]”。這是和Unix的B-Shell是相同的。</p><p>  波浪號(“~”)字符在文件名中有特殊的用途。如“~test”表示當前用戶的$HOME目錄下的test目錄。而“~hchen/test”則表示用戶hchen的宿主目錄下的 test目錄。通配符 “*.c”表示所有以后綴為c的文件。例如:&

73、lt;/p><p>  print: *.c</p><p><b>  lpr -p $?</b></p><p>  touch print</p><p>  該例說明目標print依賴于所有的[.c]文件。其中的“$?”是一個自動化變量。</p><p><b>  3.偽目標<

74、;/b></p><p>  偽目標又稱假想目標,如:</p><p><b>  clean:</b></p><p>  rm *.o temp</p><p>  這里并不生成“clean”這個文件?!皞文繕恕辈⒉皇且粋€文件,只是一個標簽,由于“偽目標”不是文件,所以make無法生成它的依賴關(guān)系和決定它是否要

75、執(zhí)行。</p><p>  可使用“make clean”來使用該目標。</p><p>  如果你的Makefile需要生成若干個可執(zhí)行文件,可把所有的目標文件都寫在一個Makefile中,可聲明了一個“all”的偽目標,例如:</p><p>  all : prog1 prog2 prog3</p><p>  prog1 : prog

76、1.o utils.o</p><p>  cc -o prog1 prog1.o utils.o</p><p>  prog2 : prog2.o</p><p>  cc -o prog2 prog2.o</p><p>  prog3 : prog3.o sort.o utils.o</p><p>  cc

77、-o prog3 prog3.o sort.o utils.o</p><p>  上面的Makefile中的第一個目標“all”會被作為其默認目標,其依賴于其它3個目標。由于偽目標總是被執(zhí)行的,其依賴的那3個目標就總是不如“all”這個目標新。所以,其它3個目標的規(guī)則總是會被決議。也就達到了連續(xù)生成多個目標的目的。從上面的例子可以看出,目標也可以成為依賴,偽目標同樣也可成為依賴。</p><

78、p>  9.3.2 Makefile文件中隱含規(guī)則</p><p>  “隱含規(guī)則”是指在Makefile中的“隱含的”,早先約定了的、不需要再寫出來的規(guī)則。若要使用隱含規(guī)則生成需要的目標,make會試圖去自動推導產(chǎn)生這個目標的規(guī)則和命令,若make可以自動推導生成這個目標的規(guī)則和命令,那么這個行為就是隱含規(guī)則的自動推導。</p><p><b>  1.常用的隱含規(guī)則&

79、lt;/b></p><p>  ● 編譯C程序的隱含規(guī)則:<n>.o的目標的依賴目標會自動推導為<n>.c,并且其生成命令是$(CC) –c $(CPPFLAGS) $(CFLAGS)。</p><p>  ● 編譯C++程序的隱含規(guī)則:<n>.o的目標的依賴目標會自動推導為<n>.cc或是<n>.C,并且其生成命令是$(

80、CXX) –c $(CPPFLAGS) $(CFLAGS)。(建議使用.cc作為C++源文件的后綴,而不是.C)</p><p>  ● 匯編和匯編預處理的隱含規(guī)則:<n>.o的目標的依賴目標會自動推導為<n>.s,默認使用編譯器as,并且其生成命令是:$(AS) $(ASFLAGS)。<n>.s的目標的依賴目標會自動推導為<n>.S,默認使用C預編譯器cpp,并且

81、其生成命令是:$(AS) $(ASFLAGS)。</p><p>  ● 鏈接Object文件的隱含規(guī)則——<n>目標依賴于<n>.o,通過運行C 的編譯器來運行鏈接程序生成(一般是ld),其生成命令是:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)。這個規(guī)則對于只有一個源文件的工程有效,同時也對多個Object文件(由不同的源文件生

82、成)也有效。</p><p>  2.隱含規(guī)則使用的變量</p><p>  在隱含規(guī)則的命令中,基本上都是使用了一些預先設(shè)置的變量??梢栽贛akefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在環(huán)境變量中設(shè)置這些值。當然,也可以利用make的-R或--no–builtin-variables參數(shù)來取消所定義的變量對隱含規(guī)則的作用。下面是隱含規(guī)則中常用到的變量。<

83、;/p><p> ?。?)關(guān)于命令的變量。</p><p>  ● AR :函數(shù)庫打包程序。默認命令是ar。</p><p>  ● AS :匯編語言編譯程序。默認命令是as。</p><p>  ● CC :C語言編譯程序。默認命令是cc。</p><p>  ● CXX :C++語言編譯程序。默認命令是g++。<

84、/p><p>  ● CPP :C程序的預處理器(輸出是標準輸出設(shè)備)。默認命令是$(CC) –E。</p><p>  ● RM :刪除文件命令。默認命令是rm –f。</p><p> ?。?)關(guān)于命令參數(shù)的變量</p><p>  以下變量都是相關(guān)上面的命令的參數(shù)。若沒有指明其默認值,則其默認值都是空。</p><p&

85、gt;  ● ARFLAGS :函數(shù)庫打包程序AR命令的參數(shù)。默認值是rv。</p><p>  ● ASFLAGS :匯編語言編譯器參數(shù)。(當明顯地調(diào)用“.s”或“.S”文件時)。</p><p>  ● CFLAGS :C語言編譯器參數(shù)。</p><p>  ● CXXFLAGS :C++語言編譯器參數(shù)。</p><p>  ● CPPF

86、LAGS :C預處理器參數(shù)。( C 和 Fortran 編譯器也會用到)。</p><p>  ● FFLAGS :Fortran語言編譯器參數(shù)。</p><p>  ● GFLAGS :SCCS get程序參數(shù)。</p><p>  ● LDFLAGS :鏈接器參數(shù)。(如:ld)</p><p><b>  3.自動化變量<

87、;/b></p><p>  自動化變量只出現(xiàn)在規(guī)則的命令中,這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。常用的自動化變量如下。</p><p>  ● $@ :表示規(guī)則中的目標文件集。在模式規(guī)則中,如果有多個目標,那么,"$@"就是匹配于目標中模式定義的集合。</p><p>  ● $% :僅

88、當目標在函數(shù)庫文件中,表示規(guī)則中的目標成員名。例如,如果一個目標是foo.a (bar.o),那么,$%就是bar.o,$@就是foo.a。如果目標不是函數(shù)庫文件(Unix下是[.a],Windows下是[.lib]),那么,其值為空。</p><p>  ● $< :依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那么"$<"將是符合模式的

89、一系列的文件集。注意,是一個一個取出來的。</p><p>  ● $? :所有比目標新的依賴目標的集合,以空格分隔。</p><p>  ● $^ :所有的依賴目標的集合,以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。</p><p>  ● $+ :這個變量很像"$^",也是所有依賴目標的集合。只是它

90、不去除重復的依賴目標。</p><p>  ● $* :這個變量表示不包含擴展名的目標文件名,即目標模式中"%"及其之前的部分。如果目標是dir/a.foo.b,并且目標的模式是a.%.b,那么,$*的值就是dir/a.foo。這個變量對于構(gòu)造有關(guān)聯(lián)的文件名是很有用。如果目標中沒有模式的定義,那么$*也就不能被推導出,但是,如果目標文件的后綴是make 所識別的,那么$*就是除了后綴的那一部分

91、。例如:如果目標是foo.c,因為.c是make所能識別的后綴名,所以,$*的值就是foo。這個特性是GNU make的,很有可能不兼容于其它版本的make,所以,你應(yīng)該盡量避免使用$*,除非是在隱含規(guī)則或是靜態(tài)模式中。</p><p>  對于上面的七個變量都可以分別加上D或是F,表示取文件的目錄部分和文件部分。下面以$@為例說明其含義:</p><p>  ● $(@D) :表示$@的

92、目錄部分(不以斜杠作為結(jié)尾),如果$@值是dir/foo.o,那么$(@D)就是dir,若$@中沒有包含斜杠的話,其值就是“.”(當前目錄)。</p><p>  ● $(@F) :表示$@的文件部分,如果$@值是dir/foo.o,那么$(@F) 就是foo.o,$(@F)相當于函數(shù)$(notdir $@)。</p><p>  9.3.3 Makefile文件的命令 </p&g

93、t;<p>  每條規(guī)則中的命令和操作系統(tǒng)Shell的命令行是一致的。make會一按順序一條一條的執(zhí)行命令,每條命令的開頭必須以Tab鍵開頭,除非命令是緊跟在依賴規(guī)則后面的分號后的。在命令行之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那么make會認為其是一個空命令。</p><p><b>  1.顯示命令</b></p><p&

94、gt;  通常,make會把其要執(zhí)行的命令行在命令執(zhí)行前輸出到屏幕上。當用“@”字符在命令行前,那么,這個命令將不被make顯示出來,如:</p><p>  @echo 正在編譯XXX模塊......</p><p>  當make執(zhí)行時,會輸出“正在編譯XXX模塊......”字符串,但不會輸出命令,如果沒有“@”,那么,make將輸出:</p><p>  e

95、cho 正在編譯XXX模塊......</p><p>  正在編譯XXX模塊......</p><p>  如果make執(zhí)行時,帶入make參數(shù)-n或--just-print,則只是顯示命令,而不會執(zhí)行命令,這個功能很有利于調(diào)試Makefile文件。</p><p>  而make參數(shù)-s或--slient則是全面禁止命令的顯示。</p><

96、p><b>  2.命令執(zhí)行</b></p><p>  當依賴目標新于目標時,也就是當規(guī)則的目標需要被更新時,make會一條一條的執(zhí)行其后的命令。如果要讓上一條命令的結(jié)果應(yīng)用到下一條命令時,應(yīng)該使用分號分隔這兩條命令。比如第一條命令是cd命令,希望第二條命令得在cd之后的基礎(chǔ)上運行,那么就不能把這兩條命令寫在兩行上,而應(yīng)該把這兩條命令寫在一行上,用分號分隔。如:</p>

97、<p><b>  exec:</b></p><p>  cd /home/hchen; pwd</p><p>  當執(zhí)行“make exec”時,cd就起作用了,pwd會打印出“/home/hchen”。</p><p><b>  3.命令出錯</b></p><p>  每當命

98、令運行完后,make會檢測每個命令的返回碼,如果命令返回成功,那么make會執(zhí)行下一條命令,當規(guī)則中所有的命令成功返回后,這個規(guī)則就算是成功完成了。如果一個規(guī)則中的某個命令出錯了(命令退出碼非零),那么make就會終止執(zhí)行當前規(guī)則,這將有可能終止所有規(guī)則的執(zhí)行。</p><p>  有時,命令的出錯并不表示就是錯誤的。例如mkdir命令,用戶一定需要建立一個目錄,如果目錄不存在,那么mkdir就成功執(zhí)行,如果目錄

99、存在,那么就出錯了。若不希望mkdir出錯而終止規(guī)則的運行,可以在Makefile的命令行前加一個減號“-”(在Tab鍵之后),標記為不管命令出不出錯都認為是成功的。例如:</p><p><b>  clean:</b></p><p>  -rm -f *.o</p><p>  若給make加上-i或是--ignore-errors參數(shù),

100、那么,Makefile中所有命令都會忽略錯誤。而如果一個規(guī)則是以.IGNORE作為目標的,那么這個規(guī)則中的所有命令將會忽略錯誤。這些是不同級別的防止命令出錯的方法,可以根據(jù)不同的愛好設(shè)置。</p><p>  若make的參數(shù)的是-k或是--keep-going,如果某規(guī)則中的命令出錯了,那么就終目該規(guī)則的執(zhí)行,但繼續(xù)執(zhí)行其它規(guī)則。</p><p>  9.3.4 Makefile文件的

101、變量</p><p>  在Makefile中,變量可以使用在“目標”,“依賴目標”,“命令”或是Makefile的其它部分中。變量的命名字可以包含字符、數(shù)字,下劃線(可以是數(shù)字開頭),但不應(yīng)該含有“:”、“#”、“=”或是空字符(空格、回車等)。變量是大小寫敏感的,傳統(tǒng)的Makefile的變量名是全大寫的命名方式,但推薦使用大小寫搭配的變量名,例如:MakeFlags。</p><p>

102、  變量在聲明時需要給予初值,而在使用時,需要給在變量名前加上“$”符號,但最好用小括號“()”或是大括號“{}”把變量給包括起來。如果你要使用真實的“$”字符,那么需要用“$$”來表示。</p><p>  1.Makefile中的變量</p><p>  頂層Makefile定義并向環(huán)境中輸出了許多變量,并為各個子目錄下的Makefile傳遞一些信息。常用的變量有以下幾類:</p

103、><p><b>  (1)版本信息</b></p><p>  版本信息有VERSION、PATCHLEVEL、SUBLEVEL、EXTRAVERSION和KERNELRE LEASE等變量,用來定義當前內(nèi)核的版本。比如,VERSION = 2,PATCHLEVEL = 4,SUBLEVEL = 18,EXTRAVERSION = -rmk7,共同構(gòu)成內(nèi)核的發(fā)行版本KE

104、RNELRELEASE:2.4.18-rmk7。</p><p> ?。?)CPU體系結(jié)構(gòu)ARCH</p><p>  在頂層Makefile的開頭,用ARCH定義目標CPU的體系結(jié)構(gòu),比如,ARCH:=arm。許多子目錄的Makefile中,要根據(jù)ARCH的定義選擇編譯源文件的列表。</p><p> ?。?)路徑信息TOPDIR和SUBDIRS</p>

105、;<p>  TOPDIR定義了Linux內(nèi)核源代碼所在的根目錄。例如,各個子目錄下的Makefile通過$(TOPDIR)/Rules.make就可以找到Rules.make的位置。</p><p>  SUBDIRS定義了一個目錄列表,在編譯內(nèi)核或模塊時,頂層Makefile根據(jù)SUBDIRS來決定進入哪些子目錄。SUBDIRS的值取決于內(nèi)核的配置,在頂層Makefile中SUBDIRS賦值為k

106、ernel drivers mm fs net ipc lib;根據(jù)內(nèi)核的配置情況,在arch/*/Makefile中擴充了SUBDIRS的值,可參考arch/arm/Makefile的例子。</p><p> ?。?)內(nèi)核組成信息HEAD,CORE_FILES,NETWORKS,DRIVERS,LIBS。 </p><p>  Linux內(nèi)核文件vmlinux是由以下規(guī)則產(chǎn)生的:<

107、/p><p>  vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs</p><p>  $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o --start-group </p><p>  $(CORE_FILES) \$(DRIVE

108、RS) $(NETWORKS) $(LIBS) --end-group -o vmlinux</p><p>  可以看出,vmlinux是由HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS和LIBS組成的。這些變量(如HEAD)都是用來定義鏈接生成vmlinux所需的目標文件和庫文件列表。其中,HEAD在arch/arm/Makefile中定義,用來確定最先鏈接

109、進vmlinux的文件列表。比如,對于ARM系列HEAD的定義為:</p><p>  HEAD := arch/arm/kernel/head-$(PROCESSOR).o \</p><p>  arch/arm/kernel/init_task.o</p><p>  表明head-$(PROCESSOR).o和init_task.o需要最先被鏈接到vmlin

110、ux中。PROCESSOR為armv或armo,取決于目標CPU。</p><p> ?。?)編譯信息CPP,CC,AS,LD,AR,CFLAGS,LINKFLAGS</p><p>  在Rules.make中定義的是編譯的通用規(guī)則,具體到特定的場合,需明確給出編譯環(huán)境,編譯環(huán)境是在以上的變量中定義的。針對交叉編譯的要求,定義了CROSS_COMPILE。比如:</p>&

111、lt;p>  CROSS_COMPILE   = arm-linux-</p><p>  CC = $(CROSS_COMPILE)gcc</p><p>  LD = $(CROSS_COMPILE)ld</p><p><b>  ......</b></p><p>

112、  由于CROSS_COMPILE定義了交叉編譯器前綴arm-linux-,表明所有的交叉編譯工具都是以arm-linux-開頭的,所以在各個交叉編譯器工具之前,都加入了$(CROSS_COMPILE),以組成一個完整的交叉編譯工具文件名,比如,arm-linux-gcc。</p><p>  CFLAGS定義了傳遞給C編譯器的參數(shù)。</p><p>  LINKFLAGS是鏈接生成vml

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 眾賞文庫僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論