Chinaunix首页 | 论坛 | 博客
  • 博客访问: 486777
  • 博文数量: 104
  • 博客积分: 3045
  • 博客等级: 少校
  • 技术积分: 1230
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-29 10:18
文章分类

全部博文(104)

文章存档

2011年(72)

2010年(1)

2009年(1)

2008年(30)

分类: LINUX

2008-03-22 08:38:43

一、序

  Linux系统是如何启动,这对将来应用开发是十分重要的,本文整理自Linux
论坛,结合Moto E680,夏新E600和飞利浦968进行简单介绍

二、重要提示

    为了方便更好的理解本文,提供下面链结。
    全系列的文章地址,手机应用开发专栏:上面的出处

三、Linux启动过程总体概述

    阅读Linux源代码,是深入学习Linux系统启动的最直接方法,Linux启动这部
分的源码主要使用的是C语言,也涉及到了少量的汇编语言。启动过程中也执行了
大量的shell脚本。下面是大概的启动流程。

    用户首先打开电源,主板BIOS开始开机自检,按BIOS中设置的启动设备(如硬
盘,光盘)进行启动,接着启动设备上安装的引导程序lilo或grub开始引导Linux,
Linux引导程序首先进行内核的引导,接下来才执行init程序,init程序调用了
rc.sysinit和rc等相关程序,rc.sysinit和rc完成系统初始化和运行服务的任务后,
返回init。再由init启动了mingetty后,打开终端供用户登录系统,这时用户就可
以登录并进入了Shell窗口,至此完成了开机到登录的整个过程。

  Power On -> BIOS -> IDE/CDROM -> lilo/grub -> Kernel Boot -> Init
  ( rc.sysinit rc ) -> mingetty -> Shell
  
四、Linux手机嵌入式系统的内核启动过程简介

    下面以E680的内核代码简单初略的说明系统启动过程。
   
    嵌入式系统或者PC机,首先使用类似lilo或grub等BootLoader引导程序引导
Linux系统,当引导程序成功完成引导任务后,Linux便从它们手中接管了CPU的控
制权,然后CPU就开始执行Linux的核心映象代码,开始了Linux启动过程。
    在BootLoader完成系统的引导以后并将Linux内核调入内存之后,调用bootLinux(),
这个函数将跳转到kernel的起始位置。如果kernel没有压缩,就可以启动了。如
果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。压缩过的
kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。它将调用
函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,
decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,
然后使用在打印出信息“Uncompressing Linux...”后,调用lib/inflate.c的gunzip()。
将内核放于指定的位置。
    启动首先运行的文件有:
    arch/arm/boot/compressed/head.S
    arch/arm/boot/compressed/head-xscale.S
    arch/arm/boot/compressed/misc.c
    这些文件主要用于解压内核和以及启动内核映象。一旦内核启动,则这些文件
所占内存空间将被释放。而且,一旦系统通过reset重起,当BootLoader将压缩过
的内核放入内存中,首先执行的必然是这些代码。

  head.S相关代码:
  
/*
 * Check to see if we will overwrite ourselves.
 *   r4 = final kernel address
 *   r5 = start of this image
 *   r2 = end of malloc space (and therefore this image)
 * We basically want:
 *   r4 >= r2 -> OK
 *   r4 + image length <= r5 -> OK
 */
  cmp r4, r2
  bhs wont_overwrite
  add r0, r4, #4096*1024 @ 4MB largest kernel size
  cmp r0, r5
  bls wont_overwrite

  mov r5, r2   @ decompress after malloc space
  mov r0, r5
  mov r3, r7
  bl decompress_kernel

  add r0, r0, #127
  bic r0, r0, #127  @ align the kernel length
/*
 * r0     = decompressed kernel length
 * r1-r3  = unused
 * r4     = kernel execution address
 * r5     = decompressed kernel start
 * r6     = processor ID
 * r7     = architecture ID
 * r8-r14 = unused
 */
  add r1, r5, r0  @ end of decompressed kernel


  misc.c相关代码
  
    decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,
        int arch_id)
    {
     output_data  = (uch *)output_start; /* Points to kernel start */
     free_mem_ptr  = free_mem_ptr_p;
     free_mem_ptr_end = free_mem_ptr_end_p;
     __machine_arch_type = arch_id;
   
     arch_decomp_setup();
   
     makecrc();
     puts("Uncompressing Linux...");
     gunzip();
     puts(" done, booting the kernel.\n");
     return output_ptr;
    }  
  
  解压缩完内核后,就通过call_kernel,运行实际的内核源码。系统将调入文件:
arch/arm/kernel/head_armv.S或arch/arm/kernel/head_armo.S。对于arm的kernel而言,
有两套.S文件:_armv.S 和 _armo.S. 选择_armv.S 还是_armo.S 依赖于处理器。ARM的
version 1, version 2, 都只支持26位的地址空间。version 3开始支持32位的地址空间,
同时还向后兼容26位的地址空间。version 4开始不再向后兼容26位的地址空间。这里由
于E680使用的是version5,故只涉及文件head_armv.S。
    head_armv.S是内核的入口点,在内核被解压到预定位置后,它将运行。head_armv.S
通过调用一些文件进行CPU,内存设置。最后打开MMU,将pipeline清空,以使所有的内存
得以正确的访问。并返回到__mmap_switched清空BSS,并保存CPU类型值(processor ID)
以及系统类型(machine type),然后跳转到start_kernel。

    相关代码:
   
   
__mmap_switched:
#ifdef CONFIG_XIP_ROM
  ldr r2, ETEXT   @ data section copy
  ldr r3, SDATA
  ldr r4, EDATA
1:
  ldr r5, [r2], #4
  str r5, [r3], #4
  cmp r3, r4
  blt 1b
#endif

  adr r3, __switch_data + 4
  ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
       @ sp = stack pointer

  mov fp, #0    @ Clear BSS (and zero fp)
1:  cmp r4, r5
  strcc fp, [r4],#4
  bcc 1b

  str r9, [r6]   @ Save processor ID
  str r1, [r7]   @ Save machine type
#ifdef CONFIG_ALIGNMENT_TRAP
  orr r0, r0, #2   @ ...........A.
#endif
  bic r2, r0, #2   @ Clear 'A' bit
  stmia r8, {r0, r2}   @ Save control register values
  b SYMBOL_NAME(start_kernel)


    start_kernel()是init/main.c中的定义的函数,start_kernel()调用了一系
列初始化函数,以完成kernel本身的设置,其做了大量的工作来建立基本的Linux
核心环境。执行完start_kernel(),则基本的Linux核心环境已经建立起来了。
    下面是一些start_kernel里的初始化函数:
   
    trap_init();
    init_IRQ();
    sched_init();
    softirq_init();
    time_init();
    console_init();
    #ifdef CONFIG_MODULES
     init_modules();
    #endif
    mem_init();
    kmem_cache_sizes_init();
    pgtable_cache_init();
   
    ...
   
    在start_kernel()函数最后调用了rest_init();

    static void rest_init(void)
    {
     kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
     unlock_kernel();
     cpu_idle();
    }

    rest_init用通过kernel_thread,创建了系统第一个核心线程,启动了init()。
核心线程init()主要进行一些外设初始化工作,包括调用do_basic_setup()完成
外设及其驱动程序的加载和初始化,完成文件系统初始化和root文件系统的安装。

    执行do_basic_setup()函数之后,init()打开/dev/console设备,并重定向三
个标准的输入输出文件stdin、stdout和stderr到控制台。

   if (open("/dev/console", O_RDWR, 0) < 0)
      printk("Warning: unable to open an initial console.\n");

    init()最后会搜索文件系统中的init程序,或者由init=命令行参数指定的程序,
使用execve()函数调用执行。

    if (execute_command)
     execve(execute_command,argv_init,envp_init);
    execve("/sbin/init",argv_init,envp_init);
    execve("/etc/init",argv_init,envp_init);
    execve("/bin/init",argv_init,envp_init);
    execve("/bin/sh",argv_init,envp_init);
    panic("No init found.  Try passing init= option to kernel.");

    到此init()函数结束,内核的引导部分也到此结束了。

五、Linux手机嵌入式系统init的执行过程

    由Shell命令 ps -ef 查看系统进程
   
    Moto E680 系列
    ------------------------------------------------------
    UID        PID  PPID  C STIME TTY          TIME CMD
    root         1     0  0 12:14 ?        00:00:00 init
   
    飞利浦 968
    ------------------------------------------------------
   
    PID  Uid     VmSize Stat Command
    1 root        512 S   init
   
    夏新E600
    ------------------------------------------------------
   
    PID  Uid     VmSize Stat Command
    1 root        532 S   init
   
    init的进程ID全部是1,从这一点就能看出,init进程是系统所有进程的起点,
