Android系统启动-Zygote详解(Android 14)

news2024/11/20 0:35:09

一、什么是Zygote

在上一篇文章Android系统启动-init进程详解(Android 14)中,分析了init进程,在init进程启动的第二阶段会解析init.*.rc文件,启动多个进程,其中包括Zygote。

Zygote又叫孵化器,是 Android 系统创建的第一个Java进程,它是所有Java进程的父进程。包括 system_server 进程以及所有的App进程都是Zygote的子进程。

Zygote 进程作为 Socket 的 Server 端,接收处理系统中创建进程的请求。Android中的应用进程的创建都是应用进程通过 Binder 发送请求给 system_server 进程中的 ActivityManagerService(AMS) ,AMS 再发送 Socket 消息给 Zygote 进程,统一由 Zygote 进程创建出来的。整个过程如下图所示:

二、Zygote的启动

app_main.cpp

在init进程启动后,会解析 init.rc 文件,创建和加载 service 字段指定的 Zygote 进程。在 /system/core/rootdir/init.rc 中,通过如下引用来 load zygote 的 rc:

import /system/etc/init/hw/init.${ro.zygote}.rc

这里根据属性 ro.zygote 的内容来引入不同的 Zygote 启动脚本。Android 5.0以后,Android开始支持64位编译,Zygote 进程也随之引入了32/64位的区别。所以,这里通过 ro.zygote 属性来控制启动不同版本的 Zygote 进程。
ro.zygote属性会有四种不同的值:

  • zygote32:代表32位模式
  • zygote32_64:代表32模式为主,64位模式为辅
  • zygote64:代表64位模式
  • zygote64_32:代表64模式为主,32位模式为辅

这里我们以64位处理器为例,init.zygote64.rc代码如下:

// system/core/rootdir/init.zygote64.rc
 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
     class main
     priority -20
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
     socket usap_pool_primary stream 660 root system
     onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
     onrestart write /sys/power/state on
     # NOTE: If the wakelock name here is changed, then also
     # update it in SystemSuspend.cpp
     onrestart write /sys/power/wake_lock zygote_kwl
     onrestart restart audioserver
     onrestart restart cameraserver
     onrestart restart media
     onrestart restart media.tuner
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh MaxPerformance
     critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

这段脚本要求 init 进程创建一个名为 zygote 的进程,该进程要执行的程序是“/system/bin/app_process”。并且为 zygote 进程创建一个 socket 资源 (用于进程间通信,ActivityManagerService 就是通过该 socket 请求 zygote 进程 fork 一个应用程序进程)。

app_process64对应的代码定义在 frameworks/base/cmds/app_process 中,不管是app_process、app_process32 还是 app_process64 对应的源文件都是 app_main.cpp。

因此,Zygote 对应的可执行程序为 app_process,该程序对应的源文件为 app_main.cpp,入口函数为 main 函数,进程名为 Zygote。

// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
    if (!LOG_NDEBUG) {
      String8 argv_String;
      for (int i = 0; i < argc; ++i) {
        argv_String.append("\"");
        argv_String.append(argv[i]);
        argv_String.append("\" ");
      }
      ALOGV("app_process main with argv: %s", argv_String.string());
    }

    //zygote传入的参数argv为“-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote”
	//zygote_secondary传入的参数argv为“-Xzygote /system/bin --zygote --socket-name=zygote_secondary”
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    argc--;
    argv++;

    const char* spaced_commands[] = { "-cp", "-classpath" };
    // Allow "spaced commands" to be succeeded by exactly 1 argument (regardless of -s).
    bool known_command = false;

    int i;
    for (i = 0; i < argc; i++) {
        if (known_command == true) {
          runtime.addOption(strdup(argv[i]));
          ALOGV("app_process main add known option '%s'", argv[i]);
          known_command = false;
          continue;
        }

        for (int j = 0;
             j < static_cast<int>(sizeof(spaced_commands) / sizeof(spaced_commands[0]));
             ++j) {
          if (strcmp(argv[i], spaced_commands[j]) == 0) {
            known_command = true;
            ALOGV("app_process main found known command '%s'", argv[i]);
          }
        }

        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i; // Skip --.
            break;
        }

        runtime.addOption(strdup(argv[i]));
        ALOGV("app_process main add option '%s'", argv[i]);
    }

    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) {
            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()) {
        //className不为空,说明是application启动模式
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);

        if (!LOG_NDEBUG) {
          String8 restOfArgs;
          char* const* argv_new = argv + i;
          int argc_new = argc - i;
          for (int k = 0; k < argc_new; ++k) {
            restOfArgs.append("\"");
            restOfArgs.append(argv_new[k]);
            restOfArgs.append("\" ");
          }
          ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());
        }
    } else {
        //进入zygote模式,新建Dalvik的缓存目录:/data/dalvik-cache
        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);

        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }

    //设置一个“好听的昵称” zygote\zygote64,之前的名称是app_process
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (!className.isEmpty()) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        //没有指定类名或zygote,参数错误
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}

