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

foxleezh發表於2018-04-23

這是一個連載的博文系列,我將持續為大家提供儘可能透徹的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型別
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的方法是可能引發異常的

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

相關文章