6 makefile
6 makefile
案例:上位機給下位機發送命令,實現開關燈和開關蜂鳴器
交叉編譯:一個一個編譯
問:如果上面的程式只要一修改,那就重新編譯,重新連結,重新去皮,交叉編譯的過程及其繁瑣,如何優化編譯過程呢?
學習資料:<<跟我一起寫Makefile>>作為工具書來用
答:只需一個小小的Makefile檔案即可搞定
明確:Makefile功能:讓程式的編譯變得簡單化
明確: Makefile語法規則:
目標:依賴
(TAB鍵)命令1
(TAB鍵)命令2
(TAB鍵)@命令3
... 可以是多條命令
注意:命令前面加@能夠遮蔽命令的執行資訊
例如:
#註釋用#
#A依賴B,也就是先有B後有A
#如果有B,通過執行下面的命令來得到A
A:B
(TAB鍵)命令
明確:Makefile檔案由make命令來解析執行
當執行make命令時,make命令會在當前目錄下找到Makefile檔案。然後解析Makefile檔案,根據Makefile制定的規則來執行規則裡面的命令
例如:有一個Makeifle,其內容如下
cd /opt/arm/05/3.0
ls
helloworld.c Makefile
vim Makefile 內容如下:
#制定的規則
helloworld:helloworld.c
gcc -o helloworld helloworld.c
make //開始編譯
說明make命令的執行流程:
當執行make命令時,make命令首先在當前目錄下找到Makefile檔案。然後開啟Makefile檔案,make命令根據Makefile檔案制定的規則
helloworl依賴helloworld.c,在編譯之前先檢查當前目錄下是否有目標helloworld存在:如果存在,此時make命令還要檢查helloworld目標和依賴helloworld.c的時間戳,如果發現helloworld.c依賴的時間要比目標helloworld的時間要新,說明helloworld.c修改過,那必須重新編譯,否則無需重新編譯。如果helloworld不存在,勢必要編譯,編譯通過通過執行:
gcc -o helloworld helloworld.c命令來生成helloworld
例如:Makefile內容如下:分析流程
#制定規則1
helloworld:helloworld.o
gcc -o helloworld helloworld.o
#制定規則2
helloworld.o:helloworld.c
gcc -c -o helloworld.o helloworld.c
說明:
當執行make命令時,make命令首先在當前目錄找Makefile,
找到以後根據規則1和規則2進行編譯,在編譯之前首先在當前目錄下
找到是否存在helloworld:
如果不存在,然後在當前目錄下找它的依賴helloworld.o
如果helloworld.o也不存在,然後在當前目錄下找helloworld.o依賴
helloworld.c,如果找到了helloworld.c,然後根據規則2的命令來生成
helloword.o,有了helloworld.o,然後根據規則1的命令生成helloworld
當然啦,如果有存在的,還要檢查時間戳來決定是否重新編譯
明確:Makefile的小技巧
main.o:main.c
gcc -c -o main.o main.c
test.o:test.c
gcc -c -o test.o test.c
...
缺點:又臭又長
優化:兩句話搞定:
%.o:%.c
gcc -c -o [email protected] $<
說明:
%.o:所有的對應的目標檔案
%.c:所有的對應的依賴檔案
@<:依賴
Makefile同樣也有變數一說
變數類似C語言的#define,便於程式碼的修改NAME=zhangsan#定義變數,並且初始化為zhangsan,訪問變數的值用$(NAME)的值就是zhangsan
案例:給裸板程式新增Makefile
上位機實施步驟:
cp /opt/arm/05/2.0 /opt/arm/day05/3.0 -fr
cd /opt/arm/day05/3.0
vim Makefile
make //編譯
make clean //刪除編譯生成的目標檔案
連結指令碼
明確:任何一個二進位制可執行程式都包含基本的三大段
.text(程式碼段)/.data(資料段)/.bss
例如:shell.elf包含三大段
main.o同樣也包含三大段
uart.o同樣也包含三大段
led.o同樣也包含三大段
strcmp.o同樣也包含三大段
連結的時候(arm...ld)會將各種.o連結到"一起"生成shell.elf
連結器ld將各個.o的各個段合成最終可執行檔案的各個段的內容
連結指令碼作用就是制定將來二進位制可執行檔案各個段的內容
連結指令碼給連結器ld使用,將來ld根據連結指令碼制定的規則進行連結
連結指令碼本質就是一個文字檔案,以.lds結尾
連結指令碼的基本語法
參考程式碼:
cp /opt/arm/05/3.0 /opt/arm/day05/4.0 -fr
cd /opt/arm/day05/4.0
vim shell.lds 建立連結指令碼檔案,新增如下內容
/*告訴連結器將來shell.bin二進位制可執行檔案的入口函式為main函式*/
ENTRY(main)
/*告訴連結器將來shell.bin包含的段的內容有以下內容*/
SECTIONS
{
/*告訴連結器將來shell.bin各個段的起始地址從0x48000000開始*/
/*注意"."後面要跟空格*/
. = 0x48000000;
/*告訴連結器將來shell.bin的text段的內容如下*/
/*注意:".text"後面要跟空格*/
.text :
{
/*將main.o中text段的內容包含進來並且放在最開始的位置進行連結*/
main.o(.text)
/*將其餘各個.o中的text段的內容包含進來*/
*(.text)
}
/*告訴連結器將來shell.bin的data段的內容如下*/
/*注意:".data"後面要跟空格*/
.data :
{
*(.data)
}
/*告訴連結器將來shell.bin的bss段的內容如下*/
/*注意:".bss"後面要跟空格*/
.bss :
{
*(.bss)
}
}
儲存退出
問:shell.lds如何使用呢?
答:
vim Makefile
將:
LDFLAGS=-nostartfiles -nostdlib -Ttext=0x48000000 -emain
修改為:
/*-Tshell.lds:告訴連結器ld將來連結的規則根據shell.lds來進行*/
LDFLAGS=-nostartfiles -nostdlib -Tshell.lds
儲存退出
make
arm-cortex_a9-linux-gnueabi-objdump -D shell.elf > shell.dis
vim shell.dis //檢視0x48000000對應的地址是否為main函式
小問題:
1)如果Makefile檔案是這麼寫:
cp /opt/arm/05/4.0 /opt/arm/05/5.0 -fr
cd /opt/arm/05/5.0
vim Makefile
將:
LDFLAGS=-nostartfiles -nostdlib -Tshell.lds
修改為:
LDFLAGS=-nostartfiles -nostdlib -Ttext=0x46000000 -Tshell.lds
儲存退出
make
問:連結器在連結的時候,制定的連結器起始地址用0x46000000還是0x48000000?
答:通過反彙編來檢視,檢視main函式的入口地址是0x46000000還是0x48000000
2)如果Makefile檔案是這麼寫:
...
OBJ=main.o uart.o led.o strcmp.o
...
修改為:
...
OBJ=uart.o led.o strcmp.o main.o
...
LDFLAGS=-nostartfiles -nostdlib -Tshell.lds
問:連結的時候,入口函式為main?
答:同樣通過反彙編來獲取答案
實戰注意:將來拿到任何一個連結指令碼,通過連結指令碼要獲取以下三個重要資訊:
1)通過連結指令碼能夠獲取程式執行的入口函式
2)通過連結指令碼能夠獲取程式將來執行的記憶體起始地址
3)通過連結指令碼能夠獲取程式執行的第一個檔案
尤其這條最關鍵!因為執行的第一個檔案中的第一個函式就是程式的入口函式,將來實際開發看程式碼的時候,一定先找到執行的第一個檔案,找到了執行的第一個檔案就找到了入口函式,然後藉助ctags或者sourceinsight原始碼閱讀工具順藤摸瓜慢慢理清程式碼的整個執行流程即可!
案例:優化shell程式
發掘shell程式的缺點:隨著硬體運算元量的增多,main函式將變得越來越長,又臭又長
例如:
if .... //判斷開燈
else if ... //判斷關燈
else if ... //判斷開蜂鳴器
else if ... //判斷關蜂鳴器
... //一堆硬體判斷
else
回顧:C語言的typedef關鍵字:
形式1:
typedef unsigned char u8;
形式2:
//宣告描述學生資訊的資料結構
struct std
{
char *name;
int id;
};
//定義初始化兩個學生物件
struct std std_info[] =
{
{"zhangsan",1234},//第一個學生物件
{"lisi",5678} //第二個學生物件
};
優化:
//宣告描述學生資訊的資料結構
typedef struct std
{
char *name;
int id;
}std_t;
//定義初始化兩個學生物件
std_t std_info[] =
{
{"zhangsan",1234},//第一個學生物件
{"lisi",5678} //第二個學生物件
};
形式3:
//宣告一個新的資料型別,本質就是一個函式指標,資料型別名稱為cb_t
typedef void (*cb_t)(void);
cb_t cb = led_on; //定義一個函式指標cb並且指向函式led_on
cb(); //呼叫cb指向的函式,就是led_on
發掘揣摩shell程式,shell程式裡面有一個事物:命令
並且命令有兩個屬性:命令的名稱和命令對應的函式
//宣告描述命令的資料結構
typedef void (*cb_t)(void);
typedef struct cmd
{
char *name;
cb_t callback;
}cmd_t;
//定義初始化開關燈命令物件
cmd_t cmd_tbl[] =
{
{"ledon", led_on},
{"ledoff", led_off}
};
//配套函式:根據命令名找到對應的命令物件
cmd_t *find_cmd(char *name)
{
//根據傳遞來的name在上面的命令物件的陣列中找到命令物件
return &命令物件;
}