AndroidRuntime.cpp

在 app_main.cpp 的 main 函数中最后调用了 AndroidRuntime.start 函数:

// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    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;
        }
    }
    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // 【虚拟机创建】
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);
    // 【JNI方法注册】
    if (startReg(env) < 0) {
        return;
    }

    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    //等价 strArray= new String[options.size() + 1];
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);

    //等价 strArray[0] = "com.android.internal.os.ZygoteInit"
    classNameStr = env->NewStringUTF(className);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    //等价 strArray[1] = "start-system-server";
    // strArray[2] = "--abi-list=xxx";
    //其中xxx为系统响应的cpu架构类型,比如arm64-v8a.
    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    //将"com.android.internal.os.ZygoteInit"转换为"com/android/internal/os/ZygoteInit"
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ...
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        // 【通过JNI调用ZygoteInit.main()方法】
        env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
    //释放相应对象的内存空间
    free(slashClassName);
    mJavaVM->DetachCurrentThread();
    mJavaVM->DestroyJavaVM();
}

start函数主要做了三件事:

  1. 调用 startVm 创建虚拟机
  2. 调用 startReg 注册 JNI 方法
  3. 通过JNI调用到 ZygoteInit.main(),进入 Java 框架层(此前没有任何代码进入过 Java 框架层)

startVM

如果每个应用程序在启动之时都需要单独运行和初始化一个虚拟机,会大大降低系统性能,因此系统首先在 Zygote 进程中创建一个虚拟机,然后通过它孵化出其他的进程,这样子进程会获得 Zygote 进程中的 Dalvik 虚拟机实例拷贝,进而共享虚拟机内存和框架层资源,大幅度提高应用程序的启动和运行速度。

下面只列举部分在调试优化过程中常用参数的源码。

// frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    // JNI检测功能,用于native层调用jni函数时进行常规检测,比较弱字符串格式是否符合要求,资源是否正确释放。该功能一般用于早期系统调试或手机Eng版,对于User版往往不会开启,引用该功能比较消耗系统CPU资源,降低系统性能。
    const bool checkJni = GetBoolProperty("dalvik.vm.checkjni", false);
    if (checkJni) {
        ALOGD("CheckJNI is ON");
  
     /* extended JNI checking */
        addOption("-Xcheck:jni");
  
     /* with -Xcheck:jni, this provides a JNI function call trace */
        //addOption("-verbose:jni");
    }

    //虚拟机产生的trace文件,主要用于分析系统问题,路径默认为/data/anr/traces.txt
    parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");

    //对于不同的软硬件环境,这些参数往往需要调整、优化,从而使系统达到最佳性能
    parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
    parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");
    parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
    parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree=");
    parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree=");
    parseRuntimeOption("dalvik.vm.heaptargetutilization",
                       heaptargetutilizationOptsBuf, "-XX:HeapTargetUtilization=");
    ...

    //初始化虚拟机
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }
}

startReg

int AndroidRuntime::startReg(JNIEnv* env)
{
    //设置线程创建方法为javaCreateThreadEtc
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    env->PushLocalFrame(200);
    //进程 JNI 方法的注册
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);
    return 0;
}

register_jni_procs:

//frameworks/base/core/jni/AndroidRuntime.cpp
  static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
  {
      for (size_t i = 0; i < count; i++) {
          if (array[i].mProc(env) < 0) {//调用gRegJNI的mProc
  #ifndef NDEBUG
              ALOGD("----------!!! %s failed to load\n", array[i].mName);
  #endif
              return -1;
          }
      }
      return 0;
  }
 
