zoukankan      html  css  js  c++  java
  • kernel启动分析

    kernel启动分析

    kernel启动分析

    一、链接脚本

      内核的链接脚本是用汇编文件vmlinux.lds.S实现的。所以必须先进行编译,然后才会生成真正的链接脚本vmlinux.lds
      根据链接脚本可以找到程序入口为ENTRY(stext)
    vmlinux.lds
      经过查找,发现入口在arch/arm/kernel目录下的head.S

    二、head.S

    1.汇编阶段

    内核运行的虚拟地址与物理地址
    //	内核运行的虚拟地址				0xC0008000
    #define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
    //	内核运行的物理地址				0x30008000
    #define KERNEL_RAM_PADDR	(PHYS_OFFSET + TEXT_OFFSET)
    重要注释

      该注释出现在真正的启动代码前。

    /*
     * Kernel startup entry point.
     * ---------------------------
     *
     * This is normally called from the decompressor code.  The requirements
     * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
     * r1 = machine nr, r2 = atags pointer.
     *
     * This code is mostly position independent, so if you link the kernel at
     * 0xc0008000, you call this at __pa(0xc0008000).
     *
     * See linux/arch/arm/tools/mach-types for the complete list of machine
     * numbers for r1.
     *
     * We're trying to keep crap to a minimum; DO NOT add any machine specific
     * crap here - that's what the boot loader (or in extreme, well justified
     * circumstances, zImage) is for.
     */

      这里的代码通常被解压代码调用。zImage是,在vmlinux.o的基础上对文件进行再压缩,然后加上相应的自解压头文件后生成的。所以这里说是被解压代码调用。
    解压内核的打印信息
      这段代码被调用的要求是——关MMU,关D-cache,r0=0r1 = machine nrr2 = atags pointerr1中存储的就是uboot传参时的机器码,该机器码被存放在linux/arch/arm/tools/mach-types,可以随时查验。
      这段代码几乎全是PIC码,如果想要链接kernel到某地址,可以使用__pa(specific address)
      这段代码不应该和任何硬件初始化相关,这些步骤应该在boot loader中完成。

    设置CPU工作模式
    	__HEAD
    ENTRY(stext)
    	setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
    						@ and irqs disabled

      提一句,__HEAD表示#define __HEAD .section ".head.text","ax",是链接时需要链接的第一段代码。

    汇编阶段
    __lookup_processor_type

      从CP15协处理器中的C0读取CPU的ID号,调用该函数进行合法性检验。如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。
      内核会维护一个本内核版本支持的CPU的ID号码数组,然后该函数就会把读取到的ID与数组中的ID作比较。

    /*
     * Read processor ID register (CP#15, CR0), and look up in the linker-built
     * supported processor list.  Note that we can't use the absolute addresses
     * for the __proc_info lists since we aren't running with the MMU on
     * (and therefore, we are not in the correct address space).  We have to
     * calculate the offset.
     * 读取处理器ID,然后在链接器内置的处理器列表中寻找该ID
     *	r9 = cpuid
     * Returns:
     *	r3, r4, r6 corrupted
     *	r5 = proc_info pointer in physical address space
     *	r9 = cpuid (preserved)
     */
    __lookup_machine_type
    /*
     * Lookup machine architecture in the linker-build list of architectures.
     * Note that we can't use the absolute addresses for the __arch_info
     * lists since we aren't running with the MMU on (and therefore, we are
     * not in the correct address space).  We have to calculate the offset.
     * 在内置的架构列表中,寻找机器架构码
     *  r1 = machine architecture number
     * Returns:
     *  r3, r4, r6 corrupted
     *  r5 = mach_info pointer in physical address space
     */
    __vet_atags
    /* 
     * Determine validity of the r2 atags pointer.  The heuristic requires
     * that the pointer be aligned, in the first 16k of physical RAM and
     * that the ATAG_CORE marker is first and present.  Future revisions
     * of this function may be more lenient with the physical address and
     * may also be able to move the ATAGS block if necessary.
     * 确认atags中tag的有效性
     * r8  = machinfo
     *
     * Returns:
     *  r2 either valid atags pointer, or zero
     *  r5, r6 corrupted
     */
    __create_page_tables

      创建页表是为了启动MMU。该页表为段格式,1M为单位。
      内核启动前期使用粗页表,后期就会再次建立细页表,以4KB为单位。

    __switch_data -- __mmap_switched

      这是一个函数指针数组,直接进入__mmap_switched函数。

    /*
     * The following fragment of code is executed with the MMU on in MMU mode,
     * and uses absolute addresses; this is not position independent.
     * 下面的代码时在MMU模式下操作MMU,是非PIC码
     *  r0  = cp#15 control register
     *  r1  = machine ID
     *  r2  = atags pointer
     *  r9  = processor ID
     */

      该函数还复制了数据段,清除了BSS段

    	str	r9, [r4]			@ Save processor ID
    	str	r1, [r5]			@ Save machine type
    	str	r2, [r6]			@ Save atags pointer
    	bic	r4, r0, #CR_A			@ Clear 'A' bit
    	stmia	r7, {r0, r4}			@ Save control register values	

      最终进入start_kernel。也就是进入c语言阶段。

    C语言阶段

      C语言阶段的函数,目前我还看不懂,所以根据打印的结果来分析。

      printk(KERN_NOTICE "%s", linux_banner);用于打印内核信息。以后几乎所有的消息均用此函数打印。
      内核信息的8个级别如下:

    #define	KERN_EMERG	"<0>"	/* system is unusable			*/
    #define	KERN_ALERT	"<1>"	/* action must be taken immediately	*/
    #define	KERN_CRIT	"<2>"	/* critical conditions			*/
    #define	KERN_ERR	"<3>"	/* error conditions			*/
    #define	KERN_WARNING	"<4>"	/* warning conditions			*/
    #define	KERN_NOTICE	"<5>"	/* normal but significant condition	*/
    #define	KERN_INFO	"<6>"	/* informational			*/
    #define	KERN_DEBUG	"<7>"	/* debug-level messages			*/

    打印信息
    打印信息

    setup_arch

      首先在setup_processor中打印了CPU相关信息
    setup_processor
      然后再在setup_mackine中校验机器码,并输出相关machine的信息。
    enter description here
      接下来校验atag地址是否有效
    atag
      输出解析后的参数地址(就是uboot中的bootargs,被封装进tag中)
    uboot传参的地址
      parse_tags解析参数,但是发现一个参数未识别。
    未识别参数
      输出解析后的参数
    参数
      参数解析后console=ttySAC2,115200root=/dev/mmcblk0p2 rwinit=/linuxrcrootfstype=ext3

    • machine_desc结构体
    static const struct machine_desc __mach_desc_SMDKV210	
     __used							
     __attribute__((__section__(".arch.info.init"))) = {	
    	.nr		= MACH_TYPE_SMDKV210,		
    	.name		= "SMDKV210",
    	.phys_io	= S3C_PA_UART & 0xfff00000,
    	.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,
    	.boot_params	= S5P_PA_SDRAM + 0x100,
    	.init_irq	= s5pv210_init_irq,
    	.map_io		= smdkv210_map_io,
    	.init_machine	= smdkv210_machine_init,
    	.timer		= &s5p_systimer,
    };

      这个结构体用来存户硬件相关信息,至少包含机器码,名字,传入参数(bootargs)等关键信息。
      set_processor实际上调用了在汇编阶段查找处理架构的__lookup_processor_type。同理可知setup_machine在底层肯定也调用了__lookup_machine_type,这个machine就是机器码。
      内核在建立的时候就把各种CPU架构的信息组织成一个一个的machine_desc结构体实例,然后都给一个段属性.arch.info.init,链接的时候会保证这些描述符会被连接在一起。__lookup_machine_type就去那个那些描述符所在处依次挨个遍历各个描述符,比对看机器码哪个相同。
    链接脚本
      该结构体中由重要函数smdkv210_machine_init,该函数负责绑定开发板内核启动过程中会初始化的各种硬件信息。

    rest_init

      进入该函数,意味着内核进入启动第三阶段。

    最终阶段启动

      内核启动终止于cpu_idle

    static noinline void __init_refok rest_init(void)
    	__releases(kernel_lock)
    {
    	int pid;
    
    	rcu_scheduler_starting();
    	/*
    	 * We need to spawn init first so that it obtains pid 1, however
    	 * the init task will end up wanting to create kthreads, which, if
    	 * we schedule it before we create kthreadd, will OOPS.
    	 */
    	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    	numa_default_policy();
    	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    	rcu_read_lock();
    	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    	rcu_read_unlock();
    	complete(&kthreadd_done);
    	unlock_kernel();
    
    	/*
    	 * The boot idle thread must execute schedule()
    	 * at least once to get things moving:
    	 */
    	init_idle_bootup_task(current);
    	preempt_enable_no_resched();
    	schedule();
    	preempt_disable();
    
    	/* Call into cpu_idle with preempt disabled */
    	cpu_idle();
    }

      在第三阶段,OS一共维持了三个内核进程。

    进程号 作用
    idle 空闲进程
    kernel_init init进程,所有用户进程的父进程
    kthreadd 守护进程,保证内核本身的正常工作
    init进程

      init进程完成了OS由内核态到用户态的转变。
      内核态下,挂载根文件系统并试图找到用户态下的init程序。init进程要从内核态过渡到用户态,就必须执行一个应用程序,要执行应用程序,就必须挂载根文件系统,因为应用程序都存储在文件系统中。
      init进程在内核态下面时,通过一个函数kernel_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了。注意这个跳跃过程中进程号是没有改变的,所以一直是进程1.这个跳跃过程是单向的,也就是说一旦执行了init程序转到了用户态下整个操作系统就算真正的运转起来了,以后只能在用户态下工作了,用户态下想要进入内核态只有走API这一条路了。
      init还构建了login进程,命令行进程与shell进程。

    • console
        打开console设备的文件描述符,然后将该文件描述符复制两份。init总共得到0,1,2三个文件描述符,分别对应stdinstdoutstderr。init进程的子进程也将继承这三个文件描述符。
    	/* Open the /dev/console on the rootfs, this should never fail */
    	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
    		printk(KERN_WARNING "Warning: unable to open an initial console.
    ");
    
    	(void) sys_dup(0);
    	(void) sys_dup(0);
    • rootfs
        挂载根文件系统。bootargs中有两个参数描述了fs位置以及类型。
    	/*
    	 * check if there is an early userspace init.  If yes, let it do all
    	 * the work
    	 */
    
    	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
        上面一旦挂载rootfs成功,则进入rootfs中寻找应用程序的init程序,这个程序就是用户空间的进程1,找到后用run_init_process去执行。
    	/*
    	 * 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) {
    		run_init_process(execute_command);
    		printk(KERN_WARNING "Failed to execute %s.  Attempting "
    					"defaults...
    ", execute_command);
    	}
    	
    	/*
    	 *	按顺序一次寻找init程序
    	 */
    	run_init_process("/sbin/init");
    	run_init_process("/etc/init");
    	run_init_process("/bin/init");
    	run_init_process("/bin/sh");
    
    	panic("No init found.  Try passing init= option to kernel. "
    	      "See Linux Documentation/init.txt for guidance.");

    三、cmdline

    物理存储

    console=ttySAC2,115200 
    root=/dev/mmcblk0p2 rw 
    init=/linuxrc 
    rootfstype=ext3

    第一种这种方式对应rootfs在SD/iNand/Nand/Nor等物理存储器上。

    网络存储

    root=/dev/nfs 
    nfsroot=192.168.1.20:/root/s3c2440/build_rootfs/aston_rootfs 
    ip=192.168.1.99:192.168.1.20:192.168.1.0:255.255.255.0::eth0:off  
    init=/linuxrc 
    console=ttySAC2,115200 

    第二种这种方式对应rootfs在nfs上。

    四、mach

      mach其实就是machine architecture。arch/arm下的一个mach-xx就代表一类以xx为cpu做主芯片的machine。该目录下的mach-yy.c则定义了该machine的一种开发板。

      plat是platform的缩写。可以理解为SoC。该目录下全是SoC内部外设的初始化。内核种吧SoC中的外部外设操作代码称作平台设备驱动。

  • 相关阅读:
    [js对象]JS入门之Date对象
    从Microsoft SqlServer 2005中返回有一定顺序的记录集
    [js对象]JS入门之Global对象
    [JS.IntelliSense]VS2008(Orcas) So Cool
    即插即用插件式框架的程序集处理遐想(TypeFinder)
    [C#3.0体验]Orcas中内置的LinQ,XLinQ[DLinQ]扩展方法
    [ASP.NET入门]页面生命周期
    [IE]IE6&IE7运行于同一个系统中
    [js对象]JS入门之Boolean&Object对象
    RSS(Really Simple Syndication)常用标签
  • 原文地址:https://www.cnblogs.com/0nism/p/12380614.html
Copyright © 2011-2022 走看看