首页 文章 精选 留言 我的

精选列表

搜索[记忆机制],共10000篇文章
优秀的个人博客,低调大师

图解kubernetes容器状态同步机制核心实现

在K8s中将Pod调度到某一台Node节点之后,后续的状态维护信息则是由对应机器上的kubelet进行维护,如何实时反馈本地运行状态,并通知apiserver则是设计的难点, 本节主要是通过感知Pod状态变化和探测状态改变两个流程来实际分析其核心数据结构,来了解内部设计 1. 状态管理 1.1 静态Pod 静态Pod主要是指的那些不是通过感知apiserver创建的pod, 因为apiserver上并不包含,但是同时也需要维护和获取这类Pod的状态, k8s中就设计了一个镜像Pod的概念,其实就是为静态Pod镜像出来一个Pod该Pod的主要信息与静态Pod一致,并且在apiserver中进行创建,通过apiserver可以感知的这个镜像Pod来反映真实的静态Pod的状态, 1.2 状态数据源 statusManager是进行状态同步的关键组件其需要综合当前Pod运行中的数据和apiserver存储的数据,从而决定最终的状态转换, 这里先关注图上画出来的,更多的状态等后续会一一介绍 2. 版本一致性 type versionedPodStatus struct { status v1.PodStatus // 单调递增的版本号(每个pod) version uint64 // Pod name & namespace, for sending updates to API server. podName string podNamespace string } 在Kubelet中为保证与apiserver端信息的同步,在本地保存了一个Pod状态版本信息,其里面除了保存当前Pod的状态数据还有一个版本版本号,通过单调递增的版本号的对比来确定是否进行状态的同步 3. 核心源码实现 statusManager的流程其实还是蛮复杂的,今天我们就只讲一个场景,即kubelet通过apiserver感知到一个Pod更新,然后顺着该功能的数据流来进行梳理statusMangaer里面的数据流转 3.1 核心数据结构 manager中的核心状态相关的数据结构可以主要分为两大类:映射数据维护(podManager、podStatuses、apiStatusVersions)数据通信管道(podStatusChannel), 剩余的则是对与apiserver通信的kublet和进行pod删除检查的podDeletionSafety type manager struct { kubeClient clientset.Interface // 管理缓存Pod,包含镜像pod和静态pod的映射 podManager kubepod.Manager // 从pod UID映射到相应pod的版本状态信息 。 podStatuses map[types.UID]versionedPodStatus podStatusesLock sync.RWMutex podStatusChannel chan podStatusSyncRequest // 存储镜像pod的版本 apiStatusVersions map[kubetypes.MirrorPodUID]uint64 podDeletionSafety PodDeletionSafetyProvider } 3.2 设置Pod状态 设置Pod状态主要是位于kubelet中的syncPod中,在接收到pod事件变更之后,会与apiserver进行 Pod最新数据的同步从而获取当前pod在apiserver端的最新状态 func (m *manager) SetPodStatus(pod *v1.Pod, status v1.PodStatus) { m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() for _, c := range pod.Status.Conditions { if !kubetypes.PodConditionByKubelet(c.Type) { klog.Errorf("Kubelet is trying to update pod condition %q for pod %q. "+ "But it is not owned by kubelet.", string(c.Type), format.Pod(pod)) } } // Make sure we're caching a deep copy. status = *status.DeepCopy() // 如果Pod被删除了则需要强制与apiserver进行信息的同步 m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil) } 3.3 更新内部缓存状态产生同步事件 3.3.1 获取缓存状态 var oldStatus v1.PodStatus // 检测之前的本地缓存数据 cachedStatus, isCached := m.podStatuses[pod.UID] if isCached { oldStatus = cachedStatus.status } else if mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod); ok { oldStatus = mirrorPod.Status } else { oldStatus = pod.Status } 3.3.2 检测容器状态 检测容器状态主要是针对容器终止状态转发的合法性进行检测,其实就是根据设定的Pod的RestartPolicy来检测针对一个终止的容器是否可以进行重启 if err := checkContainerStateTransition(oldStatus.ContainerStatuses, status.ContainerStatuses, pod.Spec.RestartPolicy); err != nil { klog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) return false } if err := checkContainerStateTransition(oldStatus.InitContainerStatuses, status.InitContainerStatuses, pod.Spec.RestartPolicy); err != nil { klog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) return false } 3.3.3 更新PodCondition最后转换时间 通过最新的status里面的condition设定对应PodCondition的LastTransitionTime更新时间未当前时间 // Set ContainersReadyCondition.LastTransitionTime. updateLastTransitionTime(&status, &oldStatus, v1.ContainersReady) // Set ReadyCondition.LastTransitionTime. updateLastTransitionTime(&status, &oldStatus, v1.PodReady) // Set InitializedCondition.LastTransitionTime. updateLastTransitionTime(&status, &oldStatus, v1.PodInitialized) // Set PodScheduledCondition.LastTransitionTime. updateLastTransitionTime(&status, &oldStatus, v1.PodScheduled) 3.3.4 校对时间截断过长信息 首先会根据当前容器的个数,从而决定每个容器最大的字节数大小,然后对容器里面的终止状态里面的Message信息,进行截断,同时进行时间的校对 normalizeStatus(pod, &status) 3.3.5 状态更新条件检测 如果之前已经缓存了对应的数据,并且缓存的数据与当前的状态未发生改变,也不需要强制更新,就直接返回 if isCached && isPodStatusByKubeletEqual(&cachedStatus.status, &status) && !forceUpdate { // 如果不强制更新 ,默认是true此处不会成立 klog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status) return false // No new status. } 3.3.6 生成同步事件更新缓存 生成最新的状态缓存数据,并且递增本地的版本信息 // 构建新的状态 newStatus := versionedPodStatus{ status: status, version: cachedStatus.version + 1, // 更新器缓存 podName: pod.Name, podNamespace: pod.Namespace, } // 更新新的缓存状态 m.podStatuses[pod.UID] = newStatus select { case m.podStatusChannel <- podStatusSyncRequest{pod.UID, newStatus}: // 构建一个新的同步请求 klog.V(5).Infof("Status Manager: adding pod: %q, with status: (%d, %v) to podStatusChannel", pod.UID, newStatus.version, newStatus.status) return true default: // Let the periodic syncBatch handle the update if the channel is full. // We can't block, since we hold the mutex lock. klog.V(4).Infof("Skipping the status update for pod %q for now because the channel is full; status: %+v", format.Pod(pod), status) return false } 3.4 探测状态更新 探测状态其实就是Pod内容器的运行状态,比如如果设置了Readiness探测,当某个容器探测失败的时候,就会通知对应的service从后端的enpoint中移除该Pod, 让我们一起看看Kubelet是如何将运行状态通知到apiserver端的 3.4.1 获取当前状态 func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) { m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() // 获取本地的容器 pod, ok := m.podManager.GetPodByUID(podUID) if !ok { klog.V(4).Infof("Pod %q has been deleted, no need to update readiness", string(podUID)) return } // 获取当前的状态 oldStatus, found := m.podStatuses[pod.UID] if !found { klog.Warningf("Container readiness changed before pod has synced: %q - %q", format.Pod(pod), containerID.String()) return } // 获取当前的容器状态 containerStatus, _, ok := findContainerStatus(&oldStatus.status, containerID.String()) if !ok { klog.Warningf("Container readiness changed for unknown container: %q - %q", format.Pod(pod), containerID.String()) return } 3.4.2 检测状态是否发生改变 // 检测前后的就绪状态是否发生改变 if containerStatus.Ready == ready { klog.V(4).Infof("Container readiness unchanged (%v): %q - %q", ready, format.Pod(pod), containerID.String()) return } 3.4.3 修改容器的就绪状态 获取容器的状态,修改就绪为当前的状态 status := *oldStatus.status.DeepCopy() containerStatus, _, _ = findContainerStatus(&status, containerID.String()) containerStatus.Ready = ready 3.4.4 根据最新的容器状态修改 会根据当前运行时的容器探测的状态,来修改对应PodCondition里面的状态,最后调用内部的更新逻辑 updateConditionFunc := func(conditionType v1.PodConditionType, condition v1.PodCondition) { conditionIndex := -1 // 获取Pod对应的PodCondition状态 for i, condition := range status.Conditions { if condition.Type == conditionType { conditionIndex = i break } } // 修改或追加Pod对应的PodCondition状态 if conditionIndex != -1 { status.Conditions[conditionIndex] = condition } else { klog.Warningf("PodStatus missing %s type condition: %+v", conditionType, status) status.Conditions = append(status.Conditions, condition) } } // 计算Ready状态 updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(&pod.Spec, status.Conditions, status.ContainerStatuses, status.Phase)) // 计算容器Ready状态 updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(&pod.Spec, status.ContainerStatuses, status.Phase)) m.updateStatusInternal(pod, status, false) 3.5 启动后台同步任务 statusManager会启动一个后台的线程来进行更新管道里面同步请求的消费 func (m *manager) Start() { // 省略非核心代码 go wait.Forever(func() { select { case syncRequest := <-m.podStatusChannel: // 获取最新的状态信息,更新apiserver klog.V(5).Infof("Status Manager: syncing pod: %q, with status: (%d, %v) from podStatusChannel", syncRequest.podUID, syncRequest.status.version, syncRequest.status.status) m.syncPod(syncRequest.podUID, syncRequest.status) case <-syncTicker: m.syncBatch() } }, 0) } 3.6 同步Pod状态 3.6.1 同步条件检测 同步条件检测主要是检测镜像Pod的版本是否发送变化、Pod当前是否被删除,如果pod没有被删除则返回false,即对一个没有删除的Pod我们还是需要继续更新其状态的 if !m.needsUpdate(uid, status) { klog.V(1).Infof("Status for pod %q is up-to-date; skipping", uid) return } 3.6.2 通过apiserver获取最新Pod数据 如果没有获取到Pod信息,则直接进行退出即可 pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(status.podName, metav1.GetOptions{}) if errors.IsNotFound(err) { klog.V(3).Infof("Pod %q does not exist on the server", format.PodDesc(status.podName, status.podNamespace, uid)) // 如果Pod已经被删除了,就直接退出就行 return } if err != nil { klog.Warningf("Failed to get status for pod %q: %v", format.PodDesc(status.podName, status.podNamespace, uid), err) return } 3.6.3 调用Patch接口进行更新 这里面会通过将最小的状态与之前的状态来进行merge合并,然后调用kubeClient进行apiserver端状态的修改 oldStatus := pod.Status.DeepCopy() // 更新服务端的状态 newPod, patchBytes, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status)) klog.V(3).Infof("Patch status for pod %q with %q", format.Pod(pod), patchBytes) if err != nil { klog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) return } 3.6.4 更新本地的Apiserver端的版本信息 // 当前是最新的状态 pod = newPod klog.V(3).Infof("Status for pod %q updated successfully: (%d, %+v)", format.Pod(pod), status.version, status.status) m.apiStatusVersions[kubetypes.MirrorPodUID(pod.UID)] = status.version 3.6.5 检测删除Pod 这里主要是最后阶段,即Pod对应的资源都已经释放了,则才最终删除apiserver端的Pod // 如果pod的DeletionTimestamp被设置,则对应的Pod需要被删除 if m.canBeDeleted(pod, status.status) { deleteOptions := metav1.NewDeleteOptions(0) deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID)) // 调用apiserver对Pod进行删除 err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) if err != nil { klog.Warningf("Failed to delete status for pod %q: %v", format.Pod(pod), err) return } klog.V(3).Infof("Pod %q fully terminated and removed from etcd", format.Pod(pod)) m.deletePodStatus(uid) } 探活整体的设计大概就是这样,希望大佬们多多关注,一起交流。 k8s源码阅读电子书地址: https://www.yuque.com/baxiaoshi/tyado3 > 微信号:baxiaoshi2020 > 关注公告号阅读更多源码分析文章 > 更多文章关注 www.sreguide.com > 本文由博客一文多发平台 OpenWrite 发布

优秀的个人博客,低调大师

一文深入理解 JNI实现机制

写在前面 说到JNI都不陌生,它的全称:Java Native Interface,即Java本地接口。 JNI不仅仅是Android特有的,它是属于Java平台的,它允许在Java虚拟机内运行的java代码与其他编程语言(如c, c++和汇编语言)编写的程序和库进行交互。 JNI调用姿势:Java —> JNI —> C/C++(SO库) 在Android平台中,使用JNI封装了跟硬件相关的操作,从而可以通过Java调用相关JNI模块,以达到对硬件的调用。 下面我们将围绕如下内容进行展开: 1.java代码编译和执行过程 2.jni常规使用姿势 3.so加载流程(jni执行流程、注册方式原理) 4.JNIEnv作用及实现 Java代码编译和执行过程 java代码编译和执行的过程涉及到两个命令:javac和java 1.首先通过javac编译java代码,生成class字节码文件 2.javacTestCode.java --> TestCode.class 3.然后通过java命令来执行class字节码 4.javaTestCode.class java命令执行过程中,会先启动虚拟机,加载TestCode类信息到内存,然后由执行引擎执行其main方法。 JVM的逻辑内存模型如下: JNI常规使用姿势 在java层通过native关键字创建本地方法,如下:(成员方法、static方法) public class JNIMethods { static { System.loadLibrary("test-jni"); } //隐式注册 public native String stringFromJNI(); public static native String testParams(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist); //显式注册 public native String stringFromJNIDis(); public static native String testParamsDis(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist); } 创建c层代码,并注册本地方法 #include #include #include "common.h" jstring stringFromJNIDis(JNIEnv *env, jobject jobj); jstring testParamsDis(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_, jobjectArray objectArray, jobject list); static jmethodID methodID_toString; //显式注册 static JNINativeMethod gMethods[] = { {"stringFromJNIDis", "()Ljava/lang/String;", (void *) stringFromJNIDis}, {"testParamsDis", "(ILjava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;Ljava/util/List;)Ljava/lang/String;", (void *) testParamsDis} }; static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int nMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { return JNI_FALSE; } if (env->RegisterNatives(clazz, methods, nMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } void appendString(JNIEnv *env, char *dest, jstring tmpStr) { if (tmpStr == NULL) { return; } const char *tmpString = env->GetStringUTFChars(tmpStr, 0); strcat(dest, tmpString); env->ReleaseStringUTFChars(tmpStr, tmpString); } //加载so时会执行该方法 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { LOGD("test-jni.cpp JNI_OnLoad"); JNIEnv *env = NULL; if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } if (NULL == env) { return -1; } jclass clazz = env->FindClass("java/lang/Object"); methodID_toString = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;"); //显式注册本地方法 if (!registerNativeMethods(env, "com/mob/test/jni/testjni/JNIMethods", gMethods, NELEM(gMethods))) { LOGE("test-jni.cpp JNI_OnLoad register JNIMethods failed"); } return JNI_VERSION_1_4; } //卸载时会执行该方法 JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { LOGD("test-jni.cpp JNI_OnUnload"); methodID_toString = NULL; } //隐式注册 extern "C" JNIEXPORT jstring JNICALL Java_com_mob_test_jni_testjni_JNIMethods_stringFromJNI(JNIEnv *env, jobject jobj) { LOGD("test-jni.cpp Java_com_mob_test_jni_testjni_JNIMethods_stringFromJNI"); return stringFromJNIDis(env, jobj); } extern "C" JNIEXPORT jstring JNICALL Java_com_mob_test_jni_testjni_JNIMethods_testParams(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_, jobjectArray arrayObject, jobject list) { LOGD("test-jni.cpp Java_com_mob_test_jni_testjni_JNIMethods_testParams"); return testParamsDis(env, type, jint_, jstring_, jobject_, arrayObject, list); } //显式注册 jstring stringFromJNIDis(JNIEnv *env, jobject jobj) { LOGD("test-jni.cpp stringFromJNIDis"); std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } jstring testParamsDis(JNIEnv *env, jclass type, jint jint_, jstring jstring_, jobject jobject_, jobjectArray arrayObject, jobject list) { LOGD("test-jni.cpp testParamsDis"); char returnValue[1024]; int i = jint_; sprintf(returnValue, "%d", i); appendString(env, returnValue, jstring_); jstring returnToString = reinterpret_cast<_jstring *>(env->CallObjectMethod(jobject_, methodID_toString)); appendString(env, returnValue, returnToString); jsize len = env->GetArrayLength(arrayObject); jobject tmpObject; jstring tmpString; for (i = 0; i < len; i++) { tmpObject = env->GetObjectArrayElement(arrayObject, i); tmpString = reinterpret_cast<_jstring *>(env->CallObjectMethod(tmpObject, methodID_toString)); appendString(env, returnValue, tmpString); } env->DeleteLocalRef(tmpObject); jclass clazz_list = env->GetObjectClass(list); jmethodID list_get = env->GetMethodID(clazz_list, "get", "(I)Ljava/lang/Object;"); jmethodID list_size = env->GetMethodID(clazz_list, "size", "()I"); int size = env->CallIntMethod(list, list_size); jstring itemStr; for (i = 0; i < size; i++) { itemStr = reinterpret_cast(env->CallObjectMethod(list, list_get, i)); appendString(env, returnValue, itemStr); } return env->NewStringUTF(returnValue); } 在gradle中配置cmake编译选项,并编写CMakeLists.txt脚本,用于生成so android { externalNativeBuild { cmake { path "CMakeLists.txt" } } } #CMakeList.txt 内容如下: # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp) add_library( # Sets the name of the library. test-jni # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/test-jni.cpp) find_library( # Sets the name of the path variable. test # Specifies the name of the NDK library that # you want CMake to locate. log) target_link_libraries( # Specifies the target library. test-jni # Links the target library to the log library # included in the NDK. ${log-lib} ) 在java层,加载本地方法前,加载so public class JNIMethods { static { //加载so System.loadLibrary("test-jni"); } //隐式注册 public native String stringFromJNI(); public static native String testParams(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist); //显式注册 public native String stringFromJNIDis(); public static native String testParamsDis(int jint, String jstring, Object jobject, Object[] arrayObject, Listlist); } so加载流程 一般我们是通过 System.loadLibrary("test-jni") 的方式来加载libtest-jni.so库的,一起来跟踪下整个加载过程(此处分析的源码是基于Dalvik的) System.loadLibrary() -> Runtime.loadLibray0() -> Runtime.doLoad() -> Runtime.nativeLoad() //java.lang.System public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); } //java.lang.Runtime synchronized void loadLibrary0(ClassLoader loader, String libname) { //...... String error = doLoad(filename, loader); //...... } private String doLoad(String name, ClassLoader loader) { //...... synchronized (this) { return nativeLoad(name, loader, librarySearchPath); } } // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives. private static native String nativeLoad(String filename, ClassLoader loader, String librarySearchPath); 在代码中我们并没有看到相关加载nativeLoad这个本地方法的so,那么它又是用什么方式在什么时候被注册的呢? 很多系统的本地方法都是在应用启动的时候通过显示注册的方式被注册的,下面我们来看下系统启动后会做哪些事情。 启动虚拟机进程app_process首先会执行app_main.cpp中的main方法 /frameworks/base/cmds/app_process/app_main.cpp main() -> AndroidRuntime.start() int main(int argc, char* const argv[]) { //...... 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"); } //...... } /frameworks/base/core/jni/AndroidRuntime.cpp start()->startVm() void AndroidRuntime::start(const char* className, const char* options) { //...... /* start the virtual machine */ JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) {//启动vm return; } } int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) { //...... if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {//创建vm ALOGE("JNI_CreateJavaVM failed\n"); goto bail; } //...... } JNI_CreateJavaVM()在art和dalvik中都有相关实现,这里仅以dalvik为主线跟踪,art的实现在/art/runtime/jni_internal.cc中 /dalvik/vm/Jni.cpp JNI_CreateJavaVM() -> Init.dvmStartUp() jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { //...... JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL); /* Initialize VM. */ gDvm.initializing = true; std::string status = dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);//初始化vm //...... } /dalvik/vm/Init.cpp dvmStartUp()-> InternalNative.dvmInternalNativeStartup() dvmStartUp()-> registerSystemNatives() -> jni.RegisterNatives() std::string dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized, JNIEnv* pEnv) { //...... if (!dvmNativeStartup()) {//初始化native return "dvmNativeStartup failed"; } if (!dvmInternalNativeStartup()) {//初始化内部native return "dvmInternalNativeStartup failed"; } if (!dvmJniStartup()) {//初始化jni return "dvmJniStartup failed"; } //...... if (!registerSystemNatives(pEnv)) {//注册系统本地方法 return "couldn't register system natives"; } //...... return ""; } static bool registerSystemNatives(JNIEnv* pEnv) { //...... JNIEXPORT jobject JNICALL Java_java_lang_Class_getDex(JNIEnv* env, jclass javaClass); const JNINativeMethod Java_java_lang_Class[] = { { "getDex", "()Lcom/android/dex/Dex;", (void*) Java_java_lang_Class_getDex }, }; //注册Dex的getDex方法 if (pEnv->RegisterNatives(c, Java_java_lang_Class, 1) != JNI_OK) { dvmAbort(); } //加载javacore和nativehelper库 loadJniLibrary("javacore"); loadJniLibrary("nativehelper"); // Back to run mode. self->status = THREAD_RUNNING; return true; } /dalvik/vm/native/InternalNative.cpp dvmInternalNativeStartup() bool dvmInternalNativeStartup() { DalvikNativeClass* classPtr = gDvmNativeMethodSet;//遍历系统类结构体,并保存到gDvm的全局变量userDexFiles中 while (classPtr->classDescriptor != NULL) { classPtr->classDescriptorHash = dvmComputeUtf8Hash(classPtr->classDescriptor); classPtr++; } gDvm.userDexFiles = dvmHashTableCreate(2, dvmFreeDexOrJar); if (gDvm.userDexFiles == NULL) return false; return true; } //系统本地方法都存在这里 static DalvikNativeClass gDvmNativeMethodSet[] = { { "Ljava/lang/Object;", dvm_java_lang_Object, 0 }, { "Ljava/lang/Class;", dvm_java_lang_Class, 0 }, //…… { "Ljava/lang/Runtime;", dvm_java_lang_Runtime, 0 }, { "Ljava/lang/String;", dvm_java_lang_String, 0 }, { "Ljava/lang/System;", dvm_java_lang_System, 0 }, //…… { "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 }, { "Ldalvik/system/VMRuntime;", dvm_dalvik_system_VMRuntime, 0 }, //…… }; //调用本地方法会到这里来寻找系统本地方 DalvikNativeFunc dvmLookupInternalNativeMethod(const Method* method) { const char* classDescriptor = method->clazz->descriptor; const DalvikNativeClass* pClass; u4 hash; hash = dvmComputeUtf8Hash(classDescriptor); pClass = gDvmNativeMethodSet;//遍历全局本地方法集合,寻找注册的系统本地方法 while (true) { if (pClass->classDescriptor == NULL) break; if (pClass->classDescriptorHash == hash && strcmp(pClass->classDescriptor, classDescriptor) == 0) { const DalvikNativeMethod* pMeth = pClass->methodInfo; while (true) { if (pMeth->name == NULL) break; if (dvmCompareNameDescriptorAndMethod(pMeth->name, pMeth->signature, method) == 0) { /* match */ return pMeth->fnPtr; } pMeth++; } } pClass++; } return NULL; } 注册本地方法 /dalvik/vm/Jni.cpp RegisterNatives()-> dvmRegisterJNIMethod()-> dvmUseJniBridge() -> Class.dvmSetNativeFunc() static jint RegisterNatives(JNIEnv* env, jclass jclazz, const JNINativeMethod* methods, jint nMethods) { //...... for (int i = 0; i < nMethods; i++) { if (!dvmRegisterJNIMethod(clazz, methods[i].name, methods[i].signature, methods[i].fnPtr)) { return JNI_ERR; } } return JNI_OK; } static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName, const char* signature, void* fnPtr) { //...... if (method->nativeFunc != dvmResolveNativeMethod) { /* this is allowed, but unusual */ ALOGV("Note: %s.%s:%s was already registered", clazz->descriptor, methodName, signature); } method->fastJni = fastJni; dvmUseJNIBridge(method, fnPtr); ALOGV("JNI-registered %s.%s:%s", clazz->descriptor, methodName, signature); return true; } void dvmUseJNIBridge(Method* method, void* func) { //…… DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod; dvmSetNativeFunc(method, bridge, (const u2*) func);//将nativeFunc指向dvmCallJNIMethod,并将insns指向func } void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) { //...... JNIEnv* env = self->jniEnv; COMPUTE_STACK_SUM(self); dvmPlatformInvoke(env, (ClassObject*) staticMethodClass, method->jniArgInfo, method->insSize, modArgs, method->shorty, (void*) method->insns, pResult);//调用方法insns,对于不同的cpu架构会有不同的调用方式 //...... } /dalvik/vm/oo/Class.cpp dvmSetNativeFunc() void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func, const u2* insns) { //...... //将nativeFunc指向dvmCallJNIMethod,并将insns指向func if (insns != NULL) { /* update both, ensuring that "insns" is observed first */ method->insns = insns; android_atomic_release_store((int32_t) func, (volatile int32_t*)(void*) &method->nativeFunc); } else { /* only update nativeFunc */ method->nativeFunc = func; } //...... } 至此,我们可以知道调用JNI方法最终会通过 Jni.dvmCallJNIMethod() -> dvmPlatformInvoke() 来根据不同cpu架构实现进行调用。 Class被加载的过程 Android中ClassLoader都是继承自BaseDexClassLoader,加载dex是通过BaseDexClassLoader来加载的,会将dex中的信息都保存到DexPathList中,后面加载类的过程如下: BaseDexClassLoader.findClass() -> DexPathList.findClass() -> 遍历dexElements -> DexFile.loadClassBinaryName() -> DexFile.defineClass() -> DexFile.defineClassNative() /dalvik/vm/native/dalvik_system_DexFile.cpp defineClassNative()-> Class.dvmDefineClass() const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { //…… { "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", Dalvik_dalvik_system_DexFile_defineClassNative }, { "getClassNameList", "(I)[Ljava/lang/String;", Dalvik_dalvik_system_DexFile_getClassNameList }, //…… }; static void Dalvik_dalvik_system_DexFile_defineClassNative(const u4* args, JValue* pResult) { //...... clazz = dvmDefineClass(pDvmDex, descriptor, loader); Thread* self = dvmThreadSelf(); //...... } /dalvik/vm/oo/Class.cpp dvmDefineClass() -> findClassNoInit() -> dvmLookupClass() -> loadClassFromDex() -> loadClassFromDex0() -> loadMethodFromDex() -> dvmResolveNativeMethod() ClassObject* dvmDefineClass(DvmDex* pDvmDex, const char* descriptor, Object* classLoader) { assert(pDvmDex != NULL); return findClassNoInit(descriptor, classLoader, pDvmDex); } static ClassObject* findClassNoInit(const char* descriptor, Object* loader, DvmDex* pDvmDex) { Thread* self = dvmThreadSelf(); ClassObject* clazz; //...... clazz = dvmLookupClass(descriptor, loader, true);//从gDvm全局变量的loadedClasses中找 if (clazz == NULL) { clazz = loadClassFromDex(pDvmDex, pClassDef, loader);//没找到,则从dex中找 } //...... return clazz; } static ClassObject* loadClassFromDex(DvmDex* pDvmDex, const DexClassDef* pClassDef, Object* classLoader) { ClassObject* result; //...... result = loadClassFromDex0(pDvmDex, pClassDef, &header, pEncodedData, classLoader); //...... return result; } static ClassObject* loadClassFromDex0(DvmDex* pDvmDex, const DexClassDef* pClassDef, const DexClassDataHeader* pHeader, const u1* pEncodedData, Object* classLoader) { ClassObject* newClass = NULL; //...... loadIFieldFromDex(newClass, &field, &newClass->ifields[i]);//遍历加载成员变量 //...... loadMethodFromDex(newClass, &method, &newClass->directMethods[i]);//遍历加载方法 //...... newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef); return newClass; } static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod, Method* meth) { //...... if (dvmIsNativeMethod(meth)) {//如果是本地方法,会将nativeFunc指向dvmResolveNativeMethod,当执行本地方法时就会执行dvmResolveNativeMethod方法 meth->nativeFunc = dvmResolveNativeMethod; meth->jniArgInfo = computeJniArgInfo(&meth->prototype); } //...... } 上面跟踪下来我们知道每个dex中的本地方法都会执行到 dvmResolveNativeMethod(),我们来看看该方法做了哪些事情? /dalvik/vm/Native.cpp dvmResolveNativeMethod() -> InternalNative.dvmLookupInternalNativeMethod() dvmResolveNativeMethod() -> lookupSharedLibMethod() -> findMethodInLib() void dvmResolveNativeMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) { //…… /* start with our internal-native methods */ DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);//寻找系统内部本地方法 if (infunc != NULL) { //…… DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc; dvmSetNativeFunc((Method*) method, dfunc, NULL); dfunc(args, pResult, method, self);//执行 return; } /* now scan any DLLs we have loaded for JNI signatures */ void* func = lookupSharedLibMethod(method);//在动态加载的so中寻找本地方法 if (func != NULL) { /* found it, point it at the JNI bridge and then call it */ dvmUseJNIBridge((Method*) method, func); (*method->nativeFunc)(args, pResult, method, self);//执行nativeFunc return; } //…… dvmThrowUnsatisfiedLinkError("Native method not found", method);//没找到本地方法,则会报UnsatisfiedLinkError } static void* lookupSharedLibMethod(const Method* method) { if (gDvm.nativeLibs == NULL) { ALOGE("Unexpected init state: nativeLibs not ready"); dvmAbort(); } return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib, (void*) method);//先从gDvm全局变量nativeLibs中寻找,没找到,则通过findMethodInLib在so中寻找,找到则保存到nativeLibs中 } static int findMethodInLib(void* vlib, void* vmethod) { const SharedLib* pLib = (const SharedLib*) vlib; const Method* meth = (const Method*) vmethod; //...... /* * First, we try it without the signature. */ preMangleCM = createJniNameString(meth->clazz->descriptor, meth->name, &len); if (preMangleCM == NULL) goto bail; mangleCM = mangleString(preMangleCM, len); if (mangleCM == NULL) goto bail; //通过createJniNameString和mangleString,得到最终函数的名称路径Java_package_methodName ALOGV("+++ calling dlsym(%s)", mangleCM); func = dlsym(pLib->handle, mangleCM);//根据动态链接库操作句柄与符号,返回符号对应的地址 //...... return (int) func; } 系统内部本地方法通过 dvmLookupInternalNativeMethod()方法来寻找具体实现的 通过System.loadLibrary加载的本地方法,则是通过 lookupSharedLibMethod()方法来寻找的 (方法名称格式:Java_package_methodName) /dalvik/vm/native/InternalNative.cpp dvmLookupInternalNativeMethod() -> java_lang_runtime.Dalvik_java_lang_Runtime_nativeLoad() DalvikNativeFunc dvmLookupInternalNativeMethod(const Method* method) { const char* classDescriptor = method->clazz->descriptor; const DalvikNativeClass* pClass; u4 hash; hash = dvmComputeUtf8Hash(classDescriptor); pClass = gDvmNativeMethodSet;//遍历全局本地方法集合,寻找内部注册的本地方法 while (true) { if (pClass->classDescriptor == NULL) break; if (pClass->classDescriptorHash == hash && strcmp(pClass->classDescriptor, classDescriptor) == 0) { const DalvikNativeMethod* pMeth = pClass->methodInfo; while (true) { if (pMeth->name == NULL) break; if (dvmCompareNameDescriptorAndMethod(pMeth->name, pMeth->signature, method) == 0) { /* match */ //ALOGV("+++ match on %s.%s %s at %p", // className, methodName, methodSignature, pMeth->fnPtr); return pMeth->fnPtr; } pMeth++; } } pClass++; } return NULL; } static DalvikNativeClass gDvmNativeMethodSet[] = { { "Ljava/lang/Object;", dvm_java_lang_Object, 0 }, { "Ljava/lang/Class;", dvm_java_lang_Class, 0 }, //…… { "Ljava/lang/Runtime;", dvm_java_lang_Runtime, 0 }, { "Ljava/lang/String;", dvm_java_lang_String, 0 }, { "Ljava/lang/System;", dvm_java_lang_System, 0 }, { "Ljava/lang/Throwable;", dvm_java_lang_Throwable, 0 }, { "Ljava/lang/VMClassLoader;", dvm_java_lang_VMClassLoader, 0 }, { "Ljava/lang/VMThread;", dvm_java_lang_VMThread, 0 }, //…… { "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 }, { "Ldalvik/system/VMRuntime;", dvm_dalvik_system_VMRuntime, 0 }, //…… }; /dalvik/vm/native/java_lang_runtime.cpp java_lang_runtime.Dalvik_java_lang_Runtime_nativeLoad() -> Native.dvmLoadNativeCode() const DalvikNativeMethod dvm_java_lang_Runtime[] = { //…… { "nativeExit", "(I)V", Dalvik_java_lang_Runtime_nativeExit }, { "nativeLoad", "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/String;", Dalvik_java_lang_Runtime_nativeLoad }, //…… }; static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) { //...... bool success = dvmLoadNativeCode(fileName, classLoader, &reason); //...... } /dalvik/vm/Native.cpp dvmLoadNativeCode() -> JNI_OnLoad() bool dvmLoadNativeCode(const char* pathName, Object* classLoader, char** detail) { SharedLib* pEntry; //...... pEntry = findSharedLibEntry(pathName);//so被加载过一次之后,就会缓存起来 if (pEntry != NULL) { //...... return true; } //…… //so第一次被加载 handle = dlopen(pathName, RTLD_LAZY);//打开so动态链接库,保存句柄到handle //...... /* create a new entry */ SharedLib* pNewEntry; pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib)); pNewEntry->pathName = strdup(pathName); pNewEntry->handle = handle;//保存handle到pNewEntry //…… SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);//将pNewEntry保存到gDvm全局变量nativeLibs中,下次可以直接通过缓存获取 //...... vonLoad = dlsym(handle, "JNI_OnLoad");//第一次加载so时,调用so中的JNI_OnLoad方法 //...... return result; } } 上面分析总结: 通过System.loadLibrary()加载的库会被保存到全局变量gDvm.nativeLibs中,并执行其JNI_OnLoad方法。 JNI显式注册是直接将本地方法调用地址指向到c层某方法,并将执行方法改成dvmCallJNIMethod(),隐式是通过方法命名规则去寻找方法的调用地址,然后通过dvmCallJNIMethod()来执行类被加载时,所有的本地方法执行地址nativeFunc都被指向了Native.dvmResolveNativeMethod() 通过JNI显式注册会将本地方法调用地址执行C能某实现方法,并将执行地址nativeFunc指向dvmCallJNIMethod(),隐式注册则是在调用的时候才会通过命名规则去寻找方法的调用地址,寻找到之后,再将执行地址nativeFunc指向dvmCallJNIMethod() 所以执行native方法时,nativeFunc未被指向到dvmCallJNIMethod()的方法(包括第一次执行的隐式注册方法和未被注册的系统方法)都会通过 Native.dvmResolveNativeMethod()来处理,针对系统本地方法,会从 InternalNative.gDvmNativeMethodSet 中寻找;针对应用本地方法,则从 gDvm.nativeLibs 中寻找。 当第二次执行同一个方法时,则会直接执行dvmCallJNIMethod(),所以隐式注册相对显示注册来说,第一次调用的时候会慢一点,但是初始化快。 Jni.dvmCallJNIMethod()执行本地方法,是通过调用dvmPlatformInvoke() 来根据不同cpu架构实现方式来执行的。 so加载核心过程: class中本地方法加载过程: 本地方法调用过程: JNIEnv作用及实现 JNIEnv是JNI指针接口,通常我们通过它来调用操作Java对象,它提供了所有的操作java对象的api。 那么JNIEnv又是何时创建,以及如何来操作Java对象的呢?我们一起来看看它的具体实现~ 首先我们来看下线程时如何被创建的: java/lang/Thread.java new Thread().start() -> VMThread.create() public synchronized void start() { checkNotStarted(); hasBeenStarted = true; VMThread.create(this, stackSize);// VMThread创建 } /dalvik/vm/native/java_lang_VMThread.cpp Dalvik_java_lang_VMThread_create() -> Thread.dvmCreateInterpThread() const DalvikNativeMethod dvm_java_lang_VMThread[] = { { "create", "(Ljava/lang/Thread;J)V", Dalvik_java_lang_VMThread_create },//线程创建方法 { "currentThread", "()Ljava/lang/Thread;", Dalvik_java_lang_VMThread_currentThread }, //…… }; static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult) { //…… dvmCreateInterpThread(threadObj, (int) stackSize);//创建线程 RETURN_VOID(); } /dalvik/vm/Thread.cpp dvmCreateInterpThread() -> interpThreadStart() -> jni.dvmCreateJNIEnv() bool dvmCreateInterpThread(Object* threadObj, int reqStackSize) { //...... Thread* newThread = allocThread(stackSize); //...... int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread);//创建线程 // ...... } /* * pthread entry function for threads started from interpreted code. */ static void* interpThreadStart(void* arg) { //...... self->jniEnv = dvmCreateJNIEnv(self);//创建jniEnv //...... return NULL; } JNIEnv的创建 /dalvik/vm/jni.cpp dvmCreateJNIEnv() JNIEnv* dvmCreateJNIEnv(Thread* self) { JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm; //…… newEnv->funcTable = &gNativeInterface;//接口函数 //...... return (JNIEnv*) newEnv; } static const struct JNINativeInterface gNativeInterface = { DefineClass, FindClass, //...... CallVoidMethod, //... GetFieldID, GetObjectField, //... CallStaticIntMethod, //...... }; //方法具体实现(下面是callVoidMethodA()具体实现) #define CALL_VIRTUAL(_ctype, _jname, _retfail, _retok, _isref) \ static _ctype Call##_jname##Method(JNIEnv* env, jobject jobj, \ jmethodID methodID, ...) \ { \ ScopedJniThreadState ts(env); \ Object* obj = dvmDecodeIndirectRef(ts.self(), jobj); \ const Method* meth; \ va_list args; \ JValue result; \ meth = dvmGetVirtualizedMethod(obj->clazz, (Method*)methodID); \ if (meth == NULL) { \ return _retfail; \ } \ va_start(args, methodID); \ dvmCallMethodV(ts.self(), meth, obj, true, &result, args);//方法调用 \ va_end(args); \ if (_isref && !dvmCheckException(ts.self())) \ result.l = (Object*)addLocalReference(ts.self(), result.l); \ return _retok; \ } \ CALL_VIRTUAL(jobject, Object, NULL, (jobject) result.l, true); //...... /dalvik/vm/interp/Stack.cpp JNIEnv.CallVoidMethodA() -> Stack.dvmCallMethodA() void dvmCallMethodA(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, const jvalue* args) { //...... if (dvmIsNativeMethod(method)) { TRACE_METHOD_ENTER(self, method); (*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult, method, self);//执行方法 TRACE_METHOD_EXIT(self, method); } //…… } JNIEnv是在Thread创建的时候创建的,因此它是属于线程的,不能线程共享。最终调用方法时通过Stack.dvmCallMethod..相关方法去调用的。至此整个流程就分析完毕了。 结语 通过上面的分析,我们知道了JNI是如何在java和本地代码之间建立桥梁和通讯的。 在使用JNI的时候,需要注意: 1.显式注册和隐式注册的区别 2.JNI_Onload方法的使用 3.JNIEnv的使用 4.JNI方法的命名,以及extern “C"的作用(告诉编译器,这部分代码请使用C来编译)等等 原文发布时间为:2018-11-2 本文作者:MobSDK 本文来自云栖社区合作伙伴“安卓巴士Android开发者门户”,了解相关信息可以关注“安卓巴士Android开发者门户”。

优秀的个人博客,低调大师

python中import的引用机制引起的坑

最近在撸Scikit-Learn的代码,想加载点Seaborn的数据训练模型,简单的一句seaborn.load_dataset('')都编译通不过。 import matplotlib.pyplot as plt import numpy as np import seaborn as sns from sklearn.linear_model import LinearRegression # 选择模型 model = LinearRegression(fit_intercept=True) # 整理数据 iris = sns.load_dataset('iris') rng = np.random.RandomState(42) x = 10* rng.rand(50) y = 2*x - 1 + rng.randn(50) X = x[:, np.newaxis] #拟合数据 model.fit(X,y) #预测 xfit = np.linspace(-1,11) Xfit = xfit[:, np.newaxis] yfit = model.predict(Xfit) plt.scatter(x,y) plt.plot(xfit, yfit) plt.show() PS D:\sanye\pythonDEMO> python Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import matplotlib.pyplot as plt >>> import numpy as np >>> import seaborn as sns Traceback (most recent call last): File "<stdin>", line 1, in <module> File "D:\sanye\pythonDEMO\seaborn.py", line 5, in <module> iris = sns.load_dataset('iris') AttributeError: module 'seaborn' has no attribute 'load_dataset' 慌了,一脸懵。仔细检查发现项目路径下有一个seaborn.py文件,改名一起恢复平静。汗~ 这个完全是因为python导入模块的搜索路径以及优先级问题引起的。import导入模块搜索顺序:先当前路径,再环境变量路径。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册