73. 矩阵置零(JS实现)

73. 矩阵置零(JS实现)

1 题目
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
进阶:
一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是*好的解决方案。
你能想出一个常数空间的解决方案吗?

2 思路
这道题的思路主要是用两个数组,分别用来存储值等于0的元素行列号,然后再遍历这两个数组将对应的行和列置0,题解中还有一种方法,把原数组的*行和*列当作行列数组来进行标记,这样就没有使用额外m + n的空间

3代码
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var setZeroes = function(matrix) {
const rows = [];
const cols = [];
const m = matrix.length;
const n = matrix[0].length;

for (let i=0; i<m; i++) {
if (cols.length === n) { //当所有列都要置为0时,就没有再遍历行的必要了
rows.push(i);
continue;
}
for (let j=0; j<n; j++) {
if (matrix[i][j] === 0) {
rows.push(i);
if (cols.includes(j)) continue;
cols.push(j);
}
}
}

for (let i=0; i<rows.length; i++) {
for (j=0; j<n; j++) {
matrix[rows[i]][j] = 0;
}
}

for (let i=0; i<cols.length; i++) {
for (let j=0; j<m; j++) {
matrix[j][cols[i]] = 0;
}
}
};

Android开机性能分析工具 Bootchart

bootchart是android原生自带的性能分析工具,可以收集android开机过程中的log。再利用jar包能够将开机时各个进程呈现在图表中。可以比较直观的看到开机时哪些比较耗时。

一、启用Bootchart
android中的bootchart源码位于system/core/init目录下,bootchart.cpp中有个do_bootchart_start()函数

static int do_bootchart_start() {
// We don’t care about the content, but we do care that /data/bootchart/enabled actually exists.
std::string start;
if (!android::base::ReadFileToString(“/data/bootchart/enabled”, &start)) {
LOG(VERBOSE) << “Not bootcharting”;
return 0;
}

g_bootcharting_thread = new std::thread(bootchart_thread_main);
return 0;
}

代码中时通过判断是否有/data/bootchart/enabled文件来启动bootchart的,所以创建一个这个文件即可启动

二、设置收集数据时间
这个在源码中没有找到相应的说明,按照网上说的6.0之后做法,试了下没看出效果

创建/data/bootchart/start文件存储bootchart采样时间(s)
adb shell ‘echo 120 > /data/bootchart/start’
三、生成图表
启用bootchart并且设置好时间后,重启机器,会在/data/bootchart下生成log文件,从源码中可以看到当前会生成哪些日志文件。除了这个log文件之外还会生成一个header文件,也是必须的。

static void bootchart_thread_main() {
LOG(INFO) << “Bootcharting started”;

// Open log files.
auto stat_log = fopen_unique(“/data/bootchart/proc_stat.log”, “we”);
if (!stat_log) return;
auto proc_log = fopen_unique(“/data/bootchart/proc_ps.log”, “we”);
if (!proc_log) return;
auto disk_log = fopen_unique(“/data/bootchart/proc_diskstats.log”, “we”);
if (!disk_log) return;

……

}

然后利用bootchart工具把这些日志转成png图片。

1.使用系统自带脚本生成png图片
执行 /system/core/init/grab-bootchart.sh 自动生成png图片
(没试过,这个手机要连接服务器电脑才行,看脚本猜测的)

2.使用bootchart.jar处理日志压缩文件
从官网下载bootchart源码,放到ubuntu目录下,执行

# http://www.bootchart.org/download.html bootchart官网地址
ant

生成bootchart.jar
将手机 /data/bootchart/ 下文件 (header proc_diskstats.log proc_ps.log proc_stat.log) pull出来copy到服务器,执行下压缩命令生成bootchart.tgz

busybox tar zcvf bootchart.tgz header proc_diskstats.log proc_ps.log proc_stat.log

使用jar转成png图

java -jar bootchart.jar bootchart/bootchart.tgz

%title插图%num

服务器-IP 子网掩码 网关 DNS 梳理

IP
按照TCP/IP(Transport Control Protocol/Internet Protocol,传输控制协议/Internet协议)协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是一串很长的数字,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多。

子网掩码
是为了让目标主机识别ip地址的网络号部分和 机号部分

子网掩码从表面意义上来看就是用来划分子网的。因为一个局域网可大可小,如果太大两台主机通信的成本就比较高,比如要走网关,路由等等。但是划分了一个个的子网,如果两台主机处在同一个网段,那就不需要走网关和路由,直接查询ARP表就可以通信,所以掩码的作用就是为了解决寻址浪费的问题。

在局域网中相同子网掩码的电脑是可以互相通信的,不然就不能

IP段:
A类IP段 0.0.0.0 到127.255.255.255
B类IP段  128.0.0.0 到191.255.255.255
C类IP段  192.0.0.0 到223.255.255.255
子网掩码:
A类的默认子网掩码 255.0.0.0     一个子网*多可以容纳1677万多台电脑
B类的默认子网掩码 255.255.0.0    一个子网*多可以容纳6万台电脑
C类的默认子网掩码 255.255.255.0   一个子网*多可以容纳254台电脑

** A类子网掩码转化为二进制就为:11111111.00000000.00000000.00000000 所有1的表示网络位,其他的0表示主机位,那么127.255.255.255 127就是网络位 后面的255.255.255就表示主机位 **

把子网掩码切换至二进制,我们会发现,所有的子网掩码是由一串连续的1和一串连续的0组成的
(一共4段,每段8位,一共32位数)
ex:
11111111.11111111.11111000.00000000 也是正确的子网掩码

子网掩码决定的是一个子网的计算机数目 1决定的是网络号标识 0决定的是*大主机数 2的0的个数次方就是该子网掩码下*大可容纳的主机数

很经典的案例一
假设一个公司有530台电脑,组成一个局域网,子网掩码设多少*合适?

首先,无疑,530台电脑用B类IP*合适(A类不用说了,太多,C类又不够,肯定是B类),但是B类默认的子网掩码是255.255.0.0,可以容纳6万台电脑,显然不太合适,那子网掩码设多少合适呢?我们先来列个公式。

首先先来看一下这两个子网掩码转换成二进制是什么样子:
255.255.255.0 11111111.11111111.11111111.00000000
255.255.255.192 11111111.11111111.11111111.11000000
255.255.255.0(系统默认的子网掩码)可以容纳2的8次方台电脑,也就是256台。
255.255.255.192(用户手动设置的子网掩码)可以容纳2的6次方台电脑,也就是64台电脑。

2的m次方=560

首先,我们确定2一定是大于8次方的,因为我们知道2的8次方是256,也就是C类IP的*大容纳电脑的数目,我们从9次方一个一个试2的9次方是512,不到560,2的10次方是1024,看来2的10次方*合适了。子网掩码一共由32位组成,已确定后面10位是0了,那前面的22位就是1,*合适的子网掩码就是:11111111.11111111.11111100.00000000,转换成10进制,那就是255.255.252.0。

所以*后就为2的10次方可以满足560台电脑的需求

经典案例二
有两台均安装XP的PC,其IP设置分别为:
IP1:192.168.1.2(子网掩码:255.255.255.0),
IP2:192.168.1.3(子网掩码:255.255.255.0),
以上两个地址的网关都是:192.168.1.1,两台电脑首先通过SWITCH连接,在以上掩码时,彼此能相互PING通。这也说明SWITCH和网线以及有关PC设置的正确:
问题1、为何将PC1的P1对应的掩码改为255.255.0.0(PC2的IP2设置保持不变),按照理论,IP1同未改设置的IP2应当不是一个网段的了,可为何还能PING通呢?俺甚至去掉了网关部分的设置,仍然可以彼此PING通;重新启动以后也可以。

255.255.255.0改为255.255.0.0是掩码增大,IP段范围扩大到
192.168.0.0~192.168.254.254

PC2的网段显然是处于上面的网段,所以PC1,PC2同属于广播域。相互可以收发彼此的ARP广播。

因为设定的两个IP地址太近了,可以设PC1是192.168.1.1;PC2是192.168.1.129;掩码修正为255.255.255.128,就是两个网络了。

网关
网关实质上是一个网络通向其他网络的IP地址。比如有网络A和网络B,网络A的IP地址范围为“192.168.1.1~192. 168.1.254”,子网掩码为255.255.255.0;网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0。在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机(如附图所示)。网络A向网络B转发数据包的过程。
所以说,只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。那么这个IP地址是哪台机器的IP地址呢?网关的IP地址是具有路由功能的设备的IP地址,具有路由功能的设备有路由器、启用了路由协议的服务器(实质上相当于一台路由器)、代理服务器(也相当于一台路由器)。

在同一个内网(局域网),如果网很小,一般都是一个网段,相互之间访问是不需要设网关。

如果局域网比较大,电脑稍微多一些,而且有重要数据,重要服务器之类,
一般是根据工作需求不同、电脑重要安全要求不同划分网段和VLAN的,网段不同
,相互之间访问就需要设网关。
一个网段内的机器相互之间访问是不需要设网关的

DNS
为什么需要DNS解析域名为IP地址?
网络通讯大部分是基于TCP/IP的,而TCP/IP是基于IP地址的,所以计算机在网络上进行通讯时只能识别如“202.96.134.133”之类的IP地址,而不能认识域名。我们无法记住10个以上IP地址的网站,所以我们访问网站时,更多的是在浏览器地址栏中输入域名,就能看到所需要的页面,这是因为有一个叫“DNS服务器”的计算机自动把我们的域名“翻译”成了相应的IP地址,然后调出IP地址所对应的网页。

具体什么是DNS?
DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。DNS就是这样的一位“翻译官”,它的基本工作原理可用以下来表示。

www.qq.com->(dns) 119.21.31.2
1
DNS的详细过程参考:https://www.zhihu.com/question/23042131
大致流程就是:
浏览器输入域名www.baidu.com 先查询本地是否有对应的域名的ip
1.本地客户端查询
(浏览器缓存 系统缓存 路由缓存 )
2.互联网服务商查询
(ISP(互联网服务提供商)DNS缓存ex:电信的dns缓存),还是没有的话就去请求dns服务器获取对应ip 浏览器
3.根域名服务器

当以上均未完成,则进入根服务器进行查询。全球仅有13台根域名服务器,1个主根域名服务器,其余12为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器IP告诉本地DNS服务器)
3.1顶级域名服务器
顶级域名服务器收到请求后查看区域文件记录,若无则将其管辖范围内主域名服务器的IP地址告诉本地DNS服务器 、
3.2主域名服务器
主域名服务器接受到请求后查询自己的缓存,如果没有则进入下一级域名服务器进行查找,并重复该步骤直至找到正确纪录;
4.保存结果至缓存
本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端通过这个IP地址与web服务器建立链接。然后就可以得到页面了

什么是路由器
路由器(Router,又称路径器)是一种计算机网络设备,它能将数据通过打包一个个网络传送至目的地(选择数据的传输路径),这个过程称为路由。路由器就是连接两个以上网络的设备,路由工作在OSI模型的第三层——即网络层。

路由器WAN接口连接的是外网,拉进来的网线就是接这个接口。
路由器LAN接口是连接的内网,家里如有几台设备需要拉线上网都是从这个接口接出去的。(一般有多个)

怎么分网段
设置LAN口的IP开始地址 结束地址 子网掩码 网关 就可以拆分网段

Android系统启动流程之解析init进程启动过程

前言
作为“Android框架层”这个大系列中的*个系列,我们首先要了解的是Android系统启动流程,在这个流程中会涉及到很多重要的知识点,这个系列我们就来一一讲解它们,这一篇我们就来学习init进程。

1.init简介
init进程是Android系统中用户空间的*个进程,作为*个进程,它被赋予了很多*其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init。本文将基于Android7.0源码来分析Init进程。

2.引入init进程
说到init进程,首先要提到Android系统启动流程的前几步:
1.启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。
2.引导程序Bootloader
引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
3.linux内核启动
内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的*个进程。
4.init进程启动

讲到第四步就发现我们这一节要讲的init进程了。关于Android系统启动流程的所有步骤会在本系列的*后一篇做讲解。

3.init入口函数
init的入口函数为main,代码如下所示。
system/core/init/init.cpp

int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), “ueventd”)) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), “watchdogd”)) {
return watchdogd_main(argc, argv);
}
umask(0);
add_environment(“PATH”, _PATH_DEFPATH);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], “–second-stage”) != 0);
//创建文件并挂载
if (is_first_stage) {
mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount(“proc”, “/proc”, “proc”, 0, “hidepid=2,gid=” MAKE_STR(AID_READPROC));
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);
}
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE(“init %s started!\n”, is_first_stage ? “first stage” : “second stage”);
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open(“/dev/.booting”, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//初始化属性相关资源
property_init();//1
process_kernel_dt();
process_kernel_cmdline();
export_kernel_boot_props();
}

//启动属性服务
start_property_service();//2
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
Parser& parser = Parser::GetInstance();
parser.AddSectionParser(“service”,std::make_unique<ServiceParser>());
parser.AddSectionParser(“on”, std::make_unique<ActionParser>());
parser.AddSectionParser(“import”, std::make_unique<ImportParser>());
//解析init.rc配置文件
parser.ParseConfig(“/init.rc”);//3

while (true) {
if (!waiting_for_exec) {
am.ExecuteOneCommand();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (am.HasMoreCommands()) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR(“epoll_wait failed: %s\n”, strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}

init的main方法做了很多事情,我们只需要关注主要的几点,在注释1处调用 property_init来对属性进行初始化并在注释2处的 调用start_property_service启动属性服务,关于属性服务,后面会讲到。注释3处 parser.ParseConfig(“/init.rc”)用来解析init.rc。解析init.rc的文件为system/core/init/init_parse.cpp文件,接下来我们查看init.rc里做了什么。

4.init.rc
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,它主要包含五种类型语句:
Action、Commands、Services、Options和Import。init.rc的配置代码如下所示。
system/core/rootdir/init.rc

on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom

on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
# set RLIMIT_NICE to allow priorities from 19 to -20
setrlimit 13 40 40

这里只截取了一部分代码,其中#是注释符号。on init和on boot是Action类型语句,它的格式为:

on <trigger> [&& <trigger>]* //设置触发器
<command>
<command> //动作触发之后要执行的命令

为了分析如何创建zygote,我们主要查看Services类型语句,它的格式如下所示:

service <name> <pathname> [ <argument> ]* //<service的名字><执行程序路径><传递参数>
<option> //option是service的修饰词,影响什么时候、如何启动services
<option>

需要注意的是在Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。我们要分析的zygote服务的启动脚本则在init.zygoteXX.rc中定义,这里拿64位处理器为例,init.zygote64.rc的代码如下所示。
system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks

其中service用于通知init进程创建名zygote的进程,这个zygote进程执行程序的路径为/system/bin/app_process64,后面的则是要传给app_process64的参数。class main指的是zygote的class name为main,后文会用到它。

5.解析service
接下来我们来解析service,会用到两个函数,一个是ParseSection,它会解析service的rc文件,比如上文讲到的init.zygote64.rc,ParseSection函数主要用来搭建service的架子。另一个是ParseLineSection,用于解析子项。代码如下所示。
system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
std::string* err) {
if (args.size() < 3) {
*err = “services must have a name and a program”;
return false;
}
const std::string& name = args[1];
if (!IsValidName(name)) {
*err = StringPrintf(“invalid service name ‘%s'”, name.c_str());
return false;
}
std::vector<std::string> str_args(args.begin() + 2, args.end());
service_ = std::make_unique<Service>(name, “default”, str_args);//1
return true;
}

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
const std::string& filename, int line,
std::string* err) const {
return service_ ? service_->HandleLine(args, err) : false;
}

注释1处,根据参数,构造出一个service对象,它的classname为”default”。当解析完毕时会调用EndSection:

void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}

