目录

kernel(四)源码浅析

  • 建立工程
  • 启动简析
  • head.s
  • 入口点
  • 查询处理器
  • 查询机器ID
  • 启动MMU
  • 其他操作
  • start_kernel
  • 处理命令行
  • 分区

kernel(四)源码浅析

建立工程

  1. 移除所有Arch,添加Arch/arm 下除了 Mach_xxx 开头的,Mach_xxx 表示机器型号,添加2410,2440,剔除 Plat_xxx,加入plat-s3c24xx

    Arch/arm/boot
    common
    configs
    kernel
    libmach-s3c2410
    mach-s3c2440
    plat-s3c24xxmm
    nwfpe
    oprofile
    tools
    vfp
  2. 移除include目录,先排除所有Asm相关,只加入asm-arm顶层文件以及2440相关的如下

    include/
    排除所有 asm-xxxinclude/asm-arm/下添加 所有顶层以及以下目录
    arch-s3c2410
    hardware
    mach
    plat-s3c24xx

启动简析

uboot通过theKernel (0, bd->bi_arch_number, bd->bi_boot_params)启动内核;中的第二个参数是机器ID,内核通过比对机器ID判断是否支持启动.gd->bd->bi_arch_number = MACH_TYPE_S3C2440;linux会这么做:

  1. 处理uboot传入的参数
  2. 挂接根文件系统
  3. 最终目的:运行应用程序(在根文件系统上)

内核跳转之前,Uboot设置内核的启动参数.内核的参数是按照tag组织的.也就是在某个地址(0x30000100,在100ask24x0.c中定义),按照某种格式存储,这种格式具体为【size....tagid....tag值】

head.s

我们发现在arch\arm\boot\compressed也存在一个head.S的文件,有些内核编译出来比较大,他会以压缩的形式存在也就是包含了自解压的代码,这个文件就是讲压缩的文件解压,在这里不做分析。我们的入口为arch\arm\kernel\head.S

入口点

链接脚本有写,也就是说_stext段为最早的入口点,搜索下head.S中的入口点.text.head

 .text.head : {                     #先放所有文件的 .text.head 段_stext = .;_sinittext = .;*(.text.head)}

查询处理器

查看是否支持__lookup_processor_type

    .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_tables

内核能够支持哪些处理器,是在编译内核时定义下来的。内核启动时去读寄存器:获取 ID。看内核是否可以支持这个处理器。若能支持则继续运行,不支持则跳到_error_p中去,这是个死循环

mrc p15, 0, r9, c0, c0      @ get processor id
bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
movs    r10, r5             @ invalid processor (r5=0)?
beq __error_p           @ yes, error 'p'

查询机器ID

如果不支持这个机器ID则跳转到__error_a,这也是死循环

bl  __lookup_machine_type       @ r5=machinfo
movs    r8, r5              @ invalid machine (r5=0)?
beq __error_a           @ yes, error 'a'

机器ID是存在R1的,因为theKernel (0, bd->bi_arch_number, bd->bi_boot_params)

3:  .long   ..long   __arch_info_begin.long   __arch_info_end@  __arch_info_begin 和 __arch_info_end 是在链接脚本中定义的
@   __arch_info_begin = .;
@   *(.arch.info.init)
@  __arch_info_end = .;/** Lookup machine architecture in the linker-build list of architectures.* Note that we can't use the absolute addresses for the __arch_info* lists since we aren't running with the MMU on (and therefore, we are* not in the correct address space).  We have to calculate the offset.**  r1 = machine architecture number* Returns:*  r3, r4, r6 corrupted*  r5 = mach_info pointer in physical address space*/.type   __lookup_machine_type, %function
__lookup_machine_type:adr r3, 3b                  @ r3= address of 3b,这个时候mmu还没有启动,是物理地址ldmia   r3, {r4, r5, r6}    @ r4=.,r5=__arch_info_begin,r6=__arch_info_end@ 这个.代表了3这个标号的虚拟地址sub r3, r3, r4              @ get offset between virt&phys 虚拟地址与物理地址的偏差 @将r5,r6转换为实际的物理地址add r5, r5, r3              @ convert virt addresses to    add r6, r6, r3              @ physical address space1:  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, lr

首先是将虚拟地址转换为物理地址,因为这个时候UBOOT 启动内核时,MMU 还没启动,r3 这是实际存在的地址

3:  .long   ..long   __arch_info_begin.long   __arch_info_endadr r3, 3b  

接下来的r4, r5, r6都是虚拟地址了. .代表虚拟地址。是标号为3的指令的虚拟地址.可以通过r3.(虚拟地址)来计算偏差.

sub r3, r3, r4  @ r3=r3-r4,也就r3=物理地址-虚拟地址的偏差
实际物理地址=虚拟地址+r3即可add r5, r5, r3              @ convert virt addresses to
add r6, r6, r3              @ physical address space

查看下__arch_info_begin和__arch_info_end具体是什么,这个是在链接脚本定义如下的,也就是代表了一个段

__arch_info_begin = .;*(.arch.info.init)
__arch_info_end = .;

.arch.info.initarch.h 中有定义 ,定义某个结构体(machine_desc)的段属性

#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             \
};

