您的位置:首页 > 编程语言

内核的配置和编译及代码分析(一)

2013-10-18 13:51 399 查看
关于内核的配置和编译和uboot是一样的

1 解压缩

2 打patch

3 配置内核

4 编译

配置内核有3种方法:

1 make menuconfig这样就需要配置所有的配置项

2 使用默认的配置,在其上面做修改。在arch/arm/configs下有很多默认的配置,XXX_defconfig,可以根据你板子所

使用的芯片来选择以下相似的配置,比如make s3c2410_defconfig,之后再make menuconfig,make menuconfig是需要顶

层目录下有一个.config文件

3 使用厂家提供的 比如: 厂家_config

注意:我们一般会选用 make XXX_defconfig 然后再make menuconfig。make XXX_defconfig之后会在源码树下生成.config文件,(其实,我们也不需要执行该条命令,可以这样做,直接执行 cp /arch/arm/configs/s3c2410_defconfig .config )都是对配置项的编译与否。以一个配置项CONFIG_DM9000网卡为例,这个配置项有三种选择,CONFIG_DM9000=y,CONFIG_DM9000=m,CONFIG_DM9000=空,为y说明将这个网卡编译进内核,为m说明将这个网卡编译成模块,为空说明内核不支持该网卡。

之后我们再make menuconfig会生成两个很重要的文件,include/linux/autoconf.h和include/config/auto.conf 这两个

文件都是来源于.config文件,autoconf.h文件里此时CONFIG_DM9000=1,也即只要是在.config文件里无论编译选项

是m或者y的,都#define CONFIG_DM9000 1,autoconf.h文件会被c语言源码用到该宏。

auto.conf文件里此时CONFIG_DM9000 = y(如果你在.config文件里是y),

该文件会被顶层makefile所包含(-include include/config/auto.conf)主要是用于子目录makefile所链接需要,

如obj-$(CONFIG_DM9000) += xxx;生成的auto.conf文件与.config文件有点类似。当然,对伊厂家提供的 厂家_config,

可以直接把它变成.config文件,即cp 厂家_config .config,然后make menuconfig

当我们编译的时候用make 或者make uImage ?这时候我们就需要分析makefile文件了,非常重要的两个makefile文件,

一个是顶层makefile,一个是arch/arm/makefile当我们编译的时候如果用make的话,则会生成vmlinux,这个是真正的内核。

而make uImage,会生成uImage,是 uboot能够识别和解析的内核:也即 头部+真正的内核(vmlinux).我们在顶层makefile中

是搜索不到uImage的,可以搜到vmlinux,但是在arch/arm/makefile下可以搜到uImage的。所以顶层makefile一定是会

包含arch/arm/makefile的,如include $(srctree)/arch/$(SRCARCH)/Makefile 我们在搜索 SRCARCH
:= $(ARCH)

当然 我们可以在这写死 ARCH = arm;否则,你就要写 make uImage ARCH=arm,意思是使用arch/arm下的makefile,

另外你同样也要配置编译器连接器。。。,同样写死CROSS_COMPILE = arm-linux- ;因为后面都会用到

AS = $(CROSS_COMPILE)as

LD = $(CROSS_COMPILE)ld

CC = $(CROSS_COMPILE)gcc

CPP = $(CC) -E

AR = $(CROSS_COMPILE)ar

NM = $(CROSS_COMPILE)nm

STRIP = $(CROSS_COMPILE)strip

OBJCOPY = $(CROSS_COMPILE)objcopy

OBJDUMP = $(CROSS_COMPILE)objdump

如果你不写死,那么你编译的时候就需要 “make uImage ARCH = arm CROSS_COMPILE=arm-linux-” 所以,你打开顶层makefile

的时候一般需要将这写死,ARCH ?= $(SUBARCH)CROSS_COMPILE
?= $(CONFIG_CROSS_COMPILE:"%"=%)

改成 ARCH = arm CROSS_COMPILE = arm-linux-

export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC 这样子目录下的makefile就可以用了

在顶层makefile里搜索all:,出现all: vmlinux,这个是默认下的内核,也是真正的内核,当仅仅用make编译的时候。

我们在搜vmlinux:,出现vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

vmlinux-init := $(head-y) $(init-y)

vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)

vmlinux-all := $(vmlinux-init) $(vmlinux-main)

vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

export KBUILD_VMLINUX_OBJS := $(vmlinux-all)

在这里我们先分析下 vmlinux-init := $(head-y) $(init-y) head-y:是在arch/arm/makefile里定义的

head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o这几个是最开始调用的文件。

init-y := init/, init-y:= $(patsubst %/, %/built-in.o, $(init-y));patsubst是makefile的函数,是模式字符串替换

的函数,即将init目录下的所涉及到的文件编译成 init/built-in.o 如果我们想看到具体的编译链接规则,

可以make uImage V=1 我们现在进入arch/arm下的makefile文件 搜索uImage:

出现zImage Image xipImage bootpImage uImage: vmlinux。即我们的uImage是基于真正的内核vmlinux而来的,

而vmlinux的编译是在顶层makefile写好的。会生成两个文件,vmlinux和uImage vmlinux是在顶层目录下 。

最终生成的uImage是在arch/arm/boot目录下

顶层makefile决定了内核跟目录下哪些子目录被编译进内核,arch/arm/makefile决定了arch/arm目录下哪些文件或者

目录被编译进内核了,各级子目录下的makefile决定所在目录下哪些文件将被编译进内核,哪些文件将被编译成模块,

进入哪些子目录继续调用他们的makefile。

第一种情况下的makefile(顶层makefile)

由上:

vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

vmlinux-init := $(head-y) $(init-y)

vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)

vmlinux-all := $(vmlinux-init) $(vmlinux-main)

vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

export KBUILD_VMLINUX_OBJS := $(vmlinux-all)

head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o //在arch/arm/makefile 里定义

init-y := init/ init-y:= $(patsubst %/, %/built-in.o, $(init-y));

drivers-y := drivers/ sound/ firmware/ drivers-y
:= $(patsubst %/, %/built-in.o, $(drivers-y))

net-y := net/ net-y
:= $(patsubst %/, %/built-in.o, $(net-y))

libs-y := lib/ libs-y1
:= $(patsubst %/, %/lib.a, $(libs-y)) libs-y2
:= $(patsubst %/, %/built-in.o, $(libs-y))

core-y := usr/

core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ core-y
:= $(patsubst %/, %/built-in.o, $(core-y))

可见顶层makefile将源码树下这14个目录分成5类 init-y, drivers-y,net-y,libs-y,core-y

只有include目录,documention目录,scripts目录,没有被编译进内核,因为他们不含内核源码,

arch下各相关的架构的makefile也被编译进内核了因为arch/arm下的makefile已经被包含进了顶层makefile

include $(srctree)/arch/$(SRCARCH)/Makefile

所以编译内核的时候一次进入init-y, drivers-y,net-y,libs-y,core-y所列出来的目录执行他们目录下的makefile,

每个子目录下都会生成built-in.o文件,lib下会生成built-in.o和lib.a两个,真正的内核vmlinux就是将这些各子目录

下的built.o和lib.a文件链接生成的

对于第二种情况下的makefile(arch/arm/makefile决定了arch/arm目录下哪些文件或者目录被编译进内核了)就需

要顶层makefile中包含的auto.conf文件了

-include include/config/auto.conf //包含了很重要的有.config而make menuconfig生成的auto.conf。

auto.conf文件与.config文件非常相似,

//它只是将.config文件中的注释去掉,并根据顶层makefile中定义的变量增加一些变量而已

该文件主要是给各个子目录下的makefile使用(第二种情况和第三种情况)

core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/

core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)

core-$(CONFIG_VFP) += arch/arm/vfp/

# If we have a machine-specific directory, then include it in the build.

core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/

core-y += $(machdirs) $(platdirs)

drivers-$(CONFIG_OPROFILE) += arch/arm/oprofile/

libs-y := arch/arm/lib/ $(libs-y)

这些配置项需要auto.conf文件,同时他们又进一步扩展了core-y,libs-y的内容

对于第三种情况下的makefile(也即各子目录下的makefile)

obj-y += a.o c.o //a.c c.c文件被编译进内核,最终和当前目录下的各子目录内的built-in.o链接生成当前目录下的built-in.o文件,当前目录下的built-in.o文件又被它的上一层makefile所使用

obj-m += b.o // 将b.c一个文件编译成内核模块b.ko

// 将a.c b.c c.c三个文件编译成内核模块 yangbo.ko

obj-m += yangbo.o

yangbo-objs :=a.o b.o c.o

分析一下arch/arm下的目录结构:

boot 目录: 生成的image zimage uimage等等会放在此目录内

configs 目录: 默认的一些单板配置文件,隐形的.config文件

tools 目录: 有一个mach-types文件,很重要,定义单板的机器ID