接着查看AddService做了什么:

void ServiceManager::AddService(std::unique_ptr<Service> service) {
Service* old_service = FindServiceByName(service->name());
if (old_service) {
ERROR(“ignored duplicate definition of service ‘%s'”,
service->name().c_str());
return;
}
services_.emplace_back(std::move(service));//1
}

注释1处的代码将service对象加入到services链表中。上面的解析过程总体来讲就是根据参数创建出service对象,然后根据选项域的内容填充service对象,*后将service对象加入到vector类型的services链表中。,

6.init启动zygote
讲完了解析service,接下来该讲init是如何启动service,在这里我们主要讲解启动zygote这个service。在zygote的启动脚本中我们得知zygote的class name为main。在init.rc有如下配置代码:
system/core/rootdir/init.rc


on nonencrypted
# A/B update verifier that marks a successful boot.
exec – root — /system/bin/update_verifier nonencrypted
class_start main
class_start late_start

其中class_start是一个COMMAND,对应的函数为do_class_start。我们知道main指的就是zygote,因此class_start main用来启动zygote。do_class_start函数在builtins.cpp中定义,如下所示。

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
ServiceManager::GetInstance().
ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
return 0;
}

来查看StartIfNotDisabled做了什么:
system/core/init/service.cpp

bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;
}

接着查看Start方法,如下所示。

bool Service::Start() {
flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
time_started_ = 0;
if (flags_ & SVC_RUNNING) {//如果Service已经运行,则不启动
return false;
}
bool needs_console = (flags_ & SVC_CONSOLE);
if (needs_console && !have_console) {
ERROR(“service ‘%s’ requires console\n”, name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}
//判断需要启动的Service的对应的执行文件是否存在,不存在则不启动该Service
struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
ERROR(“cannot find ‘%s’ (%s), disabling ‘%s’\n”,
args_[0].c_str(), strerror(errno), name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}


pid_t pid = fork();//1.fork函数创建子进程
if (pid == 0) {//运行在子进程中
umask(077);
for (const auto& ei : envvars_) {
add_environment(ei.name.c_str(), ei.value.c_str());
}
for (const auto& si : sockets_) {
int socket_type = ((si.type == “stream” ? SOCK_STREAM :
(si.type == “dgram” ? SOCK_DGRAM :
SOCK_SEQPACKET)));
const char* socketcon =
!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();

int s = create_socket(si.name.c_str(), socket_type, si.perm,
si.uid, si.gid, socketcon);
if (s >= 0) {
PublishSocket(si.name, s);
}
}

//2.通过execve执行程序
if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
ERROR(“cannot execve(‘%s’): %s\n”, args_[0].c_str(), strerror(errno));
}

_exit(127);
}

return true;
}

通过注释1和2的代码,我们得知在Start方法中调用fork函数来创建子进程,并在子进程中调用execve执行system/bin/app_process,这样就会进入framework/cmds/app_process/app_main.cpp的main函数,如下所示。
frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{

if (zygote) {
runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);//1
} else if (className) {
runtime.start(“com.android.internal.os.RuntimeInit”, args, zygote);
} else {
fprintf(stderr, “Error: no class name or –zygote supplied.\n”);
app_usage();
LOG_ALWAYS_FATAL(“app_process: no class name or –zygote supplied.”);
return 10;
}
}

从注释1处的代码可以得知调用runtime(AppRuntime)的start来启动zygote。

7.属性服务
Windows平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,它还是能够根据之前在注册表中的记录,进行相应的初始化工作。Android也提供了一个类似的机制,叫做属性服务。
在本文的开始,我们提到在init.cpp代码中和属性服务相关的代码有:
system/core/init/init.cpp

property_init();
start_property_service();

这两句代码用来初始化属性服务配置并启动属性服务。首先我们来学习服务配置的初始化和启动。

属性服务初始化与启动
property_init函数具体实现的代码如下所示。
system/core/init/property_service.cpp

void property_init() {
if (__system_property_area_init()) {
ERROR(“Failed to initialize property area\n”);
exit(1);
}
}

__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:

void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);//1
if (property_set_fd == -1) {
ERROR(“start_property_service socket creation failed: %s\n”, strerror(errno));
exit(1);
}
listen(property_set_fd, 8);//2
register_epoll_handler(property_set_fd, handle_property_set_fd);//3
}

注释1处用来创建非阻塞的socket。注释2处调用listen函数对property_set_fd进行监听,这样创建的socket就成为了server,也就是属性服务;listen函数的第二个参数设置8意味着属性服务*多可以同时为8个试图设置属性的用户提供服务。注释3处的代码将property_set_fd放入了epoll句柄中,用epoll来监听property_set_fd:当property_set_fd中有数据到来时,init进程将用handle_property_set_fd函数进行处理。
在linux新的内核中,epoll用来替换select,epoll*大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

属性服务处理请求
从上文我们得知,属性服务接收到客户端的请求时,会调用handle_property_set_fd函数进行处理:
system/core/init/property_service.cpp

static void handle_property_set_fd()
{

if(memcmp(msg.name,”ctl.”,4) == 0) {
close(s);
if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR(“sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n”,
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
//检查客户端进程权限
if (check_mac_perms(msg.name, source_ctx, &cr)) {//1
property_set((char*) msg.name, (char*) msg.value);//2
} else {
ERROR(“sys_prop: permission denied uid:%d name:%s\n”,
cr.uid, msg.name);
}
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}

注释1处的代码用来检查客户端进程权限,在注释2处则调用property_set函数对属性进行修改,代码如下所示。

int property_set(const char* name, const char* value) {
int rc = property_set_impl(name, value);
if (rc == -1) {
ERROR(“property_set(\”%s\”, \”%s\”) failed\n”, name, value);
}
return rc;
}

property_set函数主要调用了property_set_impl函数:

static int property_set_impl(const char* name, const char* value) {
size_t namelen = strlen(name);
size_t valuelen = strlen(value);
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;
if (strcmp(“selinux.reload_policy”, name) == 0 && strcmp(“1”, value) == 0) {
if (selinux_reload_policy() != 0) {
ERROR(“Failed to reload policy\n”);
}
} else if (strcmp(“selinux.restorecon_recursive”, name) == 0 && valuelen > 0) {
if (restorecon_recursive(value) != 0) {
ERROR(“Failed to restorecon_recursive %s\n”, value);
}
}
//从属性存储空间查找该属性
prop_info* pi = (prop_info*) __system_property_find(name);
//如果属性存在
if(pi != 0) {
//如果属性以”ro.”开头,则表示是只读,不能修改,直接返回
if(!strncmp(name, “ro.”, 3)) return -1;
//更新属性值
__system_property_update(pi, value, valuelen);
} else {
//如果属性不存在则添加该属性
int rc = __system_property_add(name, namelen, value, valuelen);
if (rc < 0) {
return rc;
}
}
/* If name starts with “net.” treat as a DNS property. */
if (strncmp(“net.”, name, strlen(“net.”)) == 0) {
if (strcmp(“net.change”, name) == 0) {
return 0;
}
//以net.开头的属性名称更新后,需要将属性名称写入net.change中
property_set(“net.change”, name);
} else if (persistent_properties_loaded &&
strncmp(“persist.”, name, strlen(“persist.”)) == 0) {
/*
* Don’t write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
}
property_changed(name, value);
return 0;
}

property_set_impl函数主要用来对属性进行修改,并对以ro、net和persist开头的属性进行相应的处理。到这里,属性服务处理请求的源码就讲到这。

8.init进程总结
讲到这,总结起来init进程主要做了三件事:
1.创建一些文件夹并挂载设备
2.初始化和启动属性服务
3.解析init.rc配置文件并启动zygote进程

深入理解Android启动过程

当按下Android设备上的电源键时发生了什么?

Android的启动过程是怎样的?

什么是linux内核?

桌面系统的linux内核和Android系统的linux内核之间有什么不同?

什么是Bootloader?

什么是Zygote?

什么是x86和ARM linux?

什么是init.rc?

什么是系统服务?

 

当我们在思考Android启动过程的时候,脑海中总是会浮现出这么多的问题。

 

在这里我将为你解释Android的启动过程,希望能帮助你找到以上这些问题的答案。

 

Android是一个基于linux内核的开源操作系统,x86(x86是指一系列计算机微处理器指令集架构,这些指令集架构基于intel 8086 CPU)是linux内核部署*常见的系统。然而,所有的Android设备都运行在ARM处理器(ARM(来源于高级精简指令集机器,而这又来源于ARM架构))上,当然,英特尔的Xolo设备(http://xolo.in/xolo-x900-features)除外,Xolo来源于Atom 1.6GHz x86处理器。Android设备(或者嵌入式设备,又或者基于linux的ARM设备)的启动过程与桌面版本相比有一点差别。这篇文章中,我将只解释 Android设备的启动过程。关于基于linux的桌面系统的启动过程,推荐大家阅读Inside the linux boot process这篇文章。

 

当你按下电源开关后,Android设备执行了以下步骤

%title插图%num    %title插图%num

*步:开机和系统启动

当电源按下,引导芯片代码从预定义的地方(固化在ROM)开始执行。加载Bootloader到RAM,然后执行。

 

第二步:Bootloader

Bootloader是在Android操作系统运行之前运行的一个小程序。Bootloader是运行的*个程序,所以它是针对特定的主板与芯片的。设备制造商要么使用比较流行的的Bootloader,比如redboot、uboot、qi bootloader,要么开发自己的Bootloader,它不属于Android操作系统的部分。Bootloader是OEM厂商或者运营商加锁和限制的地方。

Bootloader分两个阶段执行。*个阶段,检测外部RAM以及加载对第二阶段有用的程序,第二阶段,Bootloader设置网络、内存等等。这些对于运行内核是必要的,为了某些特定的目标,Bootloader可以向内核提供配置参数或者输入数据。

Android的Bootloader可以在<Android Source>\bootable\bootloader\legacy\usbloader目录找到。传统的加载器包含两个重要的文件,需要在这里说明:

1. init.s — 初始化堆栈,清零BBS段,调用main.c中的_main()函数

2. main.c — 初始化硬件(闹钟、主板、键盘、控制台),创建linux标签

要了解更多关于Android Bootloader的知识可以参考这个连接:

https://motorola-global-portal.custhelp.com/app/answers/detail/a_id/86208/~/bootloader-frequently-asked-questions

 

第三步:内核

Android内核的启动与桌面linux内核启动的方式类似。内核启动时开始设置高速缓存、内存保护机制、调度,加载驱动程序。当内核完成系统设置后,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的*个进程。

 

第四步:init进程

init是*个进程,我们可以管它叫root进程或者所有进程的父进程。init进程有两个任务,1. 挂载目录,比如/sys、/dev、/proc。2. 运行init.rc脚本。

· init进程可以在<Android Source>/system/core/init找到

· init.rc文件可以在<Android Source>/system/core/rootdir/init.rc找到

· readme.txt可以在<Android Source>/system/core/init/readme.txt找到

对于init.rc文件,Android中有特定的格式和规则。在Android中,我们称之为Android初始化语言。
Android初始化语言由四大类型的声明组成,分别是:Actions(动作)、Commands(命令)、Services(服务)、以及Options(选项)。
Action(动作):Action(动作)就是一些命名的命令序列,Action(动作)有一个触发器,用来决定这个Action(动作)何时发生。
语法如下

  1. n <trigger>
  2. <command>
  3. <command>
  4. <command>

Service(服务):Service(服务)是一些由init进程启动的程序、当这些Service(服务)退出时init进程会根据情况将其重启。

语法如下

  1. service <name> <pathname> [<argument>]*
  2. <option>
  3. <option>

 

Options(选项):Options(选项)是对服务的描述。它们影响init进程如何以及何时启动服务。

让我们看看默认的init.rc文件。这里我只列出了主要的事件以及服务。

Action/Service 描述
on early-init 设置init进程以及它创建的子进程的优先级。

为init进程设置安全环境。

on init 设置全局环境

为cpu accounting创建cgroup(资源控制)挂载点 ……

on fs 挂载mtd分区
on post-fs 更改系统目录的访问权限
on post-fs-data 更改/data目录及其子目录的访问权限
on boot 基本网络的初始化,内存管理等等
Service servicemanager 启动系统的服务管理器来管理所有的本地服务,比如位置、音频、Shared preference等等
service zygote 启动zygote进程

在这个阶段你就可以在设备的屏幕上看到“Android”logo了。

 

第五步:Zygote和Dalvik

在Java中,我们知道不同的虚拟机实例会为不同的应用分配不同的内存。假如Android应用应该尽可能快地启动,如果Android系统为每一个应用启动不同的Dalvik虚拟机实例,就会消耗大量的内存和时间。因此,为了克服这个问题,Android系统创造了”Zygote”。Zygote使得Dalvik虚拟机共享代码、低内存占用和*小的启动时间成为可能。Zygote是一个虚拟机进程,正如我们在前一个步骤所说,它在系统启动时启动。 Zygote预加载并且初始化核心类库。通常,这些核心类都是只读的,是Android SDK或者核心框架的一部分。在Java虚拟机中,每一个实例都有它自己的核心库类文件和堆对象的拷贝。

Zygote加载进程

1. 加载ZygoteInit类,源代码:<Android Source>/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

2. registerZygoteSocket() — 为zygote命令连接注册一个服务器套接字

3. preloadClasses()—“preloaded-classes”是一个简单的包含一系列需要预加载类的文本文件,你可以在<Android Source>/frameworks/base找到“preloaded-classes”文件。

4. preloadResources() — preloadResources意味着本地主题和布局以及android.R文件中包含的所有东西都会用这个方法加载。

在这个时候,你可以看到开机动画。

 

第六步:系统服务或服务

完成了上面几步之后,Runtime请求Zygote启动系统服务。系统服务同时使用native以及java编写,系统服务可以认为是一个进程。同一个系统服务可以以System Services的形式在Android SDK获得。系统服务包含了所有的System Services。

Zygote创建新的进程去启动系统服务。你可以在ZygoteInit类的”startSystemServer”方法中找到源代码。

核心服务:

1. 启动电源管理器

2. 创建Activity管理器

3. 启动电话注册

4. 启动包管理器

5. 设置Activity管理服务为系统进程

6. 启动上下文管理器

7. 启动系统Context Providers

8. 启动电池服务

9. 启动定时管理器

10. 启动传感服务

11. 启动窗口管理器

12. 启动蓝牙服务

13. 启动挂载服务

其他服务:

1. 启动状态栏服务

2. 启动硬件服务

3. 启动网络状态服务

4. 启动连接服务

5. 启动通知管理器

6. 启动设备存储监视服务

7. 启动定位管理器

8. 启动搜索服务

9. 启动剪切板服务

10. 启动登记服务

11. 启动壁纸服务

12. 启动音频服务

13. 启动耳机监听

14. 启动AdbSettingsObserver(处理adb命令)。

 

第七步:启动完成

一旦系统服务在内存中运行起来,Android就完成了启动过程。在这个时候“ACTION_BOOT_COMPLETED”广播就会发出去。

Android系统启动流程分析

本文讲解Android系统在启动过程中的关键动作,摈弃特定平台之间的差异,讨论共性的部分,至于启动更加详细的过程,需要结合代码分析,这里给出流程框架,旨在让大家对开机过程更明了。各个平台启动流程基本类似,但代码追踪却有较大区别。高通,MTK,Sprd各有不同处理,均有各自的一套源码,本文代码以展讯平台SC7710系列Android4.1源码进行追踪。

1,Android启动概述
Android系统启动基本可分为3个阶段:Bootloader启动,linux启动,Android启动。

1.1,Bootloader启动
系统引导bootloader(bootable/bootloader/* u-boot/*),加电后,CPU先执行bootloader程序,正常启动系统,加载boot.img,中包含内核。

源码:bootable/bootloader/* , 说明:加电后,CPU将先执行bootloader程序,此处有三种选择:
a: 开机按Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写
b: 开机按Home+Power启动到recovery模式,加载recovery.img,recovery.img包含内核,基本的文件系统,用于工程模式的烧写
c:开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况)
1.2,linux启动
由bootloader加载kernel,kernel经自解压,初始化,载入built-in驱动程序,完成启动。kernel启动后会创建若干内核线程,之后装入并执行程序/sbin/init/,载入init process,切换至user-space。

1.3,Android启动
1.3.1,init进程启动
源码:system/core/init/*
配置文件:system/rootdir/init.rc
说明:init是一个由内核启动的用户级进程,它按照init.rc中的设置执行:启动服务(这里的服务指linux底层服务,如adbd提供adb支持,vold提供SD卡挂载等),执行命令和按其中的配置语句执行相应功能。

1.3.2,zygote服务启动
源码:frameworks/base/cmds/app_main.cpp等。
说明:zygote是一个在init.rc中被指定启动的服务,该服务对应的命令是/system/bin/app_process。
作用:建立Java Runtime,建立虚拟机;建立Socket接收ActivityManangerService的请求,用于Fork应用程序;启动System Server。

1.3.3,systemserver服务启动
源码:frameworks/base/services/java/com/android/server/SystemServer.java
说明:被zygote启动,通过System Manager管理android的服务(这里的服务指frameworks/base/services下的服务,如卫星定位服务,剪切板服务等)。

1.3.4,launcher桌面启动
源码:ActivityManagerService.java为入口,packages/apps/launcher*实现。
说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,*终调用startHomeActivityLocked()启launcher。

1.3.5,lockscreen启动
源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,*终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。

1.3.6,othersapp启动
源码:frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
说明:系统启动成功后SystemServer调用ActivityManagerNative.getDefault().systemReady()通知ActivityManager启动成功,ActivityManager会通过置变量mBooting,通知它的另一线程,该线程会发送广播android.intent.action.BOOT_COMPLETED以告知已注册的第三方程序在开机时自动启动。

2,bootloader启动详细分析
2.1,Bootloader的定义和种类
简单地说,BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适状态,为运行操作系统做好准备。这样描述是比较抽象的,但是它的任务确实不多,终*目标就是把OS拉起来运行。在嵌入式系统世界里存在各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。
先区分一下Bootloader和Monitor[l1] : 严格来说,Bootloader只是引导OS运行起来的代码;而Monitor另外还提供了很多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。在开发过程中Monitor提供了很好地调试功能,不过在开发结束之后,可以完全将其设置成一个Bootloader。所以习惯上将其叫做Bootloader。

%title插图%num

更多bootloader还有:ROLO、Etherboot、ARMboot 、LinuxBIOS等。

对于每种体系结构,都有一系列开放源码Bootloader可以选用:

X86:X86的工作站和服务器上一般使用LILO和GRUB。

ARM:*早有为ARM720处理器开发板所做的固件,又有了armboot,StrongARM平台的blob,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader。

PowerPC:*早使用于ppcboot,不过现在大多数直接使用U-boot。

MIPS:*早都是MIPS开发商自己写的bootloader,不过现在U-boot也支持MIPS架构。

M68K:Redboot能够支持m68k系列的系统。

2.2,Arm特定平台的bootloader
到目前为止,我们公司已经做过多个Arm平台的android方案,包括:marvell(pxa935)、informax(im9815)、mediatek(mt6516/6517)、broadcom(bcm2157)。由于不同处理器芯片厂商对arm core的封装差异比较大,所以不同的arm处理器,对于上电引导都是由特定处理器芯片厂商自己开发的程序,这个上电引导程序通常比较简单,会初始化硬件,提供下载模式等,然后才会加载通常的bootloader。

下面是几个arm平台的bootloader方案:

marvell(pxa935) :                bootROM + OBM [l4] + BLOB

informax(im9815) :             bootROM + barbox + U-boot

mediatek(mt6516/6517) :     bootROM + pre-loader[l5]  + U-boot

broadcom(bcm2157) :          bootROM + boot1/boot2 + U-boot

为了明确U-boot之前的两个loader的作用,下面以broadcom平台为例,看下在上电之后到U-boot的流程,如图1.2.1:

%title插图%num

 

2.3,uboot启动流程详解
*常用的bootloader还是U-boot,可以引导多种操作系统,支持多种架构的CPU。它支持的操作系统有:Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持的CPU架构有:ARM、PowerPC、MISP、X86、NIOS、Xscale等。手机系统不像其他的嵌入式系统,它还需要在启动的过程中关心CP的启动,这个时候就涉及到CP的image和唤醒时刻,而一般的嵌入式系统的uboot只负责引导OS内核。所以这里我们也暂不关心CP的启动,而主要关心AP侧。

从上面第二小节中可以看出,bootloader通常都包含有处理器厂商开发的上电引导程序,不过也不是所有的处理都是这样,比如三星的S3C24X0系列,它的bootROM直接跳到U-boot中执行,首先由bootROM将U-boot的前4KB拷贝到处理器ISRAM,接着在U-boot的前4KB中必须保证要完成的两项主要工作:初始化DDR,nand和nand控制器,接着将U-boot剩余的code拷贝到SDRAM中,然后跳到SDRAM的对应地址上去继续跑U-boot。

所以U-boot的启动过程,大致上可以分成两个阶段:*阶段,汇编代码;第二阶段,c代码。

2.3.1,汇编代码阶段
U-boot的启动由u-boot/arch/arm/cpu/xxx/u-boot.lds开始,其引导调用u-boot/arch/arm/cpu/xxx/start.S。u-boot.lds:

OUTPUT_FORMAT(“elf32-littlearm”, “elf32-littlearm”, “elf32-littlearm”)
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;

. = ALIGN(4);
.text :
{
arch/arm/cpu/arm920t/start.o (.text)//调用对应的start.S,start.o由start.S编译生成
*(.text)
}

. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

. = ALIGN(4);
.data : {
*(.data)
}

. = ALIGN(4);

. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;

. = ALIGN(4);

.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}

.dynsym : {
__dynsym_start = .;
*(.dynsym)
}

.bss __rel_dyn_start (OVERLAY) : {
__bss_start = .;
*(.bss)
. = ALIGN(4);
_end = .;
}

/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
对应的Makefile文件如下:

include $(TOPDIR)/config.mk

LIB = $(obj)lib$(CPU).o

START = start.o

COBJS-y += cpu.o
COBJS-$(CONFIG_USE_IRQ) += interrupts.o

SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS-y) $(SOBJS))
START := $(addprefix $(obj),$(START))

all: $(obj).depend $(START) $(LIB)

$(LIB): $(OBJS)
$(call cmd_link_o_target, $(OBJS))

#########################################################################

# defines $(obj).depend target
include $(SRCTREE)/rules.mk

sinclude $(obj).depend
所以U-boot的*条指令从u-boot/arch/arm/cpu/xxx/start.S文件开始,*阶段主要做了如下事情:

 

(1). 设置CPU进入SVC模式(系统管理模式),cpsr[4:0]=0xd3。

(2). 关中断,INTMSK=0xFFFFFFFF, INTSUBMSK=0x3FF。

(3). 关看门狗,WTCON=0x0。

(4). 调用s3c2410_cache_flush_all函数,使TLBS,I、D Cache,WB中数据失效。

(5). 时钟设置CLKDIVN=0x3 , FCLK:HCLK:PCLK = 1:2:4。

(6). 读取mp15的c1寄存器,将*高两位改成11,表示选择了异步时钟模型。

(7). 检查系统的复位状态,以确定是不是从睡眠唤醒。

#include <asm-offsets.h>
#include <common.h>
#include <config.h>

.globl _start
_start: b start_code
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq

……

//开始的一些初始化操作
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0

bl coloured_LED_init
bl red_LED_on

……

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //重点函数
#endif

/* Set stackpointer in internal RAM to call board_init_f */
/*board.c的board_init_f()函数*/
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f//board初始化

.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */

/* Set up the stack */
stack_setup:
mov sp, r4

adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r2, _TEXT_BASE
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */

copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop

#ifndef CONFIG_PRELOADER
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif

clear_bss:
#ifndef CONFIG_PRELOADER
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
ldr r3, _TEXT_BASE /* Text base */
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */

clbss_l:str r2, [r0] /* clear loop… */
add r0, r0, #4
cmp r0, r1
bne clbss_l

bl coloured_LED_init
bl red_LED_on
#endif

/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
ldr r0, _nand_boot_ofs
mov pc, r0

_nand_boot_ofs:
.word nand_boot
#else
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it … */
mov pc, lr
/*board_init_r 此处走至u-boot\arch\arm\lib\board.c的board_init_r()函数 */
_board_init_r_ofs:
.word board_init_r – _start
#endif
/*至此走至C代码的阶段*/
_rel_dyn_start_ofs:
.word __rel_dyn_start – _start
_rel_dyn_end_ofs:
.word __rel_dyn_end – _start
_dynsym_start_ofs:
.word __dynsym_start – _start

……

#endif
根据这几条语句来判断系统是从nand启动的还是直接将程序下载到SDRAM中运行的,这里涉及到运行时域 和位置无关代码的概念,ldr r0,_TEXT_BASE的作用是将config.mk文件中定义的TEXT_BASE值(0x33f80000)装载到r0中,adr r1,_start该指令是条伪指令,在编译的时候会被转换成ADD或SUB指令根据当前pc值计算出_start标号的地址,这样的话就可以知道当前程序在什么地址运行(位置无关代码:做成程序的所有指令都是相对寻址的指令,包括跳转指令等,这样代码就可以不在链接所指定的地址上运行)。在上电之后,系统从nand启动,这里得到r0和r1值是不一样的,r0=0x33f80000,而r1=0x00000000。所以接下来会执行cpu_init_crit函数。

cpu_init_crit函数,主要完成了两个工作:首先使ICache and Dcache,TLBs中早期内容失效,再设置p15 control register c1,关闭MMU,Dcache,但是打开了Icache和Fault checking,(要求mmu和Dcache是必须要关闭的,而Icache可以打开可以关闭);其次调用/board/nextdvr2410/memsetup.S文件中的memsetup函数来建立对SDRAM的访问时序。

Relocate函数,加载nand flash中的uboot到SDRAM中,代码会加载到0x33f80000开始的地址,空间大小是512。

//这里参考的是展讯平台7710的源代码,所以并无start_armboot函数,取而代之的是board_init_r函数。请知悉。

ldr pc, _start_armboot

_start_armboot: .word start_armboot

这里将会进入第二阶段的c代码部分:board_init_r()函数,/u-boot/arch/arm/lib/board.c。

2.3.2,C代码阶段
先看/u-boot/arch/arm/lib/board.c的board_init_r()函数:

void board_init_r (gd_t *id, ulong dest_addr)
{
……
/**一系列初始化操作之后 重点为do_cboot(NULL, 0, 1, NULL)和main_loop ()*/
board_init(); /* Setup chipselects */

boot_pwr_check();

#ifdef CONFIG_SERIAL_MULTI
serial_initialize();
#endif

debug (“Now running in RAM – U-Boot at: %08lx\n”, dest_addr);

#ifdef CONFIG_LOGBUFFER
logbuff_init_ptrs ();
#endif
#ifdef CONFIG_POST
post_output_backlog ();
#endif

/* The Malloc area is immediately below the monitor copy in DRAM */
malloc_start = dest_addr – TOTAL_MALLOC_LEN;
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(4);
#endif
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(5);
#endif
boot_pwr_check();

#if !defined(CONFIG_SYS_NO_FLASH)
puts (“FLASH: “);

if ((flash_size = flash_init ()) > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
print_size (flash_size, “”);
/*
* Compute and print flash CRC if flashchecksum is set to ‘y’
*
* NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
*/
s = getenv (“flashchecksum”);
if (s && (*s == ‘y’)) {
printf (” CRC: %08X”,
crc32 (0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)
);
}
putc (‘\n’);
# else /* !CONFIG_SYS_FLASH_CHECKSUM */
print_size (flash_size, “\n”);
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
} else {
puts (failed);
hang ();
}
#endif
boot_pwr_check();

#if !defined(CONFIG_EMMC_BOOT)
#if defined(CONFIG_CMD_NAND)
puts (“NAND: “);
ret = nand_init(); /* go init the NAND */
if (ret) {
puts (“NAND init error “);
while(1);
}
#endif
#endif

boot_pwr_check();
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(6);
#endif

#if defined(CONFIG_CMD_ONENAND)
#if !(defined CONFIG_TIGER && defined CONFIG_EMMC_BOOT)
onenand_init();
#endif
#endif

#ifdef CONFIG_GENERIC_MMC
puts(“MMC: “);
mmc_initialize(bd);
#endif

#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif

#ifdef CONFIG_EMMC_BOOT
mmc_legacy_init(1);
#endif
/* initialize environment */
env_relocate ();
boot_pwr_check();

#ifdef CONFIG_VFD
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */

/*tempaily use for tiger to avoid died as refreshing LCD*/

/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr (“ipaddr”);

stdio_init (); /* get the devices list going. */
boot_pwr_check();

jumptable_init ();
boot_pwr_check();

#if defined(CONFIG_API)
/* Initialize API */
api_init ();
#endif
char fake[4]=”fak”;
setenv(“splashimage”, fake);

console_init_r (); /* fully init console as a device */
boot_pwr_check();

#if defined(CONFIG_ARCH_MISC_INIT)
/* miscellaneous arch dependent initialisations */
arch_misc_init ();
#endif
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif

/* set up exceptions */
interrupt_init ();
/* enable exceptions */
enable_interrupts ();
boot_pwr_check();

/* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
/* XXX: this needs to be moved to board init */
if (getenv (“ethaddr”)) {
uchar enetaddr[6];
eth_getenv_enetaddr(“ethaddr”, enetaddr);
smc_set_mac_addr(enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

/* Initialize from environment */
if ((s = getenv (“loadaddr”)) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv (“bootfile”)) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
boot_pwr_check();
//usb_eth_initialize(NULL);
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
……

#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(11);
#endif
extern int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);
boot_pwr_check();

do_cboot(NULL, 0, 1, NULL);//重点操作函数,此处走至u-boot\property\cmd_cboot.c
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}

/* NOTREACHED – no way out of command loop except booting */
}

该段代码完成了一些设备的初始化do_cboot(NULL, 0, 1, NULL)和main_loop ()是此处重点函数,其中do_cboot(NULL, 0, 1, NULL)的实现在u-boot/property/cmd_cboot.c而main_loop()则是在u-boot/common/main.c中。先看u-boot/property/cmd_cboot.c。

int boot_pwr_check(void)
{
static int total_cnt = 0;
if(!power_button_pressed())
total_cnt ++;
return total_cnt;
}
#define mdelay(_ms) udelay(_ms*1000)

int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
uint32_t key_mode = 0;
uint32_t key_code = 0;
volatile int i;

if(argc > 2)
goto usage;

#ifdef CONFIG_SC8830
if(cali_file_check())
calibration_detect(2);
#endif
#ifdef CONFIG_SC7710G2
{
extern void set_cp_emc_pad(void);
set_cp_emc_pad();
}
#endif
CHG_Init();

#ifdef CONFIG_SC8830
DCDC_Cal_ArmCore();
//DCDC_Cal_All(0);
#endif

#ifdef CONFIG_AUTOBOOT
normal_mode();//如果down的是autopoweron的uboot,这里会直接去正常开机
#endif

#ifdef CONFIG_SC7710G2
if(!pbint2_connected())
normal_mode();
#endif

boot_pwr_check();

#ifdef CONFIG_SC8800G
CHG_ShutDown();
if(charger_connected()){
mdelay(10);
CHG_TurnOn();
}else{
//根据sp8810.h里的LOW_BAT_VOL,如果电压低于3.5V,则直接power down
if(is_bat_low()){
printf(“shut down again for low battery\n”);
power_down_devices();
while(1)
;
}
}
#else

#ifndef CONFIG_MACH_CORI
if(is_bat_low()){
printf(“shut down again for low battery\n”);
mdelay(10000);
power_down_devices();
while(1)
;
}
#endif
#endif

boot_pwr_check();
board_keypad_init();//初始化键盘
boot_pwr_check();

#ifdef CONFIG_SPRD_SYSDUMP
write_sysdump_before_boot();
#endif

int recovery_init(void);
int ret =0;
ret = recovery_init();
if(ret == 1){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
recovery_mode_without_update();
}else if(ret == 2){
#ifndef CONFIG_SC8830
try_update_modem(); //update img from mmc
#endif
normal_mode();
}

unsigned check_reboot_mode(void);
//获取寄存器里HW的rest标志位,得到当前的开机模式
//此处主要是异常重启,恢复出厂设置,关机闹钟等(没有按power键导致的开机)
unsigned rst_mode= check_reboot_mode();
//检查是否是recovery模式
if(rst_mode == RECOVERY_MODE){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
recovery_mode();
}
else if(rst_mode == FASTBOOT_MODE){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
fastboot_mode();
}else if(rst_mode == NORMAL_MODE){
normal_mode();
}else if(rst_mode == WATCHDOG_REBOOT){
watchdog_mode();
}else if(rst_mode == UNKNOW_REBOOT_MODE){
unknow_reboot_mode();
}else if(rst_mode == PANIC_REBOOT){
panic_reboot_mode();
}else if(rst_mode == ALARM_MODE){
int flag =alarm_flag_check();
if(flag == 1)
alarm_mode();
else if(flag == 2)
normal_mode();
}else if(rst_mode == SLEEP_MODE){
sleep_mode();
}else if(rst_mode == SPECIAL_MODE){
special_mode();
}else if(rst_mode == CALIBRATION_MODE){
calibration_detect(0);
}
#ifdef CONFIG_SC8810
// normal_mode();
#endif
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(charger_connected()){
DBG(“%s: charger connected\n”, __FUNCTION__);
#if defined (CONFIG_SP8810W) || defined(CONFIG_SC7710G2)
calibration_detect(1);
#endif
charge_mode();
}
//find the power up trigger
//如果按power键的“次数”达标了,认为这个是一次长按事件
else if(boot_pwr_check() >= get_pwr_key_cnt()){
DBG(“%s: power button press\n”, __FUNCTION__);
DBG(“boot_pwr_check=%d,get_pwr_key_cnt=%d\n”,boot_pwr_check(),get_pwr_key_cnt());
//go on to check other keys
mdelay(50);
for(i=0; i<10;i++){
key_code = board_key_scan();//获取另外一个按键
if(key_code != KEY_RESERVED)
break;
}
DBG(“key_code %d\n”, key_code);
//查找对应的按键码对应的开机模式
key_mode = check_key_boot(key_code);

switch(key_mode){
case BOOT_FASTBOOT:
fastboot_mode();
break;
case BOOT_RECOVERY:
recovery_mode();
break;
case BOOT_CALIBRATE:
engtest_mode();
return 0; //back to normal boot
break;
case BOOT_DLOADER:
dloader_mode();
break;
default:
break;//如果是正常开机模式,因为没有
}
}
else if(alarm_triggered() && alarm_flag_check()){
DBG(“%s: alarm triggered\n”, __FUNCTION__);
int flag =alarm_flag_check();

if(flag == 1){
//如果是闹钟触发导致的开机,则进入关机闹钟模式
alarm_mode();
}
else if(flag == 2){
normal_mode();//如果只按了power键。
}

}else{
#if BOOT_NATIVE_LINUX_MODEM
*(volatile u32*)CALIBRATION_FLAG = 0xca;
#endif
#if !defined (CONFIG_SC8830) && !defined(CONFIG_SC7710G2)
calibration_detect(0);
#endif
//if calibrate success, it will here
DBG(“%s: power done again\n”, __FUNCTION__);
power_down_devices();
while(1)
;
}

if(argc == 1){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
normal_mode();
return 1;
}

if(argc == 2){
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”normal”) == 0){
normal_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”recovery”) == 0){
recovery_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”fastboot”) == 0){
fastboot_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”dloader”) == 0){
dloader_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”charge”) == 0){
//如果没有按power键,且插入了充电器,则进入充电模式
charge_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”caliberation”) == 0){
calibration_detect(1);
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

usage:
cmd_usage(cmdtp);
return 1;
}
接下来分析正常开机的流程也就是normal_mode(),其他几种开机流程与normal_mode()类似,不再一一分析。android共提供了多种mode:

normal_mode()的实现在 u-boot/property/normal_mode.c:

void normal_mode(void)
{
#if defined (CONFIG_SC8810) || defined (CONFIG_SC8825) || defined (CONFIG_SC8830)
//MMU_Init(CONFIG_MMU_TABLE_ADDR);
vibrator_hw_init();//初始化马达
#endif
set_vibrator(1);//起震,这个就是开机震的那一下

#ifndef UART_CONSOLE_SUPPORT
#ifdef CONFIG_SC7710G2
extern int serial1_SwitchToModem(void);
serial1_SwitchToModem();
#endif
#endif

#if BOOT_NATIVE_LINUX
vlx_nand_boot(BOOT_PART, CONFIG_BOOTARGS, BACKLIGHT_ON);
#else
vlx_nand_boot(BOOT_PART, NULL, BACKLIGHT_ON);
#endif

}

*终将操作交给vlx_nand_boot(),其实现在u-boot/property/normal_nand_mode.c

void vlx_nand_boot(char * kernel_pname, char * cmdline, int backlight_set)
{
……
char *fixnvpoint = “/fixnv”;
char *fixnvfilename = “/fixnv/fixnv.bin”;
char *fixnvfilename2 = “/fixnv/fixnvchange.bin”;
char *backupfixnvpoint = “/backupfixnv”;
char *backupfixnvfilename = “/backupfixnv/fixnv.bin”;

char *runtimenvpoint = “/runtimenv”;
char *runtimenvpoint2 = “/runtimenv”;
char *runtimenvfilename = “/runtimenv/runtimenv.bin”;
char *runtimenvfilename2 = “/runtimenv/runtimenvbkup.bin”;

char *productinfopoint = “/productinfo”;
char *productinfofilename = “/productinfo/productinfo.bin”;
char *productinfofilename2 = “/productinfo/productinfobkup.bin”;

int orginal_right, backupfile_right;
unsigned long orginal_index, backupfile_index;
nand_erase_options_t opts;
char * mtdpart_def = NULL;
#if (defined CONFIG_SC8810) || (defined CONFIG_SC8825)
MMU_Init(CONFIG_MMU_TABLE_ADDR);
#endif
ret = mtdparts_init();
if (ret != 0){
printf(“mtdparts init error %d\n”, ret);
return;
}

#ifdef CONFIG_SPLASH_SCREEN
#define SPLASH_PART “boot_logo”
ret = find_dev_and_part(SPLASH_PART, &dev, &pnum, &part);
if(ret){
printf(“No partition named %s\n”, SPLASH_PART);
return;
}else if(dev->id->type != MTD_DEV_TYPE_NAND){
printf(“Partition %s not a NAND device\n”, SPLASH_PART);
return;
}
//读取下载到nand中的boot_logo,就是开机亮的那一屏
off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
size = 1<<19;//where the size come from????//和dowload工具中的地址一致
char * bmp_img = malloc(size);
if(!bmp_img){
printf(“not enough memory for splash image\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
//*次LCD logo
lcd_display_logo(backlight_set,(ulong)bmp_img,size);
#endif
set_vibrator(0);//停止震动,如果发现开机狂震不止,那就是没走到这里。
{
nand_block_info(nand, &good_blknum, &bad_blknum);
printf(“good is %d bad is %d\n”, good_blknum, bad_blknum);
}

ret = load_sector_to_memory(fixnvpoint,
fixnvfilename2,
fixnvfilename,
(unsigned char *)FIXNV_ADR,
(unsigned char *)MODEM_ADR,
FIXNV_SIZE + 4);
……

#elif defined(CONFIG_CALIBRATION_MODE_NEW)
#if defined(CONFIG_SP7702) || defined(CONFIG_SP8810W)
/*
force dsp sleep in native 8810 verson to reduce power consumption
*/
extern void DSP_ForceSleep(void);
DSP_ForceSleep();
printf(“dsp nand read ok1 %d\n”, ret);
#endif

#ifdef CONFIG_SC7710G2
ret = try_update_spl();
if(ret == -1){
printf(“try update spl faild!\n”);
return -1;
}

ret = try_load_fixnv();
if(ret == -1){
printf(“try load fixnv faild!\n”);
return -1;
}

ret = try_load_runtimenv();
if(ret == -1){
printf(“try load runtimenv faild!\n”);
}

ret = try_load_productinfo();
if(ret == -1){
printf(“try load productinfo faild!\n”);
}
#endif
if(poweron_by_calibration())
{
#ifndef CONFIG_SC7710G2
// ———————fix nv——————————–

……

// ———————runtime nv—————————-
……
// ———————DSP —————————-
……

#endif

/* KERNEL_PART */
printf(“Reading kernel to 0x%08x\n”, KERNEL_ADR);

ret = find_dev_and_part(kernel_pname, &dev, &pnum, &part);
if(ret){
printf(“No partition named %s\n”, kernel_pname);
return;
}else if(dev->id->type != MTD_DEV_TYPE_NAND){
printf(“Partition %s not a NAND device\n”, kernel_pname);
return;
}

off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
#if 0
size = nand->writesize;
flash_page_size = nand->writesize;
ret = nand_read_offset_ret(nand, off, &size, (void *)hdr, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
if(memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)){
printf(“bad boot image header, give up read!!!!\n”);
return;
}
else
{
//read kernel image
size = (hdr->kernel_size+(flash_page_size – 1)) & (~(flash_page_size – 1));
if(size <=0){
printf(“kernel image should not be zero\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)KERNEL_ADR, &off);
if(ret != 0){
printf(“kernel nand read error %d\n”, ret);
return;
}
//read ramdisk image
size = (hdr->ramdisk_size+(flash_page_size – 1)) & (~(flash_page_size – 1));
if(size<0){
printf(“ramdisk size error\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)RAMDISK_ADR, &off);
if(ret != 0){
printf(“ramdisk nand read error %d\n”, ret);
return;
}
}
#else

ret = load_kernel_and_layout(nand,
(unsigned int)off,
(char *)raw_header,
(char *) KERNEL_ADR,
(char *) RAMDISK_ADR,
2048,
nand->writesize);

if (ret != 0) {
printf(“ramdisk nand read error %d\n”, ret);
return;
}

#endif

……

{

good_blknum = 0;
bad_blknum = 0;
nand_block_info(nand, &good_blknum, &bad_blknum);
printf(“good is %d bad is %d\n”, good_blknum, bad_blknum);
}
creat_cmdline(cmdline,hdr);
vlx_entry();//末尾进入entry(),其实现在normal_mode.c
}
该函数的重点在开头和结尾的相关操作,开头部分见注释,重点分析vlx_entry()函数,其实现在normal_mode.c:

void vlx_entry()
{
#if !(defined CONFIG_SC8810 || defined CONFIG_TIGER || defined CONFIG_SC8830)
MMU_InvalideICACHEALL();
#endif

#if (defined CONFIG_SC8810) || (defined CONFIG_SC8825) || (defined CONFIG_SC8830)
MMU_DisableIDCM();
#endif

#ifdef REBOOT_FUNCTION_INUBOOT
reboot_func();
#endif

#if BOOT_NATIVE_LINUX
start_linux();
#else
void (*entry)(void) = (void*) VMJALUNA_ADR;
entry();
#endif
}
这里的entry()跳转到VM虚拟机的首地址,start_linux()则是进入kernel的方法,仍在normal_mode.c中实现:

static int start_linux()
{
void (*theKernel)(int zero, int arch, u32 params);
u32 exec_at = (u32)-1;
u32 parm_at = (u32)-1;
u32 machine_type;

machine_type = machine_arch_type; /* get machine type */
//重点根据KERNEL的地址
theKernel = (void (*)(int, int, u32))KERNEL_ADR; /* set the kernel address */
#ifndef CONFIG_SC8830
*(volatile u32*)0x84001000 = ‘j’;
*(volatile u32*)0x84001000 = ‘m’;
*(volatile u32*)0x84001000 = ‘p’;
#endif
//根据Kernel的地址走至Kernel\init\main.c的start_kernel()
theKernel(0, machine_type, VLX_TAG_ADDR); /* jump to kernel with register set */
while(1);
return 0;
}

至此,已经到了Kernel\init\main.c的start_kernel(),即来到了linux的世界。

3,linux启动详细分析
Kernel\init\main.c的start_kernel()的kernel的起点,先看这个函数:

asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];

#ifdef CONFIG_NKERNEL
jiffies_64 = INITIAL_JIFFIES;
#endif
smp_setup_processor_id();

/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();

/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();

cgroup_init_early();

local_irq_disable();
early_boot_irqs_disabled = true;

/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE “%s”, linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

build_all_zonelists(NULL);
page_alloc_init();

printk(KERN_NOTICE “Kernel command line: %s\n”, boot_command_line);
parse_early_param();
parse_args(“Booting kernel”, static_command_line, __start___param,
__stop___param – __start___param,
&unknown_bootoption);
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();

/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time – but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption – early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (!irqs_disabled()) {
printk(KERN_WARNING “start_kernel(): bug: interrupts were ”
“enabled *very* early, fixing it\n”);
local_irq_disable();
}
idr_init_cache();
perf_event_init();
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
call_function_init();
if (!irqs_disabled())
printk(KERN_CRIT “start_kernel(): bug: interrupts were ”
“enabled early\n”);
early_boot_irqs_disabled = false;
local_irq_enable();

/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;

kmem_cache_init_late();

/*
* HACK ALERT! This is early. We’re enabling the console before
* we’ve done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic(panic_later, panic_param);

lockdep_info();

/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_CRIT “initrd overwritten (0x%08lx < 0x%08lx) – ”
“disabling it.\n”,
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
enable_debug_pagealloc();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();

check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();

ftrace_init();

/* Do the rest non-__init’ed, we’re now alive */
//*个跟init 进程相关的函数
rest_init();
}
该函数所调用的大部分都是相关的初始化操作,而跟启动关联的是结尾的rest_init() ,该函数是*个跟init进程相关的函数,看其实现:

static noinline void __init_refok rest_init(void)
{
int pid;

rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
//启动kernel_init来进行接下来的初始化
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);

/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();

/* Call into cpu_idle with preempt disabled */
cpu_idle();//将系统交给调度器处理。
}
该函数启动了kernel_init来进行后续的初始化,进而看kernel_init(),这些函数任然在main.c中实现。

static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);

cad_pid = task_pid(current);

smp_prepare_cpus(setup_max_cpus);

do_pre_smp_initcalls();
lockup_detector_init();

smp_init();
sched_init_smp();
//此函数中会调用各个驱动模块的加载函数(静态编译的,非ko)来初始化设备
do_basic_setup();

/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) “/dev/console”, O_RDWR, 0) < 0)
printk(KERN_WARNING “Warning: unable to open an initial console.\n”);

(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/

if (!ramdisk_execute_command)
ramdisk_execute_command = “/init”;

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

/*
* 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 进程的相关操作
init_post();
return 0;
}

init进程由init_post()创建 即main.c 的init_post():
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();

current->signal->flags |= SIGNAL_UNKILLABLE;

if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING “Failed to execute %s\n”,
ramdisk_execute_command);
}

/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
//至此init启动完成,接下来的启动就是System\core\init\init.c的main()
run_init_process(execute_command);
printk(KERN_WARNING “Failed to execute %s. Attempting ”
“defaults…\n”, execute_command);
}
run_init_process(“/sbin/init”);
run_init_process(“/etc/init”);
run_init_process(“/bin/init”);
run_init_process(“/bin/sh”);

panic(“No init found. Try passing init= option to kernel. ”
“See Linux Documentation/init.txt for guidance.”);
}
4,android启动详细分析
android部分的启动包括几个部分:init,zygote,systemserver,launcher,lockscreen,othersapps。

4.1,init启动
init是一个进程,确切的说,是linux系统用户空间的*个进程,android是基于linux 的,所以init也是android用户空间的*个进程,他的进程号是1,作为天字*号进程,其有很多重要的职责。其*重要的职责是创建了Zygote以及提供了systemserver。system\core\init\init.c的入口函数是main()。

int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;

if (!strcmp(basename(argv[0]), “ueventd”))
return ueventd_main(argc, argv);

/* clear the umask */
umask(0);

/* Get the basic filesystem setup we need put
* together in the initramdisk on / and then we’ll
* let the rc file figure out the rest.
*/
//创建一些文件夹,并挂载设备,这些是与linux相关的
mkdir(“/dev”, 0755);
mkdir(“/proc”, 0755);
mkdir(“/sys”, 0755);

mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
mount(“proc”, “/proc”, “proc”, 0, NULL);
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);

/* indicate that booting is in progress to background fw loaders, etc */
close(open(“/dev/.booting”, O_WRONLY | O_CREAT, 0000));

/* We must have some place other than / to create the
* device nodes for kmsg and null, otherwise we won’t
* be able to remount / read-only later on.
* Now that tmpfs is mounted on /dev, we can actually
* talk to the outside world.
*/
//重定向标准输入输出 错误输出到/dev/_null_
open_devnull_stdio();
//设置init的日志输出设备为/dev/_kmsg_,不过该文件打开后
//会立刻被unlink,这样其他进程就无法打开这个文件读取日志信息
klog_init();
//prop配置文件的解析与初始化操作,如//设置”/default.prop”属性文件
property_init();
//通过读取proc/cpuinfo得到机器的hardware名
get_hardware_name(hardware, &revision);

process_kernel_cmdline();

#ifdef HAVE_SELINUX
INFO(“loading selinux policy\n”);
selinux_load_policy();
#endif

is_charger = !strcmp(bootmode, “charger”);

INFO(“property init\n”);
if (!is_charger)
property_load_boot_defaults();

INFO(“reading config file\n”);
//解析init.rc配置文件 非常重要,文件系统的挂载,权限设置
//以及系统server的启动,包括Zygote的创建
init_parse_config_file(“/init.rc”);
/**
**解析完init.rc 会得到一系列的action动作
**keychord_init_action和console_init_action
**/
action_for_each_trigger(“early-init”, action_add_queue_tail);

queue_builtin_action(wait_for_coldboot_done_action, “wait_for_coldboot_done”);
queue_builtin_action(keychord_init_action, “keychord_init”);
//console_init_action为控制台初始化,此处会加载一帧boot logo文件为initlogo.rle
queue_builtin_action(console_init_action, “console_init”);

/* execute all the boot actions to get us started */
action_for_each_trigger(“init”, action_add_queue_tail);

/* skip mounting filesystems in charger mode */

action_for_each_trigger(“early-fs”, action_add_queue_tail);
//action_for_each_trigger(“fs”, action_add_queue_tail);
{
bool has_3partions = false;

has_3partions = (!access(“/sys/block/mmcblk0/mmcblk0p3”,R_OK))
&& (!access(“/sys/block/mmcblk0/mmcblk0p2”,R_OK))
&& (!access(“/sys/block/mmcblk0/mmcblk0p1”,R_OK));

if (has_3partions) {
action_for_each_trigger(“fs-two”, action_add_queue_tail);
} else {
action_for_each_trigger(“fs”, action_add_queue_tail);
}
}
action_for_each_trigger(“post-fs”, action_add_queue_tail);
if (!is_charger) {
//action_for_each_trigger(“post-fs”, action_add_queue_tail);
action_for_each_trigger(“post-fs-data”, action_add_queue_tail);
}

queue_builtin_action(property_service_init_action, “property_service_init”);
queue_builtin_action(signal_init_action, “signal_init”);
queue_builtin_action(check_startup_action, “check_startup”);

if (!strcmp(bootmode, “alarm”)) {
action_for_each_trigger(“alarm”, action_add_queue_tail);
}
if (is_charger) {
action_for_each_trigger(“charger”, action_add_queue_tail);
} else {
action_for_each_trigger(“early-boot”, action_add_queue_tail);
action_for_each_trigger(“boot”, action_add_queue_tail);
}

/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”);

#if BOOTCHART
queue_builtin_action(bootchart_init_action, “bootchart_init”);
#endif

for(;;) {//无限循环启动进程
int nr, i, timeout = -1;

execute_one_command();//再循环中执行动作
restart_processes();//重启已经死去的进程

if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}

if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

if (!action_queue_empty() || cur_action)
timeout = 0;

#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || –bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif

nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;

for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}

return 0;
}
从以上代码可知,init的工作任务还是很重的,上面的代码已经省略的不少,但任然很多,不过分析两个知识点来看,可将init的工作流程精简为四点:1,解析配置文件重点是init.rc。2,执行各个阶段的动作,创建zygote的工作就在其中的某一个阶段完成。3,调用property_init()初始化属性相关的资源,并且通过property_load_boot_defaults()启动属性服务。4,init进入一个无限循环,并且等待一些事情的发生。接下来重点看下解析配置文件的init.rc。解析函数:

int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;

parse_config(fn, data);
DUMP();
return 0;
}
再看init.rc文件:

……

// service管理器 —- > servicemanager.cpp/ servicemanager.java
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote —— > 启动zygote进程
onrestart restart media —— > 启动media
onrestart restart surfaceflinger—— > 启动surfaceflinger
onrestart restart drm—— > 启动drm
service vold /system/bin/vold
class core
socket vold stream 0660 root mount
ioprio be 2
service netd /system/bin/netd
class main
socket netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
service debuggerd /system/bin/debuggerd
class main
service ril-daemon /system/bin/rild
class main
socket rild stream 660 root radio
socket rild-debug stream 660 radio system
user root
group radio cache inet misc audio sdcard_rw log
// surfaceflinger服务
//对应surfaceflinger.cpp—– >在system_server中具体实现
service surfaceflinger /system/bin/surfaceflinger
class main
user system
group graphics
onrestart restart zygote
// zygote进程
//后面重点分析
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
// drm服务
service drm /system/bin/drmserver
class main
user drm
group system inet drmrpc
// mediaserver服务
//在Main_mediaserver.cpp中实现,启动audio camera 等服务
service media /system/bin/mediaserver
class main
user media
group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc
ioprio rt 4
//此处引导播放开机动画,并在surfaceflinger中具体实现
service bootanim /system/bin/bootanimation
class main
user graphics
group graphics
disabled
oneshot
……
在init.rc中完成了一系列的重要操作:文件系统权限及挂载,启动zygote,启动系统服务,播放开机动画。当然如何解析对应的代码,并完成对应的操作,如启动zygote、播放开机动画,可以参考相关资料或查看源码,此处不再详述。至此init已经将部分操作交给了zygote。
4.2,zygote启动
zygote的启动预示着真正的来到了java的世界。zygote这个词的中文意思的受精卵,他和android系统中的java世界有着重要关系。zygote本身是一个native的应用程序,与驱动,内核均无关系。根据对init的了解我们知道,zygote是有init进程根据init.rc文件中的配置项创建的。先分析其来历,zygote*初的名字叫app_process,这个名字是在android.mk文件中指定的。但在运行过程中,app_process通过linux下的pctrl系统调用将自己的名字换成了zygote,所以通过进程看到的名称是zygote。

Zygote进程中完成了java虚拟机的创建及初始化,以及准备了java运行时环境,还有jni的准备工作,所以zygote占据了整个android世界的半壁江山,另半壁江山则是system_server,后续会详细介绍。

Zygote—- >入口文件App_main.cpp —- >main()
Zygote原意是受精卵的意思。
在linux中指app_process即:frameworks/base/cmds/app_process目录下的App_main.cpp
此处可发现main()

int main(int argc, const char* const argv[])
{
// These are global variables in ProcessState.cpp
mArgC = argc;
mArgV = argv;

mArgLen = 0;
for (int i=0; i<argc; i++) {
mArgLen += strlen(argv[i]) + 1;
}
mArgLen–;

AppRuntime runtime;
const char* argv0 = argv[0];

// Process command line arguments
// ignore argv[0]
argc–;
argv++;

// Everything up to ‘–‘ or first non ‘-‘ arg goes to the vm

int i = runtime.addVmArguments(argc, argv);

// Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
const char* parentDir = NULL;
const char* niceName = NULL;
const char* className = NULL;
while (i < argc) {
const char* arg = argv[i++];
if (!parentDir) {
parentDir = arg;
} else if (strcmp(arg, “–zygote”) == 0) {
zygote = true;
niceName = “zygote”;
} else if (strcmp(arg, “–start-system-server”) == 0) {
startSystemServer = true;
} else if (strcmp(arg, “–application”) == 0) {
application = true;
} else if (strncmp(arg, “–nice-name=”, 12) == 0) {
niceName = arg + 12;
} else {
className = arg;
break;
}
}

if (niceName && *niceName) {
setArgv0(argv0, niceName);
set_process_name(niceName);
}

runtime.mParentDir = parentDir;

if (zygote) {
// do last shutdown check
ALOGV(“doLastShutDownCheck”);
doLastShutDownCheck();
runtime.start(“com.android.internal.os.ZygoteInit”,
startSystemServer ? “start-system-server” : “”);
} else if (className) {
// Remainder of args get passed to startup class main()
runtime.mClassName = className;
runtime.mArgC = argc – i;
runtime.mArgV = argv + i;
runtime.start(“com.android.internal.os.RuntimeInit”,
application ? “application” : “tool”);
} else {
fprintf(stderr, “Error: no class name or –zygote supplied.\n”);
app_usage();
LOG_ALWAYS_FATAL(“app_process: no class name or –zygote supplied.”);
return 10;
}
}

该代码主要完成工作如下:

1,niceName = “zygote”;—- >重命名,原进程名称为app_process
,2,setArgv0(argv0, niceName);
,3,set_process_name(niceName); —- >完成重命名操作
,4,AppRuntime runtime;—– >App_main.cpp的一个内部类,其继承AndroidRuntime.cpp
,5,runtime.start(“com.android.internal.os.ZygoteInit”,startSystemServer(“startsystemserver”));

备注:AppRuntime 作为一个内部类,在main()里调用。其完成:
1, getClassName() —- >运行时文件类名
2, onVmCreated()—- >java虚拟机创建
3, onStarted()—- >调用时加载
4, onZygoteInit()—- >初始化虚拟机
5, onExit()—- >退出时的操作 ——— > 上述函数基本自动调用

接着,走进runtime.start(com.android.internal.os.ZygoteInit)。runtime来自AndroidRuntime.cpp。AndroidRuntime.cpp—— >AndroidRuntime::start(const char* className, const char* options)。frameworks\base\core\jni\AndroidRuntime.cpp。分析其start()函数:

void AndroidRuntime::start(const char* className, const char* options)
{
ALOGD(“\n>>>>>> AndroidRuntime START %s <<<<<<\n”,
className != NULL ? className : “(unknown)”);

blockSigpipe();

/*
* ‘startSystemServer == true’ means runtime is obsolete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
if (strcmp(options, “start-system-server”) == 0) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}

const char* rootDir = getenv(“ANDROID_ROOT”);
if (rootDir == NULL) {
rootDir = “/system”;
if (!hasDir(“/system”)) {
LOG_FATAL(“No root directory specified, and /android does not exist.”);
return;
}
setenv(“ANDROID_ROOT”, rootDir, 1);
}

//const char* kernelHack = getenv(“LD_ASSUME_KERNEL”);
//ALOGD(“Found LD_ASSUME_KERNEL=’%s’\n”, kernelHack);

/* start the virtual machine */
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
onVmCreated(env);

/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE(“Unable to register all android natives\n”);
return;
}

/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
jstring optionsStr;

stringClass = env->FindClass(“java/lang/String”);
assert(stringClass != NULL);
strArray = env->NewObjectArray(2, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
optionsStr = env->NewStringUTF(options);
env->SetObjectArrayElement(strArray, 1, optionsStr);

/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE(“JavaVM unable to locate class ‘%s’\n”, slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, “main”,
“([Ljava/lang/String;)V”);
if (startMeth == NULL) {
ALOGE(“JavaVM unable to find main() in ‘%s’\n”, className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
free(slashClassName);

ALOGD(“Shutting down VM\n”);
if (mJavaVM->DetachCurrentThread() != JNI_OK)
ALOGW(“Warning: unable to detach main thread\n”);
if (mJavaVM->DestroyJavaVM() != 0)
ALOGW(“Warning: VM did not shut down cleanly\n”);
}

该函数完成操作:
1, onVmCreated(env);—– >创建虚拟机
2, JNIEnv* env; —- > JNI环境的初始化
3, env->CallStaticVoidMethod(startClass, startMeth, strArray); —– >*终函数与上述步骤中的runtime.start(com.android.internal.os.ZygoteInit)对应。
4, 至此走到—- ZygoteInit.java—-main()

ZygoteInit.java —main()—– >java世界准备已经完成,欢迎来到java世界。

public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();

registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());

// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup
gc();

// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

if (argv[1].equals(“start-system-server”)) {
startSystemServer();
} else if (!argv[1].equals(“”)) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

Log.i(TAG, “Accepting command socket connections”);

if (ZYGOTE_FORK_MODE) {
runForkMode();
} else {
runSelectLoopMode();
}

closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, “Zygote died with exception”, ex);
closeServerSocket();
throw ex;
}
}

