Hi!大家好,我是CrazyCatJack。今天给大家带来的是Linux内核启动过程概述。希望能够帮助大家更好的理解Linux内核的启动,并且创造出自己的内核^_^

  Linux的启动代码真的挺大,从汇编到C,从Makefile到LDS文件,需要理解的东西很多。毕竟Linux内核是由很多人,花费了巨大的时间和精力写出来的。而且直到现在,这个世界上仍然有成千上万的程序员在不断完善Linux内核的代码。今天我们主要讲解的是Linux-2.6.22.6这个内核版本。说句实话,博主也不确定自己能够讲好今天这个题目,因为这个题目太大太难。但是博主有信心,将自己学会的内容清楚地告诉大家,希望大家也能够有所收获。

1.启动文件head.S和head-common.S 

  首先,我们必须明确“我们为什么要启动Linux内核”。没错,当然是因为我们想要使用Linux系统,要明确我们的最终目的是使用Linux上的应用程序。这些应用程序可以是纯软件的,也可以是硬件相关的。博主是做嵌入式开发的,那么我想要的当然就是用Linux内核来更好的控制我的硬件。无论是做机器人、无人机或者其他智能硬件这都是必然趋势。首先我们来看内核的启动文件head.S。

    .section ".text.head", "ax".type    stext, %function
ENTRY(stext)msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode@ and irqs disabledmrc    p15, 0, r9, c0, c0        @ get processor idbl    __lookup_processor_type        @ r5=procinfo r9=cpuidmovs    r10, r5                @ invalid processor (r5=0)?beq    __error_p            @ yes, error 'p'bl    __lookup_machine_type        @ r5=machinfomovs    r8, r5                @ invalid machine (r5=0)?beq    __error_a            @ yes, error 'a'bl    __create_page_tablesldr    r13, __switch_data        @ address to jump to after@ mmu has been enabledadr    lr, __enable_mmu        @ return (PIC) addressadd    pc, r10, #PROCINFO_INITFUNC

  首先看这段汇编代码,它主要是用来做一些内核启动前的检测:__lookup_processor_type 检测内核是否支持当前CPU、__lookup_machine_type检测是否支持当前单板,并且__create_page_tables创建页表,__enable_mmu使能MMU。如果在一系列的自检过程后发现不支持,则跳到__error_p或__error_a。这里我们首先打开__lookup_machine_type。

    .type    __lookup_machine_type, %function
__lookup_machine_type:adr    r3, 3bldmia    r3, {r4, r5, r6}sub    r3, r3, r4            @ get offset between virt&physadd    r5, r5, r3            @ convert virt addresses toadd    r6, r6, r3            @ physical address space
1:    ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine typeteq    r3, r1                @ matches loader number?beq    2f                @ foundadd    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desccmp    r5, r6blo    1bmov    r5, #0                @ unknown machine
2:    mov    pc, lr3:    .long    ..long    __arch_info_begin.long    __arch_info_end

  我们在arch\arm\kernel找到__lookup_machine_type被定义在head-common.S文件中。开始分析代码:首先,读出3b的地址给r3,这里的3b就是下面的那个3:所对应的虚拟地址。然后用ldmia指令将r3存放的虚拟地址分别存入r4,r5,r6。所以现在

r4=. ; r5=__arch_info_begin ; r6=__arch_info_end

然后用r3-r4求出偏移地址,再利用这个偏移地址求出r5和r6的实际物理地址。其中__arch_info_begin和__arch_info_end定义在内核目录arch\arm\kernel下vmlinux.lds文件中,经过起始虚拟地址= (0xc0000000) + 0x00008000逐层叠加得到。