//gRegJNI是一个全局数组,定义如下:
//frameworks/base/core/jni/AndroidRuntime.cpp
  static const RegJNIRec gRegJNI[] = {
      REG_JNI(register_com_android_internal_os_RuntimeInit),
      REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
      REG_JNI(register_android_os_SystemClock),
      ........
 
//frameworks/base/core/jni/AndroidRuntime.cpp
  #ifdef NDEBUG
      #define REG_JNI(name)      { name }
      struct RegJNIRec {
          int (*mProc)(JNIEnv*);
      };
  #else
      #define REG_JNI(name)      { name, #name }
      struct RegJNIRec {
          int (*mProc)(JNIEnv*);
          const char* mName;
      };
  #endif

gRegJNI数组有100多个成员,其中每一项成员都是通过REG_JNI宏定义的。调用 mProc,就等价于调用其参数名所指向的函数。 例如 REG_JNI(register_com_android_internal_os_RuntimeInit).mProc 也就是指进入 register_com_android_internal_os_RuntimeInit 方法。

ZygoteInit.java

AndroidRuntime.start() 执行到最后通过反射调用到 ZygoteInit.main(),至此第一次进入 Java 世界。

public static void main(String argv[]) {
        // 1.创建ZygoteServer
        ZygoteServer zygoteServer = null; 
        // 调用native函数,确保当前没有其它线程在运行
        ZygoteHooks.startZygoteNoThreadCreation();        
        //设置pid为0,Zygote进入自己的进程组
        Os.setpgid(0, 0);
        ........
        Runnable caller;
        try {
            ........
            //得到systrace的监控TAG
            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
            TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
                    Trace.TRACE_TAG_DALVIK);
            //通过systradce来追踪 函数ZygoteInit, 可以通过systrace工具来进行分析
            //traceBegin 和 traceEnd 要成对出现,而且需要使用同一个tag
            bootTimingsTraceLog.traceBegin("ZygoteInit"); 
            //开启DDMS(Dalvik Debug Monitor Service)功能
            //注册所有已知的Java VM的处理块的监听器。
            //线程监听、内存监听、native 堆内存监听、debug模式监听等等
            RuntimeInit.preForkInit(); 
            boolean startSystemServer = false;
            String zygoteSocketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;            
            //2. 解析app_main.cpp - start()传入的参数
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    startSystemServer = true;
                    //启动zygote时,才会传入参数:start-system-server
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    enableLazyPreload = true;
                    //启动zygote_secondary时,才会传入参数:enable-lazy-preload
                } else if (argv[i].startsWith(ABI_LIST_ARG)) { 
                //通过属性ro.product.cpu.abilist64\ro.product.cpu.abilist32 从C空间传来的值
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
                    //会有两种值:zygote和zygote_secondary
                } else {
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            } 
            // 根据传入socket name来决定是创建zygote还是zygote_secondary
            final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);
             
            // 在第一次zygote启动时,enableLazyPreload为false,执行preload
            if (!enableLazyPreload) {
                //systrace 追踪 ZygotePreload
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                // 3.加载进程的资源和类
                preload(bootTimingsTraceLog);
                //systrae结束 ZygotePreload的追踪
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            }

            //结束ZygoteInit的systrace追踪
            bootTimingsTraceLog.traceEnd(); // ZygoteInit
            //禁用systrace追踪,以便fork的进程不会从zygote继承过时的跟踪标记
            Trace.setTracingEnabled(false, 0);            
            // 4.调用ZygoteServer 构造函数,创建socket,会根据传入的参数,
            // 创建两个socket:/dev/socket/zygote 和 /dev/socket/zygote_secondary
            zygoteServer = new ZygoteServer(isPrimaryZygote);
 
            if (startSystemServer) {
                //5.fork出system server
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); 
                // 启动SystemServer
                if (r != null) {
                    r.run();
                    return;
                }
            } 
            // 6.zygote进程进入无限循环,处理请求
            caller = zygoteServer.runSelectLoop(abiList);
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            if (zygoteServer != null) {
                zygoteServer.closeServerSocket();
            }
        } 
        // 7.在子进程中退出了选择循环。继续执行命令
        if (caller != null) {
            caller.run();
        }
    }

ZygoteInit 的 main 函数主要完成了以下工作:

  1. 调用 preload() 来预加载类和资源
  2. 调用 ZygoteServer() 创建了两个 Server 端的 Socket 分别为 /dev/socket/zygote 和 /dev/socket/zygote_secondary,Socket 用来等待 AMS 发送过来的请求,请求 zygote 进程创建新的应用程序进程。
  3. 调用 forkSystemServer 来启动 SystemServer 进程,这样系统的关键服务也会通过 SystemServer 进程启动起来。
  4. 最后调用 runSelectLoop 函数来等待客户端请求

