La era de la informatica

Tuesday, February 27, 2007

Solucion Problema 1

Hola, hoy cumplía el plazo para resolver el problema 1. No he recibido muchas respuestas, pero aquí os voy a poner la solución, si lo sabiais bien y si no pues seguro que os parece interesante. El código, que era muy sencillo, realizaba una llamada a la función ptr, que devolvía un puntero a una variable local de la función. El valor de esta variable era 3. Imprimiamos el valor de esta variable por pantalla y nos devolvía 3, pero luego lo volvíamos a imprimir y obteniamos "basura", un número que dependiendo de cada sistema y de cada momento cambia.

La pregunta que os formulé era: ¿Por qué sucede esto?

Lo más lógico sería pensar que si imprimimos un valor por pantalla, y luego sin haberlo modificado en nuestro código lo volvemos a imprimir, este valor no cambie. Pero si que cambia. Para entender esto debemos comprender como se estructura un programa en memoria cuando se está ejecutando. Y para que sirve cada zona de la memoria. Un proceso (programa en ejecución) utiliza dentro de su espacio de memoria 4 zonas bien diferenciadas:

- Zona de código: Donde se aloja el código fuente del programa. Solo se puede ejecutar una instrucción si está en memoria.
- Zona estática: Donde se alojan las variables globales del programa, que pueden ser accedidas desde cualquier punto del programa.
- Heap: Zona de memoria dinámica. Zona de memoria que debe reservarse para su uso mediante llamadas a funciones como malloc, que a su vez piden memoria al sistema operativo.
- Stack: También conocido como pila de llamadas, esta zona es la zona relevante a nuestro problema. Contiene información necesaria para las llamadas a las funciones dentro de un programa. Esta zona es de rápido acceso ya que es dinámica y no necesita ser reservada, el sistema operativo se encarga de hacerlo automáticamente.
En el siguiente gráfico se pueden ver las distintas zonas de la memoria. Dependiendo del sistema operativo y de la arquitectura el heap y el stack pueden estar intercambiados respecto a este gráfico, pero es indiferente para el funcionamiento de los programas.


Como decía el Stack es el que toma el papel protagonista en nuestro problema número 1. El stack, también conocido como pila de llamadas, es la zona encargada de albergar la información necesaria para las llamadas a las funciones de nuestro programa. La típica información que se guarda en el stack es:
- Dirección de retorno de la función. Para saber donde seguir ejecutando una vez que la función termine
- Parametros de entrada de la funcion
- Registros internos que necesiten ser almacenados
- Variables locales de la funcion

En un lenguage como C, a la hora de llamar a la función simplemente escribimos el nombre de la función y nos olvidamos de la complejidad que hay debajo. El compilador se encarga de traducir esa llamada en almacenamiento de lo necesario en el stack, dejar espacio suficiente en el stack para variables locales y de llamar finalmente a la funcion.

Vamos a ver por tanto que pasa en nuestro programa. Cuando llamamos a la función "ptr", en la pila se almacena la dirección de retorno y espacio para una variable de tipo "int" llamada "y". Cuando la función termina, devuelve un puntero a la variable "y". Es decir, devuelve un puntero a la dirección física en memoria dentro del stack. Luego metemos ese valor dentro de la variable content y lo imprimimos por pantalla.

Para imprimir ese valor por pantalla llamamos a la función printf. Esa función como todas las funciones utiliza el stack para almacenar parametros de entrada, dirección de retorno, variables locales, etc. De forma que la función printf sobreescribe la zona de memoria en el stack donde antes estaba "y". Y perdemos el valor de esta variable. Ahora referenciamos de nuevo el puntero que apuntaba a la zona donde antes estaba "y", pero ahora encontramos otro valor distinto, que nos lleva a una zona de memoria que puede contener cualquier cosa. Por tanto la salida del segundo printf es "aleatoria".

En Windows con el código original no pasó, ya que printf no sobreescribe el stack, por eso introduje una llamada a otra función que no hacía nada en especial pero que utilizaba variables locales, de forma que me aseguré de que la llamada a esa función sobreescribiese el stack y el segundo printf sería distinto al primero.

