本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.

tw
或來電(02)2705-5066

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066


嵌入式 Linux 作業系統與軟體、硬體都相關,使用的技術幾乎覆蓋了電腦技術
的全部方面。因此 Linux 技術較難被學生和讀者掌握。鑒於 Linux 這種通用作業系統
在嵌入式應用中的良好表現,電腦專業的學生學習 Linux 很有必要。
由於 Linux 實在太大了,用它來建立作業系統的概念確實很困難。如果不分析
代碼,則又是空洞無比,如若分析代碼,則又會被代碼的資料海洋所淹沒。所以本
書還是以精巧的微內核 μC/OS-II 切入,使讀者能快速建立起作業系統的調度、通
訊、同步等基礎概念。而對於 Linux,則以入門為主要目標,以介紹 Linux 用來描述
作業系統各個部分的主要資料結構為主,力求使讀者對 Linux 的組成與結構能有一
個較清晰的瞭解和掌握,為讀者進一步精研 Linux 打下基礎。再就是作為一本作業
系統內核入門的書籍,本書在文字和語言敘述上盡量言簡意賅。
全書內容共九章,參與本書編寫的有潘樹林老師、房紅征老師和任哲老師,由
任哲老師擔任主編並負責全書稿的統一整理工作。
在本書的編寫過程中,得到了很多師長的關心、指導和幫助。特別是書中在
ARM 上移植一章中使用了周立功先生的方法和程式碼,部分章節還使用了 Jean J.
Labrosse 先生書中的部分例題代碼。同時,在編寫本書的過程中,作者還參閱了大
量的參考書籍,並在書中引用了這些書籍中的一些文字和插圖。為此向為本書做過
貢獻的專家與學者表示衷心的感謝!
本書僅是一本入門書,用意是藉此引導讀者可以順利地使用文獻學習作業系
統,本書僅在相關學習難點上做了較詳細的講解。關於作業系統以及 Linux 的學習
問題,請參閱以下的問答討論:
1. 尖端軟體設計工程師,學習 Linux 作業系統是否有必要?
Yes! 因為設計的軟體在運行時,作業系統是你的應用程式的一部分。如果你對
使用的作業系統不瞭解,如何判斷你的工作是必需的,如何向客戶說明你的軟體是
安全的,如何保證當系統升級時,你的軟體是相容的?
2. 硬體研究工程師,學習作業系統有意義嗎?
現在做硬體研究的工程師一定會接觸到軟體研究。試想,要設計一個硬體驅動

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

程式,你不瞭解作業系統的設備管理可以嗎?尤其是在嵌入式系統應用如此廣泛的
現今,你具備嵌入式作業系統的選用、剪裁及改造能力嗎?你能成為一個合格的嵌
入式工程師嗎?
3. 本書為什麼都在介紹 Linux,而不是介紹 Windows 等及其他作業系統呢?
因為所依據的軟體實例必須是源碼開放的,否則會引起專利權與知識產權問
題。就目前狀況來看,適合作為電腦專業教學使用,且源碼開放的只有 Linux,而相
較於其他作業系統則只能就其公開的技術做一般的介紹。
4.Linux 軟體是否具有適用性?
此問題比較複雜,Linux 至少在作業系統中較重要的微內核概念很難通過它來
體現。此外 Linux 的進程(程序)、線程的概念不很清晰(但很實務),可能會在學
習階段容易造成一些不必要的混亂。因作業系統屬實務體,若具體到各系統就免不
了各有高手,對於電腦這種非線性系統來說,不同技術間的差異很大。因此,希望
僅以一個實例就把所有作業系統弄清楚的想法行不通,加之前面所說的開放源碼問
題,現在的教學實例也只能是 Linux 了。但由於作業系統的基本作用和原理都大致
相同,所以學習了 Linux 之後,學習其他系統也就不難了。
5. 學習作業系統能提高編程能力嗎?
Yes! 前面已經解釋過了,作業系統實際上是實體的大程式實例。在相關課程
中很難用實例介紹的資料結構、演算法、設計模式,以及軟體工程中提到的「高內
聚、低耦合」等軟體設計原則、代碼動態鏈結以及充分利用指標完成的各種虛擬技
術等,在作業系統中都有極為充分的體現,注意整理總結,個人的軟體設計能力會
有突飛猛進的提高。
6. 學習 Linux 難不難?
Linux 入門、甚至應用都不難,但要精通確實很難。想想看,從 Linux 出現到
現在,從程式設計方法上,它歷經了面向過程程式設計、面向物件程式設計兩大
階段;從內核結構上,儘管它很難改變當初的宏內核設計,但後來的一些內核新概

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

念,例如層次化內核和微內核,也給了它極大的影響,加之為了嵌入式應用它還在
實現即時內核方面做出了很大努力。因此可以說,Linux 是集各種編程思想和方法之
大成者。因 Linux 所採用技術之廣泛和繁雜,要精通它確實很難,所以從這個角度
看,它並不是一個特別理想的教學實例。在此作者也建議初學者在閱讀本書時,最
好看一些介紹其他小型作業系統的書籍和資料,例如開源的 μC/OS-II、T-KERNEL
等,以快速瞭解作業系統的一些基礎概念。另外,在閱讀本書介紹的一些大型資料
結構時,也需要某種程度的「不求甚解」,以把握其中的主要內容。
7. 這本書適合哪類讀者?如何學習 Linux ?
學無定法,作者只能提一些建議。如果只是單純以一般的尖端應用為目的,
那麼作業系統就是一堆函數,將這些函數的用途及使用方法弄清楚也就可以了,這
本書可能不適合你。但想要對作業系統用途、原理、特點和實現方法有一定程度的
瞭解或是想入門的話,這本書就比較合適,因為這本書儘量做到注重說理、深度適
中、文字通俗易懂。當然,要深入研究的話,單靠這一本小書就遠遠不夠了,還需
看一些理論性較強或對代碼剖析的比較詳細的書籍(市面上這類書籍已經逐漸多了
起來)。
學習方法建議:最好是以 Shell 編程來熟悉 Linux,以閱讀資料結構來瞭解內核
架構,以編寫守護進程和驅動程式為突破點來理解 Linux 內核。
本書作者群:為樊生文、房紅征、潘樹林與任哲,其中樊生文與任哲為主編並
負責書稿。
作者的電子郵箱為 renzhe71@sina.com。 

任哲

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

目 錄

1
2

3

Linux 的基礎知識

1

1.1 Linux 系統簡介

2

1.2 Linux 系統的嵌入式應用

6

1.3 Linux 中的 C 語音和組合語言

11

1.4 Linux 中的鏈表

13

1.5 Linux 模組

23

Linux 的記憶體管理

41

2.1 記憶體管理之目標

42

2.2 虛擬記憶體

42

2.3 Linux 實體記憶體的管理

58

2.4 Linux 虛擬記憶體空間描述

62

2.5 Linux 的內核空間

67

2.6 內核空間的 Slab 分配模式

85

2.7 Linux 記憶體管理的總貌

88

Linux 程序及其管理
3.1 Linux 程序(processing)

91
92

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

4
5

3.2 Linux 程序的創造

100

3.3 Linux 程序調度

118

3.4 Linux 2.6 對調度器的改進

125

中斷/異常和系統呼叫

135

4.1 處理器的硬體中斷機制

136

4.2 Linux 的兩級中斷

140

4.3 Linux 硬中斷結構

142

4.4 Linux 軟中斷結構

152

4.5 系統呼叫

170

Linux 檔案系統

179

5.1 檔案與檔案系統

180

5.2 檔案的儲存

184

5.3 檔案目錄

189

5.4 Ext2 檔系統

194

5.5 其他常用檔案系統

203

5.6 作業系統的檔案管理系統

206

5.7 Linux 的虛擬檔系統

210

5.8 Linux 的 Proc 檔系統簡介

226

5.9 作業系統對檔系統的管理

227

5.10 檔與程序的關聯

230

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

6

7
8

5.11 根據檔案路徑查尋索引節點

236

5.12 程序創造時檔案的複製和共用

239

Linux 程序通訊

241

6.1 基本概念

242

6.2 System V IPC 機制簡介

244

6.3 共用記憶體

248

6.4 消息佇列

260

6.5 管道

270

6.6 Linux 的訊號

278

Linux 的同步控制

291

7.1 概述

292

7.2 Linux 訊號量集

297

Linux 設備驅動

315

8.1 概述

316

8.2 設備驅動程式

325

8.3 設備驅動程式及其內核介面

329

8.4 設備管理

330

8.5 Linux 的設備驅動程式

336

8.6 Linux 的字元設備驅動程式

340

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

9

μ CLinux 簡介

359