总结

1.首先解析 init.rc 文件,创建 Zygote 进程,执行 app_process 程序,该程序入口为 app_main.cpp 的 main 函数。

2.在 app_main.cpp 中调用 AndroidRuntime.start 函数,在 start() 函数中调用 startVm() 创建 Java 虚拟机,然后调用 startReg() 来注册 JNI 函数,最后通过 JNI 调用到 ZygoteInit.main(),进入 Java 框架层(此前没有任何代码进入过 Java 框架层)。

3.在 ZygoteInit.main() 中调用 preload() 预加载类和资源,调用 ZygoteServer() 构造函数创建了两个Socket,用于响应 AMS 发送的请求。调用 forkSystemServer() 启动 SystemServer 进程。最后调用 runSelectLoop() 循环等待 AMS 的请求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1389565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线性布局LinearLayout

<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto"xmlns:tools"http://schemas.android.com/too…

在linux环境下安装lnmp

lnmp官网&#xff1a;https://lnmp.org 一&#xff1a;lnmp安装 参考&#xff1a;https://lnmp.org/install.html 1&#xff1a;下载lnmp安装包 wget https://soft.lnmp.com/lnmp/lnmp2.0.tar.gz -O lnmp2.0.tar.gz 2&#xff1a;解压lnmp安装包 tar zxf lnmp2.0.tar.gz …

【PHP】PHP利用ffmreg获取音频、视频的详细信息

目录 一、目的 二、下载并安装ffmreg 三、PHP代码 四、运行结果 一、目的 使用PHP利用ffmreg获取音频、视频的详细信息&#xff0c;音视频总时长、码率、视频分辨率、音频编码、音频采样频率、实际播放时间、文件大小。 二、下载并安装ffmreg 1、下载地址&#xff1a;htt…

关于影视字幕翻译哪个公司比较专业?

现如今&#xff0c;影视剧作为跨文化交流的重要桥梁&#xff0c;正日益受到中国观众的热爱。因此也催生了影视字幕翻译的需求。那么&#xff0c;如何做好影视作品字幕翻译&#xff0c;哪个公司在影视字幕英译中更为专业&#xff1f; 我们知道&#xff0c;字幕翻译是涉外影视作品…

美创科技助力江苏有线通过DSMM二级认证

近日&#xff0c;经中国信通院泰尔认证中心评审&#xff0c;美创科技助力江苏省广电有线信息网络股份有限公司&#xff08;以下简称“江苏有线”&#xff09;顺利通过DSMM数据安全能力成熟度二级认证&#xff0c;成为广电行业内首家获得二级认证的单位&#xff01; 背景概述 江…

蓝桥杯 最长递增

输入 7 5 2 4 1 3 7 2 输出 3 思路 这个思路也很简单&#xff0c;后面大于前面&#xff0c;长度加一。当后面不大于前面的时候&#xff0c;就是一个新的递增序列了&#xff0c;递增序列的长度最小为1。 代码 #include <iostream> using namespace std; int main() {in…

解决文库系统 本地转码 libreoffice中文乱码的问题(mkfontscale mkfontdir fc-cache -fv命令)

安装搭建好的文库系统在使用Linux系统libreoffice时&#xff0c;如果系统安装时没有安装中文字体库或者中文字体字库不全&#xff0c;将会导致无法正常生成和显示中文 文库系统中文乱码 转码问题处理好之后的效果&#xff1a; 现在中文显示就正常了 1、要查看系统中已经安…

基于GD32F103移植freemodbus从机库

首先说明github下载的freemodbus开源库不可以使用,需要修改 准备资料 下载一个freemodbus开源库 https://gitee.com/chejia12/freemodbus 开源库目录结构 建立文件夹 src inc port 将functions内部文件放入src文件夹将rtu内部的c文件放入src文件夹,h文件放入inc文件夹将m…

ICCV2023 | VL-Match: 使用Token-Level和Instance-Level Matching提升视觉语言预训练

