Linux内核在龙芯GS32I的启动过程

 注:在网上搜到的,但是在函数的讲解上与2.6.23/24有些出入,不知道其他的版本的问题。
   怀疑kernel代码应该是针对其作了定制的。

    系统加电起动后,GS32I处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是 0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将 Linux 内核映像拷贝到RAM中,并引导CPU从0x80010000处取指令,而此处正是内核入口函数kernel_entry(),该函数定义在 /arch/mips/kernel/head.s文件里。
这就是说内核引导时首先执行这一段汇编代码,这是一段与体系结构相关的汇编代码,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,
最后跳转到/arch/mips/kernel/setup.c中的init_arch()初始化硬件平台相关的代码。
kernel_entry()函数的源代码分析如下:

=======================================================================
NESTED(kernel_entry, 16, sp)
 .set noreorder     // 告诉汇编器不需要关心这段代码在CPU流水线的执行
    /* The following two symbols are used for kernel profiling. */
 EXPORT(stext) EXPORT(_stext)
    /*
     * Stack for kernel and init, current variable 初始化内核堆栈,为创
      * 建第一个进程作准备
       */
 la $28, init_task_union
 addiu t0, $28, KERNEL_STACK_SIZE-32
 subu sp, t0, 4*SZREG
 sw t0, kernelsp

     /* The firmware/bootloader passes argc/argp/envp
      * to us as arguments. But clear bss first because
       * the romvec and other important info is stored there
     * by prom_init().
         * firmware/bootloader传递argc/argp/envp到内核作为参数,在这里首先  
       * 清除bss是因为prom_init将要把romvec和其他重要的信息写到此处
         */
 
 la t0, _edata
     //这段循环将内核映像的未初始化数据段清零,也就是bss sw zero, (t0)
     //段,在_edata和_end之间
 la t1, (_end - 4) 1:
 addiu t0, 4
 bne t0, t1, 1b
 sw zero, (t0)
 jal init_arch   
     //循环结束后,跳转到init_arch()
 nop
 END(kernel_entry)
==========================================================================

 init_arch()首先检测使用的CPU类型,Linux支持众多的MIPS架构处理器, 这些处理器的类型在文件/include/asm- mips/bootinfo.h中作了定义。
init_arch()首先调用函数cpu_probe(),该函数通过MIPS CPU的PRID寄存器来确定CPU类型,从而确定使用的指令集和其他一些CPU参数,如TLB等,cpu_probe()函数的部分源码如下:

==========================================================================
 static inline void cpu_probe(void) {
 //获取CP0_CONFIG寄存器的值,根据CP0控制寄存器PRID来确定CPU的类型
    unsigned long config0 = read_32bit_cp0_register(CP0_CONFIG);   
     mips_cpu.processor_id = read_32bit_cp0_register(CP0_PRID);
     ……
    switch (mips_cpu.processor_id & 0xff0000) {
    //根据PRID来选择CPU的类型
        case PRID_IMP_R4000:
        //处理器为MIPS R4000
             if ((mips_cpu.processor_id & 0xff) == PRID_REV_R4400)
                 mips_cpu.cputype = CPU_R4400SC;
             else
                mips_cpu.cputype = CPU_R4000SC;
        case PRID_IMP_R5000:
        //处理器为MIPS R5000
            mips_cpu.cputype = CPU_R5000;
            mips_cpu.isa_level = MIPS_CPU_ISA_IV;
            mips_cpu.options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_32FPR;
            mips_cpu.tlbsize = 48;
            break;
             ……
        case PRID_IMP_GODSON:
         //处理器为GODSON
            mips_cpu.cputype = CPU_GODSON;
            mips_cpu.isa_level = MIPS_CPU_ISA_III;
            mips_cpu.options = R4K_OPTS | MIPS_CPU_FPU;
            mips_cpu.tlbsize = 48;
            break;
        default:
            mips_cpu.cputype = CPU_UNKNOWN;
             break;
     }
}
   从中可以看出,cpu_probe()通过龙芯CPU的CP0控制寄存器PRID来确定并更新mips_cpu.cputype的值,此值后面用来决定调 用相应的异常处理和内存管理程序。从cpu_probe()返回后,调用/arch/mips/au1000/pb1500/init.c中的 prom_init()函数,
