La serie “El computador mágico” está disponible también en forma de libro. |
Hemos dedicado los últimos capítulos de esta serie a ver unos ordenadores muy sencillitos, pero que capturaban la esencia de los ordenadores modernos. Como te imaginarás, los ordenadores reales son bastante más complejos, así que vamos a dedicar unas palabras a dar algunas pinceladas sobre las diferencias.
A diferencia de los capítulos anteriores, este será de mucho blah-blah-blah, mucha batallita. No creas, no obstante, que se trata de un capítulo trivial: algunas de las cosas que veremos serán importantes para los siguientes capítulos. De hecho, algunas de las cosas que veremos solo las nombraremos someramente, porque su importancia solo quedará patente cuando veamos los sistemas operativos más adelante. Eso quiere decir que si alguno de los párrafos te resulta un poco… inútil… como si no supieras muy bien por qué eso es importante… no te preocupes mucho, que profundizaremos en ello en el futuro.
En el dibujo de cabecera puedes ver el diagrama del 8051 de Atmel. Los 8051 (y sus variantes) son unos de los microcontroladores más utilizados en sistemas empotrados, en pequeños proyectos amateur y de investigación, porque es baratísimo (alrededor de 1€) y tiene una comunidad de usuarios muy grande detrás.[1]
El 8051 fue un microcontrolador diseñado por Intel en 1980. Es decir, que no es lo más de lo más en tecnología punta, pero ya es significativamente más complejo que los sistemas C16A, C16B y C16C que hemos visto nosotros. Así que no te digo nada si lo que intentáramos fuera estudiar un ordenador moderno, como los Intel Core i7…
¿Podemos ver algunas diferencias? Algunas ya las hemos ido viendo cuando veíamos la memoria o las interrupciones. Otras son solamente diferencias cuantitativas, como el tamaño de los buses y los registros; esas vamos a obviarlas, porque no suponen un cambio de concepto, simplemente tiene un tamaño distinto. Vamos a ver las diferencias que sí sean importantes.
Para empezar, es muy habitual que hoy en día ya no hablemos de un ordenador como un bloque monolítico, sino que se suelen comprar los componentes por separado: por un lado la CPU, por otro lado la memoria, por otro lado los dispositivos de entrada/salida… pero bueno, supongo que eso ya lo sabías, a estas alturas del siglo. Así que vamos a centrarnos en las diferencias más arquitecturales.
Las ALUs reales tienen muchísimas más operaciones que las que hemos visto aquí y las CPUs reales tienen también muchísimas más instrucciones.[2] Nosotros hemos tenido que hacer una subrutina para hacer la operación “multiplicar”, pero no te sorprenderá que eso sea una instrucción que venga de serie en cualquier CPU que se precie. Por ejemplo, el 8051 de más arriba tiene en torno a un centenar de instrucciones, y ya estamos hablando de una CPU antigua. Las CPUs más modernas que esa tienen operaciones para coma flotante, operaciones multimedia, gestión de otros componentes de la CPU… Nosotros hemos visto cómo hacer la multiplicación era una operación muy habitual, y hacerla a base de una subrutina que haga operaciones más simples es un incordio, de modo que parece que proporcionar instrucciones que hagan operaciones más complejas parece buena idea. Bien, pues sí lo es. Y cuantas más, mejor.
¿No?
Pues no está tan claro. Aquí hay dos corrientes distintas, y cada una tiene sus ventajas. Por un lado tenemos la corriente CISC (Complex Instruction Set Computer, computador con juego de instrucciones complejo) y por otro tenemos RISC (Reduced Instruction Set Computer, computador con juego de instrucciones reducido). La corriente CISC, de la cual los procesadores Intel que usamos en nuestros ordenadores son probablemente el mayor exponente, pretende que la mayor cantidad posible de instrucciones se hagan en el nivel más bajo posible, de modo que puedan optimizarse y hacerse muy rápidas, mucho más rápidas que hacer subrutinas que hagan lo equivalente. Por ejemplo, nuestro editor Macluskey recuerda que uno de los ordenadores con los que él ha trabajado tenía una instrucción ensamblador que hacía la ordenación (SORT).[3]
La corriente RISC, de la cual el mayor exponente ahora mismo serían los procesadores ARM que llevan casi todos los teléfonos móviles actuales, propugna en cambio que las instrucciones que ofrezca la CPU sean muy muy sencillas, pero muy muy eficientes. Puede parecer que los diseñadores de sistemas CISC no van a ser imbéciles y a hacer las instrucciones menos eficientes que los que diseñan RISC… pero no es eso lo que quiere decir. Lo que ocurre es que al reducir no solo la cantidad de instrucciones, sino también su tipo, se pueden aplicar optimizaciones que serían imposibles en CISC. Por ejemplo, en tecnología RISC se intentan evitar las instrucciones que operan con la memoria: solo las instrucciones que cargan y guardan cosas en memoria deben acceder a memoria; el resto de operaciones deben operar exclusivamente sobre los registros de la CPU (el equivalente a nuestro acumulador). El caso es que, efectivamente, con esas restricciones parece que es más fácil hacer las instrucciones sencillas más eficientemente.[4] Al final ocurre que los procesadores RISC modernos tienen más instrucciones que los CISC antiguos, de modo que la R de Reduced hay que entenderla con cuidado: no es tanto que tenga pocas instrucciones como que dichas instrucciones sean muy sencillas.
Es esta una pelea que no está resuelta, y que además probablemente no se resolverá a medio plazo, pues obviamente cada una tiene sus aplicaciones.
Otra diferencia importante entre los ordenadores reales y nuestro C16C es la memoria. Nosotros hemos proporcionado una memoria principal y una memoria interna con la que trabajar (el acumulador). Pero las CPUs reales tienen:
- Muchos más registros internos. Parecidos a nuestro acumulador, pero muchos más.
- La lectura y escritura en la memoria no suelen tardar un tick de reloj, sino muchos más.
- Para paliarlo, se suele introducir uno o más niveles de memoria intermedios entre la CPU y la memoria RAM, a los que se suele llamar memoria caché. Esta memoria caché es mucho más rápida que la memoria principal… y más cara, claro, y por eso suele ser muuucho más pequeña. Para lo que sirve es para almacenar temporalmente en ella las posiciones de memoria a las que se está accediendo muy a menudo, para que esos accesos sean más rápidos. Desde el punto de vista del programa, esta memoria caché suele ser transparente, en el sentido de que no se accede a ella directamente, sino como si fuera parte de la memoria principal. Si da la casualidad de que lo que buscamos está en esta caché, será muy rápida… pero desde el punto de vista del programa no hay diferencia.
La tercera diferencia importante está en el acceso a los dispositivos de entrada/salida. En nuestro C16C los dispositivos de entrada/salida estaban directamente conectados al bus de memoria. Es decir, escribir en la pantalla era exactamente igual que escribir en la memoria. Pero en algunos sistemas (en particular en los procesadores Intel que casi todos tenemos en casa) existen dos buses A (y consecuentemente dos buses D): uno para la memoria principal y otro para la entrada/salida. Es decir, simplificándolo, tendría una arquitectura como la siguiente:
Fíjate en que ahora tenemos un bus DM/AM (la M es de Memory, memoria) y otro bus DIO/AIO (IO es de Input-Output, entrada-salida). Lógicamente, donde antes había instrucciones ST y LD, ahora tiene que haber STM, STIO, LDM y LDIO. Ah, y un detalle más: si decíamos que el acceso a memoria no ocupaba un tick de reloj, sino muchos, el acceso a dispositivos de I/O ocupa muchísimos más. Si esos dispositivos de entrada salida implican movimientos físicos (como por ejemplo rebobinar una cinta magnética o mover el cabezal de un disco duro), el tiempo puede ser varios órdenes de magnitud mayor que un tick de reloj (esto tendrá implicaciones más adelante, cuando veamos cómo lo abordan los sistemas operativos).
La forma en que se accede a memoria es una cuarta diferencia, que no hemos visto hasta ahora. Como no afecta a cómo funciona el computador, solo vamos a nombrarlas por completitud (es decir, que si no entiendes muy bien lo que significan, no te obsesiones, que no es importante para seguir). Hasta ahora, cuando en nuestras instrucciones poníamos <addr>, significaba que allí estaba el dato con el que queríamos operar. Fácil. Es lo que se llama direccionamiento directo. Pero existen más formas de direccionamiento:
- Direccionamiento indirecto: la dirección a la que hace referencia el parámetro de la instrucción no contiene el valor con el que operar, sino la dirección en donde está el verdadero valor con el que operar. Si has programado en C (o en algún otro lenguaje de bajo nivel) esto es simplemente un puntero.
- Direccionamiento indexado: la dirección efectiva en donde está el valor con el que operar se obtiene sumando la posición base más un índice. Es decir, es una forma de recorrer arrays.
- Direccionamiento relativo: la dirección efectiva del operando se obtiene sumando la dirección en donde está la instrucción más el parámetro proporcionado (que podría ser negativo). Este mecanismo se puede usar para hacer código que se pueda mover de una posición de memoria a otra sin que deje de funcionar.
Existe una última diferencia importante respecto al tratamiento de la memoria: la MMU (Memory Management Unit, unidad de gestión de memoria). Pero esa no la veremos hasta que no lleguemos a la parte de gestión de memoria de los sistemas operativos, porque si intento explicarla ahora solo va a confundirte.
La última diferencia importante se refiere al modo de ejecución. Supongo que estás acostumbrado a que tu ordenador tenga cierta separación de privilegios. Probablemente conoces lo que es el usuario Administrador (en Windows) o root (en Unix), y sabes que puede haber más usuarios. Y sabes que (a menos que haya un bug) un usuario no puede acceder a las cosas de otro. ¿Cómo puede hacer eso el ordenador? Con lo que hemos visto hasta ahora, una vez que se está ejecutando un programa, parece que tiene acceso a todo, que puede ejecutar todas las instrucciones… en particular, podría ejecutar instrucciones que accedan a los datos de otro usuario. ¿No? Pues no. La mayor parte de los sistemas modernos tienen algún mecanismo de separación de privilegios, de modo que, cuando la CPU está en modo normal, hay muchas instrucciones que no puede ejecutar. Si el programa que está ejecutando contiene esas instrucciones, dará un fallo o simplemente las ignorará. Y si está en modo privilegiado, entonces sí, puede ejecutarlas todas.
- Ejemplos de instrucciones “privilegiadas”: gestión de la memoria, de acceso a los dispositivos de entrada-salida, de configuración de la pila, de configuración de las interrupciones… Una muy importante es precisamente “salir del modo privilegiado”.
- Ejemplos de instrucciones “normales”: operaciones aritméticas, operaciones lógicas, lectura y escritura de memoria (pero solo de determinadas zonas de memoria), saltos, subrutinas… y poco más. Ah, sí, hay una muy importante: la interrupción software.
Vamos a presentar la interrupción software muy someramente, porque este es el lugar que corresponde, pero más adelante lo veremos en detalle. La interrupción software es como una interrupción de las que hemos visto antes, con la única diferencia de que no se dispara por un evento del hardware, sino porque se ejecuta la instrucción SWIRQ (por ponerle un nombre). Una vez que salte esa interrupción, se interrumpirá la ejecución de lo que esté haciendo, como siempre que hay una interrupción, y saltará a una posición de memoria conocida y pasará al modo privilegiado. La gracia es que quien reside en esa posición de memoria es el sistema operativo (que veremos más adelante), y configurar esa posición de memoria es una instrucción privilegiada, así que no puede haberlo hecho el usuario normal, tiene que haberlo hecho el propio sistema operativo durante su inicialización.
Al juntar ambas cosas, sabemos que tenemos dos modos: el modo normal, que es como se ejecutan todos los programas de usuario. Y el modo privilegiado, que es como se ejecuta el sistema operativo. Cuidado, porque este modo privilegiado no es el usuario Administrador o el usuario root. En ambos casos tanto root como Administrador ejecutan en modo normal. Lo del modo privilegiado es aún más profundo que eso. Ni siquiera el usuario Administrador puede ejecutar las instrucciones privilegiadas directamente, tiene que hacerlo a través del sistema operativo.
El caso es que estamos continuamente con el sistema operativo para acá, el sistema operativo para allá… hombre, lo he hecho porque a estas alturas de la película seguro que sabes lo que es un sistema operativo (Windows, Linux, Android, Mac, iOS… son nombres de sistemas operativos). Pero seguro que muchas de las cosas que vamos nombrando te quedan un poco en el aire. Pues a eso dedicaremos los siguientes capítulos: al sistema operativo.
- Aunque en los últimos años los sistemas basados en Arduino están tomando mucho empuje. [↩]
- Además, como ya mencionamos en capítulos anteriores, en los sistemas modernos esas instrucciones están microprogramadas. No obstante, eso no afecta al nivel al que estamos viendo los ordenadores, así que lo vamos a obviar. [↩]
- NdE: En concreto son las CPU’s de los modernos mainframes de IBM, que tienen entre su juego de instrucciones una concreta para hacer un “Insertion Sort”. [↩]
- Cuidado de todos modos: en esta discusión, la multiplicación y cosas así siguen siendo “instrucciones sencillas”. [↩]
The Computador mágico XXVIII – Ordenadores reales by , unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 2.5 Spain License.
{ 1 } Comentarios
Que sepas que me sigue pareciendo muy interesante todo lo que cuentas y que sigo esperando la siguiente entrada.
Escribe un comentario