[1] 规则
目标: 依赖
[TAB]命令(命令名 参数 依赖 目标)
[2] 难点
1. 自动变量
作用域在当前规则中, Make给它自动赋值
$@ 目标
注意: 每一条规则只有一个目标
test test.o: test.c
[TAB]$(CC) $(CFLAGS) $< -o $@
等价于
test: test.c
[TAB]$(CC) $(CFLAGS) $< -o $@
test.o: test.c
[TAB]$(CC) $(CFLAGS) $< -o $@
$< 第一个依赖
$^ 所有依赖
2. 模式规则
目标和依赖使用%匹配符的规则
%.o: %.c
[TAB]$(CC) $(CFLAGS) $< -o $@
3. 自动推倒(自动找规则生成目标)
(1) 寻找生成目标的显示规则(自己写的非模式规则)
(2) 寻找生成目标的模式规则
(3) 寻找生成目标的隐式规则(make程序默认的模式规则)
[3] ARM工程
1. 目录
board 跟主板相关的配置文件和源代码(主板的初始化代码)
common 主程序
cpu cpu的启动代码
drivers 通用设备驱动
include 头文件
lib 库(通用算法)的源代码
2. 源文件
*.S 汇编源码
*.c C源码
*.h 头文件
config.mk 编译命令和编译参数
Makefile make脚本
map.lds 链接脚本
3. 编译
$ make
4. 目标文件
fs4412.bin binary格式的可执行文件
fs4412 ELF格式的可执行文件
fs4412.dis 反汇编文件
fs4412.map 详细符合表文件(链接过程文件)
System.map 符合表文件
5. 编译原理
1. 主Makefile
OBJS 启动代码编译出来的目标文件(*.o)
LIBS 非启动代码编译出来的静态库(lib*.a)(多个目标文件的打包)
2. 子Makefile
LIB 子目录下的源代码编译出来的目标文件,打包成的静态库文件
START 启动代码编译出来的目标文件
SOBJS 汇编代码编译出来的目标文件
COBJS C代码编译出来的目标文件
6. 如何添加模块到工程?
1. 添加源代码
2. 添加子目录
3. 添加源代码到子目录
4. 给子目录添加Makefile
拷贝别的子目录下的Makefile,修改如下变量的值:
LIB 子目录下的源代码编译出来的目标文件,打包成的静态库文件
START 启动代码编译出来的目标文件
SOBJS 汇编代码编译出来的目标文件
COBJS C代码编译出来的目标文件
5. 修改顶层目录下的Makefile, 添加新的子目录生成的目标到工程:
OBJS 启动代码编译出来的目标文件(*.o)
LIBS 非启动代码编译出来的静态库(lib*.a)(多个目标文件的打包)
[4] u-boot工程
1. 目录
cpu(cpu/cpu名称(arm_cortexa8)/...) CPU的启动代码
board(board/芯片厂家(samsung)/主板名(fsc100)/... 启动代码中需要使用的核心硬件的驱动(内存初始化、flash的读驱动、soc中部分硬件的初始化)
common 主程序和命令实现代码
lib_arm(lib_generic) 某个架构的CPU需要使用的公共代码(校验算法CRC 压缩算法 标准C库)
net 网络协议代码
drivers 通用设备驱动
fs 文件系统实现代码
disk 磁盘分区驱动
tools 工具
doc 文档(作者写的)
2. 源文件
Makefile make脚本文件
config.mk 编译命令和编译参数
README 整个工程的说明
mkconfig 工程配置的shell脚本
rules.mk 产生Makefile规则(hello.o: hello.c hello.h stdio.h)
3. 特点
1. 支持多个CPU
2. 支持多个主板
3. 支持非常多的设备驱动
4. 工程管理和代码比较混乱
任何一款产品,都不可能用到u-boot提供的所有代码, 所以编译过程应该分成:
1. 选择需要的源代码--配置
2. 编译需要的源代码--编译
4. 编译
1. 配置
$ make fsc100(主板名)_config
2. 编译
$ make
3. 清除编译
$ make clean
4. 清除配置
$ make clobber
5. 目标文件
u-boot ELF格式的可执行程序
u-boot.bin binary格式的可执行程序
u-boot.map 详细符号表(有链接过程)
System.map 符号表文件
6. 配置原理
1. 过程
《u-boot配置流程图.bmp》 注意:2010版本的u-boot没有boards.cfg
2. 结果
include/config.mk 选择源代码目录
include/config.h 选取目录下的源代码文件和从选取的源代码文件中选取一部分源代码
#include <configs/fsc100.h> 主板的配置文件(通过宏定义配置)
#include <config_cmd_default.h> 通过里面的宏定义控制命令是否编译到最终的u-boot可执行文件
(config_cmd_all.h) u-boot支持的所有命令的配置项(宏定义)
3. 原理
1. $ make fsc100_config命令的实现原理
根据配置命令($ make fsc100_config),来看主Makefile,即在主Makefile中寻找目标为fsc100_config规则:
SRCTREE := $(CURDIR)
...
MKCONFIG := $(SRCTREE)/mkconfig
...
fsc100_config: unconfig
[tab]@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 fsc100 samsung s5pc1xx
mkconfig fsc100 arm arm_cortexa8 fsc100 samsung s5pc1xx
2. 选择目录
include include/config.mk
(....
CPU = arm-cortex_a8(目录名)
)
....
LIBS += cpu/$(CPU)/lib$(CPU).a 选择arm-cortex_a8 cpu目录
3. 选择目录中的源代码文件
例: 在include/configs/fsc100.h定义:
#define CONFIG_DRIVER_DM9000 1 如果宏定义不存在, 就不会被转换成相应的Makefile变量
在make时, 它会被转换成Makefile变量,存放在include/autoconf.mk中:
CONFIG_DRIVER_DM9000 = y
在主Makefile中,会包含include/autoconf.mk,因此在子Makefile中也可以见到CONFIG_DRIVER_DM9000变量
在driver/net/Makefile中,会按照如下方式使用:
LIB = libnet.a
...
...
COBJS-$(CONFIG_DRIVER_DM9000) += dm9000x.o
...
...
COBJS = $(COBJS-y)
...
OBJS = $(COBJS)
...
$(LIB): .depend $(OBJS)
[TAB]$(AR) $(ARFLAGS) $@ $(OBJS)
4. 选取文件中的部分源码(条件编译)
例: 在include/configs/fsc100.h头文件中,存在如下宏定义:
#define CONFIG_CMD_PING 1 (注意: 命令配置项最好放入include/config_cmd_default.h)
在common/cmd_net.c文件中,存在如下代码:
#if defined(CONFIG_CMD_PING)
...(ping命令实现代码)
#endif
5. 给源代码提供数据(宏替换)
例: 在include/configs/fsc100.h头文件中,存在如下宏定义:
#define CONFIG_DM9000_BASE 0X88000000
在driver/net/dm9000x.c文件中会使用这个宏定义:
...
dm9000_probe(void)
{
...
printf("dm9000 i/o: 0x%x, id: 0x%x \n", CONFIG_DM9000_BASE, id_val);
...
}
...
7. 编译原理
1. 总体编译过程
见《u-boot编译过程图2013.01.bmp》
2. 顶层目录下的Makefile
OBJS 启动代码编译出来的目标文件
LIBS 非启动代码编译出来的目标文件
3. 子目录下的Makefile
LIB 最终生成的lib*.a文件(只有非启动代码会被打包到这个文件)
COBJS *.C编译出来的目标文件
SOBJS *.S编译出来的目标文件
8. 如何添加模块到工程?
# 包含编译命令和编译参数
include $(TOPDIR)/config.mk
# 总目标(非启动代码)
LIB := $(obj)libuart.a
# COBJS-$(CONFIG_USB_TTY) += usbtty.o
# C语言源代码编译出来的目标文件
COBJS := uart.o #$(sort $(COBJS-y))
# 汇编代码编译出来的目标文件
#SOBJS := ...
# 源代码文件,用在rules.mk中
SRCS := $(COBJS:.o=.c)
# 等价于COBJS
OBJS := $(addprefix $(obj),$(COBJS)) # $(addprefix $(obj),$(SOBJS))
# 防止LIB变量中有多个目标
all: $(LIB)
# 将目标文件打包成静态库
$(LIB): $(obj).depend $(OBJS)
$(AR) $(ARFLAGS) $@ $(OBJS)
#########################################################################
# 产生.depend文件
# defines $(obj).depend target
include $(SRCTREE)/rules.mk
# 包含.depend文件(*.o : *.c *.h 规则)
sinclude $(obj).depend
#########################################################################
1. 添加源代码
2. 添加子目录
3. 添加源代码到子目录
4. 给子目录添加Makefile
拷贝别的子目录下的Makefile,修改如下变量的值:
LIB 子目录下的源代码编译出来的目标文件,打包成的静态库文件
START 启动代码编译出来的目标文件
SOBJS 汇编代码编译出来的目标文件
COBJS C代码编译出来的目标文件
5. 修改顶层目录下的Makefile, 添加新的子目录生成的目标到工程:
OBJS 启动代码编译出来的目标文件(*.o)
注意: 有些版本,不支持将启动代码放置的cpu以外目录,这时需要修改如下规则:
$(OBJS): depend
# $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
$(MAKE) -C $(dir $(subst $(obj),,$@)) $(if $(REMOTE_BUILD),$@,$(notdir $@))
# $(dir $(subst $(obj),,$@))
$(dir A) 求A的路径,如: A是"drivers/uart/uart.o" 路径为"drivers/uart"
$(subst $(obj),,$@) obj指定目标代码的路径(当源代码和目标代码不放在同一目录时)
将@中的每个值的$(obj)部分,替换成空
# $(if $(REMOTE_BUILD),$@,$(notdir $@)) 当REMOTE_BUILD变量值非空时,整个函数的值为@变量的值
.... 空时, $(notdir $@)的结果
LIBS 非启动代码编译出来的静态库(lib*.a)(多个目标文件的打包)
注意:
1. 如何控制源代码文件是否编译到u-boot?
2. 如何空源代码文件的一部分编译到u-boot?
3. 如何为源代码文件提供数据?
开关或数据都必须在include/configs/主板名.h
9. 源代码阅读软件
vi(编辑或查看) + ctags(搜索定义)
1. 建立ctags索引文件
$ ctags -R (在工程的顶层目录下执行)
2. 查找定义
把光标移动到要查找定义的标识符,然后按ctrl + ]
3. 回退
ctrl + O
sourceinside
先建立工程, 添加所有文件到工程
10. 源代码分析
1. 自启动
1.1 核心硬件(初始化或驱动)
1.1.1 CPU(A8)
异常中断向量表(包含配置异常中断向量表的位置)
cpu/arm-cortex_A8 35 - 56行
(ALU + 控制器)传统的CPU 关闭中断, 进入SVC模式
cpu/arm-cortex_A8 106 - 109行
MMU 和 cache 关闭
cpu/arm_cortexa8/start.S
227 /*
228 * 关闭tlb和icache
229 */
230 mov r0, #0 @ set up for MCR
231 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
232 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
233
234 /*
235 * 关闭mmu和cache
236 */
237 mrc p15, 0, r0, c1, c0, 0
238 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
239 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
240 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
241 orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
242 mcr p15, 0, r0, c1, c0, 0
1.1.2 SOC
如果有看门狗 关闭或喂狗
board/samsung/fsc100/lowlevel_init.S
57 /* 关闭看门狗 */
58 ldr r0, =S5PC100_WATCHDOG_BASE @0xEA200000
59 orr r0, r0, #0x0
60 str r5, [r0]
如果需要加快代码的执行速度 初始化PLL
board/samsung/fsc100/lowlevel_init.S
96 bl system_clock_init
UART 初始化(驱动)发送即可
board/samsung/fsc100/lowlevel_init.S
101 bl uart_asm_init
如果需要用到DMA 初始化(驱动)DMA
board/samsung/fsc100/lowlevel_init.S
104 bl dma_init
如果从Nandflash启动 初始化nandflash控制器管脚
board/samsung/fsc100/lowlevel_init.S
106 bl nand_pin_mux
108 bl nand_asm_init
1.1.3 BOARD
初始化DDR
board/samsung/fsc100/lowlevel_init.S
118 bl mem_ctrl_asm_init
如果从Nandflash启动 驱动nandflash的读
cpu/arm_cortexa8/start.S
158 ldr sp, =(0x22000000)
159 bl copy_uboot_to_ram
1.2 软件(创建C语言执行环境)
1.2.1 自拷贝到DDR
cpu/arm_cortexa8/start.S
158 ldr sp, =(0x22000000)
159 bl copy_uboot_to_ram
1.2.2 栈内存分配(需要给SVC、abort、undefined instruction 三个模式分配栈空间)
1.2.3 .BSS段清空
1.2.4 跳转到内存运行
1.2.5 预留内存给malloc函数
1.2.6 预留内存给全局数据结构
2. 命令执行
2.1 接收命令输入
2.2 解析命令
2.3 执行命令
协议栈(网络协议栈(net)/USB协议栈(drivers/usb)/...)
文件系统(fs)
分区驱动(disk)
硬件驱动(drivers)
3. OS启动(bootm)
建立linux系统的启动环境:
3.1 关闭中断, 进入SVC
3.2 关闭MMU和cache
3.3 R0 0
R1 arch number(mach type id) 主板id
R2 内核参数指针(atags list)
3.4 传参给内核
3.5 跳转到内核的运行地址去启动