c语言、汇编、脚本、Makefile、Kconfig、设备树
装windows:BIOS界面、运行系统内核、安装驱动、装软件
移植就是给一个开发板装一个系统:学完这门课之后我们都是嵌入式的网管
嵌入式平台:引导程序(u-boot)、uImage、写驱动(改驱动)、第三方源码(相当于一个应用软件)
* 什么是移植?软件系统移植、硬件系统移植
一个平台的代码应用到另一个不同体系的平台上,需要做的工作就是移植
在移植过程中:
- 虚拟内存空间分配:用户空间、内核空间(文件系统,驱动,内存管理,网络协议栈,设备管理,中断,进程管理)
- 上层的open和内核的open有什么关系?
struct file_operations
{
int (*open)(struct *file,);
}
交叉编译器
- x86 arm 交叉编译器:在x86平台编写代码,然后通过交叉编译器编译后生成了适用于arm平台的二进制代码
- arm-none-linux-gnueabi-gcc
编译过程:
- 预处理 gcc -E 1.c -o 1.i 或 cpp(预处理器) 1.c -o 1.i
- 编译 gcc -S 1.i -o 1.s 或 cc1(编译器) 1.i -o 1.s
- 汇编 gcc -c 1.s -o 1.o 或 as(汇编器) 1.s -o 1.o
- 链接 gcc 1.o 或 ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o 1 1.o /usr/lib/i386-linux-gnu/crt1.o
- /usr/lib/i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.6/crtbegin.o -lc
- /usr/lib/gcc/i686-linux-gnu/4.6/crtend.o /usr/lib/i386-linux-gnu/crtn.o
- 链接器调用.o文件,.o文件中包含分段信息。
- .data .text .bss .rodata
nm 二进制文件 显示二进制文件中符号表的信息
显示的内容包含3列:
内存地址 属于哪个分段 符号名称
大写字母代表全局
小写字母代表局部
T D B R W
void fun() __atteibute__((weak));
//将fun函数变成弱标号,如果没有调用的话不会报错,编译器会直接忽略掉fun相关链接操作
strip 二进制文件 清除二进制文件中的符号 清除符号表的目的是为了节省资源
什么是符号?函数名或者变量名
符号什么作用? 符号是给链接器使用的或者说链接器通过符号的名字来找到了代码分段的位置
链接器在生成可执行文件时要使用符号,如果符号被清除,那么链接器失效。所以strip不要对中间文件进行瘦身。
在操作开发板前要先对u-boot进行配置。
u-boot有两种模式:交互模式,自启动模式.
- 在配置时要进入到交互模式中,一旦配置好之后使用自启动模式.
- 在交互模式中的命令操作:注意这里的命令不是shell命令。
设置环境变量内容:
- setenv 环境变量名 内容
- 例如:setenv serverip 172.21.1.250
删除环境变量:
- setenv 环境变量名
setenv bootargs root=/dev/nfs nfsroot=172.21.1.250:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=开发板的ip地址
//使用nfs服务,通过nfs服务挂载/rootfs,创建了init进程
setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000
//tftp 41000000 uImage 在u-boot中通过tftp命令从Ubuntu的/tftpboot目录下下载uImage文件到开发板的偏外内存41000000地址处
//三星手册给的偏外内存最大地址范围40000000 - A0000000 1.5G 我们实际用的是40000000 - 8000000 1G
//bootm 内核镜像地址 文件系统镜像地址 设备树二进制文件地址 这三个地址顺序不能改变.
//本质:1、类似于go命令有跳转到指定地址的作用
2、可以给内核传递参数
//2.4 2.6 3.0不支持设备树 ———————— 3.1x可以选择是否支持设备树
u-boot移植:
配置过程:
- 1、自己去百度
- 2、每个源码顶层目录下都会有一个README文件,查看README文件知道配置方法为:make 板子名称_config
- 我们华清的板子叫做FS4412,这个板子包含了——SOC,外设.
- 其中SOC还有分类,exynos就是SOC中的某一类,我们FS4412使用的SOC是exynos系列的芯片,这个芯片系列三星公司规定必须以origen为模板。
- 所以配置时可以执行 make origen_config
- 为什么要配置?
- 因为使用了make命令所以会用到Makefile文件。
- 在使用一个大型Makefile时,一定要先确定入口,千万别从第一行开始读。
- 通过make origen_config 可以知道当前的Makefile入口是origen_config(也就是说我们要去寻找目标或者伪目标叫做origen_config)
Makefile的基本机制:
- Makefile的基本机制:显示规则、隐式规则、变量、条件语句、函数
- 显示规则语法:
- 目标文件:依赖文件
- 生成目标文件的命令
779 %_config:: unconfig
780 @$(MKCONFIG) -A $(@:_config=)
查找变量的方法:
- 1、/变量名
- 2、命令模式下 按#
- 3、通过ctags -R生成tags文件,然后ctrl ]
- 4、make -p Makefile > 随便一个文件名
$(@:_config=) 以$@为基础添加一个规则,而这个规则的作用就是将_config替换为空,所以最终%_config会变成origen
@$(MKCONFIG) -A $(@:_config=) ==> @顶层目录/mkconfig -A origen
* 文件系统移植:根文件系统的制作以及文件系统镜像的制作
line=`egrep -i "^[[:space:]]*${2}[[:space:]]" boards.cfg`
line=origen arm armv7 origen samsung exynos
$1 $6
//-i忽略大小写
//[[:space:]]空格或者TAB
set $line 根据变量的内容重置$#的值,当前$#=6
57 CONFIG_NAME="${1%_config}" ==> CONFIG_NAME=origen
59 [ "${BOARD_NAME}" ] || BOARD_NAME="${1%_config}" ==> BOARD_NAME=origen
61 arch="$2" ==> arm
62 cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'` ==> armv7
63 spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'` spl_cpu=
64 if [ "$4" = "-" ] ; then
65 board=${BOARD_NAME}
66 else
67 board="$4" ==> board = origen
68 fi
69 [ $# -gt 4 ] && [ "$5" != "-" ] && vendor="$5" ==> samsung
70 [ $# -gt 5 ] && [ "$6" != "-" ] && soc="$6" ==> exynos
97 echo "Configuring for ${BOARD_NAME} board..."
113 cd ./include
114 rm -f asm
115 ln -s ../arch/${arch}/include/asm asm
123 ln -s ${LNPREFIX}arch-${soc} asm/arch <==> ln -s arch-exynos asm/arch
//当我们创建软链接时有一种用法是:如果当前目录下不存在源文件,会到目标路径下去寻找是否有源文件
128 ln -s ${LNPREFIX}proc-armv asm/proc
//到此为止我们发现产生了3个软连接文件。无论换成什么平台什么架构我们只需要关心这些软链接文件就可以,至于软链接文件存放的路径是随平台的改变而改变的。
134 ( echo "ARCH = ${arch}"
135 if [ ! -z "$spl_cpu" ] ; then
136 echo 'ifeq ($(CONFIG_SPL_BUILD),y)'
137 echo "CPU = ${spl_cpu}"
138 echo "else"
139 echo "CPU = ${cpu}"
140 echo "endif"
141 else
142 echo "CPU = ${cpu}"
143 fi
144 echo "BOARD = ${board}"
145
146 [ "${vendor}" ] && echo "VENDOR = ${vendor}"
147 [ "${soc}" ] && echo "SOC = ${soc}"
148 exit 0 ) > config.mk
在config.mk中存放5个变量的赋值:
ARCH = arm
CPU = armv7
BOARD = origen
VENDOR = samsung
SOC = exynos
config.mk文件会被顶层目录的Makefile引用,然后将里面的变量变成全局
154 BOARDDIR=${vendor}/${board} ==> samsung/origen
164 > config.h 创建一个空的config.h头文件
echo "#define CONFIG_SYS_ARCH \"${arch}\"" >> config.h
174 echo "#define CONFIG_SYS_CPU \"${cpu}\"" >> config.h
175 echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h
176
177 [ "${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h
178
179 [ "${soc}" ] && echo "#define CONFIG_SYS_SOC \"${soc}\"" >> config.h
180
181 cat << EOF >> config.h
182 #define CONFIG_BOARDDIR board/$BOARDDIR
183 #include <config_cmd_defaults.h>
184 #include <config_defaults.h>
185 #include <configs/${CONFIG_NAME}.h>
186 #include <asm/config.h>
187 #include <config_fallbacks.h>
188 #include <config_uncmd_spl.h>
189 EOF
将一些宏定义和系统头文件存放到include/config.h头文件中
配置u-boot的功能:产生了3个软链接,include/config.mk include/config.h
常用的Makefile命名:GNUmakefile makefile Makefile
.mk .Linux .AIX
编译过程:
- make all
428 all: $(ALL-y) $(SUBDIR_EXAMPLES)
//ALL-y = u-boot.bin 所以后续要去以u-boot.bin为目标
411 ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map
443 $(obj)u-boot.bin: $(obj)u-boot
444 $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
445 $(BOARD_SIZE_CHECK)
//要生成u-boot.bin必须事先生成u-boot,所以要搜索u-boot目标
570 $(obj)u-boot: depend \
571 $(SUBDIR_TOOLS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
572 $(GEN_UBOOT)
248 OBJS := $(addprefix $(obj),$(OBJS) $(RESET_OBJS-))
236 OBJS = $(CPUDIR)/start.o
B=
A=$(B)
//ifdef A 判断的是变量A是否有内容,如果有内容成立,没有内容不成立
LDSCRIPT=arch/arm/cpu/u-boot.lds
561 GEN_UBOOT = \
562 UNDEF_LST=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
563 sed -n -e 's/ .*(_u_boot_list_.*)/-u\1 /p'|sort|uniq`;\
564 cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) \
565 $$UNDEF_LST $(__OBJS) \
566 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
567 -Map u-boot.map -o u-boot
// sed基本功能用来字符串替换的
// \1的作用是用来标记的,标记的是前面的括号内部的内容
// -u\1作用就是用-u替换标记前面的内容
/ /所以替换完成后是 -u_u_boot_list_.*
// -u\1的用法:sed -n -e 's/.*(_u_boot)_list_.*/-u\1' 替换后变成-u_u_boot_list_.*
// \1-u用法:sed -n -e 's/.*_u_boot-u'
// $(@F) <==> $(notdir $@)
验证:
- abc=/home/linux/Makefilex
- info=$(notdir $(abc))
start:
echo $(info)
最终结果为Makefilex,无论有没有这个文件,我们关心的是能否去掉文件前的路径
$(LDFLAGS_$(@F)) 转化完成后为 LDFLAGS_u-boot
LDFLAGS_u-boot = -pie -T $(obj)u-boot.lds $(LDFLAGS_FINAL) -Ttext $(CONFIG_SYS_TEXT_BASE)
-pie -T u-boot.lds为了告诉链接器,根据u-boot.lds的内容哪些分段是需要链接的哪些分段是需要抛弃的。
312 LDFLAGS_FINAL = -Bstatic 所有内容进行静态链接
-Ttext 0x43e00000 一旦进行链接后,u-boot.lds中的00000000地址会被替换成0x43e00000
--start-group $(__LIBS) --end-group 指定一组库的二进制文件
上面一堆操作我们重点关心链接器链接时用到哪些内容?
- 1、分段,符号,分段和符号是一堆.o文件提供的
- 2、用到哪些分段u-boot.lds指定的
分段相对于符号来讲是一个整体,分段是包含符号的。
u-boot.map会告诉链接器,在链接时使用到的符号对应的地址是什么
链接最终的结果生成的u-boot可执行文件,再通过objcopy将u-boot转化为u-boot.bin
理论来讲这个u-boot.bin可以烧写到开发板上了。但是三星板子不允许。
* 我们最终用的u-boot-fs4412.bin包含u-boot.bin、bl1、bl2、填充代码、trustzone
开发板——SOC、外设
- SOC内部——CPU(cortex-a9)、外设控制器、IROM、IRAM
- 我们的SOC三星公司给取名为exynos4412
- 在SOC内部的IROM中固化了一部分代码叫做bl0,bl0的作用是将bl1加载到IRAM中运行,bl1是三星提供的二进制文件,作用是初始化硬件
同时将bl2加载到IRAM中运行,bl2是u-boot.bin的前14k,这14K的主要作用是计算出片外内存可用的高位地址,然后将u-boot.bin整体
搬移到计算出的高位地址中去运行。
linux内核:主版本号.次版本号.修订版本号
通常情况下次版本号为偶数的是稳定版本。www.kernel.org
2.4devfs 2.6sysfs 3.0 3.1x sysfs
选择内核版本时,不要选择比较老的版本,最好不要选择最新版本。
linux重要的目录:
- arch存放的是和体系架构相关的代码
- Documentation存放说明文档
- drivers存放驱动代码
- init/main.c 存放了内核启动代码的c语言程序
- include 存放的是头文件
linux内核:
配置
- 方法:
- 1、
make exynos(芯片名称)_defconfig
- 2、
cp arch/arm/configs/exynos_defconfig .config
- 1、
- 目的: 通过配置方法让内核的功能只支持当前平台。
如何对内核进行软件裁剪:make menuconfig
- 注意:
- 1、在顶层目录
- 2、屏幕不能太小
- 3、第一次在执行make menuconfig时会不成功,可能是因为没安装一个软件包
进入menuconfig后选项的种类主要使用3种:
- [] 有两种状态——选中和未选中
- <> 有三种状态——选中和未选中、模块
- () 可以存放十进制整数,十六进制整数、字符串
- 内核选项是如何添加到menuconfig中的?必须要先学习Kconfig语法
Kconfig中的属性:
- source "路径/Kconfig" 引用某个路径下的Kconfig文件
- config 字符串
- 例如config ABC 我们会在顶层目录下的.config文件中寻找到CONFIG_ABC这个变量。同时CONFIG_ABC还有可能出现在Makefile文件中
- bool "字符串" 这个字符串就是menuconfig中的一个选项名称,bool代表了选项[]
- tristate "字符串" 代表了<>
- string "字符串" 代表了(),这个小括号中只能存放字符串
- int "字符串" 代表了(),这里存放十进制整数
- hex "字符串" 代表了(),智力存放十六进制整数
- menu,endmenu 这两个配对使用,当一个选项有子选项时使用
- default y | n | m | 字符串 | 整数
- depends on 字符串 当前选项的出现要依赖另一个选项是否选中,而另一个选项是谁?通过字符串来寻找的
假设需要操作一个设备。而且内核中已经有驱动来支持这个设备,但这个驱动相关选项没有选中,如何找到这个选项?
- 1、确定用到哪个具体的驱动程序。假设这个驱动程序叫做test.c
- 2、一旦找到驱动程序所在路径,进入这个路径下的Makefile中,寻找obj-$(CONFIG_ABC) += test.o
- 3、还在当前目录下进入Kconfig文件,寻找ABC,找到ABC之后可以知道这个驱动用哪个选项了