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

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

前言

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

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

本文涉及到的文件

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.cpp

一、C++调用Java

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

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);//将字符中的.转换为/
    jclass startClass = env->FindClass(slashClassName);//找到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);//调用main函数

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ...

}

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

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

基本数据类型和void

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

引用数据类型

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

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

    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

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

#if defined(__cplusplus) //如果是C++
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else //如果是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*);
    ...
}

那这个结构体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项目中,有兴趣的可以去看看. )

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

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

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

我们看到实例中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项目中,有兴趣同学可以去具体看看.)

  static jclass FindClass(JNIEnv* env, const char* name) {
    CHECK_NON_NULL_ARGUMENT(name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker(); //获取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(), descriptor.c_str()); //查找系统类
    }
    return soa.AddLocalReference<jclass>(c);
  }

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

1.3 其他env函数

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

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

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

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

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

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

函数名 作用 类比Java
GetMethodID 获取成员方法id,所有获取成员方法的方法都要传入这个值 --
CallObjectMethod 调用返回值为Object类型的成员方法 Object o=a.a()
Call(Type)Method 调用返回值为Type类型的成员方法,如CallBooleanMethod bool b=a.b()
GetStaticMethodID 获取类方法id,所有获取类方法的方法都要传入这个值 --
CallStaticObjectMethod 调用返回值为Object类型的类方法 Object o=A.a()
CallStatic(Type)Method 调用返回值为Type类型的类方法,如CallStaticBooleanMethod bool 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函数时需要传递三个参数,第一个是函数所在的类,第二个是函数名称,第三个就是函数签名

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

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

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

符号 Java类型
B byte
C char
S short
I int
F float
D double
Z boolean
J long
V void

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

符号 Java类型
Ljava/lang/String; String
[I int[]
[Ljava/lang/object; object[]

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

1.5 异常处理

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

  • ExceptionCheck
  • ExceptionOccurred

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

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

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

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

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方法,接下来我就以这两个为例子。

public static void main(String argv[]) {

        ...

        ZygoteHooks.startZygoteNoThreadCreation(); //设置标记,不允许新建线程

        try {
            Os.setpgid(0, 0); //设置zygote进程组id为zygote的pid
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to setpgid(0,0)", ex);
        }

        ...

}

startZygoteNoThreadCreation 定义在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();

2.2 native注册

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

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

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

也就是说首先得有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

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

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

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

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

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

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

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

如何理解这个定义呢?#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

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

它也是一个宏定义,指向的是RegisterNativeMethods,这个函数定义在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);
}

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

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)); //根据类名找到class
    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) { //动态注册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;
}

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

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

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

static Runtime* instance_;

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

static Runtime* Current() {
   return instance_;
}

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

由此我们可以看到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类中相关的方法,

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

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

public final class Libcore {
    private Libcore() { }

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

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

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

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

没错,这里面全是native函数,这些native的实现又在哪儿呢?老方法,找libcore_io_Linux,果然又找到了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)));
}

注册方式也是跟之前一样,用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,请参考英文官方,中文手册

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容