martes, 21 de junio de 2011

Manual básico del fotógrafo (I) -Técnicas generales

    Los que me conocen saben muy bien que me apasiona la fotografía. Los comienzos, como en cualquier otra disciplina, son difíciles. El modo automático de la cámara piensa un montón de parámetros por ti, y soluciona "la papeleta" de forma mas o menos acertada en la mayoría de los casos. Pero basta con poner el modo manual y enfrentarte a la elección de todos esos parámetros, para darte cuenta de que no es una tarea trivial. La cámara, los ajusta siguiendo unos algoritmos generales. Lo que pretende, es tener una buena iluminación, con una buena profundidad de campo, con una buena velocidad de obturación. Al menos, un nivel aceptable en cada uno de ellos. Y esto esta muy bien cuando no queremos realizar fotos artísticas, o cuando estamos haciendo fotos con una calidad de iluminación excelente (básicamente, o durante el día, o en un estudio). Desde el primer día me planteé a mi mismo que el modo manual no podía ser tan difícil, y aunque las primeras semanas me costó lo suyo, en seguida agradeces el poder controlar la escena a tu gusto, al milímetro. Otro tema, muy distinto, será el tener la creatividad o no de saber buscar una buena foto. Pero eso, lo obviaremos del presente blog, por dos motivos: porque no es el objetivo del mismo, y porque no estoy muy seguro de tener grandes dosis de creatividad :)

    Empecemos definiendo una serie de términos, con los que los fotógrafos se entienden. Y ya que pretendemos ser uno de ellos, no es mala idea familiarizarnos con su "jerga".


  • Diafragma. Es un elemento mecánico que se situa entre el elemento sensible a la luz (película fotográfica o sensor electrónico) y el elemento óptico, permitiendo graduar la cantidad de luz que penetra hacia el primero. Además, se producen otros fenómenos asociados, como son la manipulación de la profundidad de campo a causa de la colimación de la luz, o la merma de la nitidez en la imagen con aperturas pequeñas, a causa de la difracción
  • Apertura. Es la cantidad de diafragma que hemos abierto. Se mide en "números f". A menor número f, mayor apertura, y viceversa. Como ya se ha indicado, esto afecta a la luminosidad y a la profundidad de campo.
  • Número "f". Expresa la cantidad de diafragma que hemos abierto. El número f expresa la relación entre la longitud focal y el diámetro abierto, y no el diámetro directamente. Esto quiere decir, que si variamos el zoom, haciendo que la distancia focal varíe, tendremos que manipular la apertura del diafragma para mantener el mismo número f. Afortunadamente, esto ya lo hacen las ópticas mecánicamente, de modo que no hay problema alguno en manipular el zoom. Una vez escogido un número f, se mantiene.
  • Profundidad de campo. Es la profundidad, desde el punto de vista del objetivo, que es capaz de enfocar de forma nítida. Es decir, imaginemos una escena con tres planos bien diferenciados: primer plano, plano de escena, y plano de fondo. Si tenemos una muy buena profundidad de campo, seremos capaces de enfocar nítidamente un objeto que esta a, por ejemplo, un metro de la cámara (primer plano), un objeto a 6 metros de la cámara (escena que queremos retratar), y un objeto situado a 40 metros (plano de fondo). Si por el contrario tenemos una profundidad de campo pobre, solo seremos capaces de enfocar o el primero, o el de escena, o el del fondo, o una combinación de dos, pero no los tres. Si ya la profundidad de campo es malísima, solo conseguiremos uno de los tres. Cuidado, que la profundidad de campo sea "mala", en realidad es una forma de hablar. En algunas escenas creativas, quizás interese tener una mala profundidad de campo.
  • Longitud focal. La longitud focal está relacionada con la cantidad de zoom que queremos aplicar. Una longitud focal grande, indica un zoom poderoso, por el contrario, una longitud focal pequeña, indica un zoom pobre, o incluso un gran angular, que sería todo lo contrario. Es importante referenciar siempre esta medida a una cámara de 35mm. Según el tamaño del elemento sensible a la luz (si es analógica, siempre será una película de 35mm), las longitudes focales varían. Por ejemplo, no es lo mismo 100mm de longitud focal para una óptica de un sensor CCD de media pulgada, que para un sensor "completo" de 35mm.
  • Obturador. Este elemento se encarga de hacer pasar la luz hacia el elemento sensible a la luz (sensor o película fotográfica) el tiempo estipulado. Si elegimos una velocidad de obturación muy alta, el mismo permanecerá abierto muy poco tiempo, dejando pasar muy poquita cantidad de luz. Por el contrario, con velocidades bajas, el tiempo que permanece abierto será mayor, dejando pasar, por tanto, mas cantidad de luz. Este elemento maneja por tanto dos parámetros: la cantidad de luz que deja pasar (junto con el diafragma, son los dos elementos que regulan la cantidad de luz que pasa hacia el sensor o la película fotográfica); y el tiempo que permanecerá abierto captando la escena. Esto es importante, porque si la escena se mueve muy rápidamente, y seleccionamos una velocidad de obturación muy baja, puede ocurrir que el mismo objeto en movimiento se inmortalice en dos zonas distintas del elemento fotosensible. Esto da lugar a los llamados "fantasmas". En una escena deportiva, por ejemplo, esto es un problema, ya que se pretende fotografiar al futbolista realizando un remate de cabeza en pleno vuelo (por poner un caso). Aquí no queda otra que usar velocidades altas y compensar la falta de luz con más apertura de diafragma. En otros casos este efecto "fantasma" no es un problema, sino que por el contrario se convierte en un recurso artístico. En este caso, hablamos del efecto "seda". Seguro que todos recordamos las típicas fotos de cascadas donde el agua forma un "manto" uniforme, y no se distinguen las gotas, sino que todo pasa a ser un "continuo".
  • Velocidad de obturación. Esta unidad indica, mediante una fracción de segundo, el tiempo que va a estar abierto el obturador. Si elegimos un valor de, por ejemplo, 1/25, significa que durante 1/25 segundos el obturador estará abierto. Por otro lado, si elegimos un valor de 1/100, estaremos abriendo durante menos tiempo que con 1/25, es decir, es una velocidad mayor, y un tiempo menor. En definitiva, el "truco" aquí es, a menor número, más luz y más "fantasmas" o "seda", según el caso, a mayor número, menos luz y mas estática será la imagen.
  • Flash. Bueno, no hay mucho que añadir sobre este elemento. Como todo el mundo sabe, acumula energía eléctrica durante un instante, y la suelta de golpe sobre una lámpara, produciendo un fogonazo muy corto, pero de una gran intensidad. Esta carga de energía eléctrica es la que hace que haya que esperar un poco entre cada descarga de flash. Sobre el flash, yo siempre digo una cosa: úsalo para fotografías periodísticas, ni se te ocurra usarlo para fotografías artísticas. Es muy cómodo usarlo cuando solo queremos una imagen de algún evento fugaz, y no queremos o no podemos perder tiempo en la configuración manual de la cámara. Pero si buscamos una foto "bonita", una foto artística, si se os ocurra usarlo, vuestra foto perderá toda su fuerza.

    Ahora, ya podemos hablar con los mismos términos que un señor fotógrafo. Y aprovechando que hablamos un lenguaje común, podemos ahora dedicarnos a comentar diferentes formas de hacer una foto, así como criterios generales:

  • Con respecto a situaciones de poca luz. Hay diferentes formas de conseguir que capte más luz nuestra cámara, con independencia de la sensibilidad de nuestra "película" (entiéndase como sensor o película fotosensible, según el caso). Se puede elegir un número f menor, a costa de la profundidad de campo. Podemos elegir una velocidad de obturación baja (es decir, un tiempo de exposición largo), a costa de la nitidez y de que puedan aparecer efectos "fantasmas". La conclusión es, que si queremos conseguir que entre mucha luz, tenemos que sacrificar algo. Y con tanto parámetro que afecta a tantas cosas, es hora de que vayamos poniendo cotas. Si vamos a hacer una fotografía sin trípode, la primera cota la marca nuestro pulso. Si ponemos un tiempo de exposición largo, los movimientos involuntarios de nuestro brazo producirán pérdidas de nitidez en el mejor de los casos,  fantasmas en el peor de los casos. Un valor estándar que marca hasta dónde podemos llegar con nuestro pulso es 1/25. Más tiempo supone una merma en la calidad de la foto. De modo que si queremos ganar luminosidad, podemos fijar este valor en 1/25 (también es cierto que si te apoyas en un muro y te concentras, puedes conseguir buenos resultados incluso a 1/15 y 1/10, pero aun así, aunque consigamos evitar los fantasmas, hay una leve pérdida de nitidez que no agrada). Por otro lado, abrir el diafragma a tope puede parecer una solución, pero no siempre podemos permitirnos ese lujo, y no siempre se obtienen buenos resultados. Un número f muy bajo (es decir, una gran apertura del diafragma) nos va a dar, no solo una pobre profundidad de campo (que puede que para nuestra escena no sea adecuada), sino que además, la nitidez se va a ver afectada incluso en la zona con "buena" profundidad de campo. En las ópticas antiguas, se solía tomar un valor de f:8 como bueno, en cuanto a la nitidez, pero hoy día se pueden asumir valores de f:5 y f:6 sin incurrir en defectos de calidad. La conclusión cuando se esta intentando hacer fotos con pobres condiciones de luz, está claro que son: usar un trípode, y usar un número f:5 o f:6. Si no se dispone de trípode, siempre será preferible conseguir una foto sin fantasmas, aunque sea a costa de la nitidez, por lo que usaremos una velocidad de 1/25, y un número f lo más bajo que permita la óptica. Ni que decir tiene que no usaremos flash, siempre que se pueda evitar. En caso de que usemos flash, la configuración de la cámara sería la misma que con luz diurna.
  • Con respecto a situaciones de mucha luz. Muchas veces haremos fotografías en las horas centrales de luz, con el sol en nuestra misma vertical. En este caso, eligiremos una velocidad de obturación tal que permita captar con solvencia cualquier objeto en movimiento (a no ser que queramos producir un efecto seda). Por ejemplo, un valor entre 1/100 y 1/200 puede ser adecuado. Por otro lado, colocaremos el diafragma en su posición de máxima calidad cromática y de enfoque, como he dicho antes, alrededor de f:5 y f:6. Por supuesto, si con estos valores sobreexponemos (es decir, sale demasiado clara), podemos jugar un poco con los valores hasta conseguir una buena exposición, pero dando prioridad a mantener el diafragma en f:5 o f:6 ya que es donde más calidad nos dará. Por otro lado, el uso de flash es muy recomendable par solucionar un problema: las sombras. Lo bueno de hacer fotos en las horas centrales, es que no nos va a faltar luz, podemos estar tranquilos en ese aspecto. Pero por otro lado, el hecho de que el sol este casi en nuestra vertical, hará que se proyecten sombras verticales en los rostros a retratar. La imagen que produce una sombra obscura en forma de triángulo justo en el párpado inferior del ojo es sumamente desagradable. Para evitar esto, usaremos el flash para "rellenar" de luz las sombras, justo donde el sol no es capaz de arrojar luz por culpa de su posición. Si, es cierto. Los que me conocéis sabéis que odio el flash (incluso antes he aconsejado que no se use nunca). Mata los colores, y le quita todo el romanticismo a la foto, produciendo resultados planos y feos. Pero en este caso, os aseguro, el poder del sol eclipsará a nuestro flash. El flash será capaz de rellenar aquellas sombras que el sol no consiguió iluminar, pero para nada alterará el color de la escena, ya que el sol siempre será una luz mucho mas potente.
  • Con respecto a situaciones normales de luz. En estos casos, las recomendaciones son las de siempre: diafragma en f:5 o f:6, velocidad de obturación apropiada para no producir fantasmas, evitar el uso del flash en la medida de lo posible.
  • Quiero desenfocar el fondo y enfocar el motivo principal. ¿Habéis visto esos efectos tan llamativos que difuminan el fondo y enfocan perfectamente al motivo central? El truco reside en usar el diafragma a nuestro favor. Ya hemos hablado del efecto que produce su uso en la profundidad de campo, y justo ahora queremos jugar con eso a nuestro favor. El truco consiste en abrirlo todo lo necesario para que el motivo central se destaque con respecto al fondo. Si usamos una óptica con zoom, el usar longitudes focales grandes (zoom grande) conseguirá multiplicar el efecto de desenfoque. Bien, entonces, si el truco es abrir el diafragma, pues abrámoslo "a tope". Existe un problema: solo podemos abrir el diafragma hasta justo cuando el motivo principal se empieza a difuminar. ¿Que significa esto? Significa que si abrimos demasiado el diafragma, puede ocurrir que la punta de la nariz (pongamos por caso) se vea absolutamente nítida, y el ojo (que esta más alejado de la cámara) se empiece a ver también borroso (no tanto como el fondo, que esta mucho más lejos, pero si una pérdida de nitidez). El truco, por tanto, es abrir hasta justo cuando el motivo principal empieza a dejar de estar nítido. ¿Como aumentar entonces el efecto? Podemos hacer uso de la distancia focal, si disponemos de un objetivo zoom, o podemos acercarnos al motivo principal, de modo que la distancia relativa entre el primer plano y el fondo, se acentúe.
  • Quiero hacer un "contraluz". Se trata de colocar la fuente principal de luz, o una auxiliar, justo detrás del motivo a fotografiar, de tal suerte que los bordes se vean especialmente iluminados. Si usamos la fuente de luz principal, está claro que nuestro motivo no tendrá luz con la que iluminarse. En este caso, y contradiciéndome con respecto al flash, si que haremos uso del flash. No nos queda otra ya que tenemos que restituir toda la luz que hemos ocultado, al poner nuestro motivo justo delante de la fuente de luz principal (un foco en un estudio, el sol en la calle, etc).
  • Quiero conseguir un efecto seda. En este caso, aplicaremos velocidades de obturación muy bajas, es decir, tiempos de exposición largos. Si es de día, tendremos que compensar la gran cantidad de luz que nos entrará, mediante el diafragma.
    Llegados a este punto habrá más de uno que se preguntará: ¿y que hay de la sensibilidad del elemento fotosensible? Si estamos hablando de película fotográfica "de toda la vida" pues está claro que cuanto más sensible, mejor (aunque su precio será también mayor). Por otro lado, si estamos hablando de una cámara digital, el elemento que capta a luz es el sensor. Existen dos grandes grupos de sensores: CMOS y CCD, cada uno con sus virtudes y desventajas. Pero a grandes rasgos, si algo caracteriza a un sensor  fotográfico electrónico es su ruido. Tanto en uno como en otro, el nivel de sensibilidad (a fin de cuenta la ganancia del amplificador) se mide mediante el valor "ISO". Un ISO alto indica una alta sensibilidad. Por el contrario, un ISO bajo indica una baja sensibilidad. El único consejo válido con este parámetro es que cuanto menor sea el valor ISO, mejor. Sólo se seleccionará un valor mayor de ISO cuando no haya bastado con el ajuste de la velocidad de obturación y del diafragma, o cuando la escena no permita un valor más agresivo de velocidad de obturación o de diafragma.