SECTIONS
{. = (0xc0000000) + 0x00008000;.text.head : {_stext = .;_sinittext = .;*(.text.head)}.init : { /* Init code and data        */*(.init.text)_einittext = .;__proc_info_begin = .;*(.proc.info.init)__proc_info_end = .;__arch_info_begin = .;*(.arch.info.init)__arch_info_end = .;

  这里的__arch_info_begin和__arch_info_end中间存放的是段属性为.arch.info.init的结构体。这里我们可以直接在linux下查询内核中包含.arch.info.init的文件。

Direction:include/asm-arm/arch.h
#define MACHINE_START(_type,_name)            \
static const struct machine_desc __mach_desc_##_type    \__used                            \__attribute__((__section__(".arch.info.init"))) = {    \           .nr        = MACH_TYPE_##_type,        \.name        = _name,#define MACHINE_END                \
};
Direction:arch/arm/mach-s3c2440
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

  如图所示,在include/asm-arm/arch.h中找到了定义的结构体类型machine_desc,并且在代码中它的段属性被强制定义成了.arch.info.init。这样做的目的是在刚刚我们看到的vmlinux.lds链接脚本文件中,可以将具有.arch.info.init段属性的结构体统一放在__arch_info_begin和__arch_info_end之间。非常便于处理。那么现在我们将这个结构体展开,看看它的内容。也就是将arch/arm/mach-s3c2440中的参数传入。展开后如下:

#define MACHINE_START(_type,_name)            \
static const struct machine_desc __mach_desc_S3C2440    \__used                            \__attribute__((__section__(".arch.info.init"))) = {    \.nr        = MACH_TYPE_S3C2440,        \.name        = "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,                    //0x30000100.init_irq    = s3c24xx_init_irq,.map_io        = smdk2440_map_io,.init_machine    = smdk2440_machine_init,.timer        = &s3c24xx_timer,
};

  现在我们看到,定义的结构体类型machine_desc,内容为.nr到.timer。我们可以看出这个结构体大概是存储硬件信息。nr存放机器ID,name存放单板名称,phys_io存放输入输出口,io_pg_offst存放IO的偏移地址,boot_params存放uboot传给内核的启动参数(TAG),init_irq存放的是中断初始化信息,map_io为IO的映射表,init_machine存放的是单板的初始化信息,timer存放的是单板的定时器信息。

struct machine_desc {/** Note! The first four elements are used* by assembler code in head-armv.S*/unsigned int        nr;        /* architecture number    */unsigned int        phys_io;    /* start of physical io    */unsigned int        io_pg_offst;    /* byte offset for io * page tabe entry    */const char        *name;        /* architecture name    */unsigned long        boot_params;    /* tagged list        */unsigned int        video_start;    /* start of video RAM    */unsigned int        video_end;    /* end of video RAM    */unsigned int        reserve_lp0 :1;    /* never has lp0    */unsigned int        reserve_lp1 :1;    /* never has lp1    */unsigned int        reserve_lp2 :1;    /* never has lp2    */unsigned int        soft_reboot :1;    /* soft reboot        */void            (*fixup)(struct machine_desc *,struct tag *, char **,struct meminfo *);void            (*map_io)(void);/* IO mapping function    */void            (*init_irq)(void);struct sys_timer    *timer;        /* system tick timer    */void            (*init_machine)(void);
};

  我们打开arch.h文件,看到对machine_desc结构体的定义确实和我们刚刚所说的一样。再回到head-common.S文件,这里对mmap_switch定义:

    .type    __mmap_switched, %function
__mmap_switched:adr    r3, __switch_data + 4ldmia    r3!, {r4, r5, r6, r7}cmp    r4, r5                @ Copy data segment if needed
1:    cmpne    r5, r6ldrne    fp, [r4], #4strne    fp, [r5], #4bne    1bmov    fp, #0                @ Clear BSS (and zero fp)
1:    cmp    r6, r7strcc    fp, [r6],#4bcc    1bldmia    r3, {r4, r5, r6, sp}str    r9, [r4]            @ Save processor IDstr    r1, [r5]            @ Save machine typebic    r4, r0, #CR_A            @ Clear 'A' bitstmia    r6, {r0, r4}            @ Save control register valuesb    start_kernel

  mmap_switch做了很多工作,这里我们看到有复制数据段,清BSS段,保存CPU的ID,保存机器ID,清‘A’位,保存控制寄存器的值,然后就到了C语言段——start_kernel函数。

2.C语言段——start_kernel

  

asmlinkage void __init start_kernel(void){local_irq_disable();early_boot_irqs_off();early_init_irq_lock_class();/** Interrupts are still disabled. Do necessary setups, then* enable them*/lock_kernel();tick_init();boot_cpu_init();page_address_init();printk(KERN_NOTICE);printk(linux_banner);setup_arch(&command_line);setup_command_line(command_line);printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);parse_early_param();parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,&unknown_bootoption);init_IRQ();profile_init();if (!irqs_disabled())printk("start_kernel(): bug: interrupts were enabled early\n");early_boot_irqs_on();local_irq_enable();console_init();rest_init();
}

  接下来进入start_kernel启动内核的C函数。上面是start_kernel的部分代码。这部分代码的主要作用是处理uboot传递来的参数,设置与体系结构相关的环境,初始化控制台,最后执行应用程序,实现功能。这里我把start_kernel函数的几个主要功能的子函数逐层写出,帮助大家理解start_kernel的功能结构。

