关于内核的配置和编译和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 */
.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[n] == '\0' || line[n] == '=')
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"
关于后续见笔记本