快速上手和使用makefile

简介: 快速上手和使用makefile

想学习一样东西,最好先问个为什么要这样,这样学起来才有目标。上大学时,老师讲课总是告诉我们必须这样那样,很少讲这门课是干什么的,有什么意义,有什么用。有一次我问老师,为什么要傅里叶变换,学习它能用来做什么,老师先是很惊讶,然后耐心的给所有同学都讲了讲,老师讲完也很欣慰,笑着说因为很少有学生去问这样的问题。所以也只是讲课,没讲实际的应用和原理的东西。学生们听了也有兴趣了,学也认真了。


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


相关文章
|
7月前
|
编译器 Shell Linux
Makefile(3)进阶
Makefile(3)进阶
53 0
|
Java 编译器 Linux
Makefile教程(入门介绍)
Makefile教程(入门介绍)
116 0
|
4月前
|
编译器 Linux C语言
Makefile实战论(一)
Makefile实战论(一)
|
4月前
|
存储 Java Shell
CMake01快速上手
CMake01快速上手
|
7月前
|
Linux 编译器 C语言
快速上手makefile自动化构建工具
快速上手makefile自动化构建工具
|
7月前
Makefile(1)入门
Makefile(1)入门
49 0
|
7月前
|
Shell Linux 编译器
Linux Makefile 全面教程:使用 Makefile 进行项目管理和构建
Linux Makefile 全面教程:使用 Makefile 进行项目管理和构建
488 0
|
7月前
|
存储 编译器 Shell
Makefile语法基础
Makefile语法基础
35 0
|
7月前
|
编译器 Shell C语言
Makefile快速入门
Makefile快速入门
49 0
|
Linux 编译器 开发工具
【Linux】快速上手自动化构建工具make/makefile
【Linux】快速上手自动化构建工具make/makefile
150 0