分类: 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