include 目录: 头文件目录

common, kernel,mm目录:会被编译 放在core-y 这三个目录非常关键

lib目录: 库文件 被编译 放在libs-y

mach-xxx,plat-xxx目录:根据宏的定义分别从mach目录和plat目录找一个进行编译,放在core-y

nwfpe目录:根据定义宏来是否编译 若被编译,放到core-y或者core-m

vfp目录: 根据定义宏来是否编译 若被编译,放到core-y或者core-m

oprofile目录: 根据定义宏来是否编译 若被编译,放到drivers-y或者drivers-m

KCONFIG

1. 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息、

符号表的最初的内核,大小约23MB;

arm-linux-gnu-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds

arch/arm/kernel/head.o

arch/arm/kernel/init_task.o

init/built-in.o

--start-group

usr/built-in.o

arch/arm/kernel/built-in.o

arch/arm/mm/built-in.o

arch/arm/common/built-in.o

arch/arm/mach-s3c2410/built-in.o

arch/arm/nwfpe/built-in.o

kernel/built-in.o

mm/built-in.o

fs/built-in.o

ipc/built-in.o

security/built-in.o

crypto/built-in.o

lib/lib.a

arch/arm/lib/lib.a

lib/built-in.o

arch/arm/lib/built-in.o

drivers/built-in.o

sound/built-in.o

net/built-in.o

--end-group .tmp_kallsyms2.o

2. 将上面的vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/Image,这是不带多余信息的linux内核,

Image的大小约3.2MB;

命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S vmlinux arch/arm/boot/Image

3. 将 arch/arm/boot/Image 用gzip -9 压缩生成arch/arm/boot/compressed/piggy.gz大小约1.5MB;

命令:gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz

4. 编译arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小约1.5MB,这里实际上是

将piggy.gz通过piggy.S编译进piggy.o文件中。而piggy.S文件仅有6行,只是包含了文件piggy.gz;

命令:arm-linux-gnu-gcc -Wp,-MD,arch/arm/boot/compressed/.piggy.o.d -nostdinc -isystem /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/include -D__KERNEL__ -Iinclude -mlittle-endian -D__ASSEMBLY__ -Wa,-L -gdwarf-2 -mapcs-32
-mno-thumb-interwork -D__LINUX_ARM_ARCH__=4 -march=armv4 -mtune=arm9tdmi -msoft-float -c -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S

5. 依据arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目录下的文件head.o 、piggy.o 、misc.o链接

生成 arch/arm/boot/compressed/vmlinux,这个vmlinux是经过压缩且含有自解压代码的内核,大小约1.5MB;

命令:arm-linux-gnu-ld -EL --defsym zreladdr=0x30008000 --defsym params_phys=0x30000100 -p --no-undefined -X /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o
arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux

6. 将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage大小约1.5MB;

这已经是一个可以使用的linux内核映像文件了;

命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage

7. 将arch/arm/boot/zImage添加64Bytes的相关信息打包为arch/arm/boot/uImage大小约1.5MB;

命令:/bin/sh /home/farsight/Resources/kernel/linux-2.6.14/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.14' -d arch/arm/boot/zImage arch/arm/boot/uImage 在这里会用uboot工具mkimage生成uimage

小结:真正的生成的内核是vmlinux,但它包含调试信息,注释和各种符号表

这样,去掉这些东西就是Image,在通过各种压缩形式,有Image生成zImage

所以一旦执行make,会在顶层目录下生成vmlinux,在arch/arm/boot目录下生成image,zimage

若要生成uimage,则需要用zimage来生成

------------------------------------------------------------------------------------------------------------------------------------------------------------------

linux内核的启动过程分析

也分为两个阶段,第一个阶段是汇编写的,第二阶段是c语言写的

先分析第一阶段 在arch/arm/kernel/head.S

ENTRY(stext)

msr cpsr_c, #PSR_F_BIT|PSR_I_BIT|SVC_MODE //设置为svc管理模式,静止中断

mrc p15, 0, r9,c0,c0 //读取协处理器cp15的寄存器c0获得CPUID,存放在r9寄存器中注意,这里的cpu主要是

只cpu核,如arm920t,arm11eb,x86等等 r5寄存器返回一个用来描述这个处理器的结构体的地址

即proc_info_list结构

bl _lookup_processor_type //调用该函数确定内核是否支持该款cpu,如果支持,则r5返回一个描述处理器结构

的地址,r5=procinfo 否则r5等于0 该函数在arch/arm/kernel/head-common.S中定义的

movs r10, r5

beq _error_p 如果r5=0,则报错 r5寄存器返回一个用来描述这个开发板的结构体的地址 即machine_desc结构

bl _lookup_machine_type //调用该函数确定内核是否支持该款机器ID 此时我们uboot传过来的机器ID是放在r1寄存器中,返回值为r5=machinfo,也即描述该款机器结构的地址,

movs r8,r5 // 如果内核不支持该款机器,则r5=0;

beq _error_a // r5=0,则报错

内核用若干个proc_info_list结构来描述不同的cpu,也即这些都是内核所支持的,该结构体被强制定义为

段属性为.proc.info.init的结构,在vmlinux.lds中看到,

__proc_info_begin =.; //proc_info_list结构的起始地址

*(.proc.info.init)

__proc_info_end =.; //proc_info_list结构的结束地址

struct proc_info_list {

unsigned int cpu_val; // 该成员表示cpu id

unsigned int cpu_mask;

unsigned long __cpu_mm_mmu_flags; /* used by head.S */

unsigned long __cpu_io_mmu_flags; /* used by head.S */

unsigned long __cpu_flush; /* used by head.S */

const char *arch_name;

const char *elf_name;

unsigned int elf_hwcap;

const char *cpu_name;

struct processor *proc;

struct cpu_tlb_fns *tlb;

struct cpu_user_fns *user;

struct cpu_cache_fns *cache;

};

我们来分析下_lookup_processor_type函数

这个函数是来检查机器型号的,它会读取你bootloader传进来的机器ID和他能够处 理的机器ID进行比较看是否能

够处理。内核的ID号定义在arc/arm/tool/mach_types文件中MACH_TYPE_xxxx宏定义。

内核究竟就如何检查是否是它支持的机器的呢?实际上每个机器都会

在/arc/arm/mach-xxxx/smdk-xxxx.c文件中有个描述特定机器的数据结构,如下

MACHINE_START(S3C2440,"SMDK2440")

/* Maintainer: Ben Dooks<ben@fluff.org> */

.phys_io =S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.init_irq =s3c24xx_init_irq,

.map_io =smdk2440_map_io,

.init_machine = smdk2440_machine_init,

.timer =&s3c24xx_timer,

MACHINE_END

之后展开为

staticconst struct machine_desc mach_desc_S3C2440 \

__used \

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

.nr =MACH_TYPE_S3C2440, \

.name =”SMDK2440”,};

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.init_irq =s3c24xx_init_irq,

.map_io =smdk2440_map_io,

.init_machine = smdk2440_machine_init,

.timer =&s3c24xx_timer,

}

每个机器都会有一个machine_desc mach_desc结构,内核通过检查每个machine_desc mach_desc的nr 号和bootloader传上来

的ID进行比较,如果相同,内核就认为支持该机器,

而且内核在后面的工作中会调用该机器的 machine_desc mach_desc_结构中的方法进行一些初始化工作。

在arch/arm/kernel/vmlinux.lds脚本文件中

__arch_info_begin = .; //machine_desc结构体的开始地址

*(.arch.info.init)

__arch_info_end =.; //machine_desc结构体的结束地址

即所有的struct machine_desc结构都被链接进.arch.info.init段内,开始地址为__arch_info_begin,结束地址为__arch_info_end

同样在arch/arm/kernel/head-common.S中

3: .long .

.long __arch_info_begin

.long __arch_info_end

.type __lookup_machine_type,%function

__lookup_machine_type:

adr r3,3b //读入3处的运行地址也即物理地址给r3 b指back 后面的意思

ldmia r3, {r4,r5,r6} //r4= 3处的虚拟地址 r5=__arch_info_begin r6=__arch_info_end r5,r6都是在链接脚本上定义的虚拟地址,因为此时,mmu并没有使能,所以要使用物理地址

sub r3, r3, r4 // r3 = 物理地址和虚拟地址的差值

add r5, r5, r3 // r5 = 虚拟地址转化成物理地址 __arch_info_begin对应的物理地址

add r6, r6, r3 // r6 = __arch_info_end对应的物理地址

