Zygote分析
来源:互联网 发布:python 股票 编辑:程序博客网 时间:2024/05/08 11:14
Zygote进程是通过可执行文件app_process创建的,但是app_process除了能创建Zygote进程之外,还可以创建出普通进程。
今天就来说说,Zygote都做了那些事情,这个进程创建出了那些进程。
Zygote是Android系统核心进程之一,是典型的C/S架构,其他程序给Zygote发送一个孕育请求,它就会创建出一个Activity进程。
init进程读取文件的信息
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
socket zygote stream 666
意思是:通知init进程创建一个名字为zygote的进程,这个zygote的进程要执行的程序是/system/bin/app_process后面部分需要传给app_processsocket:表示zygote进程需要一个名称是zygote的socket的资源
引言概述:
由于Zygote是由java编写的,不能直接由init进程启动运行,要在Linux上运行Java程序,必须先生成Dalvik虚拟机,再在Dalvik虚拟机上装载运行ZygoteInit类
App_main.main()
App_main.cpp
主要的功能就是创建成员变量AppRuntim,然后调用其start启动进程
int main(int argc, char* const argv[]){ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { if (errno != EINVAL) { LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno)); return 12; } } //AppRuntim是在app_process中定义的类,继承了系统的AndroidRuntime类,其主要作用是创建和初始化虚拟机。 AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); argc--; argv++; int i; for (i = 0; i < argc; i++) { if (argv[i][0] != '-') { break; } if (argv[i][1] == '-' && argv[i][2] == 0) { ++i; // Skip --. break; } runtime.addOption(strdup(argv[i])); } bool zygote = false; bool startSystemServer = false; bool application = false; String8 niceName; String8 className; ++i; //解析启动参数 while (i < argc) { const char* arg = argv[i++]; if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = ZYGOTE_NICE_NAME; } 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.setTo(arg + 12); } else if (strncmp(arg, "--", 2) != 0) { className.setTo(arg); break; } else { --i; break; } } Vector<String8> args; if (!className.isEmpty()) { args.add(application ? String8("application") : String8("tool")); runtime.setClassNameAndArgs(className, argc - i, argv + i); } else { maybeCreateDalvikCache(); if (startSystemServer) { args.add(String8("start-system-server")); } char prop[PROP_VALUE_MAX]; if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) { LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.", ABI_LIST_PROPERTY); return 11; } String8 abiFlag("--abi-list="); abiFlag.append(prop); args.add(abiFlag); for (; i < argc; ++i) { args.add(String8(argv[i])); } } //设置进程名 if (!niceName.isEmpty()) { runtime.setArgv0(niceName.string()); set_process_name(niceName.string()); } //启动java类,如果带有--zygote执行ZygoteInit if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } 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; }}
AndroidRuntime.cpp类
class AndroidRuntime{public: AndroidRuntime(char* argBlockStart, size_t argBlockSize); virtual ~AndroidRuntime(); enum StartMode { Zygote, SystemServer, Application, Tool, }; void setArgv0(const char* argv0); void addOption(const char* optionString, void* extra_info = NULL); static int registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods); status_t callMain(const String8& className, jclass clazz, const Vector<String8>& args); static jclass findClass(JNIEnv* env, const char* className); void start(const char *classname, const Vector<String8>& options, bool zygote); void exit(int code); void setExitWithoutCleanup(bool exitWithoutCleanup) { mExitWithoutCleanup = exitWithoutCleanup; } static AndroidRuntime* getRuntime(); virtual void onVmCreated(JNIEnv* env); virtual void onStarted() = 0; virtual void onZygoteInit() { } virtual void onExit(int code) { } static android_thread_id_t createJavaThread(const char* name, void (*start)(void *),void* arg); static JavaVM* getJavaVM() { return mJavaVM; } static JNIEnv* getJNIEnv(); static char* toSlashClassName(const char* className); static jstring NewStringLatin1(JNIEnv* env, const char* bytes);private: static int startReg(JNIEnv* env); bool parseRuntimeOption(const char* property,char* buffer,const char* runtimeArg,const char* defaultArg = ""); bool parseCompilerOption(const char* property,char* buffer,const char* compilerArg,const char* quotingArg); bool parseCompilerRuntimeOption(const char* property,char* buffer,const char* runtimeArg,const char* quotingArg); void parseExtraOpts(char* extraOptsBuf, const char* quotingArg); int startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote); Vector<JavaVMOption> mOptions; bool mExitWithoutCleanup; char* const mArgBlockStart; const size_t mArgBlockLength; static JavaVM* mJavaVM; static int javaCreateThreadEtc( android_thread_func_t entryFunction, void* userData, const char* threadName, int32_t threadPriority, size_t threadStackSize, android_thread_id_t* threadId); static int javaThreadShell(void* args);};
AndroidRuntime::AndroidRuntime()
做了两件事情:
- 初始化图形系统
- 将AndroidRuntime指针赋值给gCurRuntime全局指针,使其不被销毁
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) : mExitWithoutCleanup(false), mArgBlockStart(argBlockStart), mArgBlockLength(argBlockLength){ SkGraphics::Init(); mOptions.setCapacity(20); assert(gCurRuntime == NULL); // one per process gCurRuntime = this;}
AndroidRuntime::start()
主要功能是
- 创建虚拟机实例
- 注册Jni方法
- 调用com.android.internal.os.ZygoteInit的main()启动Zygote进程
也就是说,这个函数在创建完成虚拟机之后,运行Java程序。运行类和方法是:com.android.internal.so.ZygoteInit中的main()
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote){ ALOGD(">>>>>> START %s uid %d <<<<<<\n", className != NULL ? className : "(unknown)", getuid()); static const String8 startSystemServer("start-system-server"); for (size_t i = 0; i < options.size(); ++i) { if (options[i] == startSystemServer) { 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");//系统目录从环境变量ANDROID_ROOT中读取,如果没有则设置成/system 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); } JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; if (startVm(&mJavaVM, &env, zygote) != 0) {//启动虚拟机 return; } onVmCreated(env);//调用到AppRuntime中的onVmCreated重载的函数,下面是AppRuntime类的onVmCreate()函数。 if (startReg(env) < 0) {//注册系统的jni函数 ALOGE("Unable to register all android natives\n"); return; } jclass stringClass; jobjectArray strArray; jstring classNameStr; stringClass = env->FindClass("java/lang/String"); assert(stringClass != NULL); strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL); assert(strArray != NULL); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); env->SetObjectArrayElement(strArray, 0, classNameStr); for (size_t i = 0; i < options.size(); ++i) { jstring optionsStr = env->NewStringUTF(options.itemAt(i).string()); assert(optionsStr != NULL); env->SetObjectArrayElement(strArray, i + 1, optionsStr); } char* slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); } else { env->CallStaticVoidMethod(startClass, startMeth, strArray);//调用ZygoteInit类的main() } } 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");}
ZygoteInit.java
- 调用registerZygoteSocket注册Zygotr的socket监听端口,来接收启动应用程序的消息
- 装载系统资源
- 启动systemServer组件
- 进入无限loop中
这里需要注意的就是,SystemServer进程是一个java服务,也是在虚拟机创建之后进行加载。博客的第一篇就是SystemServer的详情。
public static void main(String argv[]) { try { RuntimeInit.enableDdms(); SamplingProfilerIntegration.start(); boolean startSystemServer = false; String socketName = "zygote"; String abiList = null; //解析调用的参数 for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { socketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } if (abiList == null) { throw new RuntimeException("No ABI list supplied."); } //调用registerZygoteSocket注册Zygotr的socket监听端口,来接收启动应用程序的消息 registerZygoteSocket(socketName); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis()); //装载系统资源,包括系统预加载类,Framework资源和openGL的资源,这样当应用程序被fork处理之后,进程内已经包含了这些系统资源。 preload(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis()); SamplingProfilerIntegration.writeZygoteSnapshot(); gcAndFinalize(); Trace.setTracingEnabled(false); if (startSystemServer) { //调用startSystemServer()启动SystemServer startSystemServer(abiList, socketName); } Log.i(TAG, "Accepting command socket connections"); //进入监听的接收消息的循环 runSelectLoop(abiList); closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }
ZygoteInit.registerZygoteSocket()
通过环境变量”ANDROID_SOCKET_PREFIX_zygote”来获取socket句柄,这个句柄是谁创建和设置的呢?
在Zygote服务在init.rc中的定义时候,里面有一条选项:socket zygote stream 660 root system
当Init进程会根据这个选项创建AF_UNIX的socket并把他的句柄放到环境变量ANDROID_SOCKET_PREFIX_zygote中。
那么可能有人就问了,既然有接收的socket端了,那么谁会连接socket服务端呢?
连接服务端了会干什么呢?
我们大致先说一下,谁会连接socket服务端,Android启动一个新的进程是在AMS中完成的,可能会有很多原因导致一个新的进程,最后都是在AMS中startProcessLocked()来完成这个工作。
/** * Registers a server socket for zygote command connections */ private static void registerZygoteSocket(String socketName) { if (sServerSocket == null) { int fileDesc; final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;//ANDROID_SOCKET_PREFIX_zygote try { String env = System.getenv(fullSocketName); fileDesc = Integer.parseInt(env); } catch (RuntimeException ex) { throw new RuntimeException(fullSocketName + " unset or invalid", ex); } try { FileDescriptor fd = new FileDescriptor(); fd.setInt$(fileDesc); sServerSocket = new LocalServerSocket(fd);//创建一个本地socket服务,把句柄保存到FileDescriptor成员变量desctoptor中 } catch (IOException ex) { throw new RuntimeException( "Error binding to local socket '" + fileDesc + "'", ex); } } }
ZygoteInit.preload()
装载Class,Resources,OpenGL等等资源
static void preload() { Log.d(TAG, "begin preload"); preloadClasses(); preloadResources(); preloadOpenGL(); preloadSharedLibraries(); preloadTextResources(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, // for memory sharing purposes. WebViewFactory.prepareWebViewInZygote(); Log.d(TAG, "end preload"); }
ZygoteInit.startSystemServer()
在ZygoteInit中创建了SystemServer服务
private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { long capabilities = posixCapabilitiesAsBits( OsConstants.CAP_BLOCK_SUSPEND, OsConstants.CAP_KILL, OsConstants.CAP_NET_ADMIN, OsConstants.CAP_NET_BIND_SERVICE, OsConstants.CAP_NET_BROADCAST, OsConstants.CAP_NET_RAW, OsConstants.CAP_SYS_MODULE, OsConstants.CAP_SYS_NICE, OsConstants.CAP_SYS_RESOURCE, OsConstants.CAP_SYS_TIME, OsConstants.CAP_SYS_TTY_CONFIG ); String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007", "--capabilities=" + capabilities + "," + capabilities, "--nice-name=system_server", "--runtime-args", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); //fork子进程system_server pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } //进入system_server进程 handleSystemServerProcess(parsedArgs); } return true; }
ZygoteInit.runSelectLoop()
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); //sServerSocket是socket通信中的服务端,即zygote进程 fds.add(sServerSocket.getFileDescriptor()); peers.add(null); while (true) { StructPollfd[] pollFds = new StructPollfd[fds.size()]; for (int i = 0; i < pollFds.length; ++i) { pollFds[i] = new StructPollfd(); pollFds[i].fd = fds.get(i); pollFds[i].events = (short) POLLIN; } try { Os.poll(pollFds, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } for (int i = pollFds.length - 1; i >= 0; --i) { if ((pollFds[i].revents & POLLIN) == 0) { continue; } if (i == 0) { //创建客户端连接 ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { //处理客户端数据事务 boolean done = peers.get(i).runOnce(); if (done) { peers.remove(i); fds.remove(i); } } } }}
ZygoteConnection.runOnce()
此方法用于创建子进程,
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller { String args[]; Arguments parsedArgs = null; FileDescriptor[] descriptors; try { args = readArgumentList();//从socke连接中读入多个行,比如--setuid=1等等 descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { Log.w(TAG, "IOException on command socket " + ex.getMessage()); closeSocket(); return true; } if (args == null) { closeSocket(); return true; } PrintStream newStderr = null; if (descriptors != null && descriptors.length >= 3) { newStderr = new PrintStream( new FileOutputStream(descriptors[2])); } int pid = -1; FileDescriptor childPipeFd = null; FileDescriptor serverPipeFd = null; try { parsedArgs = new Arguments(args);//将参数解析成参数列表 ... applyUidSecurityPolicy(parsedArgs, peer);//检查 applyInvokeWithSecurityPolicy(parsedArgs, peer); applyDebuggerSystemProperty(parsedArgs); applyInvokeWithSystemProperty(parsedArgs); ... //fork出子进程,挂载external storage空间,设置用户id组id等权限,在子进程中设置应用进程的上下文 pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet, parsedArgs.appDataDir); } catch (ErrnoException ex) { logAndPrintError(newStderr, "Exception creating pipe", ex); } catch (IllegalArgumentException ex) { logAndPrintError(newStderr, "Invalid zygote arguments", ex); } catch (ZygoteSecurityException ex) { logAndPrintError(newStderr, "Zygote security policy prevents request: ", ex); } try { if (pid == 0) { IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);//初始化子进程,关闭从Zygote中继承的socket文件描述符 return true; } else { IoUtils.closeQuietly(childPipeFd); childPipeFd = null; return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); } } finally { IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(serverPipeFd); }}
ZygoteConnection.handleChildProc()
private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr) throws ZygoteInit.MethodAndArgsCaller { closeSocket();//关闭从Zygote中继承的socket文件描述符 ZygoteInit.closeServerSocket(); if (descriptors != null) { try { Os.dup2(descriptors[0], STDIN_FILENO); Os.dup2(descriptors[1], STDOUT_FILENO); Os.dup2(descriptors[2], STDERR_FILENO); for (FileDescriptor fd: descriptors) { IoUtils.closeQuietly(fd); } newStderr = System.err; } catch (ErrnoException ex) { Log.e(TAG, "Error reopening stdio", ex); } } if (parsedArgs.niceName != null) { Process.setArgV0(parsedArgs.niceName); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); if (parsedArgs.invokeWith != null) { WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, VMRuntime.getCurrentInstructionSet(), pipeFd, parsedArgs.remainingArgs); } else { RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, null /* classLoader */); }}
RuntimeInit.zygoteInit()
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote"); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit"); redirectLogStreams(); commonInit();//做一些简单的初始化 nativeZygoteInit();//gCurRuntime->onZygoteInit(),其中gCurRuntime是AppRuntime类 applicationInit(targetSdkVersion, argv, classLoader);}nativeZygoteInit()最终会调用到:app_main.cppvirtual void onZygoteInit(){ sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool();}
RuntimeInit.commonInit()
做一些简单的初始化
private static final void commonInit() { if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!"); Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler()); //设置时区 TimezoneGetter.setInstance(new TimezoneGetter() { @Override public String getId() { return SystemProperties.get("persist.sys.timezone"); } }); TimeZone.setDefault(null); //重置Android的log系统以及http.agent LogManager.getLogManager().reset(); new AndroidConfig(); String userAgent = getDefaultUserAgent(); System.setProperty("http.agent", userAgent); NetworkManagementSocketTagger.install(); String trace = SystemProperties.get("ro.kernel.android.tracing"); if (trace.equals("1")) { Slog.i(TAG, "NOTE: emulator trace profiling enabled"); Debug.enableEmulatorTraceOutput(); } initialized = true;}
RuntimeInit.applicationInit()
private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { nativeSetExitWithoutCleanup(true); VMRuntime.getRuntime().setTargetHeapUtilization(0.75f); VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); final Arguments args; try { args = new Arguments(argv); } catch (IllegalArgumentException ex) { Slog.e(TAG, ex.getMessage()); return; } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); invokeStaticMain(args.startClass, args.startArgs, classLoader);//通常传递android.app.ActivityThread}
RuntimeInit.invokeStaticMain()
当异常抛出后
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { ... throw new ZygoteInit.MethodAndArgsCaller(m, argv);//抛出这个异常,然后在ZygoteInit.main()中捕获,执行caller.run()}
然后我们一步步返回
runSelectLoop() throws MethodAndArgsCaller runOnce() throws ZygoteInit.MethodAndArgsCaller handleChildProc()throws ZygoteInit.MethodAndArgsCaller zygoteInit()throws ZygoteInit.MethodAndArgsCaller applicationInit() throws ZygoteInit.MethodAndArgsCaller invokeStaticMain(new ZygoteInit.MethodAndArgsCaller(m, argv);)throws ...
最后:
try{ ... runSelectLoop(abiList); } catch (MethodAndArgsCaller caller) { caller.run();}
通过异常来传递参数
一般这里执行的是ActivityThread方法。
public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args;}public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); }}
- Zygote 分析
- Zygote分析
- Zygote分析
- android 的Zygote 分析
- android 的Zygote 分析
- zygote启动分析
- Zygote的分析
- Zygote工作流程分析
- Zygote Service分析
- zygote和system_server分析
- Zygote启动分析
- Android Zygote源码分析
- 卡巴斯基-zygote病毒分析
- zygote启动过程分析
- Zygote,SystemServer启动分析
- 深入分析Zygote
- zygote,systemserver 启动分析
- Zygote 启动流程分析
- 超市手持电话
- 博为峰JavaEE技术文章 ——MyBatis 在映射中使用枚举类型
- 使用cmd登录mysql server的一些简单操作
- BAT脚本时间格式
- MFC拖动鼠标画矩形中的三个问题
- Zygote分析
- HTML和CSS的知识点
- i怎么获得积分我要下载东西
- C++子类继承时,父子类中含有相同的函数,为何能编译通过
- 硬链接与软链接(符号链接)
- 支持向量机(下)
- Unity3D学习笔记(4)-牧师与魔鬼游戏改进
- 以任意进制形式打印输出,如六进制
- 搭建vscode的c++环境