Como veis era muy sencillo. Simplemente había que entender que cada vez que llamamos a una función se utiliza la misma zona de memoria para toda la información necesaria para la función. Por eso es una mala práctica devolver referencias a variables locales. En lugar de devolver una referencia a una variable local se debe usar la función malloc para reservar memoria en el heap, y ésta ya es inamovible. Utilizar el stack es rápido y sencillo, pero una llamada posterior a una función sobreescribirá esa memoria, así que de usarlo se debe usar con mucho cuidado.

SHell de P: Para Windows

Hola, en la anterior entrada preguntaba si se os ocurría como modificar el SHell de P para que funcionara en Windows. Después de ver las funciones para creación de procesos y manejo de hilos en la anterior entrada, la modificación del shell para que funcionase en windows es muy sencilla, teniendo en cuenta las siguientes diferencias que nos forzarán a hacer modificaciones:

- No podemos usar la función fork() ya que no existe en la API de win32. Para emular la llamada de fork() y exec que tenemos en el shell para UNIX, vamos a crear un hilo con la función CreateThread, y la función que ejecute el hilo va a utilizar la función System, que es la forma mas sencilla de ejecutar otro programa.

- Cambia el manejo de señales. Pero lo bueno es que se vuelve mas sencillo. La función signal hace que podamos especificar una función de procesado de la señal. La función de procesado de la señal es equivalente a la de UNIX.

- No necesitamos algunas de las funciones propias del shell que si necesitabamos en UNIX. Por ejemplo, no necesitamos "export" para variables de entorno, ni "cd" para cambio de directorio, estas 2 se ejecutarán en el proceso hijo como cualquier otro programa. Solamente vamos a dejar "exit" para terminar la ejecución del programa y "r" para reejecutar un programa.

- Para "pintar" el "prompt" usaremos la función _getcwd que mete en un buffer de caracteres el path completo. De esta forma coseguimos el "look" propio de ms-dos.

Solo con estos cambios nos queda un código aún más sencillo que para UNIX. El código es el siguiente:

shwindows.c
shwindows.h
Makefile-windows
shwindows.zip

Espero vuestros comentarios.

(Nota: no lo he probado mucho, por ejemplo aunque permitimos ejecutar procesos en modo "background" no he probado que funcione correctamente. Si os falla decidmelo y a ver si lo resolvemos entre todos :))

Sunday, February 25, 2007

Hilos en Win32

Hola, cuando subi el problema 2 al blog escribi que si me daba tiempo escribiria el codigo equivalente en Windows. Pero me he dado cuenta de que en windows ambos codigos presentados en el problema 2 serían tan diferentes, que no tendría sentido presentar un problema así. Esto viene de que en Windows la API de Procesos y de Hilos es distinta que en UNIX, y además funciona de diferente manera. Por ejemplo, en unix los procesos creados por otros procesos no se eliminan si el proceso "padre" termina, pero en windows si. Lo mismo sucede con los hilos, si el proceso que generó los hilos termina, los hilos creados que no hayan terminado de ejecutarse mueren automaticamente. Pero hay muchas mas diferencias. Estas son algunas de las funciones que se pueden usar para crear procesos e hilos en Windows:
- System (...) : Ejecuta un comando
- Exec (...): Exec y sus variantes (execv ...) realizan exactamente lo mismo que exec en UNIX.
- Spawn (...): Similar a exec.
- CreateProcess(...): Ejecuta un comando paralelamente al proceso que llama a la funcion. Además permite compartir memoria entre ambos procesos. Sería el equivalente de hacer fork y exec seguidos en unix.
- CreateThread(...): Crea un hilo independiente que se ejecuta en el mismo espacio de direcciones del proceso que lo crea (como en cualquier implementacion de hilos), lo que hace que se comparta la memoria y el codigo del programa. Esta funcion fuerza a especificar una funcion desde donde va a empezar a ejecutarse el hilo.

Como veis esta es la forma de "jugar" con procesos e hilos en Windows. Aquí os dejo un código parecido a los del problema 2, que ilustra la creación y manejo de Threads (Hilos) en Windows:
Thread Win32 API

Esta librería de funciones puede ser un punto de partida para modificar el shell de UNIX que cree hace 2 semanas y hacer uno similar pero para Windows. ¿Se os ocurre como?

Saturday, February 24, 2007

Problema 2: Concurrencia. Hilos

Hola. Me complace presentaros el segundo problema de "Era Informatica", este problema lo vi en un libro de sistemas operativos y es realmente muy sencillo. Aqui os dejo 2 links con 2 codigos muy parecidos pero con una pequeña diferencia. Estos codigos ejecutan hilos concurrentemente y modifican una variable global. Si los compilais y los ejecutais vereis que la salida por pantalla es distinta. La pregunta es ¿Por que la salida del programa es distinta? (De nuevo explicar con el mayor detalle posible)

Codigo 1
Codigo 2

Como comprobareis es realmente una trivialidad. Espero que os animeis a participar. Mandad un email con vuestra respuesta.

PD: La API de hilos usada es PThreads por lo que solo podreis compilarlo en sistemas tipo UNIX. Si tengo tiempo esta semana hare un codigo parecido para windows (pero no se si podre). En cualquier caso el codigo es tan sencillo que no hace falta compilarlo para deducir la salida del programa.

Tuesday, February 20, 2007

Salvar internet

Desde hace meses se esta debatiendo en Estados Unidos una petición por parte de varias empresas de telecomunicaciones, que controlan los ISP (proveedores de servicio de internet), para cambiar la politica que reina en internet.

Cuando internet fue inventado se tomo una decision que ha sido hasta el dia de hoy critica para la evolucion tan fantastica que esta plataforma ha tenido, una decision que nadie sabia si iba a ser buena o no en su dia, pero todos aquellos que diseñaron internet comparten, hoy por hoy, que fue lo mejor que se podia haber hecho entonces. Esta decision se basaba en dejar la "logica" en los "extremos".

Por "logica" entendemos los procedimientos logicos que los terminales digitales son capaces de procesar.

Por "extremos" entendemos las partes comunicantes, no el medio por el que se comunican.

Por tanto mantener la logica en los extremos significaba que los terminales digitales (moviles, pcs, etc...) conectados a internet tendrian que procesar cada mensaje por si mismos y que el medio fisico por el cual se enviaban mensajes de un terminal a otro solo se encargaba de encontrar el camino entre un punto y otro. Es decir, todo contenido que viaja por la red es igual, se trata de una señal codificada que representa un conjunto de bits y todas las señales son tratadas como iguales, independientemente de que contengan y de donde vengan. Es por tanto labor de los extremos, de los terminales conectados, el evaluar esa señal, decodificarla y convertirla finalmente en una pagina web, un video de youtube, un mensaje de voz, una señal de television, etc...

Este entorno tan libre y equitativo, ha sido la plataforma ideal para que emprendedores de todos lados del planeta sean capaces de hacer llegar su ingenio a cualquier parte del mundo. Empresas como Google, Youtube, eBay o Amazon empezaron siendo un sueño de personas que tuvieron una idea y que gracias a las posibilidades que internet les ofrecia se convirtieron en empresas multinacionales que ofrecen servicios impensables tan solo 15 años atras.

Ahora, en 2007, tras un largo debate entre empresas y politicos, pero nunca ciudadanos de a pie, se está intentando en Estados unidos que todo esto cambie. Las empresas que controlan los ISP cobran por dar un servicio de conexion a internet a sus clientes. Ademas, empresas que controlan los "dominios" (subredes con nombres facilmente reconocibles a los humanos, especialmente importantes para las paginas web) tambien cobran por el alquiler de un nombre dentro de internet.

Sin embargo, si una empresa ademas quiere vender junto con su conexion a internet un servicio digamos de mensajeria (como MSN messenger) o de blogs (como blogger), lo tiene muy dificil en un ambiente tan competitivo como el de internet. Cualquiera está dando esos servicios a coste 0, ¿como poder cobrar por ello?

