注:在网上搜到的,但是在函数的讲解上与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上启动了。