lunes, 25 de abril de 2011

Microprocesador DLX y simulador (I - punto de vista del hardware)

Recientemente he terminado una asignatura de la titulación Ingeniero Electrónico: Arquitectura de computadores. El objetivo de la asignatura es saber diseñar, a nivel de bloques funcionales, toda la arquitectura de un microprocesador (segmentación, renombramiento dinámico de registros, ejecución fuera de orden, predicción de saltos, etc.).

Teníamos que hacer un "trabajito" relacionado con la asignatura, que sería la nota mas importante de la asignatura. Como "trabajito" elegí diseñar una implementación propia del DLX a nivel de bloques. Una vez diseñado, simularía su funcionamiento mediante un programa escrito en c++.

Se que tal y como he tratado el tema aquí, exige unos conocimientos previos básicos sobre el "mundillo" de los microprocesadores. Es cierto que debería haberme "currado" un poco más el artículo, para poder llegar a un público más general. Pero sé que los que tienen nociones básicas de microprocesadores agradecerán este artículo, porque toca algorítmos muy interesantes que son la esencia del descomunal rendimiento que ofrecen los microprocesadores hoy día (al margen de los multi-núcleos que no ofrecen nada interesante, salvo la solución por fuerza bruta de la capacidad de cómputo, mediante el "copy-paste" de cores). Añadir también, que aquí se presentan los "trucos" para ganar rendmiento a nivel de bloques. La otra gran lucha para conseguir rendmiento en los microprocesadores se realiza en la simplificación del álgebra booleana inmersa en ests algoritmos, para conseguir diseños con un menor número de puertas lógicas. Además, estaría el último frente y el de más bajo nivel: conseguir cada vez transistores más rápidos. En sucesivas entradas iré tocando éstos aspectos, al menos a un nivel básico.

