免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5711 | 回复: 5
打印 上一主题 下一主题

[其他] arm 启动流程。start_kernel之前部分。 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-11-01 17:05 |只看该作者 |倒序浏览
kernel编译过程:
1. ld 所有.o vmlinux.o
2. ld -T arch/arm/kernel/vmlinux.lds vmlinux.o 到 vmlinux (elf)
3. objcpy生成Image 二进制kernel镜像。这个Image很关键,kernel的所有可执行代码和数据都在这个里面。并且文件开始部分为内核的入口代码stext。
4. 将Image压缩,并增加解压代码。生成elf格式的compressed/vmlinux (elf)
5. objcpy生成zImage,最终的压缩过的包含自解压代码的二进制kernel镜像。

bootloader流程:
一般情况下,arm平台的kernel Image(或者zImage)存放在存储中(emmc,sd卡,硬盘等)的某个分区中(没有文件系统),一般是zImage。
bootloader作用初始化硬件,显示启动画面等等。最终要的是从存储中加载kernel zImage并且跳到zImage执行。
ps:x86的bootloader一般是grub,功能比较强大,kernel image也是作为文件存放的。


zImage解压:
zImage会解压kernel,并且拷贝到zreladdr物理地址,然后跳转过去执行kernel。
zreladdr是平台相关的,一般的平台是0x8000,这样内核的物理地址和虚拟地址刚好是3G偏移,后面建页表会用到。
当然假设现在我们的平台的物理内存前1M是保留给其他用途的,所以这里的地址是0x108000.
下面kernel从0x108000开始执行了。


kernel启动流程:
1. Image开始代码为什么是stext。这个是通过vmlinux.lds链接而来的。
vmlinux.lds:
SECTIONS
{
        .init : {
                _stext = .;
                _sinitext = .;
                        HEAD_TEXT
                        INIT_TEXT
                ...
                INIT_CALLS
        }
        ...
}
#define HEAD_TEXT *(.head.text)
#define INIT_CALLS \
        __initcall_start = .;
        *(.initcallearly.init) \
        __early_initcall_end = .;
        *(.initcall0.init)\
        *(.initcall0s.init)\
        ...
        *(.initcall7.init)\
        *(.initcall7s.init)\
        __initcall_end = .;
_stext,_sinittext, __initcall_start,__initcall_end不是变量,它们不占用代码段或者数据段的空间。它们是一个符号表示一个地址,链接的时候,连接器把kernel用到这些标记的代码进行重定向。从vmlinux的readelf -s中可以看到。

2.arm/kernel/head.S
.section ".head.text", "ax"
ENTRY(stext)
        设置arm svc mode,关中断
        读取cpuid。调用__lookup_processor_type查找struct proc_info结构体(比如arm v7)
        调用__create_page_tables创建临时页表
        调用proc_info的__cpu_flush方法。一般就是初始化tlb,cache,mmu state为enable mmu做准备
        调用__enable_mmu使能mmu
        调用__map_switched,建立c运行环境,跳转到start_kernel函数。
ENDPROC(stext)
由于一开始bootloader不一定会加载image到对应的链接地址,并且mmu没有使能。所以kernel开始的代码一定要是位置无关的。
/*
* Kernel startup entry point.
* ---------------------------
* This is normally called from the decompressor code.  The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
*/
ENTRY(stext)
        setmode        PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
                                                @ and irqs disabled
        mrc        p15, 0, r9, c0, c0                @ get processor id
        bl        __lookup_processor_type                @ r5=procinfo r9=cpuid
        movs        r10, r5                                @ invalid processor (r5=0)?
THUMB( it        eq )                @ force fixup-able long branch encoding
        beq        __error_p                        @ yes, error 'p'


        //PLAT_PHYS_OFFSET是平台相关的kernel可以使用的物理地址。
        //根据我们的假设前1M内存是保留用途的话,这里PLAT_PHYS_OFFSET就必须设置为0x100000
        ldr        r8, =PLAT_PHYS_OFFSET

        /*
         * r1 = machine no, r2 = atags or dtb,
         * r8 = phys_offset, r9 = cpuid, r10 = procinfo
         */
        bl        __vet_atags
        bl        __create_page_tables

        /*
         * The following calls CPU specific code in a position independent
         * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
         * xxx_proc_info structure selected by __lookup_processor_type
         * above.  On return, the CPU will be ready for the MMU to be
         * turned on, and r0 will hold the CPU control register value.
         */
        ldr        r13, =__mmap_switched                @ address to jump to after
                                                @ mmu has been enabled
        adr        lr, BSYM(1f)                        @ return (PIC) address
        mov        r8, r4                                @ set TTBR1 to swapper_pg_dir
ARM(        add        pc, r10, #PROCINFO_INITFUNC        )  //先执行proinfo initfunc,然后跳到lr=__enable_mmu执行。
THUMB(        add        r12, r10, #PROCINFO_INITFUNC        )
THUMB(        mov        pc, r12                                )
1:        b        __enable_mmu  //然后打开mmu,代码最后会跳转到r13=__mmap_switched执行。
ENDPROC(stext)

r1=machine nr, r2=atags
bootloader需要设置r1=machine nr,定义在arch/arm/tools/mach-types文件中,每个板子都有相应的machine nr。
kernel启动中,会把r1保存到__machine_arch_type全局变量中。并且#define machine_arch_type __machine_arch_type
setup_arch()方法中,板级相关的machinde_desc *mdesc = setup_machine_tags(machine_arch_type);
实际上就是根据这个machine nr同kernel中所有编译进去的machine desc进行匹配
所以,传给kernel的machine nr一定要是正确的否则找不到相应的machine desc。

atags是一个地址,bootloader要在里面保存一组atag格式的信息,一般都有core,ramdisk,cmdline
struct tag_header {
        __u32 size; //这个tag的大小(4个字节单位)
        __u32 tag;  //tag类型,core?ramdisk?cmdline?
};
struct tag {
        struct tag_header hdr;
        union {
                struct tag_core                core;
                struct tag_mem32        mem;
                struct tag_videotext        videotext;
                struct tag_ramdisk        ramdisk;
                struct tag_initrd        initrd;
                struct tag_serialnr        serialnr;
                struct tag_revision        revision;
                struct tag_videolfb        videolfb;
                struct tag_cmdline        cmdline;
                struct tag_acorn        acorn;
                struct tag_memclk        memclk;
        } u;
};
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_CORE, parse_tag_core);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_MEM, parse_tag_mem32);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_SERIAL, parse_tag_serialnr);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_REVISION, parse_tag_revision);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
一个典型的atags是:
core tag(8字节,size=2) + ramdisk tag (16字节, size=4) + cmdline tag(n字节)
kernel在setup_arch()->setup_machine_tags()中,会对atags的每个atag分别进行处理,获取ramdisk,cmdline等信息。


获取cpu id相关的体系的proc_info_list结构。比如armV7。里面主要保存了体系相关的mmu,tlb,cache等方法。
/*
* Read processor ID register (CP#15, CR0), and look up in the linker-built
* supported processor list.  Note that we can't use the absolute addresses
* for the __proc_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.
*
*        r9 = cpuid
* Returns:
*        r3, r4, r6 corrupted
*        r5 = proc_info pointer in physical address space
*        r9 = cpuid (preserved)
*/
        __CPUINIT
__lookup_processor_type:
        //adr指令将__lookup_processor_type_data的物理地址存到r3中。(实际上一条操作pc的add,sub指令)
        adr        r3, __lookup_processor_type_data
        //r4存放__lookup_processor_type_data的虚拟地址。r5是__proc_info_begin变量虚拟地址。同理r6
        //然后根据__lookup_processor_type_data的虚实地址的偏移计算出__proc_info_begin,__pproc_info_end的物理地址。
        //这是内核启动前处理地址相关代码的常用方法
        //然后就会在所有的proc_info中根据当前cpuid查找匹配的proc_info。
        ldmia        r3, {r4 - r6}       
        sub        r3, r3, r4                        @ get offset between virt&phys
        add        r5, r5, r3                        @ convert virt addresses to
        add        r6, r6, r3                        @ physical address space
1:        ldmia        r5, {r3, r4}                        @ value, mask
        and        r4, r4, r9                        @ mask wanted bits
        teq        r3, r4
        beq        2f
        add        r5, r5, #PROC_INFO_SZ                @ sizeof(proc_info_list)
        cmp        r5, r6
        blo        1b
        mov        r5, #0                                @ unknown processor
2:        mov        pc, lr
ENDPROC(__lookup_processor_type)

//.long        __proc_info_begin表示,编译后这里存放__proc_info_begin符号指向的地址。
//__proc_info_begin不是一个变量并不占用数据段内存,链接脚本链接使用。
//链接完成后,这4个字节的内存就存放了 proc.init段开始的虚拟地址。
__lookup_processor_type_data:
        .long        .
        .long        __proc_info_begin
        .long        __proc_info_end
        .size        __lookup_processor_type_data, . - __lookup_processor_type_data
       
       
       
       
       
.macro        pgtbl, rd, phys
add        \rd, \phys, #TEXT_OFFSET - 0x4000
.endm

//这里建的页表是1M的页表。

__create_page_tables:
        //0x104000 到 0x108000的16KB存放page table。
        pgtbl        r4, r8                                @ page table address

        //将这16KB页表清0,必须地。
        mov        r0, r4
        mov        r3, #0
        add        r6, r0, #0x4000
1:        str        r3, [r0], #4
        str        r3, [r0], #4
        str        r3, [r0], #4
        str        r3, [r0], #4
        teq        r0, r6
        bne        1b

        ldr        r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

        //为__enable_mmu代码创建物理地址虚拟地址一致的页表映射。
        //假设代码在0x123450,则建立映射关系0x123450->0x123450.
        //否则就要飞了。
        /*
         * Create identity mapping to cater for __enable_mmu.
         * This identity mapping will be removed by paging_init().
         */
        adr        r0, __enable_mmu_loc
        ldmia        r0, {r3, r5, r6}
        sub        r0, r0, r3                        @ virt->phys offset
        add        r5, r5, r0                        @ phys __enable_mmu
        add        r6, r6, r0                        @ phys __enable_mmu_end
        mov        r5, r5, lsr #20
        mov        r6, r6, lsr #20

1:        orr        r3, r7, r5, lsl #20                @ flags + kernel base
        str        r3, [r4, r5, lsl #2]                @ identity mapping
        teq        r5, r6
        addne        r5, r5, #1                        @ next section
        bne        1b

        //建立映射0xc0000000开始总共kernel大小的映射。
        //映射到r4开始指定的物理地址。这里是1M。
        //所以zImage必须加载kernel到0x108000处,而且PLAT_PHYS_OFFSET必须设置为0x100000。
        /*
         * Now setup the pagetables for our kernel direct
         * mapped region.
         */
        mov        r3, pc
        mov        r3, r3, lsr #20
        orr        r3, r7, r3, lsl #20
        add        r0, r4,  #(KERNEL_START & 0xff000000) >> 18
        str        r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
        ldr        r6, =(KERNEL_END - 1)
        add        r0, r0, #4
        add        r6, r4, r6, lsr #18
1:        cmp        r0, r6
        add        r3, r3, #1 << 20
        strls        r3, [r0], #4
        bls        1b

        /*
         * Then map boot params address in r2 or
         * the first 1MB of ram if boot params address is not specified.
         */
        mov        r0, r2, lsr #20
        movs        r0, r0, lsl #20
        moveq        r0, r8
        sub        r3, r0, r8
        add        r3, r3, #PAGE_OFFSET
        add        r3, r4, r3, lsr #18
        orr        r6, r7, r0
        str        r6, [r3]

        mov        pc, lr
ENDPROC(__create_page_tables)


这里没有建立ramdisk的对应的页表。假设ramdisk的物理地址是16M 0x1000000, 200KB大小。
atag解析的时候会把这个信息存放发到phys_initrd_start中去。
static int __init parse_tag_initrd(const struct tag *tag)
{
        phys_initrd_start = __virt_to_phys(tag->u.initrd.start);
        phys_initrd_size = tag->u.initrd.size;
        return 0;
}

start_kernel()->setup_arch()->arm_memblock_init()
预留了这一段物理内存,那么这段物理内存就不会被kernel使用了。
计算物理内存对应的虚拟地址。虽然这个时候还没有建立页表映射。
但是这段物理内存必须落在直接映射区。PLAT_Phys_offset+896M内。
页表建立之后,这段虚拟地址就是ramdisk了。
        if (phys_initrd_size) {
                memblock_reserve(phys_initrd_start, phys_initrd_size);

                /* Now convert initrd to virtual addresses */
                initrd_start = __phys_to_virt(phys_initrd_start);
                initrd_end = initrd_start + phys_initrd_size;
        }

rootfs_initcall(populate_rootfs);
把ramdisk解压到rootfs中。
unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
之后ramdisk内容就在rootfs中了。之后一般就是启动rootfs的init进程了。



__data_loc -> _data ?
清除bbs。
保存processor_id, machine nr, atags到相应的全局变量给kernel使用。
设置sp=init_thread_union + THREAD_START_SP为0号进程的堆栈。
执行start_kernel(), ohyeah~~~:)大功告成
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
*  r0  = cp#15 control register
*  r1  = machine ID
*  r2  = atags/dtb pointer
*  r9  = processor ID
*/
        __INIT
