您的位置:首页 > 运维架构 > Linux

android启动之linux内核启动

2014-04-25 09:02 92 查看
本文分成两部分:智能手机的硬件系统架构和linux内核的启动两部分,前者作为后者的基础。

智能手机的硬件系统架构

移动终端,基本上可以分成两种:一种是传统手机(feature phone);另一种是智能手机(smart phone)。智能手机具有传统手机的基本功能,并有以下特点:开放的操作系统、硬件和软件可扩充性和支持第三方的二次开发。Feature
Phone是在不断扩充应用功能的无线通信终端(行业术语叫移动台),而智能手机是增加了无线通信功能的手持式电脑。智能手机的软件体系基本上照搬了PC的软件体系,将内核、驱动(可以编入内核,也可以独立)和应用分开。智能手机的硬件系统架构如下图:



硬件系统主要分为应用处理器模块AP(Application
Processor),电源管理模块(PMU),存储器模块SDRAM(Synchronous Dynamic Random Access Memory)和NAND Flash,LCD 显示模块 Camera 模块,Bluetooth和FM 模块,WiFi 模块,GPS 模块。AP 模块搭配存储单元(NAND+DDR)以及LCD(Liquid Crystal Display)、cmera、Bluetooth、WiFi、GPS(Global Position System)等外设模块,实现丰富的多媒体和短距离无线业务。PMU
一方面为整个系统的各个模块单元提供供电,另一方面提供Audio Codec、USB PHY、HKADC、Clock 等功能。

下面主要说一下存储器模块,存储器单元主要提供程序存储和运行空间,以及资料数据的存储空间,这些功能由SDRAM
( Synchronous Dynamic Random Access Memory)和NAND Flash实现。 系统内核保存在Nand Flash之上,断电后仍然存在,而运行后程序是装入SDRAM或Mobile DDR之类的内存设备运行。一般是就用ROM来指Nand Flash,RAM来指SDRAM之类设备。

linux内核的启动

1.bootloader

CPU上电或复位将先执行BootLoader程序。BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适状态,为运行操作系统做好准备。它比较像电脑上的BIOS,它就是要把OS拉起来运行。Bootloader做的事情主要有:初始化CPU时钟,内存,串口等;设置Linux启动参数;加载Linux内核镜像。BootLoader可以分为两个阶段。在阶段一,做了一些初始化,在阶段二,如果发现按键有特殊的组合,就进入相应的模式。Bootloader有三种模式如下:

a:开机后按组合键启动到fastboot模式,即命令或SD卡烧写模式,不加载内核及文件系统,可以通过数据线与电脑连接,然后在电脑上执行一些命令,如刷系统镜像到手机上。fastboot可以理解为实现了一个简单的通信协议,接收命令并更新镜像文件,其他的基本都干不了。

b:开机后按组合键启动到recovery模式,加载recovery.img,recovery.img包含内核,部分文件系统,可以读sdcard中的update.zip进行刷机,也可以清除cache和用户数据。这就是一个小型操作系统,和正常启动进入的系统的kernel差不多,只是init及之后干的事情不同。

c:开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机。

以下只分析正常启动的情况。

不同的硬件设备BootLoader不同,最常用的Bootloader还是U-boot,可以引导多种操作系统,支持多种架构的CPU。

Bootloader的上电引导程序把NandFlash上的前4K代码搬移到SDRAM,然后系统从起始地址是0x0000_0000的SDRAM启动,在这4K代码中我们必须完成CPU的核心配置,把NandFlash上的代码全部拷贝到SDRAM中去,然后跳转到SDRAM中去继续跑Bootloader。其实这就是加载各种各样的内核镜像到SDRAM中的过程。



内核镜像被加载到内存,首先进行自解压。Linux内核有多种格式的镜像,常见的zImage、bzImage、uImage等。zImage和bzImage都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip或gzip
–dc解包。用于个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个 640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。uImage是u-boot使用bootm命令引导的Linux压缩内核映像文件格式,是使用工具mkimage对普通的压缩内核映像文件(zImage)加工而得。它是U-boot专用的映像文件,它是在zImage之前加上一个长度为
64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。由于Bootloader一般要占用0X0地址,所以,uImage相比zImage的好处就是可以和bootloader共存。其实就是一个自动跟手动的区别,有了uImage头部的描述,u-boot就知道对应Image的信息,如果没有头部则需要自己手动去搞那些参数。

将内核解压到内存的指定位置,就要开始运行内核。

2.内核启动

内核入口函数是init/main.c中的start_kernel(void),Bootloader加载完内核映像后跳到Linux执行的第一个c语言程序,该程序完成的功能主要体现在其start_kernel(void)函数中,完成初始化Linux系统的进程管理,内存管理,文件系统等工作。