1: ldr r3,[r5,#MACHINFO_TYPE] // 此时r3= 第一个machine_desc结构体的nr成员

teq r3,r1 //比较r3和r1是否相等,r1存放的是uboot传过来的机器ID

beq 2f 如果相等,则意味着匹配,执行2处

add r5, r5, #SIZEOF_MACHINE_DESC //否则r5指向下一个machine_desc结构

cmp r5, r6 //是否比较完所有的machine_desc结构?

blo 1b //没有则继续比较,跳到1处执行

mov r5, #0 //比较完毕,没有匹配的machine_decs结构,则r5=0;

2: mov pc, lr

r5将返回__lookup_machine_type函数所确定的machine_desc结构

_

两个汇编函数lookup_processor_type和lookup_machine_type 分别用来表示内核是否支持该款CPU ID和板子 ID

内核把自己所支持的所有CPU id存放在.proc.info.init段内(vmlinux.lds中) 内核把自己所支持的所有单板结构

放在.proc.info.init段内(vmlinux.lds中)这两个汇编函数的原理都是一样的,lookup_processor_type 通过cp15协处理器

读取cpu的id存放在r9寄存器中,该函数枚举.proc.info.init段内的某proc_info_list结构的cpu_val成员与r9进行比较

若相等,则返回该proc_info_list结构的地址 _lookup_machine_type uboot在临死之前传的机器id存放在r1寄存器,

内核把自己所支持的所有机器结构放在.arch.info.init段内(vmlinux.lds中),该函数枚举段内的

某machine_desc结构的nr成员与 r1进行比较,若相等,则返回macinene_desc结构的地址

------------------------------------------------------------------------------------------------------------------------------------------------------------

现在我们来分析内核启动的第二阶段:在init/main.c中start_kernel函数

asmlinkage void __init start_kernel(void)

{

char * command_line;

extern const struct kernel_param __start___param[], __stop___param[];

smp_setup_processor_id();

/*

* Need to run as early as possible, to initialize the

* lockdep hash:

*/

lockdep_init();

debug_objects_early_init();

/*

* Set up the the initial canary ASAP:

*/

boot_init_stack_canary();

cgroup_init_early();

local_irq_disable();

early_boot_irqs_disabled = true;

/*

* Interrupts are still disabled. Do necessary setups, then

* enable them

*/

tick_init();

boot_cpu_init();

page_address_init();

printk(KERN_NOTICE "%s", linux_banner);

//打印内核版本信息,但是此时并没有打印,此时printk函数只是将打印信息放在缓冲区中,并没有打印到控制台上

(比如串口,lcd屏)上

//因为这个时候控制台还没有初始化化,在执行完console_init之后才打印输出

setup_arch(&command_line);

//同时获取从uboot传过来的命令参数放在command_line指针

//非常重要,主要用来处理uboot传过来的参数

//setup_arch函数主要目的两个:第一,解析uboot传过来的参数,第二,对于machine_desc结构体相关函数的调用

mm_init_owner(&init_mm, &init_task);

setup_command_line(command_line); //重要 拷贝command_line的值放在static_command_line

setup_nr_cpu_ids();

setup_per_cpu_areas();

smp_prepare_boot_cpu();
/* arch-specific boot-cpu hooks */

build_all_zonelists(NULL);

page_alloc_init();

printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); /

/打印命令行参数,也即uboot传过来的命令行参数

parse_early_param(); //或调用do_eary_param函数,主要是处理early_param宏

parse_args("Booting kernel", static_command_line, __start___param, //parse_args很重要,解析命令行参数,获取root=xxx的值交给unknown_bootoption来执行,这样就可以挂接根文件系统了

__stop___param - __start___param,

&unknown_bootoption);

// __setup宏和early_param宏非常相似,定义的都是同一个类型的结构体,放在同一个段里,

用parse_early_param函数来解析所有的early_param宏,用parse_args来解析所有的__setup宏

/*

* These use large bootmem allocations and must precede

* kmem_cache_init()

*/

pidhash_init();

vfs_caches_init_early();

sort_main_extable();

trap_init(); //异常向量表

mm_init();

/*

* Set up the scheduler prior starting any interrupts (such as the

* timer interrupt). Full topology setup happens at smp_init()

* time - but meanwhile we still have a functioning scheduler.

*/

sched_init();

/*

* Disable preemption - early bootup scheduling is extremely

* fragile until we cpu_idle() for the first time.

*/

preempt_disable();

if (!irqs_disabled()) {

printk(KERN_WARNING "start_kernel(): bug: interrupts were "

"enabled *very* early, fixing it\n");

local_irq_disable();

}

idr_init_cache();

perf_event_init();

rcu_init();

radix_tree_init();

/* init some links before init_ISA_irqs() */

early_irq_init();

init_IRQ(); //中断的初始化

prio_tree_init();

init_timers();

hrtimers_init();

softirq_init();

timekeeping_init();

time_init();

profile_init();

if (!irqs_disabled())

printk(KERN_CRIT "start_kernel(): bug: interrupts were "

"enabled early\n");

early_boot_irqs_disabled = false;

local_irq_enable();

/* Interrupts are enabled now so all GFP allocations are safe. */

gfp_allowed_mask = __GFP_BITS_MASK;

kmem_cache_init_late();

/*

* HACK ALERT! This is early. We're enabling the console before

* we've done PCI setups etc, and console_init() must be aware of

* this. But we do want output early, in case something goes wrong.

*/

console_init(); //执行到此处,内核才打印出内核版本信息。。。

if (panic_later)

panic(panic_later, panic_param);

lockdep_info();

/*

* Need to run this when irqs are enabled, because it wants

* to self-test [hard/soft]-irqs on/off lock inversion bugs

* too:

*/

locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD

if (initrd_start && !initrd_below_start_ok &&

page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {

printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "

"disabling it.\n",

page_to_pfn(virt_to_page((void *)initrd_start)),

min_low_pfn);

initrd_start = 0;

}

#endif

page_cgroup_init();

enable_debug_pagealloc();

debug_objects_mem_init();

kmemleak_init();

setup_per_cpu_pageset();

numa_policy_init();

if (late_time_init)

late_time_init();

sched_clock_init();

calibrate_delay();

pidmap_init();

anon_vma_init();

#ifdef CONFIG_X86

if (efi_enabled)

efi_enter_virtual_mode();

#endif

thread_info_cache_init();

cred_init();

fork_init(totalram_pages);

proc_caches_init();

buffer_init();

key_init();

security_init();

dbg_late_init();

vfs_caches_init(totalram_pages); //该函数第三处最重要的地方,在内存中建立了一颗vfs目录树

signals_init();

/* rootfs populating might need page-writeback */

page_writeback_init();

#ifdef CONFIG_PROC_FS

proc_root_init();

#endif

cgroup_init();

cpuset_init();

taskstats_init_early();

delayacct_init();

check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */

sfi_init_late();

ftrace_init();

/* Do the rest non-__init'ed, we're now alive */

rest_init(); // 很重要。

}

分析setup_arch函数 是在arch/arm/kernel/setup.c中

static struct init_tags {

struct tag_header hdr1; 第一个tag

struct tag_core core;

struct tag_header hdr2; 第二个tag

struct tag_mem32 mem;

struct tag_header hdr3; 第三个tag

} init_tags __initdata = {

{ tag_size(tag_core), ATAG_CORE }, ATAG_CORE为tag其实设置标记

{ 1, PAGE_SIZE, 0xff },

{ tag_size(tag_mem32), ATAG_MEM },

{ MEM_SIZE, PHYS_OFFSET },

{ 0, ATAG_NONE } ATAG_NONE为结束标记

};

该内核版本有点老了~ 分析较新的内核

void __init setup_arch(char **cmdline_p)