La respuesta es sencilla. Ya que las lineas fisicas de conexion (cables de telefono, cables coaxiales, cables de fibra optica o antenas para conexion inalambrica) son propiedad de las empresas que realizaron esa inversión en contruirlas. Y dado que todo mensaje enviado por internet se procesa a traves de esas lineas por los "servidores" que son propiedad de dichas empresas. Sería facil eliminar la premisa de "logica en los extremos", y poner algo de "logica en el medio".

Por ejemplo, cuando un ordenador de España se conecta a un ordenador en Estados Unidos, el ordenador de España manda un mensaje a traves de su ISP, el ISP lo manda al ISP del ordenador estadounidense y este a su vez se lo envia al destinatario. Ahora mismo los servidores solo "enrutan", es decir, encuentran el camino para que el paquete llegue a su destinatario. Pero con la "logica en el medio", los ISP podrian decidir que si el paquete que el ordenador en España envia es por ejemplo una web, esta deberia ser discrimanada frente a un paquete de video, y tambien si esta web esta publicada en una empresa competidora, seria tambien discriminada, de forma que aunque finalmente la conexion se pudiera realizar, esta quedaria relegada a la conexion mas lenta posible.

De esta forma se controlaria lo que las personas hacen en internet, ya que cuando acceden a ciertos servicios para los que han pagado estos se distribuirian sin problemas por la "via rapida" y si acceden a servicios de otros operadores, gratuitos o ajenos a cualquier contrato con el ISP, todo quedaria relegado a la "via lenta"

Si esta propuesta de las empresas de telecomunicaciones mas grandes de Estados Unidos y de las mas grandes del mundo se aprueba en el congreso, estas empresas crearan dos redes, la lenta y la rapida. La rapida contendra todos aquellos servicios que ellos venden (lo que promoveria contratos multimillonarios entre grandes empresas para dar TV, musica, telefonica, etc... por internet), y la lenta contendria todo lo que se queda fuera. En esencia, los datos por los que mas se pague, que seran datos audiovisuales, iran por la via rapida, mientras que consultar webs, compartir ficheros, etc, iria por la via lenta. En un extremo de todo esto esta el que al dar ese poder sobre la red a las empresas, estas implantarian un codigo segun el cual se reservan el derecho de autorizar un contenido u otro. Por ejemplo, si tienes un amigo que tiene un blog en una pagina A, y tu ISP decide que no autoriza ver contenidos que vengan de A, ese blog quedara censurado para ti hasta que cambies de proveedor, o tu amigo decida pagar por tener un blog en el mismo proveedor que tu.

Los riesgos de esto son muchos, las empresas pueden pactar precios para fijar el mercado a su antojo. Toda innovacion que no controlen no tienen porque autorizarla en sus redes (ni las rapidas ni las lentas). Empresas que salian de la nada sin nigun dinero como Google, Yahoo o Amazon, no volveran a surgir, solo nuevos servicios de empresas grandes ya consolidadas que tengan acuerdos multimillonarios con los proveedores. La censura vendra dada por intereses economicos de las empresas y no por la justicia ordinaria (en base a criterios de libertad e igualdad)

Afortunadamente aun hay esperanza. Muchos senadores norteamericanos (sobre todo democratas), aprovechando la campaña politica de 2006, se han puesto del lado de lo que alli llaman "net neutrality", que no es ni mas ni menos que oponerse a este intento de cambio de internet. Por otro lado, Estados Unidos no esta solo en el mundo, hay muchos otros paises con diferentes puntos de vista. Sin embargo no conviene dormirse ante una noticia asi. Pensar que esto aqui no pasara o que no alli lo conseguiran y olvidarse del tema, solo llevara a lamentaciones posteriores. Es necesario concienciarse de que no podemos permitir este nuevo aprovechamiento de los poderosos frente a los debiles, de destruir lo que se ha convertido en una de las mejores herramientas para el avance y la unidad de todo el mundo.

Debeis saber que como en toda democracia el poder esta en nuestras manos, el de denunciar y el de luchar por lo que es justo.

Hay mucho en internet sobre este tema, os dejo el link de una de las web que mas esta haciendo por el "net neutrality" Save the internet

Un saludo a todos.

Labels: , ,

Sunday, February 18, 2007

Codigo del Shell

