您的位置:首页 > 运维架构 > Linux

vivi开发笔记(十七):vivi与Linux kernel的参数传递情景分析(下)

2014-07-03 13:46 555 查看
原文地址:vivi开发笔记(十七):vivi与Linux
kernel的参数传递情景分析(下) 作者:fglswh

下面进入Linux kernel部分,分析与bootloader参数传递对应的部分。

移植Linux需要很大的工作量,其中之一就是HAL层的编写。在具体实现上,HAL层以arch目录的形式存在。显然,该层需要与bootloader 有一定的约定,否则就不能很好的支持。其实,这个地方应该思考一个问题,就是说,boot loader可以做到Linux kernel里面,但是这样带来的问题就是可移植性和灵活性都大为降低。而且,bootloader的功能并非操作系统的核心范畴,Linux的核心应该始终关注操作系统的核心功能上,将其性能达到最优。所以,bootloader分离出来单独设计,是有一定的道理的。bootloader现在除了完成基本功能外,慢慢地变得“肥胖”了。在高性能bootloader设计中,可能会把调试内核等的一些功能集成进来,这样在内核移植尚未完成阶段,
bootloader可以充当调试器的作用。功能趋于完善,也慢慢趋于复杂。废话不说,进入正题。

三、Linux kernel接受参数分析

这部分主要分析如下问题:

·Linux kernel支持压缩映象和非压缩映象两种方式启动,那么这两种流程和函数入口有何不同?
·如何使用非压缩映象?做一下测试。
·zImage是如何生成的?其格式如何?
·启动之后,Linux kernel如何接收参数?

这里不具体区分每个问题,按照理解和开发的思路来进行。

1、思考:前面做的基本实验中,并没有采用压缩映象。因为程序规模太小,压缩带来的时间开销反而降低了性能。但是对Linux kernel来说,映象还是比较大的,往往采用了压缩。但是,同样有需求希望Linux kernel小一些,不采用压缩方式来提高内核启动的速度,对时间要求比较苛刻。那么,这样就出现了两种情况:压缩映象和非压缩映象。由此带来的问题就在于:如果是压缩映象,那么必须首先解压缩,然后跳转到解压缩之后的代码处执行;如果是非压缩映象,那么直接执行。Linux必须对这两种机制提供支持,这里就需要从整体上来看一下生成的映象类型了。

因为vivi的Makefile都是直接来源于Linux,前面对vivi的Makefile已经分析清楚了,这里看Linux的Makefile就容易多了,大同小异,而且还有丰富的文档支持。

(1)非压缩映象

$make vmlinux

[armlinux@lqm linux-2.4.18]$ ls -l
vmlinux

-rwxrwxr-x 1 armlinux armlinux 1799697 Sep 11 14:06 vmlinux

[armlinux@lqm linux-2.4.18]$ file vmlinux

vmlinux: ELF 32-bit LSB executable, ARM, version
1 (ARM), statically linked, not stripped

这里生成的是vmlinux,是ELF文件格式。这个文件是不能烧写存储介质的,如果想了解ELF文件格式,需要参考专门的文章。当然,这里,如果想要使用非压缩映象,可以使用arm-linux-objcopy把上述ELF格式的vmlinux转化为二进制格式的vmlinux.bin,这样就可以直接烧写了。

于是我做了如下的修改,在Makefile中增加了:

vmlinux: include/linux/version.h
$(CONFIGURATION) init/main.o
init/version.o linuxsubdirs

$(LD) $(LINKFLAGS) $(HEAD) init/main.o
init/version.o \

--start-group \

$(CORE_FILES) \

$(DRIVERS) \

$(NETWORKS) \

$(LIBS) \

--end-group \

-o vmlinux