prom_init()是和硬件相关的,做一些低层的初始化,接受引导装载程序传给内核的参数,确定 mips_machgroup,mips_machtype两个变量,这两个变量分别对应着相应的芯片组合开发板;
从prom_init()返回到 init_arch()之后执行/arch/mips/mm/loadmmu.c中的loadmmu(),
这个函数初始化cache和TLB,部分源代码如下:

void __init loadmmu(void) //初始化cache和TLB
{    
    //根据mips_cpu.options 和mips_cpu.cputype 变量进行处理器的选择,决定调用相应的异常处理和内存管理程序
    if (mips_cpu.options & MIPS_CPU_4KTLB){
    #if defined(CONFIG_CPU_R4X00) || defined(CONFIG_CPU_VR41XX) || /
         defined(CONFIG_CPU_R4300) || defined(CONFIG_CPU_R5000) || /
         defined(CONFIG_CPU_NEVADA) || defined(CONFIG_CPU_RC32300)
         ld_mmu_r4xx0();
          r4k_tlb_init();
    #endif
    #if defined(CONFIG_CPU_MIPS32) || defined(CONFIG_CPU_MIPS64)
        ld_mmu_mips32();
        r4k_tlb_init();
    #endif
         ……
    #if defined(CONFIG_CPU_GODSON)
         //针对MIPS架构的龙芯GODSON处理器
        ld_mmu_godson();
        //初始化MMU
        r4k_tlb_init();
        //初始化TLB
    #endif
    }
      else
        switch(mips_cpu.cputype) {
        #ifdef CONFIG_CPU_R3000
             case CPU_R2000:
              case CPU_R3000:
              case CPU_R3000A:
              case CPU_R3081E:
                 ld_mmu_r23000();
                 r3k_tlb_init();
                 break;
              ……
             default:
                panic("Yeee, unsupported mmu/cache architecture.");
         }
 }
=========================================================================================

     从上面的代码可见,在根据mips_cpu.options 和mips_cpu.cputype 变量进行选择后,调用针对MIPS架构的龙芯GODSON处理器的ld_mmu_godson () 来初始化CPU的TLB和Cache(包括指令Cache和数据Cache)。在从loadmmu()函数返回后,关闭除了CP0以外的所有的CPU协处 理器以后,转到start_kernel()中执行,并从此再不返回了。 MMU初始化后,系统就直接跳转到/init/main.c中的start_kernel()中执行,start_kernel()是初始化的主例程,代 码分析如下:

//该函数完成一系列硬件和软件的初始化
asmlinkage void __init start_kernel(void)
 {
    char * command_line;
    unsigned long mempages;
    extern char saved_command_line[];
    ……
    lock_kernel();
     //取得内核锁
    printk(linux_banner);
     //输出linux标语字符串
    setup_arch(&command_line);
     // 完成于特定体系结构的设置
    printk("Kernel command line: %s/n", saved_command_line);
    parse_options(command_line);
     //分析内核的命令行参数,这些参数可以请求内
     //核初始化程序做一些特殊的初始化工作
    trap_init();
     //设置陷阱
    init_IRQ();
     //设置外部中断
    sched_init();
     //与进程相关的初始化
    softirq_init();
     //初始化软中断
    time_init();
     //时钟初始化
    ………………………
    mem_init();
     //初始化物理内存管理器
    kmem_cache_sizes_init();
    //内核内存管理器的初始湖,也就是初始化cache和slab
    pgtable_cache_init();
     ………………
    mempages = num_physpages;
    fork_init(mempages);
    proc_caches_init();
    //为proc文件系统创建高速缓冲
    vfs_caches_init(mempages);
    //为VFS创建slab高速缓冲
    buffer_init(mempages);
    //初始化buffer
    page_cache_init(mempages);
    //页缓冲初始化
    …………………
    check_bugs();
    //检查与处理器相关的bug
    printk("POSIX conformance testing by UNIFIX/n");
    smp_init();
    rest_init();
    //调用kernel_thread,创建系统的第一号进程
}

