Hablemos de sistemas informaticos.
Las primeras computadoras eran máquinas con un tamaño (físico) enorme que se ejecutaban desde una consola. El programador, que también era el operador del sistema de computación, escribía un programa y luego operaba el programa directamente desde la consola del operador. Primero, el programa se cargaba manualmente en la memoria usando los interruptores del tablero frontal (una instrucción a la vez), desde cinta de papel o desde tarjetas perforadas. Luego se oprimían los botones apropiados para establecer la dirección de inicio y poner en marcha la ejecución del programa. Mientras el programa se ejecutaba, el programador/operador podía vigilar su ejecución gracias a las luces de la consola. Si se descubrían errores, el programador podía detener el programa, examinar el contenido de la memoria y los registros, y depurar el programa directamente desde la consola. Las salidas se imprimían o se perforaban en cinta de papel o tarjetas para imprimirse posteriormente.
Con el paso del tiempo, se desarrolló software y hardware adicional. Los lectores de tarjetas, impresores de líneas y unidades de cinta magnética se hicieron comunes.
Se diseñaron ensambladores, cargadores y enlazadores para facilitar la tarea de programación. Se crearon bibliotecas de funciones comunes, las cuales se podían entonces copiar en un programa nuevo sin tener que escribirlas otra vez; esto permitía reutilizar el software.
Las rutinas que realizaban E/S tenían especial importancia. Cada nuevo dispositivo de E/S tenía sus propias características y requería una programación cuidadosa. Se escribía una subrutina especial para cada dispositivo de E/S. Tales subrutinas se denominan drivers de dispositivos. Un driver de dispositivo sabe cómo deben usarse los bufers, banderas, registros, bits de control y bits de estado de un dispositivo en particular. Cada tipo de dispositivo diferente tiene su propio driver.
Una tarea sencilla, como leer un carácter de un lector de cinta de papel, podría implicar secuencias complejas de operaciones específicas para el dispositivo. En vez de escribir el código necesario en cada ocasión, simplemente se usaba el driver de la biblioteca.
Más adelante aparecieron compiladores para FORTRAN, COBOL otros lenguajes, lo que facilitó mucho la tarea de programar pero complicó la operación del computador. Para preparar un programa FORTRAN para su ejecución, por ejemplo, el programador primero necesitaba cargar el compilador de FORTRAN en el computador. El compilador normalmente se guardaba en cinta magnética, por lo que era necesario montar la cinta correcta en la unidad de cinta. El programa se leía a través del lector de tarjetas y se escribía en otra cinta. El compilador de FORTRAN producía una salida en lenguaje ensamblador, que entonces era necesario ensamblar.
Este procedimiento requería montar otra cinta con el ensamblador. La salida del ensamblador tenía que enlazarse con las rutinas de biblioteca de soporte. Finalmente, la forma objeto binaria del programa quedaba lista para ejecutarse: se podía cargar en la memoria y depurarse desde la consola, igual que antes.
Queda claro que la ejecución de un trabajo podía requerir un tiempo de preparación considerable. Cada trabajo consistía en muchos pasos individuales: cargar la cinta del compilador de FORTRAN, ejecutar el compilador, descargar la cinta del compilador, cargar la cinta del ensamblador, ejecutar el ensamblador, descargar la cinta del ensamblador, cargar el programa objeto y ejecutar el programa objeto. Si ocurría un error durante cualquier paso, cabía la posibilidad de que hubiera que comenzar otra vez desde el principio. Cada paso del trabajo podría implicar la carga y descarga de cintas magnéticas, cintas de papel y tarjetas perforadas.
El tiempo de preparación del trabajo era un verdadero problema. Mientras se estaban montando cintas o el programador estaba operando la consola, la CPU permanecía ociosa. Recuerde que, en los primeros días, no había muchas computadoras y eran muy costosas (costaban millones de dólares). Además, estaban los costos de operación de electricidad, enfriamiento, programadores, y demás. Por tanto, el tiempo de computador era extremadamente valioso, y los dueños querían que sus computadores se usaran lo más posible. Se requería un grado de utilización elevado para que sus inversiones fueran rentables.
La solución tuvo dos aspectos. Primero, se contrató un operador de computador profesional. El programador ya no operaba la máquina. Tan pronto como terminaba un trabajo, el operador podía iniciar el siguiente. Puesto que el operador tenía más experiencia con el montaje de cintas que un programador, el tiempo de preparación se redujo. El usuario proporcionaba las tarjetas o cintas necesarias, así como una descripción corta de cómo debía ejecutarse el trabajo. Desde luego, el operador no podía depurar un programa incorrecto en la consola, porque no entendía el programa.
En caso de ocurrir un error, se producía un vuelco de la memoria y los registros, y el programador tenía que depurar su programa basándose en ese vuelco.
El vuelco de memoria y registros permitía al operador continuar de inmediato con el siguiente trabajo, pero dejaba al programador con un problema de depuración mucho más complejo.
El segundo ahorro de tiempo considerable implicó la reducción del tiempo de preparación.
Los trabajos con necesidades similares se agrupaban en lotes y se ejecutaban juntos en el computador. Por ejemplo, supongamos que el operador recibía un trabajo en FORTRAN, uno en COBOL y otro en FORTRAN. Si los ejecutaba en ese orden, tendría que preparar el sistema para FORTRAN (cargar las cintas del compilador, etc.), luego prepararlo para COBOL y luego prepararlo para FORTRAN otra vez. En cambio, si el operador ejecutaba los dos programas FORTRAN como un lote, sólo tenía que preparar el sistema una vez para FORTRAN, y ahorraba tiempo.
No obstante, seguía habiendo problemas. Por ejemplo, cuando un trabajo paraba, el operador tenía que percatarse de ello observando la consola, determinar por qué había parado el programa (terminación normal o anormal), obtener un vuelco si era necesario y luego cargar el dispositivo apropiado con el siguiente trabajo y reiniciar el computador. Durante esta transición de un trabajo al siguiente, la CPU permanecía ociosa.
A fin de eliminar este desperdicio de tiempo, se desarrolló el secuenciado automático de trabajos. Con esta técnica se crearon los primeros sistemas operativos rudimentarios. Lo que se buscaba era un procedimiento para transferir el control automáticamente de un trabajo al siguiente. Se creó un programa pequeño, llamado monitor residente[1], para este propósito. El monitor residente siempre está (reside en) la memoria.
Cuando el computador se encendía, se invocaba el monitor residente, el cual transfería el control a un programa. Cuando el programa terminaba, devolvía el control al monitor residente, que entonces continuaba con el siguiente programa.
Así, el monitor residente mantenía la continuidad de un programa a otro y de un trabajo a otro en secuencia.
Pero, ¿cómo sabría el monitor residente cuál programa ejecutar? Antes, se proporcionaba al operador una descripción corta de qué programas se debían ejecutar con qué datos. Para poder proporcionar esta información directamente al monitor, se introdujeron las tarjetas de control. La idea es sencilla. Además de los programas y datos de un trabajo, el programador incluía tarjetas especiales (de control) que contenían directrices para el monitor residente que indicaban cuál programa se debía ejecutar. Por ejemplo, un programa de usuario normal podría requerir la ejecución de uno de tres programas: el compilador de FORTRAN (FTN), el ensamblador
(ASM) o el programa del usuario (RUN). Podríamos usar una tarjeta de control individual para cada uno de ellos:
$FTN – Ejecutar el compilador de FORTRAN.
$ASM – Ejecutar el ensamblador.
$RUN – Ejecutar el programa de usuario.
Estas tarjetas le dicen al monitor residente cuáles programas debe ejecutar.
Podemos usar dos tarjetas de control adicionales para definir las fronteras de cada trabajo:
$JOB – Primera tarjeta de un trabajo.
$END – Última tarjeta de un trabajo.
Estas dos tarjetas podrían ser útiles para llevar la contabilidad de los recursos de la máquina empleados por el programador. Se pueden usar parámetros para definir el nombre del trabajo, el número de la cuenta a la que se harán los cargos, etc. Se pueden definir otras tarjetas de control para otras funciones, como pedir al operador que monte o desmonte una cinta.
Un problema de las tarjetas de control es cómo distinguirlas de las tarjetas de datos o de programa. La solución usual es identificarlas con un carácter o patrón especial en la tarjeta. Varios sistemas usaban el carácter de dólar ($) en la primera columna para identificar una tarjeta de control. Otros usaban un código distinto. El Job Control Language (Lenguaje de Control de Trabajos) de IBM usaba diagonales (//) en las dos primeras columnas Así, un monitor residente tiene varias partes identificables. Una es el intérprete de tarjetas de control que se encarga de leer y ejecutar las instrucciones de las tarjetas en el punto de ejecución. Intermitentemente, el intérprete de tarjetas de control invoca un cargador para que cargue programas de sistemas y de aplicación en la memoria.
Por tanto, el monitor residente debe incluir un cargador. Tanto el intérprete de tarjetas de control como el cargador necesitan efectuar E/S, por lo que el monitor residente cuenta con un juego de drivers para los dispositivos de E / S del sistema. A menudo, los programas de sistema y de aplicación se enlazan con estos mismos drivers de dispositivos para lograr la continuidad de su operación y ahorrar espacio de memoria y tiempo de programación.
Estos sistemas por lotes funcionan muy bien. El monitor residente se encarga del secuenciado automático de los trabajos según las indicaciones de las tarjetas de control.
Cuando una tarjeta de control indica que debe ejecutarse un programa, el monitor carga ese programa en la memoria y le transfiere el control. Una vez que el programa termina, transfiere el control de vuelta al monitor, que lee la siguiente tarjeta de control, carga el programa apropiado, y así sucesivamente. Este ciclo se repite hasta que se han interpretado todas las tarjetas de control del trabajo. Luego, el monitor continúa automáticamente con el siguiente trabajo.
El cambio a los sistemas por lotes con secuenciado automático de trabajos se realizó con el fin de mejorar el desempeño. El problema, sencillamente, es que los seres humanos son extremadamente lentos (en comparación con el computador, por supuesto).
Por consiguiente, es deseable sustituir la operación humana por software de sistema operativo. El secuenciado automático de trabajos elimina la necesidad del tiempo de preparación y el secuenciado de trabajos por parte de seres humanos.
Pero incluso con esta forma de hacer las cosas la CPU con frecuencia está ociosa. El problema es la rapidez de los dispositivos de E /S mecánicos, que intrínsecamente son más lentos que los dispositivos electrónicos. Aun una CPU lenta opera en el ámbito de microsegundos, y ejecuta miles de instrucciones cada segundo. Un lector de tarjetas rápido, en cambio, podría leer 1200 tarjetas por minuto (17 tarjetas por segundo). Así, la diferencia de velocidad entre la CPU y sus dispositivos de E/S podría ser de tres órdenes de magnitud o más. Con el tiempo, claro, las mejoras en la tecnología produjeron dispositivos de E/S más rápidos.
Desafortunadamente, las velocidades de CPU aumentaron a un ritmo todavía más acelerado, de modo que el problema no sólo no se resolvió, sino que se exacerbó.
Una solución común fue sustituir los lectores de cinta (dispositivos de entrada) e impresoras de líneas (dispositivos de salida), que eran lentos, por unidades de cinta magnética. La mayor parte de los sistemas de computación de fines de los años cincuenta y principios de los sesenta eran sistemas por lotes que leían de lectores de tarjetas y escribían en impresoras de líneas o perforadoras de tarjetas. Sin embargo, en vez de hacer que la CPU leyera directamente de las tarjetas, éstas se copiaban primero en una cinta magnética empleando un dispositivo aparte. Una vez que la cinta estaba suficientemente llena, se desmontaba y se llevaba al computador.
Cuando se necesitaba una tarjeta para introducirla al programa, se leía el registro equivalente de la cinta. De forma similar, las salidas se escribían en la cinta y el contenido de la cinta se imprimía posteriormente. Los lectores de tarjetas e impresoras de líneas se operaban fuera de línea, no con el computador principal.
La ventaja principal del funcionamiento fuera de línea era que el computador principal ya no estaba limitado por la rapidez de los lectores de tarjetas e impresoras de líneas, sino sólo por la de las unidades de cinta magnética, mucho más rápidas. Esta técnica de usar cinta magnética para toda la E/S podía aplicarse a cualquier equipo similar (lectores de tarjetas, perforadoras de tarjetas, graficadores, cinta de papel, impresoras).
La verdadera utilidad de la operación fuera de línea radica en la posibilidad de usar múltiples sistemas de lector a cinta y de cinta a impresora para una misma CPU. Si la CPU puede procesar las entradas a una velocidad dos veces mayor que aquella conla que el lector puede leer tarjetas, dos lectores trabajando simultáneamente pueden producir suficiente cinta como para mantener ocupada a la CPU. Por otra parte, el retardo para llevar a cabo la ejecución de un trabajo en particular ha aumentado, porque primero debe leerse y grabarse en la cinta, luego habrá un retardo hasta que se hayan grabado en la cinta suficientes trabajos adicionales para “llenarla”. Entonces, la cinta debe rebobinarse, desmontarse, llevarse manualmente a la CPU y montarse en una unidad de cinta desocupada. Desde luego, este proceso es aceptable en el caso de los sistemas por lotes. Pueden formarse lotes de muchos trabajos similares en una cinta antes de llevarlos al computador.
Aunque la preparación de trabajos fuera de línea continuó durante cierto tiempo, pronto fue reemplazada en la mayor parte de los sistemas. Los sistemas de disco comenzaron a proliferar y mejoraron considerablemente la operación fuera de línea.
El problema de los sistemas de cinta era que el lector de tarjetas no podía grabar en un extremo de la cinta mientras la CPU leía del otro. Era preciso escribir toda la cinta antes de rebobinarla y leerla, porque las cintas son por su naturaleza dispositivos de acceso secuencial. Los sistemas de disco eliminaron este problema porque son dispositivos de acceso aleatorio. Dado que la cabeza se mueve de un área del disco a otra, el disco puede conmutar rápidamente del área del disco que el lector de tarjetas está usando para almacenar nuevas tarjetas, a la posición que la CPU necesita para leer la “siguiente” tarjeta.
En un sistema de disco, las tarjetas se leen directamente del lector de tarjetas al disco. La posición de las imágenes de tarjetas se registra en una tabla mantenida por el sistema operativo. Cuando se ejecuta un trabajo, el sistema operativo satisface sus solicitudes de entradas del lector de tarjetas leyendo del disco. Así mismo, cuando el trabajo solicita que la impresora escriba una línea, esa línea se copia en un buffer del sistema y se escribe en el disco. Una vez que ha terminado el trabajo, las salidas se imprimen. Esta forma de procesamiento se denomina spooling. Con spooling, el disco básicamente se usa como un buffer grande para leer con la mayor anticipación posible de los dispositivos de entrada y almacenar los archivos de salida hasta que los dispositivos de salida estén listos para aceptarlos.
El spooling conduce de manera natural a la multiprogramación, que es la base de todos los sistemas operativos modernos.
[1] Diferente de nuestra concepción actual de monitor, refiriéndonos a la pantalla del pc.