关于linux内核启动,重点关注/init/Main.c文件.
Bootloader完成系统初始化工作后,将运行控制权交给Linux内核。根据内核是否压缩以及内核是否在本地执行,Linux通常有以下两种可选的启动方式:
Flash本地运行方式:内核的未经压缩的可执行映像固化在Flash,系统启动时内核在Flash中开始逐句执行。kernel映象为非压缩格式,通过make Image获得,那么真正的入口就是arch/arm/kernel/head_armv.S
压缩内核加载方式:内核的压缩映像固化在Flash上,系统启动时由附加在压缩映像前的解压复制程序读取压缩映像,在内存中解压后执行,这种方式相对复杂,但是运行速度更快(RAM的存取速率要比Flash高)。如果是zImage,那么程序的入口是arch/arm/boot/compressed/head.S
本节我们介绍压缩内核的启动方式,内核的Flash本地运行方式不再详细中介绍。
本地运行时内核的启动包括特定体系结构设置和Linux系统初始化两步,内核启动的入口文件是arch/arm/kernel/head-armv.s。
特定体系结构设置
本过程由汇编文件head-armv.s完成。
Head-armv.s文件位于./arch/arm/kernel/目录下,是Boot Loader将控制权交给内核后执行的第一个程序。下面是head-armv.s的基本运行过程:
1) 配置系统寄存器;
2) 初始化ROM、RAM以及总线控制寄存器等;
3) 设置堆栈指针,将bss段清零;
4) 修改pc指针,跳转到./init/main.c中的start_kernel函数,开始Linux系统的初始化。
Linux系统初始化
程序跳转到start_kernel函数执行,在这里完成处理器结构的初始化、中断的初始化、进程相关的初始化以及内存初始化等重要工作。
下面在start_kernel函数中列举了主要的初始化过程:
asmlinkage void __init start_kernel(void)
{
char * command_line;
unsigned long mempages;
extern char saved_command_line[];
/*
* Interrupts are still disabled. Do necessary setups, then enable them
*/
lock_kernel();
printk(Linux_banner); //--注1
setup_arch(&command_line); //--注2
printk("Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
trap_init(); //--注3
init_IRQ(); //--注4
sched_init(); //--注5
softirq_init(); //--注6
time_init(); //--注7
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init(); //--注8
#ifdef CONFIG_MODULES
init_modules(); //--注9
#endif
if (prof_shift) {
unsigned int size;
/* only text is profiled */
prof_len = (unsigned long) &_etext - (unsigned long) &_stext;
prof_len >>= prof_shift;
size = prof_len * sizeof(unsigned int) + PAGE_SIZE-1;
prof_buffer = (unsigned int *) alloc_bootmem(size);
}
kmem_cache_init(); //--注10
sti(); //--注11
calibrate_delay(); //--注12
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
initrd_start < min_low_pfn << PAGE_SHIFT) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
"disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
initrd_start = 0;
}
#endif
mem_init(); //--注13
kmem_cache_sizes_init(); //--注14
pgtable_cache_init(); //--注15
#ifdef CONFIG_PERFMON
perfmon_init();
#endif
mempages = num_physpages;
fork_init(mempages); //--注16
proc_caches_init(); //--注17
vfs_caches_init(mempages); //--注18
buffer_init(mempages); //--注19
page_cache_init(mempages); //--注20
#if defined(CONFIG_ARCH_S390)
ccwcache_init();
#endif
signals_init(); //--注21
#ifdef CONFIG_PROC_FS
proc_root_init(); //--注22
#endif
#if defined(CONFIG_SYSVIPC)
ipc_init(); //--注23
#endif
check_bugs(); //--注24
printk("POSIX conformance testing by UNIFIX\n");
/*
* We count on the initial thread going ok
* Like idlers init is an unlocked kernel thread, which will
* make syscalls (and thus be locked).
*/
smp_init(); //--注25
rest_init(); //--注26
}
注1:
Pintk函数位于./kernel/printk.c函数中,它是内核中的打印输出函数,在这里输出Linux版本,编译器版本以及编译者的信息。
注2:
setup_arch函数位于./arch/arm/kernel/setup.c中,它完成特定于体系结构的设置,包括初始化硬件寄存器、标识根设备和系统中可用的DRAM和Flash的大小、指定系统中可用页面的数目、文件系统大小等等。所有这些信息都以参数形式从引导装载程序传递到内核。setup_arch 还需要对Flash存储库、系统寄存器和其它特定设备执行内存映射。一旦完成了特定于体系结构的设置,控制就返回到初始化系统其余部分的 start_kernel 函数。
注3:
trap_init函数在./arch/arm/kernel/traps.c文件中。它对中断向量表进行初始化,初始化处理器的一些中断处理。它通过调用一些宏对中断向量表填写中断响应程序的偏移地址。中断要等到calibrate_delay()函数运行之前才允许被调用。
注4:
init_IRQ函数的源程序在./arch/arm/kernel/irq.c文件中。该函数初始化中断控制器,但是它们的中断输出要到系统设备和内核模块调用request_irq()函数后才能使用。
注5:
sched_init函数的源程序在./arch/arm/kernel/sched.c中。该函数初始化内核pidhash[]表(这是一个快速映射进程ID到进程描述符的查询表)。
注6:
softirq_init函数的源程序在./arch/arm/kernel/softirq.c中。初始化软中断,
注7:
time_init函数的源程序在linux-2.4.x/arch/arm/kernel/time.c中。该函数初始化目标板系统硬件时钟。
注8:
console_init函数的源程序在./drivers/char/tty_io.c中。初始化控制台
注9:
init_modules函数的源程序在./arch/arm/kernel/module.c中。初始化模块,
注10:
kmem_cache_init函数的源程序在./mm/slab.c中。该函数初始化内核SLAB内存管理子系统。SLAB是内核内部结构的动态内存管理。
注11:
Sti宏定义在./include/asm-arm/system.h中。设中断。
注12:
calibrate_delay函数的源程序在./init/main.c中。该函数执行内核的BogoMips(校准Linux内部循环延时,使得延时的时间和不同速率的处理器都保持在一个大概相同的比率上)运算。
注13:
mem_init函数的源程序在./arch/arm/mm/init.c中。该函数初始化内核的内存管理系统,并打印出内核所有可用的内存和内核已经占用的内存。
注14:
kmem_cache_sizes_init函数的源程序在./mm/slab.c中。该函数完成kmem_cache_init()函数开始的SLAB的初始化。
注15:
pgtable_cache_init函数的源程序在./arch/arm/mm/mm-armv.c中,该函数完成pgtable cache的初始化。
注16:
fork_init(mempages)函数的源程序在./kernel/fork.c中。该函数初始化max_threads和init_task变量。这些变量会在调用fork()函数时用到。
注17:
proc_caches_init函数的源程序在./kernel/fork.c中。该函数初始化内核使用的SLAB缓存,类似malloc()的初始化。
注18:
vfs_caches_init(mempages)函数的源程序在./fs/dcache.c中。该函数初始化虚拟文件系统使用的SLAB缓存。
注19:
buffer_init(mempages) 函数的源程序在./fs/buffer.c中。该函数初始化内核缓冲区。
注20:
page_cache_init(mempages) 函数的源程序在./mm/filemap.c中。该函数初始化内核页面缓存系统。
注21:
signals_init函数的源程序在./kernel/signal.c中。该程序初始化内核信号队列。
注22:
proc_root_init函数的创建proc文件系统,源程序在./fs/proc/root.c中。该函数初始化proc文件系统,创建几个类似/proc/bus和/porc/driver的标准目录。
注23:
ipc_init函数的源程序在./ipc/util.c中。该函数初始化System V的进程通信资源,包括信号灯(sem_init()子函数)、消息队列(msg_init子函数)和共享内存(shm_init()子系统)。
注24:
check_bugs函数的源程序在./include/asm-arm/bugs.c中。该函数能够检查一些处理器的错误。
注25:
smp_init函数的源程序在./init/main.c中。如果处理器类型是SMP-capable x86,该函数调用IO_APIC_init_uniprocessor()函数配置APIC设备。如果是其他的处理器,该函数不做任何处理。
注26:
rest_init函数的源程序在./init/main.c中。该函数释放初始化函数占用的内存,调用init()函数创建kernel_thread结束内核启动进程,初始化内核PCI和网络,加载操作系统。
源代码阅读网站
http://lxr.oss.org.cn/