|
-- 會員 / 註冊 --
|
|
|
|
嵌入式C語言自我修養——從芯片、編譯器到操作系統 ( 簡體 字) |
作者:王利濤 | 類別:1. -> 電腦組織與體系結構 -> 嵌入式系統 2. -> 程式設計 -> C -> C |
譯者: |
出版社:電子工業出版社 | 3dWoo書號: 54406 詢問書籍請說出此書號!【有庫存】 NT售價: 590 元 |
出版日:4/1/2021 |
頁數:568 |
光碟數:0 |
|
站長推薦: |
印刷:黑白印刷 | 語系: ( 簡體 版 ) |
|
加入購物車 │加到我的最愛 (請先登入會員) |
ISBN:9787121408564 |
作者序 | 譯者序 | 前言 | 內容簡介 | 目錄 | 序 |
(簡體書上所述之下載連結耗時費功, 恕不適用在台灣, 若讀者需要請自行嘗試, 恕不保證) |
作者序: |
譯者序: |
前言:C 語言是很多人學習編程的第一門語言。很多初學者在學習過程中,往往會產生各種各樣
的疑惑:C 語言黑屏白字,窗口界面看起來甚至還有點丑陋,現在學這個還有用嗎?能編寫一
個好玩的App 嗎?能寫爬蟲嗎?能搭建一個電商網站嗎?光靠C 語言能找到一份月薪過萬的工
作嗎?現在互聯網和人工智能這么火,大家都在學習Java、Python、Ruby……都2021 年了,C
語言是不是已經過時了?
C 語言已經過時了嗎
C 語言并沒有過時。自C 語言問世幾十年來,其實一直都是使用最廣泛的編程語言之一,
多年來一直低調地霸占著編程語言的“瑯琊榜”,目前還沒有看到衰退和被替代的跡象。只不
過在Android、移動互聯網火了之后,Java 暫時搶了風頭而已,把C 語言從編程語言排行榜上
擠到了第二的位置。滄海桑田,時過境遷,很多編程語言如過江之鯽,風云變幻,但C 語言依
然寶刀未老,在編程語言排行榜上從未跌出過前三,這也從側面說明了C 語言一直都是被廣泛
使用的編程語言。既然C 語言被廣泛使用,那么主要應用在哪些領域呢?可以這么說,基本上
在每個領域都可以看到C 語言的身影。
? 應用軟件:Linux/UNIX 環境下的工具、應用程序。
? 系統軟件:操作系統、編譯器、數據庫、圖形處理、虛擬機、多媒體庫等。
? 嵌入式開發:各種RTOS、BSP、固件、驅動、API 庫。
? 嵌入式、工業控制、物聯網、消費電子、科研領域、數值計算。
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? IV ?
? 實現其他編程/腳本語言:Lua、Python、Shell。
? 網站服務器底層、游戲、各種應用框架。
C 語言是一門高級語言。C 語言有高級語言的各種語法和特性,我們使用C 語言可以構建
大型的軟件工程。有人說,C 語言小打小鬧,上不了大臺面,編寫不了大型的項目,這個說法
其實也是站不住腳的:很多大型的GNU 開源項目,其實都是使用C 語言開發的,如Lua 腳本
語言、SQLite、Nginx、UNIX 等。現在市面上幾乎所有的操作系統都是使用C 語言開發的,如
Linux 內核、uC/OS、VxWorks、FreeRTOS。目前最新的Linux-5.x 內核代碼已多達2000 萬行,
3 萬多個源文件,這個項目應該不算小了吧!
C 語言也是一門低級語言。通過指針和位運算,我們可以修改內存和寄存器,從而直接控
制CPU 和硬件電路的運行。正是由于這種低級特性,很多操作系統內核、驅動都選擇使用C
語言進行開發。尤其在嵌入式開發領域,C 語言被廣泛使用,C 語言是嵌入式工程師必須熟練
掌握,甚至需要精通的一門編程語言。
C 語言到底要學到什么程度
學習C 語言到底要學到什么程度,才能達到面試的要求,才能勝任一份嵌入式開發的工作
呢?這是很多嵌入式初學者很關心的問題。
一般來講,不同的行業領域、不同的C 語言開發崗位、不同的學習目的,對C 語言的要求
也不一樣。如圖0-1 所示,如果你是在校學生,學習C 語言僅僅是為了應付期末考試、過計算
機二級考試、考證,那么你只要把C 語言的基本語法掌握好,基本上就可以輕松過關,稍微用
心點,說不定還能拿個優秀。如果你想做C 語言桌面軟件、網站服務器開發,那么你不僅要學
習C 語言的基本語法,還要對特定行業領域的專業知識、軟件工程、項目管理等有所涉獵。這
可不像過計算機二級考試那么簡單。計算機二級考試其實壓根就不是為程序員準備的,它是非
計算機專業學生的終極目標,而對于一個立志從事軟件開發的工程師來說,它僅僅是一個起點。
如果你想以后從事嵌入式開發、Linux 內核驅動開發等工作,那么對C 語言的要求就更高了:
你不僅要掌握C 語言的基本語法、項目管理、軟件工程,還要對硬件電路、CPU、操作系統、
編譯原理等底層機制有完整的了解,需要對C 語言進行進一步的強化學習和編程訓練。
前 言
? V ?
圖0-1 C 語言各種開發崗位和學習需求
使用C 語言編程就像寫小說一樣:為什么你掌握了3000 個常用的英文單詞、八大時態、
各種從句語法,還是寫不出《哈姆雷特》《冰與火之歌》呢?道理其實很簡單,單詞和語法只是
基礎中的基礎,只是工具而已。要想寫出優秀的小說,還需要對一門語言背后的社會背景、歷
史文化、思維邏輯、風土人情等有深入的理解和把握才行。
測一測你的C 語言水平
為了達到更好的學習效果,下面特意列出了一些問題,用來測評你真實的C 語言水平。
C 語言測試(1):基本概念考查
? 什么是標識符、關鍵字和預定義標識符?三者有何區別?
? 在C 程序編譯過程中,什么是語法檢查、語義檢查?兩者有何區別?
? 什么是表達式?什么是語句?什么是代碼塊?
? 什么是左值、右值、對象、副作用、未定義行為?
? 什么是結合性、左結合、右結合?
C 語言測試(2):一個sizeof (int)引發的思考
? sizeof 是函數,是關鍵字,還是預定義標識符?
? 在32 位和64 位的Windows 7 環境下運行,結果分別是多少?
? 在32 位和64 位的X86 CPU 平臺下運行,結果分別是多少?
? 在8 位、16 位、32 位的單片機環境下運行,結果分別是多少?
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? VI ?
? 在32 位ARM 和64 位ARM 下運行,結果分別是多少?
? 分別在VC++ 6.0、Turbo C、Keil、32 位/64 位GCC 編譯器下編譯、運行,結果一樣嗎?
? 使用32 位GCC 編譯器編譯生成32 位可執行文件,運行在64 位環境下,結果如何?
? 使用64 位GCC 編譯器編譯生成64 位可執行文件,運行在32 位環境下,結果如何?
C 語言測試(3):自增運算符
使用不同的編譯器編譯、運行下面的程序代碼,結果分別是多少?結果是否一定相同?為
什么?
#include <stdio.h>
int main(void)
{
int i = 1;
int j = 2;
printf("%dn", i++*i++);
printf("%dn", i+++j);
return 0;
}
C 語言測試(4):程序代碼分析
#include <stdio.h>
int main(void)
{
int i;
int a[0];
printf("hello world!n");
int j;
for(int k=0; k<10; k++);
return 0;
}
閱讀上面的程序代碼,然后進行如下操作,觀察運行結果并分析。
? 分別使用C-Free、GCC、VC++ 6.0、Visual Studio 編譯、運行代碼,運行結果一定相
同嗎?會出現什么問題?為什么?
? 在VC++ 6.0 環境下,新建console 工程,將上面的程序代碼分別保存為.cpp 和.c 文件
并編譯、運行,運行結果會如何?為什么?
前 言
? VII ?
C 語言測試(5):程序運行內存分析
在32 位Linux 下,編寫一個數據復制函數,在實際運行中會出現什么問題?
int *data_copy(int *p)
{
int buffer[8192*1024];
memcpy(buffer, p, 8192*1024);
return buffer;
}
C 語言測試(6):程序改錯題
在嵌入式ARM 裸機平臺上,實現一個MP3 播放器,要求實現如下功能:當不同的控制按
鍵被按下時,播放器可以播放、暫停、播放上一首歌曲、播放下一首歌曲。為了實現這些功能,
我們設計了一個按鍵中斷處理函數:當有按鍵被按下時,會產生一個中斷,我們在按鍵中斷處
理函數中讀取按鍵的值,并根據按鍵的值執行不同的操作。下面設計的按鍵中斷處理函數中有
很多不合理之處,請找出6 處以上。
int keyboard_isr(int irq_num)
{
char *buf =(char *)malloc(512);
int key_value = 0,
key_value = keyboard_scan();
if(key_value == 1)
{
mp3_decode(buf,"xx.mp3");
sleep(10);
mp3_play(buf); //播放
}
else if(key_value == 2)
mp3_pause(buf); //暫停
else if(key_value == 3)
mp3_next(buf); //播放下一首歌曲
else if(key_value == 4)
mp3_prev(buf); //播放上一首歌曲
else
{
printf("UND key !");
return -1;
}
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? VIII ?
return 0;
}
C 語言測試(7):Linux 內核代碼分析
在Linux 內核源碼中存在著各種各樣、稀奇古怪的C 語言語法,試分析下面代碼中宏定義、
零長度數組、位運算、結構體變量初始化的作用。
#define stamp(fmt, args...) pr_debug("%s:%i: " fmt "n", __func__, __LINE__, ## args)
#define pr_debug(fmt, ...) __pr(__pr_debug, fmt, ##__VA_ARGS__)
#define container_of(ptr, type, member) ({ const typeof(((type *)0)->member) * __mptr = (ptr); (type *)((char *)__mptr - offsetof(type, member)); })
#define likely(x) __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)
struct urb
{
struct kref kref;
struct usb_iso_packet_descriptor iso_frame_desc[0];
}
flags &= ~(URB_DIR_MASK | URB_DMA_MAP_SINGLE | URB_DMA_MAP_PAGE );
static struct usb_driver i2400mu_driver = {
.name = KBUILD_MODNAME,
.suspend = i2400mu_suspend,
.resume = i2400mu_resume,
.supports_autosuspend = 1,
};
C 語言測試(8):Linux 內核代碼賞析
在Linux 內核源碼中,我們經常可以看到下面的代碼風格,試分析它們的意義。
extern void usage(const char *err) NORETURN;
extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
static inline __attribute__((noinline)) int func();
static inline __attribute__((always_inline)) int func();
前 言
? IX ?
#define ftrace_vprintk(fmt, vargs) do { if (__builtin_constant_p(fmt)) { static const char *trace_printk_fmt __used __attribute__((section("__trace_printk_fmt"))) = __builtin_constant_p(fmt) ? fmt : NULL; __ftrace_vbprintk(_THIS_IP_, trace_printk_fmt, vargs); } else __ftrace_vprintk(_THIS_IP_, fmt, vargs); } while (0)
你要學習的,不僅僅是C 語言……
對于上面的幾個C 語言測試,如果你已經知道了答案,并且知道其要考查的是什么知識點,
恭喜你,你對C 語言及計算機體系結構的知識已經很熟悉了。如果回答得不是很好,偷偷用百
度也沒有搜到理想的答案,也不用氣餒,因為這次測試要考查的內容其實已經不僅僅是C 語言
的知識了,而是和嵌入式C 語言開發相關的一些理論知識,如處理器架構、操作系統、編譯原
理、編譯器特性、內存堆棧管理、Linux 內核中的GNU C 擴展語法等。
當然,上面的測試也不是為了故意扎你心或者賣關子,讓你趕緊掏腰包買下這本書,而是
想要傳遞一個信息:要想從事嵌入式開發工作,尤其是嵌入式Linux 內核驅動開發工作,你要
精通的不僅僅是C 語言,最好還要掌握和C 語言相關的一系列基礎理論和調試技能。筆者也是
過來人,從最初學習嵌入式到從事嵌入式開發工作,這一路走來坎坷崎嶇,什么都不說了,說
多了都是淚。從一開始連指針都不會用、不敢用,看內核驅動代碼一頭霧水,越看越沒底、越
看越沒自信,到現在不再犯怵,有自信和能力看懂內核中的代碼細節和系統框架,這種進步不
是天上掉下來的,也不是一不小心跌入山洞,撿到武功秘籍練出來的,而是不斷地學習和實踐、
反復迭代、不斷完善自己的知識體系和技能樹,才慢慢達到的。學習沒有捷徑可走,要想真正
學好嵌入式、精通嵌入式,個人覺得除了精通C 語言,最好還要具備以下完整的知識體系和編
程技能。
? 半導體基礎、CPU 工作原理、硬件電路、計算機系統結構。
? ARM 體系結構與匯編指令、匯編程序設計、ARM 反匯編分析。
? 程序的編譯、鏈接、安裝、運行和重定位分析。
? 熟悉C 語言標準、ARM、GNU 編譯器的特性和擴展語法。
? C 語言的模塊化編程思想,學會使用模塊化思想去分析復雜的系統。
? C 語言的面向對象編程(簡稱OOP)思想,學會使用OOP 思想去分析Linux 內核驅動。
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? X ?
? 對指針的深刻理解,對復雜指針的聲明和靈活應用。
? 對內存堆棧管理、內存泄漏、棧溢出、段錯誤的深刻理解。
? 多任務并發編程思想,CPU 和操作系統基礎理論。
本書內容及寫作初衷
本書從C 語言的角度出發,分10 章,在默認讀者已經掌握C 語言基本語法的基礎上,和
大家一起探討、學習C 語言背后的CPU 工作原理、計算機體系結構、ARM 平臺下程序的編譯/
鏈接、程序運行時的內存堆棧管理等底層知識。同時,針對嵌入式開發領域,用3 章分別探討
了C 語言的面向對象編程思想、模塊化編程思想和多任務編程思想,這些底層知識和編程思想
構成了嵌入式開發所需要的通用理論基礎和核心技能。尤其是對于很多從不同專業轉行到嵌入
式開發的朋友,由于專業背景的差異,導致每個人的知識儲備和編程技能樹參差不齊,在學習
嵌入式開發的過程中會經常遇到各種各樣的問題,陷入學習的困境。
本書的寫作初衷就是為不同專業背景的讀者搭建嵌入式開發所需要的完整知識體系和認知
框架。掌握了這些基礎理論和編程技能,也就補齊了短板,可為后續的嵌入式開發進階學習打
下堅實的基礎。
本書特色
? 大白話寫作風格,通俗易懂,不怕學不會,就怕你不學。
? 大量的配圖、原理圖,圖文并茂,更加有利于學習和理解。
? 在ARM 平臺下講解程序的編譯、鏈接和運行原理(獨創)。
? 現場“手撕”ARM 匯編代碼,從反匯編角度剖析C 函數調用、傳參過程。
? 多角度剖析C 語言:CPU、計算機體系結構、編譯器、操作系統、軟件工程。
? GNU C 編譯器擴展語法精講(在GNU 開源軟件、Linux 內核中大量使用)。
? 內存堆棧管理機制的底層剖析,從根源上理解內存錯誤。
? 從零開始一步一步搭建和迭代嵌入式軟件框架。
? 教你用OOP 思想分析Linux 內核中復雜的驅動和子系統。
? C 語言的多任務并發編程思想,CPU 和操作系統零基礎入門。
讀者定位
本書針對的是嵌入式開發,尤其是嵌入式Linux 開發背景下的C 語言進階學習,比較適合
在校學生、嵌入式學員、工作1~3 年的職場新兵閱讀和學習。為了達到更好的學習效果,在閱
前 言
? XI ?
讀本書之前,首先要確保你已經掌握了C 語言的基本語法,并且至少使用過一款C 語言集成開
發環境(VC++ 6.0、Visual Studio、C-Free、GCC 都可以),開發過一個完整的C 語言項目(課
程設計也算)。有了這些基礎和編程經驗之后,學習效果會更好。
致謝及意見反饋
本書在寫作過程中參考了很多經典圖書、論文期刊、開源代碼,包括互聯網上的很多電子
資料,由于時間和精力的關系,無法對這些資料的最初出處一一溯本求源,對各種資料的創建
者和分享者不能一一列舉。這里對他們的貢獻表示真誠的感謝。
感謝電子工業出版社的董英和李秀梅編輯,本書從選題的論證到書稿的格式審核、文字編
輯,她們都付出了辛苦的勞動并提出了很多專業意見。鑒于作者水平、時間和精力有限,書中
難免出現一些錯誤。如果你在閱讀過程中發現了錯誤或者需要改進的地方,歡迎和我聯系
,或者在我的個人博客上留言,或者掃碼
關注微信公眾號“宅學部落”。 |
內容簡介:這是一本專門為嵌入式讀者打造的C語言進階學習圖書。本書的學習重點不再是C語言的基本語法,而是和嵌入式、C語言相關的一系列知識。作者以C語言為切入點,分別探討了嵌入式開發所需要的諸多核心理論和技能,力圖幫助讀者從零搭建嵌入式開發所需要的完整知識體系和技能樹。本書從底層CPU的制造流程和工作原理開始講起,到計算機體系結構,C程序的反匯編分析,程序的編譯、運行和重定位,程序運行時的堆棧內存動態變化,GNU C編譯器的擴展語法,指針的靈活使用,C語言的面向對象編程思想,C語言的模塊化編程思想,C語言的多任務編程思想,進程、線程和協程的概念,從底層到上層,從芯片、硬件到軟件、框架,幾乎涵蓋了嵌入式開發的所有知識點。 |
目錄:第1 章 工欲善其事,必先利其器 ·······················.1
1.1 代碼編輯工具:Vim ···························.2
1.1.1 安裝Vim ···························.2
1.1.2 Vim 常用命令 ··························.3
1.1.3 Vim 配置文件:vimrc ······················.6
1.1.4 Vim 的按鍵映射 ·························.8
1.2 程序編譯工具:make ·························.10
1.2.1 使用IDE 編譯C 程序 ·······················.10
1.2.2 使用gcc 編譯C 源程序 ·····················.11
1.2.3 使用make 編譯程序 ·······················.14
1.3 代碼管理工具:Git ···························.16
1.3.1 什么是版本控制系統 ······················.16
1.3.2 Git 的安裝和配置 ························.18
1.3.3 Git 常用命令 ··························.18
第2 章 計算機體系結構與CPU 工作原理 ···················.21
2.1 一顆芯片是怎樣誕生的 ························.22
2.1.1 從沙子到單晶硅 ························.22
2.1.2 PN 結的工作原理 ························.24
2.1.3 從PN 結到芯片電路 ·······················.26
2.1.4 芯片的封裝 ··························.28
2.2 一顆CPU 是怎么設計出來的 ·······················.29
2.2.1 計算機理論基石:圖靈機 ····················.30
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XIV ?
2.2.2 CPU 內部結構及工作原理 ····················.31
2.2.3 CPU 設計流程 ··························.33
2.3 計算機體系結構 ···························.37
2.3.1 馮·諾依曼架構 ··························.38
2.3.2 哈弗架構 ····························.38
2.3.3 混合架構 ····························.38
2.4 CPU 性能提升:Cache 機制 ·······················.39
2.4.1 Cache 的工作原理 ························.39
2.4.2 一級Cache 和二級Cache ·····················.41
2.4.3 為什么有些處理器沒有Cache ···················.42
2.5 CPU 性能提升:流水線 ························.42
2.5.1 流水線工作原理 ························.43
2.5.2 超流水線技術 ·························.44
2.5.3 流水線冒險 ··························.47
2.5.4 分支預測 ····························.49
2.5.5 亂序執行 ····························.50
2.5.6 SIMD 和NEON ························.51
2.5.7 單發射和多發射 ························.52
2.6 多核CPU ·······························.54
2.6.1 單核處理器的瓶頸 ·······················.54
2.6.2 片上多核互連技術 ·······················.55
2.6.3 big.LITTLE 結構 ·························.58
2.6.4 超線程技術 ··························.59
2.6.5 CPU 核數越多越好嗎 ······················.61
2.7 后摩爾時代:異構計算的崛起 ······················.62
2.7.1 什么是異構計算 ························.62
2.7.2 GPU ······························.63
2.7.3 DSP ·····························.64
2.7.4 FPGA ·····························.64
2.7.5 TPU ·····························.65
2.7.6 NPU ······························.67
2.7.7 后摩爾時代的XPU 們 ·······················.70
目 錄
? XV ?
2.8 總線與地址 ·····························.71
2.8.1 地址的本質 ··························.72
2.8.2 總線的概念 ··························.73
2.8.3 總線編址方式 ·························.74
2.9 指令集與微架構 ···························.74
2.9.1 什么是指令集 ·························.75
2.9.2 什么是微架構 ·························.76
2.9.3 指令助記符:匯編語言 ·····················.79
第3 章 ARM 體系結構與匯編語言 ······················.81
3.1 ARM 體系結構 ····························.81
3.2 ARM 匯編指令 ····························.84
3.2.1 存儲訪問指令 ·························.85
3.2.2 數據傳送指令 ·························.87
3.2.3 算術邏輯運算指令 ·······················.87
3.2.4 操作數:operand2 詳解 ·····················.88
3.2.5 比較指令 ····························.88
3.2.6 條件執行指令 ·························.89
3.2.7 跳轉指令 ····························.90
3.3 ARM 尋址方式 ····························.91
3.3.1 寄存器尋址 ··························.91
3.3.2 立即數尋址 ··························.91
3.3.3 寄存器偏移尋址 ························.92
3.3.4 寄存器間接尋址 ························.92
3.3.5 基址尋址 ····························.92
3.3.6 多寄存器尋址 ·························.92
3.3.7 相對尋址 ····························.93
3.4 ARM 偽指令 ·····························.94
3.4.1 LDR 偽指令 ···························.94
3.4.2 ADR 偽指令 ···························.96
3.5 ARM 匯編程序設計···························.97
3.5.1 ARM 匯編程序格式 ·······················.97
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XVI ?
3.5.2 符號與標號 ··························.98
3.5.3 偽操作 ·····························.99
3.6 C 語言和匯編語言混合編程 ·······················.101
3.6.1 ATPCS 規則 ·························.101
3.6.2 在C 程序中內嵌匯編代碼 ····················.103
3.6.3 在匯編程序中調用C 程序 ····················.104
3.7 GNU ARM 匯編語言 ·························.105
3.7.1 重新認識編譯器 ························.105
3.7.2 GNU ARM 編譯器的偽操作 ····················.107
3.7.3 GNU ARM 匯編語言中的標號 ···················.108
3.7.4 .section 偽操作 ························.109
3.7.5 基本數據格式 ·························.110
3.7.6 數據定義 ··························.110
3.7.7 匯編代碼分析實戰 ·······················.111
第4 章 程序的編譯、鏈接、安裝和運行 ···················.114
4.1 從源程序到二進制文件 ························.115
4.2 預處理過程 ·····························.120
4.3 程序的編譯 ·····························.123
4.3.1 從C 文件到匯編文件 ······················.124
4.3.2 匯編過程 ··························.127
4.3.3 符號表與重定位表 ·······················.129
4.4 鏈接過程······························.132
4.4.1 分段組裝 ··························.132
4.4.2 符號決議 ··························.136
4.4.3 重定位 ···························.140
4.5 程序的安裝 ·····························.142
4.5.1 程序安裝的本質 ························.142
4.5.2 在Linux 下制作軟件安裝包 ····················.143
4.5.3 使用apt-get 在線安裝軟件 ····················.145
4.5.4 在Windows 下制作軟件安裝包 ··················.147
目 錄
? XVII ?
4.6 程序的運行 ·····························.150
4.6.1 操作系統環境下的程序運行 ···················.150
4.6.2 裸機環境下的程序運行 ·····················.153
4.6.3 程序入口main()函數分析 ·····················.154
4.6.4 BSS 段的小秘密 ························.157
4.7 鏈接靜態庫 ·····························.158
4.8 動態鏈接······························.162
4.8.1 與地址無關的代碼 ·······················.164
4.8.2 全局偏移表 ··························.165
4.8.3 延遲綁定 ··························.166
4.8.4 共享庫 ···························.168
4.9 插件的工作原理 ···························.169
4.10 Linux 內核模塊運行機制 ·······················.171
4.11 Linux 內核編譯和啟動分析 ······················.174
4.12 U-boot 重定位分析 ··························.179
4.13 常用的binutils 工具集 ························.185
第5 章 內存堆棧管理 ···························.188
5.1 程序運行的“馬甲”:進程 ·······················.189
5.2 Linux 環境下的內存管理 ························.190
5.3 棧的管理······························.192
5.3.1 棧的初始化 ··························.193
5.3.2 函數調用 ··························.195
5.3.3 參數傳遞 ··························.199
5.3.4 形參與實參 ··························.201
5.3.5 棧與作用域 ··························.206
5.3.6 棧溢出攻擊原理 ························.208
5.4 堆內存管理 ·····························.210
5.4.1 裸機環境下的堆內存管理 ····················.212
5.4.2 uC/OS 的堆內存管理 ······················.213
5.4.3 Linux 堆內存管理 ························.219
5.4.4 堆內存測試程序 ························.227
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XVIII ?
5.4.5 實現自己的堆管理器 ······················.229
5.5 mmap 映射區域探秘 ··························.233
5.5.1 將文件映射到內存 ·······················.236
5.5.2 mmap 映射實現機制分析 ·····················.239
5.5.3 把設備映射到內存 ·······················.240
5.5.4 多進程共享動態庫 ·······················.243
5.6 內存泄漏與防范 ···························.245
5.6.1 一個內存泄漏的例子 ······················.245
5.6.2 預防內存泄漏 ·························.246
5.6.3 內存泄漏檢測:MTrace ·····················.248
5.6.4 廣義上的內存泄漏 ·······················.250
5.7 常見的內存錯誤及檢測 ························.251
5.7.1 總有一個Bug,讓你淚流滿面 ···················.253
5.7.2 使用core dump 調試段錯誤 ····················.254
5.7.3 什么是內存踩踏 ························.256
5.7.4 內存踩踏監測:mprotect ····················.257
5.7.5 內存檢測神器:Valgrind ····················.259
第6 章 GNU C 編譯器擴展語法精講 ·····················.263
6.1 C 語言標準和編譯器 ·························.264
6.1.1 什么是C 語言標準 ······················.264
6.1.2 C 語言標準的內容 ·······················.265
6.1.3 C 語言標準的發展過程 ·····················.265
6.1.4 編譯器對C 語言標準的支持 ···················.268
6.1.5 編譯器對C 語言標準的擴展 ···················.268
6.2 指定初始化 ·····························.269
6.2.1 指定初始化數組元素 ······················.269
6.2.2 指定初始化結構體成員 ·····················.271
6.2.3 Linux 內核驅動注冊 ·······················.271
6.2.4 指定初始化的好處 ·······················.273
6.3 宏構造“利器”:語句表達式 ······················.273
6.3.1 表達式、語句和代碼塊 ·····················.273
目 錄
? XIX ?
6.3.2 語句表達式 ··························.274
6.3.3 在宏定義中使用語句表達式 ···················.276
6.3.4 內核中的語句表達式 ······················.280
6.4 typeof 與container_of 宏 ·······················.280
6.4.1 typeof 關鍵字 ·························.280
6.4.2 typeof 使用示例 ························.281
6.4.3 Linux 內核中的container_of 宏 ··················.281
6.4.4 container_of 宏實現分析 ····················.283
6.5 零長度數組 ·····························.286
6.5.1 什么是零長度數組 ·······················.286
6.5.2 零長度數組使用示例 ······················.288
6.5.3 內核中的零長度數組 ······················.288
6.5.4 思考:指針與零長度數組 ····················.290
6.6 屬性聲明:section···························.292
6.6.1 GNU C 編譯器擴展關鍵字:__attribute__ ·············.292
6.6.2 屬性聲明:section ·······················.294
6.6.3 U-boot 鏡像自復制分析 ·····················.297
6.7 屬性聲明:aligned ··························.299
6.7.1 地址對齊:aligned ·······················.299
6.7.2 結構體的對齊 ·························.302
6.7.3 思考:編譯器一定會按照aligned 指定的方式對齊嗎 ·········.304
6.7.4 屬性聲明:packed ·······················.305
6.7.5 內核中的aligned、packed 聲明 ··················.306
6.8 屬性聲明:format ···························.306
6.8.1 變參函數的格式檢查 ······················.306
6.8.2 變參函數的設計與實現 ·····················.308
6.8.3 實現自己的日志打印函數 ····················.312
6.9 屬性聲明:weak ···························.314
6.9.1 強符號和弱符號 ························.314
6.9.2 函數的強符號與弱符號 ·····················.316
6.9.3 弱符號的用途 ·························.317
6.9.4 屬性聲明:alias ························.319
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XX ?
6.10 內聯函數 ······························.320
6.10.1 屬性聲明:noinline ·······················.320
6.10.2 什么是內聯函數 ························.320
6.10.3 內聯函數與宏 ·························.321
6.10.4 編譯器對內聯函數的處理 ···················.322
6.10.5 思考:內聯函數為什么定義在頭文件中 ··············.324
6.11 內建函數 ······························.324
6.11.1 什么是內建函數 ························.324
6.11.2 常用的內建函數 ························.325
6.11.3 C 標準庫的內建函數 ·····················.328
6.11.4 內建函數:__builtin_constant_p(n) ·················.328
6.11.5 內建函數:__builtin_expect(exp,c) ·················.329
6.11.6 Linux 內核中的likely 和unlikely ················.330
6.12 可變參數宏 ·····························.333
6.12.1 什么是可變參數宏 ······················.334
6.12.2 繼續改進我們的宏 ······················.335
6.12.3 可變參數宏的另一種寫法 ···················.336
6.12.4 內核中的可變參數宏 ·····················.336
第7 章 數據存儲與指針 ··························.339
7.1 數據類型與存儲 ···························.340
7.1.1 大端模式與小端模式 ······················.340
7.1.2 有符號數和無符號數 ······················.343
7.1.3 數據溢出 ··························.345
7.1.4 數據類型轉換 ·························.348
7.2 數據對齊······························.352
7.2.1 為什么要數據對齊 ·······················.352
7.2.2 結構體對齊 ··························.353
7.2.3 聯合體對齊 ··························.356
7.3 數據的可移植性 ···························.358
7.4 Linux 內核中的size_t 類型 ······················.360
7.5 為什么很多人編程時喜歡用typedef ···················.361
目 錄
? XXI ?
7.5.1 typedef 的基本用法 ······················.361
7.5.2 使用typedef 的優勢 ······················.364
7.5.3 使用typedef 需要注意的地方 ··················.366
7.5.4 typedef 的作用域 ·······················.368
7.5.5 如何避免typedef 被大量濫用 ··················.368
7.6 枚舉類型······························.369
7.6.1 使用枚舉的三種方法 ······················.370
7.6.2 枚舉的本質 ··························.370
7.6.3 Linux 內核中的枚舉類型 ·····················.372
7.6.4 使用枚舉需要注意的地方 ····················.372
7.7 常量和變量 ·····························.373
7.7.1 變量的本質 ··························.374
7.7.2 常量存儲 ··························.376
7.7.3 常量折疊 ··························.377
7.8 從變量到指針 ····························.378
7.8.1 指針的本質 ··························.379
7.8.2 一些復雜的指針聲明 ······················.381
7.8.3 指針類型與運算 ························.383
7.9 指針與數組的“曖昧”關系 ·······················.386
7.9.1 下標運算符[] ··························.388
7.9.2 數組名的本質 ·························.389
7.9.3 指針數組與數組指針 ······················.392
7.10 指針與結構體 ····························.395
7.11 二級指針 ······························.399
7.11.1 修改指針變量的值 ······················.400
7.11.2 二維指針和指針數組 ·····················.402
7.11.3 二級指針和二維數組 ·····················.403
7.12 函數指針 ······························.407
7.13 重新認識void ····························.408
第8 章 C 語言的面向對象編程思想 ·····················.411
8.1 代碼復用與分層思想 ·························.412
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XXII ?
8.2 面向對象編程基礎 ··························.413
8.2.1 什么是OOP ·························.414
8.2.2 類的封裝與實例化 ·······················.414
8.2.3 繼承與多態 ··························.416
8.2.4 虛函數與純虛函數 ·······················.418
8.3 Linux 內核中的OOP 思想:封裝 ····················.419
8.3.1 類的C 語言模擬實現 ······················.420
8.3.2 鏈表的抽象與封裝 ·······················.422
8.3.3 設備管理模型 ·························.423
8.3.4 總線設備模型 ·························.427
8.4 Linux 內核中的OOP 思想:繼承 ····················.429
8.4.1 繼承與私有指針 ························.429
8.4.2 繼承與抽象類 ·························.430
8.4.3 繼承與接口 ··························.431
8.5 Linux 內核中的OOP 思想:多態 ····················.433
第9 章 C 語言的模塊化編程思想 ······················.436
9.1 模塊的編譯和鏈接 ··························.437
9.2 系統模塊劃分 ····························.439
9.2.1 模塊劃分方法 ·························.439
9.2.2 面向對象編程的思維陷阱 ····················.441
9.2.3 規劃合理的目錄結構 ······················.442
9.3 一個模塊的封裝 ···························.443
9.4 頭文件深度剖析 ···························.444
9.4.1 基本概念 ··························.444
9.4.2 隱式聲明 ··························.446
9.4.3 變量的聲明與定義 ·······················.447
9.4.4 如何區分定義和聲明 ······················.449
9.4.5 前向引用和前向聲明 ······················.450
9.4.6 定義與聲明的一致性 ······················.453
9.4.7 頭文件路徑 ··························.454
9.4.8 Linux 內核中的頭文件 ······················.456
目 錄
? XXIII ?
9.4.9 頭文件中的內聯函數 ······················.459
9.5 模塊設計原則 ····························.459
9.6 被誤解的關鍵字:goto ·························.461
9.7 模塊間通信 ·····························.462
9.7.1 全局變量 ··························.462
9.7.2 回調函數 ··························.466
9.7.3 異步通信 ··························.470
9.8 模塊設計進階 ····························.471
9.8.1 跨平臺設計 ··························.471
9.8.2 框架 ····························.473
9.9 AIoT 時代的模塊化編程 ·······················.476
第10 章 C 語言的多任務編程思想和操作系統入門 ···············.478
10.1 多任務的裸機實現 ·························.479
10.1.1 多任務的模擬實現 ······················.479
10.1.2 改變任務的執行頻率 ·····················.481
10.1.3 改變任務的執行時間 ·····················.484
10.2 操作系統基本原理 ·························.488
10.2.1 調度器工作原理 ························.490
10.2.2 函數棧與進程棧 ························.492
10.2.3 可重入函數 ··························.493
10.2.4 臨界區與臨界資源 ······················.495
10.3 中斷 ································.497
10.3.1 中斷處理流程 ·························.497
10.3.2 進程棧與中斷棧 ························.500
10.3.3 中斷函數的實現 ························.504
10.4 系統調用 ······························.506
10.4.1 操作系統的API ·······················.506
10.4.2 操作系統的權限管理 ·····················.508
10.4.3 CPU 的特權模式 ························.508
10.4.4 Linux 系統調用接口 ······················.509
嵌入式C 語言自我修養——從芯片、編譯器到操作系統
? XXIV ?
10.5 揭開文件系統的神秘面紗 ······················.510
10.5.1 什么是文件系統 ························.511
10.5.2 文件系統的掛載 ························.513
10.5.3 根文件系統 ··························.514
10.6 存儲器接口與映射 ·························.515
10.6.1 存儲器與接口 ·························.515
10.6.2 存儲映射 ···························.518
10.6.3 嵌入式啟動方式 ························.520
10.7 內存與外部設備 ···························.521
10.7.1 內存與外存 ··························.521
10.7.2 外部設備 ···························.522
10.7.3 I/O 端口與I/O 內存 ······················.523
10.8 寄存器操作 ·····························.524
10.8.1 位運算應用 ··························.524
10.8.2 操作寄存器 ··························.527
10.8.3 位域 ·····························.529
10.9 內存管理單元MMU ························.531
10.9.1 地址轉換 ···························.532
10.9.2 權限管理 ···························.534
10.10 進程、線程和協程 ·························.535
10.10.1 進程 ···························.536
10.10.2 線程 ···························.538
10.10.3 線程池 ··························.539
10.10.4 協程 ···························.540
10.10.5 小結 ···························.541
參考文獻 ·································.543 |
序: |
|