跳至主要内容

【转】vivi开发笔记(三):Makefile详解

文章说明:calmarrow(lqm)原创

文章引自:http://piaoxiang.cublog.cn

 
    在Linux下面,要分析一个项目,首先应该读Makefile。前面有了一定的Makefile基础,现在虽然还不能写复杂的Makefile脚本,但是参考资料,还可以读懂vivi的Makefile。不过vivi的Makefile给我的感觉是在组织上有点凌乱,读起来有点费劲。只有真正理解了这个项目的组织,才能明白凌乱在何处。这中间都是按照自己的认识过程,尝试了各种方法来分析,基本上完成了按照自己理解改写的Makefile。编译完成重新下载到nand flash里面,验证没有问题。
 
    下面首先给出改写的Makefile。我尝试使用英语注释,可能不太通顺。但是,第一步总是要迈出的,早总比晚要好。
 

# Modified by Qingmin Liu(piaoxiangxinling@163.com, Shandong University)
# Date: 2007-07-26
# Desc: This document is only used for studying. If you want to talk about
#       vivi or embedded system, I am very glad to receive your email.

#
# Section 1: vivi version
#


# 3 rows below supplies with a numbering scheme, which comes from Linux
# kernel. This numbering scheme uses three numbers separated by dots to
# identify the releases. The first number designates the version, the
# second designates the patch, and the third designates the release.
#
# Usually, you should use a kernel from the latest stable series for your
# embedded system.
VERSION        = 0
PATCHLEVEL     = 1
SUBLEVEL       = 4

# This macro definations is used to supply users with version information,
# and you will find the usage in [init/version.c]. If you port vivi to your
# own board successfully, the version information displays at the beginning
# through the debug serial.
#
# VIVIRELEASE = 0.1.4
VIVIRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)

#
# Section 2: target architecture
#


# Architecture is arm. If you don't understand architecture, I think, you'd
# better search some data on the internet.
ARCH := arm

#
# Section 3: tools
#


# CONFIG_SHELL gets the shell that is used on your host. In my host, OS is
# Redhat Linux 9.0, and bash shell is the default one.
#
# This is used to execute the config scripts in [scripts]. And this is vivi's
# core about interactive configuration.
#

# You need to know some about Makefile if you don't know $(shell ..). In fact,
# it works to search the shell: /bin/bash --> sh. In my host, it works well.
# for example,
#    $which bash
#    /bin/bash
#    $which sh
#    /bin/sh
CONFIG_SHELL := $(shell if [ -x /bin/bash ]; then echo /bin/bash; \
                        else echo /bin/sh; fi)

# HOST tool
HOSTCC     = gcc
HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
export HOSTCC HOSTCFLAGS

# TARGET tool
CROSS_COMPILE = /usr/local/arm/2.95.3/bin/arm-linux-

AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm        
STRIP   = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

export AS LD CC CPP AR

#
# Section 4: parameters

#


# TOPDIR means top directory of your project. More accurately, it means the
# directory of "Makefile" because you must step into the directory of Makefile
# if you want to execute make on your command line.
TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD;else /bin/pwd; fi)
export TOPDIR

# standard CFLAGS
CPPFLAGS  := -I$(TOPDIR)/include
CFLAGS    := $(CPPFLAGS) -Wall -Wstrict-prototypes -O2 -fPIC -fomit-frame-pointer
AFLAGS    := -D__ASSEMBLY__ $(CPPFLAGS)
export CFLAGS AFLAGS

# Location of the gcc arm libs.
ARM_GCC_LIBS = /usr/local/arm/2.95.3/lib/gcc-lib/arm-linux/2.95.3
OBJCOPYFLAGS = -R .comment -R .stab -R .stabstr

CLIBS     = -L$(ARM_GCC_LIBS) -lgcc -lc
LINKFLAGS = -Tarch/vivi.lds -Bstatic

#
# Section 5: files that will be included or handled with.
#


