Linux内核启动流程笔记

来源:互联网 发布:python post 下载文件 编辑:程序博客网 时间:2024/06/08 13:08

一、总体流程图

根据《嵌入式Linux开发完全手册》:


二、移植一个新内核大致步骤

1.  官网下载源码,解压缩

2.  打补丁(怎么打,可以查看内核文档)

3.  配置

(1)Makemenuconfig

(2)使用默认配置文件,在此基础上修改

    在arch/arm/config下找到相似的配置文件xxx_defconfig。执行make  xxx_defconfig,执行结果会保存在.config文件里面,Makemenuconfig会根据xxx_defconfig(读取上述的.config文件和Kconfig文件)生成一个配置界面,配置结果也会保存在.config文件里面。

(3)使用厂家提供的配置文件,cpconfig_厂家到.config文件(cp  config_厂家 .config),然后在mekemenuconfig。

(4)Make menuconfig时,输入*表示编译进内核;n表示不编译进内核;M编译成模块,用于内核启动后加载(如果在make menuconfig时输入M,那么在make  zImage前需要make  modules编译模块才能得到.KO文件);反斜杠”/”搜索。

4. 编译,一般make zImage或uImage

二、配置选项

   比如CONFIGZ_DM9000这个选项,它在如下四个地方都存在:

1. C源码,在这里面是一个宏,而c中的宏只有可能在c源文件和.h文件里面定义,即在如下的Include/linux/autoconf.h里定义。

   Make内核时,make机制会自动地根据.config文件生成autoconf.h文件。在.config文件里,配置选项被配置为y,在autoconf.h被宏定义为1,这样就可以在c源码里面使用这个宏了。如果配置选项被配置为m,在此配置选项在autoconf.h文件里面不会被宏定义。

   而y和m在c源码中体现不出来,他们在下面的makefile等文件里体现。

2. 子目录下的makefile,如drivers/net/makefile。这种makfile格式很简单,一般有如下内容:

   obj_y   += xxx.o,表示xxx.c文件最后会被编译进内核。比如obj_$(CONFIG_DM900) +=dm9000.o就表示:如果CONFIG_DM900变量的值为y,那么dm9000.c就会被编译到内核里面去。

   obj_m  +=yyy.o ,表示yyy.c文件最后会被编译成模块,最终生成yyy.ko文件。比如obj_$(CONFIG_DM900) +=dm9000.o就表示:如果CONFIG_DM900变量的值为m,那么dm9000.c就会被编译成模块。

3. Include/config/auto.conf,这个文件里面定义上述makeile文件里面的配置选项,如CONFIG_DM900。此文件也是来源于.config,且是自动生成。它被顶层Makefile文件包含。

4. Include/generated/autoconf.h(Linux3.5版内核), 编译内核时自动生成。

三、编译过程(make zImage过程)

   配置时会生成.config,make编译时根据.config文件生成autoconf.h文件和auto.conf文件。autoconf.h文件被源代码使用,auto.conf文件被顶层makefile文件包含,子目录下的makefile将使用。

   Autoconf.h用于c源文件,比如logo.c中代码:

#ifdef CONFIG_LOGO_LINUX_CLUT224

   logo =&logo_linux_clut224;

#endif        

     配置时如果配置上了“CONFIG_LOGO_LINUX_CLUT224”,在配置生成的.config文件中就会有“CONFIG_LOGO_LINUX_CLUT224”。这样,make编译时,根据.config文件生成的autoconf.h文件里面就会有代码: #define  CONFIG_LOGO_LINUX_CLUT224  1,logo.c间接包含autoconf.h后,条件编译就成立,logo的值就会被设置为&logo_linux_clut224。auto.conf文件类似。

四、Makefile文件的分析

    分析第一个文件,可以顺藤摸瓜地分析出内核启动过程,分析连接脚本可以知道内核放在哪,内核各个部分组成。

    在内核顶层源码目录中documentation目录下的Kbuild目录下的Makefiles.txt文件详细说明了makefile规则和语句,对内核里面的makefile讲得比较透彻。