Hola de nuevo, espero que hayais descansado este fin de semana. Mientras os pensais el problema, no se si alguien lo habra visto... Os publico mi codigo del SHell de P (shp), como vereis se puede optimizar en muchos puntos, pero esa hecho para que sea facil de entender y ademas es un codigo muy cortito. Podeis bajaros tanto los ficheros sueltos, como un zip donde estan los 3 ficheros que he creado (makefile, sh.c y sh.h). Espero vuestros comentarios al respecto.

Makefile
sh.c
sh.h
shp.zip

Saturday, February 17, 2007

Problema 1: Reserva de memoria en C

(EDITADO: como me apuntaba el cremero (La Cremeriè) este programa compilado con el gcc en windows muestra la misma salida por pantalla para ambos printf, lo cual lleva a confusion, pero eso no quiere decir que no suceda lo que realmente sucede, que es lo que os pregunto, ¿que sucede? Para solucionar esto y que al compilar en windows os salga una salida diferente y esto no os desvie del verdadero problema he creado un codigo que lo que hace es meter una llamada a una funcion que como vereis no hace nada, pero esto provoca que ya si la salida de los 2 printf sea distinta. Ahora resolver el misterio es mucho mas facil ¿verdad? Espero vuestros correos y amplio el plazo de resolucion hasta el dia 28/02. Recordad la pregunta, porque pasa esto? Explicar con detalle)

Hola, que tal? Ya que estoy decidido a darle un poco de vidilla a este blog, voy a poner un problemita de programación en C. Este primero es muy sencillo y curioso. El siguiente codigo tiene un problema obvio, pero las preguntas que os hago son: ¿Cual es la salida por pantalla del siguiente codigo? ¿Por que sucede esto? (Explicar con el maximo detalle posible).


(EDITADO: Nuevo codigo)
#include <stdio.h>
#include <stdlib.h>

int *ptr()
{
int y;
y=3;
return &y;
}

void funcionquenohacenada()
{
int *x;

x=(int*)malloc(sizeof(int)*5);
free(x);
}

int main()
{
int *pointer, content;
pointer = ptr();
content = *pointer;
printf("%d\n", content);
funcionquenohacenada();
content = *pointer;
printf("%d\n", content);
return 0;
}
(FIN DE EDICION)

(EDITADO: codigo antiguo, no usar en windows)
#include <stdio.h>

int *ptr()
{
int y;
y=3;
return &y;
}

int main()
{
int *pointer, content;
pointer = ptr();
content = *pointer;
printf("%d\n", content);
content = *pointer;
printf("%d\n", content);
return 0;
}

Podeis compilarlo y ejecutarlo para ver que aunque aparentemente los 2 printf son similares la salida es distinta. Aunque lo suyo es intentar hacerlo sin programarlo. Mandar vuestras respuestas a mi direccion de correo que esta en mi perfil y dentro de una semana publicare el resultado y dire quien lo ha acertado. Como es muy sencillo imagino que todo el mundo lo acertara sin problemas.
(A partir de ahora dejare las entradas de los problemas sin comentarios para que mande cada uno su solucion al email y asi no se entorpezcan los unos con los otros)

Friday, February 16, 2007

Como hacer un shell

Buenas a todos. Quizas el titulo es demasiado pretencioso. No quiero dar una forma detallada de como hacer un shell, hoy en dia hacer un shell es una tonteria ya que existen muchos en el mercado que ademas hacen cosas increibles, pero vamos a hablar de los princpios que tiene todo shell. Repito que lo mejor no es hacer un shell, sino contribuir a la mejora de los que ya existen (de eso va el software libre).