Los componentes y "features" principales del microprocesador son las siguientes:

- Bus de direcciones y de datos de 32 bits.
- Aritmética de 32 bits.
- Cauce segmentado (8 etapas).
- Arquitectura Harvard (en previsión de incluir una cache L1 independiente para datos e instrucciones).
- Renombrado dinámico de registros mediante el algoritmo de Tomasulo (tres estaciones de reservas, una para cada unidad funcional: enteros, sumas/restas en coma flotante, multiplicaciones/divisiones en coma flotante, cada una con 5 entradas).
- Ejecución fuera de orden mediante un buffer de reordenamiento.
- Una unidad de ejecución para enteros.
- Una unidad de ejecución para sumas/restas en coma flotante.
- Una unidad de ejecución para multiplicaciones/divisiones en coma flotante.
- Un banco de 32 registros para enteros.
- Un banco de 32 registros de coma flotante en simple precisión, ó 16 registros de coma flotante en doble precisión.

Algunas cosas que se deberían haber incluido, y no se hicieron por falta de tiempo:

- Una segunda unidad de enteros para sumas: de esta forma se pueden realizar cálculos de direcciones en paralelo con operaciones aritméticas "normales". De esta forma se puede tener la dirección de un salto sin tener que esperar a que la unidad de enteros esté disponible. Siempre es muy importante tener las direcciones de saltos calculadas con mucha antelación, en previsión de que haya que alojar una nueva línea de caché, es decir, se produzca un fallo de caché al buscar la instrucción siguiente al salto.
- Predicción de saltos. En este caso le propuse al profesor de la asignatura la inclusión de una BHR de 2 bits y una BHT de 2 bits también. Ya hablaré más extendidamente sobre las técnicas de predicción de saltos utilizada en los microprocesadores reales (con algún ejemplo real), y como se puede implementar una versión mas "doméstica" que utilice menos recursos (y menos área de silicio).
- 'Superescalabilidad'. Es decir, duplicar algunos de los elementos del cauce, para permitir que se ejecuten dos o más instrucciones consecutivas simultáneamente (si se quiere un microprocesador puramente 'superescalar', no queda otra que duplicar cada elemento de cada etapa del cauce). De esta forma se ejecutan dos instrucciones de un mismo proceso o hilo del sistema (es decir, el contador de programa es único).
- 'HyperThreading'. Tal y como hizo Intel en sus microprocesadores, se puede mejorar el rendimiento permitiendo que un mismo cauce ejecute dos instrucciones simultáneamente. En este caso se trata de duplicar sólo algunas partes del cauce, habiendo otras compartidas. Consigue menos rendimiento que con técnicas superescalares, pero consume muchísima menos área de silicio. Y además, a diferencia de la técnica 'superescalar', ambas instrucciones pueden pertenecer a procesos o hilos del sistema diferentes. Dicho de otra forma, es como tener el doble de microprocesadores (en caso de contar con 'HyperThreading' x2), al menos de forma virtual.
- Caché L1 y L2. El microprocesador diseñado tiene una arquitectura Harvard, pensada para incluir una cache L1. En el simulador esto se simula mediante dos archivos/instancias en RAM distintas. En cualquier caso, no se ha simulado un comportamiento de cache real, ya que en el simulador, la cache L1 tiene un tamaño desmesurado, para garantizar que cabe perfectamente todo el programa. Es más, se ha omitido todo el proceso de comparación de etiquetas de líneas de cache, carga de líneas de cache nuevas, etc.

FUNCIONAMIENTO DEL MICROPROCESADOR

- Segmentación. Imaginemos por un instante que todo el procesamiento completo, es decir, fetch de la instrucción, decodificación de la misma, ejecución, operaciones de memoria (si es una instrucción de memoria) y escritura en registros de destino, se hiciera en un mismo ciclo de reloj. Está claro que es un proceso muy complejo, y que por tanto consumiría una gran cantidad de tiempo. Como consecuencia de ésto, la frecuencia de funcionamiento del microprocesador sería muy pequeña. Pongamos por caso, a modo de ejemplo, que el proceso completo consume 100 microsegundos. La frecuencia por tanto sería de 10 kHz. Ahora, vamos a colocar unos registros entre cada una de las etapas principales del microprocesador: fetching, decodificacion, ejecución, memoria y "write back" o escritura de registros de destino. De esta forma, podemos hacer que una instrucción entre en fetch y se guarde en el registro intermedio, pasa otro ciclo y la instrucción ahora se decodifica y se guarda en el registro intermedio, pasa otro ciclo y se ejecuta, y así hasta llegar a la última etapa. En definitiva, gracias a esa memoria intermedia, llamada "registros de segmentación", podemos hacer que la tarea se realice en "n" ciclos de reloj, más pequeños que el anterior. Cualquiera podría argumentar que en realidad la instrucción tarda lo mismo en ejecutarse, porque aunque cada ciclo de reloj es menor, en vez de hacerse en uno solo, se hace en tantos ciclos como en etapas hayamos descompuesto la instrucción. Yo les diría aun más: en realidad, cada instrucción tarda más que antes, dado que hemos introducido retardos adicionales al tener que manejar un registro intermedio. Además, la frecuencia de reloj no se divide exactamente por el número de etapas. Dicho de otra manera, si tenemos 5 etapas, la frecuencia no pasa automáticamente a ser de 50 kHz porque la frecuencia pasa a depender de la etapa más lenta, y este tiempo no tiene por qué ser una división perfecta entre el periodo antes de segmentar y el número de etapas. De modo, que en realidad, las instrucciones son más lentas. ¿Que sentido tiene entonces segmentar? Tened en cuenta que si todos los microprocesadores modernos -y muchos de los antiguos- son segmentados, es porque se gana tiempo. El truco está en que gracias a los registros intermedios, una instrucción que ya ha pasado por, pongamos por caso, decodificación, en el ciclo siguiente deja totalmente libre todo este hardware para cualquier otra instrucción. Por lo que en realidad, la mayor parte del tiempo hay tantas instrucciones procesándose al mismo tiempo, como etapas de segmentación tenga el microprocesador. En nuestro ejemplo, si hay 5 etapas de segmentación, hay 5 instrucciones simultáneamente procesándose. Es cierto que la primera instrucción tarda -en nuestro ejemplo- cinco ciclos en procesarse, pero a partir de ese momento, en cada ciclo de reloj aparece una nueva instrucción procesada, y además, este ciclo de reloj es menor que cuando teníamos un microprocesador sin segmentación. Si se piensa bien, este mismo principio es el que se ha usado en las cadenas de montaje de todas las fábricas del mundo. Un operario no se encarga de todo el proceso de fabricación de un coche, sino que se especializa en, por ejemplo, montar el volante en la columna de dirección y alinearlo.


Primera parte del cauce segmentado.

Segunda parte del cauce de segmentado.

Ahora explicaremos con detenimiento cada una de las etapas supuestas en el diseño de este microprocesador. A efectos de comprender mejor los entresijos de los algoritmos empleados, consideraremos siempre el siguiente fragmento de código:

ld     r5,r2+5
add  r6,r5,r4
ori   r7,r3,45
andi r5, r3,65

- Etapa de 'Fetch'. En esta primera etapa se busca en la cache L1 la instrucción apuntada por el contador de programa, que es un registro de 32 bits que contiene la instrucción que hay que procesar en cada instante. Una vez obtiene la instrucción desde la memoria cache L1, incrementa en 4 el valor del contador de programa (desde ahora PC) para que la siguiente vez apunte a la siguiente instrucción. En caso de que la unidad de control del microprocesador decrete la introducción de una burbuja (porque exista algún riesgo estructural, es decir, hay instrucciones delante esperando algún recurso, y no puede entrar ninguna nueva), en vez de buscar una nueva instrucción en la cache L1 e incrementar el PC, deja intacto el PC, e introduce una operación NOP en el cauce. Dado que NOP no hace nada sobre ningún registro, a efectos prácticos es como introducir una "burbuja", es decir, un ciclo de reloj en el que el microprocesador no debe hacer nada. De esta forma, el cauce gana tiempo para conseguir que las instrucciones atascadas consigan su recurso esperado.

- Etapa de Decodificación1. En esta etapa se hace una decodificación básica de la instrucción. Dado que se implementó en forma de simulador software, en realidad en esta etapa descompongo la instrucción (un entero de 32 bits) en una estructura c con todos los campos que necesito. En la realidad, en esta etapa se calculan los bits de control de cada unidad funcional, en base a la instrucción. Estos bits gobiernan el 'camino de datos', es decir, decide que operandos entran en la ALU, si es una operación que se guarda en un registro del banco de registros o en el PC (es decir, una instrucción de salto), habilitando las escrituras en el registro correspondiente, decide que operación de ALU hay que realizar (suma, resta, AND, XOR, etc). El número de señales y su complejidad depende del 'camino de datos' concreto del microprocesador.

- Etapa de Decodificación 2. En el microprocesador que he diseñado, en esta etapa se solicita una plaza en el buffer de reordenamiento. Como ya he indicado anteriormente, el microprocesador ejecuta las instrucciones fuera de orden. ¿Que quiere decir esto? En un microprocesador normal, las instrucciones entran por la primera etapa, y salen por la última etapa por orden. Si un programador escribe una suma, una resta con otros operandos, y después un movimento a memoria del resultado de la suma, espera que se haga en ese mismo orden, y no en otro orden arbitrario. La secuencialidad es implícita e inherente a la naturaleza de los programas software. Pero por otro lado ejecutar en orden tiene ciertos inconvenientes, de cara a conseguir más rendimiento. ¿Que ocurre si la instrucción que entró en segundo lugar, en este caso la resta, tiene sus operandos disponibles ( es decir, el banco de registros se los puede suministrar), pero la suma no? Pues que la resta, aun teniendo sus operando listos, tendrá que esperar hasta que el banco de registros pueda suministrarle sus operandos a la suma, que entró en primer lugar. Pues bien, hagamos una cosa. Permitamos que la resta progrese en el cauce de segmentación, y que sea solo la suma la que espere a sus operandos. Hecho esto, la resta progresará, hasta que llegue a la última etapa. ¿Que ocurre ahora, permitimos que la resta acabe antes que la suma? Esta claro que el programador espera que el microprocesador acabe las tareas en orden. Así que no podemos cambiar eso, porque los programas tendrían un comportamiento aleatorio, y eso no nos sirve para nada. ¿En que punto paramos la resta, para que espere a la suma? He aquí la clave del algoritmo. Se diseña un conjunto de registros de n entradas, donde una instrucción volcará su resultado, en el orden de llegada. Me explico. Justo en esta etapa, en Decodificación 2, se solicita una posición en el registro, es decir, en el buffer de reordenamiento. Este hueco estará justo debajo de la instrucción anterior, y estará justo encima de la instrucción siguiente. Una vez la instrucción haya progresado a través del cauce y llegue a un punto apropiado, volcará su resultado sobre el buffer de reordenamiento, en la posición que le dieron en esta etapa. Se ve claramente, que los resultados se irán colocando cada uno donde corresponde. En nuestro escenario, la resta volcará su resultado en el buffer de reordenamiento, antes que la suma, pero lo hará en su posición que es uno justo debajo de la suma. La suma siempre estará por encima. ¿Ocurre algo porque haya escrito antes la resta que la suma? Evidentemente no. La lógica del microprocesador se encarga de esperar a la suma, que tiene más prioridad que la resta. Pero hemos conseguido aprovechar el cauce con la resta, que de otra forma habría permanecido sin uso, a la espera de los operandos de la suma. Para cuando la suma acabe, el resultado de la resta se usará inmediatamente en el ciclo siguiente, porque ya estaba calculado de antes.

Además, en esta etapa se realizan otras tareas en paralelo. Por un lado, se busca en el buffer de reordenamiento si la tarea que corresponde por orden de llegada ha finalizado. Volviendo a nuestro ejemplo de siempre, preguntaría si la suma ha terminado, y obviaría la resta, incluso si ésta esta ya lista. De encontrarse terminada la suma, efectuaría la etapa final de "write back", escribiendo en el registro correspondiente del banco de registro, o en el PC, según corresponda a la instrucción (en nuestro ejemplo, en el registro del banco de registro).

Por otro lado, si no hay hueco disponible en el buffer de reordenamiento, no queda más remedio que introducir "burbujas" en el cauce, introduciendo instrucciones NOP, y parando las etapas anteriores a ésta.

- Etapa de Decodificación 3. En esta etapa, se solicita plaza en la estación de reserva correspondiente. ¿Que es eso de la estación de reserva? Miremos con detenimiento el fragmento de código antes mencionado.

Se ve claramente que la suma no puede comenzar hasta que la operación de memoria termine. Esto, en un cauce sin renombramiento dinámico de registros, paralizaría todo el cauce. Pero al contar con un algoritmo de Tomasulo, esto se soluciona de la siguiente manera.

Ya en esta etapa de la segmentación, la instrucción anterior, es decir, la carga en memoria, ha solicitado y ha obtenido una plaza en el buffer de reordenamiento. Esto significa, a todos los efectos, que la posición dentro del buffer de reordenamiento es un identificador de la instrucción, durante toda su vida dentro del cauce segmentado. Dicho de otro modo: la posición ocupada en el buffer de reordenamiento es un 'DNI' de la instrucción. Es decir, a partir de la etapa de Decodificación 2, la instrucción de carga desde memoria tiene, pongamos por caso, el identificador "ROB1", es decir, ocupa la posición 1 en el buffer de reordenamiento (ROB = Re-Ordering Buffer). Dicho esto, también podemos decir que la instrucción "ROB1" escribirá en el registro 5 del banco de registros, una vez acabe su ejecución. La instrucción siguiente, que ahora mismo se encuentra en la etapa de Decodificación 2, está pidiendo su propia posición dentro del buffer de reordenamiento, y dado que las instrucciones entran por orden, y han de salir por orden, obtendrá el identificador "ROB2", es decir, ocupará la segunda posición en el buffer de reordenamiento, justo debajo de la instrucción de carga desde memoria. El problema, es que la instrucción "ROB2" tiene que esperar a que termine la instrucción "ROB1", debido a que "ROB1" escribe sobre uno de los operandos que necesita "ROB2". Pues bien, la estrategia es la siguiente. Disponemos de un conjunto de registros, llamados "estaciones de reserva", donde alojaremos provisionalmente las instrucciones, según van llegando. Y dentro de este registro guardamos, o bien el valor que contiene el registro del banco de registros (correspondiente a sus operandos), o bien apuntamos el "DNI" de la instrucción que generará dicho operando. De esta forma, cada instrucción se queda a la espera de sus propios operandos. Si una instrucción posterior, en nuestro ejemplo la 'ORI' (a la cual se le habrá asignado el identificador "ROB3") tiene sus operandos disponibles, no tiene por qué quedarse a la espera de que instrucciones más prioritarias tengan sus operandos. Obviamente solo podrán progresar hasta escribir el resultado en el buffer de reordenamiento, y no podrán volcar sus resultados sobre sus registros de destino, pero a todos los efectos, la instrucción se ha computado completamente (ya ha utilizado la unidad de ejecución, que a fin de cuentas es el cuello de botella del cauce). Es trabajo adelantado.

Pues bien, en esta etapa de la segmentación, justo se busca un hueco disponible donde alojar provisionalmente la instrucción. A diferencia del buffer de reordenamiento, que es un buffer FIFO, en este el orden no es importante, puesto que la primera instrucción en salir del mismo, no es la primera en llegar, o la última, sino la primera que tiene sus operandos disponibles.

Nuevamente, de no existir hueco disponible, habría que introducir burbujas -instrucciones NOP- y parar el cauce en las etapas anteriores.

- Etapa de Decodificación 4. Ya tenemos todos los buffers reservados, ahora solo nos queda llenarlos con los datos adecuados. 
De un lado, la instrucción "reserva" el registro de destino. Es decir, en nuestro ejemplo, la instrucción de carga desde memoria "ROB1" guarda el resultado en el registro r5. Es decir, una vez la instrucción "ROB1" acabe, el resultado deberá guardarse en el registro r5 del banco de registros. Pues bien, en esta etapa se le dice al banco de registros, que apunte que es la instrucción "ROB1" la que deberá guardarse en esta posición. ¿Que ocurre si otra instrucción quiere guardar también su resultado en el mismo registro? En nuestro ejemplo la instrucción 'andi' también quiere guardar su resultado en el registro r5 del banco de registros (y por consiguiente, con 'DNI' "ROB4"). Bueno, si es una instrucción "más tardía" que "ROB1", es decir, en la secuencialidad del programa, primero va "ROB1" y luego va "ROB4", está claro que el resultado que debe prevalecer en el banco de registros, es el resultado de la segunda instrucción. De no ser así, para cuando acabe la instrucción "ROB4" el resultado que se habrá guardado en el banco de registros es el de la instrucción "ROB1". Luego el algoritmo es sencillo: esté o no reservado por una instrucción, la instrucción que llega a esta etapa reserva su registro de destino en el banco de registros.

En paralelo, se ha realizado otra tarea necesaria: la recopilación de operandos. Hemos estado muy atareados reservando espacios en múltiples registros y búfferes, pero aun no tenemos lo necesario para ejecutar la instrucción: los operandos. En esta fase, se pregunta al banco de registros si los operandos estan disponibles, o han sido reservados por alguna instrucción. Supongamos que la instrucción "ROB2" ha llegado a esta etapa (la instrucción add), y quiere usar el registro r5. La instrucción no quiere el contenido actual del registro r5, sino el que tendrá cuando la instrucción "ROB1" (la instrucción ld) acabe. Por otro lado también necesita el registro r4, que en este caso, no necesita esperar a ninguna otra instrucción, puesto que ninguna instrucción previa va a escribir sobre este registro. Por tanto, cuando "ROB2" pregunte al banco de registro por ambos registros, le dirá que puede darle el valor del registro r4, y el 'DNI' de la instrucción que le proporcionará el registro r5 (porque "ROB1" ya fijó en el ciclo anterior que él escribiría sobre este registro cuando acabara). Es decir, la instrucción "ROB2" se quedará en la estación de reserva esperando a que termine la instrucción "ROB1". Por otro lado, cuando la instrucción "ROB3" llegue a esta etapa, querrá el registro r3, que se encuentra totalmente libre, y su otro operando es un inmediato. Por tanto, en esta etapa, la instrucción "ROB3" completará su lista de operandos, pudiendo entrar en ejecución en el ciclo siguiente (gracias a que la ejecución es fuera de orden mediante un buffer de reordenamiento, como ya indique antes).

Aquí, en esta etapa, las instrucciones se quedan esperando en las estaciones de reserva, a que los operandos estén disponibles. Se puede decir, que en cierto sentido las estaciones de reserva hacen las veces de registros de segmentación. En cierto sentido, a nivel conceptual, el micro se puede dividir en dos partes: preparación de las instrucciones y ejecución de las mismas. Hasta aquí, todos los procesos que se han realizado han preparado las instrucciones para su ejecución. Ahora podemos ejecutarlas.

- Etapa de Ejecución 1. Hemos dejado las instrucciones en la estación de reserva, recopilando sus operandos. Una vez tenga todos los operandos disponibles, la instrucción puede pasar a ejecución (a usar la ALU). Pero, ¿quién decide que instrucción esta lista para pasar a ejecución? Necesitamos un agente que compruebe que instrucción tiene todos sus operandos disponibles. En esta etapa, un agente busca instrucciones listas y las pasa a ejecución, que es la siguiente etapa. Hay tres agentes comprobando cada una de las tres estaciones de reserva, alimentando las tres unidades funcionales: ALU, sumador/restador en coma flotante y multiplicador/divisor en coma flotante.

- Etapa de Ejecución 2. Aquí es donde se realiza la ejecución de la instrucción en sí misma. Está compuesta por tres unidades funcionales diferentes: la ALU para operaciones aritméticas y cálculo de direcciones, la unidad de sumas y restas en punto flotante y la unidad de multiplicaciones y divisiones en punto flotante. Cada unidad toma un número de ciclos para completar las operaciones. Debemos recordar que este microprocesador lo implementé en forma de simulador software. Por tanto, podía modular completamente el comportamiento de estas unidades. Por ejemplo, la unidad ALU tomaba un ciclo para sus operaciones (incluida la multiplicación...), la unidad de sumas/restas en coma flotante tomaba tres ciclos y finalmente, la unidad de multiplicaciones/divisiones en coma flotante tomaba cinco ciclos. Además, en el simulador podemos indicar, para cada unidad, si está segmentada o no. Esto permite que en cada ciclo entre una nueva instrucción en la unidad de punto flotante. En los microprocesadores reales, implementados sobre silicio, el número de ciclos tomados son bastantes más, y además -y debido precisamente al número de ciclos que usan-, las unidades son casi siempre segmentadas.

- Etapa de Memoria. Las instrucciones de memoria usan la ALU de enteros para calcular la dirección. En la etapa anterior, ésta ha sido ya calculada. En esta etapa es cuando se pide a la cache L1 el dato alojado en la dirección dada. Es una etapa que puede durar más de un ciclo, dependiendo de si el dato está o no en la cache L1. Dado que esta operación puede tomar mas de un ciclo, existe el riesgo de que dos instrucciones de memoria quieran acceder al mismo tiempo. Esto detendría todo el cauce de segmentación. La solución adoptada en este diseño, es disponer de un buffer que almacena las direcciones de memoria y el tipo de acceso (escritura o lectura) de todas las instrucciones de acceso a memoria. En la implementación software, se cuenta con un buffer para lecturas de cinco entradas, y un buffer para escrituras de cinco entradas.

En la implementación software, la etapa de Ejecución 2 y la etapa de Memoria escriben su resultado en el buffer de reordenamiento en el mismo ciclo. Está claro que eso al menos consumiría otro ciclo más, por lo que es una operación que habría que segmentar en dos. Podríamos llamar a esta etapa "ROB". En la implementación software se simplificó, haciendo que la propia etapa de Ejecución 2 y la etapa de Memoria realicen la tarea.

- Unidades funcionales. Como ya he indicado, el microprocesador cuenta con tres unidades funcionales: ALU, sumas/restas en coma flotante y multiplicaciones/divisiones en coma flotante. ¿Por qué esta separación? Existen diferentes estrategias a la hora de definir el "camino de datos", es decir, el flujo que sigue la información dentro del microprocesador. Entre ellas quizás las más interesantes son las siguientes:

  1.     ALU y FPU en una misma unidad. Esto simplifica enormemente la lógica de control. Para empezar solo hace falta una estación de reserva. Pero esto tiene grandes inconvenientes. Por un lado, la diferencia de velocidad entre la unidad de enteros y la de coma flotante es abismal. En el simulador software la diferencia es mínima, pero en la realidad la cosa difiere bastante. Además, imaginemos que la unidad de ejecución se encuentra ocupada procesando una división en coma flotante (la más larga con diferencia). Y esperando, tenemos una suma de enteros para calcular un salto. El salto, no se podría calcular hasta que terminara la operación de coma flotante. Eso retrasaría no solo la instrucción de salto en sí, sino además la comprobación de la instrucción en la cache L1. ¿Y si la instrucción no está en la línea de cache y hay que traerla de memoria? Pues no se podrá hacer hasta que la operación de coma flotante termine, y no se podrá tener ese trabajo adelantado. Así que parece lógico que la primera división es separar la unidad de enteros de la unidad de coma flotante, no ya solo por este ejemplo en concreto, sino por la abismal diferencia de rendimiento que ofrecen ambas unidades.
  2.     ALU y FPU separadas. Ya hemos visto que esta es una solución absolutamente necesaria. Pero en su día, me pareció demasiado básica. Me explico. La mayor parte del tiempo realizamos sumas, muchas veces restas, y poquísimas veces multiplicaciones y divisiones (hablamos de programas de carácter general, y no de técnicas especiales de manipulación de datos). de modo que, una sola multiplicación o división en coma flotante, paralizaría todas las operaciones de sumas y restas, que son las que más se utilizan. Entonces me pareció buena idea, separar ambas operaciones en dos unidades de coma flotante: una para sumas/restas y otra para multiplicaciones/divisiones.
  3.     ALU ADDF y MULF separadas. ¿Tanto impacto tiene el tener unidas la unidad de multiplicación y la de sumas? Ya se dijo que apenas se utilizan las operaciones de multiplicación, de modo que no parece que tenga un gran impacto sobre el rendimiento, en términos estadísticos. Pero, aun así, ¿que costo adicional tiene el tener separadas ambas unidades funcionales? Únicamente la inclusión de una nueva estación de reserva. En principio, esto es un costo mínimo, y se ha supuesto que fácilmente asumible. A cambio, se consigue una leve mejora del rendimiento cuando se utilicen muchas operaciones de sumas y multiplicaciones. En nuestra implementación, se ha supuesto además que la unidad de suma no se utiliza para implementar la unidad de multiplicación, por lo que pueden vivir separadas.
No hay que olvidar que todo este diseño se ha supuesto para simplemente realizar un simulador. Sobretodo, hay que tener en cuanta que su finalidad era más didáctica que práctica. En posteriores entregas iré describiendo como se implementó finalmente en c++, y posteriormente, que adaptación del microprocesador he implementado en silicio, diseñando "transistor a transistor". Creo que será interesante ver como se llevan todas estas ideas al terreno físico y real. En el mundo del software casi todo es realizable. Y si te falta capacidad de cálculo, siempre puedes tener más máquinas corriendo. Pero el mundo del hardware es muy hostil. Para que un diseño de altas prestaciones tenga sentido, hay que pensar siempre en integrarlo todo en una misma pastilla (nada de conectar dos integrados a través de pistas en un circuito integrado). Pero obviamente, el espacio es finito, y no todo lo que se nos ocurre se puede llevar a cabo. Dicho esto, añadiré que todos estos algoritmos que he comentado los implementan los microprocesadores actuales (y muchísimas cosas más que no he contado). Es más, incluso hay cabida para tener varios de estos microprocesadores en la misma 'die' (véanse los core i3, i5 e i7 de intel), y no solo eso, sino que incluso caben cantidades ingentes de memoria caché. Por otro lado, aun siendo material de otro artículo, adelantaré que el microprocesador lo he implementado con tecnología de 0,35 micrómetros (o 'micras' en el argot), misma tecnología que se usaron en las últimas revisiones del pentium, o incluso mejor que la tecnología usada en los primeros Pentium Pro (0,50 micrómetros).

Espero que os haya parecido interesante el artículo, y que comentéis cualquier error que haya podido cometer.

lunes, 4 de abril de 2011

La luna llena, más llena

    Hoy, 19 de marzo, después de 15 años, la órbita de la luna se coloca de nuevo en su punto más cercano a la Tierra (en el perigeo), coincidiendo con la luna llena. Gracias a este fenómeno astronómico, la luna se mostrará más grande y más brillante. Eso es lo que hemos oído en televisión y demás medio de comunicación, y me gustaría precisar un poco más esta noticia.

    Como sabemos, la órbita de la luna alrededor del planeta Tierra es elíptica, estando el planeta Tierra en un foco de la misma, y la Luna en el opuesto. De tal suerte que en ocasiones la luna está en el punto más alejado, que se conoce como apogeo, y otras veces en el punto más cercano de la Tierra, que es conocido como perigeo. Alrededor del mes de Mazo, los perigeos y apogeos son los siguientes:


  • Apogeo    6 Marzo 2011   7:50     406.583'5 km
  • Perigeo    19 Marzo 2011 19:07   356.578'2 km
  • Apogeo    2 Abril 2011     9:02    406.658'8 km


    En concreto, la luna estará a 356.578'2 kilómetros de nuestro planeta. Pero, ¿siempre es el mismo Perigeo y el mismo apogeo?, si cogemos unos cuantos valores más, se podrá ver fácilmente que esto no es así:


  • Perigeo     17 Abril 2011  5:58    358.090'5 km
  • Apogeo    29 Abril 2011  18:01  406.038'1 km
  • Perigeo     15 Mayo 2011 11:24  362.133'4 km
  • Apogeo     27 Mayo 2011 9:56   405.003'0 km
  • Perigeo     12 Junio 2011  1:41   367.188,7 km
  • Apogeo    24 Junio 2011   4:11  402.274'2 km
  • Perigeo     7 Julio 2011     13:51 369.567,3 km
  • Apogeo    21 Julio 2011   22:47 404.358'1 km
  • Perigeo    2 Agosto 2011  21:05 367.757'7 km
  • Apogeo   18 Agosto 2011 16:23 405.162'5 km
  • Perigeo    30 Agosto 2011 17:38 360.856'0 km

    En realidad, los apogeos y perigeos van variando cíclicamente a lo largo de los años, debido fundamentalmente a la influencia del sol. Y además, no siempre coincide con la fase lunar de Luna llena. Por tanto, ¿por qué es tan especial este perigeo del 19 de Marzo? La razón es una doble coincidencia: es uno de los perigeos más cercanos a la Tierra, y además, coincide exactamente (con horas de diferencia) con la Luna llena. Y este hecho insólito, esta doble coincidencia, es lo que no se daba desde hacía muchos años. Además, y debido a la ilusión óptica que ocurre cuando la luna esta cerca del horizonte, teníamos asegurado un espectáculo impresionante, para deleite y disfrute de nuestros ojos.


    Como aficionado a la fotografía, no podía dejar escapar esta oportunidad. Así que, armado con mi cámara réflex Sony, mi juego de ópticas, mi trípode y un block para tomar notas, me decidí a aventurarme en la astrofotografía en su vertiente más simple: con una simple cámara de fotos. Además, y para colmo de males, mi cámara no es precisamente buena con el ruido, y de noche la necesidad de ISOs altos es apremiante. Además, no dispongo de un gran zoom, por lo que la escena era demasiado abierta (nada que no se arregle con un recorte en photoshop, aunque para ello sacrifique la resolución).  Con todo y con eso, a base de exposiciones largas y un poco de photoshop conseguí algunas fotos que se salvaban "de la quema".

    Lo primero era elegir la escena que deseaba fotografiar: la luna emergiendo del mar. Lo segundo era buscar un emplazamiento adecuado para la astrofotografía: con poca contaminación lumínica. Dado que no dispongo de tiempo como para desplazarme fuera de Málaga, tenía que buscar un buen emplazamiento aquí mismo. Y la solución fue la siguiente:

