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

  • 相关阅读:
    sqlserver中判断表或临时表是否存在
    Delphi 简单方法搜索定位TreeView项
    hdu 2010 水仙花数
    hdu 1061 Rightmost Digit
    hdu 2041 超级楼梯
    hdu 2012 素数判定
    hdu 1425 sort
    hdu 1071 The area
    hdu 1005 Number Sequence
    hdu 1021 Fibonacci Again
  • 原文地址:https://www.cnblogs.com/embedded-tzp/p/4447515.html
Copyright © 2011-2022 走看看