关闭

Linux内核启动流程分析

336人阅读 评论(0) 收藏 举报
分类:

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

load_up_mmu
//start_kernel()开始的初始化操作与处理器的类型基本无关了。

start_kernel

1. start_kernel()函数分析

下面对start_kernel()函数及其相关函数进行分析。 

1.1 lock_kernel() 

kernel_flag 是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。

  只有获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对 lock_kernel/unlock_kernel函数里至多可以有一个程序占用CPU。

printk(linux_banner) 

打印版本等信息

 trap_init() 

这个函数用来做体系相关的异常处理的初始化,在该函数中调用__trap_init((void *)vectors_base()) 

 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() 

使能中断,这里开始,中断系统开始正常工作。

vfs_caches_init 
------->mnt_init
    --------->sysfs_init
                    init_rootfs//注册rootfs文件系统。
                    init_mount_tree//创建rootfs文件系统

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)

{
         /* drivers will send hotplug events */
         init_workqueues();
         usermodehelper_init();
         driver_init();---------------->platform_bus_init(platform总线初始化)
         init_irq_proc();
         do_initcalls();
}
Do_initcalls()用来启动所有在__initcall_start和__initcall_end段的函数,而静态编译进内核的modules也会将其入口放置在这段区间里。
跟根文件系统相关的初始化函数都会由rootfs_initcall()所引用。

rootfs_initcall(populate_rootfs); (init/initramfs)

unpack_to_rootfs:顾名思义就是解压包,并将其释放至rootfs。它实际上有两个功能,一个是释放包,一个是查看包,看其是否属于cpio结构的包。功能选择是根据最后的一个参数来区分的.
在这个函数里,对应我们之前分析的三种虚拟根文件系统的情况。一种是跟kernel融为一体的initramfs.在编译kernel的时候,通过链接脚本将其存放在__initramfs_start至__initramfs_end的区域。这种情况下,直接调用unpack_to_rootfs将其释放到根目录.如果不是属于这种形式的,也就是__initramfs_start和__initramfs_end的值相等,长度为零。不会做任何处理。退出.
 
对应后两种情况。从代码中看到,必须要配制CONFIG_BLK_DEV_RAM才会支持image-initrd。否则全当成cpio-initrd的形式处理。
对于是cpio-initrd的情况。直接将其释放到根目录。对于是image-initrd的情况。将其释放到/initrd.image.最后将initrd内存区域归入伙伴系统。这段内存就可以由操作系统来做其它的用途了。



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。


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:154151次
    • 积分:3049
    • 等级:
    • 排名:第11466名
    • 原创:118篇
    • 转载:144篇
    • 译文:0篇
    • 评论:8条
    博客专栏