1. 程式人生 > >Android6.0中的NativeBridge

Android6.0中的NativeBridge

從Android從5.0開始,在其ART虛擬機器的實現中,引入了一個叫做NativeBridge的中間模組。這個模組基本上就是為了在JNI呼叫時進行動態轉碼用的,自帶了基本上所有的處理邏輯。
首先,我們來看看NativeBridge的初始化過程。大家知道,ART虛擬機器的初始化是在Runtime::Init函式中進行的,在這個函式的最後,有下面這行程式碼(程式碼位於art\runtime\runtime.cc檔案中):

 782 bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
    ...
1088 // Look for a native bridge. 1089 // 1090 // The intended flow here is, in the case of a running system: 1091 // 1092 // Runtime::Init() (zygote): 1093 // LoadNativeBridge -> dlopen from cmd line parameter. 1094 // | 1095 // V 1096 // Runtime::Start() (zygote): 1097 // No-op wrt native bridge. 1098
// | 1099 // | start app 1100 // V 1101 // DidForkFromZygote(action) 1102 // action = kUnload -> dlclose native bridge. 1103 // action = kInitialize -> initialize library 1104 // 1105 // 1106 // The intended flow here is, in the case of a simple dalvikvm call: 1107 // 1108 // Runtime::Init(): 1109
// LoadNativeBridge -> dlopen from cmd line parameter. 1110 // | 1111 // V 1112 // Runtime::Start(): 1113 // DidForkFromZygote(kInitialize) -> try to initialize any native bridge given. 1114 // No-op wrt native bridge. 1115 { 1116 std::string native_bridge_file_name = runtime_options.ReleaseOrDefault(Opt::NativeBridge); 1117 is_native_bridge_loaded_ = LoadNativeBridge(native_bridge_file_name); 1118 } ... 1124 return true; 1125 }

可以看到在初始化的最後,呼叫了LoadNativeBridge函式,並傳入了一個檔名作為引數。
LoadNativeBridge函式的實現如下:

106 bool LoadNativeBridge(std::string& native_bridge_library_filename) {
107   VLOG(startup) << "Runtime::Setup native bridge library: "
108       << (native_bridge_library_filename.empty() ? "(empty)" : native_bridge_library_filename);
109   return android::LoadNativeBridge(native_bridge_library_filename.c_str(),
110                                    &native_bridge_art_callbacks_);
111 }

其中直接呼叫了android::LoadNativeBridge函式,但是多了一個引數native_bridge_art_callbacks_,看名字應該可以猜出來是一組回撥函式表。而其中的多增加的引數的含義如下:

 93 // Native bridge library runtime callbacks. They represent the runtime interface to native bridge.
 94 //    
 95 // The interface is expected to expose the following methods:
 96 // getMethodShorty(): in the case of native method calling JNI native function CallXXXXMethodY(),
 97 //   native bridge calls back to VM for the shorty of the method so that it can prepare based on
 98 //   host calling convention.
 99 // getNativeMethodCount() and getNativeMethods(): in case of JNI function UnregisterNatives(),
100 //   native bridge can call back to get all native methods of specified class so that all
101 //   corresponding trampolines can be destroyed.
102 static android::NativeBridgeRuntimeCallbacks native_bridge_art_callbacks_ {
103   GetMethodShorty, GetNativeMethodCount, GetNativeMethods
104 };

而NativeBridgeRuntimeCallbacks的定義如下:

//~/android-6.0.1_r62/system/core/include/nativebridge/native_bridge.h中
175 struct NativeBridgeRuntimeCallbacks {
176   // Get shorty of a Java method. The shorty is supposed to be persistent in memory.
177   //
178   // Parameters:
179   //   env [IN] pointer to JNIenv.
180   //   mid [IN] Java methodID.
181   // Returns:
182   //   short descriptor for method.
183   const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid);
184   
185   // Get number of native methods for specified class.
186   //
187   // Parameters:
188   //   env [IN] pointer to JNIenv.
189   //   clazz [IN] Java class object.
190   // Returns:
191   //   number of native methods.
192   uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz);
193   
194   // Get at most 'method_count' native methods for specified class 'clazz'. Results are outputed
195   // via 'methods' [OUT]. The signature pointer in JNINativeMethod is reused as the method shorty.
196   //
197   // Parameters:
198   //   env [IN] pointer to JNIenv.
199   //   clazz [IN] Java class object.
200   //   methods [OUT] array of method with the name, shorty, and fnPtr.
201   //   method_count [IN] max number of elements in methods.
202   // Returns:
203   //   number of method it actually wrote to methods.
204   uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
205                                uint32_t method_count);
206 };

這三個回撥函式的功能如下:
1)getMethodShorty:獲得指定JNI方法的短描述符;
2)getNativeMethodCount:獲得指定類內部定義的JNI函式的個數;
3)getNativeMethods:獲得指定類內部的method_count個JNI方法。

接下來,我們繼續來看看android::LoadNativeBridge函式做了些什麼

//~/android-6.0.1_r62/system/core/libnativebridge/native_bridge.cc
169 bool LoadNativeBridge(const char* nb_library_filename,
170                       const NativeBridgeRuntimeCallbacks* runtime_cbs) {
171   // We expect only one place that calls LoadNativeBridge: Runtime::Init. At that point we are not
172   // multi-threaded, so we do not need locking here.
173 
    //定義為static constexpr const char* kNotSetupString = "kNotSetup";
174   if (state != NativeBridgeState::kNotSetup) {
175     // Setup has been called before. Ignore this call.
176     if (nb_library_filename != nullptr) {  // Avoids some log-spam for dalvikvm.
177       ALOGW("Called LoadNativeBridge for an already set up native bridge. State is %s.",
178             GetNativeBridgeStateString(state));
179     }
180     // Note: counts as an error, even though the bridge may be functional.
181     had_error = true;
182     return false;
183   }
184 
185   if (nb_library_filename == nullptr || *nb_library_filename == 0) {
186     CloseNativeBridge(false);
187     return false;
188   } else {
189     if (!NativeBridgeNameAcceptable(nb_library_filename)) {
190       CloseNativeBridge(true);
191     } else {
192       // Try to open the library.
193       void* handle = dlopen(nb_library_filename, RTLD_LAZY);
194       if (handle != nullptr) {
195         callbacks = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle,
196                                                                    kNativeBridgeInterfaceSymbol));
197         if (callbacks != nullptr) {
198           if (VersionCheck(callbacks)) {
199             // Store the handle for later.
200             native_bridge_handle = handle;
201           } else {
202             callbacks = nullptr;
203             dlclose(handle);
204             ALOGW("Unsupported native bridge interface.");
205           }
206         } else {
207           dlclose(handle);
208         }
209       }
210 
211       // Two failure conditions: could not find library (dlopen failed), or could not find native
212       // bridge interface (dlsym failed). Both are an error and close the native bridge.
213       if (callbacks == nullptr) {
214         CloseNativeBridge(true);
215       } else {
216         runtime_callbacks = runtime_cbs;
217         state = NativeBridgeState::kOpened;
218       }
219     }
220     return state == NativeBridgeState::kOpened;
221   }
222 }

首先是看一下是否已經初始化過NativeBridge了,如果已經初始化過了的話直接報錯返回。再檢查傳入的檔名是否為空,NativeBridgeNameAcceptable函式來檢查檔名且是否滿足要求:只允許一些簡單名字,需要是在/system/lib或者/vendor/lib中的檔案,只允許一小部分的字元出現,包括[a-zA-Z0-9._-]以及,首字母可以是[a-zA-Z]。

如果檔名滿足要求,接著呼叫dlopen開啟檔名指定的那個動態連結庫(所以檔名應該是以.so結尾的)。如果開啟成功的話,再呼叫dlsym,在這個動態連結庫中查詢由kNativeBridgeInterfaceSymbol指定的那個符號的地址,並將其強制轉換成指向NativeBridgeCallbacks結構體的指標。
kNativeBridgeInterfaceSymbol的定義如下:

//~/android-6.0.1_r62/system/core/libnativebridge/native_bridge.cc
 40 // The symbol name exposed by native-bridge with the type of NativeBridgeCallbacks.
 41 static constexpr const char* kNativeBridgeInterfaceSymbol = "NativeBridgeItf";

而NativeBridgeCallbacks的定義為:

 95 struct NativeBridgeCallbacks {
 96   // Version number of the interface.
 97   uint32_t version;
 98 
 99   // Initialize native bridge. Native bridge's internal implementation must ensure MT safety and
100   // that the native bridge is initialized only once. Thus it is OK to call this interface for an
101   // already initialized native bridge.
102   //
103   // Parameters:
104   //   runtime_cbs [IN] the pointer to NativeBridgeRuntimeCallbacks.
105   // Returns:
106   //   true iff initialization was successful.
107   bool (*initialize)(const NativeBridgeRuntimeCallbacks* runtime_cbs, const char* private_dir,
108                      const char* instruction_set);
109 
110   // Load a shared library that is supported by the native bridge.
111   //
112   // Parameters:
113   //   libpath [IN] path to the shared library
114   //   flag [IN] the stardard RTLD_XXX defined in bionic dlfcn.h
115   // Returns:
116   //   The opaque handle of the shared library if sucessful, otherwise NULL
117   void* (*loadLibrary)(const char* libpath, int flag);
118 
119   // Get a native bridge trampoline for specified native method. The trampoline has same
120   // sigature as the native method.
121   //
122   // Parameters:
123   //   handle [IN] the handle returned from loadLibrary
124   //   shorty [IN] short descriptor of native method
125   //   len [IN] length of shorty
126   // Returns:
127   //   address of trampoline if successful, otherwise NULL
128   void* (*getTrampoline)(void* handle, const char* name, const char* shorty, uint32_t len);
129 
130   // Check whether native library is valid and is for an ABI that is supported by native bridge.
131   //
132   // Parameters:
133   //   libpath [IN] path to the shared library
134   // Returns:
135   //   TRUE if library is supported by native bridge, FALSE otherwise
136   bool (*isSupported)(const char* libpath);
137 
138   // Provide environment values required by the app running with native bridge according to the
139   // instruction set.
140   //
141   // Parameters:
142   //    instruction_set [IN] the instruction set of the app
143   // Returns:
144   //    NULL if not supported by native bridge.
145   //    Otherwise, return all environment values to be set after fork.
146   const struct NativeBridgeRuntimeValues* (*getAppEnv)(const char* instruction_set);
147 
148   // Added callbacks in version 2.
149 
150   // Check whether the bridge is compatible with the given version. A bridge may decide not to be
151   // forwards- or backwards-compatible, and libnativebridge will then stop using it.
152   //
153   // Parameters:
154   //     bridge_version [IN] the version of libnativebridge.
155   // Returns:
156   //     true iff the native bridge supports the given version of libnativebridge.
157   bool (*isCompatibleWith)(uint32_t bridge_version);
158 
159   // A callback to retrieve a native bridge's signal handler for the specified signal. The runtime
160   // will ensure that the signal handler is being called after the runtime's own handler, but before
161   // all chained handlers. The native bridge should not try to install the handler by itself, as
162   // that will potentially lead to cycles.
163   //
164   // Parameters:
165   //     signal [IN] the signal for which the handler is asked for. Currently, only SIGSEGV is
166   //                 supported by the runtime.
167   // Returns:
168   //     NULL if the native bridge doesn't use a handler or doesn't want it to be managed by the
169   //     runtime.
170   //     Otherwise, a pointer to the signal handler.
171   NativeBridgeSignalHandlerFn (*getSignalHandler)(int signal);
172 };

所以,所謂的NativeBridge應該是一個動態連結庫,這個動態連結庫中必須要包含一個符號名字為“NativeBridgeItf”的結構體,這個結構體的前4個位元組表示這個NativeBridge的版本號。實際上VersionCheck函式就是看一下這個版本號是否和自己定義的版本號一致的:

//~/android-6.0.1_r62/system/core/libnativebridge/native_bridge.cc
144 static bool VersionCheck(const NativeBridgeCallbacks* cb) {
145   // Libnativebridge is now designed to be forward-compatible. So only "0" is an unsupported
146   // version.
147   if (cb == nullptr || cb->version == 0) {
148     return false;
149   }
150 
151   // If this is a v2+ bridge, it may not be forwards- or backwards-compatible. Check.
152   if (cb->version >= 2) {
153     if (!callbacks->isCompatibleWith(kLibNativeBridgeVersion)) {
154       // TODO: Scan which version is supported, and fall back to handle it.
155       return false;
156     }
157   }
158 
159   return true;
160 }

所以版本號必須為1,否則不相容。這個結構體後面接著的7個4位元組,分別是7個指向函式的指標,這些函式都是在實現動態轉指令集功能的動態庫中提供的,它們的功能如下:
1)initialize:顧名思義,就是用來做初始化的,初始化的時候要傳入前面提到的由ART虛擬機器提供的幾個函式,包含在android::NativeBridgeRuntimeCallbacks結構體中;
2)loadLibrary:載入一個指定路徑的動態庫;
3)getTrampoline:獲得一個跳轉函式,通過它再呼叫那個不同指令集的真正的函式,這樣對於虛擬機器來說,根本就不用管是否有NativeBridge的存在了,因為轉指令集對其來說完全是透明的;
4)isSupported:檢視一個指定路徑的動態庫是否可以用這個轉指令庫進行指令轉換;
5)getAppEnv:獲得這個動態轉指令集動態庫所支援的平臺資訊。
6)isCompatibleWith:檢查該Bridge是否與給定的版本相容,一個Bridge可能會決定不再向前或者向後相容,這時libnativebridge將會停止使用它。
7)getSignalHandler:為特定signal獲取一個native bridge的signal handler 的一個回撥。

還有一個問題,實現動態轉指令集功能的動態連結庫檔名是在哪指定的呢?在Runtime::Init函式中,呼叫LoadNativeBridge的時候,傳入的是runtime_options.ReleaseOrDefault(Opt::NativeBridge),通過進一步閱讀程式碼可以發現,這個選項是在AndroidRuntime::startVm函式中,然後通過層層呼叫得到的:

//~/android-6.0.1_r62/art/runtime/parsed_options.cc中
 63 std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognized) {
    ...
252       .Define("-XX:NativeBridge=_")
253           .WithType<std::string>()
254           .IntoKey(M::NativeBridge)
...

以及

//~/android-6.0.1_r62/frameworks/base/core/jni/AndroidRuntime.cpp
 569 int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
 570 {
    ...
 908     // Native bridge library. "0" means that native bridge is disabled.
 909     property_get("ro.dalvik.vm.native.bridge", propBuf, "");
 910     if (propBuf[0] == '\0') {
 911         ALOGW("ro.dalvik.vm.native.bridge is not expected to be empty");
 912     } else if (strcmp(propBuf, "0") != 0) {
 913         snprintf(nativeBridgeLibrary, sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX,
 914                  "-XX:NativeBridge=%s", propBuf);
 915         addOption(nativeBridgeLibrary);
 916     }
     ...
 971     return 0;
 972 }

通過閱讀以上的程式碼,可以發現NativeBridge的動態庫是在ART虛擬機器啟動的時候就已經被載入進來的,當然包括所有應用程式的父程序,即Zygote程序。也就是說只要你在那個系統屬性中指定了,那麼這個NativeBridge在系統一啟動的時候就會被載入進來。

好了,我們接下來看,當要啟動一個應用程式的時候,都是從Zygote程序fork出來的,具體的fork動作是在ForkAndSpecializeCommon這個JNI函式來實現的,在這個函式中也相應加入了對NativeBridge的支援程式碼(程式碼位於framworks\base\core\jni\com_android_internal_os_Zygote.cpp檔案中):

//~/android-6.0.1_r62/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp中:
442 // Utility routine to fork zygote and specialize the child process.
443 static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
444                                      jint debug_flags, jobjectArray javaRlimits,
445                                      jlong permittedCapabilities, jlong effectiveCapabilities,
446                                      jint mount_external,
447                                      jstring java_se_info, jstring java_se_name,
448                                      bool is_system_server, jintArray fdsToClose,
449                                      jstring instructionSet, jstring dataDir) {
    ...
456   pid_t pid = fork();
457 
458   if (pid == 0) {
    ...
472     bool use_native_bridge = !is_system_server && (instructionSet != NULL)
473         && android::NativeBridgeAvailable();
474     if (use_native_bridge) {
475       ScopedUtfChars isa_string(env, instructionSet);
476       use_native_bridge = android::NeedsNativeBridge(isa_string.c_str());
477     }
478     if (use_native_bridge && dataDir == NULL) {
479       // dataDir should never be null if we need to use a native bridge.
480       // In general, dataDir will never be null for normal applications. It can only happen in
481       // special cases (for isolated processes which are not associated with any app). These are
482       // launched by the framework and should not be emulated anyway.
483       use_native_bridge = false;
484       ALOGW("Native bridge will not be used because dataDir == NULL.");
485     }
    ...
516     if (use_native_bridge) {
517       ScopedUtfChars isa_string(env, instructionSet);
518       ScopedUtfChars data_dir(env, dataDir);
519       android::PreInitializeNativeBridge(data_dir.c_str(), isa_string.c_str());
520     }
    ...
602   }
603   return pid;
604 }

所以,要正式在新應用程式程序中啟用NativeBridge,必須要滿足幾個條件:
1)不是SystemServer程序(NativeBridge主要是用來解決JNI函式的相容性問題的,SystemServer是fork自Zygote,但是它屬於系統的一部分,肯定是根據所在平臺而編譯的,因此肯定不需要轉指令集)。
2)NativeBridge已經準備好了(程式碼位於system\core\libnativebridge\native_bridge.cc檔案中):

470 bool NativeBridgeAvailable() {
471   return state == NativeBridgeState::kOpened
472       || state == NativeBridgeState::kPreInitialized
473       || state == NativeBridgeState::kInitialized;
474 }

前面在android::LoadNativeBridge函式中,在成功載入了NativeBridge之後,會把狀態設定成NativeBridgeState::kOpened,因此一般情況下NativeBridge是可用的,這個函式會返回true。

3)真的需要NativeBridge來轉指令集(程式碼位於system\core\libnativebridge\native_bridge.cc檔案中):

239 bool NeedsNativeBridge(const char* instruction_set) {
240   if (instruction_set == nullptr) {
241     ALOGE("Null instruction set in NeedsNativeBridge.");
242     return false;
243   } 
244   return strncmp(instruction_set, kRuntimeISA, strlen(kRuntimeISA) + 1) != 0;
245 } 

instruction_set是要執行的應用程式所支援的指令集,而kRuntimeISA是指當前Android系統所執行平臺的指令集,它是在編譯時就指定好了的(程式碼位於system\core\libnativebridge\native_bridge.cc檔案中):

224 #if defined(__arm__)
225 static const char* kRuntimeISA = "arm";
226 #elif defined(__aarch64__)
227 static const char* kRuntimeISA = "arm64";
228 #elif defined(__mips__)
229 static const char* kRuntimeISA = "mips";
230 #elif defined(__i386__)
231 static const char* kRuntimeISA = "x86";
232 #elif defined(__x86_64__)
233 static const char* kRuntimeISA = "x86_64";
234 #else
235 static const char* kRuntimeISA = "unknown";
236 #endif

什麼時候需要NativeBridge呢?當然是這兩個指令集不相同的時候了。要在只支援Intel X86指令集的平臺上跑只支援ARM指令集的程式,一般情況下是不可能的,NativeBridge就是為了把它變成可能才存在的。
4)有自己的資料目錄,一般應用程式是有的,在/data/data/目錄下。
如果決定要啟用NativeBridge,則還需要呼叫android::PreInitializeNativeBridge對其做預初始化:

.......

最後,做一個簡單的總結:

1)所謂的NativeBridge,其實是Android為了支援JNI函式執行時動態轉指令集而加入的一個邏輯抽象層,可以將動態轉指令集的庫與ART虛擬機器的執行邏輯進行解耦。
2)對於虛擬機器來說(目前是ART,未來換成別的也可以),需要提供android::NativeBridgeRuntimeCallbacks結構體所定義的三個函式;
3)對於真正實現動態轉指令集的庫來說,要有一個android::NativeBridgeCallbacks全域性結構體變數及提供其定義的5個函式,且這個結構體變數的名字叫做“NativeBridgeItf”;
4)可以通過更改系統屬性“ro.dalvik.vm.native.bridge”來開啟和關閉動態轉指令集的功能,將其設定為0表示關閉,而將其設定成一個有效的動態庫的路徑表示開啟。