学习韦东山视频uboot之Makefile分析

本文深入解析了u-boot的编译流程和配置过程,包括Makefile的解析,依赖关系的生成,以及如何通过配置生成特定板级的u-boot固件。分析了rules.mk文件内容,解释了关键命令如sed和basename的使用,以及如何通过make命令定制编译选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rules.mk文件内容

_depend:    $(obj).depend

$(obj).depend:    $(src)Makefile $(TOPDIR)/config.mk $(SRCS)
        @rm -f $@
        @for f in $(SRCS); do \
            g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
            $(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
        done


        
我觉得,要看懂这些,需要在一开始把所有的变量都记录下来,后面再把所有的变量给展开,就好懂
很多了。

上面文件内容最难懂的一句是:

g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \

这里大概就是说,把basename $$f 得到的结果送到sed执行。
而sed的作用是把$$f的后缀(.xxx)改成(.o)

所以,rules.mk的内容就是:
为了生成_depend,需要依赖$(obj).depend
而为了生成$(obj).depend,需要依赖$(src)Makefile $(TOPDIR)/config.mk $(SRCS)
首先删除已有的目标
然后遍历SRCS(估计是每一个文件夹)
然后执行
把所有的x.x改成x.o
然后赋值给g
最后运行

$(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(OBJ)$$g $$f >> $@

我这里不是很明白$$是啥意思,从执行来看,就是个双指针,但是为什么这么做呢?
难道跟for语句有关?分析一下shell中的for语句先。
果然是这样,因为是数组,所以要用两个$$才能拿到变量值
所以就是说

$(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(OBJ)xxx.o xxx.x >> $@

这一句有2两个详细关键字,分别是
basename 和sed
关于命令跳到下面看,这里只看上面一句的意思,所以,首先把$$f变量的目录去掉,剩下文件名
传送给sed命令
分解一下$$f,首先f是一个变量,第一步获取$f,然后这个$f再作为一个变量,有点双指针的意思。

然后是:
sed -e 's/\(.*\)\.\w/\1.o/'basename 命令:返回一个字符串参数的基本文件名称
再terminal中测试一下 basename 命令得到一下结果

$ basename //hello/hello.c
hello.c

$ basename --help
Usage: basename NAME [SUFFIX]
  or:  basename OPTION... NAME...
Print NAME with any leading directory components removed.
If specified, also remove a trailing SUFFIX.

Mandatory arguments to long options are mandatory for short options too.
  -a, --multiple       support multiple arguments and treat each as a NAME
  -s, --suffix=SUFFIX  remove a trailing SUFFIX; implies -a
  -z, --zero           end each output line with NUL, not newline
      --help     display this help and exit
      --version  output version information and exit

Examples:
  basename /usr/bin/sort          -> "sort"
  basename include/stdio.h .h     -> "stdio"
  basename -s .h include/stdio.h  -> "stdio"
  basename -a any/str1 any/str2   -> "str1" followed by "str2"

GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
Full documentation at: <http://www.gnu.org/software/coreutils/basename>
or available locally via: info '(coreutils) basename invocation'

翻译一下上面的help意思
首先是语法:
basename NAME [SUFFIX]
或者是
basename OPTION... NAME...

意思:把NAME中的前导目录组件移除后再打印,如果如果有规定SUFFIX部分,同样需要移除掉
简单来说就是移除掉目录和SUFFIX指定的部分得到剩下的东西并返回
具体怎么用可以看Examples,简单直接sed命令:

Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

  -n, --quiet, --silent
                 suppress automatic printing of pattern space
  -e script, --expression=script
                 add the script to the commands to be executed
  -f script-file, --file=script-file
                 add the contents of script-file to the commands to be executed
  --follow-symlinks
                 follow symlinks when processing in place
  -i[SUFFIX], --in-place[=SUFFIX]
                 edit files in place (makes backup if SUFFIX supplied)
  -l N, --line-length=N
                 specify the desired line-wrap length for the `l' command
  --posix
                 disable all GNU extensions.
  -r, --regexp-extended
                 use extended regular expressions in the script.
  -s, --separate
                 consider files as separate rather than as a single continuous
                 long stream.
  -u, --unbuffered
                 load minimal amounts of data from the input files and flush
                 the output buffers more often
  -z, --null-data
                 separate lines by NUL characters
      --help     display this help and exit
      --version  output version information and exit

If no -e, --expression, -f, or --file option is given, then the first
non-option argument is taken as the sed script to interpret.  All
remaining arguments are names of input files; if no input files are
specified, then the standard input is read.

GNU sed home page: <http://www.gnu.org/software/sed/>.
General help using GNU software: <http://www.gnu.org/gethelp/>.


首先sed是一种流编辑器,可以搜索,可以替换,用这个命令前需要掌握一点正则表达式的基础。
用法:sed [OPTION]... {script-only-if-no-other-script} [input-file]...
以分析目标举例
sed -e 's/\(.*\)\.\w/\1.o/'
-e就是script
add the script to the commands to be executed
把脚本添加到命令中去并执行
大概就是说,按照这个正则表达式去匹配输入内容得到匹配的结果把。
s表示替换
所以把输入的字符串中符合‘\(.*\)\.\w’的替换成\1.o
首先用正则表达式来分解‘\(.*\)\.\w’
首先,把sed的中的字符集给移除掉。
sed中,\(..\)表示匹配字串,保存匹配的字符
所以,把\(xxx\)中的xxx保存到\1中
分为3部分
.*
开头以'('结尾以')'的任意除了换行符以外的所有字符
\.
\w
\. 表示匹配一个'.'字符
\w    匹配任何单个 "单词" 字符, 即字母, 数字或下划线. 这等同于 [a-zA-Z0-9_]. 
相反地, 大写的 \W 表示 "任何 非单词字符".
所以整个句子的意思就是,就是把a.c中的.c换成.o
例如main.c 变成 main.o
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
分析Makefile
编译uboot时,第一步是配置
make 100ask24x0_config
在Makefile中,可以查到这个目标:
 

100ask24x0_config    :    unconfig
    @$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0

MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG
SRCTREE        := $(CURDIR)

化简就是:

100ask24x0_config    :    unconfig
    mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0

mkconfig文件注释如下
APPEND=no    # Default: Create new config file
BOARD_NAME=""    # Name to print in make output

mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0

-gt  表示 大于
现在有6个,OK

$1 = 100ask24x0
没有--
没有-a
没有-n
所以跳过

while [ $# -gt 0 ] ; do
    case "$1" in
    --) shift ; break ;;
    -a) shift ; APPEND=yes ;;
    -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
    *)  break ;;
    esac
done


||表示条件如果为假则执行后面的语句,引申&&表示条件如果为真则执行后面的语句
由于BOARD_NAME = NULL,所以执行后面的语句
BOARD_NAME = 100ask24x0
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"

参数个数如果小于4或者大于6都退出,现在刚好6个,跳过

[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1

打印    "Configuring for ${BOARD_NAME} board..."

echo "Configuring for 100ask24x0 board..."

#
# Create link to architecture specific headers
#
如果SRCTREE不等于OBJTREE,执行then后面,否则执行else后面语句
分析Makefile开头时,

SRCTREE        := $(CURDIR)
OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))

OBJTREE表示如果BUILD_DIR存在则是BUILD_DIR,否则是CURDIR

截取Makefile中的BUILD_DIR语句
如果定义了O,则行下面语句,所以跳过

ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif

如果BUILD_DIR != NULL,执行下面的语句,所以也跳过

ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)

# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})

# Verify if it was successful.
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif # ifneq ($(BUILD_DIR),)

所以SRCTREE和OBJTREE都是NULL
由于SRCTREE和OBJTREE都是NULL,所以执行else后面的语句

if [ "$SRCTREE" != "$OBJTREE" ] ; then
    mkdir -p ${OBJTREE}/include
    mkdir -p ${OBJTREE}/include2
    cd ${OBJTREE}/include2
    rm -f asm
    ln -s ${SRCTREE}/include/asm-$2 asm
    LNPREFIX="../../include2/asm/"
    cd ../include
    rm -rf asm-$2
    rm -f asm
    mkdir asm-$2
    ln -s asm-$2 asm
else
    #进入include文件夹,删除asm,然后建立一个连接文件
    cd ./include
    rm -f asm
    ln -s asm-$2 asm
    #上面表示:ln -s asm-arm asm
fi

删除 asm-arm/arch文件夹
rm -f asm-$2/arch

$6长度为0或者%6为NULL执行then后面语句,否则则行else后面语句
$6 = s3c24x0,所以执行else后面语句

if [ -z "$6" -o "$6" = "NULL" ] ; then
    ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
    ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi

if [ "$2" = "arm" ] ; then
    #删除 asm-arm/proc
    rm -f asm-$2/proc
    #LNPREFIX为NULL所以下面简化为:ln -s proc-armv asm-arm/proc
    ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi

#
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk
echo "CPU    = $3" >> config.mk
echo "BOARD  = $4" >> config.mk

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk


输入以上参数到config.mk, >表示新建, >>表示追加,所以config的内容是:
ARCH    = arm
CPU        = arm920t
BOARD     = 100ask24x0
$5不为空且$5不为NULL执行后面,但是$5为NULL,所以跳过
同样,$6=s3c24x0,所以追加,最后config.mk的内容就是
ARCH    = arm
CPU        = arm920t
BOARD     = 100ask24x0
SOC     = s3c24x0    


APPEND为NO,执行else,新建一个config.h文件

#
# Create board specific header file
#
if [ "$APPEND" = "yes" ]    # Append to existing config file
then
    echo >> config.h
else
    > config.h        # Create new config file
fi


然后追加下面内容到config.h文件里

echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h

所以config.h的内容是:

/* Automatically generated - do not edit */
#include <configs/100ask24x0.h>

最后完成退出
exit 0

//////////////////////////////////////////////////////////////////////////////////
配置完,执行make
all:        $(ALL)
all依赖ALL
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
以下是Makefile的$(obj)内容,所以obj为空

# $(obj) and (src) are defined in config.mk but here in main Makefile
# we also need them before config.mk is included which is the case for
# some targets like unconfig, clean, clobber, distclean, etc.
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src

ifeq ($(CONFIG_NAND_U_BOOT),y)
NAND_SPL = nand_spl
U_BOOT_NAND = $(obj)u-boot-nand.bin
endif

如果CONFIG_NAND_U_BOOT == yes
但是Makefile中没有,所以为空所以上面命令化简为:

all:        u-boot.srec u-boot.bin System.map $(U_BOOT_NAND)


u-boot.srec:    u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@

u-boot.bin:    $(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

u-boot:        depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
        UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
        cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
            --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
            -Map u-boot.map -o u-boot

System.map:    u-boot
        @$(NM) $< | \
        grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
        sort > $(obj)System.map

上面把变量删除得到的
所以我们的目标得到的u-boot.bin是通过u-boot得到的

u-boot:        depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
        UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
        cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
            --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
            -Map u-boot.map -o u-boot
            


所以make主要执行了这个

UNDEF_SYM=`arm-linux-objdump -x \
    lib_generic/libgeneric.a \
    board/100ask24x0/lib100ask24x0.a \
    cpu/arm920t/libarm920t.a \
    cpu/arm920t/s3c24x0/libs3c24x0.a \
    lib_arm/libarm.a \
    fs/cramfs/libcramfs.a \
    fs/fat/libfat.a \
    fs/fdos/libfdos.a \
    fs/jffs2/libjffs2.a \
    fs/reiserfs/libreiserfs.a \
    fs/ext2/libext2fs.a \
    net/libnet.a \
    disk/libdisk.a \
    rtc/librtc.a \
    dtt/libdtt.a \
    drivers/libdrivers.a \
    drivers/nand/libnand.a \
    drivers/nand_legacy/libnand_legacy.a \
    drivers/usb/libusb.a \
    drivers/sk98lin/libsk98lin.a \
    common/libcommon.a \
    |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
    cd /home/inc/work/uboot_s3c2440/u-boot-1.1.6 && arm-linux-ld -Bstatic -T \
    /home/inc/work/uboot_s3c2440/u-boot-1.1.6/board/100ask24x0/u-boot.lds \
    -Ttext 0x33F80000  \
    $UNDEF_SYM cpu/arm920t/start.o \
        --start-group \
        lib_generic/libgeneric.a \
        board/100ask24x0/lib100ask24x0.a \
        cpu/arm920t/libarm920t.a \
        cpu/arm920t/s3c24x0/libs3c24x0.a \
        lib_arm/libarm.a \
        fs/cramfs/libcramfs.a \
        fs/fat/libfat.a \
        fs/fdos/libfdos.a \
        fs/jffs2/libjffs2.a \
        fs/reiserfs/libreiserfs.a \
        fs/ext2/libext2fs.a \
        net/libnet.a \
        disk/libdisk.a \
        rtc/librtc.a \
        dtt/libdtt.a \
        drivers/libdrivers.a \
        drivers/nand/libnand.a \
        drivers/nand_legacy/libnand_legacy.a \
        drivers/usb/libusb.a \
        drivers/sk98lin/libsk98lin.a \
        common/libcommon.a \
        --end-group 
        -L /home/inc/work/tool_s3c2440/gcc-3.4.5-glibc-2.3.6/bin/../lib/gcc/arm-linux/3.4.5 -lgcc \
        -Map u-boot.map -o u-boot


从上面命令可以看出,怎么组织代码,主要看连接脚本 u-boot.lds
在这里,后面存在一个:-Ttext = 0x33F80000

从连接文件可以得到,第一个文件是start.o,然后是boot_init.o
{
    第一个文件是start.o,然后是100ask24x0/boot_init.o
  cpu/arm920t/start.o    (.text)
      board/100ask24x0/boot_init.o (.text)
  *(.text)
}
所以分析源码先从这两个文件开始分析起来


 

本程序的Makefile分为3类: 1. 顶层目录的Makefile 2. 顶层目录的Makefile.build 3. 各级子目录的Makefile 一、各级子目录的Makefile: 它最简单,形式如下: EXTRA_CFLAGS := CFLAGS_file.o := obj-y += file.o obj-y += subdir/ "obj-y += file.o" 表示把当前目录下的file.c编进程序里, "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。 "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 注意: 1. "subdir/"中的斜杠"/"不可省略 2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用 3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项 二、顶层目录的Makefile: 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外, 主要是定义工具链前缀CROSS_COMPILE, 定义编译参数CFLAGS, 定义链接参数LDFLAGS, 这些参数就是文件中用export导出的各变量。 三、顶层目录的Makefile.build: 这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o 详细的讲解请看视频。 四、怎么使用这套Makefile: 1.把顶层Makefile, Makefile.build放入程序的顶层目录 在各自子目录创建一个空白的Makefile 2.确定编译哪些源文件 修改顶层目录和各自子目录Makefile的obj-y : obj-y += xxx.o obj-y += yyy/ 这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录 3. 确定编译选项、链接选项 修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项; 修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项; 修改各自子目录下的Makefile: "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 4. 使用哪个编译器? 修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-) 5. 确定应用程序的名字: 修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字 6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值