0nism

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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.\n");

	(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...\n", 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中的外部外设操作代码称作平台设备驱动。

posted on 2020-02-28 22:11  0nism  阅读(414)  评论(0编辑  收藏  举报