$(NM) vmlinux | grep -v '\(compiled\)\|\(\.o$$\)\|\(
[aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | sort > System.map

$(OBJCOPY) -O binary -R .comment -R .stab -R .stabstr -S
vmlinux vmlinux.bin

同时在clean file的列表中增加vmlinux.bin。这样就可以生成vmlinux.bin了,前面的基础实验都讲过了。然后烧写vmlinux.bin到nand flash的kernel分区,引导启动,正常,而且不会出现解压缩提示:

NOW, Booting Linux......

VIVI has completed the mission of

From now on, Linux kernel takes charge of all

Linux version 2.4.18-rmk7-pxa1 (armlinux@lqm) (gcc
version 2.95.3 20010315 (release)) #2
Tue Sep 11 14:06:14 CST 2007

可见,可以通过非压缩映象格式启动。

(2)压缩映象

下面看看压缩映象是如何得到的。顶层的Makefile没有压缩映象的生成,显然就在包含的子Makefile中。容易查知在arch/arm/下的Makefile,可见:

bzImage zImage zinstall Image bootpImage install: vmlinux

@$(MAKEBOOT) $@

也就是说,有bzImage、zImage几种。其中arch/boot下有:

SYSTEM =$(TOPDIR)/vmlinux


Image: $(CONFIGURE) $(SYSTEM)

$(OBJCOPY) -O binary -R .note -R .comment -S
$(SYSTEM) $@

bzImage: zImage

zImage: $(CONFIGURE) compressed/vmlinux

$(OBJCOPY) -O binary -R .note -R .comment -S
compressed/vmlinux $@

@echo " ^_^ The kernel image file is:" $(shell /bin/pwd)/$@

这里发现如果采用make Image,则生成的非压缩映象的二进制格式,可以直接烧写,可见前面第一步的工作是浪费了,Linux内核还是很完善的,提供了这种方式,所以,如果想要生成非压缩二进制映象,那么就要使用make Image。

另外,这里提供了两种压缩的映象,其实就是一种,这里能够看到的就是如果采用make zImage或者make bzImage,就要把compressed/vmlinux处理为二进制格式,可以下载使用。下面就看compressed/vmlinux是什么。进入compressed文件夹,看看Makefile:

vmlinux:    $(HEAD) $(OBJS) piggy.o
vmlinux.lds

$(LD) $(ZLDFLAGS) $(HEAD) $(OBJS) piggy.o
$(LIBGCC) -o vmlinux

很明显了,这里的vmlinux是由四个部分组成:head.o、head-s3c2410.o、misc.o、piggy.o。关于这几个文件是干什么用的,看看各自的编译规则就非常清晰了:

$(HEAD): $(HEAD:.o=.S) \

$(wildcard $(TOPDIR)/include/config/zboot/rom.h) \

$(wildcard $(TOPDIR)/include/config/cpu/32.h) \

$(wildcard $(TOPDIR)/include/config/cpu/26.h)

$(CC) $(AFLAGS) -traditional -c
$(HEAD:.o=.S)

piggy.o: $(SYSTEM)

$(OBJCOPY) -O binary -R .note -R .comment -S
$(SYSTEM) piggy

gzip $(GZFLAGS) < piggy > piggy.gz

$(LD) -r -o
$@ -b binary piggy.gz

rm -f piggy piggy.gz

font.o: $(FONTC)

$(CC) $(CFLAGS) -Dstatic= -c -o
$@ $(FONTC)

vmlinux.lds: vmlinux.lds.in
Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile
$(TOPDIR)/.config

@sed "$(SEDFLAGS)" < vmlinux.lds.in > $@

clean:; rm -f vmlinux core piggy* vmlinux.lds

.PHONY: clean

misc.o: misc.c $(TOPDIR)/include/asm/arch/uncompress.h
$(TOPDIR)/lib/inflate.c

可见,vmlinux是把顶层生成的非压缩的ELF映象vmlinux进行压缩,同时加入了加压缩代码部分。真正的压缩代码就是lib/inflate.c。可以看看,主要是gunzip,具体的压缩算法就不分析了。

至此,就可以用下图作出总结了:



[align=center] [/align]

bootloader把存储介质中的kernel映象下载到mem_base+0x8000的位置,执行完毕后,跳转到这一位置,执行此处的代码。这一位置的入口可能有两种情况,第一种是kernel映象为非压缩格式,通过make Image获得,那么真正的入口就是arch/arm/kernel/head_armv.S(ENTRY(stext));第二种是kernel映象为压缩格式,通过make
zImage获得,那么真正的入口就是arch/arm/boot/compressed/head.S(ENTRY(_start))。这个地方并不是kernel判断,也不需要判断。道理很简单,cpu只会按照读入的代码执行,两种情况下执行的代码不同,自然也就有两种不同的过程了。

(3)探讨zImage的magic number的位置

可以看出,如果是zImage,那么程序的入口是arch/arm/boot/compressed/head.S。分析程序头部:

.align

start:

.type start,#function


//重复如下指令8次

.rept 8

mov r0, r0

.endr

//跳转指令,跳到下面第一个标号1处

b 1f


//这就是第10条指令的位置,也就是偏移为4*9个字节

.word 0x016f2818 @ 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, #0 @ save r0

可见前面8条指令均为mov r0, r0,从前面的zImage的16进制格式中可以看出,前面8个字都是相同的,均为00 00 A0 E1,第9条指令就是b 1f,然后就应该是0x016f2818.这样就与前面程序的判断对应上了,也就是说,此处的magic number是固定位置,固定数值的,注释中也写的很清晰,那就是magic numbers to help the loader,也就是说帮助bootloader确定映象的文件格式。但是应该说明的是,在vivi的bootloader设计中,虽然检测zImage
的magic number,但是并没有进行未识别处理。也就是说,假定用ultra-edit32把此位置的0x016f2818破坏掉,其他不变,那么虽然vivi 提示无法识别zImage映象,但是并不影响实际的执行。当然,你也可以有其他的设计思路。不过设计的哲学思想是,要完成一件事情,并不只有一种方式。所以,bootloader不能限死只是使用zImage格式,需要有一定的灵活性,为了引导内核启动,可以采用不同的方式。

(4)完成了前面的理解,下面就要重点看解析参数一部分了。这里不将zImage方式的启动作为重点分析内容,静下心来跟踪代码并不是难事。从整体的角度理解,如果采用zImage,那么在执行完成解压缩之后,自然会调转到解压之后的kernel的第一条指令处。这时就是真正的启动内核了。所以我们可以看arch/arm/kernel/head-armv.S,此处做的工作可以参考taoyuetao的分析,完成的功能比较简单。这里就感兴趣的参数问题分析,需要注意的是,

/*

* Kernel startup entry point.

*

* The rules are:

* r0 - should be 0

* r1 - unique architecture number

* MMU - off

* I-cache - on or off

* D-cache - off

*

* See linux/arch/arm/tools/mach-types for the complete list of numbers

* for r1.

*/

可见R0是0,R1是mach type,这些都是必须要设定的。在这里,并没有限定R2必须为参数的起始地址。kernel本身并没有使用R0-R2,如果设定了R2,在这里也不会修改其值。后面的工作也没有设计接收参数,最后直接跳到start_kernel(【init/main.c】)

asmlinkage void __init start_kernel(void)

{

char * command_line;

unsigned long mempages;

extern char saved_command_line[];

/*

* Interrupts are still disabled. Do necessary setups, then

* enable them

*/

lock_kernel();

printk(linux_banner);

setup_arch(&command_line);

printk("Kernel command line: %s\n", saved_command_line);

parse_options(command_line);

从开头分析,首先是lock_kernel,这里是SMP相关,我的是单CPU,所以实际上该函数为空。然后打印版本信息,在vivi中已经分析过这个机制了,两者相同。下面的setup_arch就是分析的重点了,它要获取命令行启动参数,然后打印获得的命令行参数,然后进行语法解析选项。我们关注的重点就在setup_arch上了。参数设置都在【arch/arm/kernel/setup.c】,这个函数也不例外,进入setup.c。

void __init setup_arch(char **cmdline_p)

{

struct tag *tags = NULL;

struct machine_desc *mdesc;

char *from = default_command_line;

ROOT_DEV = MKDEV(0, 255);

setup_processor();

mdesc = setup_machine(machine_arch_type);

machine_name = mdesc->name;

if (mdesc->soft_reboot)

reboot_setup("s");

if (mdesc->param_offset)

tags = phys_to_virt(mdesc->param_offset);

/*

* Do the machine-specific fixups before we parse the

* parameters or tags.

*/

if (mdesc->fixup)

mdesc->fixup(mdesc, (struct param_struct *)tags,

&from, &meminfo);

/*

* If we have the old style parameters, convert them to

* a tag list before.

*/

if (tags && tags->hdr.tag != ATAG_CORE)

convert_to_tag_list((struct param_struct *)tags,

meminfo.nr_banks == 0);

if (tags && tags->hdr.tag == ATAG_CORE)

parse_tags(tags);

if (meminfo.nr_banks == 0) {

meminfo.nr_banks = 1;

meminfo.bank[0].start = PHYS_OFFSET;

meminfo.bank[0].size = MEM_SIZE;

}

init_mm.start_code = (unsigned long) &_text;

init_mm.end_code = (unsigned long) &_etext;

init_mm.end_data = (unsigned long) &_edata;

init_mm.brk     = (unsigned long) &_end;

memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

saved_command_line[COMMAND_LINE_SIZE-1] = '\0';

parse_cmdline(&meminfo, cmdline_p, from);

bootmem_init(&meminfo);

paging_init(&meminfo, mdesc);

request_standard_resources(&meminfo, mdesc);

/*

* Set up various architecture-specific pointers

*/

init_arch_irq = mdesc->init_irq;

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

conswitchp = &dummy_con;

#endif

#endif

}

这里面涉及到3个比较复杂的结构体,包括param_struct、tag、machine_desc。第一步的操作是关于根设备号,暂时不探讨;第二步工作setup_processor,是设置处理器,这是多处理器相关部分,暂时不探讨;第三步工作是setup_machine,这里就需要了解了。

首先,machine_arch_type没有定义,仅仅在头部有定义,这是全局变量,两者之间一定存在联系:

unsigned int __machine_arch_type;

看看头文件,应该有#include <asm/mach-types.h>,但是未编译时并没有,可以确定是编译前完成的。这里只有看Makefile了。因为setup.c在这里,首先看同层的Makefile。这一层没有关于mach-types.h的信息,然后到上一层Makefile,发现了:

MRPROPER_FILES += \

arch/arm/tools/constants.h* \

include/asm-arm/arch
\

include/asm-arm/proc
\

include/asm-arm/constants.h* \

include/asm-arm/mach-types.h

# We use MRPROPER_FILES and CLEAN_FILES now

archmrproper:

@/bin/true

archclean:

@$(MAKEBOOT) clean

archdep: scripts/mkdep archsymlinks

@$(MAKETOOLS) dep

@$(MAKEBOOT) dep

说现在使用MRPROPER_FILES,但是下面没有出现,故而应该看几个宏的定义:

MAKEBOOT = $(MAKE) -C
arch/$(ARCH)/boot

MAKETOOLS = $(MAKE) -C
arch/$(ARCH)/tools

由此知道,对应的子文件夹包括boot和tools,boot是与启动相关,不太可能;而前面也看到,tools下有mach-types,所以判断在tools下面,看看tools/Makefile:

all: $(TOPDIR)/include/asm-arm/mach-types.h
\

$(TOPDIR)/include/asm-arm/constants.h

$(TOPDIR)/include/asm-arm/mach-types.h: mach-types
gen-mach-types

awk -f gen-mach-types mach-types > $@

由此判断出,mach-types.h是如何生成的,主要是利用awk脚本处理生成。生成之后与s3c2410有关的部分为:

#ifdef CONFIG_S3C2410_SMDK

# ifdef machine_arch_type

# undef machine_arch_type

# define machine_arch_type __machine_arch_type

# else

# define machine_arch_type MACH_TYPE_SMDK2410

# endif

# define machine_is_smdk2410() (machine_arch_type == MACH_TYPE_SMDK2410)

#else

# define machine_is_smdk2410() (0)

#endif

由此就知道了,这里的machine_arch_type为193,所以此函数实际上执行:mdesc = setup_machine(193);它要填充结构体machine_desc,如下:

struct machine_desc {

/*

* Note! The first four elements are used

* by assembler code in head-armv.S

*/

unsigned int        nr;        /*
architecture number    */

unsigned int        phys_ram;    /*
start of physical ram */

unsigned int        phys_io;    /*
start of physical io    */

unsigned int        io_pg_offst;    /*
byte offset for io

* page tabe entry    */

const char        *name;        /*
architecture name    */

unsigned int        param_offset;    /*
parameter page    */

unsigned int        video_start;    /*
start of video RAM    */

unsigned int        video_end;    /*
end of video RAM    */

unsigned int        reserve_lp0 :1;    /*
never has lp0    */

unsigned int        reserve_lp1 :1;    /*
never has lp1    */

unsigned int        reserve_lp2 :1;    /*
never has lp2    */

unsigned int        soft_reboot :1;    /*
soft reboot        */

void            (*fixup)(struct machine_desc *,

struct param_struct *, char **,

struct meminfo *);

void            (*map_io)(void);/*
IO mapping function    */

void            (*init_irq)(void);

};

另外,还提供了一系统的宏,用于填充该结构体:

/*

* Set of macros to define architecture features. This is built into

* a table by the linker.

*/

#define MACHINE_START(_type,_name) \

const struct machine_desc __mach_desc_##_type
\

__attribute__((__section__(".arch.info"))) = { \

nr: MACH_TYPE_##_type, \

name: _name,

#define MAINTAINER(n)

#define BOOT_MEM(_pram,_pio,_vio) \

phys_ram: _pram, \

phys_io: _pio, \

io_pg_offst: ((_vio)>>18)&0xfffc,

#define BOOT_PARAMS(_params) \

param_offset: _params,

#define VIDEO(_start,_end) \

video_start: _start, \

video_end: _end,

#define DISABLE_PARPORT(_n) \

reserve_lp##_n: 1,

#define BROKEN_HLT /* unused */

#define SOFT_REBOOT \

soft_reboot: 1,

#define FIXUP(_func) \

fixup: _func,

#define MAPIO(_func) \

map_io: _func,

#define INITIRQ(_func) \

init_irq: _func,

#define MACHINE_END \

};

EDUKIT填充了一个结构体,用如下的方式:

MACHINE_START(SMDK2410, "Embest
EduKit III (S3C2410x)")

BOOT_MEM(0x30000000, 0x48000000, 0xe8000000)

BOOT_PARAMS(0x30000100)

FIXUP(fixup_smdk)

MAPIO(smdk_map_io)

INITIRQ(s3c2410_init_irq)

MACHINE_END

看到有特殊的设置部分,那就是开始为之分配了一个段,段的名字是.arch.info,也就是说把这部分信息单独作为一个段来进行处理。下面把这个宏展开如下:

const struct machine_desc
__mach_desc_smdk2410 = {

nr: 193,

name: "EDUKIT-III (s3c2410)",

phys_ram: 0x30000000,

phys_to: 0x48000000,

io_pg_offset: 0x3a00,

param_offset: 0x30000100,

fixup: fixup_smdk,//实际上为空

map_io: smdk_map_io,

init_irq: s3c2410_init_irq,

};

可见,基本的信息已经具备了,而且从这里,我们也可以看出,启动参数地址由这个段就可以完成,不需要传递了。当然,必须保证bootloader的值,与此处的相同。这样,也就说明如果不使用R2传递参数的起始地址,那么这个地方就需要把这个结构体设置好。

下面看看这个函数完成什么功能:

static struct machine_desc * __init
setup_machine(unsigned int nr)

{

extern struct machine_desc __arch_info_begin, __arch_info_end;

struct machine_desc *list;

/*

* locate architecture in the list of supported architectures.

*/

for (list = &__arch_info_begin; list < &__arch_info_end; list++)

if (list->nr == nr)

break;

/*

* If the architecture type is not recognised, then we

* can co nothing...

*/

if (list >= &__arch_info_end) {

printk("Architecture configuration botched (nr %d), unable "

"to continue.\n", nr);

while (1);

}

printk("Machine: %s\n", list->name);

return list;

}

这个地方就是要把上面这一系列的信息连贯起来,那么就不难理解了。上述的宏已经完成了.arch.info段,这个段实际上在内存中就是一个 machine_desc形式组织的信息(对Linux内核来说,并不一定仅仅有一个结构块),上述函数的两个变量__arch_info_begin和 __arch_info_end很明显是有链接脚本传递进来。于是查看近层的链接脚本(【arch/arm/vmlinux-armv.lds.in】,可以发现:

.init : { /*
Init code and data */

_stext = .;

__init_begin = .;

*(.text.init)

__proc_info_begin = .;

*(.proc.info)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist)

__tagtable_end = .;

所以上述的功能就很简单了,就是查看是否有mach-type为193的结构存在,如果存在就打印出name,这也就是开机启动后,出现Machine: Embest EduKit III (S3C2410)的原因了。

接下来关注:

if (mdesc->param_offset)

tags = phys_to_virt(mdesc->param_offset);

很明显,这里的mdesc->param_offset并不为0,而是0x30000100,所以要做一步变换,就是物理地址映射成虚拟地址。把这个地址附给tags指针。然后就是判断是param_struct类型还是tags类型,如果是param_struct类型,那么首先转换成tags类型,然后对tags类型进行解析。

if (tags && tags->hdr.tag != ATAG_CORE)

convert_to_tag_list((struct param_struct *)tags,

meminfo.nr_banks == 0);

if (tags && tags->hdr.tag == ATAG_CORE)

parse_tags(tags);

要注意parse_tags函数是非常重要的,它有隐含的功能,不太容易分析。跟踪上去,主要看这个函数:

/*

* Scan the tag table for this tag, and call its parse function.

* The tag table is built by the linker from all the __tagtable

* declarations.

*/

static int __init parse_tag(const struct tag *tag)

{

extern struct tagtable __tagtable_begin, __tagtable_end;

struct tagtable *t;

for (t = &__tagtable_begin; t < &__tagtable_end; t++)

if (tag->hdr.tag == t->tag) {

t->parse(tag);

break;

}

return t < &__tagtable_end;

}

这里又用到链接器传递参数,现在就是来解析每个部分。先看一下tagtable是如何来的。首先看【include/asm-arm/setup.h】,看看宏的定义,也就是带有__tag,就归属为.taglist段。

#define __tag
__attribute__((unused, __section__(".taglist")))

#define __tagtable(tag, fn) \

static struct tagtable __tagtable_##fn
__tag = { tag, fn }

利用__tag有构造了一个复杂的宏__tagtable,实际上就是定义了tagtable列表。现在看setup.c中的宏形式示例:

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

展开之后为:

static struct tagtable
__tagtable_ATAG_CMDLINE __tag = {

ATAG_CMDLINE,

parse_tag_cmdline

};

于是,段.taglist就是这样一系列的结构体。那么上述的函数实际上就是把传递进来的tag与此表比较,如果tag标记相同,证明设置了此部分功能,就执行相应的解析函数。以ATAG_CMDLINE为例,就要执行:

static int __init
parse_tag_cmdline(const struct tag *tag)

{

#ifndef CONFIG_NO_TAG_CMDLINE

strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);

#endif

default_command_line[COMMAND_LINE_SIZE - 1] = '\0';

return 0;

}

这样也就是实现了把tag中的命令行参数复制到了default_command_line中。

在返回来到函数【arch/arm/kernel/setup.c】,看函数setup_arch,定义中有:

char *from = default_command_line;

说明from指向数组default_command_line。于是知道,当你完成tag解析的时候,所有传递过来的参数实际上已经复制到了相应的部分,比如命令行设置复制到了default_command_line。其他类似,看相应的解析行为函数就可以了。因为现在vivi只是传递了命令行,所以只是分析清楚这个。后面执行:

memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

这就比较容易理解了,就是将传递进来的命令行参数复制到saved_command_line,后面还可以打印出此信息。再往后的工作已经与此情景关系不大,所以不再进行详细分析。

至此,vivi与Linux kernel的参数传递情景分析就完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: