第一天,google说,要有电,于是电流涌向了电路板,涌入了arm处理器,于是arm运作了。
--xxx
嵌入式世界诞生之出,世界是荒蛮的,电流涌向各个芯片,各种处理器等待着唤醒。
arm觉醒了,开始审视自己。。。。
---------------下面内容可能有误,请指正-------------------
人们叫他Bootloader,和PC中的BIOS是哥们,负责芯片各个端口的初始化,以及cpu时钟等等设置。
在手机中,bootloader一般就是刷机人口中的底包(并不全是,第二天会说),以摩托为例,买到手机后发烧友们总会首先刷底包,什么北欧、港行、巴西什么的底包。首先下载下来100~300m的sbf文件。底包这么大,都包含了什么?bootloader肯定在其中,但嵌入式世界诞生之初是荒蛮的,能有这么大么?
---------------上面内容可能有误,请指正-------------------
我们就打开一探究竟:
这里就啰嗦一下怎么解sbf包,方便具有geek精神的刷机友们一探究竟
下载 SBF-Recalc ,用这个软件解开sbf包,这里以 3.4.3_11-Stock.UKTmobile_BLUR.sbf 新新英底包为例,解压后的文件如下:
出来了这么多东西,看到有两个8m多的文件了吗?他们就是linux的基本内核,随便解开一个第三方rom刷机包,里面都有boot.img,和这两个文件是差不多的,准确的将,bootloader是哪个,水平不行,我不知道。。。。。。
一、bootloader 干了什么
一般手机当中有很多文件系统,从NOR到NAND、SDcard不等,每个rom当中都存放不同的数据。
bootloader首先会从自己的rom当中读取一些指令,初始化各种堆栈等等,然后转到定制厂商烧写进自身某个rom的开始地址的程序,进入mian函数,这里就是与看门狗、各种中断、外围rom等等打交道了。
二、bootloader之后的三条路
这里一般会处理两种中断/事件的组合,哪两个,一个是进入工厂模式(显示出bootloader这几个字,当然是刷底包啦),另一个就是Recory(恢复模式,刷rom啦)。
之后会有三条路可走
(一) bootloader烧写模式(工厂模式)
三般情况下是用户在开机时(bootloader生命期之内)按下某些键,比如开机键,我的defy在usb有连接时也会进入bootloader界面。这些系统中断就会被接受。其它的则屏蔽
没什么多讲的,纯粹是嵌入式方面,我懂得不多,对我们来讲,无非就是刷/烧写底包(一般情况下,刷包软件会把从bootloader到android rom一股脑的都烧写进各个rom[这里指硬件,存储器]当中)。
(二) Recory模式(恢复模式)
二般情况下,用户按下开机键+音量/相机键(我说反了么?可能忘了)会进入恢复模式。此时bootloader会加载存放recory镜像(boot.img,稍后会讲)的rom/分区,并将控制权交给它,并结束自己的生命周期。
(三) 正常启动
一般情况下,用户什么都不按,bootloader会正常加载系统内核(某rom/分区的boot.img)。进入到linux kernel。
三、linux kernel
boot.img是什么?img是二进制文件,它一个文件系统的二进制镜像,前面是系统镜像,后者是zimage(2mb左右),眼熟吧,linux引导都需要它,它就是kernel(内核),是由bootloader在最后阶段进入的。
它都干了什么?对于linux引导而言,用惯了pc,确实会感觉到捎了vmlinux,确实是这样,它包含了一个压缩过的内核,就是vmlinux,他首先会解压vmlinux,并放到ram中,之后,运行。
这里不得不说一下,我第一次接触linux,用的是debian,当时没见过什么u盘,都是软盘,自己按着debian的教程,下载了一个基于软盘的微型Linux,就是内核啊,启动一次得插换三次软盘啊~~~,基本的命令都包含啦~~当时的windows 98多大啊,与三张软盘比就是指数级啊,我从此爱上了linux啊。。。。。。那么舒服的cli交互,跟dos比,一个天堂一个地域啊~~
vmlinux的任务就是初始化linux世界
首先,它会初始化一些必要的东西,比如检测arm型号什么的,然后就开始Linux的初始化(start_kernel),初始化各种内核子系统,最后会开始init,注意,这是内核级的init。
然后init会挂接根文件系统(/),根文件系统就是内核镜像所在的系统了,它存在于内存之中。然后初始化设备驱动,之后,机友们所谓的第一屏启动画面,就是在这里显示出来的,比如我的defy,是一个摩托的logo。
这之后的之后,就到了第二天。
第二天,google说,荒芜要被开垦,系统便运作了,它是linux。
--xxx
荒蛮大地就要变得肥沃,linux已经运行起来了。。。。
linux就不多讲了,这里只讲讲被google大刀阔斧改了内核后的linux。
第一天最后,内核init已经干完了自己的事,把控制权交给了第一个用户级进程,也叫做init。
想知道这个init干了什么事,我们只能看看源码,不贴代码,这里只说说它干了什么。
(system/core/init/init.c -->main)
一、清空umask
也就是设置缺省权限,这里设置为0,umask为0000的话,就相当于chmod中的0777,经常使用linux对于chmod 777应该很熟悉,就是赋予某个文件的权限为,所有组、所有用户可读可写可运行,也就是最宽松的权限。
二、创建并挂载一些基本的目录
创建目录并挂载相应系统:
/dev 设备目录,所有的外围设备都在这里了,包括真实的设备如sim卡,也包括虚拟的设备如必不可少的null设备。挂载关系是 /dev -> tmpfs,tmpfs顾名思义就是临时文件系统,这个系统只占用内存空间。
/proc 系统信息目录,包含了当前系统的所有信息,比如进程、时钟等等动态的信息。挂载关系是 /proc -> proc
/sys 这里存储的东西,都是硬件设备在linux上映射的对象,比如pci设备。挂载关系是 /sys -> sysfs
/dev/pts 这个是远程终端控制台设备,字符终端啦,如果木有这个的话,就不可能adb shell调试android。挂载关系是 /dev/pts -> devpts
/dev/socket 服务于android的,socket是linux中进程通信的一种方式,/dev/socket下面就是已经被系统分配的soket资源,这里基本上是一些本地服务,比如ridl,有兴趣可以adb shell查看一下。
三、初始化NULL设备,重定向标准输入输出,初始化kmsg系统,并且解析init.rc文件
null是Linux的一个标准设备,也就是所谓的黑洞,至于为什么有它,就得从输入输出重定向说起,比如linux控制台下运行一个程序,有时会输出一大堆东西,这是它向标准输出写的,我们不想让它显示出来,就是用 > NULL给它的输出重定向到了这个黑洞设备,系统呢会给这个程序返回一个写入成功的操作,实质上,系统什么都木有干。
kmsg是linux下的一个内核级的日志系统,kernel message。就好比anroid提供的Log系统一样,只是针对内核级别的。
对于init.rc文件,这里只进行了解析,并没有执行里面的一些命令。
四、获得内核命令参数并且解析特定机型的init.*.rc文件
获得内核命令参数,也就是显式说明的一些参数,如果配置过grub或者Loli的话,就可能与这个打过交道。
每个手机硬件平台都不一样,adb shell一下,会发现有两个rc文件,其中一个就是与特定平台有关的rc配置文件,比如我的defy就是init.mapphone_umts.rc,为什么叫这个?中间就是手机硬件平台的名字,可以 cat /proc/cpuinfo来获得Hardware信息,我的如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# cat cpuinfo
cat
cpuinfo
Processor : ARMv7 Processor rev 2 (v7l)
BogoMIPS : 299.11
Features : swp half thumb fastmult vfp edsp neon vfpv3
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x3
CPU part : 0xc08
CPU revision : 2
Hardware : mapphone_UMTS
Revision : 0000
Serial : 0000000000000000
CPU Tier : 10
|
可以看到Hardware的值就是rc的副名称。
init首先会获得/porc/cpuinfo中的这个属性值,然后根据这个字符串查找特定的rc文件,最后根据rc中的配置内容,解析它。
五、执行rc文件中的命令
上一步,init已经解析了那两个rc文件,这里,会根据rc文件中的具体内容,来分别执行对应的动作,后面会独立分析rc文件的格式内容,以及执行方法。
六、变为守护神
到这里,init就进入了死循环了for(;;){}。那么它都守护了些什么?
1、porpety service 启动并守护属性服务
android下特有的。就好比windows下面的注册表,记录了各种信息。大到系统是否成功运行的标志,小到短信声音。用户在设置一些手机设置的时候,在底层,实际就是和propety service打交道。
属性前缀 |
描述 |
示例(shell下操作) |
ro. | 只读属性 | setprop ro.media.capture.maxres 5m 摄像头的最大像素 |
persist. |
额外存储到/data/property目录下 | setprop persist.sys.country CN 不解释。注意,每个属性都存储为单独的一个文件 |
net. | 联网相关,比如gprs、蓝牙… | setprop net.bt.name CAPF 蓝牙的网络名称为CAPF net.change的值为最后一次更改net.*属性的属性名,例如: net.change=net.gprs.local-ip |
ctrl.start 控制命令 |
启动init.rc中标注为service的服务 | setprop ctl.start bootanim 启动boot动态图像(第二屏启动画面) 一个服务设置后,其结果会以下面的属性返回,例如 init.svc.bootanim=running |
ctrl.stop |
停止init.rc中标注为service的服务 | setprop ctl.stop bootanim 停止boot动态图像(第二屏启动画面) 一个服务设置后,其结果会以下面的属性返回,例如 init.svc.bootanim=stoped |
想要查看并设置属性,可以通过以下三种途径:
shell浏览文件:
/default.prop
/system/build.prop
/data/property/*
java:
System.getProperty(“xxxx”);
System.setProperty(“xxxx”);
c/c++:
demo.c:
1
2
3
4
5
6
7
8
9
10
11
|
#include <cutils/properties.h>
#include <stdio.h>
void
print_prop(
const
char
* key,
const
char
* value,
void
* cookie)
{
printf
(
"key=%s,value=%s/n"
,key,value);
}
int
main()
{
property_list(print_prop,NULL);
}
|
Android.mk:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
LOCAL_PATH:= $(call my-
dir
)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= /
list_property.cpp /
LOCAL_SHARED_LIBRARIES := /
libcutils /
libutils /
LOCAL_MODULE:= list_prop
include $(BUILD_EXECUTABLE)
include $(call all-makefiles-under,$(LOCAL_PATH))
|
2、动态生成设备节点
android的linux下面是没有udev的,udev可以说是一个根据内核的硬件消息来自动发现设备的一个程序,android下面根据硬件相应的,也就是键盘与内存卡,在平板上面的话就更多了,比如鼠标、键盘等等。没有udev的话,android是如何实现硬件的热插拔呢?
和udev一样,init守护神同样监听了uevent事件,自己根据uevent的内容来做相应的事情,与udev做了异曲同工之事。
看一下大致流程。首先设备状态更改,内核会检测到,并发出uevent消息(字符串),init检测到消息,交给相应函数处理,这个函数根据uevent的内容,再做进一步处理。除了插拔内存卡等(android有专门的外存管理机制,就是vold),其它(比如数据线插入、鼠标插入)都会处理。
3、监听keychord事件
keychord是组合按键,从源码的行为来看,应该是考虑到组合键盘这种外设,大部分情况不会用到手机上,而多用在智能设备上,也就是没有触屏以及按键很少的android设备,比如运行android的手表神马的,通过不同的按键组合,来代表一个标准键盘的输入。
这个东东估计用的不多。
4、杀死僵尸进程
什么是僵尸进程?
linux的进程有个特点,一个主进程可以分裂(fork)子进程(android的受精卵zygote完美的发扬了这种精神),在桌面版的一些linux中,查看系统监视器,仔细看看进程信息,会发现很多进程会是树状结构,点击一个进程后面又展开了好几个进程,而且是个多级树。这都是一个进程有fork了好几个子进程的结果。
如果主进程被kill的话,那么它的子进程就有可能成为僵尸进程,所谓僵尸就是不干活但占用空间的程序死尸,这时,init守护神就负责回收这些无辜的灵魂,来释放本来就稀缺的内存资源。
5、守护重要服务
这些服务是native层面的服务,比如servicemanager、vold。例如重要的zygote,有时候(不经常)碰到的感觉和突然重启一样,这八成就是zygote崩溃并重启了,要知道java世界可是zygote孵化出来的。
解读init.rc
android的init.rc语法是独有的,可以说是一种语言吧。
init.rc的语法分为行为(Actions),、命令(Commands) 、服务(Services)、选项(Options)。
类别 |
名称 |
描述 |
SECTION | on | 触发条件 |
同上.. | service | 解析service |
COMMAND | chdir | 更改当前工作目录 |
同上.. | chroot | 更改参考的根目录位置 |
.. | class_start | 启动某个设置了class名称的服务 |
.. | class_stop | 停止某个设置了class名称的服务 |
.. | domainname | 域名 |
.. | exec | 调用程序并转移进程 |
.. | export | 提交变量 |
.. | hostname | 主机名 |
.. | ifup | 激活网卡 |
.. | insmod | 挂载模块 |
.. | import | 引入配置,比如etc下的一些rc文件,和java中的import差不多 |
.. | mkdir | 建立目录 |
.. | mount | 挂载文件系统 |
.. | setkey | 从源码看,应该是设置一个命令的关键字缩写,比如可以将domainname映射为dn |
.. | setprop | 设置一个属性 |
.. | setrlimit | 设置当前程序可以打开的最大文件数到系统规定程序可以打开的最大文件数 |
.. | start | 启动服务 |
.. | stop | 停止服务 |
.. | trigger | 不清楚,难道是自定义触发器? |
.. | symlink | 建立符号链接 |
.. | sysclktz | 设置基准时间 |
.. | wait | 等待文件准备好?Linux中这是进程调度的函数 |
.. | write | 向文件、设备写个什么东西。肯定不是传消息的那个wirte |
.. | copy | 不解释 |
.. | chown | 更改所有者 |
.. | chmod | 更改权限 |
.. | loglevel | Log输出级别,低于这个级别的就输出 |
.. | restart | 重启服务 |
OPTION | capability | 能力,也就是系统对进程的一种权限控制。 |
同上.. | class | 设置class name |
.. | console | 启用控制台 |
.. | critical | 是否关键,也就是4分钟之内重启超过4次的话,重启之后就进入recovery模式 |
.. | disabled | 不随class自动启动 |
.. | group | 组归属 |
.. | keycodes | 不明白。。。。。 |
.. | oneshot | 只启动一次,意外退出后不必重启 |
.. | onrestart | 重启时 |
.. | setenv | 增加环境变量 |
.. | socket | 申请socket资源 |
.. | user | 用户归属 |
.. | ioprio | io调度优先级 |
(很多属性与命令用法都与linux中同名命令差球不多)
init是分段(section)解析init.rc的,在keywords.h中可以查看关键字的定义。init是以什么标志来分段解析init.rc呢?结合init.rc的内容,可以看出,分段标记是以on 和 service来标记的。下面详细说明。
on 啥时候干什么
on属于行为。
on early-init
init之前、加载完所有rc文件后即执行,在miui的rom中,init.rc在early-init执行的是start ueventd,根据keywords.h的定义,start是个命令(COMMAND)。
这里顺便说下ueventd,android中底层(一般指驱动)通知上层的事件,用的是uevent,java层通过观察者模式实现,用到的类为 UEventObserver,使用intent来传递;native层用的是android_os_UEventObserver.cpp,使用uevent.c通过socket传递。当然,这是framework及以下的层面,一般开发不经常用到,更何况这几个类都没有被暴露出来。
on init
加载propety各项属性文件之前执行,在init变为propety service之前都属于init阶段。
on early-boot
启动属性服务后即执行。
on boot
boot的时候执行。
on property:xxxxx=x
当某个属性设置为预期值时执行。
关于init.rc,其实结合/src/system/core/init/* 源码和init.rc文件来看,会明白许多。
第三天,google说,伊甸园(linux世界)要被隔离,于是便创造了亚当(Adm)与夏娃(Eve),称它为zygote和system_server
--xxx
第二天,init跑完了,它对于android系统,最重要的,就是启动了zygote和system-server,谁是Adam谁是Eve?
从分析init.rc来看
1
|
service zygote
/system/bin/app_process
-Xzygote
/system/bin
--zygote --start-system-server
|
--start-system-server只是个参数。
分析源码
啊,原来夏娃(zygote)用自己的肋骨(fork)创造了亚当(system_server)!什么?为什么不是亚当创造夏娃!? android的世界,是无性繁殖的世界,zygote(夏娃)一开始就是个受精卵。。。。。。。
Eve诞生记(zygote启动过程)
第一步,受精(改名)
从init.rc可以看出,启动的是app_process程序,启动后,再把自己的进程名改为zygote,就算是受精了。。
第二步,着床(交由AppRuntime启动javaVM)
这时,会创建一个vm,算是android java世界的祖师爷,并且在这个vm启动时赋予各种参数,比如我们都知道默认的情况下,一个应用程序加载的内存不能超过16mb,这个参数就是在这里设置的,heapsize为16mb。这之后vm就启动了,哦,应该叫dalvik。
在启动后,还会为java的类注册JNI函数,android世界是c与java互相交织的。
第三步,生长脐带(初涉java世界)
注册好各种JNI函数后,zygote的C层面就可以调用Java代码了。
这里第一次进入了java世界。它调用了 com.android.internal.os.ZygoteInit 的main方法。
java世界并不孤立
1、它首先注册socket,使自己成为一个服务端,也就是IPC通信服务端。这就是android的伟大之处,巧妙利用了linux的所有特性。以后会讲到zygote的真谛。
2、然后预加载类以及一些android资源。洋洋洒洒1k多个java类要加载,并且还都是加载时间大于1250微妙的类,android框架加载耗时的类都到这里来加载了,这也正是android开机慢得原因,不过苦尽甘来吗,开始累点能干的都干了,以后用起来就方便了,不是么?当然,android的系统就像是量产车,各种性能不求最好只求最稳定,广大发烧友改rom就像改装汽车一样,需要精通从齿轮组到ECU的各种知识,方能达到硬件与软件完美的结合以便发挥出最大功效,扯多了。。要想定制rom,减少开机时间,还得靠nb的水平与良好的洞察力。
3、启动SystemServer。也就是system_server进程。
刚才说了,这个system_server就是Adm,夏娃的第一块肉就这么掉下来了,同样利用linux的fork机制,从zygote进程分裂出了一个system_server进程。男女搭配干活不累,亚当与夏娃共同劳动,为我们搭建美好的android世界。
后面会分析system_server都干些什么事。
4、建一个线程,转入socket侦听模式。每个apk在android中运行起来都是一个单独的linux进程,这些进程,就是zygote分裂出来的。现在zygote侦听的就是ActivityService通过system_server使用socket传入的请求,用以分裂进程。线程之外的最后,关闭之前打开的socket侦听。
现在夏娃该干的事,基本上干完了,就等着亚当再次让她受精了。。。。。 -_-!!!!
这里就顺便描述下每个Activity分娩出来的过程吧!(Activity大致启动过程)
step 1 凡人向神求仔:
启动一个activity,也就是startActivity。这个activity依附于一个未启动的进程,好比刚开机,打开一个android程序。
step 2 大神收到祈愿:
ActvityManagerService收到startActvity,梳理一番各种参数,比如apk的报名,再将这个祈愿通过送到伊甸园交由夏娃实施。
step3 伊甸园接到生仔请求:
亚当愿意别人直接找夏娃么?呵呵,各种service都是SystemServer启动起来的,而SystemServer又掌管着Binder,自己肯定会首先处理这个通知的。SystemServer通过socket这个IPC机制,向zygote发送一个fork请求,这时从java世界回到了native世界,亚当(system_server)让夏娃(zygote)受精了。。。。 -_-!!! android的繁荣离不开这种造仔活动
step4 夏娃生仔:
又要分裂了,fork出了一个新进程,并把这个进程返还给Java世界,并且由ActivityManagerService管理它。这里,就是让这个进程调用ActivityThread,ActvityThread就是main,apk程序的main。
linuix中fork出来的子进程会继承父进程的所有信息,就相当于一个拷贝,只不过变成了另一个进程。既然继承了所有信息,那么dalvik也就继承下来了,这就解释了为什么一个android程序都有一个vm进程。
Adam诞生记(SystemServer启动过程)
上面提到zygote在java层启动并fork了SystemServer,也就是夏娃身上掉下来的第一块肉。
SystemServer首先会关闭因fork而从父进程继承而来的socket。
第一步,Native层初始化
这里会通过JNI调用native方法,又回到了Native世界。
首先,初始化Skia引擎,就是一个图像引擎,封装了画图的各种操作,这样屏幕就能让显示东西了。
然后,启动Binder,也就是android IPC的核心。
第二步,换名,并进行JAVA层初始化
换名了,这个vm进程就被正式命名为system_server了。
java层初始化,也就是调用 com.android.server.SystemServer 的main。有趣的是,这里通过反射,获得SystemServer类的main方法后,不直接调用,而是抛出一个异常,这个异常包含了main这个Method。
再回头看看zygote的java世界,启动了system_server的代码是在try中,catch中就是截获上面所说的异常,并执行main的这个Method。
为什么这么做?
先来看个示例程序:
12345678910111213141516171819202122232425262728293031323334353637public
class
Method_test {
public
static
void
main(String[] args) {
ClassA ca=
new
ClassA();
ca.start();
}
}
class
ClassA{
public
ClassA() {
}
public
void
start(){
System.out.println(
"开始调用方法"
);
Method_A();
}
public
void
Method_A(){
System.out.println(
"方法A被调用"
);
Method_B();
}
public
void
Method_B(){
System.out.println(
"方法B被调用"
);
Method_C();
}
public
void
Method_C(){
System.out.println(
"方法C被调用"
);
Method_final();
}
public
void
Method_final(){
System.out.println(
"最终方法被调用"
);
}
}
上面这段代码模拟一个一个场景,方法A调用B,B又调用了C。。。 A()->B()->C()->final(),final里面可能又有更复杂的内容,而且final之后基本上不做什么了。真实情况下,A()与final()之间还有更多的调用层次。
上面的调用栈情况是这样的:
这样的话,在一些资源稀缺的平台上,,,,的确不怎么好,我们希望执行final的时候,前面调用的层次能消失,以前用过的变量什么也消失,还我们一个清净的世界,而且执行完了也不用再层层跳出了。
这时,try catch就有大作用了,看代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243public
class
Method_test {
public
static
void
main(String[] args) {
ClassA ca=
new
ClassA();
ca.start();
}
}
class
ClassA{
public
ClassA() {
}
public
void
start(){
System.out.println(
"开始调用方法"
);
try
{
Method_A();
}
catch
(RuntimeException e) {
//捕获异常后再执行目标方法
Method_final();
}
}
public
void
Method_A(){
System.out.println(
"方法A被调用"
);
Method_B();
}
public
void
Method_B(){
System.out.println(
"方法B被调用"
);
Method_C();
}
public
void
Method_C(){
System.out.println(
"方法C被调用"
);
//这里抛出一个异常
throw
new
RuntimeException();
}
public
void
Method_final(){
System.out.println(
"最终方法被调用"
);
}
}
与之前相比,把final()的调用换到了start()里,直接让c()抛出个异常,catch中执行final()。
调用栈如下:
Android的设计思想真猛啊,通过这么分析,又学到一个技巧。。。。
SystemService->main
1、加载native库并调用init1()
这里创建了几个native服务, ServiceManger、SurfaceFlinger等,并把当前线程加入到Binder的通信大军中。
2、通过JNI调用JAVA世界的init2()
在上面的init1()中,最后会调用java世界的init2()。
这里就是启动各种服务了,什么EntropyService、PowerManagerService、BatteryService、WindowManager等等。
还会把地狱犬召唤出来,也就是WatchDog(看门狗),来时刻盯着一些重要的Service,防止他们堕落。看门狗的职责就是盯着一些重要的service,万一他们挂了,就把亚当杀死,然后让init把它再原地满血复活。
最后,就进入了消息循环,负责android世界中跨线程访问的调度,google常曰 子线程要通过handler来更新UI线程,那么handler中发送的消息就是靠这里来分发的。
这里严重推荐 邓凡平 前辈所著的《深入理解Anroid 卷I》,我自己感觉这是我见过的涉及框架的最好的一本书,主要就是通俗易懂啊(其实还是自己的水平有限)
转自:
http://www.cnblogs.com/hangxin1940/archive/2011/10/01/2196964.html