# whe you execute make on the command line, MAKEFILES are read at first.
MAKEFILES = $(TOPDIR)/.config

# core files and so on
CORE_FILES = init/main.o init/version.o lib/lib.o
LIBS       := lib/priv_data/priv_data.o
SUBDIRS    = drivers lib

# files that will be deleted when "make clean"
CLEAN_FILES =    \
        vivi-elf \
        vivi     \
        vivi.nm  \
        vivi.map

# files that will be deleted when "make distclean"
DISTCLEAN_FILES =                                      \
        include/autoconf.h .menuconfig.log             \
        scripts/lxdialog/*.o scripts/lxdialog/lxdialog \
        .config .config.old TAGS tags

#

# Section 6: drivers added
#

# define CONFIGURATION if .config exists;
# else include .config
ifeq (.config, $(wildcard .config))
include .config
else
CONFIGURATION = config
endif

# drivers added by .config setup
# if CONFIG_SERIAL=y and CONFIG_MTD=y, DRIVERS equals "drivers/serial/serial.o drivers/mtd/mtd.o"
DRIVERS-y                   :=
DRIVERS-$(CONFIG_SERIAL)    += drivers/serial/serial.o
DRIVERS-$(CONFIG_MTD)       += drivers/mtd/mtd.o
DRIVERS                     := $(DRIVERS-y)

#
# Section 7: sub Makefile
#

include arch/Makefile

#

# Section 8: real handle
#

.PHONY: all do-it-all

# make all
# execute all handle procedure by the config
all: do-it-all

# search .config.If it exists, read .config first and equals "make Version; make vivi".
# else define CONFIGURATION and it equals "make config"
ifdef CONFIGURATION
do-it-all: $(CONFIGURATION)
else
do-it-all: Version vivi    
endif

# make Version
# delete [include/compile.h]
Version:
    $(RM) include/compile.h

# make vivi
# create target file -- vivi
# please remember: prerequisites in sequence.
vivi: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
    $(LD) -v $(LINKFLAGS)  \
        $(HEAD)            \
        $(CORE_FILES)      \
        $(DRIVERS)         \
        $(LIBS)            \
        -o $@-elf $(CLIBS)
    $(NM) -v -l $@-elf > $@.map
    $(OBJCOPY) -O binary -S $@-elf $@ $(OBJCOPYFLAGS)
    @echo
    @echo
" ^_^ The vivi boot image file is: $(shell pwd)/$@, and you \
            can download it to your [nor | nand] flash."

    @echo

# make oldconfig
# configure by the .config
oldconfig:
    $(CONFIG_SHELL) scripts/Configure -d arch/config.in

# make config
# configure by the [arch/defconfig]
config:
    $(CONFIG_SHELL) scripts/Configure arch/config.in

# make menuconfig
# configure in forms of menutext
menuconfig:
    $(MAKE) -C scripts/lxdialog all
    $(CONFIG_SHELL) scripts/Menuconfig arch/config.in

# make clean
# delete middle files
clean:
    find . \( -name '*.o' -o -name core -o -name ".*.flags" \) -type f -print \
    | grep -v lxdialog/ | xargs rm -f
    $(RM) $(CLEAN_FILES)

# make distclean
# delete middle files and configure files
distclean: Version clean
    $(RM) $(DISTCLEAN_FILES)

# make myboard_config
# Configuration targets. Use these to select a
# configuration for your architecture
#
# here, @ makes the command no echo back.and "CFG=$(@:_config=)" uses the advanced
# function of variables.for example,
#     foo:=a.o b.o c.o
#     bar:=$(foo:.o=.c)
# so bar = a.c b.c c.c
%_config:
    @ (                                                   \
    CFG=$(@:_config=);                                    \
    if [ -f arch/def-configs/$$CFG ]; then                \
     [ -f .config ] && mv -f .config .config.old;         \
     cp arch/def-configs/$$CFG .config;                   \
     echo "*** Default configuration for $$CFG installed";\
     echo "*** Next, you may run 'make oldconfig'";       \
    else                                                  \
     echo "$$CFG does not exist";                         \
    fi;                                                   \
    )

# make myboard
# make your work automatically
%: ./arch/def-configs/%
    $(MAKE) distclean
    cp arch/def-configs/$* ./.config -f
    $(MAKE) oldconfig
    $(MAKE)

#
# Section 9: real handle prerequisites
#

# read SUBDIRS
# here SUBDIRS = drivers lib
linuxsubdirs: $(patsubst %, _dir_%, $(SUBDIRS))

# read sub Makefile in SUBDIRS
$(patsubst %, _dir_%, $(SUBDIRS)) :
    $(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C $(patsubst _dir_%, %, $@)

# create [include/compile.h]
include/compile.h: $(CONFIGURATION)
    @echo \#define VIVI_RELEASE \"$(VIVIRELEASE)\" > .ver
    @echo -n \#define UTS_VERSION \"\#$(VIVIRELEASE) >> .ver
    @if [ -f .name ]; then echo -n \-`cat .name` >> .ver; fi
    @echo ' '`date`'"' >> .ver
    @echo \#define VIVI_COMPILE_BY \"`whoami`\" >> .ver

    @echo \#define VIVI_COMPILE_HOST \"`hostname`\" >> .ver
    @echo \#define VIVI_COMPILER \"`$(CC) $(CFLAGS) -v 2>&1 | tail -1`\" >> .ver
    @mv -f .ver $@

# compile [init/version.c]
init/version.o: init/version.c include/compile.h
    $(CC) $(CFLAGS) -DUTS_MACHINE='"$(ARCH)"' -c $< -o $@

# compile [init/main.c]
init/main.o: init/main.c
    $(CC) $(CFLAGS) $(CFLAGS_KERNEL) $(PROFILING) -c $< -o $@

# create TAGS
TAGS:
    etags `find include -name '*.h'`
    find $(SUBDIRS) init -name '*.[ch]' | xargs etags -a

# Exuberant ctags works better with -I
tags:
    CTAGSF=`ctags --version | grep -i exuberant >/dev/null &&            \
        echo "-I __initdata,__exitdata,EXPORT_SYMBOL,EXPORT_SYMBOL_NOVERS"`;     \
    ctags $$CTAGSF `find include -name '*.h'` &&                    \
    find $(SUBDIRS) init -name '*.[ch]' | xargs ctags $$CTAGSF -a

#
# Section 10: default rules
#

include Rules.make


#

# Section 11: debug
#

debug:
    @echo "VIVIRELEASE: $(VIVIRELEASE)"
    @echo "ARCH: $(ARCH)"
    @echo "CONFIG_SHELL: $(CONFIG_SHELL)"
    @echo "CROSS_COMPILE: $(CROSS_COMPILE)"
    @echo "TOPDIR: $(TOPDIR)"
    @echo "SUBDIRS: $(SUBDIRS)"
    @echo "CONFIGURATION: $(CONFIGURATION)"
    @echo "DRIVERS: $(DRIVERS)"

    修改的幅度比较大,但是这样组织之后,清晰了很多,至少我是这样感觉。现在一般的命令处理有:

    make clean:清理中间生成文件,不包括配置文件

    make distclean:清理所有生成文件

    make config:文本配置,如果没有[.config],默认读取[arch/defconfig]

    make oldconfig: 文本配置,默认读取[.config]

    make menuconfig:图像配置,需要图形库的支持

    make debug:变量调试

    make vivi:编译,生成目标文件

    make myboard_config:假设我的配置保存为[arch/def-configs/myboard],那么此命令的结果是把该配置复制到[.config]。如果原来存在[.config],则把原来的[.config]更改为[.config.old].

    make myboard:假设我的配置保存为[arch/def-configs/myboard],那么此命令实现了自动化编译。首先清理所有的生成文件,然后把myboard读取到[.config],根据此配置编译生成最终的vivi目标文件。

    我常用的做法是:(1)make smdk2410_config(2)make menuconfig(3)make vivi(4)cp .config ./arch/def-configs/myboard.如果以后改变程序,而不改变配置,就可以执行make distclean.理解了整个过程,对这些就可以熟练应用了。也可以自己添加适合自己的编译命令。

    Makefile采用了主Makefile和子Makefile配合的方式,它们之间的联系如下:

    首先,读取主Makefile,主Makefile通过该"include arch/Makefile"完成包含,主要是一些相关的配置。至于其他的子Makefile,在执行make vivi时,有依赖关系linuxsubdirs,这个部分就是完成读取并完成所有其他子Makefile.具体就在下面语句。

linuxsubdirs: $(patsubst %, _dir_%, $(SUBDIRS))

# read sub Makefile in SUBDIRS
$(patsubst %, _dir_%, $(SUBDIRS)) :
    $(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C $(patsubst _dir_%, %, $@)
   

    所有的显示规则和隐式规则都在Rules.make里,也是用include引入,这样就形成了一条链,完美的管理着你的项目。另外,配置部分需要在另外一篇文档中总结。需要读scripts下的脚本,现在还没有读完。

    相对于vivi原始工程,做了一个patch。

文件: patch.rar
大小: 6KB
下载: 下载

评论

此博客中的热门博文

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...

【转】嵌入式开发中使用DDD进行调试

 在嵌入式程序开发过程中,程序员要进行大量的调试,以此验证程序的正确性,修改潜在的错误。调试器对于程序员来说是不可或缺的必备工具。在Linux环境中,有很多调试工具和调试辅助工具,例如GDB、XXGDB、RHIDE、XWPE、GVD和DDD等。其中,DDD是命令行调试器的图形前端,除了一般的程序调试功能以外,还具有交互式图形数据显示的功能。它在嵌入式应用开发中也十分出色。本文主要讲述DDD(Data Display Debugger)的使用方法。      1.认识DDD     GNU DDD是命令行调试程序,如GDB、DBX、WDB、Ladebug、JDB、XDB、Perl Debugger或Python Debugger的可视化图形前端。它特有的图形数据显示功能(Graphical Data Display)可以把数据结构按照图形的方式显示出来。DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。     DDD是开源软件,用户可以去 http://www.cs.tubs.de/softech/ddd/ 下载.rpm格式的DDD源码文件。      图1显示的是DDD的主窗口。它主要由选单栏、工具条、数据窗口、源文件窗口、机器码窗口、控制台和命令工具窗口等几部分组成。其中,数据窗口用于观察复杂的数据结构,在删除数据之后,显示仍然有效;源文件窗口显示源代码、断点和当前执行到达的位置,选择该窗口中的"Display"项,可以显示任意表达式的值;机...

【转】函数指针和指针函数

【函数指针】        在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址。可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指针变量称为函数指针。 1.函数指针定义 函数类型 (*指针变量名)(形参列表); "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少,后面的"形参列表"表示指针变量指向的函数所带的参数列表。 例如: int (*f)(int x); double (*ptr)(double x); 在定义函数指针时请注意:     函数指针和它指向的函数的参数个数和类型都应该是―致的; 函数指针的类型和函数的返回值类型也必须是一致的。 2.函数指针的赋值 函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名就行了。 例如, int func(int x);   /* 声明一个函数 */ int (*f) (int x);    /* 声明一个函数指针 */ f=func;            /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 3.通过函数指针调用函数 函数指针是通过函数名及有关参数进行调用的。 与其他指针变量相类似,如果指针变量pi是指向某整型变量i的指针,则*p等于它所指的变量i;如果pf是指向某浮点型变量f的指针,则*pf就等价于它所指的变量f。同样地,*f是指向函数func(x)的指针,则*f就代表它所指向的函数func。所以在执行了f=func;之后,(*f)和func代表同一函数。 由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。现在我们就讨论如何用函数指针调用函数,它应执行下面三步: 首先,要说明函数指针变量。 例如:int (*f)...