Linux
 
La Evolución del Sistema de Tiempo Compartido Unix

Introducción

La deuda de Linux (acrónimo de Linus Unix - por Linus Torvalds, creador del kernel -) respecto a Unix es grande. Linux llega a ser lo que es hoy, parafraseando a Newton, porque se subió en hombros de gigantes para ver más lejos. Por supuesto esto es una parte del asunto, ya que otra cuestión de suma importancia es la filosofía que está detrás de todo el movimiento. Unix ha seguido su camino, de utilización marginal apuntada sobre todo al segmento de los servidores y las máquinas de altísima gama mientras que Linux alcanzó al usuario doméstico.

Desde muchos puntos de vista Linux es simplemente un Unix (gratuito) y me resulta muy interesante bucear en la historia de su desarrollo y cómo se convirtió en el increíble sistema operativo que es hoy en día. Los albores de Unix se sitúan en la década del 70 cuando era época de los sistemas de tiempo compartido (Time-Sharing System) en los cuales varios usuarios utilizaban simultáneamente a través de una terminal "boba" la potencia de proceso de una computadora central que era una máquina grande y cara. Lejos estaban aún los días de las computadoras personales. Cuando comenzaban a aparecer máquinas más pequeñas (pero no mucho menos caras) varios pioneros de la programación buscaron maneras de hacer que esos equipos se comportasen de la forma que ellos necesitaban y permitiesen interactuar más humanamente.

El sistema Unix fue creado por Ken Thompson (1943), Dennis Ritchie (1941) y otros colaboradores en los Laboratorios Bell. Ritchie es también el creador del lenguaje de programación C. Este artículo de 1996 (originalmente escrito en 1979) cuenta resumidamente la historia de la génesis del sistema y sus primeros avatares.


Dennis Ritchie. Foto tomada desde Linux Magazine # 7 (2001)

Mi traducción ha intentado ser lo más literal posible y, en vista de que la jerga computacional utiliza en castellano muchos términos en inglés, he decidido dejar en inglés ciertas palabras que suenan más naturales para los que trabajan con máquinas en su idioma original y las identifiqué con ' ' (comillas simples). En otras ocasiones añado observaciones entre corchetes "[ ]" para referir la palabra original utilizada. De cualquier manera la lista de equivalencias es:

pipe = tubo/tubería (enviar la salida de un comando hacia la entrada de otro).
pipelined = entubado (comandos entubados).
kernel = núcleo (de un sistema operativo).
debugging = corregir errores.
cross-assembler = compilador cruzado de ensamblador.
assembler = ensamblador (lenguaje de programación y compilador para programas en dicho lenguaje).
i-node = nodo-i (sectores de disco donde se aloja la información de un archivo bajo Unix).
I/O = entrada/salida (de programas o en general de datos).
swap = intercambio (refiere a espacio en disco rígido usado como memoria del sistema).
ID = identificador.
crashear = cuando un sistema produce un error de tal magnitud que debe reiniciarse el equipo.
messages = refiere a una aplicación encargada de enviar mensajes.

Finalmente ubico el link para conseguir el artículo original, en inglés.

SUMARIO

La evolución del sistema de tiempo compartido Unix (traducción al castellano)

The Evolution of the Unix Time-Sharing System (artículo original de Dennis Ritchie)


La Evolución del Sistema de Tiempo Compartido Unix* [TRADUCCIÓN]


Dennis M. Ritchie
Bell Laboratories, Murray Hill, NJ,07974

RESUMEN

Este artículo presenta una historia de los comienzos del desarrollo del sistema operativo Unix. Se concentra en la evolución del sistema de archivos, el mecanismo de control de procesos, y la idea de los comandos entubados [pipelined]. Se presta también alguna atención a las condiciones sociales durante el desarrollo del sistema.

NOTA: *Este artículo fue presentado primeramente en la conferencia "Language Design and Programming Methodology" en Sydney, Australia, en Septiembre de 1979. Las presentaciones de la conferencia fueron publicadas como "Lecture Notes in Computer Science #79: Language Design and Programmig Methodology, Springer-Verlag, 1980". Esta reedición se basa en una versión reimpresa que apareció en el "AT&T Bell Laboratories Technical Journal 63 No. 6 Part 2, Octubre de 1984, pags. 1577-93".

Introducción

Durante los últimos años el sistema operativo UNIX ha sido empleado en forma cada vez más extensiva, siendo esta tendencia tan notable que los Laboratorios Bell han hecho su nombre una marca registrada. Sus características más importantes son ahora conocidas por muchos. Ha sufrido mucha reescritura y experimentación desde la primera publicación que lo describió allá en 1974 [1], pero pocos cambios han sido radicales. De cualquier forma, Unix nació en 1969 y no en 1974 y la historia de su desarollo tal vez sea poco conocida y quizás instructiva. Esta publicación presenta una historia técnica y social de la evolución del sistema.

Orígenes

Para la ciencia computacional de los Laboratorios Bell, el período 1968-1969 fue algo perturbador. La razón principal para ello fue el lento, aunque inevitable, abandono del proyecto Multics. Para toda la comunidad informática del Laboratorio el problema fue la creciente aceptación de que Multics no había podido entregar rápidamente algún tipo de sistema usable, quedando lejos la panacea vislumbrada inicialmente. Durante mucho de este tiempo, el Centro de Cómputos Murray Hill estaba también corriendo una costosa máquina GE 645 que en forma inadecuada simulaba la GE 635. Otra sacudida que ocurrió durante este período fue la escisión organizacional de las áreas de servicios computacionales e investigación.