{

struct tag *tags = (struct tag *)&init_tags;

struct machine_desc *mdesc;

char *from = default_command_line;//通过parse_tags函数中的__tagtable(ATAG_CMDLINE, parse_tag_cmdline);会将命令行字符串拷贝到default_command_line,见后

init_tags.mem.start = PHYS_OFFSET; 设置内存的起始地址

unwind_init();

setup_processor();

//处理器相关的设置,它会再次调用内核第一阶段的lookup_processor_type的函数,

以获得该处理器的proc_info_list结构

mdesc = setup_machine(machine_arch_type);

//该函数会再次调用内核启动第一阶段的lookup_machine_type函数,根据当前的机器ID确定这款板子machine_desc

machine_desc = mdesc;

machine_name = mdesc->name;

if (mdesc->soft_reboot)

reboot_setup("s");

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

else if (mdesc->boot_params) {

#ifdef CONFIG_MMU

/*

* We still are executing with a minimal MMU mapping created

* with the presumption that the machine default for this

* is located in the first MB of RAM. Anything else will

* fault and silently hang the kernel at this point.

*/

if (mdesc->boot_params < PHYS_OFFSET ||

//若是定义了启动参数的地址? 若是设置的启动参数地址小于内存起始地址,但在离内存起始地址超过1M的地方

,则表示出错

mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {

printk(KERN_WARNING

"Default boot params at physical 0x%08lx out of reach\n",

mdesc->boot_params);

} else

#endif

{ // 执行此处

tags = phys_to_virt(mdesc->boot_params);

//tags 这是地址就是tag列表中的首地址,因为mmu已经使能,所以要将物理地址转换为虚拟地址

}

}

#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)

/*

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

* a tag list.

*/

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

convert_to_tag_list(tags);

#endif

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

tags = (struct tag *)&init_tags;

if (mdesc->fixup) //调用mdesc的fixup结构

mdesc->fixup(mdesc, tags, &from, &meminfo);

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

if (meminfo.nr_banks != 0) //如果在内核中已经定义了meminfo结构

squash_mem_tags(tags); //则忽略内存tag

save_atags(tags);

parse_tags(tags);

//开始解析和处理每个tag,进过解析命令行参数的时候 from获得从uboot传过来的命令行参数的值

}

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;

/* parse_early_param needs a boot_command_line */

strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

//所以 from boot_command_line cmd_line值是一样的,都是从uboot传过来的命令行参数

/* populate cmd_line too for later use, preserving boot_command_line */

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

*cmdline_p = cmd_line;

//此时setup_arch函数将通过cmdling_p这个二级指针获取uboot传过来的命令行参数

parse_early_param();

//解析early_param参数 对于early_param("mem", early_mem); early_param该宏表示,如果命令行字符串中出现mem,就用early_mem函数来处理,见后

arm_memblock_init(&meminfo, mdesc);

paging_init(mdesc); // 重新初始化页表,此处paging_init-->devicemaps_init--->mdesc->map_io

request_standard_resources(mdesc);

#ifdef CONFIG_SMP

if (is_smp())

smp_init_cpus();

#endif

reserve_crashkernel();

cpu_init();

tcm_init();

#ifdef CONFIG_MULTI_IRQ_HANDLER

handle_arch_irq = mdesc->handle_irq;

#endif

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

conswitchp = &dummy_con;

#endif

#endif

early_trap_init();

if (mdesc->init_early)

mdesc->init_early();

}

===============================================新内核==================================================

较新的内核分析:

static struct init_tags {

struct tag_header hdr1; 第一个tag

struct tag_core core;

struct tag_header hdr2; 第二个tag

struct tag_mem32 mem;

struct tag_header hdr3; 第三个tag

}

init_tags __initdata = {

{ tag_size(tag_core), ATAG_CORE }, ATAG_CORE为tag其实设置标记

{ 1, PAGE_SIZE, 0xff },

{ tag_size(tag_mem32), ATAG_MEM },

{ MEM_SIZE, PHYS_OFFSET },

{ 0, ATAG_NONE } ATAG_NONE为结束标记

};

void __init setup_arch(char **cmdline_p)

{

struct tag *tags = (struct tag *)&init_tags;

struct machine_desc *mdesc;

char *from = default_command_line;

unwind_init();

setup_processor(); // 见下 获取该cpu结构proc_info_list结构

mdesc = setup_machine(machine_arch_type);见下 获取该单板结构machine_desc

machine_name = mdesc->name;

if (mdesc->soft_reboot)

reboot_setup("s");

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

else if (mdesc->boot_params)

tags = phys_to_virt(mdesc->boot_params);

// 执行 uboot临终之前传给内核的启动参数地址都是以struct tag *形式

/*

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

* a tag list.

*/

if (tags->hdr.tag != ATAG_CORE) 不执行

convert_to_tag_list(tags);

if (tags->hdr.tag != ATAG_CORE) 不执行

tags = (struct tag *)&init_tags;

if (mdesc->fixup) 执行

mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) { 执行

if (meminfo.nr_banks != 0)

squash_mem_tags(tags);

save_atags(tags);

parse_tags(tags); 开始解析tag 这个是该函数最重要的函数

}

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;

/* parse_early_param needs a boot_command_line */

strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

/* populate cmd_line too for later use, preserving boot_command_line */

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

*cmdline_p = cmd_line;

parse_early_param();

paging_init(mdesc);

request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP

smp_init_cpus();

#endif

cpu_init();

tcm_init();

/*

* Set up various architecture-specific pointers

*/

init_arch_irq = mdesc->init_irq;

system_timer = mdesc->timer;

init_machine = mdesc->init_machine;

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

conswitchp = &dummy_con;

#endif

#endif

early_trap_init();

}

extern struct proc_info_list *lookup_processor_type(unsigned int);

extern struct machine_desc *lookup_machine_type(unsigned int);

static void __init setup_processor(void)

{

struct proc_info_list *list;

/*

* locate processor in the list of supported processor

* types. The linker builds this table for us from the

* entries in arch/arm/mm/proc-*.S

*/

list = lookup_processor_type(read_cpuid_id());// 根据cpu id获得内核所支持的cpu结构proc_info_list

if (!list) {

printk("CPU configuration botched (ID %08x), unable "

"to continue.\n", read_cpuid_id());

while (1);

}

cpu_name = list->cpu_name;

#ifdef MULTI_CPU

processor = *list->proc;

#endif

#ifdef MULTI_TLB

cpu_tlb = *list->tlb;

#endif

#ifdef MULTI_USER

cpu_user = *list->user;

#endif

#ifdef MULTI_CACHE

cpu_cache = *list->cache;

#endif

printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",

cpu_name, read_cpuid_id(), read_cpuid_id() & 15,

proc_arch[cpu_architecture()], cr_alignment);

sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);

sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);

elf_hwcap = list->elf_hwcap;

#ifndef CONFIG_ARM_THUMB

elf_hwcap &= ~HWCAP_THUMB;

#endif

cacheid_init();

cpu_proc_init();

}

static struct machine_desc * __init setup_machine(unsigned int nr)

{

struct machine_desc *list;

/*

* locate machine in the list of supported machines.

*/

list = lookup_machine_type(nr); // 根据机器id号来获取该款机器的单板结构machine_desc

if (!list) {

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

"to continue.\n", nr);

while (1);

}

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

return list;

}

=================================================================================================================

#define __tag __used __attribute__((__section__(".taglist.init"))) 即__tagtable(tag, fn)宏定义个段属性为.taglist.init的一个结构体struct tagtable

#define __tagtable(tag, fn) \

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

以下函数是解析每一个TAG

static void __init parse_tags(const struct tag *t)

{

for (; t->hdr.size; t = tag_next(t))

if (!parse_tag(t))

printk(KERN_WARNING

"Ignoring unrecognised tag 0x%08x\n",

t->hdr.tag);

}

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;

}

parse_tags(tags)----->parse_tag()----->__tagtable()

//即为每种TAG定义了相应的处理函数

__tagtable(ATAG_CORE, parse_tag_core);

__tagtable(ATAG_MEM, parse_tag_mem32); //内存TAG

__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);

__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);

__tagtable(ATAG_SERIAL, parse_tag_serialnr);

__tagtable(ATAG_REVISION, parse_tag_revision);

__tagtable(ATAG_CMDLINE, parse_tag_cmdline); //命令行TAG

以上比较重要的两个,内存TAG和命令行TAG

parse_tag_mem32 该函数根据tag定义的起始地址长度和大小,在全局结构体变量meminfo中增加内存的描述信息,

以后内核就可以通过meminfo了解开发板的内存信息

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

它只是简单的将命令行字符串复制到default_command_line中保存下来,做后续处理

下面看两个非常重要的宏__setup(str, fn) 和 early_param(str, fn),他们是在include/linux/init.h中定义

__setup(str,fn) 定义了一个段属性为 .init.setup的结构体,成员分别是 str,fn,early(为0)

early_param(str,fn) 也同样定义了一个段属性为.init.setup的结构体,成员是 str,fn,early(为1)

在链接脚本vmlinux.lds定义了该段

_setup_start=.;

*(.init.setup)

_setup_end=.;

即所有的__setup(str,fn),early_param(str,fn)两个宏所定义的结构体都被链接进这个段中,

我们可以在源码中搜索_setup_start _setup_end这两个地址,以下会被用到

#define __setup_param(str, unique_id, fn, early)
\

static const char __setup_str_##unique_id[] __initconst
\

__aligned(1) = str; \

static struct obs_kernel_param __setup_##unique_id
\

__used __section(.init.setup)
\

__attribute__((aligned((sizeof(long)))))
\

= { __setup_str_##unique_id, fn, early }

#define __setup(str, fn) \

__setup_param(str, fn, fn, 0)

#define early_param(str, fn) \

__setup_param(str, fn, fn, 1)

struct obs_kernel_param {

const char *str;

int (*setup_func)(char *);

int early;

};

即_setup宏和early_param宏定义了一个段属性为.init.setup的结构体obs_kernel_param

/* Relies on boot_command_line being set */

void __init parse_early_param(void);

void __init parse_early_options(char *cmdline);

命令行参数中几个比较重要的_setup宏 _setup宏主要定义在init/do_mounts.c和init/main.c中 当我们遇到

parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,

&unknown_bootoption)------>unknown_bootoption------->