9.1 Linux 在嵌入式應用中的局限

360

9.2 μCLinux 的架構

360

9.3 μCLinux 的記憶體管理

362

9.4 μCLinux 的檔案系統

365

9.5 μCLinux 的開發環境

379

索引

381

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

1
Linux 的基礎知識
Linux 可以說是現代電腦技術的一個奇蹟,誕生之後,只經過了十幾年的
時間就風靡世界,並且在嵌入式系統中得到了廣泛應用。本章除了在簡單介紹
Linux 的發展歷史,重點介紹了 Linux 在嵌入式應用的各種修改版的特點之外,
還介紹了學習 Linux 必須要具備的一些基礎知識。

學 習 重 點
1.1 Linux 系統簡介
1.2 Linux 系統的嵌入式應用
1.3 Linux 中的 C 語音和組合語言
1.4 Linux 中的鏈表
1.5 Linux 模組

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

1.1 Linux 系統簡介 
0 世紀 0 年代,Andrew S.Tanenbaum 教授為了滿足教學的需要,編寫了一個
小型的 UNIX 作業系統 ─ MINIX。幾年後,芬蘭赫爾辛基大學的學生 Linus
Tovalds 在 MINIX 的基礎上編寫了一個可以實用的作業系統,這就是 Linux,為
了推廣 Linux,Linus Tovalds 加入了 GNU 組織,並將其發表在了互聯網上。
GNU 是一個自由軟體組織,該組織擬定了一份通用公共許可證(GPL, General Public License),保證 GNU 組織中的任何人都有發表、共用和修改軟體的
自由。由於這個組織可以充分發揮個人的想像力和創造力,因而吸引了眾多軟
體專家及業餘愛好者,顯示出旺盛的生命力。於是,Linux 一出現,便立即引起
了人們的極大興趣和重視,積極的投入到了 Linux 的改進和提高工作中,從而
使 Linux 迅速發展並取得了卓著的成績,以致到後來世界上許多著名軟硬體公司
(例如 IBM、COMPAQ、HP、Oracle、Sybase、Informix 等)也紛紛對 Linux 給
予了大力的支援,使得支援 Linux 的軟體及硬體產品迅速增加。目前,Linux 已
成為一個具有 UNIX 全部特徵、符合 POSIX 標準的作業系統。

1. Linux 系統的結構及特點
Linux 系統的結構圖如圖 1-1 所示。

用戶態

用戶進程
系統呼叫

核心態
(系統態)

虛擬內 進程
網絡 內核
協議 定時器 存管理 管理

虛擬文件系統
VFS
Ext2
文件系統 文件系統

其他文件系統

硬體抽象層
硬體系統

圖 1-1 Linux 系統的結構圖

Linux 是個典型的巨集內核(一體化)結構,硬體系統上面是硬體介面,在 

本試閱檔為五南所有。如欲購買此書,請至五南網站
www.wunan.com.tw
或來電(02)2705-5066

1.1 Linux 系統簡介
硬體介面上面是內核服務功能模組,這些模組經由系統呼叫介面向用戶提供任務
服務。而任務管理的系統包括:任務的創造、調度、終止及等待等。
Linux 支援記憶體管理控制器 MMU,使用虛擬記憶體管理機制,而虛擬記
憶體管理系統包括:記憶體分配、記憶體回收、請求分頁和交換頁等等。由於
Linux 使用了虛擬檔管理系統 VFS,從而使它能夠支援不同的檔系統。檔管理系
統允許用戶經由一組通用的系統呼叫 open、close、read、write 及 chmod 等,可
以對不同檔系統中的檔進行執行。
Linux 從 11 年問世到現在,短短的十幾年時間已經發展成為功能強大、
設計完善的作業系統之一,不僅可以與各種傳統的商業作業系統分庭抗爭,而且
在新興的嵌入式作業系統領域內也獲得了飛速發展。
所謂的嵌入式 Linux 是指對標準 Linux 經過小型化整合處理後,能夠使用在
容量只有幾 KB 或者幾 MB 的記憶體晶片或者單片機中,適合於特定嵌入式應
用場合的專用 Linux 作業系統。嵌入式 Linux 的開發和研究是作業系統領域中的
一個熱點,目前已經開發成功的嵌入式系統中,大約有一半的作業系統是使用
Linux,或者是修改版。
Linux 之所以能在嵌入式系統領域取得如此輝煌的成績,與其本身的優越特
性是分不開的,與其他作業系統相比,Linux 具有一系列顯著的特點,分別說明
如下。
(1) 高模組化程度
Linux 的內核設計非常精巧,分成任務調度、記憶體管理、任務間通訊、虛
擬檔系統和網路介面五大部分,獨特的模組機制可以根據用戶的需要,即時地將
某些模組插入或從內核中移走。使得 Linux 系統內核可以變得非常小巧,適合於
嵌入式系統的需要。
(2) 原始碼公開
由於 Linux 系統的開發從一開始就與 GNU 緊密結合,所以它的大多數組
成部分都直接來自 GNU 項目。任何人只要遵守 GPL 條款,就可以自由地使用
Linux 原始碼,為用戶提供了最大的自由度。這一點也正投嵌入式系統之所好。
因為嵌入式系統的應用為多樣化,設計者往往需要針對具體的應用,對原始碼進
行修改和最佳化,因而是否能獲得原始碼,對於嵌入式系統的開發是非常重要

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
的。加之 Linux 的軟體資源十分豐富,每一種通用程式在 Linux 上幾乎都可以找
到,並且數量還在不斷增加。這一切就使得設計者在其基礎之上進行二次開發變
得非常容易。同時,由於原始碼開放給各個教育機構,提供了極大的方便,也促
進了 Linux 的學習、推廣和應用。
(3) 廣泛的硬體支援
Linux 能夠支持 x、ARM、MIPS、ALPHA 及 PowerPC 等多種體系結構的
微處理器,目前也已經成功的使用在到數十種硬體平台上,幾乎能夠執行在所有
流行的處理器上。再加上目前世界上有眾多開發者在為 Linux 的擴充貢獻力量,
所以 Linux 有著異常豐富的驅動程式資源,支援各種主流硬體設備和最新的硬體
技術,甚至可以在沒有儲存管理單元 MMU 的處理器上執行,都進一步增進了
Linux 在嵌入式系統中的應用。
(4) 安全性及可靠性好
Linux 內核的高效和穩定已經在各個領域內得到了實際的驗證,Linux 中大
量網路管理及網路服務等方面的功能,可以使用戶很方便地建立高效穩定的防火
牆、路由器、工作站及伺服器等。此外,為提高安全性,Linux 還提供了大量的
網路管理軟體、網路分析軟體及網路安全軟體等。
(5) 具有優秀的開發工具
開發嵌入式系統的關鍵是需要有一套完善的開發和呼叫工具,傳統的嵌入式
開發呼叫工具是使用線上模擬器(In-Circuit Emulator,ICE),主要是經由取代目
標板的微處理器,給目的程式提供一個完整的模擬環境,從而使開發者能夠非常
清楚地了解到程式在目標板上的工作狀態,便於監視和呼叫程式,但是線上模擬
器的價格非常昂貴,而且只適合做非常基層的呼叫。而對於嵌入式 Linux 而言,
一旦軟硬體能夠支援正常的串口功能時,即使不用線上模擬器,也可以很好地進
行開發和呼叫工作,從而節省了一筆不小的開發費用。嵌入式 Linux 為開發者提
供了一套完整的工具鏈(tool chain),能夠很方便地實現從作業系統到應用軟體
各個級別的呼叫。
(6) 有很好的網路支援和檔案系統支援
Linux 從誕生之日起就與 Internet 密不可分,可以支援各種標準的 Internet 網
路協定,並且很容易安裝到嵌入式系統當中。目前,Linux 幾乎支援所有主流的 

本試閱檔為五南所有。如欲購買此書,請至五南網站
www.wunan.com.tw
或來電(02)2705-5066

1.1 Linux 系統簡介
網路硬體、網路協定和檔案系統,因此它是 NFS 的一個很好的平台。
另 一 方 面, 由 於 Linux 有 很 好 的 檔 系 統 支 援( 可 以 支 援 Ext、FAT 及
romfs 等檔系統),是資料備份、同步和複製的極佳平台,這些都為開發嵌入式
系統應用打下了良好的基礎。
(7) 與 UNIX 完全相容性
目前,在 Linux 中所包含的工具和實用程式,可以完成 UNIX 的所有主要功
能,具有完全的相容性。但是由於 Linux 不是為即時而設計的,這是 Linux 在即
時系統中應用的最大缺失,因此目前有許多的自由軟體愛好者,正在為此做著不
懈的努力,也取得了相當多的成果。

