想学习一样东西,最好先问个为什么要这样,这样学起来才有目标。上大学时,老师讲课总是告诉我们必须这样那样,很少讲这门课是干什么的,有什么意义,有什么用。有一次我问老师,为什么要傅里叶变换,学习它能用来做什么,老师先是很惊讶,然后耐心的给所有同学都讲了讲,老师讲完也很欣慰,笑着说因为很少有学生去问这样的问题。所以也只是讲课,没讲实际的应用和原理的东西。学生们听了也有兴趣了,学也认真了。
makefile是什么?为什么要用makefile?简单的说makefile就是编译程序用的,因为用makefile效率高。代码小倒没什么,像linux那样几千万行代码,一个一个文件去敲命令行可敲到什么时候。还有就是调试时,如果只改动了一个文件,就要全部编译一遍,那该是有多慢。因此,makefile出现了。改动或者编写完代码,只需要简单的make一下就行了。makefile原理是什么?其实就是文件前后的依赖关系。
在windows下的IDE编程,很少听说这个东西,实际上是IDE环境自动给你做了这个工作而已,不需要你手动去编写了。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C 的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
一般来说,无论是C、C 、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C 文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.
理论得来终觉浅,还是举例说明吧。GCC开发 stm32的例子,没有用启动文件start.s
如我有以下几个文件:isr.c,uart_helloworld.c,有链接脚本文件stm32f103VET6.ld
文件内容:isr.c,
完成中断定义和C的运行时库的初始化,功能类似start.s
extern int main(void); void ResetISR(void); void NMIException(void); void HardFaultException(void); void MemManageException(void); void BusFaultException(void); void UsageFaultException(void);
....
//***************************************************************************** // // The following are constructs created by the linker, indicating where the // the "data" and "bss" segments reside in memory. The initializers for the // for the "data" segment resides immediately following the "text" segment. // //***************************************************************************** extern unsigned long _eisr_vector; extern unsigned long _text; extern unsigned long _etext; extern unsigned long _data; extern unsigned long _edata; extern unsigned long _bss; extern unsigned long _ebss; //单片机复位后首先执行,完成一些代码段的初始化工作。
//start.s汇编启动代码其实也是做了这些工作而已。 void ResetISR(void) { unsigned long *src, *dst; // copy the text segment from flash to SRAM src = &_eisr_vector; dst = &_text; while (dst < &_etext) { *dst = *src ; } // Copy the data segment initializers from flash to SRAM. dst = &_data; while (dst < &_edata) { *dst = *src ; } // Zero fill the bss segment. for(dst = &_bss; dst < &_ebss; dst ) { *dst = 0; } // Call the application's entry point. main(); }
uart_helloworld.c文件部分内容
.....
int main(void) { int i; Stm32_Clock_Init(2); //50M,外部晶振25M PLL锁相环设定 delay_init(50);//延时初始化。用不着就删了吧。 LED_Init();//led初始化用不着也删了吧。 uart_init();//串口一初始化。bbs用的是系统时钟50M uart2_init();//串口二初始化。bbs用的是外部时钟25M while (1)
{
}
}
链接脚本文件stm32f103VET6.ld ,定义代码段和内存变量等的存储位置。
/*************************************************/ /* filename: stm32f103VET6.ld */ /* linkscript for STM32F103VET6 microcontroller */ /* */ /*************************************************/ MEMORY { /*FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512k*/ /*Let's use the address space start from 0x00000000*/ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512k SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64k } /* Section Definitions */ SECTIONS { .isr_vector : { KEEP(*(.isr_vector)) isr.o(.text) . = ALIGN(4); _eisr_vector = .; } .text : AT (_eisr_vector) { _text = .; *(EXCLUDE_FILE(isr.o) .text) *(.rodata) . = ALIGN(4); _etext = .; } > SRAM .data : { _data = .; *(.data) . = ALIGN(4); _edata = . ; } > SRAM /* .bss section which is used for uninitialized data */ .bss (NOLOAD) : { _bss = . ; *(.bss) . = ALIGN(4); _ebss = . ; } > SRAM _end = . ; }
现在就可以用GCC来编译和链接文件了。
可以命令行一个个
编译:
arm-elf-gcc -g -mcpu=cortex-m3 -mthumb -c uart_helloworld.c -nostartfiles -o uart_helloworld.o
arm-elf-gcc -g -mcpu=cortex-m3 -mthumb -c isr.c -nostartfiles -o isr.o
链接:
arm-elf-ld -T stm32f103vet6.ld -o helloworld.out uart_helloworld.o isr.o
生成执行文件:
arm-elf-objcopy -O binary helloworld.out helloworld.bin
更近一步,可以把这些命令写入文件,命令为makefile,直接make一下就可自动完成编译链接和生成执行文件。
PREFIX := arm-elf- .PHONY: all clean
all: helloworld.bin
uart_helloworld.o: uart_helloworld.c arm-elf-gcc -g -mcpu=cortex-m3 -mthumb -c uart_helloworld.c -nostartfiles -o uart_helloworld.o isr.o: isr.c arm-elf-gcc -g -mcpu=cortex-m3 -mthumb -c isr.c -nostartfiles -o isr.o helloworld.out: uart_helloworld.o isr.o stm32f103vet6.ld arm-elf-ld -T stm32f103vet6.ld -o helloworld.out uart_helloworld.o isr.o helloworld.bin: helloworld.out arm-elf-objcopy -O binary helloworld.out helloworld.bin clean: rm -f *.o *.out *.bin
这个文件把各自的依赖关系写的很清楚,有利于理解makefile的原理。但是对于代码文件很多的情况就不适用了。
因此可以利用makefile的自动推导和隐式规则进一步精简。
BINARY= main PREFIX = arm-elf CC = $(PREFIX)-gcc LD = $(PREFIX)-ld OBJCOPY = $(PREFIX)-objcopy OBJDUMP = $(PREFIX)-objdump CFLAGS= -mcpu=cortex-m3 -mthumb -nostartfiles LDSCRIPT =standalone.ld LDFLAGS = -T $(LDSCRIPT) OBJS= main.o
.PHONY: clean all:images images: (BINARY).hex (BINARY).bin (BINARY).srec (BINARY).list (OBJS):%.o:%.c(CC) -c (CFLAGS) < -o @ %.elf: (OBJS) (LDSCRIPT)(CC) (CFLAGS) -o start.o -c start.s(LD) -o (*).elf start.o (OBJS) (LDFLAGS) %.bin: %.elf(OBJCOPY) -Obinary (*).elf (*).bin %.hex:%.elf(OBJCOPY) -Oihex (*).elf (*).hex %.srec: %.elf(OBJCOPY) -Osrec (*).elf (*).srec %.list: %.elf(OBJDUMP) -S (*).elf >
all: prebuild $(TARGET).elf #**************************************************************************** # Source files #**************************************************************************** SRC_C=$(shell gfind . -name "*.c") SRC_S=$(shell gfind . -name "*.s")
#**************************************************************************** # TARGET #**************************************************************************** prebuild:@echo Building app... (TARGET).elf : (OBJS) (LIBS)@echo (LD) @: ^-{LD} {LDFLAGS} -o @ ^ > @ (TARGET).bin (APPFLAG)@echo Generating hex...@(OBJCOPY) -O ihex @ (TARGET).hex@echo Generating asm...@(OBJDUMP) -D -S @ > (TARGET).asm@echo OK! ifeq (YES, {STRIP_RELEASE}){STRIP} {TARGET}.elf endif %.o : %.c{CC} -c {CFLAGS} {INCS} -o @ < %.o : %.s@ <
clean: @echo The following files: rm -f $(TARGET) *.o gfind . -name "*.[od]" |xargs rm @echo Removed! fileencoding: @ for dir in $(DIRS); do enconv -L zh_CN -x cp936 $$dir/*; done
编译单个目录下的makefile模板:
(wildcard *.c) OBJS := (patsubst %.c,%.o,(SRCS)) all: (OBJS) %.o : %.c{CC} -c {CFLAGS} {INCS} -o @
(wildcard *.c) # 调用patsubst函数,生成与源文件对应的“.o”文件列表 OBJS := (patsubst %.c, %.o, (SOURCE)) # 编译所有".o"文件生成可执行文件 all : (EXECUTABLE)(EXECUTABLE) : (OBJS) @(CC) (CFLAGS) (LDFLAGS) (OBJS) -o (EXECUTABLE) # 声明伪目标 .PHONY : clean # 删除所有中间文件和目标文件 clean : @rm -f (EXECUTABLE) (OBJS) *.o