4. CHAPTER FOUR : RESEARCH METHODS, PROCEDURES AND
4.5 DATA COLLECTION
4.5.1 Research instrument
Al compilar el código fuente de un programa se crea un archivo ejecutable que consta básicamente de cuatro partes (ver Figura 3-1):
Cabecera primaria. Contiene la siguiente información:
o El número mágico. Es un entero pequeño que permite al núcleo identificar el tipo de archivo ejecutable.
o El número de secciones que hay en el archivo.
o La dirección virtual de inicio, imprescindible para comenzar con la ejecución del proceso.
Cabeceras de las secciones. Describen cada una de las secciones del archivo. Entre otras informaciones contienen el tipo y el tamaño de la sección, además de la dirección virtual que se le debe asignar al comienzo de la región cuando el proceso se ejecute en el sistema.
Secciones. Contienen los “datos”, que son cargados inicialmente en el
espacio de direcciones del proceso, típicamente, el código (también denominado texto), los datos inicializados (variables estáticas y externas del programa conocidas en el momento de la compilación) y los datos no inicializados (también denominado bss7).
Otras informaciones. Tales como la tabla de símbolos y otros “datos”. La tabla
de símbolos es una tabla que se utiliza para almacenar los nombres definidos por el usuario en el programa fuente: variables, nombres de funciones, nombres de tipos, constantes, etc. Normalmente un compilador, debe comprobar, por ejemplo, que no se utiliza una variable sin haberla declarado
7 Bss es el acrónimo del término inglés block started by symbol cuya traducción al castellano es bloque inicializado con símbolos.
previamente, o que no se declara una variable dos veces. Para ello, el compilador tiene que almacenar el nombre de la variable (y posiblemente su tipo y algún otro dato) en la tabla de símbolos. Cuando se utiliza esta variable en una expresión, el compilador la busca en la tabla para comprobar que existe y además para obtener información acerca de ella: tipo, dirección de memoria, etc.
La información que se guarda en esta tabla depende del tipo de símbolo de que se trate. Lo habitual (excepto en compiladores muy sencillos) es implementar la tabla de símbolos utilizando una tabla de dispersión (hash) para optimizar el tiempo de búsqueda.
Figura 3-1: Estructura de un archivo ejecutable 3.3.2 Regiones de un proceso
El núcleo carga un fichero ejecutable en memoria principal durante la realización, por ejemplo, de una llamada al sistema exec. El proceso cargado tiene asignado por el compilador un espacio de direcciones de memoria virtual, también denominado espacio de direcciones de usuario. Este espacio se divide en varias regiones, cada una de las cuales
Número mágico Número de secciones Dirección virtual de inicio
Tipo de la sección Tamaño de la sección Dirección virtual Tipo de la sección Tamaño de la sección Dirección virtual Tipo de la sección Tamaño de la sección Dirección virtual “Datos” “Datos” “Datos” Otras informaciones Cabecera Primaria Cabecera Sección 1 Cabecera Sección 2 Cabecera Sección n Sección 1 Sección 2 Sección n Número mágico Número de secciones Dirección virtual de inicio
Tipo de la sección Tamaño de la sección Dirección virtual Tipo de la sección Tamaño de la sección Dirección virtual Tipo de la sección Tamaño de la sección Dirección virtual “Datos” “Datos” “Datos” Otras informaciones Cabecera Primaria Cabecera Sección 1 Cabecera Sección 2 Cabecera Sección n Sección 1 Sección 2 Sección n
delimita un área de direcciones contiguas de memoria virtual. El espacio de direcciones de memoria virtual de un proceso consiste al menos de tres regiones: la región de código (o texto), la región de datos y la región de pila. Adicionalmente, puede contener regiones de memoria compartida, que posibilitan la comunicación de un proceso con otros procesos.
Las regiones de código y de datos se corresponden con las secciones de código y datos del fichero ejecutable. La región de datos inicializados o zona estática de la región de datos es de tamaño fijo. Por el contrario el tamaño de la región de datos no inicializados
o zona dinámica de la región de datos puede variar durante la ejecución de un proceso.
La región de pila o pila de usuario se crea automáticamente y su tamaño es ajustado dinámicamente en tiempo de ejecución por el núcleo. La ejecución del código del programa irá marcando el crecimiento o decrecimiento de la pila, el núcleo asignará espacio para la pila conforme se vaya necesitando. La pila está constituida por marcos de pila lógicos. Un marco se añade a la pila cuando se llama a una función y se extrae cuando se vuelve de la misma. Existe un registro especial de la máquina denominado puntero de la pila donde se almacena la dirección, dependiendo de la arquitectura de la máquina, de la próxima entrada libre o a la última utilizada. Análogamente, la máquina indica la dirección de crecimiento de la pila, hacia las direcciones altas o bajas. Un marco de pila contiene usualmente la siguiente información: los parámetros de la función, sus variables locales y las direcciones almacenadas en el instante de la llamada a la función en diferentes registros especiales de la máquina, como por ejemplo, el contador del programa y el puntero de la pila. Salvar el contenido del contador del programa permite conocer la dirección de retorno donde debe continuar la ejecución una vez que se ha ejecutado la función. Mientras que salvar el contenido del registro de pila permite conocer la ubicación del marco de pila anterior o del siguiente libre.
Ejemplo 3.1 :
En la Figura 3-2 se representa a modo de ejemplo un diagrama del espacio de direcciones de memoria virtual de un proceso. Se observa que el proceso posee tres regiones: código, datos y pila. La dirección virtual de inicio de la región (DIRV0) de código es DIRV0=0 K. Por su parte la región de datos
comienza en DIRV0=64 K para su zona estática y DIRV0=128 K para su zona dinámica. Finalmente, la
Figura 3-2: Diagrama del espacio de direcciones de memoria virtual de un proceso
Además se observa la existencia de dos regiones de direcciones de memoria virtual libre (no asignada) la primera comienza en DIRV0=32 K y la segunda en DIRV0=224 K.
Además de las regiones descritas, existe otra parte del espacio de direcciones virtuales de un proceso denominada espacio del núcleo que se utiliza para ubicar estructuras de datos relativas a dicho proceso que son utilizadas por el núcleo. Existen básicamente dos estructuras locales a un proceso que deben ser manipuladas por el núcleo y que se suelen implementar en el espacio de direcciones del proceso: el área de usuario o área U y la pila del núcleo. Conceptualmente ambas estructuras aunque locales a un proceso son propiedad del núcleo. Obviamente, estas estructuras sólo pueden ser accedidas en modo núcleo o supervisor.
3.3.3 Operaciones con regiones implementadas por el núcleo
El núcleo dispone de una estructura local asociada a cada proceso denominada tabla de regiones por proceso, que mantiene información relevante sobre las regiones de código, datos, pila de usuario y memoria compartida (si existe) de un cierto proceso. Cada entrada de esta tabla contiene un puntero que apunta a una entrada de la tabla de regiones. Ésta es una estructura global del núcleo que contiene información sobre cada región. Las entradas de la tabla de regiones se organizan en dos listas: una lista enlazada de regiones libres y una lista enlazada de regiones activas. Simultáneamente una entrada sólo puede pertenecer a una de las dos listas.
Existen varias llamadas al sistema (fork, exec, ...) que tienen que manipular durante su ejecución el espacio de direcciones virtuales de un proceso. El núcleo dispone de algoritmos bien definidos para la realización de diferentes operaciones con las regiones. Las principales operaciones con regiones implementadas por el núcleo son:
Bloquear y desbloquear una región. El núcleo debe bloquear una región para prevenir los posibles accesos de otros procesos mientras se encuentra trabajando con ella. Cuando termina de usarla la desbloquea. Estas operaciones son independientes de las operaciones de asignar y liberar una región.
Asignar una región. Consiste en eliminar la primera entrada disponible de la lista enlazada de regiones libres y situarla en la lista enlazada de regiones activas. El núcleo implementa esta operación con el algoritmo allocreg()8. Las llamadas al sistema que usan esta operación son: fork, exec y shmget.
Ligar una región al espacio de direcciones virtuales de un proceso. Consiste en asociar a una región (que previamente ha tenido que ser asignada) una entrada de la tabla de regiones por proceso. El núcleo implementa esta operación con el algoritmo attachreg(). Las llamadas al sistema que usan esta operación son: fork, exec y shmat.
Cambiar el tamaño de una región. Las únicas regiones cuyo tamaño pueden ser modificados son las regiones de datos y de pila. Las regiones de código y las regiones de memoria compartida no pueden crecer después de ser inicializadas. El núcleo implementa esta operación con el algoritmo
growreg(). Existen dos llamadas al sistema brk y sbrk que usan esta operación, ambas trabajan con la región de datos. Además el núcleo también utiliza esta operación para implementar el crecimiento de la pila de usuario.
Cargar una región con el contenido de un fichero. El núcleo implementa esta operación con el algoritmo loadreg() que es usada por la llamada al sistema exec.
8 Los nombres de los algoritmos del núcleo que aparecen en este capítulo son los de la distribución SVR3.
Desligar una región del espacio de direcciones de un proceso. El núcleo implementa esta operación con el algoritmo detachreg(). Las llamadas al sistema que usan esta operación son: exec, exit y shmdt.
Liberar una región. Cuando una región ya no está unida a ningún proceso, el núcleo puede liberar la región y devolverla a la lista enlazada de regiones libres. El núcleo implementa esta operación con el algoritmo freereg().
Duplicar una región. El núcleo implementa esta operación con el algoritmo dupreg(), que es usado por la llamada al sistema fork.