分类: 嵌入式
2009-09-07 23:28:15
14. 内核加载地址和start参数问题
文件:
S3C2440 从cross-tools到 linux2.6.24.4.zip
大小:
43KB
下载:
下载
从上面的分析, 知道内核被加载到0x30400 0000处, 这个其实是zImage的加载地址, 就是说内核解压缩程序的运行地址. 还有一个问题,我们的内核一直是little endian 的. arm-linux-objdump下就知道.
一个问题是Big-endian的内核如何run, 另一个是解压缩程序的运行地址是随意的吗?
从monitor看, 这个环境一直在little endian运行... (big endian 实验也另作研究吧)
能否加载到任意合理地址(至少有ram吧,呵呵), 试验一下即可. 结果证明是可以的, 当然应该行,因为zImage已经支持PIC代码,并且可以配置成在纯ROM环境下运行(那就得烧到到固定地址了).
研究下zImage都包含什么东西,这个先从arch/arm/boot/Makefile看看吧:
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE #zImage 包含解压缩头的Image
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'
.......
$(obj)/uImage: $(obj)/zImage FORCE # U-boot image
$(call if_changed,uimage)
@echo ' Image $@ is ready'
$(obj)/bootp/bootp: $(obj)/zImage initrd FORCE 包含bootp目录的image bootpImage, 如要initrd,这个平台不支持,所以加载
$(Q)$(MAKE) $(build)=$(obj)/bootp $@ RamDisk 是monitor的事情了
@:
$(obj)/bootpImage: $(obj)/bootp/bootp FORCE #包含bootp目录的image bootpImage
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'
内核解压缩和PIC (position independent code)
arch/arm/boot/compressed/Makefile
#
# We now have a PIC decompressor implementation. Decompressors running
# from RAM should not define ZTEXTADDR. Decompressors running directly
# from ROM or Flash must define ZTEXTADDR (preferably via the config)
# FIXME: Previous assignment to ztextaddr-y is lost here. See SHARK
ifeq ($(CONFIG_ZBOOT_ROM),y) #从boot ROM运行时需要配置一个固定地址,还有BSS的地址
ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT)
ZBSSADDR := $(CONFIG_ZBOOT_ROM_BSS)
else
ZTEXTADDR := 0 #一般情况下,就是0, 是pic代码加上'手工'重定位,加载到任意地址
ZBSSADDR := ALIGN(4)
endif
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/
#把vmlinux.lds.in 中TEXT_START换成配置的地址(主要针对ZBOOT_ROM)
targets := vmlinux vmlinux.lds piggy.gz piggy.o font.o font.c
head.o misc.o $(OBJS)
EXTRA_CFLAGS := -fpic -fno-builtin
EXTRA_AFLAGS :=
.............
# Don't allow any static data in misc.o, which
# would otherwise mess up our GOT table
CFLAGS_misc.o := -Dstatic=
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o
$(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld)
@:
$(obj)/piggy.gz: $(obj)/../Image FORCE #piggy.gz 是压缩后的内核,见piggy.S
$(call if_changed,gzip)
$(obj)/piggy.o: $(obj)/piggy.gz FORCE
CFLAGS_font.o := -Dstatic=
$(obj)/font.c: $(FONTC)
$(call cmd,shipped)
$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile .config
@sed "$(SEDFLAGS)" < $< > $@
$(obj)/misc.o: $(obj)/misc.c include/asm/arch/uncompress.h lib/inflate.c
解压缩的pic技术以后再研究吧,挺多的.这里就是增强下信心吧. 知道可以加载到任意地址,呵呵. 下面看看decompress的入口函数start的参数问题:
arch/arm/boot/compressed/head.S
/*
* sort out different calling conventions
*/
.align
start:
.type start,#function
.rept 8
mov r0, r0
.endr
b 1f
.word 0x016f2818 &nb sp; @ Magic numbers to help the loader
.word start @ absolute load/run zImage address
.word _edata @ zImage end address
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
从这里看出, r1 存放的是architectureID, r2存放 atags 指针. 寄存器传递参数, 加上没有用的r0, 应该是这样一个函数:
void start(0, archID, *atags)
其实,在arch/arm/kernel/head.S中有关于参数的一段详细的注释,看看就明白了:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
找到mach-type是:
s3c2440 ARCH_S3C2440 S3C2440 362
不过通过实验, 我们的机器看来是SMDK兼容了,machine 参数必须传递193: SMDK2410 这个才行,而cpu类型则是自动侦测的,呵呵.
对应kernel的参数和decompressed一样:
void (*theKernel)(int zero, int arch, uint params);
另:试了试big endian,发现现在linux kernel对s3c的系统还不支持big模式.make config也无此选项.
--------------------------------------------------------------------------------
15. initrd : initial ram disk load process
让我们从新审视所得到的内核console的输出(见上文),看看需要做的东西, 先来关注最后几行的输出信息,内核相关代码是:
start_kernel->rest_init->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);->
static int __init kernel_init(void * unused)
{
..........
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command) /*由内核命令行参数 rdinit= 来控制,我们没有指定 (搜索就知道是rdinit=来控制了...)*/
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
.........
}
内核中有各种__setup宏定义的内核参数, 其前面的字符串是内核命令行, 其后的函数是这个命令行的处理函数,相关的宏定义在init.h中.grep下,很快有结果. 详细讨论先放一放.
void __init prepare_namespace(void)
{
.............
if (saved_root_name[0]) {
root_device_name = saved_root_name; /*这个就是由 root=/dev/ram传递的内核参数,稍作搜索即知*/
if (!strncmp(root_device_name, "mtd", 3)) { /*我们当然不是这个*/
.............
}
ROOT_DEV = name_to_dev_t(root_device_name); /*/dev/ram 解析出来的设备是 ROOT_DEV= root_RAM0(1,0)*/
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5; /* root_dev_name = "ram" */
}
if (initrd_load()) /* CONFIG_BLK_DEV_INITRD 之后才能使用initrd,我们的.config是有的*/
goto out;
......
mount_root();
out:
sys_mount(".", "/", NULL, MS_MOVE, NULL); /* /root 如何成为根, 何以叫mount root,原来并不是加载 "/" */
sys_chroot(".");
security_sb_post_mountroot();
}
int __init initrd_load(void)
{
if (mount_initrd) { /*只有配置了内核命令行: noinitrd才为0, 我们当然没有'自杀'了*/
create_dev("/dev/ram", Root_RAM0); /*创建设备先...*/
/*
* Load the initrd data into /dev/ram0. Execute it as initrd
* unless /dev/ram0 is supposed to be our actual root device,
* in that case the ram disk is just set up here, and gets
* mounted in the normal path.
*/
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { /*下面看看/initrd.image啥时候创建的*/
sys_unlink("/initrd.image");
handle_initrd();
return 1;
}
}
sys_unlink("/initrd.image");
return 0;
}
从我们配置的参数是Root_RAM0, 最后 void __init prepare_namespace(void)会调用mount_root:
void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
...
#endif
#ifdef CONFIG_BLK_DEV_FD
....
#endif
#ifdef CONFIG_BLOCK
create_dev("/dev/root", ROOT_DEV);
mount_block_root("/dev/root", root_mountflags); /*有默认值 MS_RDONLY | MS_SILENT*/
#endif
}
void __init mount_block_root(char *name, int flags)
{
get_fs_names(fs_names); /*所有已经安装的文件系统的名字列表*/
retry:
for (p = fs_names; *p; p += strlen(p)+1) { /*p代表fs type, 这里name 是/dev/root,就是Root_RAM0*/
int err = do_mount_root(name, p, flags, root_mount_data); /*(dev,type,mntflags,(rootflags=,给具体文件系统的参数) )*/
.......
/*如果失败了,下面的信息倒是没有显示出来....*/
printk("VFS: Cannot open root device "%s" or %s ",
root_device_name, b);
printk("Please append a correct "root=" boot option; here are the available partitions: ");
printk_all_partitions();
panic("VFS: Unable to mount root fs on %s", b);
}
printk("List of all partitions: ");
printk_all_partitions();
printk("No filesystem could mount root, tried: "); /*列出曾经尝试的文件系统类型*/
for (p = fs_names; *p; p += strlen(p)+1)
printk(" %s", p);
printk(" ");
.......
panic("VFS: Unable to mount root fs on %s", b); /*知名panic*/
...
}
好了上面的函数就是知名的panic. 这个过程就是将文件/initrd.image 拷贝到Root_RAM0设备内, 然后创建设备文件/dev/root(这个个文件就是为了fs的接口函数准备的),然后将 /dev/root 安装到/root, 最后升级/root到文件系统根目录 '/'.
遇到这个panic从代码上看,是do_mount_root在尝试用各种文件系统来解析/dev/root后竭尽失败.无奈之下,panic的.原因可就多了,比如config的时候没有选上ramdisk支持(设备层), 或者ramdisk中的文件系统内核不支持(文件系统层), 再或者initrd的加载出了问题(无论是boot loader 还是内核创建"/initrd.image", 最后参数传递错误也不成. 经过仔细检查,内核配置和参数传递应该没有啥问题. 这里好多内核的信息没有打印出来,详细的错误也就被隐蔽了.
为了验证ramdisk是否正确加载, 在rd_load_image("/initrd.image") 里面加了不少调试信息,结果发现, 根本没有/initrd.image这个文件,在这个函数打开这个文件的时候出错了, 这证明这个文件创建失败了. 原因也很多,不过还是先扫一眼这个文件在什么地方创建.一搜,在这个函数里呢:
static int __init populate_rootfs(void) 这是一个init函数,内核悄悄的运行了他.... (运行的地方好找,不提).
static int __init populate_rootfs(void)
{
... /*解压缩先略过不看...*/
if (initrd_start) { /*这个就是内核命令行传递进来的值...., 汗...monitor里这个值是0, 显然是不对的啊*/
/* ...创建 initrd.image 这个文件*/
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
.......
}
好了一个问题出来了, 改吧, 去monitor的参数里修改一番,也犯了不少错误:
1) 第一个就是initrd的初始地址不能为0...,
2) 再有就是有个地方出错比如我设置initrd初始地址0x1000(随意值), 大小是0x1000(随意值,不设置长度monitor不加载ramdisk,只对此monitor有效). 这个地址有傻问题? 看看输出:
Memory policy: ECC disabled, Data cache writeback
initrd (0x00000000 - 0x000003e8) extends beyond physical memory - disabling initrd
CPU S3C2440A (id 0x32440001)
这里又跳出个地方和initrd有关, 位置在bootmem初始化中:(ft...)
arch/arm/mm/init.c
static int __init check_initrd(struct meminfo *mi)
{
.......
if (phys_initrd_size) {/*最初传递的initrd start 和size都是0, 我ft.... */
for (i = 0; i < mi->nr_banks; i++) {
...........
}
if (initrd_node == -1) {
printk(KERN_ERR "initrd (0x%08lx - 0x%08lx) extends beyond "
"physical memory - disabling initrd ",
phys_initrd_start, end);
phys_initrd_start = phys_initrd_size = 0;
}
..........
}
没有仔细探究这个bank是个什么意思(估计就是S3C2440 cpu里对内存bank的划分吧),但是这个警告信息提醒了我.超出物理内存? 不一定是地址太大,呵呵,因为啊, ram物理地址并不是从0开始的,启动的时候是S3C2440 自己吧nand读到了地址0开始的一段内部ram中.....所以,吧initrd start地址和size设置成一个在RAM地址范围内的一段地址里. 重启,这下,果然不同了........
出现的信息还是不能加载root文件系统,但是initrd加载成功了, 即ramdisk已经包含了initrd.image, "/initrd.image"文件也已经创建成功了. 但是出现RAMDISK: image too big! (2078xx/4096) ,相关函数是
int __init rd_load_image(char *from) /*从/initrd.image 写入/dev/ram设备*/
{
......
/*nblocks是从ramdisk image中根据文件系统magic猜测的block数(identify_ramdisk_image),
*rd_blocks是从/dev/ram设备中读出的数值,ramdisk大小是4096K size ,1024 blocksize,可以配置
*/
if (nblocks > rd_blocks) { /*这个信息是发现加载的initrd image大小大于ram disk的大小,加载失败*/
printk("RAMDISK: image too big! (%dKiB/%ldKiB) ",
nblocks, rd_blocks);
goto done;
}
....
}
另外,建议调试阶段把kernel consolelog 设置成verbose,就是在setup_arch的最前端,调用下面函数:
console_verbose();
这样一些提示性和continue性质的信息能够显示出来方便调试.
这个原因是什么? 根据调试信息打印出来信息, identify_ramdisk_image:RAMDISK: cramfs filesystem found at block 0
显然是发现了一个cramfs,但是不知到是什么原因. 呵呵,秘密是我还没有烧一个initrd进去呢,用的是现有的, 其大小和格式都是未知的.这样当然累了,要知道原因就太难(?), 所以要自己制作一个....
完整下载