This is a series of blog posts, and I will continue to provide you with the best possible insight into Android source codeGithub serial address

preface

When it comes to virtual machines, we have to talk about JNI, which is the bridge between Java and C++. JNI is the full name of Java Native Interface, which can be understood as an Interface programming method. Just like the C/S mode we usually develop, the Client and Server need to communicate with each other, and the Interface is needed. JNI mainly includes two aspects:

  • C + + calling Java
  • Java call c + +

The files covered in this article

platform/libnativehelper/include/nativehelper/jni.h platform/art/runtime/java_vm_ext.cc platform/art/runtime/jni_internal.cc platform/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java platform/libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks platform/art/runtime/native/dalvik_system_ZygoteHooks.cc  platform/art/runtime/runtime.h platform/libnativehelper/JNIHelp.cpp platform/libcore/luni/src/main/java/android/system/Os.java platform/libcore/luni/src/main/java/libcore/io/Libcore.java platform/libcore/luni/src/main/java/libcore/io/BlockGuardOs.java platform/libcore/luni/src/main/java/libcore/io/ForwardingOs.java platform/libcore/luni/src/main/java/libcore/io/Linux.java platform/libcore/luni/src/main/native/libcore_io_Linux.cppCopy the code

C++ calls Java

Why am I talking about C++ calling Java first? After creating the virtual machine, I first called Java from C++, so I continue the example above. Let’s review the process of calling ZygoteInit’s main function in C++. I will explain it step by step.

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    /* * We want to call main() with a String array with arguments in it. * At present we have two arguments, the class name and an option string. * Create an array to hold them. */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    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);
    }

    /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */
    char* slashClassName = toSlashClassName(className);// Place the character in. Convert /
    jclass startClass = env->FindClass(slashClassName);/ / find the class
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main"."([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);// Call main

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif}}free(slashClassName); . }Copy the code

1.1 mapping between Java types and C++ types

For example, we have common Java Class, String,int,short, etc. In C++, these are not called the original name, but given a new name, basically adding a j in front of the original name, which stands for Java. Here’s how they correspond

Basic data types and void

Java type C + + type
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
void void

Reference data type

Java type C + + type
All objects jobject
Java. Lang. Class instance jclass
Java. Lang. String instance jstring
Java. Lang. Throwable instance jthrowable
Object[] (includes Class,String,Throwable) jobjectArray
boolean[] jbooleanArray
Byte [] (similar to other basic data types) jbyteArray

This code defines three local variables of type Class,String[], and String

    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;
Copy the code

1.2 env – > FindClass

Env ->FindClass. Env is a virtual machine environment. It is analogous to Android’s ubiquitous Context, but env is a thread-specific environment, meaning one thread for each env.

Env has a number of functions, and FindClass is one of them. Env has a number of functions, and FindClass is one of them. The implementation is the same as env->FindClass.

We come to the specific env – > FindClass implementation, the type of env is a JNIEnv, defined in the platform/libnativehelper/include/nativehelper jni. H, This JNIEnv is of different type in C and C++. In C, JNINativeInterface is defined as JNINativeInterface*, while in C++, _JNIEnv is defined as _JNIEnv. _JNIEnv is actually the corresponding function that calls JNINativeInterface. JNINativeInterface is a structure that contains the function FindClass that we’re looking for

#if defined(__cplusplus) // if C++
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else // if it is C
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif 


struct _JNIEnv {
    const struct JNINativeInterface* functions;.jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }... }struct JNINativeInterface {. jclass (*FindClass)(JNIEnv*,const char*); . }Copy the code

When does the FindClass function pointer in the JNINativeInterface structure get assigned? JNI_CreateJavaVM = jni_env; JNI_CreateJavaVM = jni_env; JNI_CreateJavaVM = jni_env; Then we will have to find libart. So the source, the corresponding source code in the platform/art/runtime/java_vm_ext cc, it invokes the runtime: : Create function to the new thread, the thread in the process of the new assignment of the JNIEnv, The JNI_CreateJavaVM function finally calls the thread’s GetJniEnv to get an instance of JNIEnv, which it assigns to p_env.

(I won’t go into detail on how a thread assigns a value to a JNIEnv during creation, but I provide a few key functions, Cc Attach and Init, jni_env_ext. Cc Create, jni_internal. Cc GetJniNativeInterface, I have put all the documents involved in the AOSP project, if you are interested, you can go and have a look.

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {...if(! Runtime::Create(options, ignore_unrecognized)) {return JNI_ERR;
  }

  *p_env = Thread::Current()->GetJniEnv();
}
Copy the code

GetJniEnv returns a JNINativeInterface instance, defined in the/platform/art/runtime/jni_internal. Cc, including the FindClass we are looking for

const JNINativeInterface gJniNativeInterface = {
  nullptr.// reserved0.
  nullptr.// reserved1.
  nullptr.// reserved2.
  nullptr.// reserved3.
  JNI::GetVersion,
  JNI::DefineClass,
  JNI::FindClass,
}
Copy the code

JNI::FindClass = JNI::FindClass = JNI::FindClass The internal implementation of ClassLinker is to get a ClassTable object from a ClassLoader, and then get the corresponding Class from a HashSet in the ClassTable. ClassLoader is also familiar to us. The dex file in apk needs to be loaded by the ClassLoader, which will eventually load the Class into a HashSet, so we FindClass in the HashSet.

Class_linker. Cc FindClass and LookupClass, class_table.cc Lookup, the files involved are all in AOSP project. Interested students can go to see.

static jclass FindClass(JNIEnv* env, const char* name) { CHECK_NON_NULL_ARGUMENT(name); Runtime* runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); / / get ClassLinker STD: : string descriptor (NormalizeJniClassDescriptor (name)); ScopedObjectAccess soa(env); mirror::Class* c = nullptr; if (runtime->IsStarted()) { StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa))); c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader); Else {c = class_linker->FindSystemClass(SOA.self (), description.c_str ()); } return soa.AddLocalReference<jclass>(c); }Copy the code

Env ->FindClass (env->FindClass); env->FindClass (env->FindClass);

1.3 Other Env Functions

Env has a lot of functions, so I’ll just list a few that we use

Create a new instance, equivalent to new in Java

The function name role Analogy to the Java
NewObject The new Object new Object
NewStringUTF New String character new String()
NewObjectArray Creating an Object array new Object[]
New(Type)Array Create an array of Type, such as NewByteArray new byte[]

Get and set member variables and class variables, equivalent to getting and setting variables in Java, using A A =new A() as an example

The function name role Analogy to the Java
GetFieldID Gets the member variable ID, passed in by all methods that get a member variable
GetObjectField Gets a member variable of type Object a.object
Get(Type)Field Gets a member variable of Type Type, such as GetBooleanField bool b=a.bool
Set(Type)Field Sets a member variable of Type Type, such as SetBooleanField a.bool=b
GetStaticFieldID Gets the class variable ID, passed in by all methods that get a class variable
GetStaticObjectField Gets a class variable of type Object A.object
GetStatic(Type)Field Gets a class variable of Type Type, such as GetStaticBooleanField bool b=A.bool
SetStatic(Type)Field Set a class variable of Type Type, such as SetStaticBooleanField A.bool=b

Call member methods and class methods, equivalent to Java call methods, the following A A =new A() example

The function name role Analogy to the Java
GetMethodID Gets the member method ID, passed in by all methods that get a member method
CallObjectMethod Calls a member method that returns a value of type Object Object o=a.a()
Call(Type)Method Call a member method that returns a Type of Type, such as CallBooleanMethod bool b=a.b()
GetStaticMethodID Gets the class method ID, passed in by all methods that get a class method
CallStaticObjectMethod Calls a class method that returns a value of type Object Object o=A.a()
CallStatic(Type)Method Call a class method that returns a Type of Type, such as CallStaticBooleanMethod bool b=A.b()

Array related operations, using bool[] bs=new bool[] as an example

The function name role Analogy to the Java
Get(Type)ArrayElements Gets an element of an array of Type Type bool b=bs[0]
Set(Type)ArrayElements Sets an element of an array of Type Type bs[0]=b

This is unique to C++ and has no corresponding Java call

The function name role Analogy to the Java
ReleaseStringUTFChars The release of the String
Release(Typge)ArrayElements Releases an array of Type Type

I’m just giving a general list of env functions. I don’t go into details about parameters and return values

1.4 Function Signature

The start function calls main at the end. To get main, you need to pass in three parameters: the class of the function, the function name, and the function signature

   jmethodID startMeth = env->GetStaticMethodID(startClass, "main"."([Ljava/lang/String;)V");
Copy the code

A function signature is a symbolic representation of the parameters and return values of a function, represented by (params)return.

Void (); void (); void (); void (); void ()

symbol Java type
B byte
C char
S short
I int
F float
D double
Z boolean
J long
V void

Reference data types and arrays. Reference data types start with L, followed by the full path, and a semicolon at the end. Don’t forget the semicolon! Don’t forget! Don’t forget! Arrays are represented by [

symbol Java type
L/java/lang/String; String
[I int[]
[L/java/lang/object; object[]

Back to our previous example ([Ljava/lang/String;)V, this means that main takes String[] and returns void.

1.5 Exception Handling

When we call a Java function in C++, we can also catch an exception. We can do this in two ways:

  • ExceptionCheck
  • ExceptionOccurred

I’ll start with ExceptionCheck, which returns a bool, true for exception and false for no exception

    env->CallStaticVoidMethod(cls,mid);
    if (env->ExceptionCheck()) {  // Check whether the JNI call throws an exception
        env->ExceptionDescribe(); // Prints error log stack information
        env->ExceptionClear(); // Clear the raised exception
        env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"Exception thrown by JNI!"); // Throw an exception
    }
Copy the code

ExceptionOccurred. This usage is similar to ExceptionCheck, except that instead of returning a bool, it returns a reference to the current exception

jthrowable exc = NULL;
exc = env->ExceptionOccurred();  Return a reference to the current exception object
if (exc) {
    env->ExceptionDescribe(); // Prints error log stack information
    env->ExceptionClear(); // Clear the raised exception
    env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"Exception thrown by JNI!"); // Throw an exception
}
Copy the code

The start function ends up using ExceptionCheck because calling Java methods can throw exceptions

Second, Java call C++

Now that we’re done with C++ calling Java, let’s look at how Java calls C++, and we’ll pick up where we left off, Env ->CallStaticVoidMethod(startClass, startMeth, strArray);

2.1 the main function

Main function at the beginning, there are two method calls startZygoteNoThreadCreation and setpgid, these two are native methods, with these two then I as an example.

public static void main(String argv[]) {... ZygoteHooks.startZygoteNoThreadCreation();// Set the flag to disallow new threads

        try {
            Os.setpgid(0.0); // Set the ZYgote process group ID to the PID of zygote
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to setpgid (0, 0)", ex); }... }Copy the code

StartZygoteNoThreadCreation defined in the platform/libcore dalvik/SRC/main/Java/dalvik/system/ZygoteHooks


    /* * Called by the zygote when starting up. It marks the point when any thread * start should be an error, as only internal daemon threads are allowed there. */
    public static native void startZygoteNoThreadCreation(a);
Copy the code

2.2 native registered

StartZygoteNoThreadCreation is a native method, there are two ways we know native registered way, one is static registration, a dynamic registration.

The so-called static registration is according to the function name and some keywords can be registered, such as startZygoteNoThreadCreation to static registration, it corresponds to the realization of the function should be

JNIEXPORT void JNICALL Java_dalvik_system_ZygoteHooks_startZygoteNoThreadCreation(JNIEnv *, jobject){}Copy the code

That is to say, the keyword JNIEXPORT and JNICALL must be included first, and the function name must start with Java, followed by the complete path of the native function’s class and the native function name. Finally, the parameters and return value must be the same, and there will be two more parameters:

  • JNIEnv, for JNI context,
  • One is jobject, which calls the Class of native functions if it’s static. If it is a normal method represents the object calling the native function

As long as you follow this rule, Java native functions will automatically call the C++ layer functions. One drawback of this static registration method is that the function name is too long, which is inconvenient to write, and there is a registration process when the first call is made, which affects efficiency. Is there any other way? The answer is dynamic registration

Actually most frameworks layer native functions are registered in dynamic way, startZygoteNoThreadCreation function too

How do we find startZygoteNoThreadCreation implementation? There’s a rule, Google engineers like to native place the full path to the realization of the c + + class name of a class, such as startZygoteNoThreadCreation in class is the full path of the dalvik. System. ZygoteHooks, We try to search for dalvik_system_ZygoteHooks and we get dalvik_system_ZygoteHooks. H and dalvik_system_ZygoteHooks. Cc, Let’s look at dalvik_system_zygotelinks.cc

static JNINativeMethod gMethods[] = {
  NATIVE_METHOD(ZygoteHooks, nativePreFork, "()J"),
  NATIVE_METHOD(ZygoteHooks, nativePostForkChild, "(JIZLjava/lang/String;) V"),
  NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V"),
  NATIVE_METHOD(ZygoteHooks, stopZygoteNoThreadCreation, "()V"),};void register_dalvik_system_ZygoteHooks(JNIEnv* env) {
  REGISTER_NATIVE_METHODS("dalvik/system/ZygoteHooks");
}
Copy the code

Originally dynamic registration is a very simple process, directly call env->RegisterNatives, binding information as a parameter can be, but the source code is more complex, I go over it step by step

First of all, if a Java native method calls a C++ function, it must have a key-value pair as binding information, which is to tell the virtual machine which native should execute which C++ function. GMethods is such a role

The type of gMethods array is JNINativeMethod. Let’s review JNINativeMethod, which is a structure. Name represents the name of native function. FnPtr represents a pointer to a C++ function that native points to. This is actually a dynamically registered mapping between a native function and a C++ function

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
Copy the code

But the gMethods array is NATIVE_METHOD, so what is NATIVE_METHOD

#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }
Copy the code

How do you understand this definition? NATIVE_METHOD {“”,””,(void*)()} #define {“”,””,(void*)()} #define {“”,””,(void*)()} We can see that {} has some #, ##, ## for stringification, which is equivalent to Java toString, ## for stringification concatenation, The equivalent of the String in the Java. The format to NATIVE_METHOD (ZygoteHooks startZygoteNoThreadCreation, “() V”), for example, After the replacement is {” startZygoteNoThreadCreation “, “V” (), (void *) (ZygoteHooks_startZygoteNoThreadCreation)}

JNINativeMethod is a structure, real function is registered in REGISTER_NATIVE_METHODS (” dalvik/system/ZygoteHooks “), we first take a look at REGISTER_NATIVE_METHODS

#define REGISTER_NATIVE_METHODS(jni_class_name) \
  RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
Copy the code

It is also a macro definition, points to the RegisterNativeMethods, this function is defined in the platform/frameworks/base/core/jni/AndroidRuntime CPP

/* * Register native methods using JNI. */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
Copy the code

Actually it is called jniRegisterNativeMethods, this definition in the platform/libnativehelper/JNIHelp CPP, JniRegisterNativeMethods will first find the corresponding class name string, and then call (*env)->RegisterNatives to dynamically register JNI. In fact, call so many layers, The key to dynamic registration is to build a structure JNINativeMethod, and then call (*env)->RegisterNatives, RegisterNatives belongs to the function of the virtual machine, I will talk about the virtual machine in the future to analyze, here we know its role on the line.

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className)); // Find class by class name
    if (c.get() == NULL) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp,
                     "Native registration unable to find class '%s'; aborting...",
                     className) == - 1) {
            // Allocation failed, print default warning.
            msg = "Native registration unable to find class; aborting...";
        } else {
            msg = tmp;
        }
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { // Dynamically register jNI
        char* tmp;
        const char* msg;
        if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == - 1) {
            // Allocation failed, print default warning.
            msg = "RegisterNatives failed; aborting...";
        } else {
            msg = tmp;
        }
        e->FatalError(msg);
    }

    return 0;
}
Copy the code

