说明

翻译KVM的文档,只是为了个人学习以做记录.如果有翻译不周到的地方,请指出,我会修正的.

为何翻译该文档

此KVM不是目前特别火的Kernel-based Virtual Machine(一个开源的系统虚拟化模块).而是一个JAVA 的虚拟机.是J2ME cldc 的一个实现.其源码的难度比hotspot简单多了.因此,想通过研读KVM,以加深对hotspot的理解

类加载, JAR文件,解压

KVM源代码包括从普通文件/目录读取Java类文件以及从(压缩)JAR文件读取的实现。一般来说,kvm类加载器可以分为两部分:

  • 通用部分
  • 平台相关部分

在文件vmcomon/src/loader.c中定义的通用部分设计为独立于目标设备的文件/存储系统。类装入器的这一部分不需要任何移植工作。jar reader 在vmextra/src/jar.c、vmextra/src/inflat.c、vmextra/h/jar.h、vmextra/h/inflat.h、vmextra/h/inflatint.h和vmextra/h/inflattables.h文件中定义,其编写方式也不需要任何移植工作。

注意 – 类加载器实现的通用部分已经在kvm 1.1中重新设计,以支持更通用、更符合J2SE的错误处理方式。

如果需要提供加载类文件的替代方法,则必须定义自己的特定于端口的类加载机制。vmextra/src/loaderfile.c中的默认实现适用于具有常规文件系统的目标系统。此实现可以用作替代特定于平台的实现的起点.

读取JAR文件的kvm代码也可以独立于读取类文件而使用。需要自己使用JAR文件的应用程序可以使用这些函数。此外,解压压缩JAR条目的函数(称为“inflation”)也可用于解压其他信息。例如,PNG图像格式使用相同的压缩和解压缩算法。

移植类文件加载接口

端口特定类文件加载接口所需的结构和函数已在文件vmcommon/h/loader.h中定义。如果不打算使用文件vmextra/src/loader file.c中提供的默认类文件加载接口,则必须为下面列出的结构和函数提供自己的定义。

必须定义C结构文件指针结构。通用代码使用定义

struct filePointerStruct; 
    typedef struct filePointerStruct *FILEPOINTER;

除此之外对这个结构的字段一无所知。

还应该对应如下函数:

  • void InitializeClassLoading()
    代码通常初始化变量ClassPathTable和虚拟机启动时加载文件所需的任何其他变量。请记住,ClassPathTable中的值通常是垃圾收集的根,必须为空或是从堆中分配的对象。
    C预处理器常量路径分隔符表示在类路径中分隔目录的字符。它的默认值是“:”。如果您使用的是Windows或类似的实现,则需要将此值更改为“;”。(在vmcomon/h/loader.h中定义)
  • void FinalizeClassLoading()

此函数与InitializeClassLoading()相反。此函数执行虚拟机关闭时所需的类加载器终结操作。根据目标体系结构的不同,实际的实现会有很大的不同。

  • FILEPOINTER openClassfile(INSTANCE_CLASS clazz)
    打开由指定clazz所对应的类文件
  • void closeClassfile(FILEPOINTER_HANDLE ClassFileH)
    关闭ClassFileH所对应的class 文件.关闭与该class 文件所关联的任何资源
  • void loadByteNoEOFCheck(FILEPOINTER_HANDLE ClassFileH)
    如果是JAR文件,则加载下一个字节,或者加载下一个字符并返回它,或者如果达到了文件结尾,则返回EOF(-1)。
  • unsigned char loadByte(FILEPOINTER_HANDLE ClassFileH)
    unsigned short loadShort(FILEPOINTER_HANDLE ClassFileH)
    unsigned long loadCell(FILEPOINTER_HANDLE ClassFileH)
    从类文件中读取下一个1、2或4个字节,并将结果返回为无符号8位、无符号16位或无符号32位值。Java类文件中的16和32位的量总是以big-endian 返回。
  • void loadBytes(FILEPOINTER_HANDLE ClassFileH, char *buffer,int len)
    将类文件中的下一个len字节加载到指定的缓冲区中。
  • int loadBytesNoEOFCheck(FILEPOINTER_HANDLE ClassFileH, char *buffer, int pos, int length)
    将类文件中的下一个长度字节加载到指定的缓冲区中,但不检查EOF。
  • void skipBytes(FILEPOINTER_HANDLE ClassFileH, unsigned long length)
    跳过类文件中的下一个长度字节
  • int getBytesAvailable(FILEPOINTER_HANDLE ClassFileH)
    获取类文件中的剩余字节数

openClassFile返回的类文件结构必须是从Java堆分配的对象

JAR reader

CLDC兼容的KVM实现需要能够从压缩的JAR文件中读取类文件。JAR文件的位置是以依赖于实现的方式指定的。

jar.c中提供了用于读取jar文件中的条目的函数。如果预处理器符号JAR_FILE_USE_STDIO为非零,则这些函数使用C标准I/O例程读取jar文件。如果此预处理器符号设置为0,则表示JAR文件在内存中。

JAR文件阅读器使用inflater,这将在下一节中讨论。

打开jar 文件

在使用JAR文件之前,必须使用函数“打开”它

bool_t openJARFile(void *nameOrAddress, int length, 
                        JAR_INFO entry)

参数如下:

  • 如果JAR_FILE_USE_STDIO不是零,那么第一个参数是jar文件的名称,第二个参数被忽略。
  • 如果JAR_FILE_USE_STDIO为零,那么第一个参数是指向jar文件开头的内存中的指针,第二个参数是jar文件的长度(以字节为单位)
  • 第三个参数是指向在jar.h中定义的struct-jarinfostruct类型的结构的指针。这个结构中填充了有关打开的jar文件的信息。如果成功打开JAR文件并解析其目录,则此函数返回true;否则返回false。

关闭jar文件

如果已使用openjar文件成功打开JAR文件,则完成后必须关闭该文件。您必须使用以下函数:

void closeJARFile(JAR_INFO entry)

参数是指向OpenJarFile所填充的相同结构的指针.

读取jar文件条目

要读取JAR文件中的特定条目,可以使用函数

static void * 
    loadJARFileEntryInternal(JAR_INFO entry, 
                     const unsigned char *centralInfo, 
                     long *lengthP, int extraBytes);

entry参数是指向OpenJarFile填充的结构的指针。centralinfo参数是以null结尾的条目名称。extra bytes条目指示JAR阅读器应该在开始时用这些额外的字节填充结果。

如果JAR reader读取成功,它会将*lengthp参数设置为JAR文件条目的长度。由于extrabytes参数,此长度不包括插入的填充。实际条目(加上填充)将作为此函数的结果返回。

如果JAR reader找不到条目,或者由于某种原因无法读取条目,则此函数返回空值。

此函数的结果是一个堆分配的对象。如果从kvm中调用此函数,则必须在必要时保护它不受垃圾收集的影响。

读取目录

要读取JAR文件的目录及其某些条目,请使用函数

void loadJARFileEntries(JAR_INFO jarFile, 
                       JARFileTestFunction testFunction, 
                       JARFileRunFunction runFunction, 
                       void* info);

arfile参数是指向由openjarfile填充的结构的指针。testFunction和runFunction参数是回调函数,其用法如下所述。JAR目录读取器不使用INFO参数,而是将该参数传递给testfunction和runfunction回调。

testfunction参数是一个回调函数,对JAR文件中的每个(非目录)条目都调用该函数。函数如下:

typedef bool_t  
        (*JARFileTestFunction)(const char *name, 
         int nameLength, 
         int *extraBytes, 
         void *info);

name和namelength参数指定jar文件目录中条目的名称。名称参数不是以null结尾的。值*ExtraBytes最初为零,但您可以将其更改为其他值,以指示结果在开始时需要用额外的字节填充。info参数与传递给loadjarFileEntries的参数相同。

如果此函数返回true,则表示要读取此条目。如果此函数返回false,则不希望读取此条目.

对于每个testfunction返回true的条目,JAR文件读取器读取数据并按如下方式调用runfunction:

typedef void  
        (*JARFileRunFunction)(const char *name, int nameLength, 
          void *value, long length, void *info);

name和namelength参数与上面相同。value参数给出读取JAR文件条目的结果。length参数是JAR文件条目的长度,不包括任何填充字节。info参数与传递给loadjarFileEntries的参数相同。

如果读取条目失败,则调用runfunction,并将value参数设置为空。

value参数是在堆上分配的,因此必须在必要时保护它不受垃圾收集的影响。

解压

解压函数可用于对使用所谓的压缩的流进行解压缩。这是JAR文件和PNG图像格式中常用的压缩算法。

解压JAR文件条目的函数也可以用于其他目的。使用以下参数调用函数.

typedef  int  (*JarGetByteFunctionType)(void *); 
 
    bool_t inflateData(void *compData, JarGetByteFunctionType 
                   getByte, 
                   int compLen, 
                   UNSIGNED_CHAR_HANDLE decompData, 
                   int decompLen);

此函数将完整字节流解压缩到解压缩字节的缓冲区中。通过重复调用获得连续的输入字节

getByte(inFile)

这个函数将被调用到complen+INFLATER_EXTRA_BYTES次,其中INFLATER_EXTRA_BYTES在inflat.h中定义为常量4。在对函数的第一个完整调用之后返回的任何值都是无关紧要的。

参数decompdata必须是指向至少包含反编译字符的缓冲区句柄的指针。使用此函数时,缓冲区必须不在堆中,或者必须向垃圾收集器注册decompdata,以便在移动缓冲区时更新decompdata。

如果解压缩成功,则此函数返回true,否则返回false。

请注意,JAR文件总是使用“/”作为目录分隔符