Chinaunix首页 | 论坛 | 博客
  • 博客访问: 195443
  • 博文数量: 47
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 470
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-18 12:09
个人简介

be a happy lazy and fat cat

文章分类
文章存档

2017年(1)

2016年(5)

2015年(22)

2014年(19)

我的朋友

分类: Android平台

2015-01-08 13:42:27


  • System.loadLibrary()是我们在使用Java的JNI机制时,会用到的一个非常重要的函数,它的作用即是把实现了我们在Java code中声明的native方法的那个libraryload进来,或者load其他什么动态连接库。

  • 算是处于好奇吧,我们可以看一下这个方法它的实现,即执行流程。(下面分析的那些code,来自于android 4.2.2 aosp版。)先看一下这个方法的code(在libcore/luni/src/main/java/java/lang/System.java这个文件中):


    点击(此处)折叠或打开

    1. /**
    2.  * Loads and links the library with the specified name. The mapping of the
    3.  * specified library name to the full path for loading the library is
    4.  * implementation-dependent.
    5.  *
    6.  * @param libName
    7.  * the name of the library to load.
    8.  * @throws UnsatisfiedLinkError
    9.  * if the library could no<span style="color:#003399;">t be loaded.
    10.  */
    11. public static void loadLibrary(String libName) {
    12.     Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
    13. }


    由上面的那段code,可以看到,它的实现非常简单,就只是先调用VMStack.getCallingClassLoader()获取到ClassLoader,然后再把实际要做的事情委托给了Runtime来做而已。接下来我们再看一下Runtime.loadLibrary()的实现(在libcore/luni/src/main/java/java/lang/Runtime.java这个文件中):


    1. /*
    2.  * Loads and links a library without security checks.
    3.  */
    4. void loadLibrary(String libraryName, ClassLoader loader) {
    5.     if (loader != null) {
    6.         String filename = loader.findLibrary(libraryName);
    7.         if (filename == null) {
    8.             throw new UnsatisfiedLinkError("Couldn't load " + libraryName
    9.                                            + " from loader " + loader
    10.                                            + ": findLibrary returned null");
    11.         }
    12.         String error = nativeLoad(filename, loader);
    13.         if (error != null) {
    14.             throw new UnsatisfiedLinkError(error);
    15.         }
    16.         return;
    17.     }
    18.  
    19.     String filename = System.mapLibraryName(libraryName);
    20.     List<String> candidates = new ArrayList<String>();
    21.     String lastError = null;
    22.     for (String directory : mLibPaths) {
    23.         String candidate = directory + filename;
    24.         candidates.add(candidate);
    25.         if (new File(candidate).exists()) {
    26.             String error = nativeLoad(candidate, loader);
    27.             if (error == null) {
    28.                 return; // We successfully loaded the library. Job done.
    29.             }
    30.             lastError = error;
    31.         }
    32.     }
    33.  
    34.     if (lastError != null) {
    35.         throw new UnsatisfiedLinkError(lastError);
    36.     }
    37.     throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    38. }


    由上面的那段code,我们看到,loadLibrary()可以被看作是一个2步走的过程:

    1. 获取到library path。对于这一点,上面的那个函数,依据于所传递的ClassLoader的不同,会有两种不同的方法。如果ClassLoader非空,则会利用ClassLoader的findLibrary()方法来获取library的path。而如果ClassLoader为空,则会首先依据传递进来的library name,获取到library file的name,比如传递“hello”进来,它的library file name,经过System.mapLibraryName(libraryName)将会是“libhello.so”;然后再在一个path list(即上面那段code中的mLibPaths)中查找到这个library file,并最终确定library 的path。
    2. 调用nativeLoad()这个native方法来load library

    这段code,又牵出几个问题,首先,可用的library path都是哪些,这实际上也决定了,我们的so文件放在哪些folder下,才可以被真正load起来?其次,在native层load library的过程,又实际做了什么事情?下面会对这两个问题,一一的作出解答。

    系统的library path

    我们由简单到复杂的来看这个问题。先来看一下,在传入的ClassLoader为空的情况(尽管我们知道,在System.loadLibrary()这个case下不会发生),前面Runtime.loadLibrary()的实现中那个mLibPaths的初始化的过程,在Runtime的构造函数中,如下:


    1. /**
    2.  * Prevent this class from being instantiated.
    3.  */
    4. private Runtime(){
    5.     String pathList = System.getProperty("java.library.path", ".");
    6.     String pathSep = System.getProperty("path.separator", ":");
    7.     String fileSep = System.getProperty("file.separator", "/");
    8.  
    9.     mLibPaths = pathList.split(pathSep);
    10.  
    11.     // Add a '/' to the end so we don't have to do the property lookup
    12.     // and concatenation later.
    13.     for (int i = 0; i < mLibPaths.length; i++) {
    14.         if (!mLibPaths[i].endsWith(fileSep)) {
    15.             mLibPaths[i] += fileSep;
    16.         }
    17.     }
    18. }


    可以看到,那个library path list实际上读取自一个system property。那在android系统中,这个system property的实际内容又是什么呢?dump这些内容出来,就像下面这样:



    点击(此处)折叠或打开

    1. 05-11 07:51:40.974: V/QRCodeActivity(11081): pathList = /vendor/lib:/system/lib
    2. 05-11 07:51:40.974: V/QRCodeActivity(11081): pathSep = :
    3. 05-11 07:51:40.974: V/QRCodeActivity(11081): fileSep = /


    然后是传入的ClassLoader非空的情况,ClassLoader的findLibrary()方法的执行过程。首先看一下它的实现(在libcore/luni/src/main/java/java/lang/ClassLoader.java这个文件中):

    点击(此处)折叠或打开

    1. /**
    2.  * Returns the absolute path of the native library with the specified name,
    3.  * or {@code null}. If this method returns {@code null} then the virtual
    4.  * machine searches the directories specified by the system property
    5.  * "java.library.path".
    6.  * <p>
    7.  * This implementation always returns {@code null}.
    8.  *

    9.  *
    10.  * @param libName
    11.  * the name of the library to find.
    12.  * @return the absolute path of the library.
    13.  */
    14. protected String findLibrary(String libName) {
    15.     return null;
    16. }


    竟然是一个空函数。那系统中实际运行的ClassLoader就是这个吗?我们可以做一个小小的实验,打印系统中实际运行的ClassLoader的String:


    点击(此处)折叠或打开

    1. ClassLoader classLoader = getClassLoader();
    2. Log.v(TAG, "classLoader = " + classLoader.toString());

    在Galaxy Nexus上执行的结果如下:
    1
    05-11 08:18:57.857: V/QRCodeActivity(11556): classLoader = dalvik.system.PathClassLoader[dexPath=/data/app/com.qrcode.qrcode-1.apk,libraryPath=/data/app-lib/com.qrcode.qrcode-1]
    看到了吧,android系统中的 ClassLoader真正的实现 在dalvik的dalvik.system.PathClassLoader。打开libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java来看 PathClassLoader这个class 的实现,可以看到,就只是简单的继承 BaseDexClassLoader而已,没有任何实际的内容 。接下来我们就来看一下 BaseDexClassLoader中 那个 findLibrary() 真正的实现( 在libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java这个文件中 ):

    点击(此处)折叠或打开

    1. @Override
    2. public String findLibrary(String name) {
    3.     return pathList.findLibrary(name);
    4. }


    这个方法看上去倒挺简单,不用多做解释。然后来看那个pathList的初始化的过程,在BaseDexClassLoader的构造函数里:


    点击(此处)折叠或打开

    1. /**
    2.  * Constructs an instance.
    3.  *
    4.  * @param dexPath the list of jar/apk files containing classes and
    5.  * resources, delimited by {@code File.pathSeparator}, which
    6.  * defaults to {@code ":"} on Android
    7.  * @param optimizedDirectory directory where optimized dex files
    8.  * should be written; may be {@code null}
    9.  * @param libraryPath the list of directories containing native
    10.  * libraries, delimited by {@code File.pathSeparator}; may be
    11.  * {@code null}
    12.  * @param parent the parent class loader
    13.  */
    14. public BaseDexClassLoader(String dexPath, File optimizedDirectory,
    15.         String libraryPath, ClassLoader parent) {
    16.     super(parent);
    17.  
    18.     this.originalPath = dexPath;
    19.     this.originalLibraryPath = libraryPath;
    20.     this.pathList =
    21.         new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    22. }


    BaseDexClassLoader的构造函数也不用多做解释吧。然后是DexPathList的构造函数:


    点击(此处)折叠或打开

    1. /**
    2.  * Constructs an instance.
    3.  *
    4.  * @param definingContext the context in which any as-yet unresolved
    5.  * classes should be defined
    6.  * @param dexPath list of dex/resource path elements, separated by
    7.  * {@code File.pathSeparator}
    8.  * @param libraryPath list of native library directory path elements,
    9.  * separated by {@code File.pathSeparator}
    10.  * @param optimizedDirectory directory where optimized {@code .dex} files
    11.  * should be found and written to, or {@code null} to use the default
    12.  * system directory for same
    13.  */
    14. public DexPathList(ClassLoader definingContext, String dexPath,
    15.         String libraryPath, File optimizedDirectory) {
    16.     if (definingContext == null) {
    17.         throw new NullPointerException("definingContext == null");
    18.     }
    19.  
    20.     if (dexPath == null) {
    21.         throw new NullPointerException("dexPath == null");
    22.     }
    23.  
    24.     if (optimizedDirectory != null) {
    25.         if (!optimizedDirectory.exists()) {
    26.             throw new IllegalArgumentException(
    27.                     "optimizedDirectory doesn't exist: "
    28.                     + optimizedDirectory);
    29.         }
    30.  
    31.         if (!(optimizedDirectory.canRead()
    32.                         && optimizedDirectory.canWrite())) {
    33.             throw new IllegalArgumentException(
    34.                     "optimizedDirectory not readable/writable: "
    35.                     + optimizedDirectory);
    36.         }
    37.     }
    38.  
    39.     this.definingContext = definingContext;
    40.     this.dexElements =
    41.         makeDexElements(splitDexPath(dexPath), optimizedDirectory);
    42.     this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    43. }


    关于我们的library path的问题,可以只关注最后的那个splitLibraryPath(),这个地方,实际上即是把传进来的libraryPath 又丢给splitLibraryPath来获取library path 的list。可以看一下DexPathList.splitLibraryPath()的实现:


    点击(此处)折叠或打开

    1. /**
    2.  * Splits the given library directory path string into elements
    3.  * using the path separator ({@code File.pathSeparator}, which
    4.  * defaults to {@code ":"} on Android, appending on the elements
    5.  * from the system library path, and pruning out any elements that
    6.  * do not refer to existing and readable directories.
    7.  */
    8. private static File[] splitLibraryPath(String path) {
    9.     /*
    10.      * Native libraries may exist in both the system and
    11.      * application library paths, and we use this search order:
    12.      *
    13.      * 1. this class loader's library path for application
    14.      * libraries
    15.      * 2. the VM's library path from the system
    16.      * property for system libraries
    17.      *
    18.      * This order was reversed prior to Gingerbread; see http://b/2933456.
    19.      */
    20.     ArrayList<File> result = splitPaths(
    21.             path, System.getProperty("java.library.path", "."), true);
    22.     return result.toArray(new File[result.size()]);
    23. }


    这个地方,是在用两个部分的library path list来由splitPaths构造最终的那个path list,一个部分是,传进来的library path,另外一个部分是,像我们前面看到的那个,是system property。然后再来看一下DexPathList.splitPaths()的实现:


    点击(此处)折叠或打开

    1. /**
    2.  * Splits the given path strings into file elements using the path
    3.  * separator, combining the results and filtering out elements
    4.  * that don't exist, aren't readable, or aren't either a regular
    5.  * file or a directory (as specified). Either string may be empty
    6.  * or {@code null}, in which case it is ignored. If both strings
    7.  * are empty or {@code null}, or all elements get pruned out, then
    8.  * this returns a zero-element list.
    9.  */
    10. private static ArrayList<File> splitPaths(String path1, String path2,
    11.         boolean wantDirectories) {
    12.     ArrayList<File> result = new ArrayList<File>();
    13.  
    14.     splitAndAdd(path1, wantDirectories, result);
    15.     splitAndAdd(path2, wantDirectories, result);
    16.     return result;
    17. }


    总结一下,ClassLoader的那个findLibrary()实际上会在两个部分的folder中去寻找System.loadLibrary()要load的那个library,一个部分是,构造ClassLoader时,传进来的那个library path,即是app folder,另外一个部分是system property。在android系统中,查找要load的library,实际上会在如下3个folder中进行:
    1. /vendor/lib
    2. /system/lib
    3. /data/app-lib/com.qrcode.qrcode-1


    上面第3个item只是一个例子,每一个app,它的那个app library path的最后一个部分都会是特定于那个app的。至于说,构造BaseDexClassLoader时的那个libraryPath 到底是怎么来的,那可能就会牵扯到android本身更复杂的一些过程了,在此不再做更详细的说明。

    Native 层load library的过程

    然后来看一下native层,把so文件load起的过程,先来一下nativeLoad()这个函数的实现(在JellyBean/dalvik/vm/native/java_lang_Runtime.cpp这个文件中):

    点击(此处)折叠或打开

    1. /*
    2.  * static String nativeLoad(String filename, ClassLoader loader)
    3.  *
    4.  * Load the specified full path as a dynamic library filled with
    5.  * JNI-compatible methods. Returns null on success, or a failure
    6.  * message on failure.
    7.  */
    8. static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
    9.     JValue* pResult)
    10. {
    11.     StringObject* fileNameObj = (StringObject*) args[0];
    12.     Object* classLoader = (Object*) args[1];
    13.     char* fileName = NULL;
    14.     StringObject* result = NULL;
    15.     char* reason = NULL;
    16.     bool success;
    17.  
    18.     assert(fileNameObj != NULL);
    19.     fileName = dvmCreateCstrFromString(fileNameObj);
    20.  
    21.     success = dvmLoadNativeCode(fileName, classLoader, &reason);
    22.     if (!success) {
    23.         const char* msg = (reason != NULL) ? reason : "unknown failure";
    24.         result = dvmCreateStringFromCstr(msg);
    25.         dvmReleaseTrackedAlloc((Object*) result, NULL);
    26.     }
    27.  
    28.     free(reason);
    29.     free(fileName);
    30.     RETURN_PTR(result);
    31. }

    可以看到,nativeLoad()实际上只是完成了两件事情,第一,是调用dvmCreateCstrFromString()将Java 的library path String 转换到native的String,然后将这个path传给dvmLoadNativeCode()做load,dvmLoadNativeCode()这个函数的实现在dalvik/vm/Native.cpp中,如下:


    点击(此处)折叠或打开

    1. /*
    2.  * Load native code from the specified absolute pathname. Per the spec,
    3.  * if we've already loaded a library with the specified pathname, we
    4.  * return without doing anything.
    5.  *
    6.  * TODO? for better results we should absolutify the pathname. For fully
    7.  * correct results we should stat to get the inode and compare that. The
    8.  * existing implementation is fine so long as everybody is using
    9.  * System.loadLibrary.
    10.  *
    11.  * The library will be associated with the specified class loader. The JNI
    12.  * spec says we can't load the same library into more than one class loader.
    13.  *
    14.  * Returns "true" on success. On failure, sets *detail to a
    15.  * human-readable description of the error or NULL if no detail is
    16.  * available; ownership of the string is transferred to the caller.
    17.  */
    18. bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
    19.         char** detail)
    20. {
    21.     SharedLib* pEntry;
    22.     void* handle;
    23.     bool verbose;
    24.  
    25.     /* reduce noise by not chattering about system libraries */
    26.     verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
    27.     verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);
    28.  
    29.     if (verbose)
    30.         ALOGD("Trying to load lib %s %p", pathName, classLoader);
    31.  
    32.     *detail = NULL;
    33.  
    34.     /*
    35.      * See if we've already loaded it. If we have, and the class loader
    36.      * matches, return successfully without doing anything.
    37.      */
    38.     pEntry = findSharedLibEntry(pathName);
    39.     if (pEntry != NULL) {
    40.         if (pEntry->classLoader != classLoader) {
    41.             ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
    42.                 pathName, pEntry->classLoader, classLoader);
    43.             return false;
    44.         }
    45.         if (verbose) {
    46.             ALOGD("Shared lib '%s' already loaded in same CL %p",
    47.                 pathName, classLoader);
    48.         }
    49.         if (!checkOnLoadResult(pEntry))
    50.             return false;
    51.         return true;
    52.     }
    53.  
    54.     /*
    55.      * Open the shared library. Because we're using a full path, the system
    56.      * doesn't have to search through LD_LIBRARY_PATH. (It may do so to
    57.      * resolve this library's dependencies though.)
    58.      *
    59.      * Failures here are expected when java.library.path has several entries
    60.      * and we have to hunt for the lib.
    61.      *
    62.      * The current version of the dynamic linker prints detailed information
    63.      * about dlopen() failures. Some things to check if the message is
    64.      * cryptic:
    65.      * - make sure the library exists on the device
    66.      * - verify that the right path is being opened (the debug log message
    67.      * above can help with that)
    68.      * - check to see if the library is valid (e.g. not zero bytes long)
    69.      * - check config/prelink-linux-arm.map to ensure that the library
    70.      * is listed and is not being overrun by the previous entry (if
    71.      * loading suddenly stops working on a prelinked library, this is
    72.      * a good one to check)
    73.      * - write a trivial app that calls sleep() then dlopen(), attach
    74.      * to it with "strace -p " while it sleeps, and watch for
    75.      * attempts to open nonexistent dependent shared libs
    76.      *
    77.      * This can execute slowly for a large library on a busy system, so we
    78.      * want to switch from RUNNING to VMWAIT while it executes. This allows
    79.      * the GC to ignore us.
    80.      */
    81.     Thread* self = dvmThreadSelf();
    82.     ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
    83.     handle = dlopen(pathName, RTLD_LAZY);
    84.     dvmChangeStatus(self, oldStatus);
    85.  
    86.     if (handle == NULL) {
    87.         *detail = strdup(dlerror());
    88.         ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
    89.         return false;
    90.     }
    91.  
    92.     /* create a new entry */
    93.     SharedLib* pNewEntry;
    94.     pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
    95.     pNewEntry->pathName = strdup(pathName);
    96.     pNewEntry->handle = handle;
    97.     pNewEntry->classLoader = classLoader;
    98.     dvmInitMutex(&pNewEntry->onLoadLock);
    99.     pthread_cond_init(&pNewEntry->onLoadCond, NULL);
    100.     pNewEntry->onLoadThreadId = self->threadId;
    101.  
    102.     /* try to add it to the list */
    103.     SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
    104.  
    105.     if (pNewEntry != pActualEntry) {
    106.         ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
    107.             pathName, classLoader);
    108.         freeSharedLibEntry(pNewEntry);
    109.         return checkOnLoadResult(pActualEntry);
    110.     } else {
    111.         if (verbose)
    112.             ALOGD("Added shared lib %s %p", pathName, classLoader);
    113.  
    114.         bool result = true;
    115.         void* vonLoad;
    116.         int version;
    117.  
    118.         vonLoad = dlsym(handle, "JNI_OnLoad");
    119.         if (vonLoad == NULL) {
    120.             ALOGD("No JNI_OnLoad found in %s %p, skipping init",
    121.                 pathName, classLoader);
    122.         } else {
    123.             /*
    124.              * Call JNI_OnLoad. We have to override the current class
    125.              * loader, which will always be "null" since the stuff at the
    126.              * top of the stack is around Runtime.loadLibrary(). (See
    127.              * the comments in the JNI FindClass function.)
    128.              */
    129.             OnLoadFunc func = (OnLoadFunc)vonLoad;
    130.             Object* prevOverride = self->classLoaderOverride;
    131.  
    132.             self->classLoaderOverride = classLoader;
    133.             oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
    134.             if (gDvm.verboseJni) {
    135.                 ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
    136.             }
    137.             version = (*func)(gDvmJni.jniVm, NULL);
    138.             dvmChangeStatus(self, oldStatus);
    139.             self->classLoaderOverride = prevOverride;
    140.  
    141.             if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
    142.                 version != JNI_VERSION_1_6)
    143.             {
    144.                 ALOGW("JNI_OnLoad returned bad version (%d) in %s %p",
    145.                     version, pathName, classLoader);
    146.                 /*
    147.                  * It's unwise to call dlclose() here, but we can mark it
    148.                  * as bad and ensure that future load attempts will fail.
    149.                  *
    150.                  * We don't know how far JNI_OnLoad got, so there could
    151.                  * be some partially-initialized stuff accessible through
    152.                  * newly-registered native method calls. We could try to
    153.                  * unregister them, but that doesn't seem worthwhile.
    154.                  */
    155.                 result = false;
    156.             } else {
    157.                 if (gDvm.verboseJni) {
    158.                     ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName);
    159.                 }
    160.             }
    161.         }
    162.  
    163.         if (result)
    164.             pNewEntry->onLoadResult = kOnLoadOkay;
    165.         else
    166.             pNewEntry->onLoadResult = kOnLoadFailed;
    167.  
    168.         pNewEntry->onLoadThreadId = 0;
    169.  
    170.         /*
    171.          * Broadcast a wakeup to anybody sleeping on the condition variable.
    172.          */
    173.         dvmLockMutex(&pNewEntry->onLoadLock);
    174.         pthread_cond_broadcast(&pNewEntry->onLoadCond);
    175.         dvmUnlockMutex(&pNewEntry->onLoadLock);
    176.         return result;
    177.     }
    178. }


    哇塞,dvmLoadNativeCode()这个函数还真的是有点复杂,那就挑那些跟我们的JNI比较紧密相关的逻辑来看吧。可以认为这个函数做了下面的这样一些事情:

    1. 调用dlopen() 打开一个so文件,创建一个handle。
    2. 调用dlsym()函数,查找到so文件中的JNI_OnLoad()这个函数的函数指针。
    3. 执行上一步找到的那个JNI_OnLoad()函数。

    至此,大体可以结束System.loadLibrary()的执行过程的分析。

    阅读(1466) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~