obsolete_checksetup(命令行参数指针)---->命令行字符串中有的,处理所有的_setup宏

以下是在init/do_mounts.c文件中定义的

static char __initdata saved_root_name[64]; //定义一个saved_root_name的字符串数组

static int __init root_dev_setup(char *line)

{

strlcpy(saved_root_name, line, sizeof(saved_root_name));

return 1;

}

__setup("root=", root_dev_setup);// 将命令行root=xxx中 xxx值赋给saved_root_name

init/main.c中

static int __init init_setup(char *str)

{

unsigned int i;

execute_command = str;

/*

* In case LILO is going to boot us with default command line,

* it prepends "auto" before the whole cmdline which makes

* the shell think it should execute a script with such name.

* So we ignore all arguments entered _before_ init=... [MJ]

*/

for (i = 1; i < MAX_INIT_ARGS; i++)

argv_init[i] = NULL;

return 1;

}

__setup("init=", init_setup); 命令行参数中出现init=

static int __init rdinit_setup(char *str)

{

unsigned int i;

ramdisk_execute_command = str;

/* See "auto" comment in init_setup */

for (i = 1; i < MAX_INIT_ARGS; i++)

argv_init[i] = NULL;

return 1;

}

__setup("rdinit=", rdinit_setup); 命令行参数中出现rdinit=

那么是什么时候会调用这个root_dev_setup这个函数指针呢

有之前的分析可以知道,__setup宏定义个一个段属性为.init.setup的结构体,我们搜索_setup_start _setup_end

第一个obsolete_checksetup函数(init/main.c)

static int __init obsolete_checksetup(char *line) 该参数是uboot传过来的命令行参数的指针

{

const struct obs_kernel_param *p;

int had_early_param = 0;

p = __setup_start; p所指向的结构是内核存储的所有的可能设计到的命令行参数

do {

int n = strlen(p->str);

if (!strncmp(line, p->str, n)) {

if (p->early) { 处理early_param宏

/* Already done in parse_early_param?

* (Needs exact match on param part).

* Keep iterating, as we can have early

* params and __setups of same names 8( */

if (line
== '\0' || line
== '=')

had_early_param = 1;

} else if (!p->setup_func) {

printk(KERN_WARNING "Parameter %s is obsolete,"

" ignored\n", p->str);

return 1;

} else if (p->setup_func(line + n))

//在这里解析了所有__setup宏定义的结构体,即该结构体包含的函数会被执行,比如__setup("root=", root_dev_setup) __setup("init=", init_setup) ,会依次执行root_dev_setup init_setup函数

//在这里调用了真正的root_dev_setup函数,参数是root=后面的值,这样我们的saved_root_name就获得了这个参数值

return 1;

}

p++;

} while (p < __setup_end);

return had_early_param;

}

而obsolete_checksetup函数是被同一个文件中的unknown_bootoption函数所调用,而unknown_bootoption函数

是在start_kernel中被调用 parse_args("Booting kernel", static_command_line, __start___param,

__stop___param - __start___param,

&unknown_bootoption); 这样我们在此处就获得了root=xxx的把值存放在saved_root_name

在arch/arm/kernel/中生成的vmlinux.lds中

OUTPUT_ARCH(arm)

ENTRY(stext)

jiffies = jiffies_64;

SECTIONS