2. Linux 內核版本
Linux 內核版本號的編排規則為「x.yy.zz」,
其中:x ─ 取值範圍:0∼;
   yy ─ 取值範圍:0∼;
   zz ─ 取值範圍:0∼。
版本號的數字越大表示版本越新,有時候會在版本號的後面會出現 pNN 字
樣,NN 介於 0 ∼ 0 之間,它表示對某一個版本的修改次數。
在 Linux 版本號 x.yy.zz 中,x 的變化表示著版本有重大變化,yy 則表示著
某個版本的變遷與版本的種類,偶數表示為「發行版」,奇數為「開發版」,所謂
的發行版是經過一段時間的執行,已經穩定的版本,而正在開發但是尚未完全穩
定的則稱為開發版。

3. Linux 的一些商業版
由 Linus 所主持開發的 Linux 只是 Linux 的內核,一個內核要完成實用及完
整的作業系統,還要根據需要進行配置及擴充。此項工作對於一般應用者來說,
是一個很困難的事情。所以有很多軟體發展商會根據不同的應用目的,在 Linux
內核的基礎上進行了配置和擴充,從而形成了在市場上銷售的商業版的 Linux。
例如,Red Hat、Caldera、Ubuntu 及 SUSE 等等。這些商業 Linux 都是在某一個
Linux 內核的基礎上,根據應用目標進行配置生成的,一般在這些商業 Linux 中
都會增加安裝程式及常用的實用工具程式等等。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

1.2 Linux 系統的嵌入式應用
儘管 Linux 不是為嵌入式系統設計的,特別不是為即時系統設計的,但是,
由於它的模組化結構使其具有良好的刪減性,以及具有豐富的資源和原始碼開
放的特點,所以還是有人把它應用在嵌入式系統中,並獲得了一定的成功。多年
來,人們一直在對 Linux 進行修改和改造,並且形成了許多可以用於嵌入式即時
系統的 Linux 版本。本節重點介紹一些 Linux 的嵌入式修改版本,以使讀者清楚
Linux 在嵌入式應用,特別是即時應用時,它的弱點之所在,以及解決的方法,
從而更深入了解嵌入式即時操作系統的本質。

1. 完成即時 Linux 的方式
在解決 Linux 系統即時性問題方面,有以下的四個方式
(1) 提高時序精確度,解決中斷和及調度延遲之問題。
() 解決在 Linux 內核不允許調度的問題。
() 提供對於即時多媒體應用的支援,包括引入新穎的調度演算法(網路包調
度,任務調度及磁片調度)。
() 引入新穎的調度框架以及資源管理觀念,能夠更深入的支援網路系統中 QoS
的要求。

2. μ CLinux
μCLinux 是一種由 Linux.0 內核發展來的嵌入式 Linux 版本,是專為沒有
MMU 的微處理器(如 ARMTDMI、Coldfire 等)所設計的嵌入式 Linux 作業系
統。μCLinux 與 Linux 基本相同,不同的只是對 Linux 的記憶體管理和任務管理
部分進行了改寫,以滿足無 MMU 處理器的要求,主要是目前大多數嵌入式系統
不需要 MMU,或者說無法使用 MMU。
儘管大多數內核原始碼都被重寫,使得 μCLinux 的內核比 Linux .0 的內核
要小得多,但是 μCLinux 成功地保留了 Linux 作業系統的良好的穩定性、優異的
網路功能以及優秀的檔系統支援等主要優點。
而 μCLinux 系統多採用 romfs 檔案系統,romfs 是一種相對簡單及佔用空
間較少的檔系統,原因是內核支援 romfs 檔案系統比支援 Ext 檔案系統需要 

本試閱檔為五南所有。如欲購買此書,請至五南網站
www.wunan.com.tw
或來電(02)2705-5066

1.2 Linux 系統的嵌入式應用
更少的代碼;其次是 romfs 檔案系統相對簡單,在建立檔案系統超級塊(Superblock)時只需要更少的儲存空間。由於 romfs 是唯讀的檔案系統,禁止寫入動
作,因此系統同時需要虛擬碟(RAMDISK)支援暫存檔案和資料檔案的儲存。
隨著技術的發展,近年來日誌檔案系統在 μCLinux 系統上得到了較多的應
用,其中以支持 NOR FLASH 的 JFFS 及 JFFS 檔案系統和支援 NAND FLASH
的 YAFFS 最為流行,這些檔案系統都支援斷電保護,同時支援標準的 MTD 驅
動。
從目標系統開發者的角度來看,創造子任務的函數 vfork() 是 μCLinux 與標
準 Linux 應用程式之間的最重要不同之處,只有對 vfork() 與 fork() 兩個函數的差
異有了詳細的了解,才能順利地完成從 Linux 到 μCLinux 的程式移植。
目前,μCLinux 已在嵌入式系統中得到了成功的應用,但由於它並不是為了
Linux 的即時性而提出的,因此 μCLinux 對即時應用的支持方面並不完整,從而
使得它在嵌入式應用領域飽受批評。因為有些人們認為嵌入式系統必定是即時系
統,既然即時性不好,那麼 μCLinux 就不能叫做嵌入式作業系統。其實並不然,
在實際應用中確實有些嵌入式系統並不是即時系統,尤其不是硬即時系統。因
此,μCLinux 在一些沒有很強即時要求的嵌入式系統中的應用還是很優秀的。但
這畢竟是 μCLinux 的一個弱點和缺憾。也正是為了彌補這個缺憾,世界上為數眾
多的專家和愛好者正在努力地工作著,以解決此一問題。

3. RT-Linux 與 RTAI
為了提高 Linux 的即時性能,人們做出了巨大的努力,也取得了一些成果。
其中比較引人注意的就是 RT-Linux。RT-Linux 的設計概念極為簡單有效:單獨
設計一個取代型即時微內核,並由這個內核管理處理器,所有的即時任務,包括
普通 Linux 都在這個微內核之上執行。也就是說,在 RT-Linux 中把 Linux 也看
作是與其他即時任務一樣的一個任務了,但是它是一個優先順序別最低的任務。
這樣,就可以將即時任務交給微內核管理,而非即時任務則交給普通 Linux 內核
處理。於是既解決了 Linux 的即時性問題,又充分的利用了 Linux 的豐富資源,
RT-Linux 的設計概念及系統結構圖如圖 1- 所示。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

RTLinux 的任務

優先級別最低的
RTLinux 任務
非即時任務

即時任務
Linux 內核

RT-Linux 內核

計算機硬體

圖 1-2 RT-Linux 的結構圖

從圖中可以看到,即時任務是由即時微內核 RT-Linux 管理的,普通任務則
是由 Linux 所管理,而 Linux 又是由微內核 RT-Linux 管理的一個優先順序別最
低的任務,也就是說,Linux 也被看成了一個即時任務,只不過它優先順序別最
低,因此它的處理器使用權可以被所有其他的即時任務所取代,只有當沒有即時
任務佔用處理器時,才有機會去處理歸它所管轄的非即時任務。
其實讀者也可以想得到:如果把 μC/OS-II 與 Linux 或者 μCLinux 加以結合,
使用 μC/OS-II 做為作業系統的內核,而把 Linux 或者 μCLinux 做為 μC/OS-II 的
空閒任務,就可以使系統既能發揮 μC/OS-II 即時性好的優勢,又具有 Linux 的
強大功能,真正地實現了優勢互補的局面。
在實現上,RT-Linux 即時內核的關鍵技術是一個使用軟體完成的中斷控制
器,這個中斷控制器主要有兩個作用:一是當 Linux 要關閉處理器的中斷時,中
斷控制器就截取此一請求,但並不真正地去關閉處理器的中斷,而只是將它加以
記錄,並且使用此一記錄製造 Linux 系統的一個假象,使它「認為」處理器的中
斷已經被關閉了,其實這時處理器還是可以回應即時中斷的,如此就避免了由
於 Linux 關中斷所造成的即時系統在一段時間沒有回應的情況,從而提高了即時
性。二是當有外部硬體中斷時,由此一中斷控制器判斷,該中斷是否為即時中
斷,如果是即時中斷,當然應該由即時內核處理,但如果不是即時中斷,則將
此一中斷申請加以記錄,等到沒有即時任務執行時,再將此一中斷提交給普通 

本試閱檔為五南所有。如欲購買此書,請至五南網站
www.wunan.com.tw
或來電(02)2705-5066