Desde el punto de vista del grupo que estuvo más involucrado en los comienzos de Unix (K. Thompson, Ritchie, M.D. McIlroy, J.F. Ossanna) la pérdida de empuje y caída de Multics tuvo un efecto que percibimos. Estábamos entre los últimos grupos de los Laboratorios Bell que trabajaban con Multics, así que su éxito era como una meta para nosotros. Más importante aún, el servicio interactivo que Multics había prometido a la comunidad entera estaba de hecho disponible para nuestro grupoo, primeramente bajo el sistema CTSS usado para desarrollar Multics y finalmente bajo Multics mismo. Pese a que Multics no podía soportar muchos usuarios nos podía soportar a nosotros, aunque a un costo exorbitante. No queríamos perder el placentero nicho que ocupábamos, porque no había nada parecido; incluso el servicio de tiempo compartido que luego sería ofrecido bajo el sistema GE no existía. Lo que queríamos preservar no era simplemente un buen entorno en el cual programar sino un sistema alrededor del cual se generaba una relación de camaradería. Debido a esa experiencia supimos que la esencia de la computación comunitaria, brindada por el acceso remoto y las máquinas de tiempo compartido, no era solamente tipear programas en una terminal en lugar de una tarjeta perforada [keypunch], sino fortalecer los lazos de comunicación.

Entonces, durante 1969, comenzamos a buscar una alternativa a Multics. La búsqueda tomó diversas formas. A lo largo de 1969 nosotros (mayormente Ossanna, Thompson, Ritchie) presionamos intensivamente para la compra de una máquina de media escala para la cual prometimos escribir un sistema operativo; las máquinas que sugerimos fueron la DEC PDP-10 y la SDS (luego Xerox) Sigma. El esfuerzo fue frustrante, debido a que nuestras propuestas nunca fueron claras finalmente disminuyendo en ambición, pero ciertamente nunca siendo aceptadas. En varias ocasiones pareció que estabamos a punto de conseguirlo. El impulso final a este esfuerzo sucedió cuando presentamos una propuestas exquisitamente complicada diseñada para minimizar el gasto financieron, que implicaba alguna compra directa, algún grado de tercerización y un plan para transformar un procesador DEC KA-10 en el más capaz KI-10, que estaba pronto a ser anunciado. La propuesta fue rechazada, y se corrió el rumor de que W.O. Baker (el vicepresidente de Investigación) había reaccionado ante ella diciendo "¡los Laboratorios Bell simplemente no hacen negocios en esa forma!"

Actualmente es perfectamente obvio en retrospectiva (y debió ser en aquel momento) que estábamos pidiendo al Laboratorio gastar demasiado dinero en demasiada poca gente con un plan demasiado vago, y estoy seguro que en esa época los sistemas operativos no eran, para nuestros jefes, un área atractiva en la cual financiar trabajo. Ellos estaban en el proceso de librarse no solamente del esfuerzo del desarrollo de un sistema operativo que había fallado, sino también de llevar adelante el Centro de Computación. Pudo parecerles que comprar una máquina como la que nosotros sugeríamos podría llevarlos a otro Multics, o si llegásemos a producir algo útil, a otro Centro de Computación del cual ellos deberían hacerse responsables.

Además de la agitación financiera que tuvo lugar en 1969, también hubo trabajo técnico. Thompson, R.H. Canaday y Ritchie desarrollaron, en pizarrones y en notas garabateadas, el diseño básico de un sistema de archivos que luego se convertiría en el corazón de Unix. Mucho del diseño fue de Thompson, asi como el impulso a pensar acerca de sistemas de archivos, pero creo que yo contribuí con la idea de los archivos de dispositivos [device files]. La pasión de Thompson para la creación de un sistema operativo tomó varias formas en este período; él también escribió, en Multics, una detallada simulación de la performance del sistema de archivos propuesto y del comportamiento de la paginación de los programas. Asimismo, comenzó a trabajar en un nuevo sistema operativo para el GE-645, llegando tan lejos como a escribir un ensamblador [assembler] para la máquina y un rudimentario kernel cuyo logro mayor era, tal como recuerdo, tipear un mensaje de bienvenida. La complejidad de la máquina era tal que el mero mensaje era ya un logro notable, pero cuando se hizo claro que la vida del 645 en el Laboratorio estaba contada en meses, el trabajo fue abandonado.

También durante 1969, Thompson desarrolló el juevo "Space Travel". Primero escrito en Multics, luego transliterado en Fortran para GECOS (el sistema operativo para la GE, luego Honeywell, 635), no era más que una simulación del movimiento de los cuerpos mayores del sistema solar, con el jugador guiando una nave por aquí y allá, observando el escenario e intentando aterrizar en algunos de los planetas y lunas. La versión GECOS no fue satisfactoria en dos aspectos: primero el display del juego daba sacudidas y era dificil de controlar porque uno tenía que tipear los comandos en él, y segundo, un juego costaba cerca de U$D 75 de tiempo de CPU en la computadora. No tomó demasiado, luego, que Thompson hallara una computadora PDP-7 con poco uso junto con un mejor display; el sistema entero se utilizó como terminal Graphic-II. Él y yo reescribimos "Space Travel" para correr en esta máquina. La empresa fue más ambiciosa de lo que pudiera parecer; debido a que desdeñamos todo el software existente, tuvimos que escribir un paquete para la aritmética de punto flotante, la especificación de los caracteres gráficos para el display y un subsistema de 'debugging' que continuamente mostrase los contenidos de los lugares tipeados en una esquina de la pantalla. Todo esto fue hecho en lenguaje ensamblador para un 'cross-assembler' que corría bajo GECOS y producía cintas de papel que podían llevarse a la PDP-7.

"Space Travel" pese a que era un juego muy atractivo, sirvió mayormente como una introducción a la intrincada tecnología de preparar programas para el PDP-7. Pronto Thompson comenzó la implementación del sistema de archivos bosquejado en papel (tal vez "sistema de archivos de tiza" [chalk file system] sería más apropiado) que había diseñado tempranamente. Un sistema de archivos sin una manera de testearlo es una proposición estéril, de forma que procedió a inbuirlo con los otros requerimientos para un sistema operativo funcional, en particular con la noción de procesos. Entonces vinieron un pequeño conjunto de utilidades para el usuario: los medios para copiar, imprimir, borrar y editar archivos y por supuesto un simple intérprete de comandos (shell). Hasta este momento todos los programas estaban escritos usando GECOS y los archivos eran transferidos a la PDP-7 sobre cinta de papel; pero una vez que un 'assembler' estuvo completo el sistema ya era capaz de soportarse a si mismo. No fue sino hasta bien entrado 1970 que Brian Kernighan sugirió el nombre "Unix", en un burlón juego de palabras respecto de "Multics", que nació el sistema operativo que conocemos hoy.

