分类: LINUX
2010-03-08 15:19:48
系统的启动流程
这里我们来讨论Uoot的启动流程,并将延伸至内核启动到start_kernel函数,我们主要讲start_kernel函数前系统都在做什么,系统是怎么样从开始运行,到解压内核,跳到内核运行,又怎么样从Uboot里将参数传到内核的。
Uboot的启动是从start.S文件开始的,系统一上电pc指针便指向这里开始程序的启动运行,在start.S文件中系统的工作主要有:
系统一上电便是reset异常,便跳到reset处进行处理,初始化Sdram,cache等,然后跳到board_init_f函数中运行。
Board_init_if(lib_mips/board.c):
在此函数中对定时器进行初始化,环境变量初始化,初始化串口,然后根据Sdram进行计算(具体的计算过程看代码),计算出一个具体的地址,即SDRAM的高端地址,然后调用relocate_code函数,将Uboot程序Copy 到内存,copy完后即跳到in_ram(内存运行)中接着运行Uboot,最后调用board_init_r函数。
Relocate_code是一段汇编代码,位于start.S中,怎么实现copy就自已仔细看代码了啊,in_ram也在start.S中。
接下来我们来主要看board_init_r函数:
开始部分仍是对系统一些功能的初始化:
/* configure available FLASH banks */
size = flash_init(); 初始化flash
gpio_init(); //初始gpio
/** leave this here (after malloc(), environment and PCI are working) **/
/* Initialize devices */
devices_init ();//初始化外设
/* initialize the console (after the relocation and devices init) */
console_init_r ();初始化console
cy_nvram_init();
mac_init();
进行一些系统初配置初始化后,Uboot就便等待三秒,等待用户输入字符,以进行不同的操作,即当按下4时会进入命令处理模式,按下CTRL+ESC时会进入tftpd,可以进行文件的上传。我们这里只探讨正常启动不探讨tftpd下载和命令处理模式。
当我们启动时什么也不操作时,默认的启动类型为3,就会进入到这里:
if(BootType == '3') {
char *argv[3];
printf(" \n3: System Boot system code via Flash.\n");
do_bootm(cmdtp, 0, 2, argv); //从这里开始对内核进行解压,并跳到内核处运行。
/* below only when boot from flash fali*/
argv[2] = &file_name_space[0];
memset(file_name_space,0,ARGV_LEN);
eth_initialize(gd->bd);
do_tftpd(cmdtp, 0, 4, argv); //启动tftpd。
}
如果kernel确实被写入了flash中,则do_bootm是不会返回的,则之后的do_tftpd就不会执行。当我们重新烧写Uboot后,必须重新烧写内核与文件系统,因此当重新烧写Uboot后,此时do_bootm就会出错,返回,则就开启了tftpd,进行下载模式。
接下来我们看看do_bootm函数,看它是怎么样解压内核,怎么样跳到内核执行的。
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
………………………….
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16); //我们传过来的参数为2,所以就从地址就是argv[1],这个地址呢就是CFG_FLASH_BASE +0X40000,即0xbc440000,这里便是kernel的起始地址
}
memmove (&header, (char *)addr, sizeof(image_header_t)); //kernel启去始地址处我们在做code.bin时,在前面通过mkimage加了一个image_header的头,之后才加上code pattern的。这里我们烧写的时侯去掉了code pattern,所以flash里开始处便是image_header头,这里就是将这个头读出来。
if (ntohl(hdr->ih_magic) != IH_MAGIC) { //这里判断这个头正确不正确,通过一个IH_MAGIC来判断,每次重新烧写Uboot后,flash内核开始处,image_header就会被破坏,这个条件就不成立,于时就会在这里直接返回,之后就像上面所讲的进入了tftpd.
{
printf ("Bad Magic Number,%08X \n",ntohl(hdr->ih_magic));
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
data = (ulong)&header;
len = sizeof(image_header_t);
checksum = ntohl(hdr->ih_hcrc); //计算校验和是否正确
hdr->ih_hcrc = 0;
if (crc32 (0, (char *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr); 将image_header头的内容打印出来
Image Type: MIPS Linux Kernel Image (lzma compressed)
Data Size: 3174400 Bytes = 3 MB
Load Address:
Entry Point: 881ee040
这个是我们当前的image_header头。
data = addr + sizeof(image_header_t);//去掉image_header头,这里便是kernel开始的部分了。
len = ntohl(hdr->ih_size);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
read_dataflash(data, len, (char *)CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
//kaiker SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
………………………………………….
switch (hdr->ih_type) { 查看这部分的类型,目前为KERNEL,
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = simple_strtoul(argv[2], NULL, 16);
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image"; //执行这里。
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[0]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
for (i=1; len_ptr[i]; ++i)
data += 4;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-5);
return 1;
}
SHOW_BOOT_PROGRESS (6);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
switch (hdr->ih_comp) { //判断压缩类型,刚才我们从image_header看出是lzma压缩的。
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
#if 1
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data + sizeof(struct trx_header), &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
#endif
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
#ifdef CONFIG_LZMA //所以就跳到这里来了,
case IH_COMP_LZMA:
printf (" Uncompressing %s ... ", name);
#ifdef CONFIG_UNCOMPRESS_TIME
tBUncompress = get_ticks();
#endif
unsigned int destLen = 0;
接来来开始解压,解压目的地址hdr->ih_load,我们从打印的image_header看出,是0x
i = lzmaBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&destLen, (char *)data + sizeof(struct trx_header), len);
if (i != LZMA_RESULT_OK) {
printf ("LZMA ERROR %d - must RESET board to recover\n", i);
return 1;
//SHOW_BOOT_PROGRESS (-6);
//udelay(100000);
//do_reset (cmdtp, flag, argc, argv);
}
#endif /* CONFIG_LZMA */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
………………………………….
switch (hdr->ih_os) { //这里我们是linux内核,所以要调用do_bootm_linux函数。
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
…………………………………………………………………………………..
}
return 1;
}
接下来我们看看do_bootm_linux函数,现在以经解压完了,所以这里我们只关注它是怎么跳转到内核开始运行的。
do_bootm_linux (cmd_tbl_t *cmdtp, int flag,
int argc, char *argv[],
ulong addr,
ulong *len_ptr,
int verify)
{
…………………………………………………..
kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong))hdr->ih_ep;//image_header,ih_ep,对于当前内核,我们可以从刚才打印imager_header参数时看出,为0x881ee040,这个地址是什么呢,它其时就是head.S,kernel_entry的地址,
……………………………………………….
(*kernel) (kbd, initrd_start, initrd_end, cmd_start, cmd_end); //这里就是跳到kernel的地方,即跳到kernel_entry处开始内核的运行了。之后就便是开始从head.S中的kernel_entry开始执行了。从而完成从Uboot到内核的交接。
}
接下来我们进入内核看看:
* Kernel entry point
*/
NESTED(kernel_entry, 16, sp) //kernel进入点
.set push
/*
* For the moment disable interrupts and mark the kernel mode.
* A full initialization of the CPU's status register is done
* later in per_cpu_trap_init().
*/
mfc0 t0, CP0_STATUS
or t0, ST0_CU0|0x
xor t0, 0x
mtc0 t0, CP0_STATUS
.set noreorder
sll zero,3 # ehb
.set reorder
/*
* 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().
*/
la t0, _edata
sw zero, (t0)
la t1, (_end - 4)
1:
addiu t0, 4
sw zero, (t0)
bne t0, t1, 1b
/*
* 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
jal init_arch //跳到init_arch函数,从这里跳到C代码执行。
.set pop
END(kernel_entry)
Init_arch函数:
asmlinkage void __init
init_arch(int argc, char **argv, char **envp, int *prom_vec)
{
/* Determine which MIPS variant we are running on. */
cpu_probe(); //CPU探测,读出CPU ID
prom_init(argc, argv, envp, prom_vec);
cpu_report();//打印CPU信息
/*
* Determine the mmu/cache attached to this machine,
* then flush the tlb and caches. On the r4xx0
* variants this also sets CP0_WIRED to zero.
*/
load_mmu();//
start_kernel();
}
int __init prom_init()
{
/* change parameter by bobtseng, 2006.1.3. */
mips_machgroup = MACH_GROUP_RT2880;
set_io_port_base(KSEG1);
prom_init_cmdline(); //初始化启动参数,将启动参数分解并存入argc数组里
prom_init_sysclk(); //初始化CPU频率
prom_init_serial_port(); /* Needed for Serial Console */ //初始化串口参数
// prom_init_mac(); // remove for test, bobtseng 2006.1.3.
prom_setup_printf(prom_get_ttysnum()); //可能有两个串口,选择哪一个
#if defined(CONFIG_RT2880_FPGA)
prom_printf("\nTHIS IS FPGA\n");
#elif defined(CONFIG_RT2880_ASIC)
prom_printf("\nTHIS IS ASIC\n");
#else
#error Please Choice Chip Type
#endif
prom_meminit(); //初始化SDRAM,取出base地址和size大小,为mmu做准备
return 0;
}
以上便是从Uboot的启动到内核start_kernel的运行过程,start_kernel之后便是跟内核原理紧密相关的启动过程,在这里就不介绍了,有兴趣的同事可以慢慢研究。这里仍有两个问题。
一个是我们正常的桌面linux(如Redhat),在系统启动的时侯,参数都是通过lilo或grub启动程序传给内核的,即同理,启动参数也可以从Uboot传到内核,问题就是参数是怎么样传递的呢?
一个是在init_arch函数里,调用了prom_init函数,调用prom_init时带了四个能数,但是prom_init原形确不带参数,这个又是为什么呢?接下来我们就讲讲这两个问题。
a、 Uboot跳到内核是通过kernel_entry的地址跳过来的,kernel_entry是汇编语言,Uboot在调用时确是C语言的实现过程,所以一般这里就不容易看出参数的传递规则了。
由于C在编译时还是要通过汇编这一关,因此我们也就只关注汇编语言中函数参数是怎么传递的,也就明白了这个问题。
通常每种架构(intel, mips,arm)的CPU,都有各自的汇编指令,都有各自独特的寄存器,同时也都有很多的共性,原理上也都大同小异。我们通常编程都是用的高级语言,高级语言中都是有函数调用的,函数往往是带参数的,高级语言在编译成汇编时,参数是怎么样表示的。因此,针对汇编语言,只有指令,与操作数,操作数就只有寄存器与立即数。因此针对每种架构的CPU,专门有预留的寄存器用来传递函数参数的,在MIPS架构中,a0-a3就是这样的寄存器。
Uboot在解压完内核后,通过一个地址调用函数进入内核中运行,并且携带启动参数,由于进入内核是汇编语言,所以这此参数放在a0-a3的寄存器中(这个是由MIPS架构决定的,a0-a3专门存放子程序的参数).
b、 这个问题跟函数名前的__init 有关系,int __init prom_init(),
#define __init __attribute__ ((__section__ (".text.init")))
这个宏的定义将这段代码放在.text.init这里,通过__attribute__展开,在这里attribute只是gcc的一个属性,针对这个属性gcc编译时对此函数不做参数方面的检查(检查不是很严格)。所以编译就没问题。gcc由于attribute,所以编译时不检查函数参数,我们在调用子函数时可以带参数,也可以不带参数,也可以少带参数等,这个根据需要而定。我想应该是这样的。
链接时将此函数放入.text.init段里,之后运行时直接根据prom_init进行跳转运行。