We went on to the above startZygoteNoThreadCreation function, By shows the native function on actual invokes ZygoteHooks_startZygoteNoThreadCreation, it defined in the platform/art/runtime/native/dalvik_system_ZygoteHooks cc

static void ZygoteHooks_startZygoteNoThreadCreation(JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) {
  Runtime::Current()->SetZygoteNoThreadSection(true);
}
Copy the code

In fact, it is called the Runtime SetZygoteNoThreadSection function, the definition in the platform/art/Runtime/Runtime. H, the realization of the function is very simple, Set zyGOTE_NO_THREADs_ to the desired bool

static Runtime* instance_;

// Whether zygote code is in a section that should not start threads.
bool zygote_no_threads_;

static Runtime* Current(a) {
   return instance_;
}

void SetZygoteNoThreadSection(bool val) {
   zygote_no_threads_ = val;
}

Copy the code

Thus we can see the native startZygoteNoThreadCreation function through layer upon layer calls, finally is to a bool variable is set to true. This is a bit of an overstatement, but the main point here is to show you how to track native implementations, as this is a necessary skill to read the frameworks layer code. Here I again recommend you to use Source Insight to look at the code, whether it is a function jump or global search is very convenient, see the details I wrote before how to read Android Source code

4.1.2 setpgid

Defined in the platform/libcore/luni/SRC/main/Java/android/system/Os Java

The os.java class is a special class. This class acts as a proxy class. All the methods call the related methods in libcore. Os.

 /** * See setpgid(2). */
  / * *@hide* / public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }

Copy the code

Libcore. OS is the implementation class of BlockGuardOs, and the parent class of BlockGuardOs is ForwardingOs, which is also a proxy class. That is, functions in os.java end up calling functions in Linux.java. In addition, some methods are overridden in the BlockGuardOs class, and some Policy permissions are checked.

public final class Libcore {
    private Libcore(a) {}/**
     * Direct access to syscalls. Code should strongly prefer using {@link #os}
     * unless it has a strong reason to bypass the helpful checks/guards that it
     * provides.
     */
    public static Os rawOs = new Linux();

    /** * Access to syscalls with helpful checks/guards. */
    public static Os os = new BlockGuardOs(rawOs);
}
 
Copy the code

What is the implementation of Linux.java

public final class Linux implements Os {
    Linux() { } 

    ...
    public native void setpgid(int pid, int pgid) throws ErrnoException; . }Copy the code

Yes, it’s full of native functions, but where are the native implementations? Libcore_io_Linux and libcore_io_linux.cpp

static JNINativeMethod gMethods[] = {
    ...

    NATIVE_METHOD(Linux, setpgid, "(II)V"),... }void register_libcore_io_Linux(JNIEnv* env) {
    jniRegisterNativeMethods(env, "libcore/io/Linux", gMethods, NELEM(gMethods));
}

static void Linux_setpgid(JNIEnv* env, jobject, jint pid, int pgid) {
    throwIfMinusOne(env, "setpgid", TEMP_FAILURE_RETRY(setpgid(pid, pgid)));
}
Copy the code

The registration method is the same as before, using jniRegisterNativeMethods, from which we know that setpgid is the system call to Linux setgpid. Pid specifies the id of the process group to which the process belongs. If the value is 0, it is the process group to which the current process belongs. The second parameter is the id of the current process. So setgpid (0,0) means to set the id of the zygote process group to the zygote pid

summary

As a step into the Java world, this article explains the bridge between C++ and Java JNI. With it, C++ and Java can call each other. This article only covers the surface