(連載)Android 8.0 : Android虛擬機之JNI

這是一個連載的博文系列,我將持續爲你們提供儘量透徹的Android源碼分析 github連載地址html

前言

前文講到虛擬機建立後反射調用了ZygoteInit的main方法,說到虛擬機,咱們就不得不說下JNI,它是溝通Java和C++的橋樑。 JNI全稱是Java Native Interface,能夠把它理解爲一種接口編程方式,就像咱們日常開發的C/S模式同樣, Client和Server要通訊,那就得用接口。JNI主要包括兩個方面的內容:java

  • C++調用Java
  • Java調用C++

本文涉及到的文件linux

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
複製代碼

1、C++調用Java

爲何我先講C++調用Java呢?由於前文建立了虛擬機後,首先是從C++調用了Java,因此我接着前文的例子來說, 咱們回顧一下以前C++調用ZygoteInit的main函數的過程,我將分段一步步爲你們解釋。android

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. 下面是他們的對應關係git

基本數據類型和voidgithub

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

env有許多的函數,FindClass只是其中一個,做用就是根據ClassName找到對應的class, 用法是否是跟Java中反射獲取Class有點像,其實Java反射也是native方法,也得走到C++層,在實現上也是跟env->FindClass同樣.oracle

咱們來具體看看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類型
L/java/lang/String; String
[I int[]
[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表示沒有異常

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的方法是可能引起異常的

2、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,請參考英文官方,中文手冊

相關文章
相關標籤/搜索