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