1 linux内核启动过程分析.docx

上传人:b****2 文档编号:24238309 上传时间:2023-05-25 格式:DOCX 页数:24 大小:23.61KB
下载 相关 举报
1 linux内核启动过程分析.docx_第1页
第1页 / 共24页
1 linux内核启动过程分析.docx_第2页
第2页 / 共24页
1 linux内核启动过程分析.docx_第3页
第3页 / 共24页
1 linux内核启动过程分析.docx_第4页
第4页 / 共24页
1 linux内核启动过程分析.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

1 linux内核启动过程分析.docx

《1 linux内核启动过程分析.docx》由会员分享,可在线阅读,更多相关《1 linux内核启动过程分析.docx(24页珍藏版)》请在冰豆网上搜索。

1 linux内核启动过程分析.docx

1linux内核启动过程分析

linux内核启动过程分析      

嵌入式linux系统从软件角度来看可分为四部分:

bootloader,linux内核,文件系统和应用程序。

在这里我选取的内核版本是linux2.6.28,硬件平台选择smdk6410。

Bootloader是系统启动或复位后首先被执行的代码,它的主要作用是初始化处理器,初始化ram,初始化相应的外设(uart,usb等等),下载内核映像(或文件系统)到ram相应的位置,然后跳转到内核下载地址c0008000,将控制权交给linux内核。

Linux内核下载到ram中的映像一般是zImage。

这是压缩版本的内核,首先要进行解压操作。

调用decompress_kernel()(位于arch/arm/boot/compressed/misc.c)进行解压缩操作,然后再次跳到c0008000,进行真正的内核初始化操作。

我们重点放在讲解内核映像解压之后linux内核的启动过程。

内核初始化启动过程如下:

1)__lookup_processor_type(),查找处理器类型。

2)__lookup_machine_type(),查找机器类型。

3)__vet_atags()。

4)__create_page_tables(),创建页表。

5)__enable_mmu(),使能MMU。

6)__mmap_switched(),拷贝数据,清BBS。

7)start_kernel(),进入真正的内核初始化函数。

8)smp_setup_processor_id();

9)unwind_init();

10)lockdep_init();

11)debug_objects_early_init();

12)cgroup_init_early();

13)local_irq_disable();

14)early_boot_irqs_off();

15)early_init_irq_lock_class();

16)lock_kernel();

17)tick_init();

18)boot_cpu_init();

19)page_address_init();

20)setup_arch(&command_line);

21)mm_init_owner(&init_mm,&init_task);

22)setup_command_line(command_line);

23)unwind_setup();

24)setup_per_cpu_areas();

25)setup_nr_cpu_ids();

26)smp_prepare_boot_cpu();

27)sched_init();

28)preempt_disable();

29)build_all_zonelists();

30)page_alloc_init();

31)parse_early_param();

32)sort_main_extable();

33)trap_init();

34)rcu_init();

35)init_IRQ();

36)pidhash_init();

37)init_timers();

38)hrtimers_init();

39)softirq_init();

40)timekeeping_init();

41)time_init();

42)sched_clock_init();

43)profile_init();

44)early_boot_irqs_on();

45)local_irq_enable();

46)console_init();

47)lockdep_info();

48)locking_selftest();

49)vmalloc_init();

50)vfs_caches_init_early();

51)cpuset_init_early();

52)page_cgroup_init();

53)mem_init();

54)enable_debug_pagealloc();

55)cpu_hotplug_init();

56) kmem_cache_init();

57) debug_objects_mem_init();

58) idr_init_cache();

59) setup_per_cpu_pageset();

60) numa_policy_init();

61) if(late_time_init)

62) late_time_init();

63) calibrate_delay();

64) pidmap_init();

65) pgtable_cache_init();

66) prio_tree_init();

67) anon_vma_init();

68) thread_info_cache_init();

69) fork_init(num_physpages);

70) proc_caches_init();

71) buffer_init();

72) key_init();

73) security_init();

74) vfs_caches_init(num_physpages);

75) radix_tree_init();

76) signals_init();

77) page_writeback_init();

78) proc_root_init();

79) cgroup_init();

80) cpuset_init();

81) taskstats_init_early();

82) delayacct_init();

83) check_bugs();

84) acpi_early_init();

85) ftrace_init();

86) rest_init();  

1.1__lookup_processor_type()

话说内核映像解压后,又跳到c0008000这个地址。

这个地址指向内核代码的什么地方,我们肯定很想知道。

在arch/arm/kernel/vmlinux.lds.S中,可以发现这样的代码:

SECTIONS

