zoukankan      html  css  js  c++  java
  • 【linux】linux启动流程

    欢迎转载,转载时请保留作者信息,谢谢。

    邮箱:tangzhongp@163.com

    博客园地址:http://www.cnblogs.com/embedded-tzp

    Csdn博客地址:http://blog.csdn.net/xiayulewa

      

    墨迹这么久,总算开始内核源代码分析了。

    阶段1

    阶段1大部分为汇编, 以程序启动到执行到start_kernel函数为第一阶段。 大概流程如下:

      

      

    变量与文件的对应关系。

    THUMB: srcarcharmincludeasmunified.h

    head.S的很多定义在srcarcharmkernelhead-common.S

    struct proc_info_list :srcarcharmincludeasmprocinfo.h

    PROC_INFO_SZ : buildincludegeneratedasm-offsets.h:

    proc.info.init : srcarcharmmmproc-arm920.S

    PAGE_OFFSET:srcarcharmincludeasmmemory.h = CONFIG_PAGE_OFFSET = 0xC0000000

      

    寻找入口

    首先从从程序入口处开始分析,linux内核下文件多不胜数,那么从哪里开始找到程序入口地址呢?

    源代码写好后,肯定是先使用编译工具编译代码,而编译工具也有相关的文件的,从这个思路,找到vmlinux.ldssrcarcharmkernelvmlinux.lds),该文件指导arm-linux-ld如何工作。该文件开始有

    OUTPUT_ARCH(arm)

    ENTRY(stext)

    jiffies = jiffies_64;

    SECTIONS

    {

    …………..

    . = 0xC0000000 + 0x00008000;

      

    可见程序入口为段 stext,代码开始地址0xC0000000 + 0x00008000,即3G + 0x8000,使用source insightsearchsearch files,找到head.S (srcarcharmkernel):ENTRY(stext),点击定位到该处。往下看,有

    mrc    p15, 0, r9, c0, c0        @ get processor id协处理器操作,详细见ARM920T_TRM1_S.pdf

        bl    __lookup_processor_type        @ r5=procinfo r9=cpuid

    上述r9的内容为0x41009200,见下图,详细见ARM920T_TRM1_S.pdf

    入口找到了,接下来就好办了,只要有点arm汇编基础,读懂代码是没有问题的,本来想自己慢慢写的,但是发现网上别人已经写的好了,就不重新写了。详细见:Linux内核启动分析之 __lookup_processor_type函数:http://blog.sina.com.cn/s/blog_63ac1cef0100vbcj.html

    adr获取的为什么是实际运行的物理地址

    __lookup_processor_type:     adr    r3, __lookup_processor_type_data @ 假设在地址16

            mov r0, #1 @ 假设在地址20

    __lookup_processor_type_data: .long     @ 假设在地址24

    如上,adr是将当前PC值加一个偏移,得到目标地址,假如程序运行到上述第一条语句时:PC = 16, 因为 __lookup_processor_type_data与其相差8个字节的偏移,所以得到r3 = PC + 8 = 24; 而因为此时程序是运行的,PC代表的就是物理地址,所以adr获取的也就是物理地址了(严格来说这是不对的,因为PC是预取的,但是为了说明问题,不考虑预取功能,不然越说越复杂了)

      

    linux mmu未开启时是如何将虚拟地址转换为物理地址的

    此处对linuxadr计算真实物理地址讨论下,挺有意思的。Arm本来有三种地址,而此时未开启mmu,有MVA = VA(虚拟地址) = PA(物理地址).

    要讨论这,就不得不提下内存模型。内存有两个基本属性,地址 + 内容。

    围绕地址+内容,衍生了很多东西。包括c中的函数名,经汇编后对应的是汇编语言中的标号,即函数名是地址,这就是为什么存在函数指针一说, 如下表:

    编译链接后各变量有其固定的地址,即虚拟地址,运行时变量也有自己的地址,两变量不一定一致。见下图,图中的地址是假定的,实际不一定如此:

      

    上图两个内存里的内容必然是一致的,唯一的差别就在于地址。当运行adr r3, __lookup_processor_type_data时,获得的是运行的地址r3 = 0x8c, ldmia    r3, {r4 - r6} 是将0x8c, 0x90, 0x94地址里的内容加载到r4,r5, r6去,因此等效于将0xc000808c, 0x c0008090, 0xc0008094地址里的内容加载到r4,r5, r6, 0xc000808c地址的内存里面的内容就是他的地址,为0xc000808c

    因此上面r3= 0x8c为实际的物理地址,而0x8c对应的虚拟地址为0xc000808c

    根据PA1 – VA1 = PA2 – VA2 = 0x8c - 0xc000808c, PA – VA值确定了,编译后VA的值也确定了,所以可以反推所有的运行时刻代码所在的物理地址。

    为何 ARM(add pc, r10,#PROCINFO_INITFUNC):是执行函数 __arm920_setup

    Asm-offsets.c (srcarcharmkernel):130 中有:

    DEFINE(PROCINFO_INITFUNC,    offsetof(struct proc_info_list, __cpu_flush));

    src/arch/arm/include/asm/procinfo.h中有__cpu_flush定义:

      

    struct proc_info_list {

    unsigned int cpu_val;

    unsigned int cpu_mask;

    unsigned long __cpu_mm_mmu_flags; /* used by head.S */

    unsigned long __cpu_io_mmu_flags; /* used by head.S */

    unsigned long __cpu_flush; /* used by head.S */

    const char *arch_name;

    const char *elf_name;

    unsigned int elf_hwcap;

    const char *cpu_name;

    struct processor *proc;

    struct cpu_tlb_fns *tlb;

    struct cpu_user_fns *user;

    struct cpu_cache_fns *cache;

    };

    src/arch/arm/mm/pro-arm920.S中定义了struct proc_info_list __arm920_proc_info 其段在.proc.info.init :

    .section ".proc.info.init", #alloc, #execinstr

      

    .type __arm920_proc_info,#object

    __arm920_proc_info:

    .long 0x41009200

    .long 0xff00fff0

    .long PMD_TYPE_SECT |

    PMD_SECT_BUFFERABLE |

    PMD_SECT_CACHEABLE |

    PMD_BIT4 |

    PMD_SECT_AP_WRITE |

    PMD_SECT_AP_READ

    .long PMD_TYPE_SECT |

    PMD_BIT4 |

    PMD_SECT_AP_WRITE |

    PMD_SECT_AP_READ

    b __arm920_setup @ __cpu_flush

    .long cpu_arch_name

    .long cpu_elf_name

    .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

    .long cpu_arm920_name

    .long arm920_processor_functions

    .long v4wbi_tlb_fns

    .long v4wb_user_fns

    在srcarcharmkernelvmlinux.lds中有:

    .init.proc.info : {

    . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
            }

      

    .type __arm920_setup, #function

    __arm920_setup:

    mov r0, #0

    mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4

    mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4

    #ifdef CONFIG_MMU

    mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4

    #endif

    adr r5, arm920_crval

    ldmia r5, {r5, r6}

    mrc p15, 0, r0, c1, c0 @ get control register v4

    bic r0, r0, r5

    orr r0, r0, r6

    mov pc, lr

    .size __arm920_setup, . - __arm920_setup

    综上: ARM(add pc, r10,#PROCINFO_INITFUNC):是 执行函数 __arm920_setup, 该函数是对mmu作一些初始化。

    __create_page_tables:虚拟地址映射页表是如何创建的

    此处省略,在文章【linux】mm内存管理 http://blog.csdn.net/xiayulewa/article/details/45193741 有关于mmu比较详细的分析。

    进入阶段2:

    阶段2

    srcinitmain.c: asmlinkage void __init start_kernel(void)

    start_kernel: 第二阶段启动入口,开始熟悉的c语言之旅。  

    srcincludelinuxinit.h: __init

    第二阶段内容太多,不在这里具体写,后面结合具体例子再说,比如说明sysfs文件系统时,会有

    /sys目录建立: start_kernel→rest_init→kernel_init→kernel_init_freeable→do_basic_setup→ driver_init的流程说明。

    从start_kernel到 shell的流程:start_kernel→rest_init→kernel_init→run_init_process

  • 相关阅读:
    一个很好用的linux下系统清理工具
    怎样将linux+qt在1S中内启动的幻灯片教程
    通过 ulimit 改善系统性能
    UBI文件系统
    利用BLCR加快Android的启动过程
    工作队列中的sleep导致控制台无法输入问题
    android system setup and building (3)
    物理地址和虚拟地址1 (MMU)
    对 makefile 中 eval 函数的学习体会
    location.href语句与火狐不兼容的问题
  • 原文地址:https://www.cnblogs.com/embedded-tzp/p/4447515.html
Copyright © 2011-2022 走看看