Emplazamiento de las fotos

    Una vez elegido el sitio, monte mi cámara en el trípode, y tiré algunas fotos de prueba, por ir tanteando. Primero hice una tirada con el diafragma ligeramente cerrado, en concreto, en la posición de mayor calidad óptica (menor aberración cromática, mayor definición, mejor profundidad de campo, etc): f :7.0 aproximadamente.
Montaje de mi cámara réflex con el trípode.
    Con esta apertura, necesitaba tiempos de exposición largos, eso estaba seguro. La cuestión estaba en elegir un buen valor de ISO. Cuanta menor cantidad de ISO se use, menor ruido se captará, y la falta de luminosidad se podrá corregir con exposiciones aun más largas. En cualquier caso hice algunas tomas a ISOSs altos, para contrastar la calidad.

  • Luna saliendo por el horizonte
20s f/7.1 ISO200 70mm (35mm eq:105mm) 
20s f/7.1 ISO200 70mm (35mm eq:105mm) 
20s f/7.1 ISO200 18mm (35mm eq:27mm) 

  • Luna a 5-10 grados del horizonte aprox.
10s f/6.3 ISO3200 50mm (35mm eq:75mm) 
10s f/6.3 ISO3200 70mm (35mm eq:105mm)


  • Luna a 20 grados del horizonte aprox.
20s f/7.1 ISO200 18mm (35mm eq:27mm) 



  • Luna a 30 grados del horizonte aprox.
15s f/16.0 ISO800 70mm (35mm eq:105mm) 


0.8s f/1.4 ISO100 50mm (35mm eq:75mm)



Referencias:
http://www.astronomo.org
http://www.nasa.gov

martes, 29 de marzo de 2011

Sistema NAS casero (II)

Tal y como prometí, hoy hablaré del sistema operativo elegido, y de su configuración. De entre todos los sistemas operativos (por ejemplo, Unix, Linux, Windows, Solaris, BSD,...etc) elegiré un candidato de entre los dos más extendidos, primero por que el sistema no necesita grandes requerimientos, y segundo porque no todos los sistemas son fáciles de localizar de forma "normal". Está claro que usar Windows para una aplicación donde no se requiere una interactividad con el usuario, y donde precisamente lo que mas se necesitará es eficiencia en la E/S, no es lo más inteligente. Por otro lado tenemos al ya de antemano ganador: Linux.
Muchas veces he oido eso de: "¿qué Linux has instalado?". A ver, hay diferentes distribuciones, pero en realidad todos son sistemas operativos Linux. Y todos son prácticamente iguales. Sólo varían, básicamente y a grosso modo, en la ubicación de los archivos de configuración, y en el software que use para sincronizar e instalar los paquetes de software. Entrando un poco más en profundidad, ya si se pueden apreciar algunas diferencias, que son interesantes de tener en cuenta para sistemas mas críticos:

- Algunas distribuciones (por ejemplo Gentoo) permite una personalización al milímetro, compilando cada paquete de software que instales según unos criterios que fijas en el momento de la instalación.
- Según que distribución se elija, la "instalación básica" consumirá más o menos espacio (por ejemplo, Ubuntu es más tragón que Debian, y éste más que Gentoo). Por supuesto que, y siempre que sepamos lo que hacemos, podremos borrar cosas que no usemos.
- La cantidad de paquetes disponibles en cada distribución varia de una a otra. Aunque de todas formas cualquier distribución hoy día tiene las aplicaciones típica de un servidor (apache, webpy, bind, dhcp-server, etc). Y en cualquier caso, siempre te puedes compilar tu propio software a partir de las fuentes.

Teniendo en cuenta las exigencias y los requisitos de mi padre, cualquier distribución habría funcionado perfectamente. En nuestro caso, elijo un Ubuntu server LTS. De esta forma, conseguimos una instalación muy fácil, que no nos exige mucha configuración inicial, y una buena colección de paquetes, ya que no se qué cosas nuevas se le pueden 'antojar' a mi padre.

Sobre como instalar un sistema Linux basado en Ubuntu y con un sistema RAID software, os remito al post Instalación de Ubuntu Server + RAID de este mismo blog.

Configuración del servidor SSH.

Lo primero es instalar el servidor ssh, para ello se ejecutan las siguientes ordenes (todas como usuario root, o en su defecto, usando sudo para coger los permisos de root temporalmente):
apt-get update
apt-get install openssh-server

Tal y como se instala, con la configuración por defecto, ya nos podremos conectar con cualquier cliente ssh, simplemente indicando nuestro usuario y nuestra clave, de la siguiente manera:

usuario@mi_ip

Aun cuando la configuración por defecto es suficiente para la mayoría de los usuarios, hay algunas opciones interesantes, de cara a la seguridad, que vendría bien reseñar (aunque no las tomaré en consideración en el proyecto de mi padre):

Editando el archivo /etc/ssh/sshd_config