1. 子目录下的makefile

   比如drivers/video/下的makefile文件,里面obj-$(CONFIG_FB_S3C)  +=s3c-fb.o即表示如果CONFIG_FB_S3C在配置文件里面被定义成了y,那么s3c-fb.c将被编译进内核,s3c-fb.o将被连接进内核。

     注意,如果a.c和b.c要编译进内核,使用obj-y  +=a.o b.o即可。但是如果要根据这两个文件组合,然后编译成模块,可以参考上述Makefiles.txt文件:

obj-m += ab.o

ab-objs := a.o b.o

   在编译的时候,a.c会被编译成a.o,b.c会被编译成b.o,然后a.o和b.o会被连接成ab.ko文件。所以如果有时间,一定要把Makefiles.txt文件详细阅读下。

2. 顶层目录下的makefile

   顶层目录下的makefile会包含arch/arm下的makefile文件,如下:

Include $(srctree)/arch/$(ARCH)/Makefile

   顶层目录下的makefile会包含Include/config/auto.conf文件,如下:

-include include/config/auto.conf




     比如图中drivers-y  :=drivers/built-in.o表示drivers目录下所有涉及的文件都会编译成built-in.o。

   第28行开始就是连接过程,其中-T后面接连接脚本,即arch/arm/kernel/vmlinux.lds,这个文件决定了上述的所有文件连接成一个内核映像时是如何排布的。

   vmlinux.lds文件根据vmlinux.lds.S文件生成。在vmlinux.lds文件中先决定内核放在哪个地址,如“. = 0xC0000000 + 0x00008000”,它是个虚拟地址。然后放所有文件的.head.text段等等。

五、Linux内核引导阶段分析(包括Head.S等文件分析)

   通过分析顶层目录makefile文件,搞清楚了内核的整个结构,也发现了第一个文件head.S。路径为arch/arm/kernel。

1. 处理u-boot传入的参数,比如机器ID等、启动参数。

(1)首先检测内核是否支持当前cpu和单板。(处理机器ID)

(2)创建页表,启动mmu

(3)跳转到start_kernel函数,Linux内核第一个c函数。(在此函数里面会处理启动参数)

六、Linux内核启动第二阶段分析(包括init/main.c等文件分析)



