kernel启动分析

摘要:
搜索后,发现条目是head。S位于arch/arm/kernel目录下。程序集stage_lookup_processor_Type从CP15协处理器中的C0读取CPU的ID号,并调用此函数以验证其有效性。如果合法,继续开始。如果这是非法的,请停止启动并返回__error_PStart failed/**读取处理器ID寄存器,并查找链接器内置*支持的处理器列表。请注意,我们不能使用MMUon*运行的proc_信息列表的绝对地址*。我们必须*计算偏移量。*读取处理器ID,然后在linker_infopointerinphysicaladdressspace中内置的处理器列表中查找ID*r9=cpuid*返回:*r3,r4,r6已损坏*r5=proc*r9=cpuid*/__查找机器类型/**查找机器架构。*请注意,我们不能使用与MMUon一起运行的e__ arch_ Info*列表的绝对地址。我们必须计算偏移量。*在内置体系结构列表中,找到机器体系结构代码*r1=机器体系结构编号*返回:*r3,r4,r6已更正*r5=mach_infopointerinphysicaladdressspace*/__ vet_atags/**2标记的确定性。该启发式要求*在物理RAM的前16kof中指定指针,*标记COREMArk是第一个表示。此函数的未来版本*可能会对物理地址更加宽松,如果需要,*也可以使用ATAGSBlock。*确认atags中标记的有效性*r8=machinefo**返回:*r2是validatedagspoint,还是zero*r5,r6已损坏*/__ create_ page_ Tables创建页表以启动MMU。
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语言阶段的函数,目前我还看不懂,所以根据打印的结果来分析。

banner

  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_initinit进程,所有用户进程的父进程
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中的外部外设操作代码称作平台设备驱动。

免责声明:文章转载自《kernel启动分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Notepad++中如何设置自动换行以及行宽MVC中用Jpaginate分页下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

内核移植和文件系统制作(2):linux内核最小系统和initramfs文件系统

linux内核最小系统,使用内核版本:https://www.kernel.org/pub/linux/kernel/v3.0/linux-3.8.1.tar.bz2 1,FL2440板子的基本硬件:晶振12MHZ CPU 型号为S3C2440,基于ARM920T,指令集ARMV4,时钟主频400MHz SDRAM H57V2562GTR-75...

[虚拟化/云][全栈demo] 为qemu增加一个PCI的watchdog外设(六)

目的: 1. 为我们自己的watchdog写一个驱动 步骤: 通过之前的介绍,我们很容易猜想到写我们基于PCI的watchdog驱动,可以分2个步骤。 1. 探测加载PCI设备 这部分代码跟我们的设备本身没有任何关系。 我们通过这部分代码,找到 厂商ID为 0x1af4, 设备ID为0x0101的设备。这个设备是我们用qemu中定义我们的watchdog中...

开源微内核seL4

微内核 越大的系统潜在的bug就越多。所以微内核在降低bug方面非常有优势,seL4是世界上最小的内核之中的一个。可是seL4的性能能够与当今性能最好的微内核相比。 作为微内核,seL4为应用程序提供少量的服务。如创建和管理虚拟内存地址空间的抽象,线程和进程间通信IPC。这么少的服务靠8700行C代码搞定。seL4是高性能的L4微内核家族的新产物,它具...

【转】使用Apache Kylin搭建企业级开源大数据分析平台

http://www.thebigdata.cn/JieJueFangAn/30143.html  本篇文章整理自史少锋4月23日在『1024大数据技术峰会』上的分享实录:使用Apache Kylin搭建企业级开源大数据分析平台。   正文如下   我先做一个简单介绍我叫史少锋,我曾经在IBM、eBay做过大数据、云架构的开发,现在是Kyligence的技...

协程,异步IO

协程 协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。实现单线程的并发。 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上...

转摘cognos学习笔记

  第一部分 准备知识  概述 (p1)    业务智能(Business Intelligence) 近年来业务智能的话题开始在国内热起来。 业务智能是在计算机应用水平达到一定程度、数据积累到一定量之后提上议事日程的一个应用领域。 业务智能是为更好的决策而对数据进行收集、转换、分析和分发的过程。 业务智能是把数据转化成知识的过程。包括信息的获取、分析和...