Empecemos por definir un shell.
Como yo no soy muy elocuente, intentare definirlo atendiendo a su funcionalidad y espero que con eso sea suficiente para que todo el mundo lo entienda (si ya lo sabes podeis obviar esta seccion). Historicamente los ordenadores no tenian lo que se llamaban terminales de entrada o consola de entrada, y la unica salida era una impresora o una cinta. Las cosas avanzaron y se crearon los sistemas operativos que procesaban las ejecuciones de los procesos en forma de ejecucion por lotes (un trabajo tras otro). Segun evolucionaba todo y a la vez de disponer de teclados y pantallas era necesaria una forma de comunicarse con el sistema operativo de forma que le indicasemos que proceso debia ejecutar a continuacion. Esa forma arcaica de comunicacion entre el humano y la maquina la llevaba a cabo el shell (interprete de comandos). Para muchos es simplemente la pantallita negra con el cursor parpadeante. El shell era el encargado de hacer las llamadas al sistema operativo necesarias para que éste ejecutase un programa. Hoy en dia todo esto esta muy avanzado, los shell no solo permiten comunicarse con el SO para ejecutar un programa, ademas permiten la ejecucion en paralelo de varios programas, crear scripts propios para automatizar tareas, mandar la salida de un proceso como entrada de otro de forma automatica, etc.

Nuestro shell basico.
Dado que mi nombre empieza por P lo voy a llamar SHell de P, abreviado shp. Las funciones que va a tener serán las siguientes:
1. Debe coger la entrada de un comando mediante teclado, y ejecutar el comando en un proceso hijo
2. Normalmente el shell debe esperar a que el proceso hijo termine de ejecutarse para pedir otro comnado, pero si el ultimo parametro del comando es &, entonces no debe esperar, ejecutandose el shell y el comando de forma simultanea.
3. Vamos a incluir la ejecucion especial de comandos propios del shell, que seran cd para cambiar de directorio, export para crear variables de entorno y exit para terminar la ejecucion del shell.
4. Mediante una señal provocada por la combinacion de teclas CTRL+C (señal SIGINT) se imprimira por pantalla el historial de los ultimos comandos ejecutados (se podra especificar mediante una variable de entorno la longitud de esta lista, que por defecto sera de 10)
5. Mediante un nuevo comnado r num (numero de linea) se reejecutara un comando que este en el historial de ejecuciones, determinado por el numero de entrada (num).

Dados los requerimientos vamos a determinar unas limitaciones del shell:
1. Tanto la creacion de procesos, como el manejo de señales sera el propio de UNIX intentando acomodarse a los estandares POSIX. Es decir, el programa no sera portable a otros sistemas operativos. Yo lo he programado y probado en BSD y Linux.
2. El shell es muy basico y no contendra de momento funcionalidades tipicas de otros shell como redireccionamiento de entrada o salida estandar, ni lectura de ficheros, etc etc.

Vamos a desarrollar el shell en C, intentando que sea ANSI, aunque no se si habre metido la pata en este punto, si es asi agradecer vuestras correcciones.
Las herramientas que necesitamos son las siguientes:
Funcion fork() para crear procesos
Funcion waitpid(...) para esperar a la ejecucion de un proceso
Funcion sigacion(...) para la captura de la interrupcion ctrl+c
Funcion execvp(...) para la ejecucion de un comando
Y lo demas son funciones de la libreria estandar de C. Para ver la ayuda de como usar estos comandos, tanto en unix como linux teclead man + nombre del comando, y vereis como se usan.

Esta es una breve descripcion funcional del proceso:
La funcion main(donde empieza todo) se mete en un bucle infinito y llama la funcion setup. La funcion setup coge la entrada de teclado, la separa en un array de comandos y retorna. La funcion main determina si es un comando propio de shell o se debe ejecutar en la maquina. Si es de shell se ejecuta en una funcion propia. Si es de la maquina se crea un proceso hijo. El proceso hijo ejecuta el comando y el shell determina si debe esperar o seguir su ejecucion (parametro &). Vuelve a empezar el proceso. Si durante el proceso el usuario pulsa ctrl+c, se imprime el historial de ejeuciones y se continua el proceso donde se dejo.

Os animo a que si teneis tiempo lo programeis, se tarda muy poquito y es divertido. Como veis no he detallado totalmente como funcionara el shell, los detalles se dejan a la imaginacion de cada uno. Si quereis podeis añadir cosas nuevas. En la proxima entrada del blog dentro de unos dias publicare el codigo fuente que yo he creado, aunque quiza el vuestro sea mejor. Agradecere comentarios y sugerencias, a ver si asi consigo hacer de esto algo participativo (aunque dada mi popularidad en la blogosfera lo dudo mucho...)