安卓系统中所有的应用程序进程和系统服务进程SystemService都是由Zygote进程fork出来的,因此就先从源码级别来看看Zygote进程的启动过程。而Linux中所有的进程都由init进程创建,而Zygote也不例外,所以在系统的bootable\diskinstaller\init.rc文件中肯定有Zygote进程的启动代码,如下:
service zygote /system/bcorein/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的进程,进程要执行的程序为/system/bcorein/app_process,而后面则是需要执行的参数;第二行指定了这个进程所属的类别,此处指定了main,在/system/core/init/ReadMe.txt中的定义如下,表示同一个类别的服务会同时启动或停止,关于init.rc中的启动脚本的格式说明都在这个文档里:
**class <name> Specify a class name for the service. All services in a
named class may be started or stopped together. A service
is in the class "default" if one is not specified via the
class option.**
第三行则是创建一个名为zygote的socket资源,要启动应用程序时ActivityManagerService就是通过这个socket给zygote进程发送fork请求的,后面的一些onrestart开头的指令则是服务在重启的时候需要执行的。启动脚本就分析的差不多了,下面便是分析app_process,源代码在frameworks/base/cmds/app_process/app_main.cpp文件中,入口函数为main函数。整个执行过程的序列图如下:
1.app_process.main
首先来看app_process中的main代码,这段代码在frameworks\base\cmds\app_process\App_main.cpp中:
int main(int argc, char* const argv[])
{
//最开始的一部分是针对arm平台的特殊处理,大意是说:暂时恢复到兼容的内存布局,以避免破坏第三方应用程序,在将来的安卓版本会被去掉;还提到了这个会破坏一些程序引入的过期的安卓链接器。具体什么意思还没搞明白。
#ifdef __arm__
/*
* b/7188322 - Temporarily revert to the compat memory layout
* to avoid breaking third party apps.
*
* THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE.
*
* http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466
* changes the kernel mapping from bottom up to top-down.
* This breaks some programs which improperly embed
* an out of date copy of Android's linker.
*/
char value[PROPERTY_VALUE_MAX];
property_get("ro.kernel.qemu", value, "");
bool is_qemu = (strcmp(value, "1") == 0);
//下面的代码则是获得"ro.kernel.qemu"的值不为1没有“NO_ADDR_COMPAT_LAYOUT_FIXUP”这个环境变量,则添加这个环境变量值为1,且重新执行这个main函数
if ((getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) && !is_qemu) {
//#define personality(pers) (pers & PER_MASK)
int current = personality(0xFFFFFFFF);
if ((current & ADDR_COMPAT_LAYOUT) == 0) {
personality(current | ADDR_COMPAT_LAYOUT);
setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1);
execv("/system/bin/app_process", argv);
return -1;
}
}
//删除这个环境变量
unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP");
#endif
//根据参数设置两个全局变量
// 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继承自AndroidRuntime
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
//这里会添加-Xzygote这个参数且返回1
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) {//第一轮循环设置父目录为/system/bin
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;
//由于上面zygote已经被设置为true,因此会执行ZygoteInit,而第二个参数则是"start-system-server"
if (zygote) {
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;
}
还有两点需要注意的地方,首先是在AppRuntime初始化的时候会调用其父类的构造函数,代码如下所示:
AndroidRuntime::AndroidRuntime() :
mExitWithoutCleanup(false)
{
...
assert(gCurRuntime == NULL); // one per process
gCurRuntime = this;
}
在构造函数的最后,其会判断全局变量gCurRuntime是否已经被赋值,如果没有则将当前对象赋值给他,这样其他地方需要这个对象的指针就可以通过下面这个函数来获得:
AndroidRuntime* AndroidRuntime::getRuntime()
{
return gCurRuntime;
}
第二点,上面的在运行start函数之前先调用AndroidRuntime类中的addVMArguments函数,功能是往全局变量mOptions中添加虚拟机启动的参数(目前理解是这样的)并返回添加成功的参数个数,这里会把参数-Xzygote添加进去:
int AndroidRuntime::addVmArguments(int argc, const char* const argv[])
{
int i;
for (i = 0; i<argc; i++) {
if (argv[i][0] != '-') {
return i;
}
if (argv[i][1] == '-' && argv[i][2] == 0) {
return i+1;
}
JavaVMOption opt;
memset(&opt, 0, sizeof(opt));
opt.optionString = (char*)argv[i];
mOptions.add(opt);
}
return i;
}
总的来说,app_main函数就是创建一个AppRuntime对象,然后设置各种启动参数,最终调用从AndroidRuntime继承过来的satrt的函数做进一步的处理。
2.AndroidRuntime.start
由于AppRuntime中没有start函数,所以这里调用的必然是AndroidRuntime种的start函数,代码在frameworks\base\core\jni\AndroidRuntime.cpp中,这里其实还有一段注释,大意是说这个start函数会启动传入的className的静态main函数,而第二个参数则是main函数的参数:
void AndroidRuntime::start(const char* className, const char* options)
{
...
/* start the virtual machine */
//初始化JniInvocation对象
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
//启动虚拟机,首先在startVm中根据大量的系统属性和定义来指定一些参数,随后调用JniInvocation的JNI_CreateJavaVM函数来进行启动dalvik虚拟机,而后在JNI_CreateJavaVM函数中再调用dvmStartup进行启动,最后在函数调用各种函数来完成
if (startVm(&mJavaVM, &env) != 0) {
return;
}
//这个函数只有一行注释:If AndroidRuntime had anything to do here, we'd have done it in 'start'.
onVmCreated(env);
/* Register android functions. */
//startReg会调用register_jni_procs注册JNI方法
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.
*/
//把com.android.internal.os.ZygoteInit中的.换成/,随后找到这个类的main函数并调用。当前线程成为虚拟机的主线程,直到虚拟机退出了才会返回
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");
}
还需要注意以下几点,首先注册的jni方法非常多,比如:
第二点就是变量env其实是JNINativeInterface对象的指针,其调用的函数都无法找到源代码,只能找到定义的接口,所以目前无法看其详细的实现。
3.ZygoteInit.main
ZygoteInit已经是Java代码了,这个类在frameworks\base\core\java\com\android\internal\os\ZygoteInit.java源文件中,这个类的说明如下,大意就是说这个类是用来启动zygote进程并初始化一些类的,然后等待一个socket信号,他就会fork处带有初始状态的虚拟机的子进程:
/**
* Startup class for the zygote process.
*
* Pre-initializes some classes, and then waits for commands on a UNIX domain
* socket. Based on these commands, forks off child processes that inherit
* the initial state of the VM.
*
* Please see {@link ZygoteConnection.Arguments} for documentation on the
* client protocol.
*
* @hide
*/
该类中main函数的代码如下:
public static void main(String argv[]) {
... ...
//注册sockets,首先获取系统的socket环境变量,而后创建文件描述符,最后创建服务器端socket,监听socket信号
registerZygoteSocket();
... ...
//如果参数正确则启动SystemServer()
if (argv[1].equals("start-system-server")) {
startSystemServer();
} else if (!argv[1].equals("")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
//这个函数是一个while循环,一直等待接收socket连接并处理
runSelectLoop();
closeServerSocket();
... ...
}
4.ZygoteInit.registerZygoteSocket
这个函数主要是创建新的socket服务端套接字,代码如下:
private static void registerZygoteSocket() {
if (sServerSocket == null) {
int fileDesc;
try {
//获取socket环境变量
String env = System.getenv(ANDROID_SOCKET_ENV);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(
ANDROID_SOCKET_ENV + " unset or invalid", ex);
}
try {
//创建文件描述符然后创建服务端套接字
sServerSocket = new LocalServerSocket(
createFileDescriptor(fileDesc));
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
ANDROID_SOCKET_ENV的定义如下:
private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
但是为何仅仅通过'ANDROID_SOCKET_zygote'这个字符串就能够获得socket环境变量呢?原来在init进程解释执行init.rc中启动service的代码时都会调用service_start函数,而这个函数中会根据init.rc中socket的描述来创建service的socket,有如下代码:
void service_start(struct service *svc, const char *dynamic_args)
{
... ...
pid = fork();
if (pid == 0) {
struct socketinfo *si;
struct svcenvinfo *ei;
... ...
//从传入的svc中获得sockets参数并创建socket
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid);
if (s >= 0) {
//添加到环境变量中
publish_socket(si->name, s);
}
}
}
其中publish_socket函数非常关键,代码如下:
static void publish_socket(const char *name, int fd)
{
char key[64] = ANDROID_SOCKET_ENV_PREFIX;
char val[64];
strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1,
name,
sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
snprintf(val, sizeof(val), "%d", fd);
//添加到环境变量
add_environment(key, val);
/* make sure we don't close-on-exec */
fcntl(fd, F_SETFD, 0);
}
而这里传进来的name变量便是字符串"zygote",ANDROID_SOCKET_ENV_PREFIX的宏定义如下:
#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_"
这样,"ANDROID_SOCKET_zygote"这个字符串便作为/dev/sockets/zygote这个socket的文件描述符的key,这样就能从环境变量就有zygote的socket了。与此同时这里的执行create_socket函数和ZygoteInit.registerZygoteSocket是在同一个进程中,所以能够直接通过字符串获得socket,但是如果其他进程需要获得这个socket,那就需要通过打开socket文件来进行通信了。
5.ZygoteInit.startSystemServer()
这个函数是用来启动SystemServer这个进程的,代码如下:
private static boolean startSystemServer()
throws MethodAndArgsCaller, RuntimeException {
... ...
/* Hardcoded command line to start the system server */
//这些是启动SystemServer进程的参数
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007",
"--capabilities=" + capabilities + "," + capabilities,
"--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 */
//fork出一个子进程以启动SystemServer
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 */
//如果返回的pid为0则会执行handleSystemServerProcess函数,注意这里是SystemServer进程执行这个函数,而不是Zygote这个进程
if (pid == 0) {
handleSystemServerProcess(parsedArgs);
}
return true;
}
6.ZygoteInit.handleSystemServerProcess
这个函数首先会关闭掉从父进程继承socket,因为他用不到这些socket。随后则会调用Runtime.Init函数进行进一步的操作:
private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws ZygoteInit.MethodAndArgsCaller {
closeServerSocket();
// set umask to 0077 so new files and directories will default to owner-only permissions.
Libcore.os.umask(S_IRWXG | S_IRWXO);
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
}
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
null, parsedArgs.remainingArgs);
} else {
/*
* Pass the remaining arguments to SystemServer.
*/
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs);
}
/* should never reach here */
}
7.RuntimeInit.zygoteInit
这个类在frameworks\base\core\java\com\android\internal\os\RuntimeInit.java这个文件中,代码如下:
public static final void zygoteInit(int targetSdkVersion, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
//重定向"System.out"和"System.err"到Android Log
redirectLogStreams();
//用于初始化诸如:默认异常处理、时区、日志、Http代理、流量、调试跟踪信息等各种设置
commonInit();
//没找到代码,猜测可能是zygote的初始化函数
nativeZygoteInit();
applicationInit(targetSdkVersion, argv);
}
这个函数进行一系列的初始化等操作后调用同一个类中的applicationInit函数继续处理。
8.RuntimeInit.applicationInit
这个函数主要就是通过invokeStaticMain函数来调用在参数中的class的main函数,而这个class正是SystemServer
private static void applicationInit(int targetSdkVersion, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
... ...
// Remaining arguments are passed to the start class's static main
invokeStaticMain(args.startClass, args.startArgs);
}
9.SystemServer.main
这个类在frameworks\base\services\java\com\android\server\SystemServer.java这个文件中,代码如下:
public static void main(String[] args) {
//前面的代码主要就是加载一些库以及做一些设置而已
... ...
// Initialize native services.
//初始化一些服务,具体的代码暂时没找到,等找到了补上
nativeInit();
// This used to be its own separate thread, but now it is
// just the loop we run on the main thread.
ServerThread thr = new ServerThread();
//这个会调用ServerThread的initAndLoop函数
thr.initAndLoop();
}
}
10.SystemThread.initAndLoop
这个函数非常大,主要用于启动一些非常关键的系统服务以及循环的给zygote发送socket请求以运行应用程序。鉴于函数比较大,所以下面将分块讲解功能以便于理解。
10.1 创建消息循环队列
首先会创建一个Looper,等到所有的初始化任务完成之后在启动这个循环:
public void initAndLoop() {
Looper.prepareMainLooper();
... ...
//最后
Looper.loop();
Slog.d(TAG, "System ServerThread is exiting!");
}
10.2 各种服务变量的定义
从下面这些变量的定义可以看到这个函数将会初始化这个服务,这些服务都是间接继承自Binder这个类,因此说是Binder服务更为恰当,至于什么是Binder,可以参考http://www.cnblogs.com/linucos/archive/2012/05/24/2516623.html:
public void initAndLoop() {
... ...
Installer installer = null;
AccountManagerService accountManager = null;
ContentService contentService = null;
LightsService lights = null;
PowerManagerService power = null;
DisplayManagerService display = null;
BatteryService battery = null;
VibratorService vibrator = null;
AlarmManagerService alarm = null;
MountService mountService = 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;
BluetoothManagerService bluetooth = null;
DockObserver dock = null;
UsbService usb = null;
SerialService serial = null;
TwilightService twilight = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
ConsumerIrService consumerIr = null;
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;
AssetAtlasService atlas = null;
PrintManagerService printManager = null;
... ...
}
10.3 各个服务的创建
部分创建上面定义的Binder服务的代码:
public void initAndLoop() {
... ...
// bootstrap services,引导服务的创建
Slog.i(TAG, "Waiting for installd to be ready.");
installer = new Installer();
installer.ping();
Slog.i(TAG, "Power Manager");
power = new PowerManagerService();
ServiceManager.addService(Context.POWER_SERVICE, power);
Slog.i(TAG, "Activity Manager");
context = ActivityManagerService.main(factoryTest);
//其他服务,鉴于服务太多,其他服务的创建就不一一列出了
Slog.i(TAG, "Display Manager");
display = new DisplayManagerService(context, wmHandler);
ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
Slog.i(TAG, "Telephony Registry");
telephonyRegistry = new TelephonyRegistry(context);
ServiceManager.addService("telephony.registry", telephonyRegistry);
Slog.i(TAG, "Scheduling Policy");
ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
... ...
}
10.4 测试服务是否正确启动
下面是部分测试的代码:
public void initAndLoop() {
... ...
try{
ActivityManagerService.self().startObservingNativeCrashes();
} catch (Throwable e) {
reportWtf("observing native crashes", e);
}
if (!headless) {
startSystemUi(contextF);
}
try {
if (mountServiceF != null) mountServiceF.systemReady();
} catch (Throwable e) {
reportWtf("making Mount Service ready", e);
}
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);
}
... ...
}
这样initAndLoop函数就执行完了,现在一直回溯直到第三步。
11.ZygoteInit.runSelectLoop
现在又回到了ZygoteInit的main函数中,接下去执行的是runSelectLoop函数,这个函数运行一个while循环来接受SystemServer进程发送过来的fork子进程的消息:
private static void runSelectLoop() throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
//把几个socket文件描述符放入fds数组中
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
int loopCount = GC_LOOP_COUNT;
while (true) {
int index;
try {
fdArray = fds.toArray(fdArray);
//返回可读的socket的在数组中的索引
index = selectReadable(fdArray);
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {
ZygoteConnection newPeer = acceptCommandPeer();
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done;
//runOnce用来fork子进程
done = peers.get(index).runOnce();
if (done) {
peers.remove(index);
fds.remove(index);
}
}
}
}
总的来说,Zygote进程的启动过程基本上分析的差不多了,当然还有少部分函数由于暂时找不到源码位置尚未分析。主要的流程是:
1.设置参数,启动虚拟机,调用Zygote的main函数
2.注册Zygote的socket套接字,fork SystemServer进程并使子进程执行指定函数
3.开启各种Binder服务,开启接收消息的循环队列
4.Zygote开始不断尝试从socket读取指令以便启动新的应用程序
这是学习安卓源代码系列第一篇,学习前人的经验,结合自己的实践学习一些东西,希望自己有所收获,进步地更快吧。后面要加快更新进度!