El sistema de archivos Unix de la PDP-7

Estructuralmente el sistema de archivos del Unix PDP-7 era casi idéntico al de hoy. Tenía:

1) Una 'i-list': un arreglo lineal de 'i-nodes' cada uno describiendo un archivo. Un 'i-node' contenía menos de lo que ahora, pero la información esencial era la misma: el modo de protección del archivo, su tipo y tamaño, y la lista de bloques físicos que tenían el contenido.
2) Directorios: un tipo especial de archivo conteniendo una secuencia de nombres y los 'i-number' asociados.
3) Archivos especiales describiendo dispositivos. La especificación de dispositivo no estaba contenida explícitamente en el 'i-node', sino que estaba codificada en el número: 'i-númbers' específicos correspondian a archivos específicos.

Las llamadas importantes del sistema de archivos estuvieron presentes desde el comienzo. Leer, escribir, abrir, crear, cerrar: con una importante excepción, discutida más abajo, era similar a lo que uno encuentra ahora. Una diferencia menor fue que la unidad de 'I/O' era la palabra, no el byte, debido a que la PDP-7 era una máquina direccionada por palabras. En la práctica esto significaba simplemente que todos los programas que trabajasen con cadenas de caracteres ignoraban caracteres, debido a que 'null' era utilizado para rellenar un archivo a un número par de caracteres. Otra diferencia menor, a veces molesta, fue la carencia de procesos 'erase' y 'kill' para terminales. Las terminales, en efecto, eran siempre en modo 'raw'. Sólo unos pocos programas (el shell y el editor) se preocuparon de implementar procesamiento 'erase-kill'.

Pese a la considerable similaridad respecto del sistema de archivos actual, el sistema de la PDP-7 era en un aspecto remarcablemente diferente: no había rutas de acceso [path names], y cada nombre de archivo para el sistema era un simple nombre (sin "/") tomado relativo al directorio actual. 'Links', en el sentido usual Unix, existían. Junto con un elaborado conjunto de convenciones, ellos eran los principales medios por los cuales la carencia de rutas fue aceptable.

Un 'link' tomaba la forma

   link(dir, file, newname)

donde dir era un archivo de directorio en el directorio actual, file era una entrada existente en ese directorio, y newname el nombre del link, el cual era añadido al directorio actual. Debido a que dir necesitaba estar en el directorio actual, es evidente que la prohibición actual contra links hacia directorios no estaba contemplada; el sistema de archivos Unix de la PDP-7 tenía la forma de un grafo general dirigido.

Como no todo usuario necesitaba mantener un link a todos los directorios de interés, existía un directorio llamado dd que contenía entradas para el directorio de cada usuario. Así, para hacer un link a un archivo x en el directorio ken, yo debía hacer

   ln dd ken ken
   ln ken x x
   rm ken

Este esquema volvía a los subdirectorios suficientemente dificiles de utilizar como para que sean, en la práctica, no usados. Otra importante barrera era que no había manera de crear un directorio una vez que el sistema estaba corriendo; todos debían ser generados durante la recreación del sistema de archivos desde la cinta de papel, de modo que los directorios eran en efecto un recurso no renovable.

La convención dd hacía el comando chdir relativamente conveniente. Tomaba múltiples argumentos, y cambiaba el directorio actual a cada uno de los directorios en orden. Así

   chdir dd ken

movería al directorio ken. (Incidentalmente, chdir se escribía ch; no recuerdo el porqué de esa expansión cuando nos movimos a la PDP-11).

El inconveniente más serio de la implementación del sistema de archivos, además de la carencia de nombres de ruta, era la dificultad de cambiar su configuración; como se mencionó, los directorios y los archivos especiales sólo se generaban cuando se recreaba el disco. La instalación de un nuevo dispositivo era un proceso poco menos que doloroso, debido a que el código de los dispositivos estaba esparcido a través de todo el sistema; existían, por ejemplo, varios 'loops' [ciclos] que visitaban cada dispositivo en turno. No es sorprendente también que no existíese la noción de montar un disco removible, porque la máquina tenía solamente un único disco fijo.

El código del sistema operativo que implementaba este sistema de archivos era una versión drásticamente simplificada del esquema presente. Una simplificación importante era el hecho de que el sistema no era multi-programado; sólo un programa a la vez estaba en memoria, y el control sólo era pasado entre procesos cuando tenía lugar una llamada [swap] explícita. Así, por ejemplo, existía una rutina iget que hacía disponible un cierto 'i-node', pero dejaba el 'i-node' en un lugar constante, estático en lugar de retornar un puntero en una tabla grande de 'i-nodes' activos. Un precursor del actual mecanismo de 'buffering' estaba presente (con 4 buffers) pero no había esencialemente superposición entre I/O de disco y cálculo. Esto fue evitado no simplemente por simplicidad. El disco conectado a la PDP-7 era rápido para su época; tranfería una palabra de 18 bits cada 2 microsegundos. Por otro lado, la PDP-7 misma tenía un ciclo de memoria de 1 microsegundo, y muchas instrucciones tomaban 2 ciclos (uno para la instrucción misma y otro para el operando). De cualquier manera, instrucciones direccionadas indirectamente requerían 3 ciclos, y la indirección era muy común, debido a que la máquina no tenía registros de índice [index registers]. Finalmente, la controladora DMA era incapaz de acceder a la memoria durante una instrucción. La contrapartida era que el disco generaba errores de sobreescritura si eran ejecutadas instrucciones indirectamente direccionadas mientras estaba transfiriendo. Entonces el control no podía ser devuelto al usuario, ni siquiera se podía ejecutar código del sistema, con el disco corriendo. Las rutinas de interrupción para el reloj y las terminales, que necesitaban poder correrse todo el tiempo, tenían que ser codificadas en una manera muy extraña para evitar la indirección.

Control de procesos

Por "control de procesos" me refiero a los mecanismos por los cuales los procesos son creados y utilizados; hoy en día las llamadas del sistema fork, exec, wait y exit implementan esos mecanismos. Al contrario que el sistema de archivos, que existía casi en su forma actual desde los primeros días, el esquema de control de procesos sufrió una mutación considerable luego de que el Unix PDP-7 comenzase a utilizarse. (La introducción de rutas de acceso en el sistema de la PDP-11 fue un avance notacional considerable, pero no un cambio fundamental a nivel estructural.)

Hoy en día, la manera en que los comandos son ejecutados por el sistema puede resumirse como sigue:

1) El shell lee una línea de comandos de la terminal
2) Crea un proceso hijo meidante fork.
3) El procesos hijo usa exec para llamar el comando desde un archivo.
4) Entretanto, el shell padre usa wait para esperar al proceso hijo [child] (comando) que finalice llamando a exit.
5) El shell padre [parent] vuelve al paso 1).

Los procesos (entidades ejecutables independientemente) existian desde los primeros días del Unix de la PDP-7. De hecho existían dos de ellos, uno por cada una de las terminales conectadas a la máquina. No había fork, wait ni exec. Existía un exit, pero su significado era muy diferente, como pronto veremos. El principal loop del shell corría como sigue.

1) El shell cerraba todos los archivos abiertos, y luego abría el archivo especial de la terminal para entrada standard y salida (descriptores de archivo 0 y 1).
2) Leía una línea de comando desde la terminal.
3) Vinculaba el archivo especificando el comando, abría el archivo, y removía el link. Entonces copiaba un pequeño programa 'bootstrap' al tope de la memoria y saltaba hacía ahí; este programa leía en el archivo el código del shell, saltando a la primera dirección del comando (en efecto un exec).
4) El comando hacía su trabajo y terminaba llamando un exit. Esta llamada causaba que el sistema leyera en una nueva copia del shell sobre el comando terminado, entonces pasaba al comienzo (y en efecto al paso 1).

La cosa más interesante acerca de la primitiva implementación es el grado en el cual se anticiparon asuntos que luego se desarrollarían mucho más profundamente. Es verdad, no podía soportar procesos en 'background' ni archivos shell de comandos (obviando el tema 'pipes' y filtros); pero la redirección de IO (a través de '<' y '>') estuvo desde temprano ahí; será discutida más abajo. La implementación de la redirección fue bastante directa; en el paso 3) sobre el shell sencillamente se reemplazaba la entrada standard o salida con el archivo apropiado. Crucial al subsiguiente desarrollo fue la implementación del shell como programa a nivel de usuario guardado en un archivo, en lugar de como parte del sistema operativo.

La estructura de este esquema de control de procesos, con un proceso por terminal, es similar a la de muchos sistemas interactivos, por ejemplo CTSS, Multics, Honeywell TSS, y IBM TSS y TSO. En general tales sistemas requerían mecanismos especiales para implementar características útiles como cálculos desprendidos y archivos de comando; Unix en esta etapa no se preocupó en suministrar los mecanismos especiales. También exhibía algunos problemas de idiosincracia irritanes. Por ejemplo, un nuevo shell recreado debía cerrar todos sus archivos abiertos para deshacerse de cualquier archivo abierto dejado por el comando que acababa de ejecutarse y cancelar la redirección IO previa. Entonces tenía que reabrir el archivo especial correspondiente a su terminal, para poder leer una nueva línea de comando. No había directorio /dev (debido a que no había rutas de acceso); además el shell no podía retener memoria entre comandos, debido a que era reejecutado nuevamente luego de cada comando. Entonces se requería una nueva convención para el sistema de archivos: cada directorio tenía que contener una entrada tty para un archivo especial que refería a la terminal del proceso que la abrió. Sí, por accidente uno cambiaba en otro directorio que carecía de la entrada, el shell quedaría en un ciclo sin esperanza; el único remedio era reiniciar. (A veces el link desaparecido podía ser hecho desde otra terminal.)

El control de procesos en su moderna forma fue diseñado e implementado en un par de días. Es increíble cuan fácilmente esto encajo en el sistema existente; al mismo tiempo es fácil ver como algunas de las características ligeramente inusuales del diseño están presentes justamente debido a que representaron pequeños y sencillos cambios en el código sobre lo que ya existía. Un buen ejemplo es la separación de las funciones fork y exec. El modelo más común para la creación de nuevos procesos implica especificar un programa para que el proceso corra; en Unix, un proceso 'forked' continúa corriendo el mismo programa como su padre hasta que este realiza un exec explícito. La separación de las funciones es ciertamente no única a Unix, y de hecho estaba ya presente en el sistema de tiempo compartido de Berkeley [2], el cual era bien conocido para Thompson. Entonces, parece razonable suponer que existe en Unix mayormente gracias a la facilidad con la cual fork pudo ser implementado sin cambiar demasiadas cosas. El sistema ya manejaba múltiples (i.e. dos) procesos; había una tabla de procesos, y los procesos eran intercambiados entre la memoria principal y el disco. La implementación inicial de fork requería solamente

1) Expansión de la tabla de procesos.
2) Adición de una llamada a fork que copiaba el proceso actual al área de 'swap' del disco, usando las rutinas primitivas IO de swap que ya estaban existentes, y hacía algunos ajustes a la tabla de procesos.

De hecho, la llamada a fork de la PDP-7 requería precisamente 27 líneas de código assembler. Por supuesto, otros cambios en el sistema operativo y en los programas de usuario fueron requeridos, y alguno de ellos fueron muy interesantes e inesperados. Pero un fork-exec combinado hubiera sido considerablemente más complicado, incluso sólo debido a que exec como tal no existía; su función ya estaba siendo realizada, usando IO explícita, por el shell.

La llamada exit, la cual previamente leía en una nueva copia del shell (actualmente algún tipo de exec automático pero sin argumentos), se simplificó considerablemente; en la nueva versión un proceso sólo tenía que limpiar su entrada en la tabla de procesos, y ceder el control.

Curiosamente, las primitivas que se convirtieron en wait fueron considerablemente más generales que el esquema presente. Un par de primitivas envíaban mensajes de una palabra entre procesos nombrados:

   smes(pid, message)
   pid, message) = rmes()

El proceso destino de smes no necesitaba tener ninguna relación ancestral con el receptor, pese a que el sistema no proveía mecanismo explícito para comunicar ID's de procesos excepto que fork retornaba a cada uno de los procesos padre e hijo [child] el ID del otro. Los mensajes no eran encolados; un emisor esperaba hasta que el receptor leía el mensaje.

La característica 'messages' se usaba como sigue; el shell padre, luego de crear un proceso para ejecutar un comando, enviaba un mensaje al nuevo proceso mediante smes; cuando el comando terminaba (asumiendo que no intentaba leer ningún mensaje) la llamada bloqueada del shell a smes retornaba un indicador de error informando que el proceso destino no existía. Entonces el smes del shell se volvía, en efecto, el equivalente del wait.

Un protocolo diferente, el cual tomaba mayor ventaja de las generalidades ofrecidas por los mensajes, fue usado entre el programa de inicialización y los shells para cada terminal. El proceso de inicialización, cuyo ID se entendía que era 1, creaba un shell para cada una de las terminales, y entonces emitía un rmes; cada shell, cuando leía el fin de su archivo de entrada, usaba smes para enviar un mensaje convencional "estoy terminando" al proceso de inicialización, el cual recreaba un nuevo proceso shell para esa terminal.

No puedo recordar algún otro uso de 'messages'. Esto explica porqué dicha característica fue reemplazada por la llamada wait del sistema presente, la cual es menos general, pero más directamente aplicable al propósito buscado. También es relevante un bug evidente en el mecanismo: si un proceso de comando intentaba utilizar 'messages' para comunicar con otro proceso, podía alterar la sincronización del shell. El shell dependía del envío de un mensaje que nunca fue recibido; si un comando ejecutaba rmes, recibiría el falso mensaje del shell, y provocaba que este último leyese otra línea de entrada como si el comando hubiese terminado. Si hubiese existido una necesidad intrínseca por un sistema 'messages' general, el bug hubiese sido reparado.

De esta forma, el nuevo esquema de control de procesos instantáneamente hizo que algunas características fueran implementables trivialmente; por ejemplo procesos desprendidos (con '&') y el uso recursivo del shell como comando. Muchos sistemas tuvieron que suplir algún tipo de característica 'batch job sumission' y un intérprete de comandos especial para archivos distintos del que se usaba interactivamente.

Pese a que la idea de múltiples procesos se insertó de una manera natural, hubo algunos contraefectos que no fueron anticipados. El más memorable de ellos fue evidente luego de que el nuevo sistema se puso en marcha y aparentemente funcionaba. En el medio de nuestra alegría, se descubrió que chdir (cambia el directorio actual) había dejado de funcionar. Hubo mucha lectura de código y nerviosa introspección acerca de como la adición de fork pudo haber roto la llamada a chdir. Finalmente la verdad apareció: en el viejo sistema chdir era un comando ordinario; ajustaba el directorio actual del (único) proceso vinculado a la terminal. Bajo el nuevo sistema, el comando chdir cambiaba correctamente el directorio actual del proceso creado para ejecutarlo, ¡pero este proceso prontamente terminaba y no tenía efecto alguno en su shell padre! Fue necesario hacer que chdir fuese un comando especial, ejecutado internamente dentro del shell. Resultó que varias funciones tipo comando tenían la misma propiedad, por ejemplo login.

Otra diferencia entre el sistema como era y el nuevo esquema de control de procesos tomó más tiempo en hacerse evidente. Originalmente, el puntero de lectura/escritura asociado con cada archivo abierto era guardado dentro del proceso que abrió el archivo. (Este puntero indica donde tendrá lugar en el archivo la próxima lectura o escritura.) El problema con esta organización se volvió evidente solamente cuando intentamos utilizar archivos de comando. Suponga que un simple archivo de comandos contiene:

   ls
   who

y es ejecutado como sigue:

   sh comfile >output

La secuencia de eventos era

1) El shell principal crea un proceso nuevo, el cual abre outfile para recibir la salida standard y ejecuta el shell recursivamente.
2) El nuevo shell crea otro proceso para ejecutar ls, el cual correctamente escribe en archivo la salida y entonces termina.
3) Otro proceso es creado para ejecutar el próximo comando. De cualquier manera, el puntero de IO para la salida es copiado desde el del shell, y aun es 0, debido a que el shell no ha escrito en su salida, y los punteros IO están asociados con procesos. El efecto es que la salida de who sobreescribe y destruye la salida del comando ls precedente.

La solución para este problema requirió crear una nueva tabla de sistema conteniendo los punteros IO de los archivos abiertos independientemente de los procesos en los cuales fueron abiertos.

Redirección de I/O

La muy conveniente notación para redirección de I/O, usando los caracteres '>' y '<' no estuvo presente desde el comienzo del sistema Unix de la PDP-7, pero apareció demasiado después. Como mucho en Unix, fue inspirada en una idea de Multics. Multics tenía un mecanismo general de redireccion I/O [3] que incluía cadenas etiquetadas de IO que podían ser dinámicamente redirigidas a varios dispositivos, archivos, e incluso a través de módulos especiales de proceso de cadenas. Incluso en la versión de Multics con la cual estábamos familiarizados una década atrás, existía un comando que cambiaba subsecuente salida normalmente destinada para la terminal a un archivo, y otro comando que redirigía la salida desde la terminal. Mientras que en Unix uno podría poner

   ls >xx

para obtener una lista de los nombres de archivo en xx, en Multics la notación era

   iocall attach user_output file xx
   list
   iocall attach user_output syn user_i/o 

Pese a que esta secuencia oscura era usada a menudo durante los días de Multics, y hubiese sido fácilmente integrable en el shell de Multics, la idea no se nos ocurrió ni a nosotros ni a nadie en ese tiempo. Especulo que la razón recaía en el tamaño del proyecto Multics: los implementadores del sistema de IO estaban en Bell Labs en Murray Hill, mientras que el shell fue hecho en el MIT. Nosotros no consideramos hacer cambios al shell (este era su programa); correspondientemente los mantenedores del shell tal vez no conocían la utilidad, pese a su obscuridad, de iocall. (El manual de Multics de 1969 [4] lista iocall como 'mantenida por el autor', lo cual significa que es un comando no estandard.) Debido a que ambos, el sistema IO de Unix y su shell, estaban bajo el control exclusivo de Thompson, finalmente cuando la idea correcta emergió, fue cosa de una hora o algo más implementarla.