{

. = 0xC0000000 + 0x00008000;

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

_stext = .;

_sinittext = .;

*(.head.text)

*(.init.text) *(.cpuinit.text) *(.meminit.text)

_einittext = .;

__proc_info_begin = .; 所有的cpu结构proc_info_list 存放在该段

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .; 所有的单板结构machine_desc 存放在该段 宏MECHINE_START和MACHINE_END结构表示 即所有的MECHINE_START和MACHINE_END宏定义的全部放在这里

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .; 所有的解析tag结构tag_table存放在该段 用宏_tagtable来表示该结构 内核所有能涉及到的tag全部储存在这里,即所有的_tagtable宏全部放在这里

*(.taglist.init)

__tagtable_end = .;

. = ALIGN(16);

__setup_start = .; 所有的解析命令行参数obs_kernel_param结构体存放在该段 用宏 _setup和early_param来表示 内核所有能处理的命令行字符串全部存储在这两个宏里,即_setup和early_param宏定义的全部放在这里

*(.init.setup)

__setup_end = .;

//小结:parse_tags是解析所有的uboot传过来的TAG参数 执行所有的__tagtable()

parse_args是解析命令行参数的 执行所有的__setup()

parse_early_param是解析early_param(),实际是也会调用parse_args函数

====================================================================================================================================================================

我们继续分析内核启动应用程序的流程

start_kernel函数快结束的时候调用了rest_init函数(在init/main.c中定义)

stat_kernel

-->rest_init

-->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

--->prepare_namespace()

-->mount_root()

挂接好根文件系统

---->init_post()

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh"); //执行应用程序

在内核和 initrd映像被解压并拷贝到内存中之后,内核就会被调用了。它会执行不同的初始化操作,最终您会发现

自己到了init/main.c:init()(subdir/file:function)函数中。这个函数执行了大量的子系统初始化操作。此处会执行

一个对init/do_mounts.c:prepare_namespace() 的调用,这个函数用来准备名称空间(挂载 dev 文件系统、RAID或 md、

设备以及最后的 initrd)。加载 initrd 是通过调用init/do_mounts_initrd.c:initrd_load() 实现的。

initrd_load() 函数调用了init/do_mounts_rd.c:rd_load_image(),它通过调用init/do_mounts_rd.c:identify_ramdisk_image()

来确定要加载哪个 RAM磁盘。这个函数会检查映像文件的 magic 号来确定它是 minux、etc2、romfs、cramfs 或 gzip 格式。

在返回到initrd_load_image 之前,它还会调用 init/do_mounts_rd:crd_load()。这个函数负责为 RAM磁盘分配空间,

并计算循环冗余校验码(CRC),然后对 RAM磁盘映像进行解压,并将其加载到内存中。现在,我们在一个适合挂载的

块设备中就有了这个 initrd 映像。

现在使用一个 init/do_mounts.c:mount_root()调用将这个块设备挂载到根文件系统上。它会创建根设备,并调用

init/do_mounts.c:mount_block_root()。在这里调用init/do_mounts.c:do_mount_root(),后者又会

调用 fs/namespace.c:sys_mount()来真正挂载根文件系统,然后 chdir 到这个文件系统中。这就是我们在清单 6 中

所看到的熟悉消息 VFS: Mounted root(ext2 file system). 的地方。

最后,返回到 init 函数中,并调用init/main.c:run_init_process。这会导致调用 execve 来启动 init 进程

(在本例中是/linuxrc)。linuxrc 可以是一个可执行程序,也可以是一个脚本

void __init prepare_namespace(void)

{

int is_floppy;

if (root_delay) {

printk(KERN_INFO "Waiting %dsec before mounting root device...\n",

root_delay);

ssleep(root_delay);

}

/*

* wait for the known devices to complete their probing

*

* Note: this is a potential source of long boot delays.

* For example, it is not atypical to wait 5 seconds here

* for the touchpad of a laptop to initialize.

*/

wait_for_device_probe();

md_run_setup();

if (saved_root_name[0]) {

root_device_name = saved_root_name; //注意 saved_root_name此时已经有值了

if (!strncmp(root_device_name, "mtd", 3) ||

!strncmp(root_device_name, "ubi", 3)) {

mount_block_root(root_device_name, root_mountflags);

goto out;

}

ROOT_DEV = name_to_dev_t(root_device_name); //ROOT_DEV

if (strncmp(root_device_name, "/dev/", 5) == 0)

root_device_name += 5;

}

if (initrd_load()) //加载initrd 初始化内存盘文件系统

goto out;

/* wait for any asynchronous scanning to complete */

if ((ROOT_DEV == 0) && root_wait) {

printk(KERN_INFO "Waiting for root device %s...\n",

saved_root_name);

while (driver_probe_done() != 0 ||

(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)

msleep(100);

async_synchronize_full();

}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (is_floppy && rd_doload && rd_load_disk(0))

ROOT_DEV = Root_RAM0;

mount_root(); //挂接根文件系统

out:

devtmpfs_mount("dev");

sys_mount(".", "/", NULL, MS_MOVE, NULL);

sys_chroot((const char __user __force *)".");

}

挂接根文件系统之后,开始启动第一个进程init

static noinline int init_post(void)

{

/* need to finish all async __init code before freeing the memory */

async_synchronize_full();

free_initmem();

mark_rodata_ro();

system_state = SYSTEM_RUNNING;

numa_default_policy();

current->signal->flags |= SIGNAL_UNKILLABLE;

if (ramdisk_execute_command) { 所以如果uboot传过来的命令行参数有rdinit=xxx,则会执行

run_init_process(ramdisk_execute_command);

printk(KERN_WARNING "Failed to execute %s\n",

ramdisk_execute_command);

}

/*

* We try each of these until one succeeds.

*

* The Bourne shell can be used instead of init if we are

* trying to recover a really broken machine.

*/

if (execute_command) { 所以如果uboot传过来的命令行参数有init=xxx,则会执行

run_init_process(execute_command);

printk(KERN_WARNING "Failed to execute %s. Attempting "

"defaults...\n", execute_command);

}

run_init_process("/sbin/init");

//如果uboot传过来的命令行参数没有init=xxx或者rdinit=xxx,则会执行该进程,一去不复返,后面的就不会执行了

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

panic("No init found. Try passing init= option to kernel. "

"See Linux Documentation/init.txt for guidance.");

}

static int __init rdinit_setup(char *str)

{

unsigned int i;

ramdisk_execute_command = str; //ramdisk_execute_command获取了rdinit= xxx 的值

/* See "auto" comment in init_setup */

for (i = 1; i < MAX_INIT_ARGS; i++)

argv_init[i] = NULL;

return 1;

}

__setup("rdinit=", rdinit_setup);

static int __init init_setup(char *str)

{

unsigned int i;

execute_command = str; //execute_command获取了init=xxx的值

/*

* In case LILO is going to boot us with default command line,

* it prepends "auto" before the whole cmdline which makes

* the shell think it should execute a script with such name.

* So we ignore all arguments entered _before_ init=... [MJ]

*/

for (i = 1; i < MAX_INIT_ARGS; i++)

argv_init[i] = NULL;

return 1;

}

__setup("init=", init_setup);

-----------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------

do_basic_setup函数

分析include/linux/init.h

#define __init __section(.init.text) __cold notrace 即__init宏

#define __initdata __section(.init.data)

#define __initconst __section(.init.rodata)

#define __exitdata __section(.exit.data)

#define __exit_call __used __section(.exitcall.exit) 后面会用到 该宏也即定义了“丢弃段”

/* modpost check for section mismatches during the kernel build.

* A section mismatch happens when there are references from a

* code or data section to an init section (both code or data).

* The init sections are (for most archs) discarded by the kernel

* when early init has completed so all such references are potential bugs.

* For exit sections the same issue exists.

* The following markers are used for the cases where the reference to

* the *init / *exit section (code or data) is valid and will teach

* modpost not to issue a warning.

* The markers follow same syntax rules as __init / __initdata. */

#define __ref __section(.ref.text) noinline

#define __refdata __section(.ref.data)

#define __refconst __section(.ref.rodata)

/* compatibility defines */

#define __init_refok __ref

#define __initdata_refok __refdata

#define __exit_refok __ref

#ifdef MODULE

#define __exitused

#else

#define __exitused __used

#endif

#define __exit __section(.exit.text) __exitused __cold

/* Used for HOTPLUG */

#define __devinit __section(.devinit.text) __cold

#define __devinitdata __section(.devinit.data)

#define __devinitconst __section(.devinit.rodata)

#define __devexit __section(.devexit.text) __exitused __cold

#define __devexitdata __section(.devexit.data)

#define __devexitconst __section(.devexit.rodata)

/* Used for HOTPLUG_CPU */

#define __cpuinit __section(.cpuinit.text) __cold

#define __cpuinitdata __section(.cpuinit.data)

#define __cpuinitconst __section(.cpuinit.rodata)

#define __cpuexit __section(.cpuexit.text) __exitused __cold

#define __cpuexitdata __section(.cpuexit.data)

#define __cpuexitconst __section(.cpuexit.rodata)

/* Used for MEMORY_HOTPLUG */

#define __meminit __section(.meminit.text) __cold

#define __meminitdata __section(.meminit.data)

#define __meminitconst __section(.meminit.rodata)

#define __memexit __section(.memexit.text) __exitused __cold

#define __memexitdata __section(.memexit.data)

#define __memexitconst __section(.memexit.rodata)

/* For assembly routines */

#define __HEAD .section
".head.text","ax"

#define __INIT .section
".init.text","ax"

#define __FINIT .previous

#define __INITDATA .section
".init.data","aw"

#define __INITRODATA .section
".init.rodata","a"

#define __FINITDATA .previous

#define __DEVINIT .section ".devinit.text", "ax"

#define __DEVINITDATA .section ".devinit.data", "aw"

#define __DEVINITRODATA .section ".devinit.rodata", "a"

#define __CPUINIT .section ".cpuinit.text", "ax"

#define __CPUINITDATA .section ".cpuinit.data", "aw"

#define __CPUINITRODATA .section ".cpuinit.rodata", "a"

#define __MEMINIT .section ".meminit.text", "ax"

#define __MEMINITDATA .section ".meminit.data", "aw"

#define __MEMINITRODATA .section ".meminit.rodata", "a"

/* silence warnings when references are OK */

#define __REF .section ".ref.text", "ax"

#define __REFDATA .section ".ref.data", "aw"

#define __REFCONST .section ".ref.rodata", "a"

#ifndef __ASSEMBLY__

/*

* Used for initialization calls..

*/

typedef int (*initcall_t)(void);

typedef void (*exitcall_t)(void);

extern initcall_t __con_initcall_start[], __con_initcall_end[];

extern initcall_t __security_initcall_start[], __security_initcall_end[];

/* Used for contructor calls. */

typedef void (*ctor_fn_t)(void);

/* Defined in init/main.c */

extern int do_one_initcall(initcall_t fn);

extern char __initdata boot_command_line[];

extern char *saved_command_line;

extern unsigned int reset_devices;

/* used by init/main.c */

void setup_arch(char **);

void prepare_namespace(void);

extern void (*late_time_init)(void);

extern int initcall_debug;

#endif

#ifndef MODULE 如果驱动模块静态编译进内核

#ifndef __ASSEMBLY__

/* initcalls are now grouped by functionality into separate

* subsections. Ordering inside the subsections is determined

* by link order.

* For backwards compatibility, initcall() puts the call in

* the device init subsection.

*

* The `id' arg to __define_initcall() is needed so that multiple initcalls

* can point at the same handler without causing duplicate-symbol build errors.

*/

typedef int (*initcall_t)(void); /*定义函数指针类型*/

extern initcall_t __initcall_start, __initcall_end; /*申明外部变量,在ld的脚本文件中定义*/

非常重要的宏 即该宏定义个段属性为.initcall" level ".init的一个initcall_t类型的函数指针

#define __define_initcall(level,fn,id) \

static initcall_t __initcall_##fn##id __used \

__attribute__((__section__(".initcall" level ".init"))) = fn

/*

* Early initcalls run before initializing SMP.

*

* Only for built-in code, not modules.

*/

#define early_initcall(fn) __define_initcall("early",fn,early)

/*

* A "pure" initcall has no dependencies on anything else, and purely

* initializes variables that couldn't be statically initialized.

*

* This only exists for built-in code, not for modules.

*/

#define pure_initcall(fn) __define_initcall("0",fn,0)

#define core_initcall(fn) __define_initcall("1",fn,1) 定义了一个.initcall1.init的段属性的函数指针_initcall_##fn##id 即core_initcall定义的函数fn全部放在.initcall1.init段中

#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)

#define postcore_initcall(fn) __define_initcall("2",fn,2) 定义了一个.initcall2.init的段属性

#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

#define arch_initcall(fn) __define_initcall("3",fn,3) // 重要 arch_initcall(customize_machine); 有的芯片在这个函数中进行设备的注册,执行machine_desc-
>init_machine();

#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)

#define subsys_initcall(fn) __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#define fs_initcall(fn) __define_initcall("5",fn,5)

#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) //重要 rootfs_initcall(populate_rootfs)

#define device_initcall(fn) __define_initcall("6",fn,6) //此处初始化了静态编译的驱动模块 优先级排在第6位

#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

#define late_initcall(fn) __define_initcall("7",fn,7) //在mtk平台上mt6575_board.c文件中定义了late_initcall(board_init); 而board_init---mt6575_board_init

在该函数中对各平台设备进行注册,也即在执行此刻的时候,会执行各platform_driver的probe函数

#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)

#define __initcall(fn) device_initcall(fn) //此处初始化了静态编译的驱动模块

#define __exitcall(fn) \

static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \

static initcall_t __initcall_##fn \

__used __section(.con_initcall.init) = fn

#define security_initcall(fn) \

static initcall_t __initcall_##fn \

__used __section(.security_initcall.init) = fn

struct obs_kernel_param {

const char *str;

int (*setup_func)(char *);

int early;

};

/*

* Only for really core code. See moduleparam.h for the normal way.

*

* Force the alignment so the compiler doesn't space elements of the

* obs_kernel_param "array" too far apart in .init.setup.

*/

#define __setup_param(str, unique_id, fn, early)
\

static const char __setup_str_##unique_id[] __initconst
\

__aligned(1) = str; \

static struct obs_kernel_param __setup_##unique_id
\

__used __section(.init.setup)
\

__attribute__((aligned((sizeof(long)))))
\

= { __setup_str_##unique_id, fn, early }

#define __setup(str, fn) \

__setup_param(str, fn, fn, 0)

/* NOTE: fn is as per module_param, not __setup! Emits warning if fn

* returns non-zero. */

#define early_param(str, fn) \

__setup_param(str, fn, fn, 1)

/* Relies on boot_command_line being set */

void __init parse_early_param(void);

void __init parse_early_options(char *cmdline);

#endif /* __ASSEMBLY__ */

/**

* module_init() - driver initialization entry point

* @x: function to be run at kernel boot time or module insertion

*

* module_init() will either be called during do_initcalls() (if

* builtin) or at module insertion time (if a module). There can only

* be one per module.

*/

#define module_init(x) __initcall(x); //此处初始化了静态编译的驱动模块 在这里真正的确定了module_init

即所有的模块入口函数,有module_init宏定义的各函数都全部被连接进

.initcall6.init段内

/**

* module_exit() - driver exit entry point

* @x: function to be run when driver is removed

*

* module_exit() will wrap the driver clean-up code

* with cleanup_module() when used with rmmod when

* the driver is a module. If the driver is statically

* compiled into the kernel, module_exit() has no effect.

* There can only be one per module.

*/

#define module_exit(x) __exitcall(x);

//此处所有模块的卸载出口函数,即module_exit宏定义的函数都放在“丢弃段”.exitcall.exit内

#else /* MODULE */ 如果驱动模块动态加载入内核

/* Don't use these in modules, but some people do... */

#define early_initcall(fn) module_init(fn)

#define core_initcall(fn) module_init(fn)

#define postcore_initcall(fn) module_init(fn)

#define arch_initcall(fn) module_init(fn)

#define subsys_initcall(fn) module_init(fn)

#define fs_initcall(fn) module_init(fn)

#define device_initcall(fn) module_init(fn)

#define late_initcall(fn) module_init(fn)

#define security_initcall(fn) module_init(fn)

/* Each module must use one module_init(). */

#define module_init(initfn) \

static inline initcall_t __inittest(void)
\

{ return initfn; }
\

int init_module(void) __attribute__((alias(#initfn)));

//insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)

//将动态驱动模块载入到内核空间

通过alias将initfn变名为init_module 通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,

在系统内部会调用sys_init_module()去找到init_module函数的入口地址。

/* This is only required if you want to be unloadable. */

#define module_exit(exitfn) \

static inline exitcall_t __exittest(void)
\

{ return exitfn; }
\

void cleanup_module(void) __attribute__((alias(#exitfn)));

#define __setup_param(str, unique_id, fn) /* nothing */

#define __setup(str, func) /* nothing */

#endif

/* Data marked not to be saved by software suspend */

#define __nosavedata __section(.data..nosave)

/* This means "can be init if no module support, otherwise module load

may call it." */

#ifdef CONFIG_MODULES

#define __init_or_module

#define __initdata_or_module

#else

#define __init_or_module __init

#define __initdata_or_module __initdata

#endif /*CONFIG_MODULES*/

/* Functions marked as __devexit may be discarded at kernel link time, depending

on config options. Newer versions of binutils detect references from

retained sections to discarded sections and flag an error. Pointers to

__devexit functions must use __devexit_p(function_name), the wrapper will

insert either the function_name or NULL, depending on the config options.

*/

#if defined(MODULE) || defined(CONFIG_HOTPLUG)

#define __devexit_p(x) x

#else

#define __devexit_p(x) NULL

#endif

#ifdef MODULE

#define __exit_p(x) x

#else

#define __exit_p(x) NULL

#endif

#endif /* _LINUX_INIT_H */

-----------------------------------------------------------------------------

在arch/arm/kernel/vmlinux.lds

SECTIONS

{

. = 0xC0000000 + 0x00008000;

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

_stext = .;

_sinittext = .;

*(.head.text)

*(.init.text) *(.cpuinit.text) *(.meminit.text)

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist.init)

__tagtable_end = .;

. = ALIGN(16); __setup_start = .;

*(.init.setup)

__setup_end = .;

__initcall_start = .; 从这里开始存放所有的initcall_t类型的函数指针 一旦执行do_initcalls函数,则即执行__initcall_start到__initcall_end段所有的函数

*(.initcallearly.init)

__early_initcall_end = .;

*(.initcall0.init)

*(.initcall0s.init)

*(.initcall1.init)

*(.initcall1s.init)

*(.initcall2.init)

*(.initcall2s.init)

*(.initcall3.init)

*(.initcall3s.init)

*(.initcall4.init)

*(.initcall4s.init)

*(.initcall5.init)

*(.initcall5s.init)

*(.initcallrootfs.init)

*(.initcall6.init)

*(.initcall6s.init)

*(.initcall7.init)

*(.initcall7s.init)

__initcall_end = .;

__con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;

__security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;

. = ALIGN((1 << 12));

__initramfs_start = .; 三种initrd中(cpio-initrd,image-initrd,initramfs)的一种,即initramfs,它是将initramfs initrd连接进linux内核中的.init.ramfs段里

*(.init.ramfs)

__initramfs_end = .;

__init_begin = _stext;

在init/main.c中函数 do_basic_setup---->do_initcalls

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)

{

initcall_t *fn;

for (fn = __early_initcall_end; fn < __initcall_end; fn++)

do_one_initcall(*fn);

/* Make sure there is no pending stuff from the initcall sequence */

flush_scheduled_work();

}

/*

* Ok, the machine is now initialized. None of the devices

* have been touched yet, but the CPU subsystem is up and

* running, and memory and process management works.

*

* Now we can finally start doing some real work..

*/

static void __init do_basic_setup(void)

{

init_workqueues();

cpuset_init_smp();

usermodehelper_init();

init_tmpfs();

driver_init();

init_irq_proc();

do_ctors();

do_initcalls(); 条用该函数

}

Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;

压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数 decompress_kernel()<./arch/arm/boot/compressed/misc.c>解压,打印 “Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."

然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:

do_initcalls函数执行到这里,调用两个非常重要的函数中的一个 rootfs_initcall(default_rootfs); 在noinitramfs.c中 没定义CONFIG_BLK_DEV_INITRD

rootfs_initcall(populate_rootfs);在initramfs.c中 定义CONFIG_BLK_DEV_INITRD

两个只执行一个,至于执行哪个,取决于linux/init目录下的makefile文件

要特别指出的是initramfs.c模块的入口函数populate_rootfs()是否执行取决于Kernel的编译选项。

# Makefile for the linux kernel.

#

obj-y := main.o version.o mounts.o

ifneq ($(CONFIG_BLK_DEV_INITRD),y) 没有定义该宏 执行此

obj-y += noinitramfs.o

else 定义该宏CONFIG_BLK_DEV_INITRD执行此

obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o

endif

obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o

mounts-y := do_mounts.o

mounts-$(CONFIG_BLK_DEV_RAM) += do_mounts_rd.o

mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o

mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o

===========================================================================================

populate_rootfs主要完成Initrd的检测工作,检查出是CPIO Initrd还是Initramfs还是Image-Initrd

static int __init populate_rootfs(void)

{

[1] char *err = unpack_to_rootfs(__initramfs_start,

第一个检测是否initramfs文件系统 若是的,将位于__initramfs_end - __initramfs_start的initramfs段释放到“/”目录下

__initramfs_end - __initramfs_start, 0);

if (err)

panic(err);

以下检测是cpio-initrd还是image-initrd 无论这两种格式,uboot都会把它加载到内存里的initrd_start地址处

[2] if (initrd_start) {

#ifdef CONFIG_BLK_DEV_RAM 说明可能存在image-initrd

int fd;

printk(KERN_INFO "checking if image is initramfs...");

[3] err = unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start, 1); 检测释放到地址initrd_start的包是否是cpio格式的

if (!err) { 如果err=0,说明是cpio格式的包 即解压到“/”目录下,

printk(" it is\n");

unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start, 0); 解压

free_initrd();

return 0;

}

printk("it isn't (%s); looks like an initrd\n", err);如果执行到这里,就说明是image-initrd,在根文件系统中创建文件/initrd.image,也即将image-initrd内容保存到文件/initrd.image

[4] fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);

if (fd >= 0) {

[5] sys_write(fd, (char *)initrd_start,

initrd_end - initrd_start);

sys_close(fd);

[6] free_initrd(); 释放

}

#else 不存在image-initrd 说明此时是cpio-initrd文件

printk(KERN_INFO "Unpacking initramfs...");

[7] err = unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start, 0);

if (err)

panic(err);

printk(" done\n");

free_initrd();

#endif

}

return 0;

}

代码[1]:unpack_to_rootfs顾名思义,就是解压包到rootfs,其具有两个功能,一个是检测是否是属于cpio包,

另外一个就是解压cpio包,通过最后一个参数进行控制。1:检测,0:解压。其实,Initramfs也是压缩过后的CPIO文件。

资料中提到,Linux2.5中开始引入initramfs,在Linux2.6中一定存在,而且编译的时候通过连接脚本

arch\arm\kernel\vmlinux.lds将其编译到__initramfs_start~__initramfs_end,执行完unpack_to_rootfs后将被拷贝到

根目录。

代码[2]:判断是否加载了Initrd,无论对于那种格式的Initrd,即无论是CPIO-Initrd还是Image-Initrd,U-Boot都会

将其拷贝到initrd_start。当然了,如果是initramfs的情况下,该值肯定为空了。

代码[3]:判断加载的是不是CPIO-Initrd。

通过在这里主要用于检测,如果是编译到Linux Kernel的CPIO Initrd,__initramfs_end - __initramfs_start应该是

大于零的,否则为零,其实也就是通过这里来判断是否为CPIO Initrd。

代码[4]:如果不是CPIO-Initrd,则就是Image-Initrd,将其内容保存到文件/initrd.image中。在根文件系统中

创建文件/initrd.image。

代码[5]:这里是对Image-Initrd提供支持的,将内存中的initrd赋值到initrd.image中,以释放内存空间。

代码[6]:释放Initrd所占用的内存空间。

另外,如果要支持Image-Initrd的话,必须要配置CONFIG_BLK_DEV_RAM,配置的方法上面已经讲过。

下面接着来分析函数kernel_init static int __init kernel_init(void * unused)

{



do_basic_setup(); --------------------->在这里执行了 populate_rootfs,即检测initrd的类型并且将其释放到目录中

/*

* check if there is an early userspace init. If yes, let it do all

* the work

*/

if (!ramdisk_execute_command) 在命令行中一般不会出现 rdinit=

ramdisk_execute_command = "/init"; 所以默认是/init, 即文件系统是initramfs或者cpio-initrd

[1] if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

//访问若指定的/init文件不存在,则prepare_namespace,即挂载真实的文件系统

ramdisk_execute_command = NULL;

prepare_namespace();

}

/*

* Ok, we have completed the initial bootup, and

* we're essentially up and running. Get rid of the

* initmem segments and start the user-mode stuff..

*/

init_post();

return 0;

}

代码[1]:前面在对函数populate_rootfs进行分析的时候已经知道,对于initramfs和cpio-initrd的情况,都会将

文件系统(其实是一个VFS)解压到根文件系统。如果虚拟文件系统中存在ramdisk_execute_command指定的文件

则直接转向init_post()来执行,否则执行函数prepare_namespace()。

3. 根文件系统的挂载

从上面的代码分析中知道,对于Image-Initrd或者VFS(即InitRamfs或者CPIO-Initrd)中不存在文件

ramdisk_execute_command的情况,则执行prepare_namespace()。

接下来看一下函数prepare_namespace()的代码: /*

* Prepare the namespace - decide what/where to mount, load ramdisks, etc.

*/

void __init prepare_namespace(void)

//对于image-initrd,有两中挂载设备,一种是root=/dev/mtdblockxx 一种是root=/dev/ram设备

{

int is_floppy;

[1] if (root_delay) {

printk(KERN_INFO "Waiting %dsec before mounting root device...\n",

root_delay);

ssleep(root_delay);

}

/*

* wait for the known devices to complete their probing

*

* Note: this is a potential source of long boot delays.

* For example, it is not atypical to wait 5 seconds here

* for the touchpad of a laptop to initialize.

*/

[2] wait_for_device_probe();

md_run_setup();

[3] if (saved_root_name[0]) {

root_device_name = saved_root_name;

if (!strncmp(root_device_name, "mtd", 3) ||

!strncmp(root_device_name, "ubi", 3)) {

[4] mount_block_root(root_device_name, root_mountflags);

goto out;

}

[5] ROOT_DEV = name_to_dev_t(root_device_name);

if (strncmp(root_device_name, "/dev/", 5) == 0)

root_device_name += 5;

}

[6] if (initrd_load()) initrd_load()=1,则说明是root=/dev/mtdblockxx 否则是root=/dev/ram

goto out;

[7] /* wait for any asynchronous scanning to complete */

if ((ROOT_DEV == 0) && root_wait) {

printk(KERN_INFO "Waiting for root device %s...\n",

saved_root_name);

while (driver_probe_done() != 0 ||

(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)

msleep(100);

async_synchronize_full();

}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (is_floppy && rd_doload && rd_load_disk(0))

ROOT_DEV = Root_RAM0; 在这里执行root=/dev/ram

mount_root();

out:

[9] sys_mount(".", "/", NULL, MS_MOVE, NULL);

[10] sys_chroot(".");

}

代码[1]:资料中提到,对于将根文件系统存放到USB或者SCSI设备上的情况,Kernel需要等待这些耗费时间比较久的

设备驱动加载完毕,所以这里存在一个Delay。

代码[2]:从字面的意思来看,这里也是来等待根文件系统所在的设备探测函数的完成。

代码[3]:参数saved_root_name存放的是Kernel参数root=所指定的设备文件,这点不再赘述,可以参照代码。

代码[4]:按照资料中的解释,这里相当于将saved_root_nam指定的设备进行加载。如下面传递给

内核的command line: CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2"

实际上就是加载/dev/mtdblock2。

代码[5]:参数ROOT_DEV存放设备节点号。

代码[6]:挂载initrd,这里进行的操作相当的复杂,可以参照后续关于该函数的详细解释。

代码[7]:如果指定mount_initrd为true,即没有指定在函数initrd_load中mount的话,则在这里重新realfs的mount操作。

代码[9]:将挂载点从当前目录(实际当前的目录在mount_root中或者在mount_block_root中指定)移到根目录。

对于上面的command line的话,当前的目录就是/dev/mtdblock2。

代码[10]:将当前目录当作系统的根目录,至此虚拟系统根目录文件系统切换到了实际的根目录文件系统。

接下来看一下函数initrd_load()的代码: int __init initrd_load(void)

{

[1] if (mount_initrd) {

[2] 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.

*/

[3] if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {

sys_unlink("/initrd.image");

[4] handle_initrd();

return 1;

}

}

sys_unlink("/initrd.image");

return 0;

}

代码[1]:可以通过Kernel的参数“noinitrd“来配置mount_initrd的值,默认为1,很少看到有项目区配置该值,

所以一般情况下,mount_initrd的值应该为1;

代码[2]:创建一个Root_RAM0的设备节点/dev/ram;

代码[3]:如果根文件设备号不是Root_RAM0则程序就会执行代码[4],换句话说,就是给内核指定的参数不是/dev/ram,

例如上面指定的/dev/mtdblock2设备节点肯定就不是Root_RAM0。

另外这行代码还将文件initrd.image释放到节点/dev/ram0,也就是对应image-initrd的操作。

代码[4]:函数handle_initrd主要功能是执行Initrd中的linuxrc文件,并且将realfs的根目录设置为当前目录。

其实前面也已经提到了,这些操作只对image-cpio的情况下才会去执行。

--------------------------------------------------------------------------------------------------------------------------

ZTEXTADDR:内核镜像加载到内存中的物理起始地址。

ZRELADDR:内核镜像解压后在内存中的起始地址。

TEXTADDR:内核启动的虚拟地址。

PHYS_OFFSET:内存中第一个bank的物理起始地址。

PAGE_OFFSET:第一个bank的虚拟起始地址。

TEXTOFFSET:内核镜像的偏移地址。

内核加载和解压

引导程序会把内核镜像加载到内存中的ZTEXTADDR位置,然后进行解压缩。解压后内核镜像的地址为ZRELADDR,也就是内核镜像启动的物理地址。ZRELADDR对应的虚拟地址为TEXTADDR。

S3C2410/S3C2440平台中各个具体的地址值:

PHYS_OFFSET的值为30000000;

PAGE_OFFSET的值为c0000000;

TEXTOFFSET的值为8000;

ZRELADDR的值为PHYS_OFFSET加上TEXTOFFSET,即30008000;

TEXTADDR的值为PAGE_OFFSET加上TEXTOFFSET,即c0008000;

linux makefile文件决定了哪些文件需要被编译,是如何编译(编译选项如KBUILD_CFLAGS KBUILD_CPPFLAGS)的,是如何链接(链接选项LDFLAGS),链接顺序是怎么样子的

Kconfig用于配置内核,它就是各种配置界面的源文件

config条目:用于配置一个项,即经过make menuconfig后,会在auto.conf文件中(.config文件)生成一个CONFIG_XXX =y

或者m或者没有该配置项

如:config JFFS2_FS_POSIX_ACL

BOOL "JFFS2 POSIX Access control lists"

关于后续见笔记本
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: