kernel(二):启动内核

        本文主要探讨210内核启动过程。

主Makefile

    定义kernel版本号(2.6.35.7)

    VERSION = 2
    PATCHLEVEL = 6
    SUBLEVEL = 35
    EXTRAVERSION = .7


       指定编译文件生成目录

  make O=/tmp 


     定义交叉编译工具链

CROSS_COMPILE    ?= /root/arm-2009q3/bin/arm-none-linux-gnueabi-

     指定架构

 ARCH        ?= arm

链接脚本

    kernel链接脚本需要条件编译,lds格式不支持条件编译
    kernel链接接脚由汇编文件vmlinux.lds.S编译生成vmlinux.lds(arch/arm/kernel/)
    vmlinux.lds中ENTRY(stext)为链接地址入口
    head.S和head-nommu.S都包含ENTRY(stext),head.S使用用MMU,head-nommu.S未使用mmu


head.S

#define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)

         内核运行的虚拟地址(KERNEL_RAM_VADDR)为0xC0008000,内核运行的物理地址(KERNEL_RAM_PADDR)为0x30008000    

(arch/arm/kernel/.head.o.cmd)
    cmd_arch/arm/kernel/head.o := /root/arm-2009q3/bin/arm-none-linux-gnueabi-gcc -Wp,-MD,arch/arm/kernel/.head.o.d  -nostdinc -isystem /root/arm-2009q3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.1/include -I/root/kernel/arch/arm/include -Iinclude  -include include/generated/autoconf.h -D__KERNEL__ -mlittle-endian -Iarch/arm/mach-s5pv210/include -Iarch/arm/plat-s5p/include -Iarch/arm/plat-samsung/include -D__ASSEMBLY__ -mabi=aapcs-linux -mno-thumb-interwork  -D__LINUX_ARM_ARCH__=7 -march=armv7-a  -include asm/unified.h -msoft-float -gdwarf-2     -DTEXT_OFFSET=0x00008000  -c -o arch/arm/kernel/head.o arch/arm/kernel/head.S

(arch/arm/include/asm/memory.h)#define PAGE_OFFSET        UL(CONFIG_PAGE_OFFSET)
(.config)CONFIG_PAGE_OFFSET=0xC0000000


(arch/arm/mach-s5pv210/include/mach/memory.h)
#if defined(CONFIG_MACH_SMDKV210)
#define PHYS_OFFSET        UL(0x30000000)
#else
#define PHYS_OFFSET        UL(0x30000000)
#endif

(include/generated/autoconf.h)
#define CONFIG_MACH_SMDKV210 1

            uboot启动内核后,内核运行zImage前解压代码解压zImage,再运行内核入口代码
            uboot启动内核(theKernel(0,machid,bd->bi_boot_params))运行时把0写入r0,machid写入r1,bd->bi_boot_params写入r2
            kernel启动时MMU是关闭的,硬件需要的物理地址,zImage是整体不能分散加载,需使用位置无关码(物理地址)

        __HEAD
    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)?
    beq    __error_p            @ yes, error 'p'
    bl    __lookup_machine_type        @ r5=machinfo
    movs    r8, r5                @ invalid machine (r5=0)?
    beq    __error_a            @ yes, error 'a'
    bl    __vet_atags
    bl    __create_page_tables

                     __HEAD定义后面代码属于.head.text段

    __HEAD
    ENTRY(stext)
    (include/linux/init.h)
    #define __HEAD        .section    ".head.text","ax"

            cp15协处理器c0读取出硬件CPU ID号,用于合法性检验,合法则继续启动,不合法则停止启动,转向__error_p启动失败
            内核包含CPU ID号码数组,该函数从硬件中读取的cpu id号码和数组中的对比,相同则不合法

__lookup_processor_type
    (System.map)
    c0008168 t __lookup_processor_type
    c00081a4 T lookup_processor_type


    (arch/arm/kernel/setup.c)
    list = lookup_processor_type(read_cpuid_id());
    if (!list) {
        printk("CPU configuration botched (ID %08x), unable "
               "to continue.\n", read_cpuid_id());
        while (1);
    }

    (arch/arm/kernel/include/asm/cputype.h)
    #define CPUID_ID    0

    static inline unsigned int __attribute_const__ read_cpuid_id(void)
    {
        return read_cpuid(CPUID_ID);
    }

           __lookup_machine_type函数用于校验机器码,类似cpu id校验
           __vet_atags函数校验uboot给内核传参(ATAGS格式) 

            __create_page_tables

                    函数用于建立页表,内核链接在虚拟地址处,kernel需要启动MMU
                    kernel建立页表:kernel先建立段式页表(同uboot建立页相同页,大小为1MB,1MB映射4GB需4096页表项,每个页表项4字节,共需16KB做页表),建立细页表(细页表项大小为4kb)


            __switch_data (arch/arm/kernel/head-common.S)

__switch_data:
     .long    __mmap_switched
    .long    __data_loc            @ r4
    .long    _data                @ 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
__mmap_switched:
    adr    r3, __switch_data + 4

    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)

             建立段式页表后执行__switch_data,__switch_data中执行__mmap_switched函数
            __mmap_switched函数复制数据段、清除bss段(构建C环境),保存cpu id、机器码、tag首地址
           start_kernel跳转到C阶段

内核启动的C阶段(start_kernel,init/main.c)


        smp_setup_processor_id()设置对称多处理器(多核CPU)
        lockdep_init内核调试模块处理内核自旋锁死锁
        cgroup_init_early处理进程组

        printk(KERN_NOTICE "%s", linux_banner);

const char linux_banner[] =
    "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
    LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
(include/generated/utsrelease.h)
#define UTS_RELEASE "2.6.35.7"

(include/generated/compile.h)
#define UTS_VERSION "#1 PREEMPT Tue Dec 12 11:46:41 CST 2023"
#define LINUX_COMPILE_BY "root"
#define LINUX_COMPILE_HOST "kax-virtual-machine"
#define LINUX_COMPILER "gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67) "

include/generated/下的文件为编译过程中生成的头文件


                printk定义打印级别(0-7)来过滤显示机制

#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT    "<2>"    /* critical conditions            */
#define    KERN_ERR    "<3>"    /* error conditions            */
#define    KERN_WARNING    "<4>"    /* warning conditions            */
#define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define    KERN_INFO    "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */


          setup_arch

                用来确定内核机器的arch和machine

void __init setup_arch(char **cmdline_p)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    unwind_init();

    setup_processor();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;

    if (mdesc->soft_reboot)
        reboot_setup("s");

    if (__atags_pointer)
    {
        tags = phys_to_virt(__atags_pointer);
        printk("@@@@@@@ atags_pointer not null\n");
        
    }
    else if (mdesc->boot_params)
    {
        tags = phys_to_virt(mdesc->boot_params);
        printk("@@@@@@@ boot params not null\n");
    }
    printk("@@@@@@@linter#####boot_params:%p,mdesc->boot_params:%p\n",tags);
    /*
     * If we have the old style parameters, convert them to
     * a tag list.
     */
    if (tags->hdr.tag != ATAG_CORE)
        convert_to_tag_list(tags);
    if (tags->hdr.tag != ATAG_CORE)
        tags = (struct tag *)&init_tags;

    if (mdesc->fixup)
        mdesc->fixup(mdesc, tags, &from, &meminfo);

    if (tags->hdr.tag == ATAG_CORE) {
        if (meminfo.nr_banks != 0)
            squash_mem_tags(tags);
        save_atags(tags);
        parse_tags(tags);
    }

    init_mm.start_code = (unsigned long) _text;
    init_mm.end_code   = (unsigned long) _etext;
    init_mm.end_data   = (unsigned long) _edata;
    init_mm.brk       = (unsigned long) _end;

    /* parse_early_param needs a boot_command_line */
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    printk("$$$$$$$$$cmdline:%s\n",cmd_line);
    parse_early_param();

    paging_init(mdesc);
    request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
    smp_init_cpus();
#endif

    cpu_init();
    tcm_init();

    /*
     * Set up various architecture-specific pointers
     */
    init_arch_irq = mdesc->init_irq;
    system_timer = mdesc->timer;
    init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif
    early_trap_init();
}
setup_processor();

                  setup_processor用来查找CPU信息并打印信息

static void __init setup_processor(void)
{
    struct proc_info_list *list;

    /*
     * locate processor in the list of supported processor
     * types.  The linker builds this table for us from the
     * entries in arch/arm/mm/proc-*.S
     */
    list = lookup_processor_type(read_cpuid_id());
    if (!list) {
        printk("CPU configuration botched (ID %08x), unable "
               "to continue.\n", read_cpuid_id());
        while (1);
    }

    cpu_name = list->cpu_name;

#ifdef MULTI_CPU
    processor = *list->proc;
#endif
#ifdef MULTI_TLB
    cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
    cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
    cpu_cache = *list->cache;
#endif

    printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
           cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
           proc_arch[cpu_architecture()], cr_alignment);

    sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);
    sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);
    elf_hwcap = list->elf_hwcap;
#ifndef CONFIG_ARM_THUMB
    elf_hwcap &= ~HWCAP_THUMB;
#endif

    cacheid_init();
    cpu_proc_init();
}

               setup_machine的参数machine_arch_type是机器码编号(2456)

mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
(include/generated/mach-types.h)
#define MACH_TYPE_SMDKV210             2456

#ifdef CONFIG_MACH_SMDKV210
# ifdef machine_arch_type
#  undef machine_arch_type
#  define machine_arch_type    __machine_arch_type
# else
#  define machine_arch_type    MACH_TYPE_SMDKV210
# endif
# define machine_is_smdkv210()    (machine_arch_type == MACH_TYPE_SMDKV210)
#else
# define machine_is_smdkv210()    (0)
#endif
(arch/arm/kernel/setup.c)
struct machine_desc *mdesc;

(arch/arm/include/asm/mach/arch.h)
struct machine_desc {
    /*
     * Note! The first four elements are used
     * by assembler code in head.S, head-common.S
     */
    unsigned int        nr;        /* architecture number    */
    unsigned int        phys_io;    /* start of physical io    */
    unsigned int        io_pg_offst;    /* byte offset for io 
                         * page tabe entry    */

    const char        *name;        /* architecture name    */
    unsigned long        boot_params;    /* tagged list        */

    unsigned int        video_start;    /* start of video RAM    */
    unsigned int        video_end;    /* end of video RAM    */

    unsigned int        reserve_lp0 :1;    /* never has lp0    */
    unsigned int        reserve_lp1 :1;    /* never has lp1    */
    unsigned int        reserve_lp2 :1;    /* never has lp2    */
    unsigned int        soft_reboot :1;    /* soft reboot        */
    void            (*fixup)(struct machine_desc *,
                     struct tag *, char **,
                     struct meminfo *);
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_irq)(void);
    struct sys_timer    *timer;        /* system tick timer    */
    void            (*init_machine)(void);
};

 

ENTRY(lookup_machine_type)
    stmfd    sp!, {r4 - r6, lr}
    mov    r1, r0
    bl    __lookup_machine_type
    mov    r0, r5
    ldmfd    sp!, {r4 - r6, pc}
ENDPROC(lookup_machine_type)

                 setup_machine调用lookup_machine_type调用__lookup_machine_type
                 内核把各种CPU架构信息组成machine_desc实例并赋予段属性(.arch.info.init)保证链接在一起,__lookup_machine_type遍历各描述符

                setup_arch函数处理cmdline(初步bootargs参数)

char *from = default_command_line;

/* parse_early_param needs a boot_command_line */
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    printk("$$$$$$$$$cmdline:%s\n",cmd_line);
    parse_early_param();

                default_command_line(CONFIG_CMDLINE)是默认命令行参数
                uboot通过tag给kernel传递cmdline为空则使用kernel默认命令行参数

static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

(include/generated/autoconf.h)
#define CONFIG_CMDLINE "console=ttySAC2,115200"


                setup_command_line(cmdline解析,kernel/init/init)

cmdline:console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3


                        解析为
                                console=ttySAC2,115200  
                                root=/dev/mmcblk0p2 rw 
                                init=/linuxrc             
                                rootfstype=ext3    

                        cmdline参数

                                root=/dev/xxx指定根文件系统位置
                                rootfstype=xxx指定根文件系统的文件系统类型
                                console=指定控制台信息(串口,波特率)
                                mem=xxx指定内核系统内存大小
                                init=指定init进程        

        start_kernel其他函数
                trap_init                    设置异常向量表
                mm_init                        内存管理模块初始化
                scheduler_init                    内核调度系统初始化
                early_irq_init&init_IRQ        中断初始化
                console_init                控制台初始化


        rest_init(init文件)
                start_kernel调用许多xx_init函数为初始化内核模块函数,rest_init用于串联已初始化的模块

static noinline void __init_refok rest_init(void)
    __releases(kernel_lock)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);
    unlock_kernel();

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}

                调用kernel_thread启动2个内核线程:kernel_init和kthreadd
                调用schedule函数开启内核调度系统,linux系统运行
                调用cpu_idle函数结束内核启动进入死循环
                内核调度系统里的进程需要运行,调度系统终止cpu_idle死循环进程(空闲进程),执行该进程
                进程0:cpu_idle()函数,idle进程(死循环)
                进程1:kernel_init函数为init进程
                进程2:kthreadd函数是进程2,是linux内核守护进程,保证linux内核正常工作的
                init进程挂载根文件系统并运行根文件系统下的init程序转变为用户态应用程序,脱离内核态,转为用户态

                init进程

                        init进程构建交互界面,创建其他进程(login、命令行、shell...),shell进程启动其他用户进程,shell工作后就可运行其他命令和程序

                       打开控制台

                             打开/dev/console文件(210)并复制2次文件描述符,三个文件描述符分别是0(标准输入)、1(标准输出)、2(标准错误)
                             进程1打开三个文件描述符,进程1的子进程都有3个三描述符

                       挂载根文件系统
                              prepare_namespace函数中挂载根文件系统(init/do_mounts.c)
                              uboot传参中root=/dev/mmcblk0p2 rw定义根文件系统位置和权限
                              uboot传参中rootfstype=ext3定义rootfs类型。


                       执行用户态下(init进程)
                               uboot传参init=/linuxrc指定rootfs中的init程序
                               其他备用init进程:/sbin/init,/etc/init,sbin/init,/bin/sh

        start_kernel函数打印了内核初始化信息、内核模块初始化(譬如内存管理、调度系统、异常处理···)
        setup_arch:机器码架构查找,架构相关硬件初始化,uboot传参cmdline给内核
        rest_init:构建进程0,1,2,挂载根文件系统,进程1转为用户态进程

内核框架图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值