1.2 Linux 系統的嵌入式應用
Linux 去處理。
另外,對於一個作業系統而言,精確的定時機制雖然可以提高任務調度的
即時性,但會增加處理器定時中斷的時間。RT-Linux 對時間精度和時序中斷處
理的時間開銷進行了折中考慮。不將計時器設計成 10ms 產生一次定時中斷的固
定模式,而是將計時器晶片設置為終端計時中斷方式。根據最近的任務的時間需
要,不斷地調整計時器的定時間隔,這樣不僅可以獲得高定時精度,同時中斷處
理的時間又可以做到最省。由於這種方法簡單有效,目前已經被很多作業系統,
包括一些商用版系統用來增強作業系統的即時性能。
RT-Linux 對於在重負荷下工作的專有系統來說,確實是一個不錯的選擇,
但是它僅僅提供了對於處理器資源的調度,即時系統和普通 Linux 系統之間關係
不是十分密切,從而使即時任務在一定程度上不能充分利用 Linux 系統中已經完
成的功能。所以 RT-Linux 適合應用在工業控制等即時任務功能簡單,並且有硬
體支援的場合。
再就是由於 Linux 在發展過程中,因為版本眾多,所以造成了 RT-Linux 在
不同 Linux 的版本之間安裝上的困難。為了解決這個問題,義大利的 RTAI 公司
在 Linux 上定義了一個即時抽象層(介面)。如此一來,即時任務就可以經由此
一介面和 Linux 系統進行交互傳遞,從而在 Linux 內核中增加即時支援時,可以
盡可能少地修改 Linux 的內核原始碼。

4. Kurt-Linux
Kurt-Linux 是由堪薩斯大學開發,可以提供微秒級(micro second)的即時
精度的即時操作系統,不同於 RT-Linux 單獨實現一個即時內核的做法,而是在
一般用的 Linux 系統基礎上所完成的。Kurt-Linux 將系統分為正常態、即時態和
混合態三種狀態,在正常態時採用普通的 Linux 的調度策略,在即時態只執行即
時任務,在混合態即時和非即時任務都可以執行,其中即時態可以用於對於即時
性要求比較嚴格的情況中。
在一般的概念中,為了提高 Linux 系統的即時特性,必須提高系統所支援的
時序精度,但如果僅僅簡單地提高時序頻率,會引起系統負載的增加,從而嚴重
地降低了系統的性能。為了解決上述的問題,Kurt-Linux 採用了提高 Linux 系統

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
中的時序精度的方法,亦即將時序晶片設置為單次觸發狀態,每次都是根據即時
任務的需要對時序晶片設置一個合理的定時時間。因為在電腦中,一個精確的定
時的真正含義是在一個比較精確的時間發生中斷,而並不是一定需要系統時序頻
率達到此精度。
對於即時任務的調度,Kurt-Linux 採用基於時間(TD)的靜態即時調度演
算法,即時任務在設計階段就需要明確地說明它們即時事件要發生的時間,這
種調度演算法相較於有些迴圈執行的任務能夠取得較好的調度效果,不過 KurtLinux 的主要缺點則是需要頻繁地對時序晶片進行程式撰寫。

5. RED-Linux
RED-Linux 是加州大學 Irvine 分校開發的即時 Linux 系統,是將即時調度
和 Linux 整合在同一個作業系統內核中,支援 Time-Driven、Priority-Dirven 及
Share-Driven 三種類型的調度演算法。
為了提高系統的調度細緻度,RED-Linux 從 RT-Linux 處參考了軟體類比中
斷管理器的機制,並且提高了時序中斷頻率。當硬體中斷來臨時,RED-Linux 的
中斷類比程式僅僅是簡單地將中斷放到一個佇列中進行排隊,並不執行真正的中
斷處理程式,從而減少了中斷所引起的延遲。另外,為了解決 Linux 任務在內核
態不能被取代的問題,RED-Linux 在 Linux 內核的很多函數中插入了取代碼(原
語),使得任務在內核態時,也可以在一定程度上被優先使用,經由此種方法而
提高了內核的即時特性。
RED-Linux 的設計目標就是提供一個可以支援各種調度演算法的一般用框
架,給予每個任務增加以下幾項屬性,並且做為任務調度的依據
(1) Priority:作業的優先順序;
() Start-Time:作業的開始時間;
() Finish-Time:作業的結束時間;
() Budget:作業在執行期間所要使用的資源量。
經由調整以上所列之屬性的取值,以及按照優先順序使用這些屬性值,就
可以完成所有的調度演算法。而 RED-Linux 的調度程式由兩部分組成,其中
Schedule Allocator 是將初始化送到 job 的屬性值中,而 Schedule Dispatcher 則是

10
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.3 Linux 中的 C 語音和組合語言
根據 job 的屬性值選擇一個 job 進行執行。

1.3 Linux 中的 C 語音和組合語言
1. Linux 中的 C 語言
在 Linux 內核中使用的 C 語言與通常的有所不同,它的編譯器為 gcc,是一
個 C/C++ 編譯器,所以 Linux 在 C 中使用了很多 C++ 的技術。例如在一些函數
前面使用了修飾字 inline,使被修飾的函數成為了內聯函數。還有在定義一個結
構類型的對象時,不像普通的 C 語言只使用結構名,而是在結構名前面還要有
關鍵字 struct,例如
struct student{
......
};

在普通的 C 語言中,使用以下的方式定義一個結構的變數
student S;

而在 gcc 的 C 語言中,定義則改為
struct student S;

總之,在讀 Linux 代碼時會碰到一些與普通的 C 語言不同的語法格式,此
時最好查一查 Linux 手冊。

2. Linux 中的組合語言
我們通常見到或使用的是 Intel 格式的組合語言,而 gcc 採用的是 AT&T 的
彙編格式語言。
(1) 基本語法:以上的兩種組合語言,在基本語法上有以下幾個不同。
(a) 暫存器命名原則

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 11
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
在 AT&T 彙編指令中,在暫存器的名稱前面要帶有首碼 %,例如:%eax。
(b) 運算元的順序
在 AT&T 彙編資料傳輸指令中,資料的傳遞方向與 Intel 指令的方向相反,
例如 Intel 指令 mov ebx,eax,在 AT&T 中為 movl %eax,%ebx。
(c) 常數的格式
在 AT&T 指令中的常數要帶有首碼 $,例如:movl $_value,%ebx。而在 Intel
中則為:mov eax,_value,兩條指令的功能均為把 _value 放入 eax 暫存器。
(d) 運算元長度標識
在 AT&T 的資料傳輸指令符號中帶有表明資料長度的尾碼,l 表示長整型
( 位元)資料,w 表示字(1 位元),b 表示位元組( 位元),例如:
movw %ax,%bx。
(e) 暫存器間接位址
在使用暫存器間接定址方式時,在 AT&T 指令中使用(),而 Intel 彙編使
用 [],例如(%eax)。
(f) 變數位址
在使用變數位址方式時,AT&T 與 Intel 的區別如下所示
AT&T: _variable(%eax)    Intel: [eax + _variable]
AT&T: _array(,%eax,4)    Intel: [eax*4 + _array]
AT&T: _array(%ebx,%eax,8) Intel: [ebx + eax*8 + _array]

(2) 嵌入 C 代碼中的行內彙編
在行內彙編方面比較簡單,一般的格式是 asm("statements"),例如:
asm("nop"); asm("cli")。

asm 和 __asm__ 是完全一樣的,如果有多行彙編,則每一行都要加上 "\n\t"。
例如:

12
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.4 Linux 中的鏈表
asm("pushl %eax\n\t"
"movl $0,%eax\n\t"
"popl %eax");

