Linux u-boot 移植学习笔记

Dec 15, 2015


记录移植U-boot,Linux过程中的知识点和技巧。


启动内核时出现undefined instruction错误

启动内核时会出现各种错误,我遇到的是如下情况:

......
Image Name:linux-2.6.32.2
......
Load Address:30008000
Entry Point:30008000
Verifying Checksum...OK
XIP Kernel Image...OK
OK
Starting kernel...
undefined instruction
...
Resetting CPU...
resetting...

在解决这个问题前有必要说一下uImage是如何生成的,因为出现这样的问题肯定与uImage的正确性有密切关系。 分析uImage是如何生成的,肯定是去分析Makefile了。

  • 看一下顶层中的Makefile,其中:
zImage Image xipImage bootpImage uImage: vmlinux
	$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

当我们执行make uImage时,运行其中的命令行,展开其中的变量,得到:

make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage

上面是展开变量得到的结果,还有一种简单的方法就是利用神器grep,执行:

#make uImage -n | grep "make -f"
...
make -f scripts/Makefile.build obj=net/sunrpc
make -f scripts/Makefile.build obj=net/unix
make -f scripts/Makefile.build obj=net/wireless
make -f scripts/Makefile.build obj=arch/arm/lib
make -f scripts/Makefile.build obj=lib
make -f scripts/Makefile.build obj=lib/zlib_deflate
make -f scripts/Makefile.build obj=lib/zlib_inflate
make -f /home/saiyn/work/linux-2.6.32.69/scripts/Makefile.modpost vmlinux.o
echo '  GEN     .version'; set -e; if [ ! -r .version ]; then rm -f .version; echo 1 >.version; else mv .version .old_version; expr 0$(cat .old_version) + 1 >.version; fi; make -f scripts/Makefile.build obj=init
make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage
make -f scripts/Makefile.build obj=arch/arm/boot/compressed arch/arm/boot/compressed/vmlinux

上面其实就是kbuild的工作实例,kbuild使用的典型方式如下:

$(MAKE) $(build)=\<subdir\> [target]
  • 下面简单地阐述一下make的工作原理。

通过-f选项,指定Makefile为scripts目录下的Makefile.build。 而当make解释执行Makefile.build时,再将子目录中的Makefile包含到Makefile.include中来, 这就动态的组成子目录的真正的Makefile。 make始终工作于顶层目录下,所以需要跟踪编译所在的子目录,为此,kbuild定义了两个变量:src和obj。 其中,src始终指向需要构建的目录,obj指向构建的目标存放的目录。所以在Makefile.build的 一开头,变量src的值为$(obj):


/scripts/kbuild.include:
src := $(obj)
PHONY := __build
__build:
...

让我们继续回到uImage如何生成的问题上来

make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410/ arch/arm/boot/uImage

上面这句命令的效果就是去执行arch/arm/boot中的Makefile, 目标是arch/arm/boot/uImage,

  • 现在分析arch/arm/boot中的Makefile(Linux2.6.32):

$(obj)/uImage:	$(obj)/zImage FORCE
	$(call if_changed,uimage)
	@echo '  Image $@ is ready'

因为目标是arch/arm/boot/uImage,所以首先执行的是上面部分, uImage依赖zImage 和 FORCE,其中 FORCE是一个伪目标,作用就是不管zIamge是否最新,都要执行底下的命令行。 要弄懂$(call if_changed,uimage)这句的意思,得清楚call函数(见Makefile 学习笔记),以及if_changed函数。 if_changed定义在scripts/Kbuild.include文件中:


if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

上面的命令比较复杂,不过看到$(cmd_$(1))这句我们大概知道就是要执行cmd_uimage, 看一下Makefile, 果然有这么一段:


quiet_cmd_uimage = UIMAGE  $@
      cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
		   -C none -a $(LOADADDR) -e $(STARTADDR) \
		   -n 'Linux-$(KERNELRELEASE)' -d $< $@

Makefile的一开始就定义了MKIMAGE变量,MKIMAGE := $(srctree)/scripts/mkuboot.sh, mkuboot.sh脚本会去寻找’mkimage’程序,mkimage一般在U-Boot的tools目录下。所以我们得将其拷贝到我们的/usr/local/bin目录下。所以我们得将其拷贝到我们的/usr/local/bin目录 后面的-A arm -O linux -T kernel -C none -a $(LOADADDR) -e $(STARTADDR) -n 'Linux-$(KERNELRELEASE)' -d $< $@ 都是传递给mkimage的参数,其中最重要的就是$(LOADADDR)$(STARTADDR),可以猜到,出现undefined instruction错误 就是这两个参数定义出了问题。

  • 分析$(LOADADDR)$(STARTADDR)这两个参数的定义

$(LOADADDR)是定义U-Boot该从哪个地址加载Linux内核镜像,


ifeq ($(CONFIG_ZBOOT_ROM),y)
$(obj)/uImage: LOADADDR=$(CONFIG_ZBOOT_ROM_TEXT)
else
$(obj)/uImage: LOADADDR=$(ZRELADDR)
endif

因为没有定义CONFIG_ZBOOT_ROM,所以LOADADDR=$(ZRELADDR)

ZRELADDR    := $(zreladdr-y)

ifneq ($(MACHINE),)
include $(srctree)/$(MACHINE)/Makefile.boot
endif

ZRELADDR依赖zreladdr-y,而zreladdr-y就定义在$(srctree)/$(MACHINE)/Makefile.boot中,即~/arch/arm/mach-s3c2410/Makefile.boot

root@ubuntu:~/work/linux-2.6.32.69#cat arch/arm/mach-s3c2410/Makefile.boot
   zreladdr-y	:= 0x30008000
params_phys-y	:= 0x30000100

这样可以清楚的知道-a $(LOADADDR),其实就是-a 0x30008000,即在0x30008000处加载内核。 清楚了$(LOADADDR),那么$(STARTADDR)就清楚了。


ifeq ($(CONFIG_THUMB2_KERNEL),y)
# Set bit 0 to 1 so that "mov pc, rx" switches to Thumb-2 mode
$(obj)/uImage: STARTADDR=$(shell echo $(LOADADDR) | sed -e "s/.$$/1/")
else
$(obj)/uImage: STARTADDR=$(LOADADDR)
endif

因为没有定义CONFIG_THUMB2_KERNEL,所以STARTADDR=$(LOADADDR),即加载地址等于入口地址,这显然是不对的,因为加载地址后的40个字节放的是 u-boot传入的参数,至此终于搞清楚为什么会出现undefined instruction错误了。知道问题所在,那么解决起来就不难了,有2中方法:

  • 1.修改Makefile
  • 2.输入命令之间调用mkimage

利用busybox制作根文件系统

  • 第一步:制作最新根文件系统,让内核可以正常启动工作
  • 第二步:完善根文件系统,添加启动脚本