start_kernel(void)

asmlinkage void __init start_kernel(void)
{
	...
	//输出Linux版本信息
	printk(KERN_NOTICE "%s", linux_banner);
	//设置与体系结构相关的环境
	setup_arch(&command_line);
	...
	//页表结构化
	page_alloc_init();

	printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
	//内核选项的解析,内核解析完后,各个子系统的初始化就可通过kernel_init()=>do_basic_setup()=>do_initcalls()来完成。
	parse_early_param();
	parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   0, 0, &unknown_bootoption);
	...
	//使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口
	trap_init();
	//内存初始化
	mm_init();
	...
	//时间,定时器初始化
	time_init();
	...
	//控制台初始化
	console_init();
	...
	/* Do the rest non-__init'ed, we're now alive */
	rest_init();
}
rest_init(void)

开启内核线程等。

static noinline void __init_refok rest_init(void)
{
	...

 	//尽管init程将会挂起来等待创建kthreads进程complete,然而我们必须先创建init内核进程,这样它的pid为1。
	//启动一个内核线程,这里的kernel_init是要执行的函数的指针,NULL表示传递给该函数的参数为空,CLONE_FS |CLONE_SIGHAND为do_fork产生线程时的标志,表示进程间的fs信息共享,信号处理和块信号共享。
	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

	...
	//创建kthreadd内核线程
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

	...
 	//获取kthreadd的线程信息,获取完成说明kthreadd已经 创建成功。并通过一个complete变量(kthreadd_done)来通知kernel_init线程。
	complete(&kthreadd_done);

	//为让系统运作起来,boot idle线程必须至少执行一次schedule()。
	init_idle_bootup_task(current);//设置当前进程为idle(闲置)进程类。   
	schedule_preempt_disabled();//进程调度完成,回到这里,禁用抢占。  
	...

	//在抢占禁用时调用cpu_idle,此时内核本体进入了idle状态,用循环消耗空闲的CPU时间片,该函数从不返回。在有其他进程需要工作的时候,该函数就会被抢占。
	cpu_idle();
}
kernel_thread

创建一个内核线程,又称守护进程。源码在kernel/Process.c

/*
 * Create a kernel thread.
 */
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	struct pt_regs regs;

	memset(®s, 0, sizeof(regs));

	regs.ARM_r4 = (unsigned long)arg;
	regs.ARM_r5 = (unsigned long)fn;
	regs.ARM_r6 = (unsigned long)kernel_thread_exit;
	regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;
	regs.ARM_pc = (unsigned long)kernel_thread_helper;
	regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;
	//do_fork来产生一个新的线程,共享父进程地址空间,并且不允许调试子进程。源码在kernel/Fork.c
	return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
kernel_init(void * unused)

此时与体系相关的部分已经初始化完成,开始初始化设备,完成外设及驱动程序(直接编译进内核的模块)的加载和初始化。

static int __init kernel_init(void * unused)
{
	...
	//此时与体系相关的部分已经初始化完成,do_basic_setup()开始初始化设备,完成外设及驱动程序(直接编译进内核的模块)的加载和初始化。
	do_basic_setup();
	...
	/*
	 * Ok, we have completed the initial bootup, and
	 * we're essentially up and running. Get rid of the
	 * initmem segments and start the user-mode stuff..
	 */
	//开启init用户进程,所谓Android的启动流程从这里才刚刚开始。
	init_post();
	return 0;
}
do_basic_setup(void)

初始化设备

static void __init do_basic_setup(void)
{
	...
	do_initcalls();
}
kthreadd(void *unused)

内核线程函数,源码在kernel/kthread.c

int kthreadd(void *unused)
{
	...

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		//如果发现kthread_create_list是一空链表,则调用schedule调度函数,会使当前进程进入睡眠
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;
			
			//会删除create对应的列表entry
			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);

			//在create_kthread()函数中,会调用kernel_thread来生成一个新的进程,该进程的内核函数为kthread,调用参数为create,kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
			//kthread将会使其所在的进程进入休眠状态,直到被别的进程唤醒。如果被唤醒,将会调用create->threadfn(create->data);
			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}
总之到目前为止,android的kernel创建了两个重要的进程,ps一下如下:

root@android:/ # ps
ps
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
root      1     0     624    476   c0123e84 0000872c S /init
root      2     0     0      0     c00b6600 00000000 S kthreadd


init进程父进程为0号进程,执行根目录底下的init可执行程序,是用户空间进程。

kthreadd父进程为0号进程,是内核进程,其他内核进程都是直接或者间接以它为父进程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: