当前位置:   article > 正文

(连载)Android 8.0 : Android虚拟机之JNI

android v8.0 ics虚拟机文件

这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 github连载地址

前言

前文讲到虚拟机创建后反射调用了ZygoteInit的main方法,说到虚拟机,我们就不得不说下JNI,它是沟通Java和C++的桥梁。 JNI全称是Java Native Interface,可以把它理解为一种接口编程方式,就像我们平常开发的C/S模式一样, Client和Server要通信,那就得用接口。JNI主要包括两个方面的内容:

  • C++调用Java
  • Java调用C++

本文涉及到的文件

  1. platform/libnativehelper/include/nativehelper/jni.h
  2. platform/art/runtime/java_vm_ext.cc
  3. platform/art/runtime/jni_internal.cc
  4. platform/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
  5. platform/libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks
  6. platform/art/runtime/native/dalvik_system_ZygoteHooks.cc
  7. platform/art/runtime/runtime.h
  8. platform/libnativehelper/JNIHelp.cpp
  9. platform/libcore/luni/src/main/java/android/system/Os.java
  10. platform/libcore/luni/src/main/java/libcore/io/Libcore.java
  11. platform/libcore/luni/src/main/java/libcore/io/BlockGuardOs.java
  12. platform/libcore/luni/src/main/java/libcore/io/ForwardingOs.java
  13. platform/libcore/luni/src/main/java/libcore/io/Linux.java
  14. platform/libcore/luni/src/main/native/libcore_io_Linux.cpp
  15. 复制代码

一、C++调用Java

为什么我先讲C++调用Java呢?因为前文创建了虚拟机后,首先是从C++调用了Java,所以我接着前文的例子来讲, 我们回顾一下之前C++调用ZygoteInit的main函数的过程,我将分段一步步为大家解释。

  1. void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
  2. {
  3. /*
  4. * We want to call main() with a String array with arguments in it.
  5. * At present we have two arguments, the class name and an option string.
  6. * Create an array to hold them.
  7. */
  8. jclass stringClass;
  9. jobjectArray strArray;
  10. jstring classNameStr;
  11. stringClass = env->FindClass("java/lang/String");
  12. assert(stringClass != NULL);
  13. strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
  14. assert(strArray != NULL);
  15. classNameStr = env->NewStringUTF(className);
  16. assert(classNameStr != NULL);
  17. env->SetObjectArrayElement(strArray, 0, classNameStr);
  18. for (size_t i = 0; i < options.size(); ++i) {
  19. jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
  20. assert(optionsStr != NULL);
  21. env->SetObjectArrayElement(strArray, i + 1, optionsStr);
  22. }
  23. /*
  24. * Start VM. This thread becomes the main thread of the VM, and will
  25. * not return until the VM exits.
  26. */
  27. char* slashClassName = toSlashClassName(className);//将字符中的.转换为/
  28. jclass startClass = env->FindClass(slashClassName);//找到class
  29. if (startClass == NULL) {
  30. ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
  31. /* keep going */
  32. } else {
  33. jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
  34. "([Ljava/lang/String;)V");
  35. if (startMeth == NULL) {
  36. ALOGE("JavaVM unable to find main() in '%s'\n", className);
  37. /* keep going */
  38. } else {
  39. env->CallStaticVoidMethod(startClass, startMeth, strArray);//调用main函数
  40. #if 0
  41. if (env->ExceptionCheck())
  42. threadExitUncaughtException(env);
  43. #endif
  44. }
  45. }
  46. free(slashClassName);
  47. ...
  48. }
  49. 复制代码

1.1 Java中各类型在C++的对应关系

比如说我们Java中有常见的Class,String,int,short等,这些在C++中并不是叫原来的名字,而是另外取了个名字, 基本就是在原来的名字前加了个j,表示java. 下面是他们的对应关系

基本数据类型和void

Java类型C++类型
booleanjboolean
bytejbyte
charjchar
shortjshort
intjint
longjlong
floatjfloat
doublejdouble
voidvoid

引用数据类型

Java类型C++类型
All objectsjobject
java.lang.Class实例jclass
java.lang.String实例jstring
java.lang.Throwable实例jthrowable
Object[](包含Class,String,Throwable)jobjectArray
boolean[]jbooleanArray
byte[](其他基本数据类型类似)jbyteArray

那其实下面的代码就好理解了,就相当于定义了三个局部变量,类型为Class,String[],String

  1. jclass stringClass;
  2. jobjectArray strArray;
  3. jstring classNameStr;
  4. 复制代码

1.2 env->FindClass

我们再接着往下看,env->FindClass, env是虚拟机的环境,可以类比为Android中无处不在的Context, 但是这个env是指特定线程的环境,也就是说一个线程对应一个env.

env有许多的函数,FindClass只是其中一个,作用就是根据ClassName找到对应的class, 用法是不是跟Java中反射获取Class有点像,其实Java反射也是native方法,也得走到C++层,在实现上也是跟env->FindClass一样.

我们来具体看看env->FindClass的实现,env的类型是JNIEnv,定义在platform/libnativehelper/include/nativehelper/jni.h中, 这个JNIEnv 在C环境和C++环境类型不一样,在C环境中定义的是JNINativeInterface* , 而C++中定义的是_JNIEnv,_JNIEnv其实内部也是调用JNINativeInterface的对应函数,只是做了层代理, JNINativeInterface是个结构体,里面就有我们要找的函数FindClass

  1. #if defined(__cplusplus) //如果是C++
  2. typedef _JNIEnv JNIEnv;
  3. typedef _JavaVM JavaVM;
  4. #else //如果是C
  5. typedef const struct JNINativeInterface* JNIEnv;
  6. typedef const struct JNIInvokeInterface* JavaVM;
  7. #endif
  8. struct _JNIEnv {
  9. const struct JNINativeInterface* functions;
  10. ...
  11. jclass FindClass(const char* name)
  12. { return functions->FindClass(this, name); }
  13. ...
  14. }
  15. struct JNINativeInterface {
  16. ...
  17. jclass (*FindClass)(JNIEnv*, const char*);
  18. ...
  19. }
  20. 复制代码

那这个结构体JNINativeInterface中FindClass的函数指针什么时候赋值的呢?还记得上文中有个创建虚拟机的函数JNI_CreateJavaVM, 里面有个参数就是JNIEnv,其实也就是在创建虚拟机的时候把函数指针赋值的,我们知道JNI_CreateJavaVM是加载libart.so时获取的, 那我们就得找libart.so的源码,这个对应的源码在platform/art/runtime/java_vm_ext.cc,它会调用Runtime::Create函数去新建线程, 在线程新建的过程中会对JNIEnv进行赋值,JNI_CreateJavaVM函数最后会去调用线程的GetJniEnv得到JNIEnv的实例,将实例赋值给p_env.

(线程在新建过程中如何对JNIEnv进行赋值的,就不细讲了,我提供几个关键的函数,runtime.cc的Create和Init、thread.cc的Attach和Init、 jni_env_ext.cc的Create、jni_internal.cc的GetJniNativeInterface,涉及到的文件我都放在AOSP项目中,有兴趣的可以去看看. )

  1. extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  2. ...
  3. if (!Runtime::Create(options, ignore_unrecognized)) {
  4. return JNI_ERR;
  5. }
  6. *p_env = Thread::Current()->GetJniEnv();
  7. }
  8. 复制代码

GetJniEnv返回的是一个JNINativeInterface的实例,定义在/platform/art/runtime/jni_internal.cc,其中就有我们要找的FindClass

  1. const JNINativeInterface gJniNativeInterface = {
  2. nullptr, // reserved0.
  3. nullptr, // reserved1.
  4. nullptr, // reserved2.
  5. nullptr, // reserved3.
  6. JNI::GetVersion,
  7. JNI::DefineClass,
  8. JNI::FindClass,
  9. }
  10. 复制代码

我们看到实例中FindClass对应的函数是JNI::FindClass,定义在当前文件中,FindClass的工作是交给ClassLinker, ClassLinker内部的实现是通过ClassLoader获取一个ClassTable对象,再通过ClassTable中的一个HashSet得到对应的Class, ClassLoader其实我们也比较熟悉,Java层中就有,我们apk中的dex文件就是需要ClassLoader去加载,最终会将Class装进一个HashSet中, 因此,我们FindClass也去这个HashSet中去找.

(ClassLinker内部的实现我就不细讲了,我提供几个关键的函数,class_linker.cc的FindClass和LookupClass、class_table.cc的Lookup ,涉及到的文件我都放在AOSP项目中,有兴趣同学可以去具体看看.)

  1. static jclass FindClass(JNIEnv* env, const char* name) {
  2. CHECK_NON_NULL_ARGUMENT(name);
  3. Runtime* runtime = Runtime::Current();
  4. ClassLinker* class_linker = runtime->GetClassLinker(); //获取ClassLinker
  5. std::string descriptor(NormalizeJniClassDescriptor(name));
  6. ScopedObjectAccess soa(env);
  7. mirror::Class* c = nullptr;
  8. if (runtime->IsStarted()) {
  9. StackHandleScope<1> hs(soa.Self());
  10. Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
  11. c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader); //查找类
  12. } else {
  13. c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str()); //查找系统类
  14. }
  15. return soa.AddLocalReference<jclass>(c);
  16. }
  17. 复制代码

说完env->FindClass,其实其他env->方式调用的函数也就大体知道源码在哪儿了,在接下来的分析中我就只说明下对应函数的作用,具体实现可以根据 自己的需要深入去看.

1.3 其他env函数

env函数特别多,我这里只列举一些我们常用的

新建实例,相当于Java中的new

函数名作用类比Java
NewObject新建Objectnew Object
NewStringUTF新建String字符new String()
NewObjectArray新建Object数组new Object[]
New(Type)Array新建Type数组,如NewByteArraynew byte[]

获取和设置成员变量和类变量,相当于Java中的获取和设置变量,下面以A a=new A()为例子

函数名作用类比Java
GetFieldID获取成员变量id,所有获取成员变量的方法都要传入这个值--
GetObjectField获取Object类型的成员变量a.object
Get(Type)Field获取Type类型的成员变量,如GetBooleanFieldbool b=a.bool
Set(Type)Field设置Type类型的成员变量,如SetBooleanFielda.bool=b
GetStaticFieldID获取类变量id,所有获取类变量的方法都要传入这个值--
GetStaticObjectField获取Object类型的类变量A.object
GetStatic(Type)Field获取Type类型的类变量,如GetStaticBooleanFieldbool b=A.bool
SetStatic(Type)Field设置Type类型的类变量,如SetStaticBooleanFieldA.bool=b

调用成员方法和类方法,相当于Java中的调用方法,下面以A a=new A()为例子

函数名作用类比Java
GetMethodID获取成员方法id,所有获取成员方法的方法都要传入这个值--
CallObjectMethod调用返回值为Object类型的成员方法Object o=a.a()
Call(Type)Method调用返回值为Type类型的成员方法,如CallBooleanMethodbool b=a.b()
GetStaticMethodID获取类方法id,所有获取类方法的方法都要传入这个值--
CallStaticObjectMethod调用返回值为Object类型的类方法Object o=A.a()
CallStatic(Type)Method调用返回值为Type类型的类方法,如CallStaticBooleanMethodbool b=A.b()

数组相关操作,以bool[] bs=new bool[] 为例

函数名作用类比Java
Get(Type)ArrayElements获取Type类型的数组的某个元素bool b=bs[0]
Set(Type)ArrayElements设置Type类型的数组的某个元素bs[0]=b

内存释放相关,这个是C++独有的,没有Java相应的调用

函数名作用类比Java
ReleaseStringUTFChars释放String--
Release(Typge)ArrayElements释放Type类型的数组--

我这里只是笼统地列举了一些env函数的作用,对于参数及返回值并没有细讲,主要是这些属于API范畴的东西,要用的时候再查也不迟

1.4 函数签名

start函数最后会调用main函数,在获取main函数时需要传递三个参数,第一个是函数所在的类,第二个是函数名称,第三个就是函数签名

  1. jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");
  2. 复制代码

函数签名其实就是对一个函数的参数及返回值的一种符号表示,表示形式是 (params)return 下面我列举一下符号与Java类型的一一对应关系:

基本数据类型和void,我们可以看到除了boolean和long表示得不一样外,其他都是以首字母进行表示,我想主要原因可能是B与byte冲突了,L与object冲突

符号Java类型
Bbyte
Cchar
Sshort
Iint
Ffloat
Ddouble
Zboolean
Jlong
Vvoid

引用数据类型和数组,引用数据类型以L开头,后面接完整路径,最后有个分号,这个分号一定不要忘记!一定不要忘记!一定不要忘记!数组用 [ 表示

符号Java类型
L/java/lang/String;String
[Iint[]
[L/java/lang/object;object[]

我们回到刚才的例子 ([Ljava/lang/String;)V ,这个就表示main函数的参数是String[],返回值是void.

1.5 异常处理

我们在Java中经常用try catch来处理异常非常方便,我们在C++中调用Java函数时,也可以去捕获异常,我们可以有两种方式:

  • ExceptionCheck
  • ExceptionOccurred

我先讲讲 ExceptionCheck ,这个函数是会返回一个bool值,true表示有异常,false表示没有异常

  1. env->CallStaticVoidMethod(cls,mid);
  2. if (env->ExceptionCheck()) { // 检查JNI调用是否有引发异常
  3. env->ExceptionDescribe(); //打印错误日志堆栈信息
  4. env->ExceptionClear(); // 清除引发的异常
  5. env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"JNI抛出的异常!"); //抛出异常
  6. }
  7. 复制代码

再看看ExceptionOccurred,这个用法其实跟ExceptionCheck差不多,只是它返回的不是bool值,而是当前异常的引用

  1. jthrowable exc = NULL;
  2. exc = env->ExceptionOccurred(); // 返回一个指向当前异常对象的引用
  3. if (exc) {
  4. env->ExceptionDescribe(); //打印错误日志堆栈信息
  5. env->ExceptionClear(); // 清除引发的异常
  6. env->ThrowNew(env->FindClass(env,"java/lang/Exception"),"JNI抛出的异常!"); //抛出异常
  7. }
  8. 复制代码

start函数最后就用到了ExceptionCheck,因为调用Java的方法是可能引发异常的

二、Java调用C++

讲完了C++调用Java,我们再看看Java如何调用C++,我们接着前面的讲,之前通过 env->CallStaticVoidMethod(startClass, startMeth, strArray) 调用了ZygoteInit的 main 函数,我们就以main函数为例讲解Java调用C++的过程。

2.1 main函数

main函数开头有两个方法调用 startZygoteNoThreadCreation和setpgid,这两个其实都是native方法,接下来我就以这两个为例子。

  1. public static void main(String argv[]) {
  2. ...
  3. ZygoteHooks.startZygoteNoThreadCreation(); //设置标记,不允许新建线程
  4. try {
  5. Os.setpgid(0, 0); //设置zygote进程组id为zygote的pid
  6. } catch (ErrnoException ex) {
  7. throw new RuntimeException("Failed to setpgid(0,0)", ex);
  8. }
  9. ...
  10. }
  11. 复制代码

startZygoteNoThreadCreation 定义在platform/libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks中

  1. /*
  2. * Called by the zygote when starting up. It marks the point when any thread
  3. * start should be an error, as only internal daemon threads are allowed there.
  4. */
  5. public static native void startZygoteNoThreadCreation();
  6. 复制代码

2.2 native注册

startZygoteNoThreadCreation 是一个native方法,我们知道native方法有两种注册方式,一种是静态注册,一种动态注册。

所谓静态注册就是根据函数名称和一些关键字就可以注册, 比如 startZygoteNoThreadCreation 要静态注册的话,它对应的实现函数应该是

  1. JNIEXPORT void JNICALL Java_dalvik_system_ZygoteHooks_startZygoteNoThreadCreation(JNIEnv *, jobject){
  2. }
  3. 复制代码

也就是说首先得有JNIEXPORT,JNICALL这些关键字,其次函数名称必须以Java开头,后面接的是native函数所在类的完整路径加native函数名, 最后参数及返回值要相同,参数会多出两个:

  • JNIEnv,表示JNI上下文,
  • 一个是jobject,如果是static方法表示调用native函数的Class. 如果是普通方法表示调用native函数的对象

只要你按照这个规则写,Java的native函数就会自动调用这个C++层的函数。这种静态的注册方式有个不好的地方就是函数名太长,书写不方便,而且在首次调用时会有一个注册过程, 影响效率,那有没有其他方式呢?答案就是动态注册

其实大多数frameworks层的native函数都是用动态方式注册的,startZygoteNoThreadCreation函数也是

我们怎么寻找startZygoteNoThreadCreation的实现呢?这里有个规律,Google工程师喜欢以native所在类的完整路径为C++的实现类名,比如 startZygoteNoThreadCreation所在类的完整路径是dalvik.system.ZygoteHooks,我们尝试搜索dalvik_system_ZygoteHooks, 就会出现dalvik_system_ZygoteHooks.h和dalvik_system_ZygoteHooks.cc,我们看下dalvik_system_ZygoteHooks.cc

  1. static JNINativeMethod gMethods[] = {
  2. NATIVE_METHOD(ZygoteHooks, nativePreFork, "()J"),
  3. NATIVE_METHOD(ZygoteHooks, nativePostForkChild, "(JIZLjava/lang/String;)V"),
  4. NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V"),
  5. NATIVE_METHOD(ZygoteHooks, stopZygoteNoThreadCreation, "()V"),
  6. };
  7. void register_dalvik_system_ZygoteHooks(JNIEnv* env) {
  8. REGISTER_NATIVE_METHODS("dalvik/system/ZygoteHooks");
  9. }
  10. 复制代码

本来动态注册是个很简单的过程,直接调用 env->RegisterNatives ,将绑定信息作为参数即可,但是这个源码里写得比较复杂,我一步步讲吧

首先Java的native方法要调用到C++函数,肯定得有个键值对作为绑定信息,也就是告诉虚拟机哪个native该执行哪个C++函数,gMethods就是这样一个角色

gMethods数组的类型是JNINativeMethod,我们回顾下 JNINativeMethod ,它是一个结构体,name表示native函数名,signature表示用字符串描述native函数的参数和返回值, fnPtr表示native指向的C++函数指针,这其实就是动态注册的映射关系了,将native函数对应一个C++函数

  1. typedef struct {
  2. const char* name;
  3. const char* signature;
  4. void* fnPtr;
  5. } JNINativeMethod;
  6. 复制代码

但是gMethods数组中却是NATIVE_METHOD,我们看看这个NATIVE_METHOD是什么

  1. #define NATIVE_METHOD(className, functionName, signature) \
  2. { #functionName, signature, (void*)(className ## _ ## functionName) }
  3. 复制代码

如何理解这个定义呢?#define是宏定义,也就是说编译期间要做宏替换,这里就是把NATIVE_METHOD替换成 {"","",(void*)()},具体怎么替换呢?我们看到{}里有些#、##,#表示字符串化,相当于Java中的toString,##表示字符串化拼接,相当于Java中的 String.format,以NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V")为例,替换后就是 {"startZygoteNoThreadCreation","()V",(void*)(ZygoteHooks_startZygoteNoThreadCreation) }

JNINativeMethod只是个结构体,真正注册的函数是在 REGISTER_NATIVE_METHODS("dalvik/system/ZygoteHooks"),我们先看看 REGISTER_NATIVE_METHODS

  1. #define REGISTER_NATIVE_METHODS(jni_class_name) \
  2. RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
  3. 复制代码

它也是一个宏定义,指向的是RegisterNativeMethods,这个函数定义在platform/frameworks/base/core/jni/AndroidRuntime.cpp

  1. /*
  2. * Register native methods using JNI.
  3. */
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
  5. const char* className, const JNINativeMethod* gMethods, int numMethods)
  6. {
  7. return jniRegisterNativeMethods(env, className, gMethods, numMethods);
  8. }
  9. 复制代码

其实它调用的是jniRegisterNativeMethods,这个定义在platform/libnativehelper/JNIHelp.cpp, jniRegisterNativeMethods函数首先是将传过来的类名字符串找到对应的class,然后就是调用(*env)->RegisterNatives动态注册JNI, 其实调用这么多层,动态注册最关键的就是构建一个结构体JNINativeMethod,然后调用(*env)->RegisterNatives,RegisterNatives属于 虚拟机内的函数了,今后讲虚拟机时我再具体去分析,这里我们知道它的作用就行了.

  1. extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
  2. const JNINativeMethod* gMethods, int numMethods)
  3. {
  4. JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
  5. ALOGV("Registering %s's %d native methods...", className, numMethods);
  6. scoped_local_ref<jclass> c(env, findClass(env, className)); //根据类名找到class
  7. if (c.get() == NULL) {
  8. char* tmp;
  9. const char* msg;
  10. if (asprintf(&tmp,
  11. "Native registration unable to find class '%s'; aborting...",
  12. className) == -1) {
  13. // Allocation failed, print default warning.
  14. msg = "Native registration unable to find class; aborting...";
  15. } else {
  16. msg = tmp;
  17. }
  18. e->FatalError(msg);
  19. }
  20. if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { //动态注册jni
  21. char* tmp;
  22. const char* msg;
  23. if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
  24. // Allocation failed, print default warning.
  25. msg = "RegisterNatives failed; aborting...";
  26. } else {
  27. msg = tmp;
  28. }
  29. e->FatalError(msg);
  30. }
  31. return 0;
  32. }
  33. 复制代码

我们接着上面的startZygoteNoThreadCreation函数讲,由上可知这个native函数实际会调用ZygoteHooks_startZygoteNoThreadCreation, 它定义在platform/art/runtime/native/dalvik_system_ZygoteHooks.cc

  1. static void ZygoteHooks_startZygoteNoThreadCreation(JNIEnv* env ATTRIBUTE_UNUSED,
  2. jclass klass ATTRIBUTE_UNUSED) {
  3. Runtime::Current()->SetZygoteNoThreadSection(true);
  4. }
  5. 复制代码

其实它又是调用Runtime的SetZygoteNoThreadSection函数,这个定义在platform/art/runtime/runtime.h,这个函数的实现非常简单, 就是将zygote_no_threads_这个bool值设置为想要的值

  1. static Runtime* instance_;
  2. // Whether zygote code is in a section that should not start threads.
  3. bool zygote_no_threads_;
  4. static Runtime* Current() {
  5. return instance_;
  6. }
  7. void SetZygoteNoThreadSection(bool val) {
  8. zygote_no_threads_ = val;
  9. }
  10. 复制代码

由此我们可以看到startZygoteNoThreadCreation这个native函数经过层层调用,最终就是将一个bool变量设置为true. 讲得是有点多了, 这里主要是告诉大家如何去追踪native函数的实现,因为这是阅读frameworks层代码必备的技能. 这里我还是再次推荐大家用Source Insight 来看代码,不管是函数跳转还是全局搜索都是非常方便的,详情请看我之前写的如何阅读Android源码

4.1.2 setpgid

定义在platform/libcore/luni/src/main/java/android/system/Os.java

这个Os.java类是比较特殊的一个类,这个类相当于一个代理类,所有的方法都是去调用Libcore.os类中相关的方法,

  1. /**
  2. * See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
  3. */
  4. /** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
  5. 复制代码

而Libcore.os的实现类是BlockGuardOs,BlockGuardOs的父类是ForwardingOs,ForwardingOs也是个代理类,里面所有方法都是调用 Linux.java中的对应函数,也就是说Os.java中的函数最终调用的是Linux.java中的函数. 另外在BlockGuardOs类中有重载一些方法,做了一些 Policy权限的检查.

  1. public final class Libcore {
  2. private Libcore() { }
  3. /**
  4. * Direct access to syscalls. Code should strongly prefer using {@link #os}
  5. * unless it has a strong reason to bypass the helpful checks/guards that it
  6. * provides.
  7. */
  8. public static Os rawOs = new Linux();
  9. /**
  10. * Access to syscalls with helpful checks/guards.
  11. */
  12. public static Os os = new BlockGuardOs(rawOs);
  13. }
  14. 复制代码

我们再来看看Linux.java的实现是怎样的

  1. public final class Linux implements Os {
  2. Linux() { }
  3. ...
  4. public native void setpgid(int pid, int pgid) throws ErrnoException;
  5. ...
  6. }
  7. 复制代码

没错,这里面全是native函数,这些native的实现又在哪儿呢?老方法,找libcore_io_Linux,果然又找到了libcore_io_Linux.cpp

  1. static JNINativeMethod gMethods[] = {
  2. ...
  3. NATIVE_METHOD(Linux, setpgid, "(II)V"),
  4. ...
  5. }
  6. void register_libcore_io_Linux(JNIEnv* env) {
  7. jniRegisterNativeMethods(env, "libcore/io/Linux", gMethods, NELEM(gMethods));
  8. }
  9. static void Linux_setpgid(JNIEnv* env, jobject, jint pid, int pgid) {
  10. throwIfMinusOne(env, "setpgid", TEMP_FAILURE_RETRY(setpgid(pid, pgid)));
  11. }
  12. 复制代码

注册方式也是跟之前一样,用jniRegisterNativeMethods,由此我们知道setpgid就是调用Linux的系统调用setgpid. 这个系统调的作用是设置进程组id,第一个参数pid是指设置哪个进程所属的进程组,如果是0,就是当前进程所属的进程组,第二个参数是设置的id值, 如果是0,那么就把当前进程的pid作为进程组的id. 所以setgpid(0,0)的意思就是将zygote进程所在进程组id设置为zygote的pid

小结

作为进入Java世界的铺垫,本篇讲解了C++与Java之间的桥梁JNI,有了它,C++和Java就可以相互调用,本文只是讲了一些皮毛的东西,要深入理解和使用JNI,请参考英文官方,中文手册

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/577002
推荐阅读
相关标签
  

闽ICP备14008679号