该函数重点完成如下3项工作:

1, registerZygoteSocket();
2, startSystemServer();—– > 核心方法,Zygote进程一分为二,此处分裂出一个system_server进程。
3, 至此system_server进程进入SystemServer.java—- >main()

先看下startSystemServer()方法:

private static boolean startSystemServer()
throws MethodAndArgsCaller, RuntimeException {
/* Hardcoded command line to start the system server */
String args[] = {
“–setuid=1000”,
“–setgid=1000”,
“–setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007”,
“–capabilities=130104352,130104352”,
“–runtime-init”,
“–nice-name=system_server”,
“com.android.server.SystemServer”,
};
ZygoteConnection.Arguments parsedArgs = null;

int pid;

try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}

/* For child process */
if (pid == 0) {
handleSystemServerProcess(parsedArgs);
}

return true;
}
com.android.server.SystemServer的创建,预示着SystemServer的的正式启动,自此Zygote一分为二。Zygote将系统服务交给SystemServer统一管理。而zygote则负责java运行时环境和Dalvik虚拟机的管理工作。
4.3,systemserver启动
system_server进程是android的第二大进程,其余zygote紧密联系,若其中任何一个进程死掉,就会导致系统死掉,其启动过程包含两个阶段Main()—– >init1()和init2()。
Init1(),为system_server的*阶段SystemServer.java— >Init1()的本地实现在com_android_server_SystemServer.cpp中。

先看frameworks\base\services\java\com\android\server\SystemServer.java的main()函数。

native public static void init1(String[] args);

public static void main(String[] args) {
if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
// If a device’s clock is before 1970 (before 0), a lot of
// APIs crash dealing with negative numbers, notably
// java.io.File#setLastModified, so instead we fake it and
// hope that time from cell towers or NTP fixes it
// shortly.
Slog.w(TAG, “System clock is before 1970; setting to 1970.”);
SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
}

if (SamplingProfilerIntegration.isEnabled()) {
SamplingProfilerIntegration.start();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SamplingProfilerIntegration.writeSnapshot(“system_server”, null);
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}

// Mmmmmm… more memory!
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();

// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);

System.loadLibrary(“android_servers”);
init1(args);
}

public static final void init2() {
Slog.i(TAG, “Entered the Android system server!”);
Thread thr = new ServerThread();
thr.setName(“android.server.ServerThread”);
thr.start();
}

其中init1()的本地实现在com_android_server_SystemServer.cpp中:

static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz)
{
system_init();
}

system_init()在frameworks\base\cmds\system_server\library\system_init.cpp中:

extern “C” status_t system_init()
{
ALOGI(“Entered system_init()”);

sp<ProcessState> proc(ProcessState::self());

sp<IServiceManager> sm = defaultServiceManager();
ALOGI(“ServiceManager: %p\n”, sm.get());

sp<GrimReaper> grim = new GrimReaper();
sm->asBinder()->linkToDeath(grim, grim.get(), 0);

char propBuf[PROPERTY_VALUE_MAX];
property_get(“system_init.startsurfaceflinger”, propBuf, “1”);
if (strcmp(propBuf, “1”) == 0) {
// Start the SurfaceFlinger
SurfaceFlinger::instantiate();
}

property_get(“system_init.startsensorservice”, propBuf, “1”);
if (strcmp(propBuf, “1”) == 0) {
// Start the sensor service
SensorService::instantiate();
}

// And now start the Android runtime. We have to do this bit
// of nastiness because the Android runtime initialization requires
// some of the core system services to already be started.
// All other servers should just start the Android runtime at
// the beginning of their processes’s main(), before calling
// the init function.
ALOGI(“System server: starting Android runtime.\n”);
AndroidRuntime* runtime = AndroidRuntime::getRuntime();

ALOGI(“System server: starting Android services.\n”);
JNIEnv* env = runtime->getJNIEnv();
if (env == NULL) {
return UNKNOWN_ERROR;
}
jclass clazz = env->FindClass(“com/android/server/SystemServer”);
if (clazz == NULL) {
return UNKNOWN_ERROR;
}
jmethodID methodId = env->GetStaticMethodID(clazz, “init2”, “()V”);
if (methodId == NULL) {
return UNKNOWN_ERROR;
}
env->CallStaticVoidMethod(clazz, methodId);

ALOGI(“System server: entering thread pool.\n”);
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI(“System server: exiting thread pool.\n”);

return NO_ERROR;
}
再回到SystemServer.java的main()中的init2():init2()将操作交给了内部类ServerThread处理,看起run()函数:

public void run() {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
SystemClock.uptimeMillis());

Looper.prepare();

android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND);

…….

AccountManagerService accountManager = null;
ContentService contentService = null;
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
VibratorService vibrator = null;
AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
WifiP2pService wifiP2p = null;
WifiService wifi = null;
NsdService serviceDiscovery= null;
IPackageManager pm = null;
Context context = null;
WindowManagerService wm = null;
BluetoothService bluetooth = null;
BluetoothA2dpService bluetoothA2dp = null;
DockObserver dock = null;
UsbService usb = null;
SerialService serial = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
InputManagerService inputManager = null;

//Bug#185069 fix low storage ,check the space&delete the temp file weather need.
DeviceStorageMonitorService.freeSpace();

…….
ServiceManager.addService(“xxx”,XXX);
…….

DevicePolicyManagerService devicePolicy = null;
StatusBarManagerService statusBar = null;
InputMethodManagerService imm = null;
AppWidgetService appWidget = null;
NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
TextServicesManagerService tsms = null;
LockSettingsService lockSettings = null;
DreamManagerService dreamy = null;

……

// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
final UsbService usbF = usb;
final ThrottleService throttleF = throttle;
final UiModeManagerService uiModeF = uiMode;
final AppWidgetService appWidgetF = appWidget;
final WallpaperManagerService wallpaperF = wallpaper;
final InputMethodManagerService immF = imm;
final RecognitionManagerService recognitionF = recognition;
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
final DreamManagerService dreamyF = dreamy;
final InputManagerService inputManagerF = inputManager;
final BluetoothService bluetoothF = bluetooth;

ActivityManagerService.self().systemReady(new Runnable() {
public void run() {
Slog.i(TAG, “Making services ready”);

if (!headless) startSystemUi(contextF);
try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
reportWtf(“making Battery Service ready”, e);
}
try {
if (networkManagementF != null) networkManagementF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Managment Service ready”, e);
}
try {
if (networkStatsF != null) networkStatsF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Stats Service ready”, e);
}
try {
if (networkPolicyF != null) networkPolicyF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Policy Service ready”, e);
}
try {
if (connectivityF != null) connectivityF.systemReady();
} catch (Throwable e) {
reportWtf(“making Connectivity Service ready”, e);
}
try {
if (dockF != null) dockF.systemReady();
} catch (Throwable e) {
reportWtf(“making Dock Service ready”, e);
}
try {
if (usbF != null) usbF.systemReady();
} catch (Throwable e) {
reportWtf(“making USB Service ready”, e);
}
try {
if (uiModeF != null) uiModeF.systemReady();
} catch (Throwable e) {
reportWtf(“making UI Mode Service ready”, e);
}
try {
if (recognitionF != null) recognitionF.systemReady();
} catch (Throwable e) {
reportWtf(“making Recognition Service ready”, e);
}
Watchdog.getInstance().start();

// It is now okay to let the various system services start their
// third party code…

try {
if (appWidgetF != null) appWidgetF.systemReady(safeMode);
} catch (Throwable e) {
reportWtf(“making App Widget Service ready”, e);
}
try {
if (wallpaperF != null) wallpaperF.systemReady();
} catch (Throwable e) {
reportWtf(“making Wallpaper Service ready”, e);
}
try {
if (immF != null) immF.systemReady(statusBarF);
} catch (Throwable e) {
reportWtf(“making Input Method Service ready”, e);
}
try {
if (locationF != null) locationF.systemReady();
} catch (Throwable e) {
reportWtf(“making Location Service ready”, e);
}
try {
if (countryDetectorF != null) countryDetectorF.systemReady();
} catch (Throwable e) {
reportWtf(“making Country Detector Service ready”, e);
}
try {
if (throttleF != null) throttleF.systemReady();
} catch (Throwable e) {
reportWtf(“making Throttle Service ready”, e);
}
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Time Service ready”, e);
}
try {
if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady();
} catch (Throwable e) {
reportWtf(“making Common time management service ready”, e);
}
try {
if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
} catch (Throwable e) {
reportWtf(“making Text Services Manager Service ready”, e);
}
try {
if (dreamyF != null) dreamyF.systemReady();
} catch (Throwable e) {
reportWtf(“making DreamManagerService ready”, e);
}
try {
if (inputManagerF != null) inputManagerF.systemReady(bluetoothF);
} catch (Throwable e) {
reportWtf(“making InputManagerService ready”, e);
}
}
});

//PowerManagerServer WakeLock dump thread
(new Thread(new WakelockMonitor(power))).start();

……

Looper.loop();
Slog.d(TAG, “System ServerThread is exiting!”);
}

该函数有3个重要功能:

1,ServiceManager.addService(“xxx”,XXX),将系统服务注册进去。

2,systemReady(),告诉已经实现该接口servers,系统已经启动OK。

3,WakelockMonitor的启动。

至此,systemserver的启动工作已经完成。

4.4,launcher启动
桌面launcher即Home:
1)源码:ActivityManagerService.java为入口,packages/apps/launcher*实现
2)说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,*终调用startHomeActivityLocked()启动launcher。Home在((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)。函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。这个函数的实现部分在文件:ActivityManagerService.java中。

先看ActivityManagerService.java的systemReady():

public void systemReady(final Runnable goingCallback) {

……

retrieveSettings();

if (goingCallback != null) goingCallback.run();

synchronized (this) {
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info
= (ApplicationInfo)apps.get(i);
if (info != null &&
!info.packageName.equals(“android”)) {
addAppLocked(info, false);
}
}
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
}

// Start up initial activity.
mBooting = true;

try {
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
Message msg = Message.obtain();
msg.what = SHOW_UID_ERROR_MSG;
mHandler.sendMessage(msg);
}
} catch (RemoteException e) {
}

mMainStack.resumeTopActivityLocked(null);
}
}
跳转至launcher的操作由resumeTopActivityLocked()完成,其实现在ActivityStack.java里的resumeTopActivityLocked()。

final ActivityManagerService mService;
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
// Find the first activity that is not finishing.
ActivityRecord next = topRunningActivityLocked(null);

// Remember how we’ll process this pause/resume situation, and ensure
// that the state is reset however we wind up proceeding.
final boolean userLeaving = mUserLeaving;
mUserLeaving = false;

if (next == null) {
// There are no more activities! Let’s just start up the
// Launcher…
if (mMainStack) {
ActivityOptions.abort(options);
return mService.startHomeActivityLocked(0);
}
}
从上述代码可以看出其实是走到了mService.startHomeActivityLocked(0),而这里的mService也就是ActivityManagerService.java,再次回到ActivityManagerService.java的startHomeActivityLocked(0),至此launcher启动完成。

4.5,lockscreen启动

源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,*终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。此处不再详细分析。

frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java的systemReady()方法:

/** {@inheritDoc} */
public void systemReady() {
if (mKeyguardMediator != null) {
// tell the keyguard
mKeyguardMediator.onSystemReady();
}
synchronized (mLock) {
updateOrientationListenerLp();
mSystemReady = true;
mHandler.post(new Runnable() {
public void run() {
updateSettings();
}
});
}
}

*步,告诉锁屏控制器,系统已经启动完成,接下来有锁屏处理。 frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewMediator.java:

public void onSystemReady() {
synchronized (this) {
if (DEBUG) Log.d(TAG, “onSystemReady”);
mSystemReady = true;
doKeyguardLocked();
}
}

再看其doKeyguardLocked()方法:

private void doKeyguardLocked() {

if(engModeFlag){
Log.d(TAG, “show engmode!”);
engModeFlag = false;
return ;
}

// if another app is disabling us, don’t show
if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because externally disabled”);

// note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
// for an occasional ugly flicker in this situation:
// 1) receive a call with the screen on (no keyguard) or make a call
// 2) screen times out
// 3) user hits key to turn screen back on
// instead, we reenable the keyguard when we know the screen is off and the call
// ends (see the broadcast receiver below)
// TODO: clean this up when we have better support at the window manager level
// for apps that wish to be on top of the keyguard
return;
}

// if the keyguard is already showing, don’t bother
if (mKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because it is already showing”);
return;
}

final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
final boolean lockedOrMissing = isSimLockedOrMissing();

if (!lockedOrMissing && !provisioned) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because device isn’t provisioned”
+ ” and the sim is not locked or missing”);
return;
}

if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because lockscreen is off”);
return;
}

if (DEBUG) Log.d(TAG, “doKeyguard: showing the lock screen”);
showLocked();
}
至此,锁屏启动完成。
4.6,othersapps启动
系统启动完成后,launcher会加载系统已经安装的apk,并显示在launcher上。

至此,android启动完成。

5,android启动动画效果剖析
在android启动的过程中我们通常可以看到若干个启动画面,均代表着不同的启动阶段,接下来根据启动阶段分析启动画面。

uboot启动:会有一帧 uboot logo。

kernel启动:会有一帧kernel logo。(默认不显示,其控制宏是默认关闭的)

android启动:会有一帧静态图片+一个闪动的图片序列(即开机动画)。

通常情况下,我们在分析android的开机动画效果时,很少去分析uboot logo和kernel logo,因为ubootlogo 属于uboot阶段,kernel logo 属于linux范围。正常情况下,我们在down版本,烧到手机里去时,会吧logo.bmp加进去,这是系统的处理是:uboot logo,kernel logo,android static logo是同一张图片,即我们加的logo.bmp。

双framebuffer显示logo机制分析:本来一直走的是一级logo显示,从uboot logo一直持续到系统动画,但考虑期间时间偏长,欲采用标准三级logo。1、uboot logo  2、kernle logo 3 initlogo.rle *后动画bootanimation.zip。但是kernel 对framebuffer修改较大,故考虑在uboot开始和结束显示两张logo(第二幅logo显示调用在theKernel()跳入内核函数之前),kernel跳过。uboot 直接刷屏显示第二幅logo 动作过慢,效果不佳,经考虑采用双buffer策略。思路:

1.原来只要显示一张uboot logo :把nand 中boot.logo 拷贝至lcd_base+fbsize处,然后搬至lcd_base显示;
2.现在创建第二个framebuffer于lcd_base+2*fbsize处,在显示第二幅logo前把nand 中第二幅logo 仍然拷贝至lcd_base+fbsize处,然后搬至lcd_base+2*fbsize第二个framebuffer基地址;
3.把第二个framebuffer基地址告诉lcd 控制寄存器,更新framebuffer基地址;
4.但在kernel中,寄存器仍然会指向*个framebuffer基地址,那么第二幅logo显示犹如昙花一现啊,不过这个问题好解决,既然第二幅logo已经搬进了第二个framebuffer那,那么只要在进入内核前做一个memcpy就好了。
注:logo是bmp格式,在拷贝前需要进行相应的解析,参考uboot给的解析代码,自定义函数。
5.1,uboot logo
以正常模式启动分析uboot logo。即normal_mode.c根据前部分的分析可知,流程会走至normal_nand_mode.c的vlx_nand_boot()函数。

//读取下载到nand中的boot_logo,就是开机亮的那一屏
off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
size = 1<<19;//where the size come from????//和dowload工具中的地址一致
char * bmp_img = malloc(size);
if(!bmp_img){
printf(“not enough memory for splash image\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
//*次LCD logo
lcd_display_logo(backlight_set,(ulong)bmp_img,size);
即由lcd_display_logo()完成相关操作。该函数在normal_mode.c中定义。
void lcd_display_logo(int backlight_set,ulong bmp_img,size_t size)
{
#ifdef CONFIG_SPLASH_SCREEN
extern int lcd_display_bitmap(ulong bmp_image, int x, int y);
extern void lcd_display(void);
extern void *lcd_base;
extern void Dcache_CleanRegion(unsigned int addr, unsigned int length);
extern void set_backlight(uint32_t value);
if(backlight_set == BACKLIGHT_ON){
lcd_display_bitmap((ulong)bmp_img, 0, 0);
#if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830)
Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large.
#endif
lcd_display();
set_backlight(255);
}else{
memset((unsigned int)lcd_base, 0, size);
#if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830)
Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large.
#endif
lcd_display();
}
#endif
}
5.2,kernel logo
kernel logo 属于linux系统自带的logo机制,由于在android平台其显示默认是关闭的,此处不做多的分析,详细可参考博文:Android系统的开机画面显示过程分析 ,该博文只分析了启动过程的 kernel logo,android logo anim。

相关代码:
/kernel/drivers/video/fbmem.c
/kernel/drivers/video/logo/logo.c
/kernel/drivers/video/logo/Kconfig
/kernel/include/linux/linux_logo.h

static int nologo;
module_param(nologo, bool, 0);
MODULE_PARM_DESC(nologo, “Disables startup logo”);
/* logo’s are marked __initdata. Use __init_refok to tell
* modpost that it is intended that this function uses data
* marked __initdata.
*/
const struct linux_logo * __init_refok fb_find_logo(int depth)
{
const struct linux_logo *logo = NULL;
if (nologo)
return NULL;
……
}
5.3,android logo anim
Android 系统启动后,init.c中main()调用queue_builtin_action(console_init_action, “console_init”)时会根据console_init_action函数调用load_565rle_image()函数读取/initlogo.rle(一张565 rle压缩的位图),如果读取成功,则在/dev/graphics/fb0显示Logo图片;如果读取失败,则将/dev/tty0设为TEXT模式, 并打开/dev/tty0,输出文本“A N D R I O D”字样。

 

static int console_init_action(int nargs, char **args)
{
int fd;
char tmp[PROP_VALUE_MAX];

if (console[0]) {
snprintf(tmp, sizeof(tmp), “/dev/%s”, console);
console_name = strdup(tmp);
}

fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);

if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open(“/dev/tty0”, O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = “\n”
“\n”
“\n”
“\n”
“\n”
“\n”
“\n” // console is 40 cols x 30 lines
“\n”
“\n”
“\n”
“\n”
“\n”
“\n”
“\n”
” A N D R O I D “;
write(fd, msg, strlen(msg));
close(fd);
}
}
return 0;
}

由此调用logo.c 的load_565rle_image()函数。

int load_565rle_image(char *fn)
{
struct FB fb;
struct stat s;
unsigned short *data, *bits, *ptr;
unsigned count, max;
int fd;

if (vt_set_mode(1))
return -1;

fd = open(fn, O_RDONLY);
if (fd < 0) {
ERROR(“cannot open ‘%s’\n”, fn);
goto fail_restore_text;
}

if (fstat(fd, &s) < 0) {
goto fail_close_file;
}

data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
goto fail_close_file;

if (fb_open(&fb))
goto fail_unmap_data;

max = fb_width(&fb) * fb_height(&fb);
ptr = data;
count = s.st_size;
bits = fb.bits;
while (count > 3) {
unsigned n = ptr[0];
if (n > max)
break;
android_memset16(bits, ptr[1], n << 1);
bits += n;
max -= n;
ptr += 2;
count -= 4;
}

munmap(data, s.st_size);
fb_update(&fb);
fb_close(&fb);
close(fd);
unlink(fn);
return 0;

fail_unmap_data:
munmap(data, s.st_size);
fail_close_file:
close(fd);
fail_restore_text:
vt_set_mode(0);
return -1;
}

该图片格式是565RLE image format格式的,可用工具将bmp格式转化为rle格式。之后会有init.rc 并发开机动画。

相关文件:
/frameworks/base/cmds/bootanimation/BootAnimation.h
/frameworks/base/cmds/bootanimation/BootAnimation.cpp
/frameworks/base/cmds/bootanimation/bootanimation_main.cpp
/system/core/init/init.c
/system/core/rootdir/init.rc

init.c解析init.rc(其中定义服务:“service bootanim /system/bin/bootanimation”),bootanim 服务由SurfaceFlinger.readyToRun()(property_set(“ctl.start”, “bootanim”);)执行开机动画、bootFinished()(property_set(“ctl.stop”, “bootanim”);)执行停止开机动画。 BootAnimation.h和BootAnimation.cpp文件放到了/frameworks/base/cmds /bootanimation目录下了,增加了一个入口文件bootanimation_main.cpp。Android.mk文件中可以看到,将开机 动画从原来的SurfaceFlinger里提取出来了,生成可执行文件:bootanimation。Android.mk代码如下:

//=============Android.mk======================
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
bootanimation_main.cpp \
BootAnimation.cpp
# need “-lrt” on Linux simulator to pick up clock_gettime
ifeq ($(TARGET_SIMULATOR),true)
ifeq ($(HOST_OS),linux)
LOCAL_LDLIBS += -lrt
endif
endif
LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils \
libui \
libcorecg \
libsgl \
libEGL \
libGLESv1_CM \
libmedia
LOCAL_C_INCLUDES := \
$(call include-path-for, corecg graphics)
LOCAL_MODULE:= bootanimation
include $(BUILD_EXECUTABLE)
//==========================================
备注:
1,adb shell后,可以直接运行“bootanimation”来重新看开机动画,它会一直处于动画状态,而不会停止。

2,adb shell后,命令“setprop ctl.start bootanim”执行开机动画;命令“getprop ctl.start bootanim”停止开机动画。这两句命令分别对应SurfaceFlinger.cpp的两句语 句:property_set(“ctl.start”, “bootanim”);和property_set(“ctl.stop”, “bootanim”)。

至此android启动动画分析结束。

Android系统启动过程学习

使用 android 手机已经长时间了,同时,从大学学习 android 开发开始,也进行过多款 android app 项目的开发,但是对 android 内部的启动过程,即当我们从按下电源键开机开始, android 系统内部是如何运行的,由于android 系统的内核使用的是 linux 内核,那么在启动过程中,android 系统和桌面Linux系统的启动过程是否是一样的?我们在之前的一篇博客中,曾学习过Linux内核的启动过程,在这里,我们学习一下android系统的启动过程,并从大体代码上讲解其启动过程。

启动步骤
在android系统的启动过程中,大体可以分为以下几个步骤:

%title插图%num

android启动过程分析
*步 启动电源以及启动系统
当电源按下的时候,会引导固化在芯片中的代码从预定义的位置开始启动,并加载引导程序到内存,即RAM中,然后执行。

第二步 引导程序运行
引导程序,英文名称为Boot Loader,顾名思义,其为启动加载器,其主要作用是引导android系统内核的启动。引导程序是运行的*个程序。其主要作用是在引导内核启动之前,检测相关硬件以及外部的RAM,设置网络,内存等,并根据相关参数或输入数据设置内核。
Boot Loader引导程序可以在\bootable\bootloader\legacy\usbloader中找到,一般的加载器包含着两个重要的文件:

init.S初始化堆栈,清空BBS栈,调用main.c的_main()函数
main.c初始化硬件(闹钟,主板,键盘,控制台),创建Linux标签
第三步 内核运行
当引导程序引导内核启动之后,android的内核运行和linux的内核运行相似。内核启动的时候,设置缓存,被保护的存储器,计划列表,加载驱动。当内核完成系统设置之后,便找到系统文件中”init”文件,然后启动系统的*个进程。

第四步 init运行
init进程是android系统运行的*个进程,也就是说所有的android进程都是直接或间接的被init进程创建的。在init进程运行过程中,主要负责两个事情,一个是挂载系统目录,像/dev,/proc,/sys等,另外一件事情是执行init.rc文件。

init位于/system/core/init目录中。
init.rc位于/system/core/rootdir目录中。
在/system/core/init/readme.txt文件中可以学习.rc文件的相关语法,我们将在下面的博客中学习该语法。
总体来说,在init.rc文件中,主要是启动相关进程和服务,同时设置相关参数和挂载相关目录。

我们先来看一下init中的相关代码:

int main(int argc, char **argv)
{
//…..
#ifndef NO_DEVFS_SETUP
mkdir(“/dev”, 0755);
mkdir(“/proc”, 0755);
mkdir(“/sys”, 0755);

mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
mount(“proc”, “/proc”, “proc”, 0, NULL);
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);

close(open(“/dev/.booting”, O_WRONLY | O_CREAT, 0000));

open_devnull_stdio();
klog_init();
#endif
property_init();

//…….

is_charger = !strcmp(bootmode, “charger”);

INFO(“property init\n”);
if (!is_charger)
property_load_boot_defaults();

INFO(“reading config file\n”);

if (!charging_mode_booting())
init_parse_config_file(“/init.rc”);
else
init_parse_config_file(“/lpm.rc”);

/* Check for an emmc initialisation file and read if present */
if (emmc_boot && access(“/init.emmc.rc”, R_OK) == 0) {
INFO(“Reading emmc config file”);
init_parse_config_file(“/init.emmc.rc”);
}

/* Check for a target specific initialisation file and read if present */
if (access(“/init.target.rc”, R_OK) == 0) {
INFO(“Reading target specific config file”);
init_parse_config_file(“/init.target.rc”);
}

//…….

在这些事情处理完成之后,在init.rc中便会启动zygote进程。

service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd

第五步 zygote进程
在java中不同的虚拟机实例会为不同的应用分配不同的内存。假如android应用应该尽可能快的启动,但如果android系统为每一个应用启动不同的Dalvik虚拟机实例,就会消耗大量的内存以及时间。因此为了解决这个问题,Android系统创造了Zygote。Zygote让Dalvik虚拟机共享代码,低内存占用以及*小的启动时间成为可能。Zygote是一个虚拟机进程,正如我们在前一个步骤所说的在系统引导的时候启动。Zygote预加载以及初始化核心库类。

Zygote加载进程:

public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();

registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());

preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());

// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup
gcAndFinalize();

// Disable tracing so that forked processes do not inherit stale tracing tags from
// Zygote.
Trace.setTracingEnabled(false);

// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

if (argv[1].equals(“start-system-server”)) {
startSystemServer(); //启动系统服务
} else if (!argv[1].equals(“”)) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

Log.i(TAG, “Accepting command socket connections”);

runSelectLoop();

closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, “Zygote died with exception”, ex);
closeServerSocket();
throw ex;
}
}

加载ZygoteInit类,代码位于/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。
registerZygoteSocket()为zygote命令连接注册一个服务器套接字。
preload()预加载类,资源文件以及OpenGL。
第六步 系统服务或服务
在Zygote完成相应的初始化之后,开始申请启动系统服务,系统服务是android系统中供上层运行的基础。系统服务同时使用native以及java编写,系统服务可以认为是一个进程。同一个系统服务在Android SDK可以让System Services形式获得。

核心服务:

启动电源管理器;
创建Activity管理器;
启动电话注册;
启动包管理器;
设置Activity管理服务为系统进程;
启动上下文管理器;
启动系统Context Providers;
启动电池服务;
启动定时管理器;
启动传感服务;
启动窗口管理器;
启动蓝牙服务;
启动挂载服务。
其他服务:

启动状态栏服务;
启动硬件服务;
启动网络状态服务;
启动网络连接服务;
启动通知管理器;
启动设备存储监视服务;
启动定位管理器;
启动搜索服务;
启动剪切板服务;
启动登记服务;
启动壁纸服务;
启动音频服务;
启动耳机监听;
启动AdbSettingsObserver(处理adb命令)。

第七步 引导完成
一单系统服务启动,android系统的引导过程就完成了。此时,”ACTION_BOOT_COMPLETE”开机启动广播就发出去了。

IP地址,同一网段,子网掩码,默认网关,添加路由

自己在学习网络通信的这个过程中,渐渐的对网络的一些基本知识,有了一种了解,突然想把这里的一些基本的问题,进行下总结,也给自己留个笔记。

(1)IP

首先来说说ip地址吧,IP地址是指互联网协议地址(Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址的作用很简单,就像是我们每个人的家庭住址都有个门牌号码一样,你在整个的互联网环境下,别人怎么能够找到你呢,肯定找不到,然后就要知道你的唯一标识。这就产生了IP地址的概念,而IP地址主要分为两类,一类是IPV4,用4个字节32位来表示地址,而IPV6用128个位来表示IP地址。现在普遍使用的是IPv4地址。今天也会重点说下IPV4.IPV4地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110),

IP地址编址方案:IP地址编址方案将IP地址空间划分为A、B、C、D、E五类,其中A、B、C是基本类,D、E类作为多播和保留使用。将IP地址分成了网络号和主机号两部分,设计者就必须决定每部分包含多少位。网络号的位数直接决定了可以分配的网络数(计算方法2^网络号位数-2);主机号的位数则决定了网络中*大的主机数(计算方法2^主机号位数-2)。

IP地址的范围如下:

类别 网络号 /占位数 主机号 /占位数用途
A 1~126 / 8         0~255 0~255 1~254 / 24 国家级
B 128~191 0~255 / 16 0~255 1~254 / 16 跨过组织
C 192~223 0~255 0~255 / 24            1~254 / 8 企业组织

我们在日常的生活中使用的IP地址主要是C类。

(2)同一网段

所谓的同一个网段,就是两个IP地址的网络标识完全相同,一般会有这样的结论,就是如果两个ip 的前三个字节相同,那么就是同一个网段,但是这个并不是准确的,这个要结合子网掩码来看,因为我们C类地址的子网掩码默认是255.255.255.0 如果使用这个子网掩码的话,应该刚才的结论就是对的,如果不是,就可能不正确了。判断是否是同一个网段,就要计算下。
例子:两个ip 地址分别是211.95.165.24和211.95.164.78他们的子网掩码是255.255.254.0,那么这俩个IP是否在同一个子网里呢。
子网掩码255.255.254.0,现在分别将上述两个IP地址分别与掩码做与运算,如下所示:
211.95.165.24 11010011 01011111 10100101 00011000
255.255.254.0 11111111 11111111 111111110 00000000
与的结果是: 11010011 01011111 10100100 00000000
211.95.164.78 11010011 01011111 10100100 01001110
255.255.254.0 11111111 11111111 111111110 00000000
与的结果是: 11010011 01011111 10100100 00000000
可以看出,得到的结果(这个结果就是网络地址)都是一样的,因此可以判断这两个IP地址在同一个子网。