start_kernelsetup_arch(&command_line);setup_command_line(command_line);unknown_bootoptionobsolete_checksetup    parse_early_paramdo_early_param        rest_init;kernel_initprepare_namespacemount_rootinit_post

  这里每一个退格(TAB)都代表此函数被上一个函数调用(例如obsolete_checksetup是unknown_bootoption调用的函数)。setup_arch(&command_line)和setup_command_line(command_line)就是用来处理uboot传递进来的启动参数的(处理TAG)。obsolete_checksetup从__setup_start到 __setup_end,调用用非early标识的函数;do_early_param从__setup_start到 __setup_end,调用用early标识的函数(但因为__setup_param(str, fn, fn, 0)中early赋值为0,所以不在这里调用),所以我们主要用obsolete_checksetup。这在后面我们会提到。mount_root是挂载根文件系统,因为Linux上的应用程序最终要在根文件系统上运行。最后是init_post中运行应用程序。那么现在就有一个问题,Linux内核是如何接收uboot传来的根文件系统信息的呢?

bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

  上面是uboot启动时打印的环境变量。其中我们能够看到根文件系统挂载到第4个分区:root=/dev/mtdblock3 (从0分区开始)。上面我们提到过,setup_arch(&command_line)和setup_command_line(command_line)就是用来处理uboot传递进来的启动参数的(处理TAG)。但这个处理只是简单的复制粘贴而已,这两个函数将TAG保存,但并未进行真正的处理。那么真正告诉内核在哪里挂载的函数是什么呢?我们通过查看prepare_namespace可以看到一个saved_root_name。查找saved_root_name,发现在Do_mounts.c文件中有对它的调用:

static int __init root_dev_setup(char *line)
{strlcpy(saved_root_name, line, sizeof(saved_root_name));return 1;
}__setup("root=", root_dev_setup);   //传入一个字符串,一个函数

  根据我们之前的经验,我们可以猜测这个__setup宏,也是定义了一个结构体。通过查找__setup我们找到了它的宏定义:

Dir:init.h
#define __setup(str, fn)                    \__setup_param(str, fn, fn, 0)#define __setup_param(str, unique_id, fn, early)            \static char __setup_str_##unique_id[] __initdata = str;    \static struct obs_kernel_param __setup_##unique_id    \__attribute_used__                \__attribute__((__section__(".init.setup")))    \__attribute__((aligned((sizeof(long)))))    \= { __setup_str_##unique_id, fn, early }

  在init.h文件里,定义__setup等于__setup_param。那么在__setup_param的宏定义里,我们可以知道:它先定义了一个字符串,然后定义了一个结构体类型obs_kernel_param __setup。这个结构体的段属性为.init.setup,内容为一个字符串,一个函数,还有early。具备这个属性的结构体被链接脚本文件放到一起,从__setup_start到 __setup_end搜索调用。在vmlinux.lds中
  __setup_start = .;
   *(.init.setup)
  __setup_end = .;

  但是在Flash里没有分区,只能和uboot一样,将分区在代码里写死。一般在启动Linux的时候,Linux会自动打印出分区的信息。这里我的分区是这样的:

Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit":
0x00000000-0x00040000 : "bootloader"
0x00040000-0x00060000 : "params"
0x00060000-0x00260000 : "kernel"
0x00260000-0x10000000 : "root"

  我们搜索这个分区名 grep "\"bootloader\"" * -nR。在arch/arm/plat-s3c24xx中找到分区代码:

static struct mtd_partition smdk_default_nand_part[] = {[0] = {.name   = "bootloader",.size   = 0x00040000,.offset    = 0,},[1] = {.name   = "params",.offset = MTDPART_OFS_APPEND,.size   = 0x00020000,},[2] = {.name   = "kernel",.offset = MTDPART_OFS_APPEND,.size   = 0x00200000,},[3] = {.name   = "root",.offset = MTDPART_OFS_APPEND,.size   = MTDPART_SIZ_FULL,}
};

  就是这样,在处理完uboot传递的参数,进行CPU和单板的校验,挂载根文件系统等一系列操作后,最终内核执行init_post()中的应用程序。内核启动流程讲解完毕^_^