有如下应用

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

展开看看

  static const 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 这个结构体内容,可以发现支持多少单板,就有多少这个宏的使用

 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);
};

接下去就是从这个结构体读取第一个参数nr也就是ID来逐个比较了.这个在内核中定义与uboot定义是一致的

#define MACH_TYPE_S3C2440              362

启动MMU

bl  __create_page_tables    @创建页表
ldr r13, __switch_data      @ address to jump to after,这是使能mmu后的跳转地址@ mmu has been enabled
adr lr, __enable_mmu        @ return (PIC) address  使能mmu
add pc, r10, #PROCINFO_INITFUNC@__enable_mmu 中会调用 __turn_mmu_on,最后 mov pc, r13
b   __turn_mmu_on.align  5.type   __turn_mmu_on, %function
__turn_mmu_on:mov r0, r0mcr p15, 0, r0, c1, c0, 0       @ write control regmrc p15, 0, r3, c0, c0, 0       @ read id regmov r3, r3mov r3, r3mov pc, r13                     @这个是关键,pc最后=r13=__switch_data

其他操作

复制数据段,清bss段等操作

start_kernel

启动mmu后会跳转到__switch_data,如何跳到 __switch_data,在__enable_mmu 中会调用 __turn_mmu_on这个函数最后 mov pc, r13,在调用__enable_mmu 前是先赋值的ldr r13, __switch_data

ldr r13, __switch_data      @ address to jump to after
....b   start_kernel

注意 这是内核的第一个 C 函数,接下来要处理UBOOT 传输的第三个启动参数bd->bi_boot_params.这个文件在init/main.c,在以下函数处理参数

setup_arch(&command_line);
setup_command_line(command_line);

一览流程如下

start_kernelsetup_arch          //解析uboot传入的参数,只是先存起来字符串setup_command_line  //解析uboot传入的参数,只是先存起来字符串parse_early_paramdo_early_param从__setup_start到__setup_end,调用early函数unknown_bootoptionobsolete_checksetup从__setup_start到__setup_end,调用非early函数rest_initkernel_initprepare_namespacemount_root  //挂接根文件系统,init_post    //执行应用程序注意:
__setup_start和__setup_end是在链接脚本定义,它们中间包含的是所有文件的(.init.setup)段,这种段是通过一个宏来设置的,有以下宏定义:#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 }将宏函数__setup("root=", root_dev_setup)进行宏展开得到:static char __setup_str_root_dev_setup[] __initdata = "root="; \static struct obs_kernel_param __setup_root_dev_setup  \__attribute_used__             \__attribute__((__section__(".init.setup")))  \__attribute__((aligned((sizeof(long)))))   \= { __setup_str_root_dev_setup, root_dev_setup, 0}
从上面可以看出宏函数__setup("root=", root_dev_setup);定义一个结构体变量,这个结构体的段属性被强制设置为(".init.setup")段。

setup_arch(解析tag)

这里是先查找mdesc这个结构体,这个结构体在上面分析机器ID的时候已经发现他保存了一系列参数.boot_params就是uboot存放参数的地址.然后在parse_tags(tags)处理具体的tag

