系统移植笔记 (2)
1. 交叉开发与交叉编译
本地开发 交叉开发
编译主机 本地(PC) 本地(PC)
运行主机 本地(PC) 目标主机(开发板)
使用的工具 本地编译器 交叉编译器
编译流程:
1. 预处理 :头文件的展开,宏替换
gcc -E hello.c -o hello.i
2. 编译 :把C语言生成汇编语言
gcc -S hello.i -o hello.s
3. 汇编 :把汇编语言生成机器码
gcc -c hello.s -o hello.o
4. 连接 : 连接库文件和分配连接地址
gcc hello.o -o hello
gcc的选项名:
-c 只编译
-o 输出文件名
-O 优化级别 -O0 -O1 -O2 -Os
-g 生成gdb的调试信息
-Wall 显示所有的警告
-I 包含一个头文件搜索路径
-L 包含一个库文件搜索路径
-l 连接一个库
gnu的二进制工具集
1. readelf 可以显示elf格式可执行文件的信息
readelf hello -h 显示elf的头信息
readelf hello -s 显示elf的段信息
readelf hello -a 显示elf的所有信息
readelf 显示所有的帮助信息
2. as 汇编器 在 gcc -c 会调用as工具
gcc hello.s -o hello.o -c 等价于
as hello.s -o hello.o
3. size列出目标文件每一段的大小以及总体的大小。
size hello -A 纵向显示
size hello -B 横向显示
4. nm可以列出目标文件中的符号。用法虽然简单,但是功能很强大。
nm hello 显示系统的标号
ls -l hello 显示文件的大小
strip hello 用来去除系统的标号,对程序瘦身
ls -l hello 显示文件的大小
nm hello
5. strip用来丢弃目标文件中的全部或者特定符号,减小文件体积。
6. strings 用来显示程序中的字符串
strings hello
strings hello -f 显示文件名
7. objdump可以显示一个或者更多目标文件的信息,主要用来反汇编。
objdump -D hello >hello.dis
8. objcopy可以进行目标文件格式转换。
objcopy -O binary hello hello.bin --gap-fill=0xff
9. addr2line能够把程序地址转换为文件名和行号
gcc hello.c -o hello -g
nm hello 找到系统的标号
addr2line 0x80483d4 -e hello -f 把地址转换成标号和文件名
uboot内的命令:
1. printenv 显示所有的环境变量
print
bootdelay 是用来确定系统进入自启动模式的延时时间
baudrate=115200 用来确定终端的波特率
gatewayip=192.168.0.1 表示开发板的网关
serverip=192.168.0.18 表示是主机(vmware的ip地址)的ip地址
ipaddr=192.168.0.250 表示是自己开发板的IP地址
2. setenv 用来这是环境变量的值
3. saveenv 保存环境变量的值
4. tftp命令 从tftp服务器中下载文件到内存当中
tftp <内存地址> <文件名>
tftp 20008000 zImage
5. md 显示内存里面的内容
md 20008000
6. nand erase nand的擦除命令
nand erase <基地址> <偏移量>
nand erase 100000 300000
7. nand write 把内存里
面的内容写入到nand当中
nand write <内存地址> <基地址> <偏移量>
nand write 20008000 100000 300000
8. nand read 把nand当中的数据读入到内存当中
nand read <内存地址> <基地址> <偏移量>
nand read 20008000 100000 300000
9. go 命令
go
go 20008000
10. bootcmd 是一个环境变量,里面存放的是一个或者是多个命令
如果是多个命令,命令之间用分号隔开
setenv bootcmd tftp 20008000 zImage \;go 20008000
如果当前模式计入自启动模式后,会执行bootcmd里面的命令
如果当前模式在交互模式就不在执行bootcmd里面的指令
通过网络加载内核和文件系统(产品开发阶段)
# setenv bootargs root=nfs nfsroot=192.168.1.100:/source/rootfs ip=192.168.1.200
init=/linuxrc console=ttySAC0,115200
bootargs : 内核启动后以什么方式挂载文件系统
root=nfs : 使用的是网络文件系统
nfsroot=192.168.0.31:/source/rootfs : nfs服务器的IP和路径
ip=192.168.0.200 : 开发板的IP
init=/linuxrc : 应用程序的第一个脚本(shell)
console=ttySAC0,115200
setenv bootargs root=nfs nfsroot=192.168.0.31:/source/rootfs ip=192.168.0.200 init=/linuxrc console=ttySAC0,115200
从 nand flash 加载内核和文件系统(产品阶段)
setenv bootargs root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200
root=/dev/mtdblock2 : 是nandflash的第2个分区, 第二个分区存放是的rootfs
setenv bootcmd nand read 20008000 100000 300000\; go 20008000
uboot的配置和编译
1. 修改编译的工具
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
修改为:
ifeq (arm,$(ARCH))
CROSS_COMPILE ?=arm-cortex_a8-linux-gnueabi-
2. make distclean 清除一个工程的过程文件
3. 设定某一个开发板的配置
make
make smdkc100_config
4. 编译工程
make all
uboot的生成过程分析:(u-boot.bin)
1. make smdkc100_config
会执行当前目录下的Makefile当中的smdkc100_config这个目标
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 smdkc100 samsung s5pc1xx
MKCONFIG = /home/linux/workdir/source-pack/u-boot-2010.03/mkconfig
@ : 代表的是目标 smdkc100_config
/home/linux/workdir/source-pack/u-boot-2010.03/mkconfig smdkc100 arm arm_cortexa8 smdkc100 samsung s5pc1xx
$1 smdkc100
$2 arm
$3 arm_cortexa8
$4 smdkc100
$5 samsung
$6 s5pc1xx
删除asm文件
创建一个软连接 asm -> asm-arm
ln -s ${LNPREFIX}arch-$6 asm-$2/arch arch-s5pc1xx -> asm-arm/arch
ARCH = arm
CPU = arm_cortexa8
BOARD = smdkc100
VENDOR = samsung
SOC = s5pc1xx
2. 编译make
会执行当前目录下makefile的all目标
查看可以知道系统有两个all目标:
makefile规则: 系统当中可以有多个目标,但是只能有一个目标动作(命令)
sinclude $(obj)include/a
utoconf.mk.dep 生成autoconf.mk的依赖文件
sinclude $(obj)include/autoconf.mk 用来决定工程配置的文件
all:$(ALL)
ALL = u-boot.bin
u-boot.bin依赖u-boot
u-boot依赖 工程中的目标文件和库文件 + u-boot.lds
分析连接脚本u-boot.lds可以知道,程序的入口位置为_start
cpu/arm_cortexa8/start.o (.text) 是程序执行的第一个段
make -j2 用双核的进行编译
uboot启动流程分析:
1. cpu/arm_cortex_a8/start.s
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
系统的异常常量表
reset:
/*
* set the cpu to SVC32 mode disable irq and fiq
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
设置cpu的模式为svc模式,禁止irq和fiq中断
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
关闭指令cache 关闭mmu
关闭看门狗, 清除中断源
bl system_clock_init
初始化系统的时钟
bl uart_asm_init
初始化串口
bl mem_ctrl_asm_init
初始化动态内存
ldr r0, =_TEXT_BASE
addr_adr:
adr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
判断系统的启动方式
如果是usb启动 r0 = r1 不搬移uboot
如果是nand启动r0 != r1 搬移uboot到SDRAM中
#ifdef CONFIG_CMD_NAND
ldr sp, =(0x22000000)
bl copy_uboot_to_ram
b stack_setup
把uboot 复制到内存中
stack_setup:
ldr r0, =0xe03001c4
ldr r1, =0x0
str r1, [r0]
ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot
sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo
sub sp, r0, #12 @ leave 3 words for abort-stack
and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
初始化系统的堆栈
clear_bss:
ldr r0, _bss_start @ find start of bss segment
ldr r1, _bss_end @ stop here
mov r2, #0x00000000 @ clear value
clbss_l:
str r2, [r0] @ clear BSS location
cmp r0, r1 @ are we at the end yet
add r0, r0, #4 @ increment clear index pointer
bne clbss_l @ keep clearing till at end
清除bss段
ldr pc, _start_armboot @ jump to C code
进入C程序
void start_armboot (void)
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
_armboot_start = 0x2ff80000
CONFIG_SYS_MALLOC_LEN = 1M+128K
memset ((void*)gd, 0, sizeof (gd_t));
清零gd所指向的内存
memset (gd->bd, 0, sizeof (bd_t));
清零gd->bd所指向的内存
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
批量初始化系统,
C阶段的系统初始化
以上都是初始化系统,
for (;;) {
main_loop (); // 引导操作系统
}
程序进入死循环
s = getenv ("bootdelay");
获取环境变量bootdelay
run_command (s, 0);
解析环境变量
s = getenv ("bootcmd"); //bootcmd里面存放的是自启动的命令
run_command (s, 0);
解析环境变量里面的字
符串
向uboot当中添加命令:
1. 添加头文件
#include
#include
2. 实现命令的处理函数
3. 注册命令 U_BOOT_CMD
U_BOOT_CMD(name,maxargs,repeat,func,usage ,help)
name: 命令的名称 myhello
maxargs:命令的最大参数个数
repeat : 如果输入回车键,是否能够重复执行命令 为1 代表可以重复执行,为0 不能
func: 实现命令所有调用的函数
usage:用法提示
help: 帮助信息
4. 把当前目标加入到当前目录下的Makefile
内核配置与编译
1. make help 帮助信息
2. make clean : 删除大部分过程文件,保留配置文件
make mrproper : 删除所有生成的文件,删除配置,删除备份
make distclean :mrproper + 打包文件,编辑备份文件
3. 修改Makefile 指定交叉工具连
ARCH ?=arm
CROSS_COMPILE ?=arm-cortex_a8-linux-gnueabi-
4. 找一个就近配置文件
cp arch/arm/configs/s5pc100_defconfig .config
5. make menuconfig 对内核进行配置,执行make menuconfig 会执行当前目录下的
.config
make menuconfig 基于菜单的字符界面
make nconfig 基于ncurses行的字符界面
make xconfig 基于QT的图形界面
make gconfig 基于GTK的图形界面
6. 编译内核 make zImage
make menuconfig
内核的Kconfig语法
Highlighted letters are hotkeys. 高亮的字符是热键(快捷键)
Pressing
Press
< > 尖括号 三选一 * 表示编译进内核,称为内核的一部分(静态编译)
M 表示编译成模块,不属于内核的一部分,最后会称为*.KO,可以手动的进行装载(动态编译)
无 表示不选中
[ ] 二选一 * 表示选中 无 不选中
mainmenu "Linux Kernel Configuration" 系统的菜单,只能有一个
source "init/Kconfig" 包含一个文件(include)
menu "General setup" 用来创建一个菜单 ,这个菜单是有字菜单的
endmenu
config 变量名
bool "菜单的名称" [] 二选一
tristate <> 三选一
int ()一个十进制的数
depends on 变量名 依赖变量名称 变量名成立,当前选项会显示
default y 默认值
choice 多选一
endchoice
select 变量名 如果当前选项成立,会把select选择的变量也给选择
举例:
menu "linux-2.6.35-test"
config a1
bool "steup-a1"
default y
---help---
setup a1
config a2
tristate "setup-a2"
depends on a1
help
setup a2 help
config a3
int "setup-a3"
config a4
hex "setup-a4"
config a5
string "setup-a5"
choice
prompt "setup linux"
config gzip
bool "gzip"
config rar
bool "rar"
config zip
bool "zip"
endchoice
在makefile中 obj-y 表示静态编译,称为内核的一部分
obj-m 动态编译 ,称为*.KO文件
make zImage 编译内核
内核编译的流程: zImage 目录arch/arm/boot/
1. make zImage 会执行当前目录下的Makefile的在zImage目标 ,当前目录没有
但是在makefile有include $(srctree)/arch/$(SRCARCH)/Makefile
当中找到 zImage Image xipImage bootpImage uImage: vmlinux ,由此可以知道
zImage 依赖vmlinux
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
zImage <- vmlinux (压缩的内核可执行文件)通过格式转换 objcopy
vmlinux(压缩的内核) 生成由 piggy.gzip.o + head.o +misc.o + vmlinux.lds
在arch/arm/boot/compressed目录下
head.s -> 生成head.o //是内核的重定位(自我搬移)
misc.c -> 生成misc.o // 内核自我解压的代码
piggy.gzip -> 生成的 piggy.gzip.o
pippy.gzip <- vmlinux(顶层目录下的,没有压缩的内核) 通过gzip压缩算法得来的一个压缩文件
vmlinux <- vmlinux.lds arch/arm/kernel/vmlinux.lds
+
vmlinux.o 顶层目录下的
内核的运行:
tftp 20008000 zImage
go 20008000 pc = 0x20008000
内核的启动流程:
1. go 20008000 pc = 0x20008000
由程序的生成过程可以知道程序执行的第一个段是由连接脚本决定的: vmlinux.lds
vi arch/arm/boot/compressed/vmlinux.lds
ENTRY(_start) 程序的入口是_start
.text : {
_start = .; //是程序要执行的第一个指令段
*(.start)
*(.text)
*(.text.*)
分析程序可以知道:程序的首先会执行arch/arm/boot/compressed/head.S中的start段
1: mov r7, r1 @ r1 = 1826 平台ID
mov r8, r2 @ save atags pointer
beq not_relocated @ 判断内核是否需要自我搬移
bl decompress_kernel
内核的自我解压和自搬移流程:
1. 程序运行zImage 把重定位代码搬移到高端内存中.
2. 内核的自我解压,把zImage中的内核接到到高端内存中,即vmlinux
3. 执行重定位代码
4. 重定位代码会搬移解压后的内核(vmlinux)到0x20008000 ,然后从0x20008000 处去执行指令
以上为内核启动的第一阶段:
内核启动的第二个阶段:
执行解压后内核的第一指令:
vi arch/arm/kernel/vmlinux.lds
ENTRY(stext)
vi arch/arm/kernel/head.S
mrc p15, 0, r9, c0, c0 @ get processor id 获取cpu的id
bl __lookup_processor_type @ r5=procinfo r9=cpuid 检查cpu的id是否正确
beq __error_p @ yes, error 'p' 如果错误 打印
bl __lookup_machine_type @ r5=machinfo 获取平台设备号
beq __error_a @ yes, error 'a' 如果错误打印 "a"
ldr r13, __switch_data @ address to jump to after
@运行后r13的值为__mmap_switched
b __turn_mmu_on @ 打开系统的MMU功能
mov r3, r13
mov pc, r3 @ pc = __m
map_switched
b start_kernel @进入内核启动的C阶段,
进入init/main.c的start_kernel函数,执行内核启动的C阶段初始化
setup_arch(&command_line); //处理uboot给内核传递的参数
做系统的各种初始化
在最后执行rest_init() // init/main.c
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 内核开启的第一个线程
在kernel_init 函数中进行系统的初始化 //init/main.c
在函数中最后调用init_post();
run_init_process("/sbin/init"); 用户运行的第一个应用程序 ,
bootargs root=nfs nfsroot=192.168.1.100:/source/rootfs ip=192.168.1.200
init=/linuxrc console=ttySAC0,115200
可以知道 linuxrc -> bin/busybox 是连接到busybox的软件接
内核在内存中运行过后,要执行的第一个用户应用程序是 : busybox