转载于:https://www.cnblogs.com/xieqing/p/6519880.html

Linux内核启动过程概述相关推荐

  1. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  2. Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述     一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader ...

  3. linux文件系统启动流程,linux 内核启动过程以及挂载android 根文件系统的过程

    转载 作者:汕头大学-黄珠唐 时间:2009 年10 月29 日 主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源 ...

  4. linux内核启动过程5:启动用户空间

    上一篇<<linux内核启动过程4:内核运行时>>分析到了内核进入运行时状态(不退出),本篇分析用户空间(用户层)的加载过程. 启动应用空间 进入kernel_init函数,在 ...

  5. linux内核启动过程4:内核运行时

    上一篇<<linux内核启动过程3:内核初始化阶段>>分析到了start_kernel执行流程,本篇继续内核切换到运行时状态. 内核运行时状态 内核初始化流程已经分析完成,如何 ...

  6. linux内核启动过程3:内核初始化阶段

    上一篇<<linux内核启动过程2:保护模式执行流程>>分析了保护模式启动过程以及bzImage的解压入口函数,本篇继续分析内核启动过程,从保护模式到C代码初始化. start ...

  7. 从linux内核启动,学习Linux内核启动过程:从start_kernel到init

    一.实验步骤: 1:运行menuos: a)cd LinuxKernel/ b)qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd root ...

  8. linux启动过程中内核拷贝,轻松识破linux内核启动过程中的“”套路“”

    内核启动流程相关的内容让很多热爱linux的小伙伴既爱又恨,因为这是了解linux系统基本构造的良好过程同时由于其本身复杂且底层,脑子中的脉络不是很清晰,本文就总结了一些优秀博文,以自己的理解来解构一 ...

  9. linux内核启动过程2:保护模式执行流程

    上一篇<<linux内核压缩制作bzImage>>分析了bzImage制作流程,本篇继续分析内核启动过程,从实模式跳转到保护模式及后续执行流程. protected_mode_ ...

最新文章

  1. 【HDU 2507】【ACM-ICPC算法基础训练教程 题1-6】迷瘴(贪心)
  2. pta龟兔赛跑Java_PTA-龟兔赛跑
  3. linux中线程的挂起与恢复(进程暂停)
  4. redis 图片2进制保存_Redis数据结构底层的SDS了解吗
  5. php td生成excel 斜杠表头_邮件合并功能都不掌握,还怎么成为Excel达人?
  6. u盘循环冗余能修复吗_激素脸怎么办?激素脸还能改善修复好吗?
  7. oracle是否启用dataguard,启动和关闭data guard的步骤
  8. 安卓应用间的数据共享:ContentProvider、ContentResolver全解
  9. python:for循环修改list的值,应使用range
  10. 最全的Vim操作快捷键
  11. linux系统构建学习笔记
  12. 获取自己win10系统的产品密匙
  13. 数据流程分析【停车场管理系统】
  14. gabor滤波 matlab,图像处理 – 使用matlab应用Gabor方程创建Gabor滤波器
  15. wamp服务器离线 所有服务正常运行_你在使用开票软件的时候“突然”!发生“离线发票”我给你支几招...
  16. redisson + CacheManager缓存管理
  17. matlab计算光场斯托克斯参量来表征偏振态
  18. ssh服务器banner信息,几种情况下的banner信息修改
  19. 盘古开源:计算机信息安全中大数据技术的应用策略
  20. 英语单词如何正确发音?入股血赚!!!

热门文章

  1. 【嵌入式】C语言高级编程-可变参数宏(12)
  2. 【Protocol Buffer】Protocol Buffer入门教程(八):Windows平台部署Protobuf环境
  3. 【Linux】一步一步学Linux——whatis命令(14)
  4. 【信息安全】ECC加密算法入门介绍
  5. Android 高仿微信6.0主界面 带你玩转切换图标变色
  6. 移动零—leetcode283
  7. 剑指offer 从上到下打印二叉树
  8. java redis服务_java链接redis服务器
  9. 计算机中减法的实现原理
  10. 什么是Boot Loader