想学习一样东西,最好先问个为什么要这样,这样学起来才有目标。上大学时,老师讲课总是告诉我们必须这样那样,很少讲这门课是干什么的,有什么意义,有什么用。有一次我问老师,为什么要傅里叶变换,学习它能用来做什么,老师先是很惊讶,然后耐心的给所有同学都讲了讲,老师讲完也很欣慰,笑着说因为很少有学生去问这样的问题。所以也只是讲课,没讲实际的应用和原理的东西。学生们听了也有兴趣了,学也认真了。
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 > $(*).list clean: rm -f *.o rm -f *.d rm -f *.elf rm -f *.bin rm -f *.hex rm -f *.srec rm -f *.list
如果文件分散在更多其他的目录,则可以配合linux的shell命令去自动找到.c文件
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).map @echo Generating bin... @elftobin $@ $(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 $(AS) $(ASFLAG) -o $@ $< 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模板:
incdir= ../../ include $(incdir)make.rules SRCS := $(wildcard *.c) OBJS := $(patsubst %.c,%.o,$(SRCS)) all: $(OBJS) %.o : %.c ${CC} -c ${CFLAGS} ${INCS} -o $@ $< clean: @ rm -f $(OBJS) *~ core ############################################## # # 单目录通用Makefile # 目标文件可自己的设定 # 始须调试程序,修改 CFLAGS 变量为-Wall -g # By # Time 2011-6-3 # ############################################## # EXECUTABLE为目标的可执行文件名, 可以根据具体的情况对其进行修改。 EXECUTABLE := ICC # 修改隐含规则中宏 CC := arm-none-linux-gnueabi-gcc CFLAGS := -Wall -O2 #LDFLAGS+= -static LDFLAGS+= -L ./ -lstategrid # 列出工作目录下所有以“.c”结尾的文件,以空格分隔,将文件列表赋给变量SOURCE SOURCE := $(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