El advenimiento de la PDP-11

Para el comienzo de 1970, el PDP-7 Unix era un asunto en marcha. Primitivo para los estandares de hoy en día, era aún capaz de proveer un ambiente de programación mas gratificante que sus alternativas. De cualquier manera estaba claro que el PDP-7, una máquina que ni siquiera poseíamos, ya era obsoleta y sus sucesoras en la misma línea ofrecían poco interés. Tempranamente en 1970 propusimos la adquisición de una PDP-11, que acababa de ser introducida por Digital. En algún sentido nuestra propuesta era simplemente la última en una serie de intentos que habíamos estado haciendo todo el año precedente. Difería en dos importantes cuestiones. Primero, el monto de dinero (cerca de $65,000) era un orden de magnitud menor de lo que previamente habiamos solicitado; segundo,la propuesta no era escribir algún (no especificado) sistema operativo, sino en lugar de ello crear un sistema diseñado específicamente para editar y formatear texto; lo que hoy podríamos llamar un "sistema de procesamiento de texto". Los ímpetus para la propuesta vinieron mayormente de J.F.Ossanna, quien entonces y hasta el fin de su vida estuvo interesado en procesamiento de textos. Si nuestras primeras propuestas eran muy vagas, esta era tal vez demasiado específica; pero en un principio también fue mirada con disconformidad. Luego de mucho, de cualquier forma, los fondos fueron obtenidos a través de L.E. McMahon y una orden para una PDP-11 fue puesta en Mayo.

El procesador arribó a fin del vernao, pero el PDP-11 era un equipo tan nuevo que no habría disco disponible hasta Diciembre. Entretanto, una versión rudimentaria y sólo-núcleo de Unix fue escrita utilizando un 'cross-assembler' en la PDP-7. La mayor parte del tiempo, la máquina estuvo en una esquina, enumerando todas las vueltas cerradas de un caballo en un tablero de ajedrez de 6x8; un trabajo que duró tres meses.

El primer sistema PDP-11

Una vez llegado el disco, el sistema fue rápidamente completado. En estructura interna, la primera versión de Unix para el PDP-11 representó un avance relativamente menor respecto del sistema del PDP-7; escribirlo fue casi por completo un trabajo de transliteración. Por ejemplo, no había multi-programación; solamente un único programa de usuario estaba presente en el núcleo en cualquier momento. Por otro lado, hubo importantes cambios en la interfaz con el usuario: la estructura de directorios presente, con rutas de acceso completas, ya estaba ahí, junto con las formas modernas de exec y wait, asimismo como ciertas conveniencias como caracteres 'erase' y procesos 'line-kill' para las terminales. Pero la cosa más interesante de todo el proyecto era su reducido tamaño: había 24K de memoria de núcleo (16K para el sistema, 8K para programas de usuario), y un disco con bloques de 1K (512Kbytes). Los archivos estaban limitados a 64Kbytes.

Al momento de elevar la orden para la PDP-11, pareció natural o al menos expeditivo, prometer un sistema dedicado al procesamiento de textos. Durante el postergado arribo del hardware, la creciente utilidad del sistema Unix de la PDP-7 justificó la creación del Unix para la PDP-11 como herramienta de desarrollo, para ser utilizada en escribir el sistema más específico. Para la primavera de 1971, estaba claro que nadie tenía interés en fragmentar Unix. De cualquier manera, nosotros transliteramos el formateador de textos roff en lenguaje ensamblador de la PDP-11, comenzando con la versión para la PDP-7 que había sido transliterada de la versión BCPL de McIlroy en Multics, la cual en última instancia estaba inspirada por el programa runoff de CTSS. A comienzos del verano, con el editor y el formateador disponibles, nos preparamos para llenar los requisitos del pedido ofreciendo suplir un servicio de procesamiento de textos al departamento de Patentes, para preparar aplicaciones de patentes. En ese momento, ellos estaban evaluando un sistema comercial para ese fin; las principales ventajas que ofrecíamos nosotros (además de la dudosa ventaja de tomar parte de un experimento 'in-house') fueron dos: primera, soportabamos terminales Teletype model 37, las cuales con una extensión podían imprimir la mayoría de los símbolos matemáticos que necesitaban; segunda, rápidamente añadimos en roff la posibilidad de producir páginas numeradas por línea, lo cual era un requisito de la Oficina de Patentes que el otro sistema no podía cumplir.

Durante la última mitad de 1971, tuvimos tres tipeadores del departamento de Patentes, quienes pasaban el día tipeando, editanto y formateando aplicaciones de patentes, y mientras tanto nosotros intentábamos realizar nuestro trabajo. Unix tiene una reputación de brindar servicios más que interesantes en hardware modesto, y este período podría marcar un hito en la tasa beneficio/equipamiento; en una máquina sin protección de memoria y con un único disco de 0.5 MB, cada prueba de un nuevo programa requería cuidado y atención, debido a que podía 'crashear' al sistema, y luego de pocas horas de trabajo de los tipeadores había que trasladar la información al DECtape, debido a la pequeña capacidad del disco.

El experimento fue estresante pero exitoso. No solamente el departamento de Patentes adoptó Unix, y entonces se volvió el primero de muchos grupos en los Laboratorios en ratificar nuestro trabajo, sino que también logramos suficiente credibilidad para convencer a nuestra jefatura de adquirir uno de los primeros sistemas PDP11/45 que salieron al mercado. Habíamos acumulado mucho hardware desde entonces, y trabajado continuamente en el software, pero debido a que mucho del trabajo más interesante ya había sido publicado, (ej. sobre el sistema mismo [1,5,6,7,8,9]) parece innecesario repetirlo aquí.

Pipes

Una de las contribuciones más ampliamente admiradas de Unix a la cultura de los sistemas operativos y lenguajes de comandos es la 'pipe' [tubería], como se usa en una línea de comandos 'pipelined' [entubados]. Por supuesto, la idea fundamental no era nueva; la pipe es simplemente una forma especial de corutina. Incluso su implementación no fue un hecho sin precedentes, pese a que nosotros no lo supimos en ese momento; los 'communication files' del Sistema de Tiempo Compartido Dartmouth [10] hicieron tempranamente lo que las pipes de Unix hacen, aunque no parecen haber sido explotados tan profusamente.

Las pipes aparecieron en Unix en 1972, antes de que la versión del sistema para la PDP-11 estuviera funcional, por la sugerencia (o tal vez insistencia) de M.D. McIlroy, un antiguo entusiasta del flujo de control no jerárquico que caracteriza corutinas. Algunos años antes de que las pipes fueron implementadas, él sugirió que los comandos deberían ser pensados como algún tipo de operadores binarios, cuyos operando izquierdo y derecho especificasen la entrada y salida. Entonces una utilidad de copiado sería ejecutada así

   inputfile copy outputfile

Para hacer una tubería, podían encadenarse los operadores. Entonces, para ordenar una 'entrada' con el comando 'sort', paginarla e imprimir los resultados podríamos escribir

   input sort paginate offprint

En un sistema de hoy en día, esto corresponde a

   sort input | pr | opr

La idea, explicada una mañana en un pizarrón, nos intrigó pero no alcanzó a motivarnos en una acción inmediata. Hubo varias objeciones a la misma como estaba formulada: la notación parecía demasiado radical (estabamos muy acostumbrados a tipear "cp x y" para copiar x a y); y además tampoco veíamos cómo distinguir parámetros de un comando de los archivos de entrada o salida. Asimismo el modelo de la ejecución de un comando con una-entrada y una-salida nos parecía demasiado confinante. ¡Qué falta de imaginación!

Algún tiempo después, gracias a la persistencia de McIlroy, las pipes fueron finalmente instaladas en el sistema operativo (un trabajo relativamente simple), y se introdujo una nueva notación. Por ejemplo, la tubería de arriba pudo haberse escrito:

   sort input > pr > opr >

La idea es que siguiendo un '>' podría estar un archivo, que especificaba redirección hacia ese archivo, o un comando en el cual la salida del comando anterior era tomada como entrada. El '>' final era necesario en el ejemplo para especificar que la salida (noexistente) de opr debía ser dirigida a la consola; de otra manera el comando opr directamente no se ejecutaría; en su lugar se hubiese creado un archivo llamado opr.

La nueva característica fue recibida entusiastamente, y el término "filtro" se acuño pronto. Muchos comandos fueron cambiados para hacerlos usables en pipes. Por ejemplo, nadie había imaginado que alguien quisiera utilizar sort o pr para ordenar o imprimir la entrada estandard si no se le daban argumentos explícitos a esos comandos.

Al poco tiempo algunos problemas con la notación se volvieron evidentes. Mayormente eran temas de léxico: la cadena luego de '>' estaba delimitada por espacios en blanco de manera que para dar un parámetro a pr, en el ejemplo, uno tenía que introducir comillas:

   sort input > "pr -2" >opr>

Segundo, en un intento de alcanzar generalidad, la notación de pipes aceptaba '<' como redirección de la entrada en una manera correspondiente a '>'; esto significaba que la notación no era única. Uno podía escribir, por ejemplo,

   opr <pr<"sort input"<

o incluso

   pr <"sort input"< >opr>

La notación de pipes utilizando '<' y '>' sobrevivió sólo un par de meses; fue reemplazada por la actual que utiliza un operador único para separar componentes en una línea de entubados. Pese a que la vieja notación tenía un cierto estilo y consistencia, la nueva es ciertamente superior. Por supuesto, también tiene sus limitaciones. Es claramente lineal, pese a que hay situaciones en las cuales múltiples entradas redirigidas y salidas son llamadas. Por ejemplo, ¿cuál es la mejor manera de comparar la salida de dos programas? ¿Cuál es la notación apropiada para invocar a un programa con dos salidas paralelas?

Mencioné arriba en la sección de redirección de IO que Multics proveía un mecanismo por el cual los flujos de IO podían ser redirigidos a través de módulos en camino hacia (o desde) el dispositivo o archivo sirviendo de fuente o sumidero. Entonces parecería que la división de flujos en Multics fue el precursor directo de las tuberías de Unix, dado que la redirección de IO en Multics estaba para su versión Unix. De hecho no pienso que esto sea cierto, ni siquiera en algún limitado sentido. No solamente las corutinas eran ya en ese momento bien conocidas sino que su incorporación como módulos de Multics requirió que los mismos sean especialmente escritos de una manera en que no podían ser utilizados para otro propósito. La genialidad de las tuberías de Unix es que precisamente están construídas con los mismos comandos que uno utiliza constantemente de forma sencilla. El salto mental necesario para ver esta posibilidad y para inventar la notación es uno muy grande.

Lenguajes de alto nivel

Cada programa para el sistema Unix original de la PDP-7 estaba escrito en lenguaje assembler, y uno realmente básico de modo que, por ejemplo, no había macros. Asimismo no había cargador ni editor de links, de manera que cada programa debía completarse a si mismo. El primer lenguaje interesante que apareció fue una versión del TMG de McClure [11] que fue implementada por McIlroy. Pronto, luego de que TMG estuviera disponible, Thompson decidió que no podía pretender ofrecer un servicio de computación real sin Fortran, de forma que se sentó a escribir un Fortran en TMG. Tal como yo lo recuerdo, el intento de manejar Fortran duró una semana. Lo que él produjo en su lugar fue una definición de un compilador para el nuevo lenguaje B [12]. B estaba muy influenciado por el lenguaje BCPL [13], siendo otras influencias el gusto de Thompson por las sintaxis espartanas, y el reducido espacio en el cual el compilador tenía que entrar. El compilador producía código interpretado simple; pese a que éste y los programas que producía eran bastante lentos, hacían la vida mucho más placentera. Una vez que interfaces para las llamadas regulares del sistema estuvieron disponibles, comenzamos otra vez a disfrutar de los beneficios de utilizar un lenguaje razonable para escribir lo que usualmente podríamos llamar "programas de sistema": compiladores, ensambladores, y cosas como esas. (Pese a que alguien podría considerar que el PL/I, que usábamos sobre Multics, era algo no razonable, era mucho mejor que el lenguaje assembler.) Entre otros programas, el compilador cruzado B de la PDP-7 para la PDP-11 fue escrito en B, y con el curso del tiempo, el compilador B para el PDP-7 mismo fue transliterado de TMG en B.