{

#ifdefCONFIG_XIP_KERNEL

      .=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

      .=PAGE_OFFSET+TEXT_OFFSET;

#endif

      .text.head:

{

             _stext=.;

             _sinittext=.;

             *(.text.head)

      }

}

一般内核都不配置成XIP方式的,所以这段脚本等同于:

SECTIONS

{

      .=PAGE_OFFSET+TEXT_OFFSET;

      .text.head:

{

             _stext=.;

             _sinittext=.;

             *(.text.head)

      }

}

这段脚本告诉我们SECTIONS的起始地址是.text.head的起始地址_stext,且

_stext=PAGE_OFFSET+TEXT_OFFSET;

PAGE_OFSET在.config文件中设置:

PAGE_OFFSET=0xC0000000;

TEXT_OFFSET在架构目录下的Makefile文件中设置:

textofs-y  :

=0x00008000

TEXT_OFFSET:

=$(textofs-y)

 

结合arch/arm/kernel/head.S,会发现如下代码:

.section".text.head","ax"

ENTRY(stext)

      msr cpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode

                                         @andirqsdisabled

      mrcp15,0,r9,c0,c0         @getprocessorid

      bl    __lookup_processor_type            @r5=procinfor9=cupid

ENDPROC(stext)

第一句代码的意思是表示下面的内容都属于.text.head段的,”ax”表示这段内容是可分配且可执行的(allocableandexecutable)。

所以c0008000处放的代码就是stext的入口地址。

接下来的

msr cpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE

就是把fiq_mask(快速中断屏蔽位)和irq_mask(快速中断屏蔽位)都置位,同时把处理器模式设置为svc模式。

这就是要告诉闲杂人等不要来打扰,这里要办重要的事。

cpsr_c代表当前状态寄存器;鉴于当前状态寄存器的重要性,arm特意开发了msr指令,专门用来设置当前状态寄存器。

mrcp15,0,r9,c0,c0

是将协处理器cp15c0的值赋值到r9中。

接下来的

bl    __lookup_processor_type

是长跳转到__lookup_processor_type,查看processorID是否被内核支持。

__lookup_processor_type:

      adr  r3,3f

      ldmda     r3,{r5-r7}

      sub r3,r3,r7              @getoffsetbetweenvirt&phys

      add r5,r5,r3              @convertvirtaddressesto

      add r6,r6,r3              @physicaladdressspace

1:

    ldmia      r5,{r3,r4}                  @value,mask

      and r4,r4,r9              @maskwantedbits

      teq  r3,r4

      beq 2f

      add r5,r5,#PROC_INFO_SZ            @sizeof(proc_info_list)

      cmpr5,r6

      blo  1b

      movr5,#0                          @unknownprocessor

2:

    movpc,lr

ENDPROC(__lookup_processor_type)

adr是条伪指令,作用就是把标号为3位置的地址赋值给r3寄存器。

3后面加f是表示这是个长距离(far)的标号。

有同学可能就要问了,ldr也能起到这个作用,为什么不用ldr?

首先ldrr3,3f取的是标号3这个地址的内容,而不是地址本身;其次,可以用ldrr3,=3f来取地址本身,但这是一个绝对地址;而adr取得的是相对地址。

如果要保证程序在任何内存都能运行,就必须保证代码是地址无关的,也就是PIC(positionindependentcode)。

显然adr伪指令很对PIC的胃口,它的取相对地址方式符合PIC的设定。

      .long      __proc_info_begin

      .long      __proc_info_end

3:

    .long      .

      .long      __arch_info_begin

      .long      __arch_info_end

我们接着往下看。

ldmda     r3,{r5-r7}

      sub r3,r3,r7              @getoffsetbetweenvirt&phys

      add r5,r5,r3              @convertvirtaddressesto

      add r6,r6,r3              @physicaladdressspace

1:

    ldmia      r5,{r3,r4}                  @value,mask

      and r4,r4,r9              @maskwantedbits

      teq  r3,r4

      beq 2f

      add r5,r5,#PROC_INFO_SZ            @sizeof(proc_info_list)

      cmpr5,r6

      blo  1b

      movr5,#0                          @unknownprocessor

2:

    movpc,lr

ldmadar3,(r5-r7)是把标签3所指的地址的内容(也就是标签3的虚拟地址)赋值给r7,把比标签3所指的地址小4的地址的内容(也就是__proc_info_end)赋值给r6,把比标签3所指的地址小8的地址的内容(__proc_info_begin)赋值给r5。

这里的虚拟地址是线性逻辑地址,它和物理地址之间有着一一映射关系。

因为__proc_info_begin和__proc_info_end都是虚拟地址,此时我们MMU还没有打开,就必须要使用物理地址。

