Anexo
Texto íntegro de la conversión. Las figuras y la disposición de las notas se pueden consultar en el PDF.
Página 29
Anexo
A) Las fases de ejecución de un programa
AA) El lenguaje informático
Los programadores de aplicaciones escriben sus programas en ficheros de texto que siguen las normas de un lenguaje informático determinado. Estas normas determinan la gramática del lenguaje de programación. Eso es debido al hecho de que se intenta utilizar un lenguaje tan cercano como sea posible al lenguaje natural con el fin de describir la solución del problema. Para las personas de habla inglesa los lenguajes de programación son bastante naturales, dado que la mayoría están escritos en su lengua. El fichero generado es de texto y, por lo tanto, no se puede ejecutar de manera inmediata.
En el momento de resolver un problema determinado, los programadores de aplicaciones informáticas tienen que escribir las instrucciones o las sentencias de un programa siguiendo unas normas. El formato que utilizan se llama lenguaje informático.
La figura 8 presenta un ejemplo de uso de lenguaje informático. El programa que se representa efectúa la multiplicación de dos variables a y b y deja el resultado a c. Supongamos que las variables a y b están inicializadas en los valores correspondientes al cálculo deseado.
"c = 0;
for (i = 0; i < b; i++) c = c + a;"
Figura 8. Programa de ejemplo que calcula el producto de los enteros a y b (notad que el programa presentado es únicamente un ejemplo y no contiene todo el código necesario).
Un lenguaje de programación o informático es una convención de sentencias y estructuras de datos que, mediante un proceso de traducción o interpretación, se puede convertir en lenguaje máquina y se puede ejecutar. Con los lenguajes de programación se pueden escribir algoritmos.
La idea de los lenguajes de programación es que los programas sean independientes de la arquitectura física del ordenador y del sistema operativo en el que se ejecuten finalmente, es decir, que sean programas portables a otros sistemas y que sean útiles sin tener que introducir cambios. Organismos como el American National Standards Institute (ANSI) y la International Standards Organization (ISO) se ocupan de la normalización de los diferentes lenguajes.
Página 30
Así pues, el sistema operativo tiene que proporcionar las herramientas necesarias para llevar a cabo todo el proceso de creación de una aplicación informática que se pueda ejecutar en el sistema del mismo sistema operativo.
Clasificamos los diferentes tipos de lenguajes de programación de la manera siguiente:
-
Lenguaje de alto nivel. Cuando las instrucciones se escriben en un formato de texto muy cercano al lenguaje natural, aunque con unas reglas mucho más estrictas, decimos que estamos utilizando un lenguaje de alto nivel. El ejemplo anterior está escrito en un lenguaje de alto nivel. La máquina únicamente puede ejecutar el lenguaje máquina propio y, por lo tanto, tiene que haber un proceso que transforme el programa anterior a un formato conocido por la máquina. Este proceso se llama, de manera genérica, compilación.
-
Lenguaje ensamblador. Para simplificar el uso directo del lenguaje máquina se han diseñado lenguajes en los que cada instrucción máquina corresponde a una instrucción en un lenguaje mnemotécnico más inteligible. Uno de estos lenguajes es el lenguaje ensamblador, que permite escribir un código muy eficiente por el hecho de que se pueden escribir las instrucciones nativas del procesador controlando toda la ejecución a un nivel muy bajo. Sin embargo, hay que decir que el lenguaje máquina es cada día más sofisticado y que es prácticamente inviable programarlo a mano, de manera que esta tarea tan complicada quedará reservada a los compiladores y a los optimizadores de código.
-
Código máquina. Finalmente, las únicas instrucciones que reconoce un procesador son las que están codificadas en su código particular. Este código es binario, en el sentido de que se interpretan directamente las secuencias de ceros y unos. Se puede ejecutar de manera directa y recibe el nombre de código máquina. Normalmente, una sentencia de lenguaje de alto nivel genera diversas instrucciones de código máquina.
En la figura 9, vemos cómo sería el aspecto del programa del ejemplo anterior escrito en lenguaje de alto nivel, en lenguaje ensamblador y en código máquina. A la hora de codificar las instrucciones CALL y RET se utiliza el mismo código que para la instrucción BEQ y se utiliza el campo de origen para distinguir de qué instrucción se trata: 0 para BEQ, 1 para CALL y 2 para RET.
Página 31
Figura 9. Tres versiones de una rutina de ejemplo en lenguaje de alto nivel, en lenguaje ensamblador y en lenguaje máquina (este ejemplo se basa en la idea de una máquina sencilla con un juego de instrucciones muy reducido, por ejemplo, no dispone de la instrucción producto).
Hemos representado el código máquina en notación decimal para cada campo. El código de operación sólo ocupa 2 bits y los códigos de la variable de origen y de destino ocupan 7 bits cada uno. Siempre dan un mensaje directo a la posición de memoria del operando, por lo que los valores constantes cero y uno se tienen que registrar en una posición de memoria.
La máquina no dispone de una operación de salto incondicional. Con el fin de simularla (ir a la etiqueta for:), hacemos que el resultado de la comparación previa sea siempre cierto, cosa que conseguimos comparando cualquier posición de memoria con ella misma (en este ejemplo, la variable i). AB) Las herramientas para la creación de una aplicación informática
Dentro de los programas de utilidad del sistema operativo, hay un amplio abanico de catalogados como programas de apoyo a los lenguajes de programación, entre los cuales podemos encontrar las herramientas siguientes:
-
Editores de texto: forman parte de las utilidades del sistema que permiten la edición de ficheros que contienen información en formato de texto. Estos ficheros pueden ser de mensajes, de configuración del sistema o de pequeños programas que utilizan el intérprete de comandos del sistema operativo (scripts o shellscripts), pero también pueden ser ficheros de código de alto nivel de una aplicación informática. Recordemos que los programas escritos en alto nivel no son sino ficheros de texto escritos con las normas de un lenguaje de alto nivel.
-
Compiladores-ensambladores: son programas que transforman los programas editados en formato de texto a un formato que sea ejecutable por la máquina. Los ficheros resultantes se llaman ficheros ejecutables y su
Notas y pies de figura de la fuente
Ved también Podéis ver el valor de las posiciones de memoria de cada variable en la tabla de símbolos que hemos definido en este anexo.
Página 32
contenido está representado en el formato binario, que el hardware puede ejecutar de manera directa. Tanto los programas fuente de alto nivel como los editados en ensamblador están escritos en formato de texto. No es habitual establecer la transformación de programas de alto nivel a ejecutables y que la transformación se pueda hacer en un solo paso. Más adelante detallaremos este proceso.
-
Bibliotecas: para ayudar a crear programas, el sistema operativo permite la gestión de bibliotecas. Éstas permiten extender las funciones que se pueden utilizar de manera directa y, así, ahorran al programador el trabajo pesado de programar muchos procedimientos de entrada/salida o de gestión de ficheros, que se repiten para cualquier tipo de aplicación. Utilizando las bibliotecas aprovechamos un conjunto de rutinas que ya está muy depurado y experimentado.
-
Montadores (enlazadores o linkers): son aplicaciones que agrupan diferentes módulos objeto y las bibliotecas del sistema para obtener un programa ejecutable único. Su tarea principal es resolver las referencias cruzadas entre los diferentes elementos que no han sido resueltas en las primeras fases de compilación. Aunque a veces se encuentran como aplicaciones separadas de los sistemas operativos, normalmente se ejecutan de manera automática en la fase final de los entornos integrados de compilación.
-
Depuradores: están formados por una serie de aplicaciones que permiten la ejecución controlada de un programa con el fin de resolver posibles errores. Para hacer viable la utilización de un depurador es necesario realizar las fases previas en esta modalidad. El compilador y el montador tienen que preparar el código resultante para este fin.
-
Ejecutadores-cargadores: son el último paso del proceso y permiten la ejecución de los ficheros ejecutables resultantes de los pasos anteriores después de asignarles recursos del sistema. En los sistemas operativos antiguos esta orden era explícita, mientras que actualmente, al indicar el nombre de un fichero ejecutable, se interpreta que se tiene que iniciar su ejecución.
AC) El proceso de creación de un programa ejecutable
Utilizando las herramientas que hemos descrito podemos crear y ejecutar una aplicación. Para poder lograrlo, tenemos que pasar por un conjunto de fases que desarrollamos acto seguido.
ACA) La edición
Página 33
En esta fase, el programador tiene que transcribir todas las ideas para solucionar los problemas en un fichero de texto llamado fichero fuente. Las instrucciones del programa se tienen que escribir siguiendo las normas estrictas del lenguaje de programación que se utilice.
La herramienta que se utiliza es cualquier procesador de texto que, de hecho, puede ser muy sencillo, ya que no se necesitan grandes prestaciones en cuanto al formato del texto. En cambio, es conveniente que pueda gestionar más de un fichero simultáneamente o que disponga de buenas herramientas de búsqueda y de manipulación del texto. Hay editores específicos para algunos lenguajes de programación que pueden resolver ciertos problemas sintácticos del código o que pueden presentar las partes de un programa en diferentes colores.
ACB) La compilación (ensamblaje)
Con el proceso de traducción (compilación) de un programa escrito en lenguaje de alto nivel, llamado programa fuente, obtenemos uno equivalente en lenguaje máquina que llamamos programa objeto. Si el programa fuente es el ensamblador, esta fase también se llama ensamblar y, en este caso, la traducción es más sencilla, dado que, como hemos dicho, a cada instrucción en ensamblador le corresponde una instrucción máquina. A partir de esta fase, no hay ninguna diferencia entre los objetos que provienen de un lenguaje de alto nivel y los que son de formato ensamblador.
La estructura de un programa objeto ya contiene los códigos correspondientes a las instrucciones máquina, pero el programa objeto no puede resolver todas las direcciones que incluye a causa de las dos razones siguientes:
-
En primer lugar, porque los programas de una aplicación habitualmente están escritos en diferentes ficheros o módulos. Así, si tenemos que hacer referencia a una variable o a un procedimiento que está escrito en otro módulo, no la podemos resolver en este paso.
-
En segundo lugar, porque las rutinas de uso muy común, como los cálculos matemáticos más o menos complejos, el control de determinadas operaciones de entrada/salida con ficheros o dispositivos y otros procedimientos muy genéricos ya están generalmente programadas, compiladas e incluidas en un conjunto de ficheros llamados bibliotecas.
Las bibliotecas son un conjunto de módulos objeto organizados adecuadamente. El mismo fabricante del software de apoyo a los lenguajes de programación suele facilitar este fichero. Gracias a las bibliotecas, el programador se ahorra mucho trabajo y se reducen considerablemente el tamaño del pro-
Notas y pies de figura de la fuente
Ejemplo Se pueden utilizar los recursos del editor de texto para diferenciar las palabras reservadas, los nombres de las variables o los nombres de los procedimientos, entre otros. Nota Este programa equivalente tendrá la misma información, pero en un formato diferente.
Página 34
grama y el tiempo de programación. Normalmente, el fabricante no facilita el programa fuente de las bibliotecas, pero sí proporciona las instrucciones y los parámetros necesarios para utilizarlas.
Tanto los módulos objeto como las bibliotecas indican las direcciones de manera relativa al inicio del módulo. Después de esta primera fase de compilación, disponemos de un conjunto de módulos objeto con referencias de menaje relativas al módulo y también otras referencias externas sin resolver. En esta fase se crean tres estructuras:
- La tabla de símbolos, que indica la ubicación de cada símbolo definido en un módulo objeto (variable, procedimiento o constante).
- La tabla de referencias externas, que indica qué instrucciones hacen referencia a símbolos no definidos dentro de un módulo objeto.
- La tabla de reubicación, que indica qué referencias a símbolos se tendrán que modificar en el proceso de montaje. Sin embargo, no es objeto de esta asignatura dar los detalles de cómo se tratan estas tablas ni de la manera como se utilizan.
Ejemplo de compilación
Consideramos que hay dos módulos, producte.c y principal.c. En principal.c (figura 10), encontramos un bucle que imprime los cuadrados de los n primeros números. En el ejemplo hemos tomado el valor n = 10. Este programa hace una serie de llamadas a un segundo módulo en el que hemos escrito el código para el producto. Recordad que sólo es un ejemplo didáctico en el que no lo hemos resuelto todo. Primeramente podemos ver, de manera muy simplificada, cómo es la compilación de producte.c. Presentamos el código ensamblador sólo para intuir el significado del código máquina. Las constantes 0 y 1 se han cargado en posiciones de memoria, ya que la máquina dispone únicamente de direccionamiento directo.
Es importante ver que ahora hay una serie de referencias externas no resueltas, como las direcciones de las variables a y b y las de los procedimientos producte y printf.
n = 10; for (i = 0; i ‹ n; i++){ a = i; b = i; producte(); printf(_i = %d quadrat =... }
Página 35
Figura 10. Código en alto nivel, ensamblador y lenguaje máquina del programa principal.c
ACC) El montaje (enlazado o linkaje)
El montaje es el proceso encargado de coger todos los módulos correspondientes a un programa, y también las bibliotecas si es necesario, y agruparlos para construir el programa ejecutable. Los programas que llevan a cabo estas funciones se llaman montadores.
De hecho, el trabajo principal del enlazador es resolver las direcciones externas entre los módulos y también con la biblioteca. El resultado final del enlazador es el programa ejecutable definitivo con un espacio lógico de direcciones completamente construido.
La cabecera de un fichero ejecutable contiene información relativa a la manera como se tiene que cargar en la memoria, como el tamaño de la pila y de los datos no inicializados o dinámicas, el número de segmentos (si los hay) o la especificación de si el código puede ser compartido con otros procesos, entre otros. También indica si se genera código para su depuración y, en ese caso, adjunta información con el nombre de las variables, de los procedimientos y de
Página 36
las direcciones que sean necesarias. Es habitual que la cabecera contenga una marca, que es la firma del fichero. Con este identificador, el sistema operativo sabe cuándo un fichero es realmente ejecutable, siempre que la versión del sistema operativo sea correcta o el código máquina generado sea el apropiado.
El enlazado de los módulos del ejemplo propuesto es el que podéis ver en el gráfico de la figura 11.
Página 37
Figura 11. Enlazado de los módulos de ejemplo
Página 38
Podemos ver que a la hora de generar el fichero ejecutable surgen diferentes problemas:
- Las direcciones de las tres partes que se tienen que enlazar empiezan por 0.
- Hay dos símbolos locales con el mismo nombre: i.
- Existen variables y procedimientos todavía sin montar.
- Las direcciones de los saltos incondicionales (BEQ) pueden cambiar.
Para solucionar estos problemas, el fichero ejecutable podría ser parecido a lo que presentamos en la figura 12.
Como podemos ver, se han resuelto las referencias externas. El compilador no puede simplificar la doble definición de la constante del cero y la mantiene por separado. El orden de los módulos es irrelevante, de manera que podríamos crear un ejecutable equivalente alterando el orden de los módulos. También hay que subrayar que el código de la biblioteca se ha gestionado como un módulo más y que se ha añadido el código de la rutina de biblioteca printf. Por otra parte, si alteramos un solo módulo objeto, claro está que el resto de objetos son perfectamente válidos para su montaje.
Página 39
Figura 12. Fichero ejecutable correspondiente al programa de ejemplo
Página 40
ACD) La carga
La tarea principal del cargador es buscar los recursos que pide el programa ejecutable. Entre estos recursos está la memoria. El cargador tiene que buscar memoria libre, copiar el código y también asignar espacio para datos y pila.
Todo este proceso se hace adaptando las direcciones lógicas de los programas ejecutables a las direcciones físicas de la memoria real del sistema. Finalmente, el cargador transfiere el control a la primera instrucción del programa y marca el proceso como preparado.
Cuando muchos usuarios llaman al mismo programa, si éste permite que se utilice en modalidad compartida, el cargador mirará si hay alguna copia en la memoria del programa. Si la respuesta es afirmativa, hará que el nuevo proceso tenga todo su entorno propio excepto la parte de memoria donde reside el código, que será compartida con otros procesos. Recordemos el ejemplo de los programadores que utilizan al mismo tiempo el editor.
ACE) La ejecución (depuración)
Mientras se lleva a cabo el proceso de desarrollo de un programa o de una aplicación informática, suele ser muy interesante seguir la ejecución paso a paso, cosa que es factible gracias a los programas depuradores (debuggers).
Los programas depuradores tienen como única misión controlar la ejecución de un programa y permiten que el programador pueda ver una ejecución paso a paso (instrucción por instrucción) o por intervalos de programa visualizando constantemente el entorno que se va produciendo. El programador fija los puntos donde se tiene que detener la ejecución y en cada parada puede comprobar si el entorno es el correcto e, incluso, puede modificarlo.
Así, se pueden ver y corregir errores que difícilmente se encontrarían de otra manera y se obtiene un programa final más eficiente (figura 13).
El hecho de partir el programa fuente en diferentes ficheros presenta varias ventajas tanto para el programador como para el sistema:
- Por una parte, porque una aplicación informática puede tener decenas de miles de líneas de código y editar un fichero de estas dimensiones puede resultar poco práctico.
Página 41
- El proceso de compilación también sale beneficiado porque en la primera fase sólo tiene que traducir los módulos de código fuente que se hayan modificado. En la fase final o de enlace, sí tiene que actuar en todos los módulos objeto, tanto los que se han cambiado como los antiguos.
Figura 13. Etapas de la creación de un programa ejecutable
ACF) Interpretación frente a compilación
Los intérpretes intentan unificar todo el proceso de creación de un programa (figura 15). A medida que se va leyendo cada instrucción del lenguaje de alto nivel, se transforma en el conjunto correspondiente de instrucciones máquina, que se ejecuta inmediatamente. El interpretador controla directamente todas las fases de la creación y la ejecución del programa. Es un paso necesario para la ejecución, ya que no se genera ningún otro fichero y siempre se trabaja con el fichero fuente.
La interpretación de programas fue el primer proceso que apareció, dado que los ordenadores eran poco potentes y el proceso de compilación demasiado costoso. Era demasiado lento para aplicarlo continuamente durante el proceso de creación y depuración de la aplicación. Con el aumento progresivo de la potencia de los sistemas informáticos, la fase de compilación es cada día más rápida y permite trabajar de manera continua en esta modalidad. En los últimos tiempos, con la evolución de las redes de comunicaciones y la creación
Página 42
de aplicaciones pensadas para este entorno, se está volviendo a la ejecución interpretada. Los procedimientos se envían en código de alto nivel hasta las máquinas remotas que los ejecutan mediante el uso de intérpretes. Esta modalidad tiene dos grandes ventajas: el código de alto nivel es más compacto que el código máquina y, además, es independiente de la plataforma en la que se ejecuta.
Figura 14. Proceso de interpretación
B) Los espacios de direcciones de un proceso
Ahora analizaremos el mecanismo de carga de un programa en la memoria y la manera como esta memoria se diferencia de la memoria asignada a otros procesos o de la asignada al sistema operativo mismo.
BA) El espacio lógico y el espacio físico
Partimos del espacio definido en el fichero ejecutable, que puede tener una estructura de una o dos dimensiones (espacio lineal o segmentado). Estos espacios se tienen que transportar desde los ficheros ejecutables hasta la memoria que gestiona el sistema operativo. La estructura que tiene esta memoria puede ser muy diferente en función del modelo escogido por el sistema operativo.
Un programa reubicable se puede ejecutar en direcciones diferentes, que son asignadas por el cargador. Por este motivo, se distingue entre direcciones lógicas y direcciones físicas:
- Direcciones lógicas: son las referenciadas por el programador. Suelen ser identificadores que reconocen las diferentes partes de un programa dentro de su espacio de direcciones. Son referencias locales en el sentido de que el entorno en el que se tengan que ejecutar no tiene importancia.
Página 43
- Direcciones físicas: son las direcciones asignadas en tiempo de carga, es decir, el lugar donde residirá realmente el proceso durante su ejecución.
BB) La carga en la memoria: reubicación
La propiedad de la reubicación hace referencia a la capacidad de cargar y después ejecutar un programa en cualquier posición de memoria. Es la propiedad contraria a la obligatoriedad de conocer todas las direcciones de manera fija en tiempo de compilación. Obviamente, existe un mecanismo de traducción entre las direcciones lógicas y las físicas. En función de cómo se haga, podemos distinguir dos tipos de reubicación:
-
Reubicación estática: se da antes o durante la carga del programa en la memoria para ser ejecutado. Durante el proceso de creación de un programa ejecutable, normalmente se toma la dirección lógica 0 como dirección inicial del programa. De esta manera, las demás direcciones del programa serán direcciones lógicas relativas a la dirección final de carga del programa. Cuando se carga la imagen del programa, todas las direcciones marcadas como reubicables se transforman en la dirección física final. La referencia para poder hacerlo es la dirección lógica inicial. Una vez se han modificado todas estas direcciones para reubicarlas, no se pueden distinguir las direcciones reubicadas de las originales, en otras palabras, no se puede volver a reubicar durante la ejecución. Si por alguna razón el proceso tiene que salir de su espacio físico asignado, o bien vuelve exactamente al mismo lugar o bien se tiene que iniciar el proceso de reubicación desde el principio, es decir, se tiene que volver a cargar. A causa del problema anterior, la reubicación estática está prácticamente limitada a ciertos sistemas operativos en los que el mecanismo de carga de programas en la memoria está muy determinado y el código tiene que ir a particiones de memoria predeterminadas.
-
Reubicación dinámica: en este caso, la transformación entre las direcciones físicas y lógicas se efectúa en tiempo de ejecución. Como antes, los programas ejecutables resultantes de las compilaciones adoptan la dirección inicial de referencia 0. Estas imágenes de los programas ejecutables se cargan directamente en cualquier posición de memoria y es una parte del hardware el que se encarga de hacer el trabajo de reubicación de direcciones lógicas a físicas. Cuando el proceso se está ejecutando, antes de acceder realmente a la memoria física se reubican todos los accesos a la memoria. Este proceso se lleva a cabo utilizando los registros de base. Cada referencia que hace un proceso durante su ejecución es transformada por la suma de la dirección lógica más el registro de base para obtener la dirección física. Este mecanismo es totalmente transparente para el programador. La transformación de direcciones depende en gran medida del hardware del sistema. Por este motivo no podemos dar más detalles sin hablar de una
Página 44
arquitectura de ordenador determinada; pero, de hecho, eso no es objeto de esta asignatura.
C) Mapa conceptual
El mapa conceptual de la figura 15 refleja las relaciones que existen entre los diferentes conceptos que hemos presentado en este módulo.
Figura 15. Mapa conceptual
Ver fragmento extraído sin normalizar
## Página 29
<!-- source-page: 29 -->
GNUFDL • PID_00214804 29 El sistema operativo: una máquina virtual
Anexo
A)Lasfasesdeejecucióndeunprograma
AA)Ellenguajeinformático
Los programadores de aplicaciones escriben sus programas en ficheros de texto
que siguen las normas de un lenguaje informático determinado. Estas normas
determinan la gramática del lenguaje de programación. Eso es debido al hecho
de que se intenta utilizar un lenguaje tan cercano como sea posible al lenguaje
natural con el fin de describir la solución del problema. Para las personas de
habla inglesa los lenguajes de programación son bastante naturales, dado que
la mayoría están escritos en su lengua. El fichero generado es de texto y, por
lo tanto, no se puede ejecutar de manera inmediata.
En el momento de resolver un problema determinado, los programadores de
aplicaciones informáticas tienen que escribir las instrucciones o las sentencias
de un programa siguiendo unas normas. El formato que utilizan se llama len-
guajeinformático.
La figura 8 presenta un ejemplo de uso de lenguaje informático. El programa
que se representa efectúa la multiplicación de dos variables a y b y deja el
resultado a c. Supongamos que las variables a y b están inicializadas en los
valores correspondientes al cálculo deseado.
"c = 0;
for (i = 0; i < b; i++)
c = c + a;"
Figura 8. Programa de ejemplo que calcula el producto de los enteros a y b (notad que el
programa presentado es únicamente un ejemplo y no contiene todo el código necesario).
Un lenguajedeprogramaciónoinformático es una convención de senten-
cias y estructuras de datos que, mediante un proceso de traducción o interpre-
tación, se puede convertir en lenguaje máquina y se puede ejecutar. Con los
lenguajes de programación se pueden escribir algoritmos.
La idea de los lenguajes de programación es que los programas sean indepen-
dientes de la arquitectura física del ordenador y del sistema operativo en el
que se ejecuten finalmente, es decir, que sean programas portablesaotros
sistemas y que sean útiles sin tener que introducir cambios. Organismos como
el American National Standards Institute (ANSI) y la International Standards
Organization (ISO) se ocupan de la normalización de los diferentes lenguajes.
## Página 30
<!-- source-page: 30 -->
GNUFDL • PID_00214804 30 El sistema operativo: una máquina virtual
Así pues, el sistema operativo tiene que proporcionar las herramientas nece-
sarias para llevar a cabo todo el proceso de creación de una aplicación infor-
mática que se pueda ejecutar en el sistema del mismo sistema operativo.
Clasificamos los diferentes tipos de lenguajes de programación de la manera
siguiente:
• Lenguajedealtonivel. Cuando las instrucciones se escriben en un for-
mato de texto muy cercano al lenguaje natural, aunque con unas reglas
mucho más estrictas, decimos que estamos utilizando un lenguaje de alto
nivel.
El ejemplo anterior está escrito en un lenguaje de alto nivel. La máquina
únicamente puede ejecutar el lenguaje máquina propio y, por lo tanto,
tiene que haber un proceso que transforme el programa anterior a un for-
mato conocido por la máquina. Este proceso se llama, de manera genérica,
compilación.
• Lenguajeensamblador. Para simplificar el uso directo del lenguaje má-
quina se han diseñado lenguajes en los que cada instrucción máquina co-
rresponde a una instrucción en un lenguaje mnemotécnico más inteligi-
ble. Uno de estos lenguajes es el lenguaje ensamblador, que permite escri-
bir un código muy eficiente por el hecho de que se pueden escribir las
instrucciones nativas del procesador controlando toda la ejecución a un
nivel muy bajo. Sin embargo, hay que decir que el lenguaje máquina es
cada día más sofisticado y que es prácticamente inviable programarlo a
mano, de manera que esta tarea tan complicada quedará reservada a los
compiladores y a los optimizadores de código.
• Códigomáquina. Finalmente, las únicas instrucciones que reconoce un
procesador son las que están codificadas en su código particular. Este có-
digo es binario, en el sentido de que se interpretan directamente las se-
cuencias de ceros y unos. Se puede ejecutar de manera directa y recibe el
nombre de código máquina. Normalmente, una sentencia de lenguaje de
alto nivel genera diversas instrucciones de código máquina.
En la figura 9, vemos cómo sería el aspecto del programa del ejemplo anterior
escrito en lenguaje de alto nivel, en lenguaje ensamblador y en código máqui-
na. A la hora de codificar las instrucciones CALL y RET se utiliza el mismo có-
digo que para la instrucción BEQ y se utiliza el campo de origen para distinguir
de qué instrucción se trata: 0 para BEQ, 1 para CALL y 2 para RET.
## Página 31
<!-- source-page: 31 -->
GNUFDL • PID_00214804 31 El sistema operativo: una máquina virtual
Figura 9. Tres versiones de una rutina de ejemplo en lenguaje de alto nivel, en lenguaje ensamblador y en lenguaje
máquina (este ejemplo se basa en la idea de una máquina sencilla con un juego de instrucciones muy reducido, por
ejemplo, no dispone de la instrucción producto).
Hemos representado el código máquina en notación decimal para cada campo.
El código de operación sólo ocupa 2 bits y los códigos de la variable de origen
y de destino ocupan 7 bits cada uno. Siempre dan un mensaje directo a la
posición de memoria del operando, por lo que los valores constantes cero y
uno se tienen que registrar en una posición de memoria.
La máquina no dispone de una operación de salto incondicional. Con el fin Ved también
de simularla (ir a la etiqueta for:), hacemos que el resultado de la comparación
previa sea siempre cierto, cosa que conseguimos comparando cualquier posi- Podéis ver el valor de las po-
siciones de memoria de cada
ción de memoria con ella misma (en este ejemplo, la variable i). variable en la tabla de símbo-
los que hemos definido en este
anexo.
AB)Lasherramientasparalacreacióndeunaaplicacióninformática
Dentro de los programas de utilidad del sistema operativo, hay un amplio aba-
nico de catalogados como programas de apoyo a los lenguajes de programa-
ción, entre los cuales podemos encontrar las herramientas siguientes:
• Editoresdetexto: forman parte de las utilidades del sistema que permi-
ten la edición de ficheros que contienen información en formato de tex-
to. Estos ficheros pueden ser de mensajes, de configuración del sistema o
de pequeños programas que utilizan el intérprete de comandos del siste-
ma operativo (scripts o shellscripts), pero también pueden ser ficheros de
código de alto nivel de una aplicación informática. Recordemos que los
programas escritos en alto nivel no son sino ficheros de texto escritos con
las normas de un lenguaje de alto nivel.
• Compiladores-ensambladores: son programas que transforman los pro-
gramas editados en formato de texto a un formato que sea ejecutable por
la máquina. Los ficheros resultantes se llaman ficheros ejecutables y su
## Página 32
<!-- source-page: 32 -->
GNUFDL • PID_00214804 32 El sistema operativo: una máquina virtual
contenido está representado en el formato binario, que el hardware puede
ejecutar de manera directa. Tanto los programas fuente de alto nivel como
los editados en ensamblador están escritos en formato de texto. No es ha-
bitual establecer la transformación de programas de alto nivel a ejecuta-
bles y que la transformación se pueda hacer en un solo paso. Más adelante
detallaremos este proceso.
• Bibliotecas: para ayudar a crear programas, el sistema operativo permite la
gestión de bibliotecas. Éstas permiten extender las funciones que se pue-
den utilizar de manera directa y, así, ahorran al programador el trabajo pe-
sado de programar muchos procedimientos de entrada/salida o de gestión
de ficheros, que se repiten para cualquier tipo de aplicación. Utilizando
las bibliotecas aprovechamos un conjunto de rutinas que ya está muy de-
purado y experimentado.
• Montadores(enlazadoresolinkers): son aplicaciones que agrupan dife-
rentes módulos objeto y las bibliotecas del sistema para obtener un progra-
ma ejecutable único. Su tarea principal es resolver las referencias cruzadas
entre los diferentes elementos que no han sido resueltas en las primeras
fases de compilación. Aunque a veces se encuentran como aplicaciones
separadas de los sistemas operativos, normalmente se ejecutan de manera
automática en la fase final de los entornos integrados de compilación.
• Depuradores: están formados por una serie de aplicaciones que permiten
la ejecución controlada de un programa con el fin de resolver posibles
errores. Para hacer viable la utilización de un depurador es necesario reali-
zar las fases previas en esta modalidad. El compilador y el montador tienen
que preparar el código resultante para este fin.
• Ejecutadores-cargadores: son el último paso del proceso y permiten la
ejecución de los ficheros ejecutables resultantes de los pasos anteriores des-
pués de asignarles recursos del sistema. En los sistemas operativos antiguos
esta orden era explícita, mientras que actualmente, al indicar el nombre de
un fichero ejecutable, se interpreta que se tiene que iniciar su ejecución.
AC)Elprocesodecreacióndeunprogramaejecutable
Utilizando las herramientas que hemos descrito podemos crear y ejecutar una
aplicación. Para poder lograrlo, tenemos que pasar por un conjunto de fases
que desarrollamos acto seguido.
ACA)Laedición
## Página 33
<!-- source-page: 33 -->
GNUFDL • PID_00214804 33 El sistema operativo: una máquina virtual
En esta fase, el programador tiene que transcribir todas las ideas para
solucionar los problemas en un fichero de texto llamado ficherofuen-
te. Las instrucciones del programa se tienen que escribir siguiendo las
normas estrictas del lenguaje de programación que se utilice.
La herramienta que se utiliza es cualquier procesador de texto que, de hecho, Ejemplo
puede ser muy sencillo, ya que no se necesitan grandes prestaciones en cuanto
al formato del texto. En cambio, es conveniente que pueda gestionar más de un Se pueden utilizar los recursos
del editor de texto para dife-
fichero simultáneamente o que disponga de buenas herramientas de búsqueda renciar las palabras reservadas,
los nombres de las variables
y de manipulación del texto. Hay editores específicos para algunos lenguajes o los nombres de los procedi-
mientos, entre otros.
de programación que pueden resolver ciertos problemas sintácticos del código
o que pueden presentar las partes de un programa en diferentes colores.
ACB)Lacompilación(ensamblaje)
Nota
Con el proceso de traducción (compilación) de un programa escrito en
lenguaje de alto nivel, llamado programa fuente, obtenemos uno equi- Este programa equivalente
tendrá la misma información,
valente en lenguaje máquina que llamamos programaobjeto. Si el pro- pero en un formato diferente.
grama fuente es el ensamblador, esta fase también se llama ensamblar
y, en este caso, la traducción es más sencilla, dado que, como hemos
dicho, a cada instrucción en ensamblador le corresponde una instruc-
ción máquina. A partir de esta fase, no hay ninguna diferencia entre
los objetos que provienen de un lenguaje de alto nivel y los que son de
formato ensamblador.
La estructura de un programa objeto ya contiene los códigos correspondientes
a las instrucciones máquina, pero el programa objeto no puede resolver todas
las direcciones que incluye a causa de las dos razones siguientes:
• En primer lugar, porque los programas de una aplicación habitualmente
están escritos en diferentes ficheros o módulos. Así, si tenemos que hacer
referencia a una variable o a un procedimiento que está escrito en otro
módulo, no la podemos resolver en este paso.
• En segundo lugar, porque las rutinas de uso muy común, como los cálculos
matemáticos más o menos complejos, el control de determinadas opera-
ciones de entrada/salida con ficheros o dispositivos y otros procedimien-
tos muy genéricos ya están generalmente programadas, compiladas e in-
cluidas en un conjunto de ficheros llamados bibliotecas.
Las bibliotecas son un conjunto de módulos objeto organizados adecuada-
mente. El mismo fabricante del software de apoyo a los lenguajes de progra-
mación suele facilitar este fichero. Gracias a las bibliotecas, el programador
se ahorra mucho trabajo y se reducen considerablemente el tamaño del pro-
## Página 34
<!-- source-page: 34 -->
GNUFDL • PID_00214804 34 El sistema operativo: una máquina virtual
grama y el tiempo de programación. Normalmente, el fabricante no facilita el
programa fuente de las bibliotecas, pero sí proporciona las instrucciones y los
parámetros necesarios para utilizarlas.
Tanto los módulos objeto como las bibliotecas indican las direcciones de ma-
nera relativa al inicio del módulo. Después de esta primera fase de compila-
ción, disponemos de un conjunto de módulos objeto con referencias de me-
naje relativas al módulo y también otras referencias externas sin resolver. En
esta fase se crean tres estructuras:
• Latabladesímbolos, que indica la ubicación de cada símbolo definido
en un módulo objeto (variable, procedimiento o constante).
• Latabladereferenciasexternas, que indica qué instrucciones hacen re-
ferencia a símbolos no definidos dentro de un módulo objeto.
• Latabladereubicación, que indica qué referencias a símbolos se tendrán
que modificar en el proceso de montaje. Sin embargo, no es objeto de esta
asignatura dar los detalles de cómo se tratan estas tablas ni de la manera
como se utilizan.
Ejemplo de compilación
Consideramos que hay dos módulos, producte.c y principal.c. En principal.c (figura 10),
encontramos un bucle que imprime los cuadrados de los n primeros números. En el ejem-
plo hemos tomado el valor n = 10. Este programa hace una serie de llamadas a un se-
gundo módulo en el que hemos escrito el código para el producto. Recordad que sólo
es un ejemplo didáctico en el que no lo hemos resuelto todo. Primeramente podemos
ver, de manera muy simplificada, cómo es la compilación de producte.c. Presentamos el
código ensamblador sólo para intuir el significado del código máquina. Las constantes 0
y 1 se han cargado en posiciones de memoria, ya que la máquina dispone únicamente
de direccionamiento directo.
Es importante ver que ahora hay una serie de referencias externas no resueltas, como las
direcciones de las variables a y b y las de los procedimientos producte y printf.
n = 10;
for (i = 0; i ‹ n; i++){
a = i;
b = i;
producte();
printf(_i = %d quadrat =...
}
## Página 35
<!-- source-page: 35 -->
GNUFDL • PID_00214804 35 El sistema operativo: una máquina virtual
Figura 10. Código en alto nivel, ensamblador y lenguaje máquina del programa principal.c
ACC)Elmontaje(enlazadoolinkaje)
El montaje es el proceso encargado de coger todos los módulos corres-
pondientes a un programa, y también las bibliotecas si es necesario, y
agruparlos para construir el programa ejecutable. Los programas que
llevan a cabo estas funciones se llaman montadores.
De hecho, el trabajo principal del enlazador es resolver las direcciones externas
entre los módulos y también con la biblioteca. El resultado final del enlaza-
dor es el programa ejecutable definitivo con un espacio lógico de direcciones
completamente construido.
La cabeceradeunficheroejecutable contiene información relativa a la ma-
nera como se tiene que cargar en la memoria, como el tamaño de la pila y de
los datos no inicializados o dinámicas, el número de segmentos (si los hay) o la
especificación de si el código puede ser compartido con otros procesos, entre
otros. También indica si se genera código para su depuración y, en ese caso, ad-
junta información con el nombre de las variables, de los procedimientos y de
## Página 36
<!-- source-page: 36 -->
GNUFDL • PID_00214804 36 El sistema operativo: una máquina virtual
las direcciones que sean necesarias. Es habitual que la cabecera contenga una
marca, que es la firmadelfichero. Con este identificador, el sistema operati-
vo sabe cuándo un fichero es realmente ejecutable, siempre que la versión del
sistema operativo sea correcta o el código máquina generado sea el apropiado.
El enlazado de los módulos del ejemplo propuesto es el que podéis ver en el
gráfico de la figura 11.
## Página 37
<!-- source-page: 37 -->
GNUFDL • PID_00214804 37 El sistema operativo: una máquina virtual
Figura 11. Enlazado de los módulos de ejemplo
## Página 38
<!-- source-page: 38 -->
GNUFDL • PID_00214804 38 El sistema operativo: una máquina virtual
Podemos ver que a la hora de generar el fichero ejecutable surgen diferentes
problemas:
• Las direcciones de las tres partes que se tienen que enlazar empiezan por 0.
• Hay dos símbolos locales con el mismo nombre: i.
• Existen variables y procedimientos todavía sin montar.
• Las direcciones de los saltos incondicionales (BEQ) pueden cambiar.
Para solucionar estos problemas, el fichero ejecutable podría ser parecido a lo
que presentamos en la figura 12.
Como podemos ver, se han resuelto las referencias externas. El compilador no
puede simplificar la doble definición de la constante del cero y la mantiene por
separado. El orden de los módulos es irrelevante, de manera que podríamos
crear un ejecutable equivalente alterando el orden de los módulos. También
hay que subrayar que el código de la biblioteca se ha gestionado como un
módulo más y que se ha añadido el código de la rutina de biblioteca printf.
Por otra parte, si alteramos un solo módulo objeto, claro está que el resto de
objetos son perfectamente válidos para su montaje.
## Página 39
<!-- source-page: 39 -->
GNUFDL • PID_00214804 39 El sistema operativo: una máquina virtual
Figura 12. Fichero ejecutable correspondiente al programa de ejemplo
## Página 40
<!-- source-page: 40 -->
GNUFDL • PID_00214804 40 El sistema operativo: una máquina virtual
ACD)Lacarga
La tarea principal del cargador es buscar los recursos que pide el progra-
ma ejecutable. Entre estos recursos está la memoria. El cargador tiene
que buscar memoria libre, copiar el código y también asignar espacio
para datos y pila.
Todo este proceso se hace adaptando las direcciones lógicas de los programas
ejecutables a las direcciones físicas de la memoria real del sistema. Finalmente,
el cargador transfiere el control a la primera instrucción del programa y marca
el proceso como preparado.
Cuando muchos usuarios llaman al mismo programa, si éste permite que se
utilice en modalidad compartida, el cargador mirará si hay alguna copia en la
memoria del programa. Si la respuesta es afirmativa, hará que el nuevo proceso
tenga todo su entorno propio excepto la parte de memoria donde reside el
código, que será compartida con otros procesos. Recordemos el ejemplo de los
programadores que utilizan al mismo tiempo el editor.
ACE)Laejecución(depuración)
Mientras se lleva a cabo el proceso de desarrollo de un programa o de una
aplicación informática, suele ser muy interesante seguir la ejecución paso a
paso, cosa que es factible gracias a los programas depuradores (debuggers).
Los programasdepuradores tienen como única misión controlar la eje-
cución de un programa y permiten que el programador pueda ver una
ejecución paso a paso (instrucción por instrucción) o por intervalos de
programa visualizando constantemente el entorno que se va producien-
do. El programador fija los puntos donde se tiene que detener la ejecu-
ción y en cada parada puede comprobar si el entorno es el correcto e,
incluso, puede modificarlo.
Así, se pueden ver y corregir errores que difícilmente se encontrarían de otra
manera y se obtiene un programa final más eficiente (figura 13).
El hecho de partir el programa fuente en diferentes ficheros presenta varias
ventajas tanto para el programador como para el sistema:
• Por una parte, porque una aplicación informática puede tener decenas de
miles de líneas de código y editar un fichero de estas dimensiones puede
resultar poco práctico.
## Página 41
<!-- source-page: 41 -->
GNUFDL • PID_00214804 41 El sistema operativo: una máquina virtual
• El proceso de compilación también sale beneficiado porque en la primera
fase sólo tiene que traducir los módulos de código fuente que se hayan
modificado. En la fase final o de enlace, sí tiene que actuar en todos los
módulos objeto, tanto los que se han cambiado como los antiguos.
Figura 13. Etapas de la creación de un programa ejecutable
ACF)Interpretaciónfrenteacompilación
Los intérpretes intentan unificar todo el proceso de creación de un pro-
grama (figura 15). A medida que se va leyendo cada instrucción del len-
guaje de alto nivel, se transforma en el conjunto correspondiente de
instrucciones máquina, que se ejecuta inmediatamente. El interpreta-
dor controla directamente todas las fases de la creación y la ejecución
del programa. Es un paso necesario para la ejecución, ya que no se ge-
nera ningún otro fichero y siempre se trabaja con el fichero fuente.
La interpretación de programas fue el primer proceso que apareció, dado que
los ordenadores eran poco potentes y el proceso de compilación demasiado
costoso. Era demasiado lento para aplicarlo continuamente durante el proceso
de creación y depuración de la aplicación. Con el aumento progresivo de la
potencia de los sistemas informáticos, la fase de compilación es cada día más
rápida y permite trabajar de manera continua en esta modalidad. En los últi-
mos tiempos, con la evolución de las redes de comunicaciones y la creación
## Página 42
<!-- source-page: 42 -->
GNUFDL • PID_00214804 42 El sistema operativo: una máquina virtual
de aplicaciones pensadas para este entorno, se está volviendo a la ejecución
interpretada. Los procedimientos se envían en código de alto nivel hasta las
máquinas remotas que los ejecutan mediante el uso de intérpretes. Esta mo-
dalidad tiene dos grandes ventajas: el código de alto nivel es más compacto
que el código máquina y, además, es independiente de la plataforma en la que
se ejecuta.
Figura 14. Proceso de interpretación
B)Losespaciosdedireccionesdeunproceso
Ahora analizaremos el mecanismo de carga de un programa en la memoria y
la manera como esta memoria se diferencia de la memoria asignada a otros
procesos o de la asignada al sistema operativo mismo.
BA)Elespaciológicoyelespaciofísico
Partimos del espacio definido en el fichero ejecutable, que puede tener una es-
tructura de una o dos dimensiones (espacio lineal o segmentado). Estos espa-
cios se tienen que transportar desde los ficheros ejecutables hasta la memoria
que gestiona el sistema operativo. La estructura que tiene esta memoria puede
ser muy diferente en función del modelo escogido por el sistema operativo.
Un programa reubicable se puede ejecutar en direcciones diferentes, que son
asignadas por el cargador. Por este motivo, se distingue entre direcciones lógi-
cas y direcciones físicas:
• Direccioneslógicas: son las referenciadas por el programador. Suelen ser
identificadores que reconocen las diferentes partes de un programa dentro
de su espacio de direcciones. Son referencias locales en el sentido de que
el entorno en el que se tengan que ejecutar no tiene importancia.
## Página 43
<!-- source-page: 43 -->
GNUFDL • PID_00214804 43 El sistema operativo: una máquina virtual
• Direccionesfísicas: son las direcciones asignadas en tiempo de carga, es
decir, el lugar donde residirá realmente el proceso durante su ejecución.
BB)Lacargaenlamemoria:reubicación
La propiedad de la reubicación hace referencia a la capacidad de cargar y des-
pués ejecutar un programa en cualquier posición de memoria. Es la propiedad
contraria a la obligatoriedad de conocer todas las direcciones de manera fija
en tiempo de compilación. Obviamente, existe un mecanismo de traducción
entre las direcciones lógicas y las físicas. En función de cómo se haga, pode-
mos distinguir dos tipos de reubicación:
• Reubicaciónestática: se da antes o durante la carga del programa en la
memoria para ser ejecutado. Durante el proceso de creación de un progra-
ma ejecutable, normalmente se toma la dirección lógica 0 como dirección
inicial del programa. De esta manera, las demás direcciones del programa
serán direcciones lógicas relativas a la dirección final de carga del progra-
ma. Cuando se carga la imagen del programa, todas las direcciones mar-
cadas como reubicables se transforman en la dirección física final. La re-
ferencia para poder hacerlo es la dirección lógica inicial.
Una vez se han modificado todas estas direcciones para reubicarlas, no se
pueden distinguir las direcciones reubicadas de las originales, en otras pa-
labras, no se puede volver a reubicar durante la ejecución. Si por alguna
razón el proceso tiene que salir de su espacio físico asignado, o bien vuel-
ve exactamente al mismo lugar o bien se tiene que iniciar el proceso de
reubicación desde el principio, es decir, se tiene que volver a cargar.
A causa del problema anterior, la reubicación estática está prácticamente
limitada a ciertos sistemas operativos en los que el mecanismo de carga de
programas en la memoria está muy determinado y el código tiene que ir
a particiones de memoria predeterminadas.
• Reubicacióndinámica: en este caso, la transformación entre las direccio-
nes físicas y lógicas se efectúa en tiempo de ejecución. Como antes, los
programas ejecutables resultantes de las compilaciones adoptan la direc-
ción inicial de referencia 0.
Estas imágenes de los programas ejecutables se cargan directamente en
cualquier posición de memoria y es una parte del hardware el que se en-
carga de hacer el trabajo de reubicación de direcciones lógicas a físicas.
Cuando el proceso se está ejecutando, antes de acceder realmente a la me-
moria física se reubican todos los accesos a la memoria. Este proceso se
lleva a cabo utilizando los registros de base. Cada referencia que hace un
proceso durante su ejecución es transformada por la suma de la dirección
lógica más el registro de base para obtener la dirección física. Este meca-
nismo es totalmente transparente para el programador.
La transformación de direcciones depende en gran medida del hardware
del sistema. Por este motivo no podemos dar más detalles sin hablar de una
## Página 44
<!-- source-page: 44 -->
GNUFDL • PID_00214804 44 El sistema operativo: una máquina virtual
arquitectura de ordenador determinada; pero, de hecho, eso no es objeto
de esta asignatura.
C)Mapaconceptual
El mapa conceptual de la figura 15 refleja las relaciones que existen entre los
diferentes conceptos que hemos presentado en este módulo.
Figura 15. Mapa conceptual