2015/06/04 09:42
vmlinux.lds(/arch/powerpc/kernel)定义了内核的入口为_stext
系统引导从head_32.s(arch/powerpc/kernel)开始执行
_stext和_start是一个地址,是因为OpenFirmware定义的过程描述符
_start之后
early_init:执行必要的底层配置和清空BSS,然后关闭MMU,清空BAT和TLB为后面初始化MMU做准备
保证系统环境的干净。
CPU将重新初始化BAT寄存器。注意,这里使用BAT的目的是为了在完全启用MMU之前先使用简单的BAT映射来设置Linux的运行环境,从而使后面复杂的操作可以使用C语言完成。
relocate_kernel:
1.获取内核大小,先拷16K再通过copy_and_flush函数将剩余内核拷贝到内存物理起始处。
2.打开MMU(turn_on_mmu)
然后跳转到start_here处(可以认为是真正内核开始运行。。。)
本过程详细见《linux内核启动过程学习总结》(D:\myDoc\学习资料\linux内核分析)
1.线程和堆栈的初始化和板件平台相关的初始化
//加载0号线程上下文
init_task
machine_init
//用于初始化早期调试输出,可以通过配置config文件使能其中的一个
udbg_early_init
//用于启动时对扁平设备树(FDT)的初始化,用来获取内核前期初始化所需的启动参数和cmd_line等引导信 息,主要的功能就是检查设备树的chosen节点确定设备的基本信息,然后为设备初始化一个MEMBLOCK,并预留相应空间。
early_init_devtree(arch/powerpc/kernel/prom.c)
MMU_init
//重新装载MMU相关的寄存器,开启MMU并跳到start_kernel
1. start_kernel()函数分析
下面对start_kernel()函数及其相关函数进行分析。
1.1 lock_kernel()
kernel_flag 是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。
只有获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对 lock_kernel/unlock_kernel函数里至多可以有一个程序占用CPU。
打印版本等信息
trap_init()
init_IRQ()
这个函数用来做体系相关的irq处理的初始化.
irq_desc数组是用来描述IRQ的请求队列,NR_IRQS代表中断数目(512)
sched_init()
初始化系统调度进程 ,主要对定时器机制和时钟中断的Bottom Half的初始化函数进行设置。
softirq_init()
调用tasklet_init初始化tasklet_struct结构 HI_SOFTIRQ用于实现bottom half,TASKLET_SOFTIRQ用于公共的tasklet。
time_init()
这个函数用来做 体系相关的timer的初始化 , 调用了setup_timer()函数,设计与硬件设计紧密相关,主要是根据硬件设计情况设置时钟中断号和时钟频率等。
console_init()
控制台初始化。控制台也是一种驱动程序,由于其特殊性,提前到该处完成初始化,主要是为了提前看到输出信息,据此判断内核运行情况。
sti()
使能中断,这里开始,中断系统开始正常工作。
mnt_init会创建一个rootfs,这个是虚拟的rootfs,是内存文件系统(ramfs),后面还会指向具体的根文件系统。
rest_init();
此函数调用kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);函数。
kernel_thread函数中通过 __syscall(clone) 创建新线程。
kernel_init()函数完成下列功能。
do_basic_setup()是一个很关键的函数,所有直接编译在kernel中的模块都是由它启动的。代码片段如下:
static void __init do_basic_setup(void)
rootfs_initcall(populate_rootfs); (init/initramfs)
1) 在RamDisk为initramfs时
start_kernel()->vfs_caches_init()->mnt_init()->init_rootfs()->init_mount_tree()注册了类型为rootfs的fs
然后:start_kernel 最后 rest_init->kernel_init
kernel_init->do_basic_setup->do_initcalls 调用 rootfs_initcall 注册过的函数
rootfs_initcall(populate_rootfs);
populate_rootfs解压initramfs到rootfs, initramfs必须包含init文件,否则还将挂在其他的文件系统
然后kernel_init检查init是否存在,如果有就不会注册其他的根文件系统,
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)
会导致 prepare_namespace 不被调用。
否则还会尝试挂在其他的根文件系统。
2) 在RamDisk (initrd) 非initramfs时
kernel_init->prepare_namespace->initrd_load->rd_load_image(加载)->(identify_ramdisk_image)
如果ROOT_DEV != Root_RAM0
则handle_initrd通过linuxrc启动用户态
该种情况可由initrd=0xXXXXX 但 root=/dev/mtdblock2矛盾导致。
如果ROOT_DEV == Root_RAM0
rd_load_image加载后,也是通过mount_root加载Root_RAM0根文件系统
3) 没有RamDisk 例如使用mtd
kernel_init->prepare_namespace->mount_root(加载)
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
检查是否有过早期用户空间的初始化,判断当前系统有没有init文件,没有则进入prepare_namespace
很多驱动中都有类似module_init(usb_init)的代码,通过该宏定义逐层解释存放到.initcall.int节中。
init执行过程
在内核引导结束并启动init之后,系统就转入用户态的运行,在这之后创建的一切进程,都是在用户态进行。
内核中的init()函数的源代码在/init/main.c中,是内核的一部分。而/sbin/init程序的源代码是应用程序。
init程序启动之后,要完成以下任务:检查文件系统,启动各种后台服务进程,最后为每个终端和虚拟控制台启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的,因此它又是其它一切用户进程的父进程。
init进程启动后,按照/etc/inittab的内容进程系统设置。很多嵌入式系统用的是BusyBox的init,它与一般所使用的init不一样,会先执行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。