【linux】linux启动流程

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

邮箱:tangzhongp@163.com

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

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

  

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

阶段1

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

  

  

变量与文件的对应关系。

THUMB: src\arch\arm\include\asm\unified.h

head.S的很多定义在src\arch\arm\kernel\head-common.S

struct proc_info_list :src\arch\arm\include\asm\procinfo.h

PROC_INFO_SZ : build\include\generated\asm-offsets.h:

proc.info.init : src\arch\arm\mm\proc-arm920.S

PAGE_OFFSET:src\arch\arm\include\asm\memory.h = CONFIG_PAGE_OFFSET = 0xC0000000

  

寻找入口

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

源代码写好后,肯定是先使用编译工具编译代码,而编译工具也有相关的文件的,从这个思路,找到vmlinux.ldssrc\arch\arm\kernel\vmlinux.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 (src\arch\arm\kernel):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 (src\arch\arm\kernel):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

在src\arch\arm\kernel\vmlinux.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

 

src\init\main.c: asmlinkage void __init start_kernel(void)

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

src\include\linux\init.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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值