- PermitRootLogin. Esa opción fija la forma en la que permitiremos conectar a nuestro usuario root por ssh. Mi consejo es, siempre dejarlo como "no" (PermitRootLogin no). De esta forma solo podrá conectarse el usuario root en local, sin posibilidad de hacerlo remotamente. Imaginaos las consecuencias de tener un ssh escuchando en la IP publica, y permitiendo un 'logeo' del usuario root...o incluso que se produzca un acceso a otra maquina de la misma subred....etc. No es la mejor política de seguridad. Lo mejor es que solo los usuarios "normales" puedan conectarse por ssh. Si es necesario trabajar como usuario root, hacemos un cambio de usuario con el comando 'su', e introducimos la clave del usuario root  (así, obligamos a que el intruso tenga que 'logearse', primero con el usuario 'normal' y luego como root). Ni que decir tiene que se desactivarían las funciones sudo. Otra opción es permitir que el usuario root acceda, usando una clave pública. En este caso lo único que conseguimos es  facilitar la vida del que administra muchos equipos, ya que usaría la misma clave para acceder a cada uno de ellos.
- El puerto. Si dejamos el servidor ssh en el puerto por defecto, se lo pondremos en bandeja a cualquiera que quiera intentar acceder de forma ilícita. Al menos, hagamos que tenga que 'escanear' todos los puertos, para que a priori no sepa por donde atacar. Cambiar el puerto que trae por defecto hacia uno 'alto' es siempre una buena política de seguridad.
- MaxAuthTries. Con esta opción indicamos cuántos intentos fallidos de acceso permitimos. Una buena política es emplear 2 ó 3, pero no más.
- MaxStartUps. Con este parámetro indicamos cuántas conexiones no autenticadas permitimos de forma simultánea. Es decir, si por ejemplo "juan" esta accediendo, pero aun no ha terminado de escribir su contraseña, y simultáneamente lo esta intentando "pedro", deberemos seleccionar un valor mínimo de 2. De esta forma permitiremos dos conexiones no autenticadas (pero que están en proceso de ser autenticadas). Si dejamos un valor muy alto, estamos poniendo en bandeja de plata que los intrusos paralelicen el intento de ataque, probando múltiples claves simultáneamente. Un valor de 2 ó 3 es una buena política de seguridad.

Configuración del servidor SAMBA.

Comenzamos con la instalación del mismo (deberas ser el usuario root, o en su defecto ejecutar los siguientes comandos con los permisos de root, mediante sudo):

apt-get update
apt-get install samba

Editamos el archivo de configuracion de samba:

nano /etc/samba/smb.conf

Y añadimos las siguientes lineas al final:

[DATOS]
comment = Mis cositas varias
path = /home/datos
public = yes
writable = yes
browseable = yes

Si el directorio a compartir es otro, habría que cambiar /home/datos por el directorio deseado.

De esta forma conseguimos tener un recurso samba compartido, sin necesidad de utilizar un usuario y una contraseña para acceder, facilitando la conexión a todos aquellos usuarios que necesitan una solución doméstica, y no quieren pensar en usuarios y contraseñas. Ni que decir tiene que esta no es la opción recomendable en entornos empresariales.

Ahora, para acceder a nuestros datos, basta con que hagamos desde nuestro equipo windows domestico lo siguiente:

1) Abrir un explorador de archivos (por ejemplo "mi pc").
2) Escribir lo siguiente en la barra de direcciones: \\mi_ip\datos

De esta forma se listará todo su contenido como si fuera una carpeta cualquiera.

Dejo para otro post la configuración de samba en entornos empresariales.

domingo, 27 de febrero de 2011

Sistema NAS casero (I)

Hace tiempo que mi padre me viene pidiendo un sistema de almacenamiento con redundancia, y que se pueda acceder por red. La cuestión es que, hace ya algunos meses, perdió un montón de fotos, y todo por culpa de un disco duro en mal estado y una mala política de copias de seguridad (tan mala como que no existía).

Era evidente que me aprovecharía de la experiencia acumulada en mi actual trabajo, donde tenemos un sistema de almacenamiento de alta redundancia, alta disponibilidad y alta capacidad (100 TeraBytes), diseñado por nosotros mismos (quizás algún día me anime a dar algunas pinceladas de como se montó, isn entrar en detalles técnicos comprometidos). El sistema contaría con los siguientes elementos:

- 2 discos duros de 2 TeraBytes cada uno, marca Samsung de 7200 RPM (de los domésticos, el que mejor rendimiento y durabilidad nos ha dado).
- Placa base mini-itx Asus con microprocesador Intel Atom fanless (disipación del calor pasiva). El consumo global del sistema (micro+placa base) ronda los 20 watios max. por lo que es posible que el consumo medio este entre los 10-15 watios.
- Caja mini-itx formato "cubo" con dos bahías 3'5" para sendos discos duros.
- 2 GB de RAM DDR2 a 800 Mhz (habría bastado con 1 GB de RAM, pero en este caso era un requisito de mi padre que el sistema estuviera "holgado" en todos los sentidos, además, tampoco descarto que en un futuro se instale el servidor de streaming, así se tendría un sistema multimedia para toda la casa).

Mesa de estudio de mi padre

Con todo esto, se tiene un sistema que consume muy poca energía, que no hace ruido (0db reales) y que ofrece un rendimiento muy aceptable para lo que debe ofrecer un NAS. Además, el hecho de contar con un microprocesador x86 nos permite introducir todo el software que imaginemos (servidores de streaming para ver vídeos desde tu navegador, alojándolos en el NAS, sistemas de descarga P2P, etc).

El montaje se realizó intentando optimizar el poco espacio que ofrece la caja (requisito imprescindible, ya que mi padre disponía de poco espacio para la caja):

- Paso 1: Etiquetando. Siempre etiqueto cada disco duro con los últimos tres dígitos de su número de serie, en la parte trasera del mismo. De este modo, si alguno se rompe, los puedo identificar inmediatamente sin tener que sacarlos uno por uno. Uso sólo los tres últimos dígitos, porque las probabilidades de que coincida en los dos discos duros son tan reducidas (y en cualquier caso, comprobable) que no me compensa escribir todo el número de serie al completo. Y esto, en el sistema que tengo montado en mi empresa, en el caso de mi padre habría bastado con un solo dígito, pero ya es costumbre emplear tres.
Detalle del etiquetado del disco duro
- Paso 2: Colocación de la placa madre. Siempre se colocarán los módulos de RAM con la placa madre aun sin introducir en la caja. De otro modo, al presionar para encajar el módulo de RAM, combaremos la placa madre. Seguidamente, se coloca la cubierta trasera con el "layout" de los conectores, y atornillamos la placa madre a la caja.
 - Paso 3: Conexiones caja-placa madre. Seguidamente se realizan las conexiones necesarias entre la caja, y la placa madre: el panel frontal (botón de encendido, LEDs indicadores, etc), los conectores externos de audio (entrada y salida de sonido analógico), y los conectores USB del frontal.
- Paso 4: Colocando la fuente de alimentación. En este caso no hay ninguna tarea especial que realizar, salvo tener cuidado de que no se nos caiga la fuente encima de la placa madre.

- Paso 5: Colocando el disco duro. Doblando adecuadamente el mazo de cables principal (el del conector ATX), se consigue que el disco duro acople de forma adecuada, ya que esta caja y placa madre han tenido la mala fortuna de hacer coincidir la posición del disco duro con la posición del conector ATX de la placa madre. En cualquier caso, con solo doblar debidamente el mazo de cables, se consigue que convivan ambos elementos, aunque no favorece precisamente que haya un buen canal de ventilación.
- Paso 6: Finalmente, una vez comprobado que el equipo arranca y que todas las conexiones están bien realizadas, cerramos el equipo.

En la siguiente entrada veremos que sistema operativo hemos elegido, y que configuración realizamos.

sábado, 26 de febrero de 2011

Presentación


Aprovecho esta primera entrada del Blog para presentarme:

Mi nombre es Alejandro Bermúdez Aragüez, y soy Ingeniero técnico Industrial especialista en electrónica industrial por la Universidad de Málaga. Actualmente, me encuentro cursando el segundo ciclo de la titulación, también especializándome en electrónica. Además, desde hace tres años trabajo en una importante empresa de seguridad informática, en el Parque Tecnológico de Málaga, como ingeniero de sistemas. Independientemente de mi formación académica, soy un apasionado de las ciencias y de la tecnología, y creo que quedará patente a lo largo de la diferentes entradas del Blog.

Espero que encontreis interesante los artículos, y que comentéis de forma constructiva para corregir posibles deficiencias. Lo interesante y lo importante de este Blog, es que la información sea útil y veraz.

Un saludo y hasta la próxima.