android启动之linux内核启动
2014-04-25 09:02
92 查看
本文分成两部分:智能手机的硬件系统架构和linux内核的启动两部分,前者作为后者的基础。
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之类设备。
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的信息,如果没有头部则需要自己手动去搞那些参数。
将内核解压到内存的指定位置,就要开始运行内核。
start_kernel(void)
开启内核线程等。
创建一个内核线程,又称守护进程。源码在kernel/Process.c
此时与体系相关的部分已经初始化完成,开始初始化设备,完成外设及驱动程序(直接编译进内核的模块)的加载和初始化。
初始化设备
内核线程函数,源码在kernel/kthread.c
init进程父进程为0号进程,执行根目录底下的init可执行程序,是用户空间进程。
kthreadd父进程为0号进程,是内核进程,其他内核进程都是直接或者间接以它为父进程
智能手机的硬件系统架构
移动终端,基本上可以分成两种:一种是传统手机(feature phone);另一种是智能手机(smart phone)。智能手机具有传统手机的基本功能,并有以下特点:开放的操作系统、硬件和软件可扩充性和支持第三方的二次开发。FeaturePhone是在不断扩充应用功能的无线通信终端(行业术语叫移动台),而智能手机是增加了无线通信功能的手持式电脑。智能手机的软件体系基本上照搬了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号进程,是内核进程,其他内核进程都是直接或者间接以它为父进程
相关文章推荐
- Linux 内核启动挂载android根文件系统过程分析
- linux 内核启动过程以及挂载android 根文件系统的过程
- Linux内核怎样启动Android
- Android 内核 - 00 Android模块及Linux 启动过程
- linux 内核启动过程以及挂载android 根文件系统的过程 ( 转)
- linux 内核启动过程以及挂载android 根文件系统的过程
- linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析
- Linux 内核启动挂载android根文件系统过程分析
- linux 内核启动过程以及挂载android 根文件系统的过程
- Linux 内核启动挂载android根文件系统过程分析
- Android笔记-Android启动之Linux内核启动
- linux 内核启动过程以及挂载android 根文件体系的过程 ( 转)
- Android笔记 - Android启动之Linux内核启动
- Android系统和linux内核的关系详解
- 全面解析Linux 内核 3.10.x - initramfs 启动流程
- 3.分析Linux内核的启动过程
- 跟踪分析Linux内核的启动过程
- Linux下android内核编译
- Linux启动过程的内核代码分析
- 在Linux启动时自动加载内核模块