说明
翻译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文件总是使用“/”作为目录分隔符