Cuando la PDP-11 arribó, B se le incorporó inmediatamente. De hecho, una versión del programa multiprecisión "desk calculator" (dc) fue uno de los primeros programas en correr sobre la PDP-11, bien antes de que el disco llegase. Sin embargo, B no se extendió inmediatamente. Sólo fue una solución de momento para reescribir el sistema operativo en B en lugar de en assembler, y lo mismo fue cierto para muchas de las utilidades. No obstante el assembler mismo fue reescrito en assembler. Se tomó este enfoque debido mayormente a la lentitud del código interpretado. De pequeña pero aún real importancia fue el desfasaje entre el lenguaje B, orientado a palabras y la PDP-11 direccionada en bytes.

Entonces, en 1971, comenzó el trabajo de lo que se convertiría en el lenguaje C[14]. La historia de su desarrollo se cuenta en otros lugares [15], y no necesita repetirse aquí. Tal vez el hito más importante ocurrido durante 1973 fue cuando el kernel del sistema operativo fue reescrito en C. Fue en ese punto que el sistema asumió la forma que tiene hoy en día; el cambio de mayor alcance fue la introducción de la multi-programación. Hubo pocos cambios visibles externamente, pero la estructura interna del sistema se volvió mucho más racional y general. El éxito de este esfuerzo nos convenció de que C era útil como herramienta universal de programación de sistemas, en lugar de un simple juguete para aplicaciones sencillas.

Hoy en día, el único programa importante de Unix aún escrito en assembler es el assembler mismo; casi todos los programas utilitarios están escritos en C, así como la mayoría de las aplicaciones, pese a que existen sitios con muchos en Fortran, Pascal, y también Algol 68. Parece que mucho del éxito de Unix se desprende de su legibilidad, modificabilidad y portabilidad de su software que a su vez proviene de su expresión en lenguajes de alto nivel.

Conclusión

Una de las cosas reconfortantes respecto de los viejos recuerdos es su tendencia a tomar un tinte rosado. El ambiente de programación provisto por las primeras versiones de Unix parece, cuando es descrito aquí, ser extremadamente hostil y primitivo. Estoy seguro de que si me veo forzado a volver a una PDP-7 lo encontraría intolerablemente limitante y carente de muchas comodidades. De cualquier manera, no era esa la impresión en aquellos días; la memoria se queda en lo bueno y lo que perduró, y la diversión de ayudar a crear las mejoras que hicieron la vida más sencilla. En diez años, espero que podamos mirar hacia atrás con la misma impresión mezclada de progreso y continuidad.

Agradecimientos

Estoy agradecido con S.P. Morgan, K. Thompson y M.D. McIlroy por haberme provisto documentos y desenterrar memorias.

Debido a que estoy más interesado en describir la evolución de las ideas, este artículo atribuye ideas y trabajo a personas solamente cuando parece más importante. El lector no se equivocará, en general, si lee cada ocurrencia de "nosotros" con antecedente no claro como "Thompson, con alguna asistencia mía."

Referencias

1. D. M. Ritchie and K. Thompson, ‘The Unix Time-sharing System, C. ACM 17 No. 7 (July 1974),pp 365-37.
2. L. P. Deutch and B. W. Lampson, ‘SDS 930 Time-sharing System Preliminary Reference
Manual,’ Doc. 30.10.10, Project Genie, Univ. Cal. at Berkeley (April 1965).
3. R. J. Feiertag and E. I. Organick, ‘The Multics input-output system,’ Proc. Third Symposium on
Operating Systems Principles, October 18-20, 1971, pp. 35-41.
4. The Multiplexed Information and Computing Service: Programmers’ Manual, Mass. Inst. of
Technology, Project MAC, Cambridge MA, (1969).
5. K. Thompson, ‘Unix Implementation,’ Bell System Tech J. 57 No. 6, (July-August 1978), pp.
1931-46.
6. S. C. Johnson and D. M. Ritchie, Portability of C Programs and the Unix System,’ Bell System
Tech J. 57 No. 6, (July-August 1978), pp. 2021-48.
7. B. W. Kernighan, M. E. Lesk, and J. F. Ossanna. ‘Document Preparation,’ Bell Sys. Tech. J., 57
No. 6, pp. 2115-2135.
8. B. W. Kernighan and L. L. Cherry, ‘A System for Typesetting Mathematics,’ J. Comm. Assoc.
Comp. Mach. 18, pp. 151-157 (March 1975).
9. M. E. Lesk and B. W. Kernighan, ‘Computer Typesetting of Technical Journals on Unix,’ Proc.
AFIPS NCC 46 (1977), pp. 879-88.
10. Systems Programmers Manual for the Dartmouth Time Sharing System for the GE 635 Computer,
Dartmouth College, Hanover, New Hampshire, 1971.
11. R. M. McClure, ‘TMG--A Syntax-Directed Compiler,’ Proc 20th ACM National Conf. (1968),
pp. 262-74.
12. S. C. Johnson and B. W. Kernighan, ‘The Programming Language B,’ Comp. Sci. Tech. Rep. #8,
Bell Laboratories, Murray Hill NJ (1973).
13. M. Richards, ‘BCPL: A Tool for Compiler Writing and Systems Programming,’ Proc. AFIPS
SJCC 34 (1969), pp. 557-66.
14. B. W. Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall, Englewood
Cliffs NJ, 1978. Second Edition, 1979.
15. D. M. Ritchie, S. C. Johnson, and M. E. Lesk, ‘The C Programming Language,’ Bell Sys. Tech. J.
57 No. 6 (July-August 1978) pp. 1991-2019.
Copyright © 1996 Lucent Technologies Inc. All rights reserved.


Artículo original

El artículo original, en idioma inglés, puede encontrarse en:

D. M. Ritchie, "The Evolution of the Unix Time-sharing System" (September 1979), Bell Labs. 1996. Lucent Technologies Inc.


E. Lavia
Ultima actualización: 28-Nov-2009