Android系统启动之init进程启动+Zygote进程启动分析
一、基础概念理解
init进程
Android系统所有进程的祖先,是Android系统内核初始化完毕后,进入用户空间启动的第一个进程。
Android虚拟机
Dalvik虚拟机是谷歌自己设计的用于Android平台的虚拟机。Android4.4同时提供了Dalvik和ART虚拟机。Android5.0以后,Dalvik虚拟机彻底被删除,ART虚拟机取而代之。
Zygote
中文翻译为“受精卵、结合子”
Zygote是Android系统中,负责孵化所有其他应用进程的一个进程,Android系统是基于Linux内核的系统,所以Zygote进程是由Linux启动的用户级init进程创建的,Zygote是init进程的子进程,Zygote是一个java进程,负责启动Android虚拟机(Dalvik、ART)。
二、Android系统启动过程
第一步:BootLoader
BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。
第二步:Kernel
Kernel指的是操作系统内核Linux初始化及启动。
第三步:init进程启动
init进程是Android内核初始化之后创建并启动的第一个进程,是所有进程的父进程,init进程首先调用/system/core/init/init.cpp的main方法,该方法做了两件事:
(1),搭建系统运行环境,创建相关目录,设置相关路径和属性;
(2),解析rc配置文件,执行rc配置文件所要求的动作和命令
三、Zygote进程启动
1,init如何启动Zygote服务
(1)解析init.rc
init.cpp的main方法中其中有一句代码parser.ParseConfig("/init.rc");用来读取rc配置并执行init.rc位于/system/core/rootdir/init.rc
7 import /init.environ.rc
8 import /system/etc/init/hw/init.usb.rc
9 import /init.${ro.hardware}.rc
10 import /vendor/etc/init/hw/init.${ro.hardware}.rc
11 import /system/etc/init/hw/init.usb.configfs.rc
12 import /system/etc/init/hw/init.${ro.zygote}.rc
...
946 # It is recommended to put unnecessary data/ initialization from post-fs-data
947 # to start-zygote in device's init.rc to unblock zygote start.
948 on zygote-start && property:ro.crypto.state=unencrypted
949 wait_for_prop odsign.verification.done 1
950 # A/B update verifier that marks a successful boot.
951 exec_start update_verifier_nonencrypted
952 start statsd
953 start netd
954 start zygote
955 start zygote_secondary
956
957 on zygote-start && property:ro.crypto.state=unsupported
958 wait_for_prop odsign.verification.done 1
959 # A/B update verifier that marks a successful boot.
960 exec_start update_verifier_nonencrypted
961 start statsd
962 start netd
963 start zygote
964 start zygote_secondary
965
966 on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
967 wait_for_prop odsign.verification.done 1
968 # A/B update verifier that marks a successful boot.
969 exec_start update_verifier_nonencrypted
970 start statsd
971 start netd
972 start zygote
973 start zygote_secondary
/init.${ro.zygote}.rc会根据硬件实际的配置,选择导入32位还是64位。在Android12上有这几个选择:init.zygote32.rc,init.zygote64.rc,init.zygote64_32.rc
(2)启动Zygote服务
start zygote是启动Zygote服务的方式,所以Zygote服务就会被启动
Zygote服务就是下面配置文件中的服务【service zygote】
比如设备是64位的,则init启动的就是/system/core/rootdir/init.zygote64.rc配置文件中的【service zygote】
1 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
2 class main
3 socket zygote stream 660 root system
4 onrestart write /sys/android_power/request_state wake
5 onrestart write /sys/power/state on
6 onrestart restart audioserver
7 onrestart restart cameraserver
8 onrestart restart media
9 onrestart restart netd
10 writepid /dev/cpuset/foreground/tasks
综上所述,Zygote进程是Init进程通过解析init.rc过程中又去解析了init.zygote64.rc或者init.zygote32.rc配置文件,以Service的方式创建并启动
2,Zygote服务启动后,做了什么
上面的zygote service配置中,有一个重要选项/system/bin/app_process64,通过执行app_process来进入zygote进程。
app_process主要作用是解析启动参数,然后根据启动参数选择不同的启动模式
执行 /system/bin/app_process64命令之后,程序就会执行到/frameworks/base/cmds/app_process/,该目录下面就是可执行文件,该路径下有app_main.cpp,最终执行的就是app_main.cpp的main方法:
/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
上面标红的是调用app_main.cpp的main()方法的入参,使用中间的空格符切割一下,得到5个值如下:
- /system/bin/app_process64
- -Xzygote
- /system/bin
- --zygote
- --start-system-server
下面的main()函数两个入参,int argc,char* const argv[],其中argc是传入参数的数目,这里应该等于5,argv是传入参数的值,就是上面这5个切割后的字符串
int main(int argc, char* const argv[])
{...
//argc=5
// argv[0]="/system/bin/app_process64"
// argv[1]="-Xzygote"
// argv[2]="/system/bin"
// argv[3]="--zygote"
// argv[4]="--start-system-server"argv[0]="/system/bin/app_process64"被传入runtime
//AppRuntime继承自AndroidRuntime,
//这里创建AppRuntime对象,main方法的操作都是通过这个runtime完成的AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));argc--;//执行此语句块后,argc=4argv++;//argv[0]被忽略
// argc=4
// argv[1]="-Xzygote"
// argv[2]="/system/bin"
// argv[3]="--zygote"
// argv[4]="--start-system-server"int i;for (i = 0; i < argc; i++) {if (argv[i][0] != '-') {//很明显遍历到argv[2]="/system/bin"时候直接breakbreak;}if (argv[i][1] == '-' && argv[i][2] == 0) {++i; // Skip --.break;}//只有argv[1]="-Xzygote"会走到这一步,再往后遍历直接break了runtime.addOption(strdup(argv[i]));}
//上一步又使用了一个参数argv[1]="-Xzygote"。忽略掉,所以代码走到这里还剩下三个参数
// argc=3
// argv[2]="/system/bin"
// argv[3]="--zygote"
// argv[4]="--start-system-server"bool zygote = false;bool startSystemServer = false;bool application = false;String8 niceName;String8 className;++i; // Skip unused "parent dir" argument.while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {// argv[3]="--zygote"满足条件,zygote=true,表示当前进程是zygotezygote = true;//static const char ZYGOTE_NICE_NAME[] = "zygote64";//static const char ZYGOTE_NICE_NAME[] = "zygote";niceName = ZYGOTE_NICE_NAME;//设置niceName为zygote64或者zygote} else if (strcmp(arg, "--start-system-server") == 0) {// argv[4]="--start-system-server",这里说明需要启动System ServerstartSystemServer = 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;}}//设置后面调用ZygoteInit.java的main方法或者RuntimeInit.java的Main方法的参数//这里的参数先是传给了Runtime的start方法,start方法里面又将其转成Java参数的Vector<String8> args;if (!className.isEmpty()) {//非Zygote模式,这种情况可能是application模式或者tool模式args.add(application ? String8("application") : String8("tool"));runtime.setClassNameAndArgs(className, argc - i, argv + i);} else {//Zygote模式,这里可以证明后续调用ZygoteInit的main方法,会启动System Server服务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);// In zygote mode, pass all remaining arguments to the zygote// main() method.for (; i < argc; ++i) {args.add(String8(argv[i]));}}//niceName在参数解析时被设置。此处变更了app_process启动的进程名为zygote。if (!niceName.isEmpty()) {runtime.setArgv0(niceName.string());set_process_name(niceName.string());}//runtime.start启动Android运行环境if (zygote) {//com.android.internal.os.ZygoteInit是Android Java运行时环境的初始化类,runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {//非Zygote模式,则执行RuntimeInit.java的main方法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;}
}
app_main.cpp的main方法,主要做了三件事儿:
- 初始化AndroidRuntime;
- 解析main函数的参数,判断当前是不是Zygote模式;
- 根据是否是Zygote模式,调用AndroidRuntime的start方法传入不同的参数
app_process是c++本地程序,源码目录为 frameworks/base/cmds/app_process/
app_process是可以执行java代码的命令(因为它启动了一个java虚拟机),它有两种启动模式:
1.zygote 模式:通常情况下,在–start-system-server启动参数的配置下,app_process启动之后,直接fork system_server 子进程,拉起整个android系统,之后用来孵化apk进程
2.非zygote模式:分两种子模式
(1).application模式:这种模式是zygote 创建进程后通过shell来重新加载app_process命令再执行的
(2).tool模式:这个模式主要是用来执行调试命令(如am\pm\wm等等)
AppRuntime继承自AndroidRuntime,AndroidRuntime的start方法,我们暂且先看下它的注释吧:
启动Android运行时。这涉及到启动虚拟机并在类中调用“static void main(String[] args)”方法由“className”命名。
传递给main函数两个参数,类名和指定的选项字符串。
/frameworks/base/core/jni/AndroidRuntime.cpp的start方法
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{.../* start the virtual machine */JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;//startVm 创建、启动虚拟机,并且设置相关参数。Dalvik或者ART虚拟机if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {return;}onVmCreated(env);/**向虚拟机注册Android JNI native函数,(系统so库,用户自定义so库,加载函数等)*/if (startReg(env) < 0) {ALOGE("Unable to register all android natives\n");return;}//给接下来要调用的java main方法准备参数//非常经典的在Native层创建Java层对象的操作://创建一个java.lang.String的数组对象//并根据传入的参数对数组对象逐个元素进行赋值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);}....//找到传进来的类的main方法,比如ZygoteInit的main方法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 {//通过JNI技术调用main方法env->CallStaticVoidMethod(startClass, startMeth, strArray);#if 0if (env->ExceptionCheck())threadExitUncaughtException(env);
#endif}}...
}
综上所述,AndroidRuntime.star方法,主要做了三件事儿:
- 创建、启动了Android虚拟机;
- 向虚拟机注册了Android JNI native函数;
- 利用JNI技术调用了参数传进来的className对应类的main()函数。
如果不考虑非Zygote模式,那么Zygote服务的启动,执行app_main.cpp的main方法
而app_main.cpp的main方法做了四件事:
- 创建、启动了Android虚拟机;
- 向虚拟机注册了Android JNI native函数;
- 准备Java类的main方法的参数
- 利用JNI技术调用了ZygoteInit的main()方法。
下面就要分析ZygoteInit了
3,执行ZygoteInit.java的main方法
ZygoteInit:负责Zygote进程Java层的初始化工作
public static void main(String argv[]) {//创建Zygote服务管理类,用来注册Socket监听ZygoteServer zygoteServer = new ZygoteServer();//这里主要目的是拦截创建线程,//调用了这句代码以后,标记着Zygote进程开始Java层的初始化工作//如果此时创建线程会产生错误ZygoteHooks.startZygoteNoThreadCreation();// 将Zygote设置进他自己的进程组try {//将参数pid指定进程所属的组识别码设为参数pgid指定的组识别码。//Os.setpgid(int pid, int pgid)//pid=0表示设置当前进程所在的组的进程组pgid//pgid=0表示当前进程的PID为进程组pgidOs.setpgid(0, 0);} catch (ErrnoException ex) {throw new RuntimeException("Failed to setpgid(0,0)", ex);}final Runnable caller;try {// Report Zygote start time to tron unless it is a runtime restart...日志打印//启动DDMS虚拟机监控调试服务RuntimeInit.enableDdms();//参数解析boolean startSystemServer = false;String socketName = "zygote";String abiList = null;boolean enableLazyPreload = false;for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {//开启系统服务,这个参数是传进来的startSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {//abi类型,"--abi-list="这个参数也是传经来的abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {//解析Socket namesocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {//未知参数throw new RuntimeException("Unknown command line argument: " + argv[i]);}}//没有指定ABI参数会抛异常if (abiList == null) {throw new RuntimeException("No ABI list supplied.");}//注册Zygote的Socket服务端,用来监听接收启动应用程序的消息,这里的IPC不是Binder通信zygoteServer.registerServerSocketFromEnv(socketName);// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.if (!enableLazyPreload) {//没有延迟加载...//执行预加载操作,包括系统预加载类,FrameWork资源,OpenGL资源preload(bootTimingsTraceLog);...} else {//有延迟加载//重置线程优先级,设置为默认的Thread.NORM_PRIORITYZygote.resetNicePriority();}...//运行几个指定的GC,尝试清除几代的软引用和可达的对象,以及任何其他垃圾。//这只在fork()之前有用。强制进行了一次垃圾收集gcAndFinalize();...//在fork之前调用一些安全初始化操作Zygote.nativeSecurityInit();//将整体的的存储目录/storage卸载,取而代之的是挂载临时目录,//这个动作和Android的沙箱(隔离存储)有关Zygote.nativeUnmountStorageOnInit();//呼应前面的ZygoteHooks.startZygoteNoThreadCreation()方法//告诉虚拟机,现在可以创建县城了ZygoteHooks.stopZygoteNoThreadCreation();if (startSystemServer) {//启动systemServer进程//这里的forkSystemServer方法导致进程进程发生了fork,也就是孵化裂变//从Zygote进程变成了Zygote和systemserver两个进程,fork会导致堆栈段的复制//进程会切换执行,涉及到CPU的切换,上下文的切换导致两个进程代码执行都停留在fork函数//两个进程都执行到fork函数等待返回,因此fork函数我们看代码是两次返回。//实际上fork函数的两次返回,是在两个进程中进行的://先是在父进程中返回了被fork出来的子进程的pid//CPU执行切换到子进程之后因为在没有fork子进程,所以返回了0Runnable r = forkSystemServer(abiList, socketName, zygoteServer);//如果r=null,说明当前代码在Zygote进程中执行,直接跳过if (r != null) {//如果r!=null,说明当前代码在子进程(systemserver)中执行//启动进程后返回//因为Runnable的run方法实际上包装了SystemServer的main方法,所以这里会运行main方法r.run();return;}}//走到这一步,代码没有返回,说明这里的代码是在Zygote进程中执行的,//因为如果是孵化的子进程的话,走不到这一步代码就return了//此处进入一个无限循环,处理Zygote Socket接收到的数据,caller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, "System zygote died with exception", ex);throw ex;} finally {//关闭释放Sockte连接//这里的代码主要是给子进程调用的,因为Zygote进程在runSelectLoop中无限循环阻塞了,//正常情况不会执行到这//子进程是由Zygote这个父进程fork出来的,所以也会附带有Socket连接,//但是子进程不需要Zygote的Socket服务,这里保证关闭zygoteServer.closeServerSocket();}//我们在子进程中并退出了select循环。继续执行命令。if (caller != null) {caller.run();}
}
(1)registerServerSocketFromEnv注册Zygote的Socket服务
void registerServerSocketFromEnv(String socketName) {
...//这里的socketName来源于上面的main函数zygote//所以最终fullSocketName=ANDROID_SOCKET_zygotefinal String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
...//找到名称为ANDROID_SOCKET_zygote的环境变量String env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);...FileDescriptor fd = new FileDescriptor();fd.setInt$(fileDesc);//创建一个本地socket服务,赋值给全局变量mServerSocketmServerSocket = new LocalServerSocket(fd);...
}
注意上面的这句代码:
String env = System.getenv(fullSocketName);
获取进程中名字为ANDROID_SOCKET_zygote的环境变量,那么这个环境变量是什么时候写进去的呢?我们前面分析/system/core/rootdir/init.rc时候讲到,他会解析/system/core/rootdir/init.zygote64.rc这个配置文件,init.zygote64.rc里面有这样一句socket zygote stream 660 root system,init进程在解析到这句配置的时候,会创建一个Socket fileDesc(简称socket fd)也就进程独有的文件描述符并且与ANDROID_SOCKET_zygote这个名字绑定,然后将socket名字(ANDROID_SOCKET_zygote)和socket fd注册到init进程的环境变量里面。其他进程都是init进程的子进程,可以通过System.getenv("ANDROID_SOCKET_zygote")获取到这个环境变量。
我们感兴趣的可以看下某 socket fd长什么样子?如下:
root@ubuntu:~# ll /proc/1583/fd
total 0
lrwx------ 1 root root 64 Jul 19 12:37 7 -> socket:[18892]
lrwx------ 1 root root 64 Jul 19 12:37 8 -> socket:[18893]
LocalSocket是Google为我们带来的,比Java的socket效率更高,没有经过协议栈,是Android自己实现的类似共享内存一样的东西,在传输大量数据的时候就需要用到。
(2)preload预加载
Android的Java进程都是通过Zygote进程fork的,Zygote通过预加载类和资源可以加快子进程的执行速度和内存优化,因为预加载的类和资源比较多,所以开机时也需要重点关注preload的耗时。
static void preload(TimingsTraceLog bootTimingsTraceLog) {//设置软引用保护,避免在预加载期间创建的引用被GC回收beginIcuCachePinning();//预加载系统类//读取设备本地/system/etc/preloaded-classes文件,解析该文件,//通过反射技术加载文件中声明的所有类,不同的手机厂商定义的类数量有差异,//有时需要加载数千个类,这也是设备启动慢的原因之一//Android12中preloaded-classes的源码在/frameworks/base/config/preloaded-classespreloadClasses();//预加载系统资源//com.android.internal.R.array.preloaded_drawables//com.android.internal.R.array.preloaded_color_state_lists//Android系统有一个framework-res.apk包,这些系统资源就是存在这个当中//Android应用可以使用这些公共资源preloadResources();//调用native方法加载HAL(硬件抽象层)代码//所谓HAL硬件抽象层nativePreloadAppProcessHALs();加载OpenGL资源preloadOpenGL();//加载一些so库:libandroid.so、libcompiler_rt.so、libjnigraphics.sopreloadSharedLibraries();//加载字体资源preloadTextResources();//要求WebViewFactory所有初始化必须在Zygote进程中进行WebViewFactory.prepareWebViewInZygote();//与前面的beginIcuCachePinning()呼应,取消软引用保护endIcuCachePinning();//初始化JCA相关参数warmUpJcaProviders();sPreloadComplete = true;
}
Zygote进程启动时候预加载了不少资源,那么后续Zygote在fork新进程的时候,采用了COW(copy-on-write)技术,即写时拷贝技术。当App通过fork创建的时候,为了节省开销、加快应用启动,Zygote fork子进程不进行内存复制,而是共享Zygote进程预加载的系统类和系统资源,只有当子进程需要修改共享资源时,才会将共享内存复制到自己的进程内做修改。
(3)forkSystemServer孵化systemserver进程并启动
SystemServer是Android基本服务的提供者,是Android系统运行的最基本需求,所有Service运行在一个叫system_server的进程中,system_server为Android系统提供了各种Service。system_server进程是Android Java虚拟机中第一个进程,可以说整个Android系统的业务都是system_server展开的。
forkSystemServer方法,为孵化SystemServer进程准备参数,并且fork出systemServer进程
注意,forkSystemServer方法因为是fork新的进程出来,新进程代码也会执行到forkSystemServer方法,这就会导致Zygote进程、system_server进程一起从fork函数返回。如果方法是在Zygote进程中执行的,则返回被fork出的SystemServer进程的Runnable对象;如果方法是在SystemServer进程中执行的,则因为其没有继续fork子进程,所以没有需要执行的Runnable任务,返回null
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {...//参数准备String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010","--capabilities=" + capabilities + "," + capabilities,"--nice-name=system_server","--runtime-args","--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,"com.android.server.SystemServer",};ZygoteConnection.Arguments parsedArgs = null;int pid;try {//参数解析,生成目标函数parsedArgs = new ZygoteConnection.Arguments(args);ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);boolean profileSystemServer = SystemProperties.getBoolean("dalvik.vm.profilesystemserver", false);if (profileSystemServer) {parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;}//这里是重点,fork出system_server进程,并运行system_server进程//fork动作会产生一个新的与Zygote一样的新的进程,并且云心新进程,//这样就导致了Zygote进程与system_server两个进程CPU执行权切换,//两个进程都停留在fork方法等待返回,两个fork方法是在两个进程中运行的,自然分别有对应返回值。//当fork方法运行在Zygote进程中时候,则返回其fork出子进程的pid,也就是system_server的pid;//当fork方法运行在system_server进程中时候,因为其没有继续fork的子进程,所以返回0pid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,parsedArgs.gids,parsedArgs.runtimeFlags,null,parsedArgs.permittedCapabilities,parsedArgs.effectiveCapabilities);} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);}/* For child process */if (pid == 0) {//走到这里说明当前代码运行在system_Server进程当中if (hasSecondZygote(abiList)) {//如果设备同时配置了两个Zygote进程,那么这里等待第二个Zygote进程//这里主要是一些设备配置了init.zygote32_64.rc或者init.zygote64_32.rc//这样就会启动两个Zygote进程,一个为主,一个为次waitForSecondaryZygote(socketName);}//子进程是由Zygote孵化出来的,所以也具备了zygoteServer的Socket服务//但是子进程用不到,所以直接关闭zygoteServer.closeServerSocket();//handleSystemServerProcess方法的作用://利用反射技术找到SystemServer的main函数,并将这个函数包装到Runnable的run方法中//返回包装后的Runnable对象return handleSystemServerProcess(parsedArgs);}return null;
}
(4)zygoteServer.runSelectLoop进入循环阻塞等待for新进程的请求
Zygote进程会启动一个Socket本地服务,等待Socket客户端连接请求,当新的App启动的时候,就会请求zygote server服务fork新的进程。
zygoteServer.runSelectLoop会进入循环阻塞等待,等待处理启动新应用的请求,一旦收到请求会fork新的进程出来。
与上面的forkSystemServer一个道理,Zygote在fork新的进程,就会造成新老进程的runSelectLoop方法都执行但是返回值不同。当在子进程中时,会跳出runSelectLoop的循环,返回Runnable对象,也就是说在子进程中是不存在阻塞的,Zygote进程会保持阻塞监控新的连接请求
Runnable runSelectLoop(String abiList) {ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();//执行完下面这句代码,表示fds中只有一个元素,那就是mServerSocketfds.add(mServerSocket.getFileDescriptor());peers.add(null);while (true) {//每次循环都会动态创建新的pollFdsStructPollfd[] 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.pull是Linux系统的一个处理文件描述符的方法,等待文件描述符上的某个事件,I/O服用机制//监听pollFds数组中的事件,当pollFds有事件到来就往下执行//这里的-1表示当没有事件到来时一直阻塞,Os.poll(pollFds, -1);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}//代码走到这里说明上面的poll阻塞方法有返回了,也就是pollFds有数据到来了for (int i = pollFds.length - 1; i >= 0; --i) {if ((pollFds[i].revents & POLLIN) == 0) {//采用I/O多路复用机制,当接收到客户端发出连接请求 或者数据处理请求到来,则往下执行;//否则进入continue,跳出本次循环。continue;}if (i == 0) {//fds[0]是mServerSocket//Socket服务端是LocalServerSocket,Socket客户端应该是LocalSocket//acceptCommandPeer方法就是给mServerSocket创建了一个与之连接的LocalSocket客户端//ZygoteConnection内部分装了LocalSocket客户端ZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);//将通信对象添加到fdsfds.add(newPeer.getFileDesciptor());} else {//这里就是遍历到了上面新添加的Socket通信对象try {ZygoteConnection connection = peers.get(i);//processOneCommand会读取参数并且fork新的子进程及相关操作//因为这里最终会是fork操作,所以同样的会造成父进程子进程都运行这段代码//当在子进程中运行,则command不为空,Zygote中command为空final Runnable command = connection.processOneCommand(this);if (mIsForkChild) {//已经fork了子进程,并且当前代码运行在子进程if (command == null) {//保证fork子进程后必须有command返回throw new IllegalStateException("command == null");}//在子进程中,返回commandreturn command;} else {if (command != null) {//保证父进程不能存在commandthrow new IllegalStateException("command != null");}//Zygote进程,完成了子进程的fork后,应该关闭本次的Socket连接,并清除该连接if (connection.isClosedByPeer()) {connection.closeSocket();peers.remove(i);fds.remove(i);}}} catch (Exception e) {if (!mIsForkChild) {//发生异常后清除操作ZygoteConnection conn = peers.remove(i);conn.closeSocket();fds.remove(i);} else {Log.e(TAG, "Caught post-fork exception in child process.", e);throw e;}} finally {//每次处理完客户端请求后,保证mIsForkChild为falsemIsForkChild = false;}}}}
}
下面看下connection.processOneCommand(this)方法的源码
Runnable processOneCommand(ZygoteServer zygoteServer) {String args[];Arguments parsedArgs = null;FileDescriptor[] descriptors;try {//读取参数args = readArgumentList();descriptors = mSocket.getAncillaryFileDescriptors();} catch (IOException ex) {throw new IllegalStateException("IOException on command socket", ex);}...//解析参数parsedArgs = new Arguments(args);if (parsedArgs.abiListQuery) {handleAbiListQuery();return null;}if (parsedArgs.preloadDefault) {handlePreload();return null;}if (parsedArgs.preloadPackage != null) {handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);return null;}if (parsedArgs.apiBlacklistExemptions != null) {handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);return null;}if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);return null;}if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {throw new ZygoteSecurityException("Client may not specify capabilities: " +"permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));}//参数检查和设置// 检查客户端手有权限指定进程的用户ID、组ID// 如果是 root 进程,可以任意指定// 如果是 sys 进程,需要在ro.factorytest值 > 0时可以指定applyUidSecurityPolicy(parsedArgs, peer);// 判断是否具有invoke-with的执行权限applyInvokeWithSecurityPolicy(parsedArgs, peer);// 如果ro.debuggable是1的话,启动JDWP协议applyDebuggerSystemProperty(parsedArgs);// 如果ro.debuggable是1的话,启动JDWP协议applyInvokeWithSystemProperty(parsedArgs);...//fork子进程,fork方法孵化子进程后,与Zygote进程都会运行并返回值//当前代码运行在Zygote进程中时,返回的pid就是fork得到的子进程的pid//当前代码如果运行在子进程中,则pid=0pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,parsedArgs.instructionSet, parsedArgs.appDataDir);try {if (pid == 0) {//当前处于子进程中zygoteServer.setForkChild();//子进程由Zygote进程fork出来,所以也具备了zygoteServer,但是不需要,所以关闭zygoteServer.closeServerSocket();IoUtils.closeQuietly(serverPipeFd);serverPipeFd = null;//在子进程中完成子进程的初始化工作return handleChildProc(parsedArgs, descriptors, childPipeFd,parsedArgs.startChildZygote);} else {//fork完子进程后,父进程中处理一些关闭及清理的工作IoUtils.closeQuietly(childPipeFd);childPipeFd = null;handleParentProc(pid, descriptors, serverPipeFd);return null;}} finally {IoUtils.closeQuietly(childPipeFd);IoUtils.closeQuietly(serverPipeFd);}
}
我们来看下fork出子进程之后,子进程是如何完成初始化的操作的,handleChildProc方法源码如下
private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,FileDescriptor pipeFd, boolean isZygote) {//关闭本次操作的Socket连接closeSocket();...if (parsedArgs.invokeWith != null) {//启动参数有--invoke-withWrapperInit.execApplication(parsedArgs.invokeWith,parsedArgs.niceName, parsedArgs.targetSdkVersion,VMRuntime.getCurrentInstructionSet(),pipeFd, parsedArgs.remainingArgs);// Should not get here.throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");} else {//通常情况下--invoke-with参数为空,所以代码会进入这里if (!isZygote) {//很显然当前是在子进程运行而非Zygote进程中,所以代码执行这里return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,null /* classLoader */);} else {return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,parsedArgs.remainingArgs, null /* classLoader */);}}
}
我们进入ZygoteInit.zygoteInit方法
public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,String[] argv, ClassLoader classLoader) {...//一些通用的简单初始化RuntimeInit.commonInit();//调用了一个本地native方法,最终调用了我们前面提到的AndroidRuntime的子类AppRuntime的onZygoteInit()方法//初始化binder的使用环境ZygoteInit.nativeZygoteInit();//这里最终调用了RuntimeInit.findStaticMain方法return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,classLoader);
}
我们先来看一下AppRuntime的onZygoteInit()方法的源码,源码在app_main.cpp的AppRuntime下面
virtual void onZygoteInit(){sp<ProcessState> proc = ProcessState::self();ALOGV("App process: starting thread pool.\n");proc->startThreadPool();
}
这个onZygoteInit主要就是用来初始化Binder的使用环境
我们最后看下RuntimeInit.findStaticMain方法,其实这个方法在前面forkSystemServer中也用过这个方法,findStaticMain方法,会将参数传入的className通过反射的技术,找到其main方法,并且包装到Runnable的run方法中返回
protected static Runnable findStaticMain(String className, String[] argv,ClassLoader classLoader) {Class<?> cl;try {cl = Class.forName(className, true, classLoader);} catch (ClassNotFoundException ex) {throw new RuntimeException("Missing class when invoking static main " + className,ex);}Method m;try {m = cl.getMethod("main", new Class[] { String[].class });} catch (NoSuchMethodException ex) {throw new RuntimeException("Missing static main on " + className, ex);} catch (SecurityException ex) {throw new RuntimeException("Problem getting static main on " + className, ex);}...return new MethodAndArgsCaller(m, argv);
}
static class MethodAndArgsCaller implements Runnable {private final Method mMethod;private final String[] mArgs;public MethodAndArgsCaller(Method method, String[] args) {mMethod = method;mArgs = args;}public void run() {...mMethod.invoke(null, new Object[] { mArgs });...}
}
而我们这里,handleChildProc方法,最终返回的MethodAndArgsCaller(Runnable的子类),实际上是利用反射技术将android.app.ActivityThread的main()方法封装到Runnable的run()方法里面。而ActivityThread的main方法正式整个App入口。
(5)caller.run()子进程调用App启动入口方法ActivityThread.main
我们回顾一下ZygoteInit.main方法的最后处理逻辑
public static void main(String argv[]) {...final Runnable caller;try {...if (startSystemServer) {//启动systemServer进程Runnable r = forkSystemServer(abiList, socketName, zygoteServer);if (r != null) {r.run();return;}}//如果当前代码运行在Zygote进程,则返回null//如果当前代码运行在子进程,则返回callercaller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, "System zygote died with exception", ex);throw ex;} finally {zygoteServer.closeServerSocket();}//根据前面的讲解,我们能够知道Zygote在fork进程时会发生两个进程各自运行同一处代码,//根据桑上面的runSelectLoop方法会将Zygote进程处于阻塞状态,无限循环等待Socket客户端//发起请求,比如App的启动会出发zygoteServer与Zocket客户端连接,然后fork出App的进程//当代码运行在App进程(子进程)中时,将会跳出阻塞,返回caller,Zygote父进程继续保持//阻塞监听。if (caller != null) {//执行到这一步,我们可以确定当前代码运行在子进程,此处的run方法运行的就是ActivityThread//的main方法。caller.run();}
}
4,Android如何利用Zygote启动一个新进程
Android启动一个新的进程都是在ActivityManagerService(简称AMS)中完成的,可能会有很多原因导致系统启动一个新的进程,最终在AMS中都是通过调用startProcess()方法来实现。
通过查看ActivityManagerService.startProcess的源码,找到了关键的代码,最终调用了ZygoteProcess类的一个私有方法startViaZygote()方法,我们重点看下startViaZygote的关键代码
private Process.ProcessStartResult startViaZygote(final String processClass,final String niceName,final int uid, final int gid,final int[] gids,int runtimeFlags, int mountExternal,int targetSdkVersion,String seInfo,String abi,String instructionSet,String appDataDir,String invokeWith,boolean startChildZygote,String[] extraArgs)throws ZygoteStartFailedEx {ArrayList<String> argsForZygote = new ArrayList<String>();// --runtime-args, --setuid=, --setgid=,// and --setgroups= must go firstargsForZygote.add("--runtime-args");argsForZygote.add("--setuid=" + uid);argsForZygote.add("--setgid=" + gid);argsForZygote.add("--runtime-flags=" + runtimeFlags);if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {argsForZygote.add("--mount-external-default");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {argsForZygote.add("--mount-external-read");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {argsForZygote.add("--mount-external-write");}argsForZygote.add("--target-sdk-version=" + targetSdkVersion);// --setgroups is a comma-separated listif (gids != null && gids.length > 0) {StringBuilder sb = new StringBuilder();sb.append("--setgroups=");int sz = gids.length;for (int i = 0; i < sz; i++) {if (i != 0) {sb.append(',');}sb.append(gids[i]);}argsForZygote.add(sb.toString());}if (niceName != null) {argsForZygote.add("--nice-name=" + niceName);}if (seInfo != null) {argsForZygote.add("--seinfo=" + seInfo);}if (instructionSet != null) {argsForZygote.add("--instruction-set=" + instructionSet);}if (appDataDir != null) {argsForZygote.add("--app-data-dir=" + appDataDir);}if (invokeWith != null) {argsForZygote.add("--invoke-with");argsForZygote.add(invokeWith);}if (startChildZygote) {argsForZygote.add("--start-child-zygote");}argsForZygote.add(processClass);if (extraArgs != null) {for (String arg : extraArgs) {argsForZygote.add(arg);}}//关键代码就是这一句//1,openZygoteSocketIfNeeded会创建与Zygote进程ServerSocket的连接//2,zygoteSendArgsAndGetResult会将进程启动的参数发送给Zygote进程,fork出新进程后会将进程的pid返回synchronized(mLock) {return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);}
}
openZygoteSocketIfNeeded方法源码
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");if (primaryZygoteState == null || primaryZygoteState.isClosed()) {try {//与Zygote的Socket服务建立连接primaryZygoteState = ZygoteState.connect(mSocket);} catch (IOException ioe) {throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);}...}...if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {try {//针对一些设备配置了init.zygote32_64.rc或者init.zygote64_32.rc//需要与另一个Zygote的Socket服务建立连接secondaryZygoteState = ZygoteState.connect(mSecondarySocket);} catch (IOException ioe) {throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);}...}
...
}
ZygoteState的connect方法
public static ZygoteState connect(LocalSocketAddress address) throws IOException {...final LocalSocket zygoteSocket = new LocalSocket();...try {zygoteSocket.connect(address);
...} catch (IOException ex) {try {zygoteSocket.close();} catch (IOException ignore) {}throw ex;}
...return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,Arrays.asList(abiListString.split(",")));
}
zygoteSendArgsAndGetResult方法源码
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(ZygoteState zygoteState, ArrayList<String> args)throws ZygoteStartFailedEx {...//输出流,参数写入给Zygotefinal BufferedWriter writer = zygoteState.writer;//输入流,从Zygote进程读取fork得到的新进程final DataInputStream inputStream = zygoteState.inputStream;writer.write(Integer.toString(args.size()));writer.newLine();for (int i = 0; i < sz; i++) {String arg = args.get(i);writer.write(arg);writer.newLine();}//App启动参数写入发送writer.flush();//fork子进程后读取新的进程Process.ProcessStartResult result = new Process.ProcessStartResult();result.pid = inputStream.readInt();result.usingWrapper = inputStream.readBoolean();...return result;} catch (IOException ex) {zygoteState.close();throw new ZygoteStartFailedEx(ex);}
}
四、总结
Android系统启动流程,前两步我们简单说明下
1,BootLoader
BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境;
2,Kernel
Kernel指的是操作系统内核Linux初始化及启动;
3,init进程启动
硬件初始化、内核初始化及启动之后,init将会启动
init进程启动先调用了init.cpp中的main()方法,init.cpp的main()方法中有这样一句代码:parser.ParseConfig("/init.rc"),解析init.rc文件,init.rc文件中有zygote start等启动zygote的命令,而且有导入zygote配置的语句import /system/etc/init/hw/init.${ro.zygote}.rc,$(ro.zygote).rc这个引用具体会指向设备具体的配置文件,配置文件按照不同的硬件厂商目前有3种:
init.zygote32.rc、init.zygote32_64.rc、init.zygote64.rc,这三种文件决定了启动32位还是64位的Zygote服务。不过这几个文件中第一行配置就是service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server这一句,service zygote意思是Zygote进程是以Service服务的方式启动,所以会先启动一个Zygote service服务,/system/bin/app_process64表示回去执行app_process,最终会去执行app_main.cpp中的main方法,并且会将下面的参数传给app_main.cpp的main方法:
- /system/bin/app_process64
- -Xzygote
- /system/bin
- --zygote
- --start-system-server
app_main.cpp的main方法,主要干了三件事:
- 初始化AndroidRuntime的子类:AppRuntime;
- 解析main函数的参数,判断当前是不是Zygote模式;
- 根据是否是Zygote模式,调用AndroidRuntime的start方法传入不同的参数
AndroidRuntime的start方法主要做三件事
- 创建、启动了Android虚拟机;
- 向虚拟机注册了Android JNI native函数;
- 利用JNI技术调用了参数传进来的className对应类的main()函数
我们上面的分析,传给start方法的className是ZygoteInit,所以最终会调用ZygoteInit的main方法
我的理解是Zygote进程在上面的init解析init.zygote.rc配置文件后,以service的方式启动了。所以在调用ZygoteInit.java的main方法之前,Zygote进程已经启动了
ZygoteInit的main方法是通过Zygote进程去调用的,总结一下ZygoteInit.java的main方法做了什么:
- ZygoteHooks.startZygoteNoThreadCreation()拦截线程创建;
- Os.setpgid(0, 0)将Zygote设置进他自己的进程组;
- RuntimeInit.enableDdms()启动DDMS虚拟机监控调试服务;
- 解析AndroidRuntime传进来的参数;
- zygoteServer.registerServerSocketFromEnv(socketName)创建注册本地LocalServerSocket服务,用来与后面的客户端LocalSocket通信,注意这里并不是Binder通信;
- preload预加载,包括预加载系统类、Framwork资源、OpenGL等;
- gcAndFinalize在执行fork之前,做一次GC垃圾回收;
- Zygote.nativeSecurityInit()在fork之前调用一些安全初始化操作;
- Zygote.nativeUnmountStorageOnInit()将整体的的存储目录/storage卸载,取而代之的是挂载临时目录,这个动作和Android的沙箱(隔离存储)有关;
- ZygoteHooks.stopZygoteNoThreadCreation()呼应前面的ZygoteHooks.startZygoteNoThreadCreation()方法告诉虚拟机,现在可以创建线程了;
- Runnable r = forkSystemServer fork出system_server进程。如果当前代码执行在system_server进程中,则返回的Runnable不为空,继续执行run方法之后return,这里的run方法就是SystemServer的main方法;如果当前代码执行在Zygote进程中,则Runnable为空,跳过run方法。
- caller = zygoteServer.runSelectLoop,当前代码执行在Zygote进程中则代码进入阻塞无线循环,等待并且等待Socket发起的请求,如果有新的进程要创建启动,则Zygote会fork出新的进程。此时,如果代码运行在Zygote进程中,则继续保持阻塞,等待下一次请求;如果代码运行在子进程,则return跳出runSelectLoop方法,返回caller,意味着子进程跳出阻塞。
- caller.run();这是main方法的最后一步,只有跳出阻塞的子进程才会走到这一步,这里的run方法与上面的forkSystemServer原理一样,都是利用反射技术,将某一个类的main方法封装到Runnable的run方法中,这里封装的是ActivityThread的main方法。
五、题外,为什么Zygote进程与其子进程的通信采用的是Socket而非Binder
(133条消息) android中AMS通知Zygote去fork进程为什么使用socket而不使用binder?_失落夏天的博客-CSDN博客_安卓socke zygote
相关文章:
Android系统启动之init进程启动+Zygote进程启动分析
一、基础概念理解 init进程 Android系统所有进程的祖先,是Android系统内核初始化完毕后,进入用户空间启动的第一个进程。 Android虚拟机 Dalvik虚拟机是谷歌自己设计的用于Android平台的虚拟机。Android4.4同时提供了Dalvik和ART虚拟机。Android5.0以后…...
微信这样的加人方式,既安全又解放双手
在当今竞争激烈的市场环境下,如何高效地管理和运营私域流量成为企业发展的关键。 1.批量自动化加好友的优势 (1)提高效率:批量自动化添加好友功能可以帮助企业添加大量潜在客户或目标客户。相比手动逐个添加好友,自动…...
CVE-2023-5129:libwebp开源库10分漏洞
谷歌为libwebp漏洞分配新的CVE编号,CVSS评分10分。 Libwebp是一个用于处理WebP格式图像编解码的开源库。9月6日,苹果公司安全工程和架构(SEAR)部门和加拿大多伦多大学研究人员在libwebp库中发现了一个0 day漏洞,随后&…...
从零开始的C++(六)
1.类和对象补充: 静态成员,有静态成员函数和静态成员变量,特点是不为类的某个对象所有,而是为同类所有对象共有。因为是为同类对象共同拥有,所以计算对象的大小的时忽略静态成员。因为静态成员是放在静态区࿰…...
leetcode 518. 零钱兑换 II、377. 组合总和 Ⅳ
518. 零钱兑换 II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 …...
【网络安全 --- kali2022安装】kali2022 超详细的安装教程(提供镜像)
如果你还没有安装vmware 虚拟机,请参考下面博客安装 【网络安全 --- 工具安装】VMware 16.0 详细安装过程(提供资源)-CSDN博客【网络安全 --- 工具安装】VMware 16.0 详细安装过程(提供资源)https://blog.csdn.net/m0…...
网络安全(黑客)——自学笔记
前言: 想自学网络安全(黑客技术)首先你得了解什么是网络安全!什么是黑客 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“…...
【C++】List -- 详解
一、list的介绍及使用 https://cplusplus.com/reference/list/list/?kwlist list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 list 的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中&…...
浅谈.net 垃圾回收机制(1)
大家都知道.net程序创建对象后没法写代码销毁对象,它有它自己的处理机制,今天来大概说说它的原理,探讨下它是如何管理对象即管理内存的 在程序里使用new 关键字实例化一个对象 如果这个对象类型是引用类型则在堆上分配然后由GC管理 new 操作…...
超大视频如何优雅切片
背景 有一次录屏产生了一个大小为33G的文件, 我想把他上传到B站, 但是B站最大只支持4G. 无法上传, 因此做了一个简单的探索. 质疑与思考 a. 有没有一个工具或一个程序协助我做分片呢? 尝试 a. 必剪 > 有大小限制, 添加素材加不进去(而且报错信息也提示的不对) b. PR &…...
计算机竞赛 题目:基于深度学习卷积神经网络的花卉识别 - 深度学习 机器视觉
文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 基…...
Spring总结的question
Spring 一. 控制反转(IoC) 1.手动 使用了Spring的Configuration和Bean注解来明确指定了哪些类需要被纳入容器的管理。在AppConfig配置类中,通过Bean注解创建了Service和Controller的实例,Spring会自动将这些实例纳入容器的管理,并处理它们…...
LVS和keepalived
Keepalived及其工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案,可以解决静态路由出现的单点故障问题。 在一个LVS服务集群中通常有主服务器(MASTER)和备份服务器(BACKUP)两种角色的服务器&#x…...
2023年腾讯云优惠券(代金券)无门槛领取方法汇总
腾讯云作为国内知名的云计算服务提供商,为了吸引用户,腾讯云经常推出各种优惠活动,其中包括优惠券的免费发放。通过使用优惠券,可以享受到更多的折扣和优惠,节省成本,获得更好的用户体验。那么,…...
linux scsi命令读取文件
SCSI Read(10)是一种用于从SCSI设备读取数据的命令。下面是一个简单的示例代码,演示如何使用SCSI Read(10)命令来读取指定大小的文件: #include <stdio.h> #include <stdlib.h> #include <string.h>#define READ_CAPACITY_CMD 0x2…...
c#设计模式-行为型模式 之 策略模式
🚀简介 🐤作为一个开发人员,开发需要选择一款开发工具,如在编写C#时,我们可以选择VisualStudio进行开发,也可以使用Rider 进行开发。 🐳该模式定义了一系列算法,并将每个算法封装起来…...
【拿完年终奖后】想要转行网络安全,一定不要错过这个时间段。
网络安全,作为当下互联网行业中较为热门的岗位,薪资可观、人才需求量大,作为转行必考虑。 在这里奉劝所有零基础想转行(入门) 网络安全的朋友们 在转行之前,一定要对网络安全行业做一个大概了解…...
day10_复习_数组_方法
非常重要的: 方法设计(参数,返回值,调用) 数组也重要,但是后续很少用! 是因为后期有更简单的【集合】,重要在于是一种思想,也是一种代码逻辑 关于数组: 声明创建取值,存值遍历面试|算法 --> 排序内存图(堆,栈,引用) 今天 数组工具类:Arrays数组扩容(拷…...
Linux:TCP三握四挥简析
文章目录 1. 前言2. 背景3. TCP连接的建立和断开3.1 TCP协议状态机3.2 TCP的三握四挥3.2.1 TCP 连接建立的三次握手过程分析3.2.1.1 服务端和客户端套接字的创建3.2.1.2 服务端进入 LISTEN 状态3.2.1.3 服务端在 LISTEN 状态等待客户端的 SYN 请求3.2.1.4 客户端向服务端发送 S…...
2023年全球市场数字干膜测量仪总体规模、主要生产商、主要地区、产品和应用细分研究报告
内容摘要 按收入计,2022年全球数字干膜测量仪收入大约149.2百万美元,预计2029年达到191.6百万美元,2023至2029期间,年复合增长率CAGR为 3.6%。同时2022年全球数字干膜测量仪销量大约 ,预计2029年将达到 。2022年中国市…...
Python爬虫脚本的基本组成
一个基本的Python爬虫脚本通常由以下几部分组成: 导入必要的库:Python中有许多库可用于爬虫,如requests用于发送HTTP请求,BeautifulSoup用于解析HTML或XML,selenium用于模拟浏览器操作等。你需要根据你的需求导入相应…...
IIS部署Flask
启用 CGI 安装wfastcgi pip install wfastcgi 启用 wfastcgi 首先以管理员身份运行wfastcgi-enable来在IIS上启用wfastcgi,这个命令位于c:\python_dir\scripts,也就是你需要确保此目录在系统的PATH里,或者你需要cd到这个目录后再执行。 #…...
告警繁杂迷人眼,多源分析见月明
随着数字化浪潮的蓬勃兴起,网络安全问题日趋凸显,面对指数级增长的威胁和告警,传统的安全防御往往力不从心。网内业务逻辑不规范、安全设备技术不成熟都会导致安全设备触发告警。如何在海量众多安全告警中识别出真正的网络安全攻击事件成为安…...
【Python】概述
【Python】概述 特点 Python 是一种面向对象、解释性、弱类型(动态数据类型)的脚本语言(高级程序设计语言)。 由于Python是解释型语言,所以具有跨平台特性。 解释型语言: 这意味着开发过程中没有了编译…...
MySQL运维之日志管理
目录 一、日志 1.1错误日志 1.2二进制日志 1.2.1格式 1.2.2查看 1.2.3删除 1.3查询日志...
Yolov5 ONNX导出报错: export failure: Unsupported ONNX opset version: 17
目录 1.问题描述 1.1 报错1 : 1.2 报错 2 2.解决方案 介绍 ONNX(Open Neural Network Exchange)是一个用于机器学习模型的开放式标准,它旨在使不同的深度学习框架能够将训练好的模型在不同平台上无缝运行。它是由Microsoft和F…...
2023年全球市场儿科PICC导管总体规模、主要生产商、主要地区、产品和应用细分研究报告
内容摘要 按收入计,2022年全球儿科PICC导管收入大约 百万美元,预计2029年达到 百万美元,2023至2029期间,年复合增长率CAGR为 %。同时2022年全球儿科PICC导管销量大约 ,预计2029年将达到 。2022年中国市场规模大约为 百…...
Adler-32算法使用Neon优化
1、简单实现 下面代码是Adler-32算法的简单实现,我们来整理一下这段代码的逻辑: A = 1 + D1 + D2 + ... + Dn (mod 65521)B = (1 + D1) + (1 + D1 + D2) + ... + (1 + D1 + D2 + ... + Dn) (mod 65521)= nxD1 + (n-1) x D2 + (n-2) x D3 + ... + Dn + n (mod 65521)Adler-3…...
数据结构-----平衡二叉树
目录 前言 1.平衡二叉树 1.1概念与特点 1.2与二叉排序树比较 1.3判断平衡二叉树 2.平衡二叉树的构建 2.1平衡因子 BF 2.2 LL型失衡(右旋) 2.3 RR型失衡(左旋) 2.4 LR型失衡(先左旋再右旋) 2.5 RL…...
vue3 keepalive翻页保存页面状态
描述 实现页面 A-> B , B->A(A保存之前页面状态,不刷新页面) // router/index.tsimport { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vueconst router createRouter({h…...
关于做旅游网站的参考文献/建立网站一般要多少钱
序曲出塞二首 其一【唐】 秦时明月汉时关,万里长征人未还。但使龙城飞将在,不教胡马度阴山。这是一首边塞诗,昌龄从描写景物景入手,首句勾勒出一幅冷月照边关的苍凉景象。"秦时明月汉时关"暗示了这里的战事自秦汉以来一…...
上海高端网站公司/工具大全
本文详细介绍四种事务隔离级别,并通过举例的方式说明不同的级别能解决什么样的读现象。并且介绍了在关系型数据库中不同的隔离级别的实现原理。 在DBMS中,事务保证了一个操作序列可以全部都执行或者全部都不执行(原子性)ÿ…...
怎么下载在别的网站上用的js特效/网站推广方案有哪些
文章目录C 输入std::operator>>std::getlinestd::istream::getlinestd::istream::getstd::istream::ignore总结牛客 OJ 输入输出练习AB(1)AB(2)AB(3)AB(4)AB(5)AB(6)AB(7)字符串排序(1)字符串排序(2)字符串排序(3)自测本地通过提交为0C 输入 std::operator>> #in…...
做c语言题目的网站/智能识别图片
Writer:BYSocket(泥沙砖瓦浆木匠)一、前言针对并发,老生常谈了。目前一个通用的做法有两种:锁机制:1.悲观锁;2.乐观锁。但是这篇我主要用于记录我这次处理的经历,另外希望能看的大神,大牛&#…...
南和企业做网站/站长综合查询工具
ISIS将网络层又分了两个层: 子网独立子层(subnetwork independent sublayer)(为传输层提供统一服务); 子网依赖子层(subnetwork dependent sublayer)(存取数据链路层提供…...
李沧网站建设公司/seo排名诊断
点击↑↑技成培训 ,关注并置顶即可长期免费订阅20万工控人关注的微信平台:技术分享、学习交流、工控视频当一个或者多个指令(程序)重复多次(次数可知)时,可使用FOR指令。FOR为有限次循环指令。如上图,程序的执行过程主要分为3个步…...