實際上 gcc 在處理彙編時,要把 asm(...) 的內容「列印」到彙編檔中,所以
格式控制字元是必要的,例如:
asm("movl %eax,%ebx");
asm("xorl %ebx,%edx");
asm("movl $0,_booga);

在以上的例子中,由於在行內彙編中改變了 edx 和 ebx 的值,但是由於 gcc
的特殊的處理方法,亦即先形成彙編檔,再交給 GAS 彙編,所以 GAS 並不知道
已經改變了 edx 和 ebx 的值,如果程式的上下文需要 edx 或 ebx 做暫存,這樣就
會引起嚴重的後果。為了解決這個問題,就要用到擴展的行內彙編語法。
(3) 嵌入 C 代碼中的擴展行內彙編
擴展的行內彙編類似於 Watcom,基本的格式是
asm ("statements": output_regs: input_regs: clobbered_regs);

clobbered_regs 指的是被改變的暫存器。

1.4 Linux 中的鏈表
鏈表是一種常用的組織有序數據的資料結構,是經由指標將一系列資料節
點連接成一條資料鏈。相對於陣列,鏈表具有更好的動態性,建立鏈表時無需預
先知道資料總量,可以隨機分配空間,也可以高效率地在鏈表中的任意位置即時
插入或刪除資料,鏈表主要的動作時間是取決於執行的順序性和組織鏈的空間容
量。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 13
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

1. Linux 鏈表的設計概念
通常在定義一個雙向鏈表時,鏈表的一個節點是按照以下方式所定義
struct student_struct {
student_struct *next, *prev; // 分別指向下一個和上一個節點的指標
char name[NAME_SIZE];  // 學生名稱
int age;

// 年齡

int sex;

// 性別

......

};
但是這種方式會因為程式中的各個結構不同,而必須為對每一種資料類型
都定義各自的鏈表結構,就顯得很不方便。那能不能把定義鏈表的方法統一起來
呢? Linux 設計者看到,雙向鏈表有個共同的特點,它們都有兩個指標域,分別
指向鏈表的前一個節點和後一個節點。那為什麼不可以把這兩個指標定義成一個
標準的結構,再定義某個鏈表的節點時,把它做為這個節點的一個域不就可以了
嗎?其實,不但雙向鏈表這樣,單向鏈表也是如此,於是,Linux 在 linux/list.h
中定義了一個具有兩個指標,稱為 list_head 的結構(見圖 1-):
struct list_head
{
struct list_head *next, *prev;
};

struct head_list
prev next

圖 1-3 兩個指標的 Struct list_head 結構

可以看到,結構中只有兩個為了將資料結構串成雙向鏈表的 next 和 prev,
有了 list_head 結構,建立一個資料結構鏈表的簡單方法就是在該結構中包含
14
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.4 Linux 中的鏈表
list_head 結構。例如有描述學生結構的 student_struct:
struct student_struct{
char name[NAME_SIZE];
int age;
int sex;
......
};

如果需要把這個資料結構連接成鏈表,在這個結構中添加上一個 list_head
結構的 list 域,亦即
struct student_struct{
char name[NAME_SIZE];
int age;
int sex;
struct list_head list;

// 有兩個指標的結構 list_head 的實例

......
};

這樣,就使得 student_struct 中有了兩個指示,student_struct 的結構如圖 1-
所示。
struct student_struct
name[]
age
sex

list

prev next

圖 1-4 student_struct 的結構

可以得知除了兩個指示之外的所有成員被承載在了結構 head_list 之上,圖

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 15
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
1- 是用 head_list 所完成的一個雙向鏈表,當然也可以完成單向和雙向迴圈兩種
鏈表。
struct student_struct

struct student_struct

struct student_struct

name[]

name[]

name[]

age

age

age

sex

sex

sex

prev next

prev next

prev next

圖 1-5 student_struct 的雙向鏈表

2. 鏈表頭的創造及鏈表節點的插入
如果有一個 student_struct 結構如下:
struct student_struct{
char name[NAME_SIZE];
int age;
int sex;
struct list_head list;

// 有兩個指標的結構 list_head 的實例

......
};

如果希望創造一個 student_struct 類型,名稱為 student_list 的鏈表,做法為
(1) 使用 Linux 定義的巨集 LIST_HEAD( ) 創造鏈表頭。
為了使用戶可以在系統初始化時創造一個鏈表頭,Linux 在檔 linux/list.h 中
提供巨集 LIST_HEAD( ),定義為
#define LIST_HEAD_INIT(name) {&(name), &(name)}
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)

16
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.4 Linux 中的鏈表
當用 LIST_HEAD(student_list) 聲明一個名為 student_list 的鏈表頭時,它的
next 及 prev 指標都將被初始化而指向本身,所創造的鏈表頭如圖 1- 所示。
struct list_head student_list
prev next

圖 1-6 創建的鏈表頭

除了可以使用 LIST_HEAD( ) 巨集在初始化時創造一個鏈表頭以外,Linux
還提供了另一個可以在執行時創造鏈表頭的巨集 INIT_LIST_HEAD( ),在文件
linux/list.h 巨集的定義為
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

(2) 插入節點
創造了鏈表頭之後,就可以在需要時向鏈表中插入節點了,頭部插入一個節
點的函數為
static inline void list_add(
  struct list_head *new, // 待插入節點
  struct list_head *head // 鏈表
  )
{
__list_add(new, head, head->next);
}

其中 __list_add( ) 的定義為

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 17
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
static inline void __list_add(
  struct list_head *new,

// 待插入節點

  struct list_head *prev,

// 鏈表頭節點指示

  struct list_head *next

// 鏈表頭節點的 next

  )
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}

這 個 函 數 的 作 用 是 將 一 個 新 節 點 new 插 入 鏈 表 的 尾 部, 假 設 有 一 個 新
student_struct 結構變數 student_node 需要添加到 student_list 鏈表頭,則操作為
list_add(&student_node.list, &student_list);

可以看出,student_list 鏈表中記錄的並不是 student_node 的位址,而是其
中的 list 元素的位址。也就是說,鏈表鏈接的各個節點都是 student_node 的成員
list,那麼如何經由鏈表執行到 student_node 呢?以下會有詳細的介紹。此外,
也可以在鏈表的尾部插入一個節點,函數的原型為
static inline void list_add_tail (struct list_head *new, struct list_head *head);

3.鏈表節點的訪問
(1) 鏈表節點宿主結構的訪問
前面談到鏈表中的節點都是 list,但是 list 又是使用戶結構的成員,所以根
據 list 在結構中的位置,經過適當的運算,可以經由 list 執行以 list 為成員的用
戶結構,亦即其宿主結構,Linux 為此提供了一個 list_entry( ) 巨集:

18
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.4 Linux 中的鏈表
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)

在文件 linux/kernel.h 中定義的 container_of( ) 為:
#define container_of(ptr, type, member) ({

\

const typeof( ((type *)0)->member ) *__mptr = (ptr);

\

(type *)( (char *)__mptr - offsetof(type,member) );})

其中 ptr 是指向用戶資料結構中 list_head 成員的指標,也就是它在鏈表中的
地址值,type 是用戶資料結構的類型,member 則是用戶資料結構中 list_head 成
員的變數名,例如,我們要執行 student_list 鏈表中首個 student_struct 變數,則
使用以下的方式呼叫
list_entry(student_list->next, struct student_struct, list);

此處「list」就是 struct student_struct 結構中定義的用於鏈表操作的節點成員
變數名。
list_entry 的使用相當簡單,相比之下,它的完成則有一些難懂,此處使用的
是一個利用編譯器技術的小技巧,亦即先求得結構成員在與結構中的偏移量,然
後根據成員變數的位址反過來得出屬主結構變數的位址。
container_of() 和 offsetof() 並不僅僅用於鏈表操作,最有趣的地方是 ((type
*)0)->member,它將 0 位址強制「轉換」為 type 結構的指標,再執行到 type 結
構中的 member。在 container_of 巨集中,是用來提供參數給 typeof()(typeof()
是 gcc 的擴展,和 sizeof() 類似),以獲得 member 的資料類型;在 offsetof() 中,
member 的位址實際上就是 type 資料結構中 member 相對於結構變數的偏移量。
對於給定一個結構,offsetof(type,member) 是一個常數,list_entry() 就是利用這個
不變的偏移量,求得鏈表資料項目的變數位址,offsetof() 的示意圖如圖 1- 所示。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 19
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
student_node
(struct student_struct *)0

name[]
age

offsetof(struct student_struct.list)
((struct student_struct *)0)->list

sex
prev next

圖 1-7 offsetof( ) 的示意圖

(2) 鏈表的遍歷(traversable)
可以使用巨集 list_for_each( ) 做鏈表的遍歷,巨集有兩個參數,第一個參
數用來指向當前項,第二個參數為需要遍歷的鏈表指示,在每次遍歷時,第一個
參數隨著遍歷在鏈表中不斷的移動,直到所有節點都被執行。
在文件 include/linux/list.h 中,巨集 list_for_each() 的定義為
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)

實際上是一個 for 迴圈,利用傳入的 pos 做為迴圈變數,從表頭 head 開始,
逐項向後(next 方向)移動 pos,直至又回到 head(prefetch() 可以不考慮,用於
預取以提高遍歷速度)。
在 使 用 巨 集 list_for_each( ) 遍 歷 一 個 鏈 表 時, 首 先 要 定 義 一 個 (struct
list_head *) 指標變數 i,然後呼叫 list_for_each(i,&student_list) 進行遍歷。
list_for_each(i, & student_list) {
struct student_struct *ops = (struct student_struct *)i;
......
}
......

在大多數情況下,遍歷鏈表的時候都需要獲得鏈表節點資料項目,也就是說

20
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.4 Linux 中的鏈表
list_for_each() 和 list_entry() 總是同時使用,因此 Linux 給了一個 list_for_each_
entry() 巨集
#define list_for_each_entry(pos, head, member)

