配置内核的结果是,生成一个.config文件,该文件又自动生成一个include/config/auto.conf和include/linux/autoconf.h,其中后者给C的源代码使用,会包含在C源码代码中;前者被顶层的makefile包含,给子目录的makefile使用。
这一点可以通过查找其中某一项模块来跟踪。
架构相关的makefile(/arch/arm/makefile);
分析一个makefile,从其命令开始分析,make,make uImage,从顶层makefile开始分析。
分析make uImage命令,该目标uImage在顶层makefile里没有,其位置在arch/arm/makefile,则此makefile会被包含进入顶层makefile中。
auto.conf文件也被顶层makefile包含。
回到目标make uImage,在架构makefile中,uImage是内核加头部,uImage依赖于vmlinux,vmlinux是真正的内核;
我们直接命令make,这执行第一个目标all,all也依赖与vmlinux。那么vmlinux依赖于谁呢?
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(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
然后,接着分析
head-y:不在顶层makefile,在架构相关makefile中
将上诉依赖一一陈列,再去细细分析可能就会比较繁琐,我们可以通过一一分析makefile去了解文件的连接方式,也可以使用另一种比较实用的方式,直接编译,找出最后一句已执行命令的输出,分析该输出语句。
通过该语句的分析,可以找出:
第一个文件:arch/arm/kernel/head.S
连接脚本:arch/arm/kernel/vmlinux.lds(源码中是vmlinux.lds.S)
文件连接的顺序由命令中.o文件的位置决定
内核启动流程:
分析makefile的时候我们看到的第一个文件head.S,就是我们分析内核的起始文件!
head.S有两个,一个处于compressed里面:
内核很大,压缩一下,压缩后的代码前加一个自解压代码,因此有一段head.S是在compressed文件夹里。
我们此处暂且只关心真正的head.S。
head.s内核工作:
1.处理uboot传入的数据:机器ID,启动参数;
a.判断是否支持这个CPU;
b.判断是否支持这个单板;
c.建立一级页表;
d.使能mmu
c.跳到start_kernel,(kernel_sabrelite\init\main.c)此函数即为kernel的第一个c函数;
abc中是在汇编代码中处理有关机器ID的相关的东西,启动参数是在start_kernel开始处理;
直到这里,进行各种初始化。
我们移植uboot,kernel等等,最终的目的是什么?是运行我们的应用程序。那么放在哪里?应用程序放在根文件系统中,因此在运行应用程序之前我们要挂在文件系统。
start_kernel最后回去调用rest_init,该函数嗲用kernel_thread,启动一个线程函数kernel_init,在其中调用prepare_namespace,该函数中又调用了mount_root()挂载根文件系统。挂载上根文件系统后,kernel_init中后面调用init_post()执行应用程序。这个启动层级结构如下:
这一点可以通过查找其中某一项模块来跟踪。
本节:
子目录的makefile;架构相关的makefile(/arch/arm/makefile);
分析一个makefile,从其命令开始分析,make,make uImage,从顶层makefile开始分析。
分析make uImage命令,该目标uImage在顶层makefile里没有,其位置在arch/arm/makefile,则此makefile会被包含进入顶层makefile中。
auto.conf文件也被顶层makefile包含。
回到目标make uImage,在架构makefile中,uImage是内核加头部,uImage依赖于vmlinux,vmlinux是真正的内核;
我们直接命令make,这执行第一个目标all,all也依赖与vmlinux。那么vmlinux依赖于谁呢?
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(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
然后,接着分析
head-y:不在顶层makefile,在架构相关makefile中
将上诉依赖一一陈列,再去细细分析可能就会比较繁琐,我们可以通过一一分析makefile去了解文件的连接方式,也可以使用另一种比较实用的方式,直接编译,找出最后一句已执行命令的输出,分析该输出语句。
通过该语句的分析,可以找出:
第一个文件:arch/arm/kernel/head.S
连接脚本:arch/arm/kernel/vmlinux.lds(源码中是vmlinux.lds.S)
文件连接的顺序由命令中.o文件的位置决定
内核启动流程:
分析makefile的时候我们看到的第一个文件head.S,就是我们分析内核的起始文件!
head.S有两个,一个处于compressed里面:
内核很大,压缩一下,压缩后的代码前加一个自解压代码,因此有一段head.S是在compressed文件夹里。
我们此处暂且只关心真正的head.S。
head.s内核工作:
1.处理uboot传入的数据:机器ID,启动参数;
a.判断是否支持这个CPU;
b.判断是否支持这个单板;
c.建立一级页表;
d.使能mmu
c.跳到start_kernel,(kernel_sabrelite\init\main.c)此函数即为kernel的第一个c函数;
abc中是在汇编代码中处理有关机器ID的相关的东西,启动参数是在start_kernel开始处理;
直到这里,进行各种初始化。
我们移植uboot,kernel等等,最终的目的是什么?是运行我们的应用程序。那么放在哪里?应用程序放在根文件系统中,因此在运行应用程序之前我们要挂在文件系统。
start_kernel最后回去调用rest_init,该函数嗲用kernel_thread,启动一个线程函数kernel_init,在其中调用prepare_namespace,该函数中又调用了mount_root()挂载根文件系统。挂载上根文件系统后,kernel_init中后面调用init_post()执行应用程序。这个启动层级结构如下:
链接脚本:
arch/arm/boot/compressed/vmlinux.lds
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
#define __setup_param(str, unique_id, fn, early) \
const char __setup_str_##unique_id[] __initconst = str; \
\
struct obs_kernel_param __setup_##unique_id \
= { __setup_str_##unique_id, fn, early }
//每一个命令行参数的相关结构体如下:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
/*所有的命令行参数 解析方式放在这个结构体中,所有obs_kernel_param
存放在从__setup_start到__setup_end之间.
比如early_param("vmalloc", early_vmalloc);
他也放在这个区间, 通过这个宏,他就是early类型的,最终会通过调用do_early_param,
它又会调用early_vmalloc.
总之对于非early的参数还是early类型参数,都会调用它的setup_func函数处理.
经过检验,除了vmalloc以外,所有的参数都是非early类型
*/
start_kernel //Main.c (init)
setup_arch(&command_line);
setup_command_line(command_line);
parse_early_param(); //参数来自boot_command_line
parse_args("early options", cmdline, NULL, 0, do_early_param);
do_early_param
//从__setup_start到__setup_end对于early标识为真就是early的 调用do_early_param 处理
unknown_bootoption
obsolete_checksetup(param)
//从__setup_start到__setup_end ,对于early标识为0就是非early的调用obsolete_checksetup进行处理
rest_init();
kernel_init
prepare_namespace();//(init/Do_mounts.c)
mount_root();//decide what/where to mount, load ramdisks, etc.
init_post();//printk(KERN_DEBUG"'%s'\n", line);