论文标题&#xff1a;VL-Match: Enhancing Vision-Language Pretraining with Token-Level and Instance-Level Matching 代码&#xff1a;None 单位&#xff1a;中国科学院北京计算技术研究所 中国科学院大学 微软 在VLP种&#xff0c;通常采用两种预训练任务&#xff0…

IF=16.6 | Quick CTL细胞免疫佐剂免疫HLA转基因小鼠,助力TCR- T细胞构建!

023年10月12日&#xff0c;中国科学院微生物研究所高福研究团队和谭曙光研究团队于Nature Communications发表了题为KRAS G12V neoantigen specific T cell receptor for adoptive T cell therapy against tumors的研究论文。 影响因子&#xff1a;16.6 Doi&#xff1a;KRAS G…

免费scrum管理工具Leangoo敏捷做缺陷跟踪管理

缺陷管理通常关注如下几个方面&#xff1a; 1. 缺陷的处理速度 2. 缺陷处理的状态 3. 缺陷的分布 4. 缺陷产生的原因 使用Leangoo敏捷看板我们可以对缺陷进行可视化的管理&#xff0c;方便我们对缺陷的处理进展、负责人、当前状态、分布情况等各个方面一目了然。 下面我们…

ATFX汇市:美元指数延续反弹态势,USDCHF年内已涨超2%

ATFX汇市&#xff1a;上周五和本周一&#xff0c;美元指数均以阳线收盘。今日盘中&#xff0c;美元指数呈中阳线形态&#xff0c;如果晚间的美国1月纽约联储制造业指数没有爆冷&#xff0c;美元指数今日以阳线收盘的概率极高。连续三日以阳线收盘&#xff0c;意味着美元指数的反…

文件的创建时间可以修改吗,怎么改?

文件的创建时间可以修改吗&#xff0c;怎么改&#xff1f;文件的创建时间是由操作系统自动生成并记录的&#xff0c;通常情况下无法直接修改。创建时间是文件的属性之一&#xff0c;它反映了文件在文件系统中的生成时间。一旦文件被创建&#xff0c;其创建时间就被确定下来&…

FEP水质取样器应用环境检测无溶出析出深水取样器

FEP水质取样器是一种用于采集水样的工具&#xff0c;它具有以下特点&#xff1a; 1.抗腐蚀性强&#xff1a;FEP材料具有出色的耐腐蚀性&#xff0c;可以在各种恶劣的水质环境中使用。 2.热稳定性好&#xff1a;FEP材料具有良好的热稳定性&#xff0c;能够在高温环境下保持结构完…

发送HTTP POST请求并处理响应

发送HTTP POST请求并处理响应是Web开发中的常见任务。在Go语言中&#xff0c;可以使用net/http包来发送HTTP POST请求并处理响应。 以下是一个示例代码&#xff0c;演示了如何发送HTTP POST请求并处理响应&#xff1a; go复制代码 package main import ( "b…

MessageBox:HubSpot x Facebook全方位对接!

在当今数字化营销的浪潮中&#xff0c;将多个业务系统高效整合成为推动企业成功的核心。HubSpot作为一体化的市场营销平台&#xff0c;与Facebook的整合通过强大的工具——MessageBox&#xff0c;为企业提供了更灵活、高效的整合方案。今天运营坛将深入探讨在HubSpot平台上整合…

Intel Processor Trace(一)

文章目录 前言一、Features and Capabilities1.1 Packet Summary 二、Intel Processor Trace Operational Model2.1 Change of Flow Instruction (COFI) Tracing2.1.1 Direct Transfer COFI2.1.2 Indirect Transfer COFI2.1.3 Far Transfer COFI 2.2 Software Trace Instrument…

PLC远程控制网关:实现智能化生产的关键

近年来&#xff0c;随着工业自动化的快速发展&#xff0c;越来越多的企业开始采用PLC远程控制网关来实现生产过程的智能化管理。这种创新的技术不仅能够提高生产效率&#xff0c;还可以降低成本&#xff0c;并且为企业带来更多的商业机会。 PLC远程控制网关是一种基于互联网的…

贵阳贵安持续打造面向全国的算力保障基地

作者&#xff1a;黄玉叶 当前&#xff0c;新一轮科技革命和产业变革正在重塑全球经济结构&#xff0c;算力作为数字经济的核心生产力&#xff0c;成为全球战略竞争的新焦点。2021年5月&#xff0c;国家发展改革委、中央网信办、工业和信息化部、国家能源局联合印发《全国一体化…

【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…