Linux在完成内核引导后,就开始执行init程序了。与PC机的init不同,嵌入式设备
本身就可以依靠独立的init进程完成初始化工作,如/sbin/init /sbin/init.new
没有源码,我们也很难得知他究竟会做些什么,不过init程序还需要读取配置文件
/etc/inittab,inittab是一个普通文本文件,由若干行指令组成。
   
    下面是夏新E600的inittab文件:
   
    # Format for each entry: :::
    #
    # id        == tty to run on, or empty for /dev/console
    # runlevels == ignored
    # action    == one of sysinit, respawn, askfirst, wait, and once
    # process   == program to run
   
    ::sysinit:/etc/rc.d/rc.sysinit
    ::respawn:/root/logger
    ::respawn:/root/watchdog
    ::once:/root/run_dv2.sh
 
    #
    # Do we have to wait here ?
    #::wait:/etc/rc.d/rc 3
   
    # What to do when CTRL-ALT-DEL is pressed.
    ::ctrlaltdel:/sbin/poweroff
   
    # What to do before system shutdown
    null::shutdown:/bin/shutdown.sh
   
    # What to do before power is cut(post init)
    null::postinit:/bin/postinit.sh
   
    #::respawn:/sbin/getty -L ttyS1 38400 vt100
    #::respawn:/sbin/getty -L ttyS2 115200 vt100
    #::once:/sbin/getty -L ttyUSB0 115200 vt100
    #ttyUSB0::respawn:-/bin/sh
    ttyS2::respawn:-/bin/sh
   
    再看看Moto的inittab文件:
   
    # /etc/inittab: init(8) configuration.
    # $Id: inittab,v 1.8 1998/05/10 10:37:50 miquels Exp $
   
    # The default runlevel.
    # ezx -- id:3:initdefault:
    id:2:initdefault:
   
    # Boot-time system configuration/initialization 脚本.
    # This is run first except when booting in emergency (-b) mode.
    si::sysinit:/etc/init.d/rcS
   
    # What to do in single-user mode.
    ~~:S:wait:/sbin/sulogin
   
    # /etc/init.d executes the S and K 脚本s upon change
    # of runlevel.
    #
    # Runlevel 0 is halt.
    # Runlevel 1 is single-user.
    # Runlevels 2-5 are multi-user.
    # Runlevel 6 is reboot.
   
    l0:0:wait:/etc/init.d/rc 0
    l1:1:wait:/etc/init.d/rc 1
    l2:2:wait:/etc/init.d/rc 2
    l3:3:wait:/etc/init.d/rc 3
    l4:4:wait:/etc/init.d/rc 4
    l5:5:wait:/etc/init.d/rc 5
    l6:6:wait:/etc/init.d/rc 6
    # Normally not reached, but fallthrough in case of emergency.
    z6:6:respawn:/sbin/sulogin
   
    # What to do when CTRL-ALT-DEL is pressed.
    ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
   
    # Action on special keypress (ALT-UpArrow).
    kb::kbrequest:/bin/echo "Keyboard Request--edit /etc/inittab to let this work."
   
    # What to do when the power fails/returns.
    pf::powerwait:/etc/init.d/powerfail start
    pn::powerfailnow:/etc/init.d/powerfail now
    po::powerokwait:/etc/init.d/powerfail stop
   
    # This line provides a nice out-of-box experience.  For regular use, you
    # should replace it with the proper getty lines below.
   
    # ezx -- con:2345:respawn:/sbin/getty console
    con:2345:once:/sbin/getty -l /bin/fakelogin3 -n console

    其中以#开始的行是注释行,除了注释行之外,每一行都有以下格式:
  id:runlevel:action:process

  简单作下中文解释,最直接了当的方式就是通过#man inittab查看标准手册。

  A.id
   
  id就是入口标识符,是一个1-4位的字符串,对于getty或mingetty等其他
login程序项,要求id与tty的编号相同,否则getty程序将不能正常工作。

  B.runlevels

  runlevel是init所处于的运行级别标识,一般使用0-6以及S或s。0、1、6运行
级别被系统保留。0作为halt动作,1作为重启至单用户模式,6为重启。S和s意义相
同,表示单用户模式,且无需inittab文件,因此也不在inittab中出现,实际上,
进入单用户模式时,init直接在控制台(/dev/console)上运行/sbin/sulogin。
    在PC的Redhat系统中,2表示无NFS支持的多用户模式,3表示完全多用户模式
