Chinaunix首页 | 论坛 | 博客
  • 博客访问: 114438
  • 博文数量: 34
  • 博客积分: 1430
  • 博客等级: 上尉
  • 技术积分: 257
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-04 08:37
文章分类

全部博文(34)

文章存档

2011年(1)

2010年(1)

2009年(32)

我的朋友

分类:

2009-06-09 09:26:01

Blob 学习报告(一)
── 基础篇
张成跃(challengezcy@163.com)
之所以写这份学习报告,主要是受杜云海学长的三篇《ARM 学习报告》的影响,在此对
杜学长表示诚挚的谢意。另外觉得把学习过程写下来即是对自己学习的一个阶段的总结,也
为下一阶段的学习做积累和指导。
由于本人是一位初学者,加上初次写类似的这种报告,其中错误再所难免,希望各位能
够及时指出、讨论,共同进步。
开始阅读本文之前,先下载一份源码:blob-2.0.5-pre2.tar.gz

一、概述
在开始具体讨论之前,我们先看看这个解压之后的blob-2.0.5-pre2 文件夹里到底都有些
什么。
$ tar zxvf blob-2.0.5-pre2.tar.gz
$ cd blob-2.0.5-pre2
$ tree //此命令用来输出目录树结构,如下所示
//(对几个主要目录和文件做简单注释)
.
|-- AUTHORS //此文件记载的是开发者等相关信息
|-- COPYING //此文件在配置过程中自动生成
|-- ChangeLog //同上
|-- INSTALL //同上
|-- Makefile.am //此文件将在后面做重点介绍
|-- Makefile.in //自动生成
|-- NEWS //当代码打包发布时用,对理解源码没有用处
|-- README //同上
|-- RELEASE-NOTES //同上
|-- acconfig.h //此文件将在后面做介绍
|-- aclocal.m4 //自动生成
|-- configure //同上
|-- configure.in //此文件也将在后面做重点介绍
|-- doc //里面的几个.txt 文件是对blob 代码的一些基本说明,建议阅读之
| |-- Makefile.am
| |-- Makefile.in
| |-- commandlist.txt
| |-- diag.txt
| `-- porting.txt
|-- include //包含的头文件
| |-- Makefile.am
| |-- Makefile.in
| `-- blob
| |-- Makefile.am
| |-- Makefile.in
| |-- arch
| | |-- Makefile.am
| | |-- Makefile.in
| | |-- assabet.h
| | |-- badge4.h
| | |-- brutus.h
| | |-- clart.h
| | |-- h3600.h
| | |-- idr.h
| | |-- jornada720.h
| | |-- lart.h
| | |-- nesa.h
| | |-- pleb.h
| | |-- shannon.h
| | `-- system3.h
| |-- arch.h
| |-- command.h
| |-- command_hist.h
| |-- config.h.in
| |-- errno.h
| |-- error.h
| |-- flash.h
| |-- icache.h
| |-- init.h
| |-- lcd.h
| |-- led.h
| |-- linux.h
| |-- main.h
| |-- md5.h
| |-- md5support.h
| |-- memory.h
| |-- memsetup.h
| |-- param_block.h
| |-- partition.h
| |-- reboot.h
| |-- sa1100.h
| |-- sa1111.h
| |-- serial.h
| |-- stamp-h.in
| |-- terminal.h
| |-- time.h
| |-- types.h
| |-- util.h
| |-- uucodec.h
| `-- xmodem.h
|-- src //相关的源代码主要集中于此,我们要重点攻克的对象
| |-- Makefile.am
| |-- Makefile.in
| |-- blob
| | |-- Makefile.am
| | |-- Makefile.in
| | |-- amd32.c
| | |-- assabet.c
| | |-- badge4.c
| | |-- bootldrpart.c
| | |-- brutus.c
| | |-- chain.S
| | |-- chkmem.c
| | |-- clart.c
| | |-- clock.c
| | |-- commands.c
| | |-- debug.c
| | |-- flash.c
| | |-- flashasm.S
| | |-- h3600.c
| | |-- idr.c
| | |-- initcalls.c
| | |-- intel16.c
| | |-- intel32.c
| | |-- jornada720.c
| | |-- lart.c
| | |-- ledasm.S
| | |-- linux.c
| | |-- main.c
| | |-- memory.c
| | |-- memsetup-sa1100.S
| | |-- memsetup-sa1110.S
| | |-- nesa.c
| | |-- nullflash.c
| | |-- param_block.c
| | |-- partition.c
| | |-- pleb.c
| | |-- reboot.c
| | |-- rest-ld-script.in
| | |-- shannon.c
| | |-- stack.S
| | |-- start-ld-script
| | |-- start.S
| | |-- system3.c
| | |-- testmem.S
| | |-- testmem2.S
| | |-- trampoline.S
| | |-- uucodec.c
| | `-- xmodem.c
| |-- diag
| | |-- Makefile.am
| | |-- Makefile.in
| | |-- assabet.c
| | |-- badge4.c
| | |-- brutus.c
| | |-- clart.c
| | |-- command_hist.c
| | |-- commands.c
| | |-- h3600.c
| | |-- idr.c
| | |-- initcalls.c
| | |-- jornada720.c
| | |-- lart.c
| | |-- lcd.c
| | |-- ld-script
| | |-- main.c
| | |-- nesa.c
| | |-- pleb.c
| | |-- regs-sa11x0.c
| | |-- shannon.c
| | |-- stack.S
| | |-- start.S
| | `-- system3.c
| `-- lib
| |-- Makefile.am
| |-- Makefile.in
| |-- command.c
| |-- error.c
| |-- icache.c
| |-- init.c
| |-- led.c
| |-- md5.c
| |-- md5support.c
| |-- memcpy.c
| |-- reboot.c
| |-- serial-sa11x0.c
| |-- serial.c
| |-- strlen.c
| |-- strncmp.c
| |-- strncpy.c
| |-- strtou32.c
| |-- terminal.c
| |-- time.c
| `-- util.c
|-- tools //对此文件夹中的内容做一下了解
| |-- Makefile.am
| |-- Makefile.in
| |-- README
| |-- config.guess
| |-- config.sub
| |-- install-sh
| |-- missing
| |-- mkinstalldirs
| |-- rebuild
| `-- rebuild-gcc
`-- utils
|-- Makefile.am
|-- Makefile.in
|-- README
|-- build
| |-- Makefile.am
| |-- Makefile.in
| |-- README
| |-- build_Makefile
| `-- build_all
`-- mkparamblock
|-- Makefile.am
|-- Makefile.in
`-- mkparamblock.c
从上面的目录树中,我们可以清楚的发现每个文件夹中都有Makefile.am 和Makefile.in
文件,Makefiel.in 是自动生成的,而根目录下除了那些与源代码不相关和自动生成的文件
外,留下的就是configure.in,acconfig.h。下面对这三个文件做一简单的说明:
1. configure.in:这是最重要的文件,整个配置、编译、安装过程都由它来主导。
2. Makefile.am:命令automake 根据它来生成Makefile.in,再由./configure 把
makefile.in 变成最终的Makefile。
3. acconfig.h:命令autoheader 根据它来生成config.h.in(上面的目录树中在blob
文件夹中),再由./configure 把config.h.in 变成最终的config.h
二、Configure.in 文件
首先谈一下此文件的作用:简单的说此文件是configure 文件的老爸──configure 文
件是由命令autoconf 根据configure.in 文件自动生成,具体的生成步骤在后面做介绍。在
详细介绍configure.in 文件的内容之前,对configure.in 文件做一概述。
请参考GNU Autoconf文档:
或相关的中文资料
2.1 概述:
configure.in 文件主要包含了一些对软件包需要的或者可以使用的系统特征进行测试
的宏。这些宏基本可分为两类:一类以AC 开头,表示由autoconf 提供;一类以AM 开头,表
示由automake 提供。除了少数特殊情况外,此文件中调用宏的顺序并不重要。但在进行任何
测试之前必须包含一个AC_INIT 宏调用,以及在结尾处包含一个AC_OUTPUT 宏调用。此文件
的大体结构是:
AC_INIT(file)
checks for programs
checks for libraries
checks for header files
checks for typedefs
checks for structures
checks for compiler characteristics
checks for library functions
checks for system services
AC_OUTPUT([file...])
2.2 对configure.in 文件的具体注释说明
dnl Process this file with autoconf to produce a configure script. -*- m4 -*-
//dnl 类似于注释语句,以下出现的dnl 语句不再说明。
AC_REVISION([$Id: configure.in,v 1.34 2002/01/07 14:58:16 erikm Exp $])
/ *宏AC_REVISION把删除了美元符或者双引号的修订标记复制到configure脚本中.如:
*在configure.in 中有:
* AC_REVISION($Revision:1.30$)
*在configure 中就会产生:
* #! /bin/sh
* # From configure.in Revision:1.30
* /
AC_INIT(src/blob/start.S)
//AC_INIT 宏用来检查源码所在的路径,括号中的源码文件可随意,但要存在。
AC_CONFIG_AUX_DIR(tools)
//该宏告诉configure 使用tools 目录中的install-sh,config.sub,config.gess 等辅助配
//置脚本。
PACKAGE=blob
BLOB_MAJOR_VERSION=2
BLOB_MINOR_VERSION=0
BLOB_MICRO_VERSION=5-pre2
BLOB_VERSION=$BLOB_MAJOR_VERSION.$BLOB_MINOR_VERSION.$BLOB_M ICRO_VERSION
VERSION=$BLOB_VERSION
//类似于变量赋值
AM_INIT_AUTOMAKE($PACKAGE, $VERSION)
//该宏描述了要生成的软件包的名字和版本号。当使用make dist 命令时,在最上层目录下
//就会生成blob-2.0.5-pre2.tar.gz 软件发行包
AM_CONFIG_HEADER(include/blob/config.h)
//该宏告诉automake 我们所用的配置文件。使用本宏时,应在同一目录下创建"stamp-h.in"
//文件,可以为空。另外注意:config.h 文件是通过./configure 由config.h.in 文件变来
//的。而config.h.in 是由autoheader 命令根据acconfig.h 生成的。
AM_MAINTAINER_MODE
//该宏为configure 添加一个"--enable-maintainer-mode"选项。
AC_CANONICAL_HOST
//该宏检测主机类型,运行该宏需要文件config.guess 等
AC_CHECK_PROGS(CC, arm-linux-gcc gcc, echo)
AC_CHECK_PROGS(OBJCOPY, arm-linux-objcopy objcopy, echo)
AC_CHECK_PROGS(RANLIB, arm-linux-ranlib ranlib, echo)
AC_CHECK_PROGS(AR, arm-linux-ar ar, echo)
//该宏在系统路径中依次寻找以空格分隔的程序列表中的文件,如"arm-linux-gcc gcc",
//如果找到,就把该程序名赋给前面的变量,如"CC"。
AC_PROG_CC
//该宏检查系统所使用的C 编译器
if test "x$ac_cv_prog_gcc" != "xyes" ; then
AC_MSG_WARN("C compiler is not gcc. This may lead to problems!");
AC_MSG_WARN("Trying to continue with $CC ...");
Fi
//ac_cv_prog_gcc 是一个缓存变量名。缓存变量的名字应该符合如下格式:
// package-prefix_cv_ value-type_ specific-value[_ additional-options] 各个部分为:
package-prefix
包或者组织的缩写,对于由发布的Autoconf宏使用的缓存值,它是`ac'。
_cv_
表明本shell 变量是一个缓存值。
value-type
关于缓存值类别的惯例,以生成一个合理的命名系统。在Autoconf中使用的值在宏名
中列出。
specific-value
指明本测试应用于缓存值类的那个成员。例如,那个函数(`alloca')、程序(`gcc')
或者输出变量(`INSTALL')。
additional-options
给出应用本测试的特定成员的任何特殊行为。例如,`broken'或者`set'。如果没有
用,名字的这个部分可能被忽略掉。
//宏AC_MSG_WARN 告知configure 的使用者可能出现的问题,configure 继续向后执行
AC_PROG_RANLIB
//如果在包中创建了任何库,就需该宏。关于如何在包中创建库主要是在Makefile.am 中设
//置相关参数。请参考相关资料。
AC_PROG_INSTALL
//该宏用来设定INSTALL变量。如果在当前目录中找到一个与BSD兼容的install程序,就
//把变量INSTALL设置成到该程序的路径。否则,就把INSTALL设置成` dir/install-sh -c',
//检查由AC_CONFIG_AUX_DIR指明的目录(或者它的缺省目录)以确定dir。
AC_PROG_LN_S
//如果`ln -s'能够在当前文件系统中工作(操作系统和文件系统支持符号连接),就把输出
//变量LN_S设置成`ln -s',否则就把它设置成`ln'。
AC_ARG_WITH(board, [ --with-board=NAME Name of the target board
Valid names are:
assabet Intel Assabet
neponset Intel Assabet with Neponset board
: :
Default board is lart],
board_name="$withval",
board_name="lart")
// AC_ARG_WITH (package, help-string [, action-if-given [, action-if-not-given]])
如果用户以选项`--with- package'或者`--without- package'调用configure,就运行
shell 命令action-if-given。如果两个选项都没有给出, 就运行shell 命令
action-if-not-given。名字package给出了本程序应该与之协同工作的其它软件包。它应该
仅仅由字母、数字和破折号组成。
shell命令action-if-given可以通过shell变量withval得到选项的参数,该变量的值实
际上就是把shell变量with_ package的值中的所有`-'字符替换为`_'而得的。如果你愿意,
可以使用变量with_ package。
参数help-string是对选项的描述如果需要给出更多的细节,help-string可能多于一行。
只要确保
`configure --help'中按列的排列就可以了。不要在求助字符串中使用tab。不过需要用`['和
`]'包围它以生成前导空格。
AC_MSG_CHECKING(target board)
case "$board_name" in
assabet)
board_name="Intel Assabet"
AC_DEFINE(ASSABET)
BLOB_PLATFORM_OBJ="assabet.o"
AC_MSG_WARN([Please check assabet memory config in arch/assabet.h])
BLOB_FLASH_OBJS="intel32.o"
DIAG_PLATFORM_OBJ="assabet.o"
use_cpu="sa1110"
use_lcd="no"
;;
: :
*)
AC_MSG_RESULT(unknown)
AC_MSG_ERROR([Unknown board name, bailing out])
;;
esac
// AC_MSG_CHECKING告知用户configure正在检查特定的特征。本宏打印一条以`checking '
//开头,以`...' 结尾, 如果运行configure给出了选项`--quiet'或者选项`--silent',本
//宏什么也不打印。AC_DEFINE(ASSABET)定义宏ASSABET。在configure.in中使用到的宏,
//都应该在文件acconfig.h 中声明,一般用#undef 来声明。
// AC_MSG_RESULT告知用户测试的结果, 本宏应该在AC_MSG_CHECKING之后调用,并且
// result-description 应该完成由AC_MSG_CHECKING所打印的消息。
// AC_MSG_ERROR告知用户一条使configure不能完成的错误。
AC_DEFINE_UNQUOTED(BOARD_NAME, "${board_name}")
//该宏类似于AC_DEFINE,但还要对variable和value进行三种shell替换(每种替换只进
//行一次):变量扩展(`$'),命令替换(``'),以及反斜线传义符(`\')。
AC_SUBST(MEMSETUP)
//从一个shell变量创建一个输出变量。让AC_OUTPUT把变量variable替换到输出文件中(通
//常是一个或多个`Makefile')。这意味着AC_OUTPUT将把输入文件中的`@ variable@'实例
//替换成调用AC_OUTPUT时shell变量variable的值。
AC_ARG_WITH(linux-prefix,[ --with-linux-prefix=PFX Prefix where the ARM Linux
sources live],
linux_prefix="$withval",
linux_prefix="/usr/src/linux")
dnl Do some sanity checks
AC_MSG_CHECKING([if the Linux source tree in $linux_prefix is sane])
if test ! -d "$linux_prefix/include" ; then
AC_MSG_RESULT([no])
AC_MSG_ERROR([$linux_prefix doesn't look like a configured Linux source tree.])
AC_MSG_ERROR([Please supply a proper prefix with the --with-linux-prefix flag])
exit -1
fi
if test ! -f "$linux_prefix/include/asm/setup.h" ; then
AC_MSG_RESULT([no])
AC_MSG_ERROR([The Linux source tree in $linux_prefix is not configured.])
AC_MSG_ERROR([Please run "make lart_config ; yes no | make old_config" in the])
AC_MSG_ERROR([Linux source tree and retry.])
exit -1
fi
AC_MSG_RESULT([yes])
//这段容易理解,不罗索了。其中-d 测试目录,-f 测试文件
CFLAGS=`echo $CFLAGS -I${linux_prefix}/include`
//注意等号后面用得是反单引号,如果有什么不明白,请参考shell 编程。-I 选项表示包含
//后面的目录为查找目录。
AC_ARG_ENABLE(clock-scaling,
[ --enable-clock-scaling Enable support for clock scaling (SA1100 only)],
[clock_scaling_flag=$enable_clock_scaling],
[clock_scaling_flag=no])
// AC_ARG_ENABLE (feature, help-string [, action-if-given [, action-if-not-given]])
//如果用户以选项`--enable- feature'或者`--disable- feature'调用configure,就运行
//shell命令action-if-given。如果两个选项都没有给出,就运行shell命令
// action-if-not-given。名称feature表示可选的用户级功能。它应该仅仅由字母、数字和
//破折号(dashes)组成。shell命令可以通过访问shell变量enableval来得到选项的参//

AC_C_INLINE
//如果C编译器支持关键字inline,就什么也不作。如果C编译器可以接受__inline__或者
//__inline,就把inline定义成可接受的关键字,否则就把inline定义为空。
AC_LANG_SAVE
if test "x$ac_cv_prog_gcc" = "xyes" ; then
dnl gcc is the easiest C compiler
warning_CFLAGS="-Wall"
fi
AC_LANG_RESTORE
// AC_LANG_SAVE 在堆栈中记录当前的语言, 不改变当前使用的语言。在需要暂时地切换到
//其它特殊语言的宏之中使用本宏和AC_LANG_RESTORE。
CFLAGS=`echo $CFLAGS | sed 's/\ *-g\ */\ /'`
LDFLAGS=`echo $LDFLAGS | sed 's/\ *-g\ */\ /'`
//其中的sed 's/\ *-g\ */\ /'命令作用是将-g 用空格代替。
CFLAGS=`echo $CFLAGS -march=armv4 -mtune=strongarm1100 -fomit-frame-pointer
-fno-builtin -mapcs-32 -nostdinc`
LDFLAGS=`echo $LDFLAGS -static -nostdlib`
OCFLAGS="-O binary -R .note -R .comment -S"
//-march 该参数用来确定所使用的ARM 体系结构的目标名,编译器根据该名字来确定所使用
//的汇编指令集。
//-mtune 使用该选项对有些ARM 体系的实现可以得到更好的优化。
//-fomit-frame-pointer 不要把用不到的函数的帧指针放在寄存器里。
//-fno-builtin 不使用不是以__builtin__开始的内置函数。
//-nostdinc 仅在当前目录或用选项-I 包含的目录中寻找相关的头文件。
//-nostdlib 当连接时不使用系统库中的启动文件或库
//-O 确定输出文件的格式
//-R .note 在输出文件中删除.note 段
//-S 在输出文件中不复制原文件中的动态载入和符号表信息。
好了,到此为止configure.in 文件里的基本解释完毕。
三、下面开始Makefile.am 之旅
3.1 ../Makefile.am
宏SUBDIRS保存了需要进行各种创建的子目录列表,且在SUBDIRS中提到的目录必须是当前目
录的直接子目录。
宏EXTRA_DIST 所包含的文件是指需要被发布,但没有在自动规则之中
宏CLEANFILES 清除任何以~结尾的临时文件
3.2 ../src/Makefile.am
同上注释。
这里的srcdir 变量是由命令automake 自动生成的,一般为当前目录。
3.3 ../src/blob/Makefile.am
该文件是我们要重点注释的对象,因为几乎所有的编译工作都是在该文件的指导之下完成的。
还是按照顺序来:
宏bin_PROGRAMS定义需要被编译、连接的目标。多个输出结果在bin_PROGRAMS后指出就可以
了。注意要有空格分隔。
宏INCLUDE 一个`-I'选项的列表,可以包含需要的特殊目录。另外变量top_builddir 和
top_srcdir 和上面提到的srcdir 一样,也是由命令automake 自动生成的,其值一般为当前
所编译文件的最上层目录。
宏BUILT_SOURCE 该宏罗列这类文件:一个可以被称作"源文件"的文件(例如一个C `.h'
文件)实际上是从其它文件中派生出来的。
如文件中的:
BUILT_SOURCES = \
rest-ld-script
rest-ld-script: rest-ld-script.in
$(CC) -x c-header -undef -nostdinc ${INCLUDES} -E $< | sed 's/^#.*//' > $@
其中-x c-header 等选项请查GNU gcc 手册。
blob_start_elf32_SOURCES = \
start.S \
ledasm.S \
testmem.S
//*_SOURCES 指定产生* 时所需要的源代码。如果它用到了多个源文件,那么请使用空格
//符号将它们隔开。
EXTRA_blob_start_elf32_SOURCES = \
memsetup-sa1100.S \
memsetup-sa1110.S
//指memsetup-sa1100.S、memsetup-sa1110.S 可能被有条件地包含在
//blob_start_elf32。
blob_start_elf32_DEPENDENCIES = \
@MEMSETUP@ \
start-ld-script
//*_ DEPENDENCIES 实现以下功能:有时候,是否创建一个程序依赖于不属于那个程序的
//某些其它目标,而其它目标就包含在该宏中。注意这里的MEMSETUP 在acconfig.h 中定义。
blob_start_elf32_LDFLAGS += \
-Wl,-T,${srcdir}/start-ld-script
//*_LDFLAGS 为连接器提供的Stripping(`-s')选项和其他各种选项。其中-Wl 表示将选
//项传递-T,${srcdir}/start-ld-script 给连接器。-T 表示使用后面的连接脚本命令。
blob_start_elf32_LDADD += \
@MEMSETUP@ \
-lgcc
//*_LDADD 被用来确定* 要连接的其它目标文件或库。编译* 时,全局变量LDADD 将被暂
//时覆盖,被赋以后面的值。其中-lgcc 是指链接器将连接GCC 的支持库libgcc.a。
blob: blob-start blob-rest
rm -f $@
dd if=blob-start of=$@ bs=1k conv=sync
dd if=blob-rest of=$@ bs=1k seek=1
chmod +x $@
//这段语句很重要,特别是在理解start.S代码"add r0, r0, #0x400"时,先打个伏笔。其
//中if=blob-start 表示输入文件为blob-star,of=$@表示输出文件为blob,$@是特殊变量指
//的是目标即blob, bs=1k 表示数据块大小为1k 字节即1024 字节。
//dd if=blob-rest of=$@ bs=1k seek=1 这一行命令中seek=1 表示从第1 个数据块后开始
//写输出数据。这一行的作用实际是将blob-rest 文件写入到blob 文件的第1024 字节开始
//处。这样便完成了将第一阶段代码放入0-1k 处,将第2 阶段代码放入1k 后。由于第一阶
//段代码不到1024 字节,所以第一阶段代码与第2 阶段代码之间有空隙,一般会自动填0。
好了,到此为此该文件基本介绍完毕。
至于其它文件夹中的Makefile.am 自己对着上面介绍和参考相关资料,应该可以理解了。
三、./acconfig.h
下面简单介绍一下acconfig.h 这个文件。
3.1 @TOP@ 和@BOTTOM@
如果`./acconfig.h'包含了字符串`@TOP@',autoheader 就把在包含`@TOP@' 的行之前
的所有行复制到它生成的文件的开头。相似地,如果`./acconfig.h'包含了字符串
`@BOTTOM@', autoheader 就把那一行之后的所有行复制到它生成的文件的末尾。这两个字
符串的任何一个都可以被忽略,也可以被同时忽略。
3.2 宏定义
凡是在configure.in 文件中出现的变量,除了configure.in 的内部变量之外,其余变
量必须在这个文件里定义,使用格式:#undef .
好了,在结束本文之前,介绍一下,这些自动生成文件的具体步骤,也就是在linux 下做程序开
发的一般步骤.
1) 建立工程目录结构,并完成各个功能模块代码的开发.
2) 在顶层目录文件夹里建立configure.in 文件.有两种建立方法:一种是直接手工编辑,另
外一种是使用autoscan 命令,它将会生成configure.scan 文件,可以作为configure.in
文件的基本摸板,然后在这摸板上做修改,同时将文件改为configure.in.同时如果需要
的话,编写acconfig.h 文件.
3) 在各个文件夹里(包括子文件夹)里建立Makefile.am 文件.
4) 运行aclocal 命令,将会生成aclocal.m4.
5) 运行autoheader 该命令会根据configure.in 和acconfig.h 来生成config.h.in。
6) 运行autoconf 该命令会根据configure.in 生成configure 文件.
7) 运行automake –add-missing 该命令会根据各个Makefile.am 文件生成各个
Makefile.in 文件.同时生成一些相关的其它文件,比如install.sh,mkinstalldirs 等.
8) 运行./configure 脚本,它根据生成的Makefile.in 文件在各个目录下生成Makefile 文
件。
9) 好了,如果一切顺利的话,就可以使用make 命令了,来生成我们想要的可执行文件.
结束语
这篇文章没有涉及到blob 原代码的设计结构和思想,而仅仅是对blob 文件夹内的文件
及其相互关系的一个初步认识,我想在下一篇<>中来讨论
blob 原代码的设计结构和思想,请您指教.谢谢!

Blob 学习报告(二)
——探索篇
张成跃(challengezcy@163.com )
在《Blob 学习报告(一)》中,我们重点介绍了configure.in 和Makefile.am 等文件
以及相关文件之间的相互生成关系。在这篇文章中,我们将重点讨论blob 的设计结构和思
想。在对此进行讨论之前先看./src/blob 目录中的两个文件:start-ld-script、
rest-ld-script.in。
一、链接脚本
链接脚本文件是用来控制输入、输出目标文件格式。脚本文件中主要包含一些简单关键
词定义和SECTIONS、MEMORY 命令的定义。有关它的详细描述请参考ld 手册:命令语言、ELF
格式说明等资料。至于这两个文件具体是怎么用的,请结合《Blob 学习报告(一)》参看此
目录下的Makefile.am 文件。这里对ld 和链接文件作一概述并简单地解释一下这两个脚本。
1、1 概述
ld,即GNU 的连接工具,用于将各目标文件合并在一起,并重新安排他们的数据以及符
号的引用,常常是程序编译的最后一步。
Ld scripts 即ld 脚本。ld 脚本的主要目的是要描述怎样将输入文件的各段印象到输
出文件中去。它控制输出文件在内存的布局情况。
Ld scripts是使用连接命令语言(Linker Command Language)写成的文件;是一种用
AT&T的连接编辑命令语言的超集写成的文件,用来在连接的整个过程中提供显式的,全局的
控制。用户可以明确地要求使用ld链接程序将几个模块组合成一个单独的可执行文件。可以
使用"ld --verbose"命令看到默认使用的ld链接脚本。
1、2 start-ld-script 和rest-ld-script.in
start-ld-script:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
// OUTPUT_FORMAT命令为输出文件使用的BFD格式命名。这里采用地格式为:
//OUTPUT_FORMAT(DEFAULT, BIG, LITTLE) 带有三个参数以使用不同的基于'-EB'和'-EL'
//的命令行选项的格式。如果'-EB'和'-EL'选项都没有使用, 那输出格式会是第一个参数
//DEFAULT, 如果使用了'-EB',输出格式会是第二个参数BIG, 如果使用了'-EL', 输出格式
//会是第三个参数, LITTLE。比如,有如下链接脚本命令:
//OUTPUT_FORMAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
//表示缺省的输出文件格式是elf32-bigmips, 但是当用户使用'-EL'命令行选项的时候,
//输出文件就会以elf32-littlemips格式创建。当然对于当前的脚本命令,不管用或不用
//命令行选项,都是以elf32-littlearm的格式创建。
OUTPUT_ARCH(arm)
// OUTPUT_ARCH指定一个特定的输出机器架构, 这个参数是BFD库中使用的一个名字。
ENTRY(_start)
//设置_start为执行程序入口点。程序入口点的设置有以下几种方法,优先级从高到低,高
//优先级将覆盖低优先级:
//1)用命令行选项-e指定;
//2)就是这里提到的用ENTRY(_start)指定;
//3) 如果程序中出现start符号的话;
//4)如果有.text段的话,那么由其起始地址指定;
//5) 物理地址0x0;
SECTIONS //SECTIONS是链接脚本中最重要的部分,具体请参考Command Language手册
{
. = 0x00000000;
// . 是链接器的一个特殊变量,指明当前计数输出的位置,只递增。
. = ALIGN(4);
// ALIGN(4)返回4字节对齐的地址。
.text : { *(.text) }
// 定义输出文件中的text段,它将包括输入目标文件中所有.text段,注意输入文件的先后

// 序(在Makefile.am中指定),其顺序影响输出文件的布局。
. = ALIGN(4);
.rodata : { *(.rodata) }
// 只读数据段。
. = ALIGN(4);
.data : { *(.data) }
// 可读写数据段
. = ALIGN(4);
.got : { *(.got) }
// 保存着全局的偏移量表。
. = ALIGN(4);
.bss : { *(.bss) }
// 未初始化的数据段。
}
rest-ld-script.in:
#include
#include
//引用头文件,为了下面BLOB_ABS_BASE_ADDR的引用。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_trampoline)
SECTIONS
{
. = BLOB_ABS_BASE_ADDR;
. = ALIGN(4);
.text : {
__text_start = .;
//定义变量_text_start,并且将当前计数输出的位置值赋给该变量
*(.text)
__text_end = .;
}
. = ALIGN(4);
.rodata : {
__rodata_start = .;
*(.rodata)
__rodata_end = .;
}
. = ALIGN(4);
.data : {
__data_start = .;
*(.data)
__data_end = .;
}
. = ALIGN(4);
.got : {
__got_start = .;
*(.got)
__got_end = .;
}
. = ALIGN(4);
.commandlist : {
//在include/blob/command.h文件中的54行有:
//#define __command __attribute__((unused, __section__(" .commandlist")))
//该宏的具体含义在后面作介绍,其中__section__表示把相关代码放到.commandlist段中
__commandlist_start = .;
*(.commandlist)
__commandlist_end = .;
//在文件src/lib/command.c中
//49行: extern u32 __commandlist_start;
//63行: commands = (commandlist_t *) &__commandlist_start;
//对于__commandlist_end,类似的语句存在于src/lib/command.c文件中。
}
. = ALIGN(4);
.initlist : {
// 在文件include/blob/init.h中第43行有:
//#define __init __attribute__((unused, __section__(".initlist")))
__initlist_start = .;
*(.initlist)
__initlist_end = .;
//同样在文件src/lib/init.c有代码:
//extern u32 __initlist_start;
//extern u32 __initlist_end;
//(initlist_t *)&__initlist_start,
//(initlist_t *)&__initlist_end,
}
. = ALIGN(4);
.exitlist : {
//在文件include/blob/init.h中52行:
//#define __exit __attribute__((unused, __section__(".exitlist")))
__exitlist_start = .;
*(.exitlist)
__exitlist_end = .;
}
. = ALIGN(4);
.ptaglist : {
//在文件include/blob/param_block.h中146行:
//#define __ptag __attribute__((unused, __section__(".ptaglist")))
__ptagtable_begin = .;
*(.ptaglist)
__ptagtable_end = .;
}
/* the BSS section should *always* be the last section */
. = ALIGN(4);
.bss : {
__bss_start = .;
/* first the real BSS data */
*(.bss)
/* and next the stack */
. = ALIGN(4);
__stack_start = .;
*(.stack)
__stack_end = .;
__bss_end = .;
}
}
好了,介绍完这两个链接脚本文件,下面开始我们的blob 代码分析吧!
二、Blob 代码结构分析
网上的这篇文章《Blob在S3C44B0 上的移植》对Blob的总体结构和运行过程作了概要的
分析、描述。请大家参考。为了本篇报告的完整性,这里会做一些重复性地描述。
2、1 鸟瞰Blob
Blob文件夹中的文件确实不少,在学习报告一中目录结构都列了4 页。其实Blob的整
体结构相对来说比较简单,这里借用一下《Blob在S3C44B0 上的移植》文章中的这张图:
对,它的整体架构就这么简单,三个文件:start.S, trampoline.S, main.c。这三个
文件将所有相关的功能文件都联系了起来,这主要就要感叹内部程序设计的巧妙了。
另外,在学习报告一中对./src/blob/Makefile.am 文件的分析中,我们可以清楚地发
现Blob 代码分为两个阶段。注意到这段语句了吗?
blob: blob-start blob-rest
rm -f $@
dd if=blob-start of=$@ bs=1k conv=sync
dd if=blob-rest of=$@ bs=1k seek=1
chmod +x $@
其中blob-start就是代码执行的第一阶段,它的主角就是start.S。它存在于从地址0x00
开始的flash空间中,当系统加电时,这段代码就从地址0x00开始在flash空间中运行,主要
完成对必要的硬件初始化,然后将第二阶段blob-reset代码复制到内存指定的地址开始处。
复制完后,就由语句"ldr r0, BLOB_START mov pc, r0"将控制权转给第二阶段,
blob-rest。第二阶段的主角当然就是main.c了,trampoline.S代码其实很简单,顾名思义,
实现"蹦床"的作用。
好了,对blob的整体运行有个大概的了解之后,我们来分而治之。
2、2 start.S
该文件是和具体的体系结构相关的,不同的体系结构要做不同的修改、调整。就针对当
前的代码来说,它主要完成的工作是:屏蔽中断,设置CPU速率,初始化LED,设置MEMORY、
检测内存等。其中最主要的任务就是将blob代码复制到内存中。看到这段语句:
adr r0, _start
/* relocate the second stage loader */
add r2, r0, #(64 * 1024) /* blob maximum size is 64kB */
add r0, r0, #0x400 /* skip first 1024 bytes */
ldr r1, BLOB_START
/* r0 = source address
* r1 = target address
* r2 = source end address
*/
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
变量BLOB_START就是blob复制到内存中的起始地址。开始看这段代码,其中令人不解的地方
就是常数0x400,为什么_start地址加上0x400就是第二阶段blob-reset的代码呢?对了,如
果你注意到报告一中的"伏笔",那问题就迎刃而解了。这里稍做重复:
dd if=blob-rest of=$@ bs=1k seek=1
这一行命令中seek=1 表示从第1 个数据块后开始写输出数据。这一行的作用实际是将
blob-rest 文件写入到blob 文件的第1024 字节开始处,也就是0x400的地方。
2.3 trampoline.S和main.c
trampoline.S将未初始化的数据段清零,然后设置堆栈指针,然后使用语句:bl main
跳到main函数。注意其中的_bss_start、_bss_end、_stack_end的定义与赋值在
reset-ld-script.in中。
好了,到了main函数,局面打开了。
2.3.1 init_subsystems()函数
进到main函数,首当其冲的就是init_subsystems()函数。若是你首次跟踪该函数,那
你一定会被两个for循环语句弄地不知所谓了。别急,咱们回头看看在reset-ld-script.in
的注释中关于.initlist段的注释。
知道了变量__initlist_start、__initlist_end为何物。另外,三个宏定义:#define
INIT_LEVEL_MIN (0)、#define INIT_LEVEL_MAX (99)、#define INIT_MAGIC (0x496e6974),
加上该结构体:
#define __init __attribute__((unused, __section__(".initlist")))
#define __initlist(fn, lvl) \
static initlist_t __init_##fn __init = { \ //__init_##fn:表示用fn替换##fn
magic: INIT_MAGIC, \ //若fn为init_commands,则替换后
callback: fn, \ //有__init_init_commands
level: lvl }
我想对两个for语句所做的事也可以略猜一二了。.initlist是占据内存中连续的一段,而它
的基本组成单位就是结构体initlist_t,而每个结构体里面包含三个元素,分别是:magic、
callback、level,变量magic是一个定值,可以不理它了;变量level相当于一个优先级,
就是确定初始化时先执行那些优先级高的函数,这样第一个for循环就可以理解了。从下面
的语句中:
typedef void(*initfunc_t)(void);
typedef struct {
u32 magic;
initfunc_t callback;
int level;
} initlist_t;
我们可以看出:callback是个函数指针,它也是第二个for语句的主角。
为了便于理解,把.initlist段的结构画一下:
__initlist_start
magic
initlist_t callback
level 某个函数的执行
代码
initlist_t
__initlist_end
通过上面的介绍,对两个for语句应该有所认识了。然而,大家可能会产生另外一个疑
问:为什么结构体initlist_t都会连续地被放到.initlist段中呢?对了,这就是该语句的
功劳:
#define __init __attribute__((unused, __section__(".initlist")))
这里的__attribute__、unused、__section__都是GNU编译器的保留字。
__attribute__:表示属性,也就是赋予它所修饰的变量或函数后面指定的属性;
Unused:表示该变量或函数代码的执行过程中并不一定会被用到;
__section__(".initlist"):指将其所修饰的变量或函数编译进.initlist段。
那么两个for循环语句到底做了什么呢?请看:
src/lib/command.c:78:__initlist(init_commands, INIT_LEVEL_OTHER_STUFF);
src/blob/flash.c:165:__initlist(init_flash, INIT_LEVEL_OTHER_STUFF + 1);
src/blob/initcalls.c:49:__initlist(serial_default_init, INIT_LEVEL_INITIAL_HARDWARE);
src/blob/initcalls.c:50:__initlist(enable_icache, INIT_LEVEL_INITIAL_HARDWARE);
src/blob/initcalls.c:51:__initlist(led_init, INIT_LEVEL_INITIAL_HARDWARE);
src/blob/initcalls.c:52:__initlist(TimerInit, INIT_LEVEL_OTHER_HARDWARE);
src/blob/assabet.c:58:__initlist(init_assabet_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/assabet.c:78:__initlist(assabet_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/brutus.c:55:__initlist(init_brutus_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/brutus.c:66:__initlist(brutus_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/badge4.c:65:__initlist(init_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/badge4.c:82:__initlist(init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/clart.c:58:__initlist(init_clart_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/clart.c:69:__initlist(clart_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/h3600.c:118:__initlist(init_h3600_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/h3600.c:133:__initlist(h3600_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/idr.c:52:__initlist(init_idr_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/idr.c:61:__initlist(idr_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/jornada720.c:78:__initlist(init_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/jornada720.c:103:__initlist(init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/lart.c:67:__initlist(init_lart_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/lart.c:78:__initlist(lart_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/nesa.c:62:__initlist(init_nesa_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/nesa.c:73:__initlist(nesa_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/pleb.c:48:__initlist(lock_pleb_led, INIT_LEVEL_INITIAL_HARDWARE + 1);
src/blob/pleb.c:72:__initlist(init_pleb_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/pleb.c:83:__initlist(pleb_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/shannon.c:61:__initlist(init_shannon_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/shannon.c:72:__initlist(shannon_init_hardware, INIT_LEVEL_DRIVER_selectION);
src/blob/system3.c:99:__initlist(init_system3_flash_driver, INIT_LEVEL_DRIVER_selectION);
src/blob/system3.c:110:__initlist(system3_init_hardware, INIT_LEVEL_DRIVER_selectION);
上面列表中的函数都是两个for语句可能做的,但具体会做哪几个,由编译时的配置决定。
一般执行最前面的(公共的)6个和后面两个和体系结构相关的:***_flash_driver、
***_init_hardware。在举具体的例子之前,咱们先看看它的那几个宏定义到底是什么,体
会一下level变量。在include/blob/init.h中:
#define INIT_LEVEL_DRIVER_selectION (0)
#define INIT_LEVEL_INITIAL_HARDWARE (10)
#define INIT_LEVEL_OTHER_HARDWARE (30)
#define INIT_LEVEL_OTHER_STUFF (40)
现在咱们选取assabet这种体系结构,然后执行按照优先级的高低来看看for语句做了什
么。根据该语句:
__initlist(init_assabet_flash_driver, INIT_LEVEL_DRIVER_selectION)
和插图,我们知道第二个for语句的item->callback()调用了init_assabet_flash_driver
函数,然后调用了assabet_init_hardware,若跟踪这两个函数会发现它们都很简单,主要
就是两个赋值语句:
flash_driver = &intel32_flash_driver;

serial_driver = &sa11x0_serial_driver;
然后按顺序会调用serial_default_init、enable_icache、led_init、init_commands、
init_flash 和TimerInit。
这几个函数只要我们肯花时间一定能把它们给搞明白,在做什么,这里就不具体跟踪介
绍了。这里主要想介绍一下enable_icache这个函数调用,因为它里面用到了C和汇编的混合
编程。函数如下:
void enable_icache(void)
{
register u32 i;
/* read control register */
asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i));
/* set i-cache */
i |= 0x1000;
/* write back to control register */
asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i));
}
其中asm是内嵌汇编的标志。MRC 从协处理器传送一个单一的字并把它放置到ARM 寄存器
Rd 中,上面的指令也就是把该字放在某个寄存器中,同时传给变量(i),这里i做为输出变量;
MCR 传送ARM 寄存器Rd 的内容到协处理器,第二个语句中i作为输入变量。关于具体的细
节请参考网上《gcc中的内嵌汇编语言》等相关文章。
2.3.2 .commandlist段
main执行完init_subsystems()函数后,一直到咱们熟悉的语句:
SerialOutputString("Autoboot in progress, press any key to stop ");
为止,都不难理解。如果此时按下任意键就进入了blob等待用户输入命令的界面。下面列举
了可能使用的命令:
src/lib/command.c:261:__commandlist(help, "help", helphelp);
src/blob/commands.c:38:__commandlist(reset_terminal, "reset", resethelp);
src/blob/commands.c:39:__commandlist(reboot, "reboot", reboothelp);
src/blob/linux.c:80:__commandlist(boot_linux, "boot", boothelp);
src/blob/main.c:320:__commandlist(Download, "download", downloadhelp);
src/blob/main.c:402:__commandlist(xdownload, "xdownload", xdownloadhelp);
src/blob/main.c:489:__commandlist(Flash, "flash", flashhelp);
src/blob/main.c:543:__commandlist(SetDownloadSpeed, "speed", speedhelp);
src/blob/main.c:642:__commandlist(PrintStatus, "status", statushelp);
src/blob/main.c:708:__commandlist(Reload, "reload", reloadhelp);
src/blob/reboot.c:68:__commandlist(reblob, "reblob", reblobhelp);
src/blob/chkmem.c:229:__commandlist(ChkMem, "chkmem", chkmemhelp);
src/blob/clock.c:98:__commandlist(SetClock, "clock", clockhelp);
src/blob/debug.c:118:__commandlist(CmdMemcpy, "memcpy", memcpyhelp);
src/blob/debug.c:216:__commandlist(Poke, "poke", pokehelp );
src/blob/debug.c:312:__commandlist(Peek, "peek", peekhelp );
src/blob/debug.c:378:__commandlist(dump, "dump", dumphelp );
src/blob/debug.c:441:__commandlist(BitChange, "bitchg", bitchghelp );
src/blob/debug.c:505:__commandlist(Call, "call", callhelp);
src/blob/system3.c:170:__commandlist( cmd_download_file, "dlfile", downloadhelp );
src/blob/system3.c:215:__commandlist( cmd_flash_write, "fwrite", flashwritehelp );
src/blob/system3.c:252:__commandlist( cmd_flash_erase, "ferase", flasherasehelp );
这些命令在内存中的组织格式和上面介绍的.initlist段完全相同,这里不再重复。这里简
单的介绍一下它的命令执行机制:
(1) 函数GetCommand(commandline, MAX_COMMANDLINE_LENGTH, 600)获取用户输入的命
令及其参数;
(2) 函数parse_command(commandline))中:
i) 函数parse_args(cmdline, &argc, argv)将命令和参数分开,记录在指针数组
argv中;
ii) 由函数strncmp(cmd->name, argv[0], len)在.commandlist段中找到相关的执
行代码,然后由语句cmd->callback(argc, argv)调用相应命令所对应的函数。
三结束语
到此为止,blob的结构基本清楚了。希望上面的分析能给像我一样的初学者起到抛砖
引玉的作用。在了解了blob的结构后,接下来的任务就是去细细地品味.commandlist段中的
每个函数的具体实现了。比如boot命令所引出的boot_linux函数、download命令所引出的
Download函数等等,都不失为一顿丰盛的午餐。如果有兴趣,咱们一起探讨,学习。
部分参考的网址:
http://sanecat.blogchina.com/sanecat/1777309.html

阅读(968) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~