分 析上面的源代码可以看出,它在关中断定情况下获得内核锁,然后输出linux标语字符串,接着调用setup_arch()作为执行的第一步,这个函数在 /arch/mips/kernel/setup.c文件中,在其中完成于特定体系结构的设置,包括初始化硬件寄存器、标识根设备和系统中可用的 DRAM和闪存的数量、指定系统中可用页面的数目、文件系统的大小等等,所有这些信息都以start_kernel()中的command_line指针 的参数形式从引导装载程序传递到内核。在基于龙芯处理器的内核中,setup_arch()在执行时,首先根据处理器类型选择调用 /arch/mips/au1000/pb1500/setup.c文件中的au1x00_setup()函数,这个函数对系统寄存器和其它一些特定设备 进行设置,如串口,USB设备、PCI总线控制其等。从au1x00_setup()返回后,setup_arch()调用paging_init()进 行页面初始化后返回start_kernel()函数中。接着start_kernel()函数会激活系统的各种基本功能并进行初始化,这些初始化部分包 括:
  设置陷阱
  初始化中断,包括软中断和硬中断
  初始化***
  初始化控制台
  执行mem_init(),它计算各种区域、高内存区域等在内的页面数
  初始化slab分配器并为VFS、缓冲区高速缓存等创建slab高速缓存其中涉及体系结构相关的函数有:
     trap_init(),在arch/mips/kernel/trap.c中定义
    init_IRQ(),在arch/mips/kernel/irq.c中定义
    time_init()arch/mips/kernel/time.c中定义
 最后start_kernel()函数通过调用rest_init()创建内核线程init(),完成这个工作的主要是rest_init()调用的 kernel_thread()函数。
 在创建了内核线程后,打开内核锁,接着就转入了cpu_idle()执行。该函数定义在 arch/mips/kernel/process.c中,源代码如下:
ATTRIB_NORET void cpu_idle(void) {
 /* endless idle loop with no priority at all */
    current->nice = 20;
    current->counter = -100;
    init_idle();
    while (1) {
          while (!current->need_resched)
            if (cpu_wait)
                (*cpu_wait)();
        schedule();
        check_pgt_cache();
    }
}

分析源代码可以看出,进入cpu_idle()函数后,就再也不会返回了,条件满足的情况下,通过调用schedule()函数进行一次调度,一般情况下,先调度刚刚建立的init()进程。
内核线程init()的执行是对系统的上层的初始化 ,定义在init/main.c中,其源代码如下:
static int init(void * unused)
{
    lock_kernel();
    do_basic_setup();
    prepare_namespace();   /*
                * Ok, we have completed the initial bootup, and
                * we're essentially up and running. Get rid of the
                * initmem segments and start the user-mode stuff..
                    */
    free_initmem();
    unlock_kernel();
    if (open("/dev/console", O_RDWR, 0) < 0)
        printk("Warning: unable to open an initial console./n");
    (void) dup(0);
        (void) dup(0); /*
            * We try each of these until one succeeds.
            *
            * The Bourne shell can be used instead of init if we are
            * trying to recover a really broken machine.
            */
     if (execute_command)
        execve(execute_command,argv_init,envp_init);
     execve("/sbin/init",argv_init,envp_init);
     execve("/etc/init",argv_init,envp_init);
     execve("/bin/init",argv_init,envp_init);
     execve("/bin/sh",argv_init,envp_init);
     panic("No init found. Try passing init= option to kernel.");
}
 分 析源代码可以看出,在开始执行init()后,先加内核锁,接着调用do_basic_setup()(在init/main.c中定义),该函数完成 PCI的初始化、网络功能的初始化、外设和驱动程序的初始化以及文件系统的初始化等。do_basic_setup()完成这些初始化后,init()释 放内核锁,通过oper()系统调用打开/dev/console作为标准输入,并调用dup()连接控制台到标准输入和标准输出。最后调用execve ()转入用户空间执行sbin/init。至此linux内核在gs33i上启动了。

 

 

你可能感兴趣的:(Linux内核在龙芯GS32I的启动过程)