1.相关初始化

   init/main.c文件中的start_kernel函数里面进行了较多初始化等工作,输出内核信息(printk(KERN_NOTICE "%s", linux_banner),给板子上电,在串口终端输出的那些信息,这里的printk函数调用时还没有打印,只是先把要打印的数据存放在缓存区,等后面的console_init()函数调用后,这些信息才被输出到终端上。

2. setup_arch函数

   init/main.c文件中的start_kernel函数里开头不远处调用了setup_arch(&command_line)函数。setup_arch()函数是start_kernel阶段最重要的一个函数,每个体系都有自己的setup_arch()函数,它是体系结构相关的,具体编译哪个体系的setup_arch()函数,由顶层Makefile中的ARCH变量决定。

   setup_processor()函数做cpu相关初始化。它从处理器内核描述符表中找到匹配的描述符,并初始化一些处理器变量。

   setup_machine_tags()函数里会把启动参数存放在全局缓存区boot_command_line中。

   parse_early_param()函数里面会做第一次启动参数的解析,针对友善之臂的4412开发板,它会触发代码early_param("lcd", tiny4412_setup_lcd)的运行,然后就会执行tiny4412_setup_lcd函数(arch/arm/mach-exynos/tiny4412-lcds.c里面),此函数的参数就是boot_command_line存放启动参数(即“lcd=”后面的值HD101),在tiny4412_setup_lcd函数里面会向终端输出TINY4412: HD101 selected的调试信息。

   因此,arch/arm/mach-exynos/tiny4412-lcds.c里面的tiny4412_setup_lcd函数肯定是在setup_arch()函数调用parse_early_param()函数,然后执行parse_early_param()函数中的过程中执行的。tiny4412_setup_lcd函数主要作用是确定全局静态变量lcd_idx的值。

   注意,在setup_arch函数中已经调用过一次parse_early_param()函数,此函数里面的一个静态局部变量done就会加一,setup_arch函数执行完后,继续执行start_kernel函数里面将调用的函数,比如执行到parse_early_param函数时,由于此函数被执行过一次,所以这次就不会再执行里面的代码了(友善之臂使用的linux3.5版内核是这样定义的),即早期参数解析在setup_arch函数里解析后,start_kernel函数里就不会解析了,start_kernel函数会继续调用parse_args函数进行后期参数的解析。        

   setup_arch函数中request_standard_resources()函数主要是完成从内核启动到此时,cpu使用到的所有外围总线设备实体的注册登记工作。

3. 处理u-boot传入的启动参数

   setup_command_line(command_line)函数将保存u-boot传入的启动参数到其他变量以备后面使用。启动参数被保存在某个字符串里面,最后会调用一些函数去一一分析,代码如下(下面代码在start_kernel函数里面):

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

         parse_early_param();

         parse_args("Bootingkernel", static_command_line, __start___param,

                      __stop___param - __start___param,

                      -1, -1, &unknown_bootoption);

   上述unknown_bootoption函数又会调用obsolete_checksetup等函数,这些都是在处理命令行参数。

   一定要注意针对友善之臂的4412开发板中lcd启动参数已经在setup_arch函数中处理过了,所以start_kernel函数里面调用parse_early_param()函数实际上没有做什么事情。

4. rest_init()函数

      start_kernel函数最后会调用rest_init()函数,rest_init()函数里面会调用kernel_thread函数即调用kernel_init函数,kernel_init函数里面会调用prepare_namespace函数,此函数里面最后会调用mount_root函数,即挂接根文件系统(具体挂接哪个由命令行参数决定)。kernel_init函数函数里面还会调用init_post函数,此函数里面执行应用程序。

   rest_init()函数最后会调用cpu_idle(),此函数位于\arch\arm\kernel\process.c。

七、友善之臂4412开发板参数传递

    由于友善之臂bootloader不开源,所以无法控制u-boot给linux内核传递参数。但是经过实验可知:

1. 当用sd卡安装bootloader、内核、文件系统时,如果把lcd屏线拔掉(断电)安装,那么安装好后直接启动系统,u-boot给linux传递的参数中没有“lcd=HD101”一项。接上LCD线后,重新启动系统,u-boot给linux内核传递的参数中就包含“lcd=HD101”一项了,但是触摸屏还是不能用。

2. 当用sd卡安装bootloader、内核、文件系统时,如果把lcd屏线接上安装,那么安装好后直接启动系统,u-boot给linux传递的参数中有“lcd=HD101”一项。拔掉LCD线后,重新启动系统,u-boot给linux内核传递的参数中也会包含“lcd=HD101”一项。拔掉SD卡也一样。

    所以,为了解决上电屏闪一下的问题,在bootloader引导阶段不会打开屏电源(主要是显示和背光电源),这样在显示logo前就不会闪一下。但是u-boot也不会给linux内核传递参数,也就是说,即使logo显示前给屏上电,系统也不会初始化lcd显示。

      所以可以使用上述2.中的方法,在安装软件时,先把0欧电阻焊上,这样lcd电源在安装过程中都是打开了的,第一此启动时lcd电源也是有的。以后使用时,就把0欧电阻去掉,改用程序控制lcd电源,这个时候不用担心logo显示前不给lcd上电会导致bootloader不给linux内核传递参数从而无法初始化lcd的问题。

八、总结

    通过上述简单分析,Linux启动流程中解析bootloader传入的参数过程较复杂,但只要源码在手,一切都不是问题,都可以看源码解决,一切资料都不会比Linux内核源码更权威。

    另外,Linux系统各个子系统初始化过程可以作为看源码的开始,找到各子系统个入口函数对学习各子系统很重要。








0 0
原创粉丝点击