if (mdesc->boot_params)tags = phys_to_virt(mdesc->boot_params);//在上面定义机器id结构体的时候,.boot_params  = S3C2410_SDRAM_PA + 0x100,=0x30000100
#define S3C2410_CS6 (0x30000000)
#define S3C2410_SDRAM_PA    (S3C2410_CS6)
//我们在uboot的时候存储参数的地址也是这个
board_init -----gd->bd->bi_boot_params = 0x30000100;
然后开始处理tagsstatic const 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,};

setup_command_line

所谓命令行,就是uboot设置的bootargs,linux通过getenv("bootargx")获取参数,如果没有设置这个参数,内部有一个默认参数. 这里只是将命令行复制到指定的数组,并没有处理

char *from = default_command_line;      //这是默认的命令行参数static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
#define CONFIG_CMDLINE "root=/dev/hda1 ro init=/bin/bash console=ttySAC0"memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); //
// cmdline_p 是 传递的参数,用作拷贝
// from 是默认参数

挂载根文件系统

创建一个线程,可以理解为运行程序kernel_init

rest_initkernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);kernel_init>prepare_namespace>mount_root 挂在根文件系统>init_post(); 执行应用程序

处理命令行

uboot设置命令tag,多了参数commandline,源自环境变量bootargs查看下环境变量bootargs,使用print查看,也可搜索下代码

"bootargs=" CONFIG_BOOTARGS         "\0"
//include/configs/100ask24x0.h
#define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"
  • root=/dev/mtdblock3表示根文件系统从第四个FLASH分区开始(从0开始计数)可以往上看分区空间
  • init=/linuxrc指示第一个应用程序
  • console=ttySAC0,内核打印信息从串口0 打印

我们需要知道ROOT_DEV是什么,可以看到在函数prepare_namespace中有saved_root_name存储这个这个数组.

if (saved_root_name[0]) {root_device_name = saved_root_name;if (!strncmp(root_device_name, "mtd", 3)) {mount_block_root(root_device_name, root_mountflags);goto out;}ROOT_DEV = name_to_dev_t(root_device_name);if (strncmp(root_device_name, "/dev/", 5) == 0)root_device_name += 5;
}

搜索下saved_root_nameroot_dev_setup对齐赋值,再继续查找函数的引用,只有一个宏使用了它

static int __init root_dev_setup(char *line)
{strlcpy(saved_root_name, line, sizeof(saved_root_name));return 1;
}

解析下这个宏__setup("root=", root_dev_setup);

#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 }static char __setup_str_root_dev_setup[] __initdata = "root=";
static struct obs_kernel_param __setup_root_dev_setup __attribute_used____attribute__((__section__(".init.setup"))) __attribute__((aligned((sizeof(long)))))    ={__setup_str_root_dev_setup,root_dev_setup,root_dev_setup,0}这个结构体的原型如下
struct obs_kernel_param
{const char *str;int (*setup_func)(char *);int early;
};