这就需要我们先把它们转换为物理地址。

接下来的三句代码就是完成这样的工作。

__proc_info_begin和__proc_info_end是在vlinux.lds.S中定义的。

__proc_info_begin=.;

                    *(.proc.info.init)

             __proc_info_end=.;

这说明在__proc_info_begin和__proc_info_end之间的是所有的.proc.info.init段。

我们可以在arch/arm/mm/proc_*.S中找到相应的.proc.info.init段。

Smdk6410属于armv6,我们可以在proc_v6.S找到armv6处理器的id和id掩码。

之后的代码就是把处理器的id和id掩码赋值到r3,r4中;把r9与处理器掩码做与操作,然后与处理器id(r3)比较,看是否相等;如不相等,就取下一个处理器id进行比较;如果到最后都没有处理器id相符,就将r5赋值为0:

1:

    ldmia      r5,{r3,r4}                  @value,mask

      and r4,r4,r9              @maskwantedbits

      teq  r3,r4

      beq 2f

      add r5,r5,#PROC_INFO_SZ            @sizeof(proc_info_list)

      cmpr5,r6

      blo  1b

      movr5,#0                          @unknownprocessor

2:

    movpc,lr

最后一句是跳出__lookup_processor_type函数。

跳出之后会对处理器id是否有效做一个判断;如果不是有效的处理器,就进行相应的错误处理;如果是有效的处理器,就进行机器类型查找:

      movs     r10,r5                        @invalidprocessor(r5=0)?

      beq __error_p                    @yes,error'p'

bl    __lookup_machine_type       @r5=machinfo

1.2__lookup_machine_type()

机器类型的查找代码如下:

__lookup_machine_type:

      adr  r3,3b

      ldmia      r3,{r4,r5,r6}

      sub r3,r3,r4              @getoffsetbetweenvirt&phys

      add r5,r5,r3              @convertvirtaddressesto

      add r6,r6,r3              @physicaladdressspace

1:

    ldr  r3,[r5,#MACHINFO_TYPE]      @getmachinetype

      teq  r3,r1                          @matchesloadernumber?

      beq 2f                        @found

      add r5,r5,#SIZEOF_MACHINE_DESC    @nextmachine_desc

      cmpr5,r6

      blo  1b

      movr5,#0                          @unknownmachine

2:

    movpc,lr

ENDPROC(__lookup_machine_type)

我们可以看到,这和处理器类型查找函数很类似,在这里只进行简单的解说。

      .long      __proc_info_begin

      .long      __proc_info_end

3:

    .long      .

      .long      __arch_info_begin

      .long      __arch_info_end

 

 

 

__arch_info_begin和__arch_info_end在arch/arm/kernel/vlinux.lds.S中定义:

             __arch_info_begin=.;

                    *(.arch.info.init)

             __arch_info_end=.;

.arch.info.init段我们可以找到在arch/arm/include/asm/mach/arch.h中有引用:

#defineMACHINE_START(_type,_name)                 \

staticconststructmachine_desc__mach_desc_##_type   \

 __used                                           \

 __attribute__((__section__(".arch.info.init")))={    \

      .nr         =MACH_TYPE_##_type,           \

      .name            =_name,

 

#defineMACHINE_END                           \

};

我们可以在arch/arm/mach-*.c文件中找到一系列关于MACHINE_START所定义的结构。

1.3__vet_atags()

函数代码如下:

__vet_atags:

      tst   r2,#0x3               @aligned?

      bne 1f

 

      ldr  r5,[r2,#0]                  @isfirsttagATAG_CORE?

      subsr5,r5,#ATAG_CORE_SIZE

      bne 1f

      ldr  r5,[r2,#4]

      ldr  r6,=ATAG_CORE

      cmpr5,r6

      bne 1f

 

      movpc,lr                           @atagpointerisok

 

1:

    movr2,#0

      movpc,lr

ENDPROC(__vet_atags)

atag是bootloader传递给linux内核的参数列表。

这个参数列表是以tag的列表形式来表示的。

这个列表起始位置的tag是ATAG_CORE,用来表示这是一个有效的tag列表。

如果起始tag不是ATAG_CORE,就认为bootloader没有传递tag参数给内核。

以下是tag值的定义和描述,以及tag结构的定义。

 

Tagname

Value

Size

Description

ATAG_NONE

0x00000000

2

Emptytagusedtoendlist

ATAG_CORE

0x54410001

5(2ifempty)

Firsttagusedtostartlist

ATAG_MEM

0x54410002

4

Describesaphysicalareaofmemory

ATAG_VIDEOTEXT

0x54410003

5

DescribesaVGAtextd

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工作范文 > 行政公文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1