__mmap_switched:
        adr        r3, __mmap_switched_data

        ldmia        r3!, {r4, r5, r6, r7}
        cmp        r4, r5                                @ Copy data segment if needed
1:        cmpne        r5, r6
        ldrne        fp, [r4], #4
        strne        fp, [r5], #4
        bne        1b

        mov        fp, #0                                @ Clear BSS (and zero fp)
1:        cmp        r6, r7
        strcc        fp, [r6],#4
        bcc        1b

ARM(        ldmia        r3, {r4, r5, r6, r7, sp})
THUMB(        ldmia        r3, {r4, r5, r6, r7}        )
THUMB(        ldr        sp, [r3, #16]                )
        str        r9, [r4]                        @ Save processor ID
        str        r1, [r5]                        @ Save machine type
        str        r2, [r6]                        @ Save atags pointer
        bic        r4, r0, #CR_A                        @ Clear 'A' bit
        stmia        r7, {r0, r4}                        @ Save control register values
        b        start_kernel
ENDPROC(__mmap_switched)

        .align        2
        .type        __mmap_switched_data, %object
__mmap_switched_data:
        .long        __data_loc                        @ r4
        .long        _sdata                                @ r5
        .long        __bss_start                        @ r6
        .long        _end                                @ r7
        .long        processor_id                        @ r4
        .long        __machine_arch_type                @ r5
        .long        __atags_pointer                        @ r6
        .long        cr_alignment                        @ r7
        .long        init_thread_union + THREAD_START_SP @ sp //堆栈地址。
        .size        __mmap_switched_data, . - __mmap_switched_data




































论坛徽章:
22
丑牛
日期:2014-08-15 14:32:0015-16赛季CBA联赛之同曦
日期:2017-12-14 15:28:14黑曼巴
日期:2017-08-10 08:14:342017金鸡报晓
日期:2017-02-08 10:39:42黑曼巴
日期:2016-11-15 15:48:38CU十四周年纪念徽章
日期:2016-11-09 13:19:1015-16赛季CBA联赛之同曦
日期:2016-04-08 18:00:03平安夜徽章
日期:2015-12-26 00:06:30程序设计版块每日发帖之星
日期:2015-12-03 06:20:002015七夕节徽章
日期:2015-08-21 11:06:17IT运维版块每日发帖之星
日期:2015-08-09 06:20:002015亚冠之吉达阿赫利
日期:2015-07-03 08:39:42
2 [报告]
发表于 2012-11-01 19:12 |只看该作者
赞一个!

论坛徽章:
1
拜羊年徽章
日期:2015-03-03 16:15:43
3 [报告]
发表于 2012-11-05 16:47 |只看该作者
总的来说写得不错,感兴趣的内容都有解释。
问一个问题:initrd解压到rootfs后,它原本占用的200k内存能否不保留?

论坛徽章:
0
4 [报告]
发表于 2012-11-05 22:51 |只看该作者
回复 3# linuxfellow


    这个我没有具体看,什么预留内存,解压过程都是大概猜测的。我觉得应该会在什么时候释放的。请高手补充下

论坛徽章:
0
5 [报告]
发表于 2012-11-07 08:48 |只看该作者
mark,学习了

论坛徽章:
0
6 [报告]
发表于 2012-11-08 01:58 |只看该作者
最近这几天开始看这个,谢谢你的分析。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP