1. 程式人生 > >6 makefile

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:所有的對應的依賴檔案

[email protected]:目標

@<:依賴         

     

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 &命令物件;

}