Linux内核启动流程

一、配置及编译

配置

三种方法配置:

  1. 直接输入 make menuconfig 命令,从头到尾重新配置菜单 (非常复杂)

    make menuconfig时修改配置项,最终的配置结果会保存在.config文件中,这主要是Kconfig的功能,

    再执行make menuconfig时就可以回去读取.config文件。这是内核配置的过程。

  2. 通过make xxx_defconfig命令在默认的配置上进行修改,然后再输入make menuconfig配置菜单

    可以使用,使用 find –name \*defconfig 命令查找所有带defconfig名字的文件.

  3. 使用厂家提供的配置config_ok文件

    在linux-2.6.22.6目录下,使用cp config_ok .config将config_ok复制覆盖新的.config隐藏文件(通过 ls -la 命令可以查看.config隐藏文件),

    最后执行make menuconfig时就可以回去读取.config文件

可见,最后的配置文件为.config文件。该配置文件通过make uImagemake命令后会影响以下几个文件:

  • 相关的源代码(包含该宏,该宏的具体定义在下一个文件autoconf.h中)

  • 该源码所对应的头文件(include/linux/autoconf.h)

    该头文件会通过make命令根据.config文件自动生成,同时将CONFIG_XXX定义为1(不论是否为模块)

  • 子目录中的makefile

    编译成模块或者是内核是通过该文件体现出来的:

    例子:

    1
    obj-$(CONFIG_XXX) += xxx.o

    这里CONFIG_XXX的值就是下一点中的配置文件auto.conf中的内容,即:

    • obj-y:编译到内核
    • obj-m:编译成模块
  • 配置文件(include/config/auto.conf)

    该文件中的内容是通过make命令根据.config文件自动生成

    例子:

    CONFIG_XXX=y(编译到内核)

    CONFIG_XXX=m(编译成模块)

分析顶层makefile

分析makefile的主要目的是找到第一个文件和链接脚本,相关方法和uboot中的过程类似

详细的文档可以查看Documentation/kbuild/makefiles.txt

通过上面的讲解,我们可以知道每个文件是如何被编译成模块或者是内核中去的,这里拓展下多个文件一起编译的情况:

例子:a.c、b.c两个文件需要同时编译成一个模块

1
2
obj-m += ab.o
ab-objs := a.o b.o

此时会将a.c、b.c两个文件编译成ab.ko这个模块

通过make uImage命令,发现目标uImage位于arch/arm/makefile

顶层的makefile包括了子目录下的makefile以及上文中讲的.config配置文件

1
2
ZImage Image xipImage bootpImage uImage:vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

此时目标vmlinux又在顶层makefile中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
all: vmlinux
......
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/$(ARCH)/kernel/vmlinux.lds#链接脚本
......
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
ifdef CONFIG_HEADERS_CHECK#编译命令:
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
$(call if_changed_rule,vmlinux__)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
$(Q)rm -f .old_version

同分析uboot的方法一致,这里直接执行make uImage V=1命令,关心最后一条命令(展开后):

V=1:打印信息更加详细的列出来

1
2
3
4
5
arm-linux-ld -EL -P --no-urde fined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds
arch/arn/kernel/head.o arch/arm/kernel/init_task.o
initi/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/mach-s3c2400/built-in.o arch/arm/mach-s3c2412/built-in.o arch/arm/mach-s3c2440/built-in.o arch/arm/mach-s3c2442/built-in.o arch/arm/mach-s3c2443/built-in.o arch/arm/nwfpe/built-in.o arch/arm/plat-s3c24xx/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 block/built-in.o arch/arm/lib/lib.a 1ib/1ib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o
net/built-in.o --end-group .tmp_kallsyms2.o

可见:

  • 第一个文件为:arch/arn/kernel/head.S
  • 链接脚本为:arch/arm/kernel/vmlinux.lds(根据make命令由同级目录下的vmlinux.lds.S文件生成)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SECTIONS
{
. = (0xc0000000) + 0x00008000; //设置内核运行地址

.text.head : {
_stext = .;
_sinittext = .;
*(.text.head) //存放.text.head段
}

.init : {
*(.init.text) //存放.init.text段

... ...

二、第一个文件head.S

head.S步骤:

  1. 检查是否支持该处理器__lookup_processor_type

  2. 检查单板ID(就是uboot传进来的机器ID)

    这里会通过__lookup_machine_type函数,将链接脚本中定义的.arch.info.init段中存放的架构相关的初始化信息和uboot传进来的机器ID进行对比,如果不匹配就进入死循环

    注:通过MACHINE_START宏可以定义架构相关的初始化信息在.arch.info.init段中

  3. 创建页表__create_page_tables

  4. 使能MMU__enable_mmu

  5. 跳转到start_kernel函数(内核的第一个C函数)

三、启动内核start_kernel

start_kernel步骤

  1. 中断初始化、时钟初始化、打印内核版本信息

  2. 处理uboot传进来的启动信息setup_arch(&command_line);

    1. 提取信息,处理
    2. 解析命令行信息parse_cmdline
  3. 处理命令行信息setup_command_line(command_line);

  4. 调度初始化等各种初始化

  5. 挂接根文件系统rest_init

    1. 创建kernel_init线程

      1. 早期的用户空间初始化prepare_namespace

        1. 挂载根文件系统mount_root
      2. 初始化某些设备init_post

        1. 打开/dev/console设备

        2. 执行应用程序

          1
          2
          3
          4
          run_init_process("/sbin/init");
          run_init_process("/etc/init");
          run_init_process("/bin/init");
          run_init_process("/bin/sh");

处理uboot传进来的启动信息

通过调用宏__setup来处理uboot传进来的bootargs命令行参数

1
__setup("命令",调用的函数)

例子:

__setup(“root=”,root_dev_setup):当uboot传进来bootargs命令行参数中有root参数时,会调用函数root_dev_setup。同时用了一个专门的结构体obs_kernel_param来保存相关的一些数据并存在了.init.setup段中