快速上手和使用makefile

来源:互联网 发布:韩国的历史书 知乎 编辑:程序博客网 时间:2024/04/30 10:25

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

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

0 0
原创粉丝点击