(也是最常用的级别),4保留给用户自定义,5表示XDM图形登录方式。7-9级别也
是可以使用的,传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值,
以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功
才会执行。
  如:
  # Default runlevel. The runlevels used by RHS are:
  #   0 - halt (Do NOT set initdefault to this)
  #   1 - Single user mode
  #   2 - Multiuser, without NFS (The same as 3, if you do not havenetworking)
  #   3 - Full multiuser mode
  #   4 - unused
  #   5 - X11
  #   6 - reboot (Do NOT set initdefault to this)

  C.action
  action是描述后面process的运行方式。action可取的值包括:respawn,wait,
once,boot,bootwait,off,ondemand,initdefault,sysinit等等很多。
  sysinit、boot、bootwait等action将在系统启动时无条件运行,并忽略其中的
runlevel。
    respawn表示此进程遇到关闭时会自动重启。
  initdefault是一个特殊的action值,用于标识缺省的启动级别;当init由核心激
活以后,它将读取inittab中的initdefault项,取得其中的runlevel,并作为当前的
运行级别。如果没有inittab文件,或者其中没有initdefault项,init将在控制台上
请求输入runlevel。后面的process会被忽略。
    once表示此进程在相应运行级别只会运行一次。
    wait也是只会运行一次,init会一直等到他运行结束。
    kbrequest表示init在接受一个特殊的键盘事件时会启动后面的process。
   
  D. process
  process是具体的执行程序。程序后面可以带参数。


五、Linux嵌入式系统初始化过程

    在init进程执行inittab定义的进程时,其实也进行了一些系统初始化。在上面
inittab示例中都有如下类似的内容:
   
    夏新E600:
    ::sysinit:/etc/rc.d/rc.sysinit
   
    MOTO E680:
    # Boot-time system configuration/initialization 脚本.
    # This is run first except when booting in emergency (-b) mode.
    si::sysinit:/etc/init.d/rcS
   
    l0:0:wait:/etc/init.d/rc 0
    l1:1:wait:/etc/init.d/rc 1
    l2:2:wait:/etc/init.d/rc 2
    l3:3:wait:/etc/init.d/rc 3
    l4:4:wait:/etc/init.d/rc 4
    l5:5:wait:/etc/init.d/rc 5
    l6:6:wait:/etc/init.d/rc 6
   
    rc.sysinit,rcS,rc这些都是shell的脚本,完成大量的系统初始化的工作。
    主要工作包括:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要
优先执行任务。这些脚本内容就比较多了,夏新E600相对简单一些,在此做些简
要的说明。

    首先调用/etc/rc.d/rc.sysinit脚本。
    rs.sysinit首先会设置PATH环境变量然后调用/etc/rc.d/init.d/functions
进行一些环境变量设置并注册一些函数。这些函数主要都是管理进程的。   
    完成之后返回rs.sysinit进行一些文件系统,网络的一些设置。接着调用
/etc/rc.d/rc.modules完成驱动安装。
    最后判断是否已经init完成,否则会执行“出厂设置的脚本”。
    [ -f "/mnt/user/etc/init_done" ] || /usr/bin/origin.sh
   
    到这里::sysinit:/etc/rc.d/rc.sysinit就结束了。

    接着便运行下面一些必要程序,两个log都是二进制文件没有什么好说的。不
过这里的/root是一个连接,实际位置是/mnt/user/etc。
    ::respawn:/root/logger
    ::respawn:/root/watchdog
   
    接着启动的最后一个脚本,具体位置也在/mnt/user/etc下,这个关键脚本就
会运行手机的图形化平台了。
    ::once:/root/run_dv2.sh

六、小结

   
    这是简单浏览下系统的启动过程,有个初步的概念。但我们也能了解到,夏新
E600一些功能闲置。比起Moto E680,夏新E600和飞利浦968的启动着实简单。不过
夏新E600和飞利浦968更喜欢做封装,基本的sh命令全封装于busybox,图形化程序
又全部封装于quicklauncher。init初始化过程很不透明,启动的服务也做了很多
封装。此外安装软件的限制可以用变态来形容,软件管理方式也是十分呆板。真不
知道还应不应该叫他Linux手机,或者叫Windows更贴切。

 

出处:http://blog.csdn.net/liwei_cmg 作者:草木瓜  于 2006-11-25

阅读(1662) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~