......

與 list_for_each() 不 同,pos 是 資 料 項 目 結 構 指 標 類 型, 而 不 是 (struct
list_head *)。

4. 鏈表的其餘操作
(1) 刪除一個節點
以下的函數用來在鏈表中刪除一個節點:
static inline void list_del (struct list_head *entry);

此處所說的刪除一個節點,實質上是把該節點從鏈表中移出,亦即釋放 entry 所佔用的記憶體。
以下的函數在刪除一個節點時,可以對該結構再一次進行初始化。
static inline void list_del_init (struct list_head *entry);

(2) 轉移節點
以下的函數從一個鏈表移出一個節點 list,並且將其加入另一個鏈表的節點
head 之後。
static inline void list_move (struct list_head *list, struct list_head *head);

以下的函數從一個鏈表移出一個節點 list,並且將其加入另一個鏈表的節點
head 之前。
static inline void list_move_tail (struct list_head *list, struct list_head *head);

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 21
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
(3) 鏈表檢測
以下的函數可以對一個鏈表進行測試,當鏈表為空時,函數返回非 0 值,否
則返回 0。
static inline int list_empty (struct list_head *head);

(4) 合併鏈表
以下的函數可以將兩個鏈表進行合併,將鏈表 list 插入到鏈表 head 之後。
static inline void list_splice (struct list_head *list, struct list_head *head);

以下的函數可以將兩個鏈表進行合併,將鏈表 list 插入到鏈表 head 之後,
並重新初始化。
static inline void list_splice_init (struct list_head *list, struct list_head *head);

5. 哈希鏈表(hlist)
Linux 鏈表設計者認為雙頭(next 及 prev)的雙鏈表對於 HASH 表來說「過
於浪費」,因而另行設計了一套用於 HASH 表應用的 hlist 資料結構,它是屬於單
指標表頭雙迴圈鏈表,普通雙向迴圈鏈表與哈希鏈表的比較如圖 1- 所示。
head

(a) 普通雙向循環鏈表
head

(b) 哈希鏈表

圖 1-8 普通雙向迴圈鏈表與哈希鏈表
22
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
可以看出,hlist 的表頭僅有一個指向首節點的指標,而沒有指向尾節點的指
標,這樣在可能是巨量的 HASH 表中儲存的表頭就能減少一半的空間消耗,而
在檔 include/linux/list.h 中定義的 hlist 鏈表結構為
struct hlist_head {
struct hlist_node *first;
};

struct hlist_node {
struct hlist_node *next, **pprev;
};

因為表頭和節點的資料結構不同,插入操作如果發生在表頭和首節點之間,
以往的方法就行不通了。表頭的 first 指標必須修改指向新插入的節點,卻不能使
用類似 list_add() 這樣統一的描述。因此,hlist 節點的 prev 不再是指向前一個節
點的指標,而是指向前一個節點(可能是表頭)中的 next(對於表頭則是 first)
指標(struct list_head **pprev),從而在表頭插入的操作可以經由一致的 *(node>pprev) 執行和修改前驅節點的 next(或 first)指標。

1.5 Linux 模組
Linux 的模組也叫做內核模組,既可以靜態載入也可以動態載入並且工作在
內核態,換句話說,模組可以根據需要隨時添加到作業系統的內核上,為系統提
供額外的功能。

1. 模組的基本框架代碼
從代碼的特徵上來看,模組就是可以完成一個獨立功能的一組函數的集合,
但是以特殊的方式來編譯,從而使之可以在被需要時隨時安裝,在不需要時隨時
卸載。準確地說,模組就是一個已經編譯但未經連接的可執行檔。
制定一個 Linux 內核模組必須要遵守一些規則,以下為一個最簡單的模組。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 23
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
首先在 Linux 中的某個目錄中創建造一個 C 語言原始檔案,名稱為「Hello.
c」,內容為
#include<linux/module.h>
MODULE_LICENSE("GPL");

其中的 MODULE_LICENSE("GPL") 是一個巨集,GNU 組織規定,當人們
編寫涉及內核的代碼時,應該使用此一巨集表明本檔中的原始碼是開放的,以便
其他使用這個代碼的人,能夠根據原始碼追蹤錯誤,如果不使用的話,當用戶
向內核鏈結模組時,系統會發出警告。接著再編寫一個編譯創造模組的 Makefile
檔,並命名為「Makefile」,內容為
#Makefile******************************************
obj-m := hello.o
KERNELBUILD :=/lib/modules/$(shell uname -r)/build

目錄中的檔如圖 1- 所示。

圖 1-9 目錄中的 C 檔和 Makefile
24
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
使用 Linux 的命令行終端,在進入檔所在目錄後鍵入 make 指令
$ make

隨後,系統便會將 C 檔編譯成副檔名為 ko 的模組檔,如圖 1-10 所示。

模組檔

圖 1-10 目錄中的模組檔

使用 insmod 命令可將模組鏈結至內核。對於本例來說,指令為
$ sudo insmod hello.ko

因為 insmod 為特權用戶指令,所以在其前面使用了 sudo,最後面的 hello.
ko 便是模組檔的名稱。
模組載入完畢之後,可以使用 cat 命令到一個專門為提供系統資訊的 proc 檔
系統中的 modules 檔中觀察已載入模組的情況,查詢後的部分結果如圖 1-11 所
示。

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 25
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

hello

圖 1-11 連接到內核中的模組

如果需要卸載模組,則使用 rmmod 命令,對於本例來說,卸載 hello 模組的
命令為
$ sudo rmmod hello

當然,像上面的這種沒有任何功能代碼的模組不具任何意義,為了向內核提
供某種實際功能,模組檔中總會有一些函數,也常常會有一些初始化工作和模組
卸載時的收尾工作。顯然,這兩個函數一定是由內核所呼叫的函數,一個在模組
載入時呼叫,另一個在模組卸載時呼叫,既然它們是由內核呼叫的兩個函數,那
麼命名方式就要遵守一定的規則。為此,Linux 提供了兩個巨集
module_init() 和 module_exit()

用 戶 編 寫 的 用 於 模 組 載 入 時 由 內 核 的 呼 叫 的 初 始 化 函 數, 名 稱 要 寫 在
module_init 巨集的括弧中,卸載函數的名稱必須要寫在 module_exit 巨集的括弧
中。因為只有這樣,內核才能知道哪個函數為初始化函數,哪個為卸載函數,哪

26
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
個應該在載入時呼叫,哪個應該在卸載時呼叫。除此之外,在函數名前面分別要
以 __init 和 __exit 進行註明,以表示與其他普通函數的區別。
【例題 1-1】 試編寫一個模組,該模組含有一個初始化函數 hello_init() 和一
個卸載函數 hello_exit(),為了可以瞭解這兩個函數的呼叫情況,在初始化函數
hello_init() 中列印字串「Hello World!」;在卸載函數中列印字串「Bye World!」
(提示:內核中的列印語句為 printk 而不是 printf)。
解:(1) 模組代碼:依照題意可寫出模組的代碼為
#include<linux/module.h>
#include<linux/kernel.h>
MODULE_LICENSE("GPL");
static int __init hello_init(void)
{
printk(KERN_ALERT "Hello World!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Bye World!\n");
}
module_init(hello_init);
module_exit(hello_exit);

() Makefile 檔:如果將模組檔命名為 Hello_mod.c,則對應的 Makefile 檔
為:
#Makefile******************************************
obj-m := Hello_mod.o
KERNELBUILD :=/lib/modules/$(shell uname -r)/build

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 27
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
() 觀察模組載入和卸載的情況:使用 make 命令生成了模組檔 Hello_mod.
ko 後,使用 insmod 命令載入模組,然後使用 dmesg 命令觀察 printk 的
列印結果,命令為
$ dmesg |tail -10

其中,|tail -10 是讓系統只顯示 printk 列印結果的尾部 10 條。命令執行後,
結果如圖 1-1 所示,可見模組在載入時,初始化函數已經被呼叫了。

呼叫了初始化函數

圖 1-12 模組載入時內核呼叫了初始化函數

當使用命令 rmmod 卸載模組時,使用命令 dmesg 觀察的結果如圖 1-1 所
示。

內核呼叫了卸載函數

圖 1-13 模組卸載時內核呼叫了卸載函數

28
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
如果用戶對某一個模組感興趣,可以經由 modinfo 命令查看模組資訊,對於
本例來說,命令為
$ sudo modinfo Hello_mod.ko

顯示結果為
filename:
license:
srcversion:

Hello_mod.ko
GPL
5AE1900EE1F1617C327CFC7

depends:
vermagic:

2.6.35-25-generic SMP mod_unload modversions 686

2. 模組的輔助框架代碼
為了使模組的框架更加完善,Linux 還提供了一些其他的巨集以充實框架的
功能。
(1) 模組參數的傳遞
Linux 允許在模組中定義總體變數,並做為模組參數,該參數可以在載入
模組時在 insmod 命令行中給定數值。如果希望模組中的某個總體變數為模組參
數,則需要使用巨集 module_param() 進行聲明,格式為
module_param(name, type, perm);

其中 name 為參數的變數名;type 為參數類型(byte, short, ushort, int, uint,
long, ulong, charp, bool 及 invbool 之一);perm 指定了在 sysfs 中的執行許可權,
通常為 S_IRUGO。
注意在使用上述巨集時,需要包含頭檔 <linux/moduleparam.h>。關於模組的
參數還有更多的細節,請參閱其他資料。
【例題 1-2】 下面是一個以字串變數為模組參數的實例,請閱讀代碼並觀察
參數的使用方法,程式碼為

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 29
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
#include<linux/module.h>
#include<linux/kernel.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
static const char*str="default";
module_param(str, charp, S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_ALERT "%s\n",str);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Bye World!\n");
}
module_init(hello_init);
module_exit(hello_exit);

解:觀察參數的使用,使用已下的命令載入模組。
$ sudo insmod Hello_mod.ko str=Hello!

載入模組後使用 dmesg 命令,可以看到模組列印結果如圖 1-1 所示,亦即
載入函數按照載入命令中的參數值進行了列印,這就是說,命令行中的參數確實
傳遞到了模組之中。

圖 1-14 模組載入並傳遞參數後,初始化函數的執行結果

30
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
(2) 模組說明資訊的添加
Linux 還提供了一些用以說明模組其他資訊的巨集,常用的有:說明模組作
者的巨集 MODULE_AUTHOR(),簡短描述模組功能的巨集 MODULE_DESCRIP
-TION(),說明模組版本的巨集 MODULE_VERSION 等等。
對例題 1- 的程式加上了作者名和模組描述資訊的代碼為
#include<linux/module.h>
#include<linux/kernel.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
static const char*str="default";
module_param(str, charp, S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_ALERT "%s\n",str);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Bye World!\n");
}
module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("renzhe");

// 作者名

MODULE_DESCRIPTION("this is a module");   // 模組功能描述

使用以下的指令,可以看到內核中模組的資訊,對於本題之模組結果如圖
1-1 所示。
$ modinfo Hello_mod.ko

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 31
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識

圖 1-15 模組的相關訊息

3. Linux 模組的實現機制及其管理
從前面的內容中可以知道,從代碼的特徵上來看,模組是可以完成一項獨
立功能的一組函數的集合。從使用特徵上來看,在被需要時可以隨時被安裝,而
在不需要時又可以隨時被卸載。從用戶的角度來看,模組是內核的一個外掛的配
件,需要時可將其掛接到內核上,以完成用戶所要求的任務,不需要時即可將其
刪除。給用戶提供了擴充內核功能的手段。從內核的角度來看,模組由在執行時
可以連接並刪除的、包含了至少  個函數的代碼段。這個代碼段一旦被連接到內
核,就可以是內核的一部分,所以也被稱為做內核模組,總之模組是一個為內核
或其他內核模組提供使用功能的代碼組。
其實,從更宏觀的角度來看,內核也是一個模組,只不過大一些罷了,所
以說,模組就是一個已經編譯但未連接的可執行檔。聯想到編譯鏈結,那麼馬上
就能知道,模組一定是一種向外提供了外部可以使用的「可移出」符號的可執行
檔,而這種可執行檔的鏈結工作則由作業系統動態的加以完成。
當然,作為內核的外掛,模組可以引用內核及其他模組的可移出符號,而
內核不能引用模組的可移出符號,也就是說,內核與模組之間的互連是一種「單
向」互連,模組與內核之間連接示意圖如圖 1-1 所示。

32
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
export

extern

export
c

a

opf
jiffies

內核

jiffies

……

可安裝
的模組

adg

圖 1-16 模組與內核之間連接圖

有了這些可移出符號,在實際模組的設計中,在需要內核的某個變數或函
數時,在聲明了這是引用的一個外部符號之後,就可以直接在該變數或函數的位
置引用。編譯器在對模組進行編譯時,就會在這個位置留下一個空位,以便在模
組載入時,再根據內核提供的移出符號表,具體確定這些變數和函數,更確切地
說,是確定這些變數或函數的位址。
在 . 版的內核下,使用以下命令可以看到內核可移出符號表
$ cat /proc/kallsyms | more

電腦上 Linux 的可移出符號為
c010edf0 t intel_pmu_disable_all
c010eed0 t x86_pmu_read
c010eee0 t x86_pmu_start_txn
c010ef10 t x86_pmu_cancel_txn
c010ef40 t backtrace_warning_symbol
c010ef50 t backtrace_warning
c010ef60 t backtrace_stack
c010ef70 t backtrace_address
c010efd0 T perf_arch_fetch_caller_regs
c010f040 T perf_instruction_pointer

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 33
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
c010f070 T perf_misc_flags
c010f0d0 t p4_pmu_disable_all
c010f140 t x86_pmu_enable_all
c010f1b0 t x86_pmu_disable_all
--More--

在內核符號表中,左邊一列是符號位址,右邊一列是函數和變數。當一個模
組載入後,模組導出的符號也會成為內核符號表的一部分,在模組的原始碼中,
是使用以下巨集定義模組的可導出符號。
EXPORT_SYMBOL( 符號名 );
EXPORT_SYMBOL_GPL( 符號名 );

其中,巨集 EXPORT_SYMBOL_GPL() 只限於使用在 GPL 許可權的模組。
另外,定義了可移出符號的模組示例有
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
int add_int(int a, int b)
{
return (a + b);
}
int sub_int(int a, int b)
{
return (a - b);
}

EXPORT_SYMBOL(add_int);
EXPORT_SYMBOL(sub_int);

34
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
編譯後用 insmod 載入到內核中,然後執行以下的命令
$ cat /proc/kallsyms |grep add_int
f9d70034 r __ksymtab_add_int

[modadd]

f9d7004c r __kstrtab_add_int

[modadd]

f9d70040 r __kcrctab_add_int

[modadd]

6ce40a9f a __crc_add_int

[modadd]

f9d70000 T add_int

[modadd]

$cat /proc/kallsyms |grep sub_int
f9d7002c r __ksymtab_sub_int

[modadd]

f9d70044 r __kstrtab_sub_int

[modadd]

f9d7003c r __kcrctab_sub_int

[modadd]

bb5e85f9 a __crc_sub_int

[modadd]

f9d70004 T sub_int

[modadd]

4. 模組的內核描述
模組在內核中使用 strcut module 結構描述一個模組,內核中所有模組的 strcut module 實例組成了一個鏈表,該鏈表就是模組在內核中的註冊表,而內核就
是依據這個註冊表管理所有的模組。
struct module 定義於 include/linux/module.h 檔中,主要內容為
struct module
{
enum module_state state;

// 模組的狀態

......
struct list_head list;

// 模組鏈表

char name[MODULE_NAME_LEN];

// 模組名稱

......
const struct kernel_symbol *syms;;

// 模組的內核符號

......
const struct kernel_symbol *gpl_syms;

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 35
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
......
int (*init)(void);

// 模組初始化函數指標

......
void (*exit)(void);

// 模組退出時的卸載函數指標

......
};

struct kernel_symbol
{
unsigned long value;

// 符號值(位址)

const char *name;

// 符號名稱

};

在檔 kernel/linux/module.c 中定義的模組鏈表為
static LIST_HEAD(modules);

每當內核需要使用到這個模組提供的功能時,就會到鏈表 modules 中尋找這
個模組,並且呼叫模組使用 export 修飾的功能函數。
結構 struct module 中,state 為模組當前的狀態。它是一個列舉型變數,可
取的值為:MODULE_STATE_LIVE、MODULE_STATE_COMING 及 MODULE_
STATE_ GOING,分別表示模組當前正常使用中,模組當前正在被載入及模組當
前正在被卸載。
在模組向內核載入時,insmod 呼叫了內核的模組載入函數,在完成模組的
部分創造工作後,將模組狀態置為 MODULE_STATE_COMING。接著內核將
呼叫內核模組初始化函數,並且在完成模組的全部初始化工作後,把模組狀態
置為 MODULE_ STATE_LIVE。如果使用 rmmod 命令卸載模組時,內核會呼叫
delete_module,並把模組的狀態置為 MODULE_STATE_GOING。
list 表明所有的內核模組都被維護在一個全局鏈表中,鏈表頭就是前面鏈表
定義中的 struct module *modules,凡是新創造的模組,都會被加入到這個鏈表的
36
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
頭部,內核可以經由 modules->next 引用該模組,其中 name 為模組的名字,通
常模組名就是模組檔案名。
為了方便對模組的執行,Linux 定義了 THIS_MODULE 巨集
#define THIS_MODULE (&__this_module)

其中,__this_module 是一個 struct module 變數,代表了當前模組。
【例題 1-3】 編寫一個模組,借用模組的初始化函數執行內核模組中的相應
資訊。
解:根據前面的介紹,編寫如下的模組,並且在模組的初始化函數中執行當前模
組的狀態資訊,模組名以及當前模組之前模組的名稱。在模組檔 Hello_mod.
c 中,模組碼為
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
unsigned int cpu = get_cpu();
struct module *mod;
printk(KERN_ALERT "this module:
%p==%p\n", &__this_module, THIS_MODULE );
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state );
printk(KERN_ALERT "module name: %s\n", THIS_MODULE->name );
list_for_each_entry(mod, *(&THIS_MODULE->list.prev), list )
printk(KERN_ALERT "module name: %s\n", mod->name );
return 0;
}

static void hello_exit(void)
{

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 37
或來電(02)2705-5066

Chapter 1 Linux 的基礎知識
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state );
}

module_init(hello_init);
module_exit(hello_exit);

使用 insmod 命令載入模組後,在使用 dmesg 命令之下,電腦上所得到的結
果為
[ 33.764420] EXT4-fs (sda1): re-mounted. Opts: errors=remount-ro,commit=0
[ 349.247276] this module: e08a5160==e08a5160
[ 349.247621] module state: 1
[ 349.247630] module name: Hello_mod
[ 349.247642] module name: Hello_mod
[ 349.247651] module name: acpiphp
[ 349.247653] module name: binfmt_misc
[ 349.247655] module name: snd_ens1371
[ 349.247656] module name: gameport
[ 349.247658] module name: snd_ac97_codec
[ 349.247659] module name: ac97_bus
[ 349.247661] module name: snd_pcm
[ 349.247663] module name: snd_seq_midi
[ 349.247664] module name: snd_rawmidi
[ 349.247666] module name: snd_seq_midi_event
[ 349.247668] module name: snd_seq
[ 349.247669] module name: snd_timer
[ 349.247671] module name: snd_seq_device
[ 349.247672] module name: ppdev
[ 349.247674] module name: intel_agp
[ 349.247675] module name: snd
[ 349.247677] module name: vmware_balloon
[ 349.247678] module name: psmouse

38
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

1.5 Linux 模組
[ 349.247680] module name: serio_raw
[ 349.247682] module name: parport_pc
[ 349.247683] module name: soundcore
[ 349.247685] module name: snd_page_alloc
[ 349.247686] module name: shpchp
[ 349.247688] module name: lp
[ 349.247689] module name: agpgart
[ 349.247691] module name: i2c_piix4
[ 349.247692] module name: parport
[ 349.247694] module name: mptspi
[ 349.247695] module name: mptscsih
[ 349.247699] module name: pcnet32
[ 349.247701] module name: mptbase
[ 349.247702] module name: floppy
[ 349.247704] module name: scsi_transport_spi
[ 349.247705] module name: mii
[ 349.247707] module name: vmw_pvscsi

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 39
或來電(02)2705-5066

索 引

索 引
英文專有名詞索引
D
DMA區(ZONE_DMA) 69, 71
M
MTD(memory technology device,
記憶體技術設備) 7, 373, 374
N
NFS(Network File System) 5,
117, 204, 205
中文專有名詞索引
二劃
二進位工具(Binutils) 380
四畫
中間頁目錄(page middle decretory,
PMD) 56
中斷訊號(SIGINT) 142, 279
元資料(meta-data) 374
七畫
即時時序(Real Time Clock,
RTC) 164, 165, 167, 168
八畫
具名管道(named pipe) 245, 270,
278

九畫
頁目錄(page decretory, PGD) 
55, 56, 57, 65, 67, 93, 364
頁表(page table,PTE) 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 59,
67, 68, 69, 70, 71, 72, 73, 93, 139,
188, 196, 243, 249, 251, 362, 364
十畫
套接字(socket) 244, 245
記憶體「快閃(flash memory)」 
3, 6, 21, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56,
58, 59, 60, 61, 62, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 83, 85, 86, 88, 92,
93, 94, 95, 97, 101, 89, 101, 92,
93, 97, 103, 107, 108, 110, 113,
115, 116, 117, 136, 157, 161, 176,
180, 184, 185, 187, 188, 193, 196,
206, 207, 210, 213, 215, 217, 218,
219, 220, 222, 223, 224, 225, 227,
236, 238, 239, 241, 242, 244, 245,
246, 247, 248, 249, 250, 251, 252,
253, 254, 255, 256, 257, 258, 259,
260, 265, 270, 271, 272, 276, 280,
301, 316, 317, 318, 319, 322, 323,
326, 327, 329, 330, 343, 348, 349,
350, 359, 360, 361, 362, 363, 364,
365, 370, 372, 373, 374, 376, 377,
378, 379, 380
訊號(signal) 65, 95, 96, 97, 99,

本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw 381
或來電(02)2705-5066

索 引
136, 139, 142, 143, 153, 241, 244,
245, 246, 247, 248, 251, 260, 263,
264, 266, 278, 279, 280, 281, 282,
283, 284, 285, 286, 287, 288, 289,
290, 291, 292, 294, 295, 296, 297,
298, 299, 300, 301, 302, 303, 304,
305, 306, 307, 308, 309, 311, 312,
313, 318, 323, 330, 337
訊號量集(semaphore) 244, 245,
246, 247, 248, 291, 292, 297, 298,
299, 300, 301, 302, 303, 304, 305,
306, 307, 308, 311, 312, 313
高端記憶體區(ZONE_HIGHMEM)
69, 73
匿名管道(pipe) 245, 270, 271,
274, 276, 278
十一畫
掛載(mount) 375
通用公共許可證(GPL, General
Public License) 2

(POS-IX) 244
十四畫
遠端程序呼叫(remote process call,
RPC) 205
十五畫
線上模擬器(In-Circuit Emulator,
ICE) 4
十七畫
檔控制塊(FCB) 190, 193, 206,
209
十八畫
臨界段(critical section) 293
臨界資源(critical resource) 293
十九畫
壞塊(bad block) 372

十二畫
普通區(ZONE_NORMOL) 69,
72
虛擬碟(RAMDISK) 7
超級塊(Superblock) 7, 193, 200,
201, 202, 213, 214, 215, 216, 217,
219, 220, 227, 229, 230, 239, 366,
368, 370
十三畫
微秒級(micro second) 9
電腦環境可攜性作業系統介面

382
本試閱檔為五南所有。如欲購買此書,請至五南網站 www.wunan.com.tw

或來電(02)2705-5066

嵌入式Linux系統實務入門/任哲, 樊生文編
著. --初版.--臺北市:五南, 2012.12
 面; 公分
ISBN 978-957-11-6942-2 (平裝)
1.作業系統
312.54

101025633

5DF8

嵌入式軟體Linux入門
Linux in Embedded System
作  者 ─ 任哲 樊生文
校  訂 ─ 溫坤禮
發 行 人 ─ 楊榮川
總 編 輯 ─ 王翠華
主  編 ─ 穆文娟
責任編輯 ─ 王者香
封面設計 ─ 簡愷立
出 版 者 ─ 五南圖書出版股份有限公司
地  址:106台 北 市 大 安 區 和 平 東 路 二 段 3 3 9 號 4 樓
電  話:(02)2705-5066  傳  真:(02)2706-6100
網  址:http://www.wunan.com.tw
電子郵件:wunan@wunan.com.tw
劃撥帳號:0 1 0 6 8 9 5 3
戶  名:五南圖書出版股份有限公司
台中市駐區辦公室/台中市中區中山路6號
電  話:(04)2223-0891  傳  真:(04)2223-3549
高雄市駐區辦公室/高雄市新興區中山一路290號
電  話:(07)2358-702   傳  真:(07)2350-236
法律顧問 元貞聯合法律事務所 張澤平律師
© 2011,北京航空航天大學出版社,版權所有
本書中文繁體字版由北京航空航天大學出版社獨家
授權出版發行

出版日期 2 0 1 3 年 2 月 初 版 一 刷
定  價 新 臺 幣 5 2 0 元

※版權所有.欲利用本書內容,必須徵求本公司同意※
本試閱檔為五南所有。如欲購買此書,請至五南網站

或來電(02)2705-5066

www.wunan.com.tw

Sign up to vote on this title
UsefulNot useful