最终大概分析一下也就是定义了一个char数组和一个有特殊段属性.init.setup的结构体.注意这里的early是0.这个段属性肯定是在lds中定义,搜索下这个段的起始和结束地址的调用情况.

  __setup_start = .;*(.init.setup)__setup_end = .;obsolete_checksetupdo_early_paramstatic int __init do_early_param(char *param, char *val)
{struct obs_kernel_param *p;for (p = __setup_start; p < __setup_end; p++) {if (p->early && strcmp(param, p->str) == 0) {if (p->setup_func(val) != 0)printk(KERN_WARNING"Malformed early option '%s'\n", param);}}/* We accept everything at this stage. */return 0;
}static int __init obsolete_checksetup(char *line)
{struct obs_kernel_param *p;int had_early_param = 0;p = __setup_start;do {int n = strlen(p->str);if (!strncmp(line, p->str, n)) {if (p->early) {/* 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))return 1;}p++;} while (p < __setup_end);return had_early_param;
}

可以看出obsolete_checksetup先判断这个结构的early属性,为0则执行setup_func方法,这符合我们的这个宏__setup("root=", root_dev_setup);

结论:挂接根文件系统的参数是由命令行给出的,内核函数去分析这个命令行,去赋值ROOT_DEV

分区

分区表是没有的,是代码里面写死的,我们可以启动内核的时候发现有以下输出

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

可以搜索这个"bootloader"发现在arm/plat-s3c24xx/common-smdk.c下面定义

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,}
};

MTDPART_OFS_APPEND 这个 offset 意思是紧接着上面一个分区的意思

总结

转载:https://www.cnblogs.com/zongzi10010/p/10023696.html

内核启动流程分析(四)源码浅析相关推荐

  1. Spring事件监听流程分析【源码浅析】

    一.简介 Spring早期是通过实现ApplicationListener接口来定义监听事件,在spring4.2的时候开始我们可以通过@EventListener注解来定义监听事件,Applicat ...

  2. 一起分析Linux系统设计思想——03内核启动流程分析(六)

    在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习.我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直 ...

  3. struts2 处理请求流程分析(结合源码)1- 孤星随缘ツ http://t.sina.com.cn/samzhxing-iteye技术网站...

    struts2 处理请求流程分析(结合源码)1- 孤星随缘ツ http://t.sina.com.cn/samzhxing-iteye技术网站 2011年08月01日 struts2 源码版本2.0. ...

  4. 详述 Spring MVC 启动流程及相关源码分析

    文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...

  5. android源码学习- APP启动流程(android12源码)

    前言: 百度一搜能找到很多讲APP启动流程的,但是往往要么就是太老旧(还是基于android6去分析的),要么就是不全(往往只讲了整个流程的一小部分).所以我结合网上现有的文章,以及源码的阅读和调试, ...

  6. Linux内核启动流程分析(一)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...

  7. 低温linux内核启动readl,Linux内核启动流程分析(一)

    很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接粘过来的有点乱,敬请谅解! S3C2410 Linux 2.6.35.7启动分析(第一阶段) 1.依据arch/ar ...

  8. 韦东山衔接班——3.4_linux内核启动流程分析之内核启动

    作者:GWD 时间:2018.8.25 一.汇编部分到第一个C之前准备 1.问:内核的工作 答 2.问:为什么有两个head.S 答:其中一个是自解压代码,运行时先解压内核 3.uboot中将参数写入 ...

  9. Redis源码阅读01-读了一下redis启动流程涉及的源码我都读了个啥

    阅读源码是学习一门技术的必经之路,经过1周左右的c语言入门学习,我就开始硬读redis的源码了.因为公司的多版本的改造,所以源码就选择redis6.x的最高版本redis6.2.7. 在阅读源码前,首 ...

最新文章

  1. thinkphp-volist2
  2. 一个待办事列表todolist
  3. 【Linux学习】pthread_create主线程与创建的新线程之间退出关系
  4. The Use Case Definition in UML
  5. 图解 Go 切片的深拷贝和浅拷贝
  6. HDOJ水题集合11:桶排序, 折半搜索
  7. sprint test 添加事务回滚机制
  8. linkedblockingqueue 后 take 不消化_消化不良的症状原因有哪些?
  9. SQLServer2019安装教程
  10. php做网站步骤_怎么制作php网站
  11. Axure简易计算器
  12. wps演示怎么提高列表级别_wps文字如何设置标题级别
  13. 中国石油大学《输气管道设计与管理(含课程设计)》第三阶段在线作业
  14. 基于DCT的信息隐藏
  15. 冬天洗衣不动手,这几款智慧洗衣机可以帮到你
  16. SMS短信通API——(3)SMS短信通资费标准
  17. nginx 同一个端口同时 支持 http 和 https
  18. JAVA —— 就业面面案例①(WEB综合案例)
  19. Java实现excel大数据量导出
  20. 金山云与天润科技战略合作 推动智慧城市建设

热门文章

  1. Python3入门(十一)——IO编程
  2. puppet 工作原理
  3. JavaScript采用append添加的元素错误
  4. Spring JTA应用JOTM Atomikos III Atomikos
  5. SharePoint场管理-PowerShell(一)
  6. 关于javascript 一些有用的数值转换常量和方法
  7. GIS地图界面和计算机图形学填充算法
  8. 图解Oracle dump 命令初步
  9. Turtle-可视化界面画圣诞树
  10. 关于变量作用域的一点整理