(3)子网掩码

子网掩码这个概念是不能单独存在的,他是伴随着IP地址而存在的,他的目的是来对IP地址中的网络位和主机位进行区分的,对于这个A类B类C类的IP地址默认的子网掩码如下所示:
255.0.0.0    255.255.0.0255.255.255.0
在使用默认子网掩码的时候,划分出的网络位数和主机位数很好看了,就不用再过多的说明了。
but 随着互连网应用的不断扩大,原先的IPv4的弊端也逐渐暴露出来,即网络号占位太多,而主机号位太少,所以其能提供的主机地址也越来越稀缺,而且IPV4就是有4段数字,每一段*大不超过255。由于互联网的蓬勃发展各项资料显示全球IPv4位址可能在2005至2010年间全部发完(实际情况是在2011年2月3日IPv4位地址分配完毕)。这个时候让我们想到了变量的作用域的作用,大家想下,我们在学校里上学,不是也有不同班级里的名字有重复的情况吗,而且我们在不同学校里的也有相同的高一一班啊,但是也没有乱啊,而是大家都能区分开的,这就是变量的作用域的意思,我们在一个小范围内部不重复就可以了,比如一个班级里,如果有两个人名,而且把人名作为唯一的标识,那么肯定就会出现重复识别不了的问题,但是如果两个班级内重复就无所谓了。
通常都对一个高类别的IP地址进行再划分,以形成多个子网,提供给不同规模的用户群使用。
这里主要是为了在网络分段情况下有效地利用IP地址,通过对主机号的高位部分取作为子网号,从通常的网络位界限中扩展或压缩子网掩码,用来创建某类地址的更多子网。但创建更多的子网时,在每个子网上的可用主机地址数目会比原先减少。
*后做三道理:

1.我们看一个考试中常见的题型:一个主机的IP地址是202.112.14.137,掩码是255.255.255.224,要求计算这个主机所在网络的网络地址和广播地址。
这个很好做,我的方法不一定是*简单的,先看子网掩码,一看不是那种默认的子网掩码,224到256还差了32个,
255.255.255.224的掩码所容纳的IP地址有256-224=32个(包括网络地址和广播地址),那么也就是说它的主机号占了5位。或者直接计算224的二进制表示形式是 11100000    所以他是把主机位的前三位扩展成了网络位。  所以这个主机的所在的网络的网络地址是   202.112.14+128(137二进制形式的前三位有效   再把低位全部置0------100 00000)
然后再计算广播地址,202.112.14+159(137二进制形式的前三位有效   再把低位全部置0------100 11111)
2.还有一种题型,要你根据每个网络的主机数量进行子网地址的规划和计算子网掩码。这也可按上述原则进行计算。给你分配一个C类IP段,192.168.72.0      比如一个子网要有10台主机,那么对于这个子网需要的子网掩码地址是多少:
这个要考虑清楚,虽然是10个主机,但是你得加上 3 ,1个是全0  一个是全1  一个是网关地址。
所以现在就有13个地址,所以只能选择三个二进制位不够,4个二进制位,那就是主机位占4位,那么高四位变成了 网络位。子网掩码就是11111111.11111111.11111111.11110000=255.255.255.240。

如果一个子网有14台主机,不少人常犯的错误是:依然分配具有16个地址空间的子网,而忘记了给网关分配地址。这样就错误了,因为:

14+1+1+1=17


17大于16,所以我们只能分配具有32个地址(32等于2的5次方)空间的子网。这时子网掩码为:255.255.255.224
3.根据子网数进行计算
在求子网掩码之前必须先搞清楚要划分的子网数目,以及每个子网内的所需主机数目。
1)将子网数目转化为二进制来表示
2)取得该二进制的位数,为 N
3)取得该IP地址的类子网掩码,将其主机地址部分的前N位置1 即得出该IP地址划分子网的子网掩码。
如欲将B类IP地址168.195.0.0划分成27个子网:
1)27=11011
2)该二进制为五位数,N = 5
3)将B类地址的子网掩码255.255.0.0的主机地址前5位置1(B类地址的主机位包括后两个字节,所以这里要把第三个字节的前5位置1),得到 255.255.248.0
即为划分成27个子网的B类IP地址 168.195.0.0的子网掩码(实际上是划成了32-2=30个子网)。

(4)默认网关

四. 默认网关(地址)
什么是网关?
(可以联想下海关?什么是海关?)
连接两个不同的网络的设备都可以叫网关设备;网关的作用就是实现两个网络之间进行通讯与控制。
网关设备可以是 交互机(三层及以上才能跨网络)、路由器、启用了路由协议的服务器、代理服务器、防火墙等
网关地址就是网关设备的IP地址。
假设我们有两个网络:
网络A的IP地址范围为“192.168.1.1~192.168.1.254”,子网掩码为255.255.255.0
网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0
要实现这两个网络之间的通信,则必须通过网关。
如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机。网络A向网络B转发数据包的过程。
%title插图%num
只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。
默认网关
一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关,就把数据包发给默认指定的网关,由这个网关来处理数据包。现在主机使用的网关,一般指的是默认网关。
扩展:自动设置默认网关
自动设置就是利用DHCP(Dynamic Host Configuration Protocol, 动态主机配置协议)服务器来自动给网络中的计算机分配IP地址、子网掩码和默认网关 。
一旦网络的默认网关发生了变化时,只要更改了DHCP服务器中默认网关的设置,那么网络中所有的计算机均获得了新的默认网关的IP地址。这种方法适用于网络规模较大、TCP/IP参数有可能变动的网络。

(5)添加路由

这个时候,是很重要的,就是我们使用windows下的wireshark抓包工具的时候,比方说我们公司自己的软件,软件在架构上就分为了三个,客户端,服务器,网关。
而这三个同时部署在一个电脑上的时候,我就我无法用wireshark抓包工具抓取他们之间的交互协议,那么原因是什么呢,因为wireshark抓取的是网卡上的东西,那么由于是IP内部之间的传输,所以不会经过网卡,只有向外传输的时候,才会经过网卡用wireshark能够抓取到,所以我们想要它经过网卡,那这样就好办了,就是你指定如果这个包是发往本地IP的,你就让他费点劲去找网关,然后让网关把它再发配回来就好了,因为你自己的网关是知道你自己在哪里的,这样就经过了网卡,然后网关再送回来又经过了网卡,这样就满足了要求,但是会出现两条一样的指令。
%title插图%num
添加路由的指令如下:
route add 192.168.72.109    mask  255.255.255.255      192.168.72.253
意思是说发往这个192.168.72.109的内容先发送到网关 192.168.72.253
而这个子网掩码的作用 限定了网络号,现在就是只有发送给192.168.72.109这个号码的才会发送给网关。
而如果你这样添加路由,
route add 172.16.0.0   mask  255.255.255.0   192.168.72.253
那就是你只要是发送给172.16.0…网络号的就都会送给网关了。

74. 搜索二维矩阵(JS实现)

74. 搜索二维矩阵(JS实现)

1 题目
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的*个整数大于前一行的*后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false

2 思路
这道题就是用二分查找先找到在哪一行,然后在这一行内继续二分查找

3 代码
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
var searchMatrix = function(matrix, target) {
let m = matrix.length;
if (m === 0) return false;
let n = matrix[0].length;

let low = 0;
let high = m – 1;
let mid;
let row = -1;

while(low <= high) {
mid = Math.floor((low + high) / 2);

if (matrix[mid][0] > target) {
high = mid – 1;
} else if (matrix[mid][n – 1] < target) {
low = mid + 1;
} else {
row = mid;
break;
}
}

if (row < 0) return false;

low = 0;
high = n – 1;

while(low <= high) {
mid = Math.floor((low + high) / 2);

if (matrix[row][mid] > target) {
high = mid – 1;
} else if (matrix[row][mid] < target) {
low = mid + 1;
} else {
return true;
}
}

return false;
};

IP地址,子网掩码、默认网关,DNS的设置和工作原理(总结)

概念:

1. 概述

IP地址:人们在Internet上为了区分数以亿计的主机而给每台主机分配的一个专门的地址,通过IP地址就可以访问到每台主机。

子网掩码:不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。

网关:实质上是网络通向其它网络的IP地址。

DNS:域名服务器,为Internet上的主机分配域名地址的IP地址。

2.IP地址

每个IP地址都是由“网络号+主机号”两部分组成。IP地址管理机构在分配IP地址时,只分配网络号,而剩下的主机号则由得到该网络号的单位自行分配。实际上一个IP地址用来标志一个主机或一个路由器或一条链路的接口,当一个主机同时连到两个网络时(如,实验室的代理服务器),该主机必须有两个相应的IP地址,也就必须具备两张网卡。

IP地址由32位二进制数组成,通常是十进制表示,并以“.”分隔。IP地址是一种逻辑地址,用来表示网络中的一个个主机,并且IP地址具有唯一性,即每台机器的IP地址在全世界是唯一的。

DNS是域名服务器,用来解析域名的(域名和IP之间的解析)。如果没有这东西,登陆某个网站时就必须输入该网站的IP地址,有了DNS就可以直接输入网址。这样方便人们的记忆。

比如百度,网址是www.baidu.com,它的IP地址是 61.135.169.125,我可以不用记忆IP地址,直接输入网址即可登陆。

%title插图%num

这种IP地址和域名的对应数据放在公网的服务器里,叫DNS服务器。电脑访问网络时,会首先到这个服务器里,根据域名找到对应的IP地址,然后才真正去往目的地,不过这个过程很快的,根本感觉不到罢了。以上这个过程,叫IP地址解析。所以上网时设置的参数里都有DNS服务器这项,因为需要它首先完成地址解析任务。

DNS服务器在全球范围内都有,一般来说,你在哪个城市,就设置为哪个城市的DNS地址(网络状态好的话无所谓),比如在北京,经常配置的DNS地址有:202.106.0.20,202.106.196.115等,这些上网都可以查到。国内目前开放的114DNS响应也不错,为电信联通移动全国通用DNS,地址也好记:114.114.114.114,位于江苏南京。

3. 子网掩码

子网掩码只有一个功能,就是将IP地址划分为网络地址和主机地址两部分。 如同现实生活中的通讯地址,可以看作省市部分和具体门牌号部分。相同的IP地址,但掩码不一样,则指向的网络部分和主机部分不一样。如IP地址192.168.1.11,255.255.255.0 的掩码表示网络地址192.168.1,主机地址是1;255.255.0.0 的掩码表示网络地址192.168,主机部分是1.1 。

子网掩码用来判断任意两台计算机的IP地址是否在同一个子网中的根据。如果相同,说明两台计算机在同一个子网中,可以直接通讯。当然,子网掩码不同的两台计算机也可以通讯,方法及原理见下文“网关”。。。。。。

IP地址可分为五类:A类地址,B类地址,C类地址,D类地址,E类地址,总体来说,

1.0.0.0到126.255.255.255为A类 主要分配给具有大量主机而局域网络数量较少的大型网络

128.0.0.0到191.255.255.255为B类 一般用于国际性的大公司和政府机构

192.0.0.0到223.255.255.255为C类 用于一般小公司 校内网 研究机构等

244.0.0.0到339.255.255.255为D类 用于特殊用途 又称为广播地址

240.0.0.0到247.255.255.255为E类 暂时保留

IP地址可以说是你的网络地址

4. 网关

网关(Gateway)就是一个网络连接到另一个网络的“关口”。

按照不同的分类标准,网关也有很多种。TCP/IP协议里的网关是*常用的,在这里我们所讲的“网关”均指TCP/IP协议下的网关。

网关实质上是一个网络通向其它网络的IP地址。这个IP地址,是具有路由功能的IP地址,默认网关也是一个网关,也是具有路由功能的设备的IP地址。注意:在填写默认网关时,主机的IP地址必须和默认网关的IP地址处于同一段。

举例:

比如有网络A和网络B,网络A的IP地址范围为“192.168.1.1~192. 168.1.254”,子网掩码为255.255.255.0;网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0。

在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。

如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机。网络B向网络A转发数据包的过程也是如此。

所以说,只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。

 

提问:

(1)那么这个IP地址(作为网关IP地址)是哪台机器的IP地址呢?

网关的IP地址是具有路由功能的设备的IP地址,具有路由功能的设备有路由器、启用了路由协议的服务器(实质上相当于一台路由器)、代理服务器(也相当于一台路由器)。

(2)什么是默认网关 ?

默认网关必须是主机自己所在的网段中的IP地址,而不能填写其他网段中的IP地址。

如果搞清了什么是网关,默认网关也就好理解了。就好像一个房间可以有多扇门一样,一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关,就把数据包发给默认指定的网关,由这个网关来处理数据包。现在主机使用的网关,一般指的是默认网关。

(3)如何设置默认网关 ?

一台主机的默认网关是不可以随随便便指定的,必须正确地指定,否则一台电脑就会将数据包发给不是网关的电脑,从而无法与其他网络的电脑通信。

默认网关的设定有手动设置和自动设置两种方式。

a. 手动设置

手动设置适用于电脑数量比较少、TCP/IP参数基本不变的情况,比如只有几台到十几台电脑。因为这种方法需要在联入网络的每台电脑上设置“默认网关”,非常费劲,一旦因为迁移等原因导致必须修改默认网关的IP地址,就会给网管带来很大的麻烦,所以不推荐使用。

b. 自动设置

自动设置就是利用DHCP服务器来自动给网络中的主机分配IP地址、子网掩码和默认网关。这样做的好处是一旦网络的默认网关发生了变化时,只要更改了DHCP服务器中默认网关的设置,那么网络中所有的电脑均获得了新的默认网关的IP地址。这种方法适用于网络规模较大、TCP/IP参数有可能变动的网络。

附注:

配置默认网关,可以在IP路由表中创建一个默认路径。

赋予路由器IP地址的名称,与本地网络连接的机器必须把向外的流量传递到此地址中以超出本地网络,从而使那个地址成为本地子网以外的IP地址的”网关”.也就是*近常用的网关,当主机路由表目或网络输入不存在于本地主机的路由表时数据包发送到那里。

5. DNS

DNS地址是一个域名服务器地址,它负责把用户的网站地址解析成IP地址。如果这个服务器出现问题,那么你就可能上不了网了。我估计世界上没有哪个强人能记住所有自己经常去的网站的IP地址吧,哈哈。。。

DNS 全名叫 Domain Name Server,中文俗称“域名服务器”,在说明 DNS Server 之前,可能要先说明什么叫 Domain Name(域名)。正如上面所讲,在网上辨别一台电脑的方法是利用 IP地址,但是 IP用数字表示,没有特殊的意义,很不好记,因此,我们一般会为网上的电脑取一个有某种含义又容易记忆的名字,这个名字我们就叫它“DomainName”。

由于ISP的拨号服务器一般都有缺省的DNS,所以你可以不用设置DNS,如果你需要指定一台DNS,你一定要了解这台DNS的准确IP(比如福州的163用户的DNS为202.101.98.55)。

 

查询:

开始–运行–输入cmd–输入ipconfig

%title插图%num

开始–运行–输入cmd–输入ipconfig/all

%title插图%num