8. Inicio y terminación de la KVM.

Anuncio
8. Inicio y terminación de la KVM.
Cuando se instala la KVM y en general cualquier maquina virtual en un sistema
operativo, se ubica en algún punto de la estructura de directorios del sistema un archivo
(usualmente de nombre kvm.exe) que es el que se debe ejecutar para iniciar la maquina
virtual.
El proceso de inicialización de la maquina virtual Java y en particular de la
KVM es un proceso relativamente complejo pues conlleva una actuación sobre casi
todos los módulos que la integran. Además dichas actuaciones dependen en gran
medida del entorno de ejecución y del sistema operativo en el cual este instalado la
maquina virtual.
Este proceso de inicialización como se verá en el siguiente apartado, es el punto
de comienzo para el flujo genérico de ejecución de la KVM. Es decir, actúa como la
función main típica de entrada de cualquier programa escrito en C.
La KVM suministra dos mecanismos para el inicio de la maquina virtual
dependiendo del tipo de plataforma en la que nos encontremos:
•
•
A través de la línea de comandos del sistema.
A través de interfaces que son sean la línea de comandos como puede ser una
interfaz GUI, un micro-browser, etc…
El primero de los mecanismo es el proceso típico de ejecución de un archivo, en
esta caso la maquina virtual. Es decir, desde la línea de comandos se ejecuta el archivo
kvm.exe pasándole los parámetros que se deseen separados por espacios. El segundo
mecanismo es en realidad una máscara para el primer mecanismo, es decir esta pensado
para aquellos sistemas que dispongan de una interfaz mas amigable que la línea de
comandos para arrancar la maquina virtual.
El proceso de finalización de la maquina virtual, por otro lado, es fundamental
pues con el se ha de asegurar que se liberan todos los recursos que se habían ocupado
durante la ejecución de la KVM.
8.1. Flujo genérico de la KVM.
La operación que se encarga de controlar el flujo de ejecución de la KVM para
cualquier tipo de plataforma es la siguiente:
int StartJVM(int argc, char* argv[])
Como vemos tiene los mismos parámetros que una función main puesto que
actúa como tal. Dentro de esta operación se comprueba primero si existe alguna clase a
ejecutar dentro de los parámetros que se han pasado (argv):
if (argc <= 0 || argv[0] == NULL) {
AlertUser(KVM_MSG_MUST_PROVIDE_CLASS_NAME);
return -1;
}
Si existe tal clase se invoca a la operación interna KVM_start que inicia la
maquina virtual y empieza a ejecutar la clase que ha sido pasada como parámetro.
returnValue = KVM_Start(argc, argv);
Cuando se termina la ejecución de la KVM sea por el motivo que sea se invoca
la operación KVM_cleanUp (para finalizar correctamente todas las gestiones de la
maquina virtual con el sistema como por ejemplo la memoria):
KVM_Cleanup();
Finalmente se devuelve al sistema el valor de retorno que se obtuvo al finalizar
la ejecución de la maquina virtual y que indica si ha existido algún error en su
ejecución:
return returnValue;
8.2. Inicio privado de la maquina virtual.
La operación que se encarga de iniciar la maquina virtual como hemos
comentado en al apartado anterior es:
int KVM_Start(int argc, char* argv[])
Esta operación simplemente invoca de forma iterativa las operaciones de
inicialización de cada uno de los módulos que componen la KVM. Así se ejecutan por
este orden los siguientes módulos:
•
Creación de la memoria ROM: sólo si la opción ROMIZING esta activa:
CreateROMImage();
•
Inicialización de operaciones I/O asíncronas: solo
AYNCHRONOUS_NATIVE_FUNCTIONS esta activa:
InitalizeAsynchronousIO();
•
Inicializa el módulo de interfaces nativas:
InitializeNativeCode();
•
Inicializa el núcleo de la maquina virtual:
InitializeVM();
•
Inicializa las variables globales:
InitializeGlobals();
si
la
opción
•
Inicializa los variables para datos estadísticos (profiling):
InitializeProfiling();
•
Inicializa el gestor de memoria:
InitializeMemoryManagement();
•
Inicializa las hashtables que se emplean de forma auxiliar para almacenamiento
de datos:
InitializeHashtables();
•
Inicializa la cache del sistema:
InitializeInlineCaching();
•
Inicializa el módulo Loader:
InitializeClassLoading();
•
Inicializa el sistema de clases Java y sus estructuras internas:
InitializeJavaSystemClasses();
•
Inicializa el verificador de clases:
InitializeVerifier();
•
Inicializa el gestor de eventos del sistema:
InitializeEvents();
El siguiente paso a seguir es la inicialización de la KVM es cargar la clase
principal por la cual comienza la ejecución que sería la clase que contiene la función
main, para ello se invoca a la función auxiliar loadMainClass. En caso de que no se
encuentre la clase se alerta al usuario y se sale de la maquina virtual devolviendo un
código de error -1:
mainClass = loadMainClass(argv[0]);
if (!mainClass) {
sprintf(str_buffer, KVM_MSG_CLASS_NOT_FOUND_1STRPARAM,
argv[0]);
AlertUser(str_buffer);
returnValue = 1;
}
Si se ha podido cargar la clase que contiene el método main el siguiente paso es
mediante la función auxiliar readCommandLineArguments parseamos los parámetros
pasados por línea de comandos por el usuario a un array:
ARRAY arguments = readCommandLineArguments(argc - 1, argv + 1);
Una vez que ya hemos cargado la función main se inicializa el módulo de
gestión de hilos de la KVM (con el array de argumentos antes creado y la clase main) y
en caso de que este activado el depurador de inicializa dicho módulo:
InitializeThreading(mainClass, arguments);
#if ENABLE_JAVA_DEBUGGER
if (debuggerActive) {
InitDebugger();
}
#endif
Si bien las clases de sistema ya han sido cargadas e inicializadas anteriormente
es conveniente asegurarse de que esto ha sucedido así por lo que se inicializan las clases
del sistema Java y se ponen en la pila de ejecución:
initializeClass(JavaLangOutOfMemoryError);
initializeClass(JavaLangSystem);
initializeClass(JavaLangString);
initializeClass(JavaLangThread);
initializeClass(JavaLangClass);
Una vez más si el depurador esta activado y ejecutándose se lanza un evento que
indica al usuario que la maquina virtual acaba de arrancar:
#if ENABLE_JAVA_DEBUGGER
if (vmDebugReady) {
setEvent_VMInit();
if (CurrentThread == NIL) {
CurrentThread = removeFirstRunnableThread();
loadExecutionEnvironment(CurrentThread);
}
sendAllClassPrepares();
}
#endif
Finalmente se arranca el intérprete que es el que comienza a ejecutar los
bytecodes de los programas Java creados y ya compilados previamente por el usuario:
Interpret();
Todo el bloque de inicialización de la KVM forma un bloque del tipo TRYCATCH para que de esta forma si se produce un error en alguno de los procesos de
inicialización que conlleva capture el error y devuelva el código correspondiente sin
seguir inicializando la maquina virtual.
8.3. Finalización privada de la KVM.
El proceso de finalización de la KVM se ejecuta automáticamente desde el flujo
genérico de ejecución una vez que la maquina virtual ha terminado de ejecutar todos los
programas o se ha producido un error. La operación es la siguiente:
void KVM_Cleanup()
El proceso comienza lanzando el evento de finalización de la maquina virtual si
el depurador esta activo y ejecutándose, se cierra el depurador y se eliminan todos los
breakpoints que pudieran existir:
#if ENABLE_JAVA_DEBUGGER
if (vmDebugReady) {
setEvent_VMDeath();
CloseDebugger();
clearAllBreakpoints();
}
#endif
Seguidamente se invocan las operaciones de finalización de los módulos que en
su funcionamiento implican uso de recursos de sistema (memoria, dispositivos I/O,…):
•
Finalización genérica de la maquina virtual:
FinalizeVM();
•
Finalización de la memoria caché:
FinalizeInlineCaching();
•
Finalización de funciones nativas:
FinalizeNativeCode();
•
Finalización del sistema Java de clases:
FinalizeJavaSystemClasses();
•
Finalización del módulo Loader:
FinalizeClassLoading();
•
Finalización del módulo de gestión de memoria:
FinalizeMemoryManagement();
•
Destrucción de la memoria ROM:
DestroyROMImage();
•
Finalización de la estructuras auxiliares hashtables:
FinalizeHashtables();
8.4. Inicio de la KVM desde línea de comandos.
Para todos aquellos sistemas operativos o dispositivos sobre los cuales se va a
instalar y ejecutar la KVM que soporten línea de comandos (caso de Windows o Linux)
se emplea una función genérica main de arranque. Es decir la KVM se iniciaría desde la
función main contenida en main.h.
Al arrancar la maquina virtual primero se configuran una serie de parámetros
que son:
•
El uso del módulo JAM (Java Application Manager):
#if USE_JAM
char *jamInstalledAppsDir = "./instapps";
#endif
JamEnabled = FALSE;
JamRepeat = FALSE;
•
El tamaño de memoria por defecto que se va a usar:
RequestedHeapSize = DEFAULTHEAPSIZE;
•
El flag que se emplea para habilitar la suspensión de la maquina virtual desde el
módulo de depuración:
#if ENABLE_JAVA_DEBUGGER
suspend = TRUE;
#endif
El siguiente paso es leer de la línea de comandos los distintos parámetros que se
pasan (por ejemplo java –version –jar Prueba.jar) y que permiten configurar como se
ejecutara la maquina virtual:
•
Para que muestre por salida estándar la versión actual de la KVM sin arrancarla:
if (strcmp(argv[1], "-version") == 0) {
fprintf(stdout, "Version: %s\n", BUILD_VERSION);
exit(1);
}
•
Para que muestre por la salida estándar el conjunto de parámetros de arranque
disponibles sin arrancar la KVM. Se hace uso de la funció n auxiliar
printHelpText:
if (strcmp(argv[1], "-help") == 0) {
printHelpText();
exit(0);
}
•
Para ejecutar o no el depurador (activarlo o no se hace a través de banderas de
compilación de la propia maquina virtual):
if (strcmp(argv[1], "-debugger") == 0) {
debuggerActive = TRUE;
argv++; argc--;
}
•
Para permitir que sea suspendido o no la ejecución:
if (strcmp(argv[1], "-suspend") == 0) {
suspend = TRUE;
argv++; argc--;
} else if (strcmp(argv[1], "-nosuspend") == 0) {
suspend = FALSE;
argv++; argc--;
}
•
Para especificar los puertos de depuración que se van a usar:
if ((strcmp(argv[1], "-port") == 0) && (argc > 2)) {
debuggerPort = (short)atoi(argv[2]);
argv+=2; argc -=2;
}
•
Para permitir o no el algoritmo de recolección de basura límite:
if (strcmp(argv[1], "-excessivegc") == 0) {
excessivegc = TRUE;
argv++; argc--;
}
•
Para modificar el tamaño de memoria que se va a emplear:
if ((strcmp(argv[1], "-heapsize") == 0) && (argc > 2)) {
char *endArg;
long heapSize = strtol(argv[2], &endArg, 10);
switch (*endArg) {
case '\0':
break;
case 'k': case 'K':
heapSize <<= 10; break;
case 'm': case 'M':
heapSize <<= 20; break;
default:
printHelpText(); exit(1);
}
}
Dicho tamaño se debe encontrar entre un rango que abarca un valor mínimo de
16 kilobytes hasta 64 megabytes que es el tamaño máximo que permite el
recolector de basura. Sin embargo hay que tener en cuenta que en la práctica el
recolector de basura funciona mejor con memorias más pequeñas:
if (heapSize < 16 * 1024) {
fprintf(stderr, KVM_MSG_USES_16K_MINIMUM_MEMORY "\n");
heapSize = 16 * 1024;
}
if (heapSize > 64 * 1024 * 1024) {
fprintf(stderr, KVM_MSG_USES_64M_MAXIMUM_MEMORY "\n");
heapSize = 64 * 1024 * 1024;
}
/* Para que sea divisible por 4 */
heapSize -= heapSize%CELL;
argv+=2; argc -=2;
RequestedHeapSize = heapSize;
•
Para especificar el classpath:
if ((strcmp(argv[1], "-classpath") == 0) && argc > 2) {
if (JamEnabled) {
fprintf(stderr,
KVM_MSG_CANT_COMBINE_CLASSPATH_OPTION_WITH_JAM_OPTION);
exit(1);
}
UserClassPath = argv[2];
argv+=2; argc -=2;
}
•
Para especificar el nombre de usuario que invoca la maquina virtual:
if (strcmp(argv[1], userName) == 0) {
varName = 1;
argv++; argc--;
}
•
Para especificar si se quiere una traza completa por salida estándar de la
ejecución de la maquina virtual:
if (strcmp(argv[1], "-traceall") == 0) {
#define TURN_ON_OPTION(varName, userName) varName = 1;
FOR_EACH_TRACE_FLAG(TURN_ON_OPTION)
argv++; argc--;
}
•
Para especificar el uso de Jam:
if (strcmp(argv[1], "-jam") == 0) {
JamEnabled = TRUE;
argv++; argc--;
} else if (JamEnabled && (strcmp(argv[1], "-repeat") == 0)) {
#if ENABLE_JAVA_DEBUGGER
if (debuggerActive) {
fprintf(stderr,
KVM_MSG_CANT_COMBINE_DEBUGGER_OPTION_WITH_REPEAT_OPTION);
exit(1);
}
#endif
JamRepeat = TRUE;
argv++; argc--;
}
Por supuesto se comprueba que el depurador no esta activo pues es incompatible
con el uso de Jam.
•
Para especificar el directorio de aplicaciones en caso de que el JAM este
activado:
if (JamEnabled && (strcmp(argv[1], "-appsdir") == 0) && argc >
2) {
jamInstalledAppsDir = argv[2];
argv+=2; argc-=2;
}
Seguidamente se leen de las variables del entorno el classpath que se ha de usar
en caso de que este no haya sido indicado mediante parámetro por línea de comandos:
if (UserClassPath == NULL && !JamEnabled) {
UserClassPath = getenv("classpath");
if (UserClassPath == NULL) {
UserClassPath = getenv("CLASSPATH");
if (UserClassPath == NULL) {
UserClassPath = ".";
}
}
}
Si el Jam ha sido habilitado se inicializa dicho módulo y se carga la URL que
necesita dicho módulo leyendo de la línea de comandos:
if (JamEnabled) {
if (argc != 1 ) {
fprintf(stderr,
KVM_MSG_EXPECTING_HTTP_OR_FILE_WITH_JAM_OPTION);
exit(1);
}
JamInitialize(jamInstalledAppsDir);
do {
result = JamRunURL(argv[0], JamRepeat);
if (result == JAM_RETURN_ERR) {
break;
}
} while(JamRepeat);
JamFinalize();
}
Finalmente se invoca la operación genérica de inicialización de la KVM:
result = StartJVM(argc, argv);
Si esta activada la opción de generación de estadísticas se imprime por la salida
estándar la información recogida:
#if ENABLEPROFILING
printProfileInfo();
#endif
Si no se ha especificado ningún archivo a ejecutar se ejecuta la función
printHelpText para mostrar por la salida estándar la información de invocación de la
maquina virtual:
if (result == -1) printHelpText();
8.5. Funciones auxiliares.
Hemos visto a lo largo del capítulo algunas funciones auxiliares en las cuales se
han apoyado las operaciones principales de inicialización de la maquina virtual.
Veámoslas en detalle empezando por la operación de parseo de línea de comandos:
static ARRAY readCommandLineArguments(int argc, char* argv[])
Mediante esta operación transformándolos parámetros pasados por línea de
comandos (argv) en una estructura de tipo ARRAY interna. Para ello primero se crea un
array de clases cuyos elementos sean de tipo JavaLangString y de tamaño 1 y se toma el
número de argumentos que hemos de copiar:
ARRAY_CLASS arrayClass = getArrayClass(1, JavaLangString, 0);
ARRAY stringArray;
int numberOfArguments = argc;
int argCount = (numberOfArguments > 0) ? numberOfArguments : 0;
int i;
Se inicializa el array de clases anteriormente creado como una matriz de cadenas
de caracteres:
IS_TEMPORARY_ROOT(stringArray,
argCount));
instantiateArray(arrayClass,
Se recorren cada uno de los argumentos añadiendo al array la cadena de
caracteres leída de argv previamente parseada a formato interno de la KVM mediante la
función instantiateString:
for (i = 0; i < numberOfArguments; i++) {
stringArray->data[i].cellp =
(cell *)instantiateString(argv[i], strlen(argv[i]));
}
Finalmente se devuelve el array de strings:
return stringArray;
Otra de las operaciones que hemos citado es la operación:
static INSTANCE_CLASS loadMainClass(char* className)
Esta operación se encarga como su nombre indica de cargar la clase que lleve la
función main dado que esta clase es cargada de forma diferente al resto (no se invoca al
loader). Primero se comprueba que no sea un array la clase pasada como parámetro:
if (*className == '[') return NULL;
Se sustituyen los . del la ruta de la clase por / para poder navegar a través de los
paquetes en los que integrado la clase.
replaceLetters(className, '.', '/');
Finalmente se obtiene la clase mediante la operación getClass que forma parte
del módulo de gestión de estructuras internas de la KVM y de loader:
return (INSTANCE_CLASS)getClass(className);
8.6. Conclusiones.
En este capítulo se han descrito en profundidad las tareas que se han de ejecutar
y completar cuando la maquina virtual es arrancada. Se ha podido comprobar como a la
hora de iniciar la maquina virtual se pueden emplear dos mecanismos si bien ambos
usan un método intrínseco de inicialización.
Dicho método se basa en la lectura de los parámetros que se emplean para la
ejecución de la KVM. Así por ejemplo se realiza una primera comprobación acerca de
si se ha especificado un archivo de clase a ejecutar. Seguidamente se van invocando de
forma individual cada una de las operaciones de inicialización del sistema por este
orden:
•
•
•
•
•
•
•
Gestión de memoria.
HashTables (estructuras de datos auxiliares).
Caché.
Loader.
Sistema de clases Java.
Verificador.
Módulo de gestión de eventos.
Posteriormente se inicializan las clases de sistema si es que no esta activa la
opción de que dispone la KVM para precargalas y se invoca al módulo Loader para que
cargue la clase principal por la que debe comenzar la ejecución del programa Java.
Solo cuando la clase principal esta debidamente cargada, se inicia el módulo de
gestión de hilos, procurando que la clase a ejecutar en el hilo principal sea la
correspondiente a la clase principal pasada como parámetro en el arranque de la
maquina virtual. Finalmente se ejecuta el intérprete de bytecodes que es el punto en el
cual se implementa la ejecución real del contenido del programa del archivo .class
Otro de los aspectos fundamentales en el proceso de arranque/parada de la
maquina virtual es la finalización de la misma. Es muy importante que cuando se
finalice la maquina virtual el sistema operativo quede exactamente en el mismo estado
en el que estaba antes de ejecutarse sobre todo en lo que se refiere a procesos de bajo
nivel arrancados y ocupación de memoria.
Descargar