Skip to main content.

03 Estructura y diseño <>

alarm.c
/**
 * Copyright (C) 2013 Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 *
 * Este programa es software libre: usted puede redistribuirlo y/o
 * modificarlo bajo los términos de la Licencia Pública General GNU
 * publicada por la Fundación para el Software Libre, ya sea la versión
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
 * misma.
 *
 * Este programa se distribuye con la esperanza de que sea útil, pero
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
 * Consulte los detalles de la Licencia Pública General GNU para obtener
 * una información más detallada.
 *
 * Debería haber recibido una copia de la Licencia Pública General GNU
 * junto a este programa.
 * En caso contrario, consulte <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Ejemplo de timeout utilizando la señal ALARM.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Una alarma corresponde a un tipo de señal, que llama a la función
 * asociada a dicha la señal después de que el tiempo para el cual se
 * programo la alarma. En este ejemplo se utiliza esto para programar
 * una función que terminará el proceso por timeout después de 5
 * segundos, tiempo que se programa para que ocurra la alarma. Si la
 * ejecución el proceso dura menos de 5 segundos (determinado por sleep)
 * entonces tendrá un termino normal.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall alarm.c -o alarm
 * 
 * Ejecutar con (analizar diferentes casos):
 *  $ time ./alarm
 *  $ time ./alarm 10
 *  $ time ./alarm 3
 *
 * Preguntas:
 * 1. ¿Qué hace la función signal?
 * 2. ¿Qué significa el parámetro SIGALRM en la función signal?
 * 3. ¿Cuándo se ejecuta la función timeout_exit?
 * 4. ¿Para qué es la función sleep?
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void timeout_exit () {
	printf("Terminando por timeout\n");
	exit(EXIT_FAILURE);
}

int main (int argc, char **argv) {
	/* Crear una señal de alarma para generar timeout */
	signal(SIGALRM, timeout_exit);
	alarm(5);
	/* "Pausar" la ejecución del proceso por X segundos */
	if(argc==2) sleep(atoi(argv[1]));
	/* Desactivar la alarma */
	alarm(0);
	/* Mostrar mensaje de termino normal */
	printf("Terminando con normalidad\n");
	/* Retorno del sistema */
	return EXIT_SUCCESS;
}

sigaction.c
/**
 * Copyright (C) 2014 Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 *
 * Este programa es software libre: usted puede redistribuirlo y/o
 * modificarlo bajo los términos de la Licencia Pública General GNU
 * publicada por la Fundación para el Software Libre, ya sea la versión
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
 * misma.
 *
 * Este programa se distribuye con la esperanza de que sea útil, pero
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
 * Consulte los detalles de la Licencia Pública General GNU para obtener
 * una información más detallada.
 *
 * Debería haber recibido una copia de la Licencia Pública General GNU
 * junto a este programa.
 * En caso contrario, consulte <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Ejemplo de como saber quien me envió una señal
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * Original: www.linuxprogrammingblog.com/code-examples/sigaction
 * 
 * Compilar con:
 *  $ gcc -Wall -pedantic sigaction.c -o sigaction
 * 
 * Ejecutar con:
 *  $ ./sigaction
 * 
 * Probar enviar señales SIGUSR1 o SIGUSR2 con:
 *  $ kill -SIGUSR1 PID
 *  $ kill -SIGUSR2 PID 
 */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

static void signal_handler (int signal, siginfo_t *info, void *context)
{
	printf ("Recibí la señal %d enviada por el proceso %d\n",
						signal, info->si_pid);
}
 
int main()
{
	struct sigaction action;
	printf("Soy el PID %d\n", getpid());
	action.sa_sigaction = &signal_handler;
	action.sa_flags = SA_SIGINFO;
	sigaction(SIGUSR1, &action, NULL);
	sigaction(SIGUSR2, &action, NULL);
	for(;;);
	return 0;
}
signal_catch.c
/**
 * Copyright (C) 2013 Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 *
 * Este programa es software libre: usted puede redistribuirlo y/o
 * modificarlo bajo los términos de la Licencia Pública General GNU
 * publicada por la Fundación para el Software Libre, ya sea la versión
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
 * misma.
 *
 * Este programa se distribuye con la esperanza de que sea útil, pero
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
 * Consulte los detalles de la Licencia Pública General GNU para obtener
 * una información más detallada.
 *
 * Debería haber recibido una copia de la Licencia Pública General GNU
 * junto a este programa.
 * En caso contrario, consulte <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Ejemplo de como capturar y manejar diferentes señales de término.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Nota: revisar explicación en signal_ignore.c
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall signal_catch.c -o signal_catch
 * 
 * Ejecutar con:
 *  $ ./signal_catch
 * 
 * Probar (verificar mensajes en archivo log que se genera):
 *  - Ctrl+c
 *  - Enviar señal TERM
 * 
 * Para terminar la ejecución:
 *  $ kill -9 PID
 *
 * Problema del ejemplo: si se presiona Ctrl+c dos veces el programa
 * se cierra, ¿por qué?, lo mismo ocurre al enviar dos veces la señal
 * TERM. Ejercicio propuesto: soluciónelo.
 *
 * Preguntas:
 * 1. ¿Cuándo se gatilla SIGINT?
 * 2. ¿Cuando se gatilla SIGTERM?
 * 3. ¿Por qué es necesario el for(;;);?
 * 4. ¿Que hace el comando kill?
 * 5. ¿Qué hace la señal KILL o -9?
 * 6. ¿Cuál es la diferencia entre los comándos kill y killall?
 *
 */

#include <signal.h>
#include <stdio.h>

void sigint_handler (int signal) {
	printf("\nTrataste de cerrarme con SIGINT/%d (Ctrl+c)!\n", signal);
}

void sigterm_handler (int signal) {
	printf("Recibí la señal SIGTERM/%d, pero no me cerraré!\n", signal);
}

int main (void) {
	/* Se ignorarán señales que pueden terminar el proceso */
	signal(SIGINT, sigint_handler);
	signal(SIGTERM, sigterm_handler);
	/* Hacer nada */
	for(;;);
	/* Retorno al sistema */
	return 0;
}
signal_ignore.c
/**
 * Copyright (C) 2013 Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 *
 * Este programa es software libre: usted puede redistribuirlo y/o
 * modificarlo bajo los términos de la Licencia Pública General GNU
 * publicada por la Fundación para el Software Libre, ya sea la versión
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
 * misma.
 *
 * Este programa se distribuye con la esperanza de que sea útil, pero
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
 * Consulte los detalles de la Licencia Pública General GNU para obtener
 * una información más detallada.
 *
 * Debería haber recibido una copia de la Licencia Pública General GNU
 * junto a este programa.
 * En caso contrario, consulte <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Ejemplo de como ignorar diferentes señales de término.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * SIGINT: Signal Interrupt
 * http://en.wikipedia.org/wiki/SIGINT_(POSIX)
 * Utilizada cuando el proceso es interrumpido por el usuario (Ctrl+c).
 * 
 * SIGHUP: Signal Hang Up
 * http://en.wikipedia.org/wiki/SIGHUP
 * Se envía al proceso cuando la terminal que controla al proceso, su
 * padre, es cerrada.
 * 
 * SIGTERM: Signal Terminate
 * http://en.wikipedia.org/wiki/SIGTERM
 * Enviada a un proceso al cual se le solicita terminal, caso por
 * defecto al utilizar kill sobre un proceso.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall signal_ignore.c -o signal_ignore
 * 
 * Ejecutar con:
 *  $ ./signal_ignore
 * 
 * Probar:
 *  - Ctrl+c
 *  - Cerrar la terminal (verificar con ps aux en otra que aun corre)
 *  - Enviar señal term
 * 
 * Para terminar la ejecución:
 *  $ kill -9 PID
 *
 * Ejercicio:
 *
 * Verificar antes de cerrar la terminal con pstree el proceso, ¿de quién es hijo?
 *
 * init─┬─ ...
 *      ├─konsole─┬─bash───signal_ignore
 *      ...
 *
 * Verificar después de cerrar la terminal con pstree el proceso, ¿de quién es hijo?
 *
 * init─┬─ ...
 *      ├─signal_ignore
 *      ...
 *
 * Fuente: http://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html
 *
 * Preguntas:
 * 1. ¿Por qué signal_ignore no fue cerrado al cerrar a su padre?
 * 2. ¿Qué función se ejecuta cuando la señal es envíada?
 * 3. De un ejemplo de porque puedo querer ignorar una señal.
 * 4. ¿Qué señal un proceso no puede ignorar?
 * 
 */

#include <signal.h>

int main (void) {
	/* Se ignorarán señales que pueden terminar el proceso */
	signal(SIGINT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	/* Hacer nada */
	for(;;);
	/* Retorno al sistema */
	return 0;
}
systemcall.c
/**
 * Copyright (C) 2013 Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 *
 * Este programa es software libre: usted puede redistribuirlo y/o
 * modificarlo bajo los términos de la Licencia Pública General GNU
 * publicada por la Fundación para el Software Libre, ya sea la versión
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
 * misma.
 *
 * Este programa se distribuye con la esperanza de que sea útil, pero
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
 * Consulte los detalles de la Licencia Pública General GNU para obtener
 * una información más detallada.
 *
 * Debería haber recibido una copia de la Licencia Pública General GNU
 * junto a este programa.
 * En caso contrario, consulte <http://www.gnu.org/licenses/gpl.html>.
 */

/**
 * Ejemplos de llamadas al sistema.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Las llamadas al sistema presentarán una interfaz para poder hacer
 * uso de los servicios del sistema operativo. Muchas veces no haremos
 * uso directo de las llamadas al sistema del sistema operativo, sino
 * que haremos uso de la API del lenguaje la cual será la que traducirá
 * los requerimientos a las llamadas de sistema correspondientes.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall systemcall.c -o systemcall
 * 
 * Ejecutar con:
 *  $ ./systemcall
 * 
 * Programa quedará en pausa (por la systemcall pause), para verificar
 *  $ ps aux | grep systemcall
 *  8212 pts/0    S+     0:00 ./systemcall
 * La S+ indica que el proceso está suspendido y que espera por un
 * suceso (que nunca llegará) enviar señal TERM para detener, se hace
 * mediante el comando kill que a su vez hace uso de la systemcall kill:
 *  $ kill -TERM 8212
 * Donde 8212 es el PID del proceso
 * 
 * Fuente: http://cursos.delaf.cl/archivos/cursos/sistemas-operativos/material-de-apoyo/Linux%20System%20Call%20Quick%20Reference.pdf
 *
 * Preguntas:
 * 1. ¿Qué hace la función syscall?
 * 2. ¿En qué archivo está declarada la constante SYS_getpid?
 * 3. ¿Qué hace la llamada a sistema SYS_pause?
 * 4. ¿Cuáles son las llamadas a sistema para trabajar con archivos?
 * 5. ¿Cómo sabe Linux qué código ejecutar para cada llamada a sistema de
 *    operaciones sobre dispositivos (específicamente de caracter)?
 * 
 */

#include <syscall.h>
#include <unistd.h>
#include <stdio.h>

void _getpid () {
	/* llamada al sistema usando directamente su número */
	printf("syscall(39)         = %d\n", syscall(39));
	/* llamada al sistema usando su constante */
	printf("syscall(SYS_getpid) = %d\n", syscall(SYS_getpid));
	/* llamada al sistema usando la función de la API de C */
	printf("getpid()            = %d\n", getpid());
	printf("\n");
}

void _getuid () {
	/* llamada al sistema usando su constante */
	printf ("syscall(SYS_getuid) = %d\n", syscall(SYS_getuid));
	printf("\n");
}

void _pause () {
	syscall(SYS_pause);
}

int main(void) {
	_getpid();
	_getuid();
	_pause();
	return 0;
}

04 Procesos <>

01 Paralelo <>

fork.c
/**
 * Ejemplo de creación de procesos pesados (hijos) usando fork.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * La creación de procesos pesados puede ser realizada de diferentes
 * formas, una de ellas corresponde a la usando fork, esta función
 * permitirá crear un hijo que será identico al padre en t=0, para luego
 * según se haya establecido en el programa seguir ejecutandose. Esto
 * creará un proceso pesado, con su propio PID y espacio en memoria.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall fork.c -o fork
 * 
 * Ejecutar con:
 *  $ ./fork
 *
 * Preguntas:
 * 1. ¿Qué pasa si no se utiliza wait?
 * 2. ¿Este programa se ejecuta en paralelo?
 * 2. En la implementación de un servidor web, se puede utilizar wait(). Justifique.
 * 3. Modificar el programa, de tal forma que si el status del proceso hijo es de fallo (hint: simular con modulo) el proceso padre deje de producir hijos.
 * 4. Modificar el programa, de tal forma que se ejecuten en paralelo los procesos hijos y el padre.
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define HIJOS 5

int main(void) {
	/* crear hijos */
	int i, processID;
	int status;
	for (i=0; i<HIJOS; ++i) {
		/* hace un fork al proceso creando un hijo */
		if ((processID = fork()) < 0) {
			perror("No se ha podido hacer fork()");
			exit(EXIT_FAILURE);
		}
		/* si este es el proceso padre */
		else if (processID > 0) {
			/* mostrar mensaje de nuevo hijo creado */
			printf("Creado nuevo hijo con PID %d\n", processID);
			wait(&status);
			printf("Terminando el proceso con PID %d y estado %d\n", processID, status);
		}
		/* si este es el proceso hijo */
		else if (processID == 0) {
			/* fin de la ejecución */
			printf("Mi padre es el proceso con PID %d\n", getppid());
			exit(EXIT_SUCCESS);
		}
	}
	/* retorno al sistema */
	return EXIT_SUCCESS;
}
thread.c
/**
 * Ejemplo de creación de procesos livianos (hebras).
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * La creación de un proceso liviado implicará que los procesos que se
 * ejecutan compartirán la memoria con entre ellos. Se compartirán
 * variables globales y cualquier otra variables local que sea pasada
 * mediante un puntero a los argumentos de la hebra. Para poder utilizar
 * estas variables compartidas de forma segura es necesario sincronizar,
 * es por esto que en este ejemplo se hace uso de un mutex para la
 * sincronización de la variable "threads".
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall thread.c -o thread -lpthread
 * 
 * Ejecutar con:
 *  $ ./thread
 *
 * Preguntas:
 * 1. ¿Ejecución en paralelo? Justifique.
 * 2. ¿Para qué es utilizada la estructura ThreadArgs?
 * 3. ¿Para que es el segundo parámetro de pthread_create? ¿Qué significa que sea NULL?
 * 4. Modificar el programa para que el valor de (int)status sea distinto a 0.
 * 5. ¿Se puede recuperar a través de pthread_join un valor de retorno diferente a int?
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define THREADS 5

typedef struct {
	int i;
} ThreadArgs;

void *ThreadMain (void *threadArgs);

int main(int argc, char * argv[]) {
	/* declaracion de variables */
	pthread_t threadID; /* thread id de pthread_create() */
	ThreadArgs *threadArgs; /* puntero a los argumentos del thread */
	int status;
	/* crear hebras */
	int i;
	for (i=0; i<THREADS; ++i) {
		/* crear espacio de memoria para los argumentos de la hebra */
		threadArgs = malloc(sizeof(ThreadArgs));
		/* colocar argumentos para la hebra */
		threadArgs->i = i;
		/* crear thread */
		pthread_create(&threadID, NULL, ThreadMain, (void *) threadArgs);
		/* mostrar mensaje de nueva hebra creada */
		printf("Creada nueva hebra con ID %ld\n", (long int) threadID);
		/* esperar por la hebra */
		pthread_join(threadID, (void *)&status);
		printf("Retorno de la hebra con ID %ld fue %d\n", (long int) threadID, (int) status);
		/* liberar memoria de los argumentos */
		free(threadArgs);
	}
	/* retorno al sistema */
	return EXIT_SUCCESS;
}

void *ThreadMain (void *threadArgs) {
	/* recuperar parámetros pasados a la hebra */
	ThreadArgs *args = (ThreadArgs *) threadArgs;
	/* fin de la conexion */
	printf("Fin de la hebra lanzada con i=%d, con ID %ld\n", args->i, pthread_self());
	/* terminar la hebra */
	return NULL;
}
thread_paralelo.c
/**
 * Ejemplo de creación de procesos livianos (hebras).
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * La creación de un proceso liviado implicará que los procesos que se
 * ejecutan compartirán la memoria con entre ellos. Se compartirán
 * variables globales y cualquier otra variables local que sea pasada
 * mediante un puntero a los argumentos de la hebra. Para poder utilizar
 * estas variables compartidas de forma segura es necesario sincronizar,
 * es por esto que en este ejemplo se hace uso de un mutex para la
 * sincronización de la variable "threads".
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall thread_paralelo.c -o thread_paralelo -lpthread
 * 
 * Ejecutar con:
 *  $ ./thread_paralelo
 *
 * Preguntas:
 * 1. ¿Por qué es necesario utilizar pthread_mutex_lock y pthread_mutex_unlock?
 * 2. Si no se utilizara pthread_mutex_lock y pthread_mutex_unlock ¿qué podría ocurrir con el proceso?
 * 3. ¿Qué pasa si no se utiliza pthread_detach?
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define THREADS 5

typedef struct {
	int i;
	int *threads;
	pthread_mutex_t *mutex;
} ThreadArgs;

void *ThreadMain (void *threadArgs);

int main(int argc, char * argv[]) {
	/* declaracion de variables */
	pthread_t threadID; /* thread id de pthread_create() */
	ThreadArgs *threadArgs; /* puntero a los argumentos del thread */
	/* crear hebras */
	int i;
	int threads = 0; /* contador para los threads */
	pthread_mutex_t mutex; /* para sección crítica "threads" */
	pthread_mutex_init (&mutex, NULL);
	for (i=0; i<THREADS; ++i) {
		/* aumentar contador de threads */
		pthread_mutex_lock (&mutex);
		++threads;
		pthread_mutex_unlock (&mutex);
		/* crear espacio de memoria para los argumentos de la hebra */
		threadArgs = malloc(sizeof(ThreadArgs));
		/* colocar argumentos para la hebra */
		threadArgs->i = i;
		threadArgs->threads = &threads;
		threadArgs->mutex = &mutex;
		/* crear thread */
		pthread_create(&threadID, NULL, ThreadMain, (void *) threadArgs);
		/* mostrar mensaje de nueva hebra creada */
		printf("Creada nueva hebra con ID %ld\n", (long int) threadID);
	}
	/* esperar a que los threads finalicen */
	while(threads>0) sleep(1);
	/* retorno al sistema */
	return EXIT_SUCCESS;
}

void *ThreadMain (void *threadArgs) {
	/* recuperar parámetros pasados a la hebra */
	ThreadArgs *args = (ThreadArgs *) threadArgs;
	/* marcar la hebra como separada, se liberaran los recursos cuando termine la hebra, no requiere hacer join */
	pthread_detach(pthread_self());
	/* fin de la conexion */
	printf("Fin de la hebra lanzada con i=%d, con ID %ld\n", args->i, pthread_self());
	/* decrementar contador de hebras */
	pthread_mutex_lock (args->mutex);
	--(*args->threads);
	pthread_mutex_unlock (args->mutex);
	/* terminar la hebra */
	return NULL;
}

02 IPC <>

mensajes.c
/**
 * Pronto
 */
 
pipes.c
/**
 * Pronto
 */
 
sockets.c
/**
 * Pronto
 */ 

05 Sincronizacion <>

c <>

contador_con_datarace.c
/**
 * Ejemplo de datarace.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * El problema de datarace ocurre al no realizar la sincronización de
 * los procesos (en este caso livianos, con hebras) al querer acceder
 * a una sección crítica (memoria compartida). El ejemplo mostrará (en
 * el peor de los casos) como el valor del contador puede ser totalmente
 * diferente.
 * 
 * En vez de utilizar la estructura para pasar las variables compartidas
 * (mediante punteros), se utilizarán variables globales (esto desde el
 * punto de vista de la correcta programación no se debe hacer) ya que
 * son más simples y se parecen más a los ejemplos vistos en clases.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall contador_con_datarace.c -o contador_con_datarace -lpthread
 * 
 * Ejecutar con:
 *  $ ./contador_con_datarace
 * 
 * Recordar que la situación que lleva al datarace es no determinística
 * por lo cual a veces podría dar un resultado correcto, se recomienda
 * ejecutar varias veces para ver que ocurre. Esto se puede hacer así:
 * 
 * $ for i in `seq 1 100`; do ./contador_con_datarace; done
 * 
 * Con "mala suerte" el resultado contendrá algo como:
 * contador = 100
 * contador = 100
 * [...]
 * contador = 96      <- ¡DATARACE!
 * contador = 100
 * [...]
 * contador = 100
 * 
 * Preguntas:
 * 1. ¿Por qué contador es sección crítica?
 * 2. ¿Cómo se evitan los datarace en threads?
 * 3. ¿Qué hace "while(threads>0) sleep(1);"?
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

/* prototipo de la función principal de las hebras */
void *ThreadMain (void *threadArgs);

/* variable para controlar0 y esperar a que terminen las hebras
   esto si está sincronizado (para evitar problemas, esta no es la
   sección crítica "interesante") */
int threads = 0;
pthread_mutex_t mutex;

/* variable compartida (esta será la sección crítica "interesante") */
int contador = 0;

/**
 * Programa principal (lanza hebras)
 */
int main(int argc, char * argv[]) {
	/* variables */
	pthread_t threadID; /* identificador de la hebra */
	int i; /* contador */
	/* inicializar mutex para hebras */
	pthread_mutex_init (&mutex, NULL);
	/* crear hebras */
	for (i=0; i<100; ++i) {
		/* aumentar contador de threads */
		pthread_mutex_lock(&mutex);
		++threads;
		pthread_mutex_unlock(&mutex);
		/* crear theread */
		pthread_create(&threadID, NULL, ThreadMain, NULL);
	}
	/* esperar a que los threads finalicen */
	while(threads>0) sleep(1);
	/* mostrar valor del contador */
	printf("contador = %d\n", contador);
	/* retorno al sistema */
	return EXIT_SUCCESS;
}

/**
 * Programa principal de cada hebra
 */
void *ThreadMain (void *threadArgs) {
	/* aumentar contador */
	contador = contador + 1;
	/* decrementar contador de hebras */
	pthread_mutex_lock(&mutex);
	--threads;
	pthread_mutex_unlock(&mutex);
	/* terminar la hebra */
	return NULL;
}
contador_sin_datarace.c
/**
 * Ejemplo de solución al problema de datarace.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Este ejemplo corresponde al problema "contador_con_datarace.c" pero
 * en este caso se ha realizado la sincronización para evitar el
 * datarace.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall contador_sin_datarace.c -o contador_sin_datarace -lpthread
 * 
 * Ejecutar con:
 * $ for i in `seq 1 100`; do ./contador_sin_datarace; done
 * 
 * El resultado esta vez deberá ser siempre:
 * contador = 100
 * 
 * Preguntas:
 * 1. ¿Qué implica el NULL en "pthread_mutex_init (&semaforo, NULL);"?
 * 2. ¿El mutex al ser inicializado está libre o tomado?
 * 3. ¿Qué sucedería si no tuvieramos "pthread_mutex_unlock(&semaforo);"?
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

/* prototipo de la función principal de las hebras */
void *ThreadMain (void *threadArgs);

/* variable para controlar y esperar a que terminen las hebras
   esto si está sincronizado (para evitar problemas, esta no es la
   sección crítica "interesante") */
int threads = 0;
pthread_mutex_t mutex;

/* variable compartida (esta será la sección crítica "interesante") */
int contador = 0;
pthread_mutex_t semaforo;

/**
 * Programa principal (lanza hebras)
 */
int main(int argc, char * argv[]) {
	/* variables */
	pthread_t threadID; /* identificador de la hebra */
	int i; /* contador */
	/* inicializar mutex para hebras */
	pthread_mutex_init (&mutex, NULL);
	/* crear hebras */
	for (i=0; i<100; ++i) {
		/* aumentar contador de threads */
		pthread_mutex_lock(&mutex);
		++threads;
		pthread_mutex_unlock(&mutex);
		/* crear theread */
		pthread_create(&threadID, NULL, ThreadMain, NULL);
	}
	/* esperar a que los threads finalicen */
	while(threads>0) sleep(1);
	/* mostrar valor del contador */
	printf("contador = %d\n", contador);
	/* retorno al sistema */
	return EXIT_SUCCESS;
}

/**
 * Programa principal de cada hebra
 */
void *ThreadMain (void *threadArgs) {
	int aux;
	/* aumentar contador */
	pthread_mutex_lock(&semaforo);
	aux = contador;
	contador = aux + 1;
	pthread_mutex_unlock(&semaforo);
	/* decrementar contador de hebras */
	pthread_mutex_lock(&mutex);
	--threads;
	pthread_mutex_unlock(&mutex);
	/* terminar la hebra */
	return NULL;
}

06 Planificación <>

prioridad.c
/**
 * Ejemplos de uso de la llamada a sistema nice para la modificación
 * de la prioridad estática de un proceso.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * La llamada a sistema nice permite modificar la prioridad estática de
 * un proceso, esta prioridad servirá como una referencia para iniciar
 * el cálculo la prioridad dinámica del proceso. Es esta última, la
 * prioridad dinámica, la que se utilizará para la planificación.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall prioridad.c -o prioridad
 * 
 * Ejecutar con (analizar cada caso por separado):
 *  $ ./prioridad
 *  $ nice -n 10 ./prioridad
 *  $ ./prioridad 5
 *  $ nice -n 7 ./prioridad 3
 */

#include <sys/time.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <stdio.h>

int main (int n, char* args[]) {
	/* crear variables para guardar las prioridades */
	int prioridadActual, prioridadNueva;
	/* determinar prioridad nueva */
	if(n==2) prioridadNueva = atoi(args[1]);
	else prioridadNueva = 0;
	/* mostrar prioridad con que se llamo al proceso */
	prioridadActual = getpriority(PRIO_PROCESS, 0) + 120;
	printf("Prioridad original: %d\n", prioridadActual);
	/* cambiar prioridad y mostrarla */
	setpriority(PRIO_PROCESS, 0, prioridadNueva);
	prioridadActual = getpriority(PRIO_PROCESS, 0) + 120;
	printf("Prioridad cambiada: %d\n", prioridadActual);
	/* salir del programa :-) */
	return EXIT_SUCCESS;
}

07 Memoria <>

malloc.c
/**
 * Ejemplo de solicitud de memoria dinámica utilizando malloc
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Un programa esta formado por dos partes: código y datos, esto último
 * corresponde a los datos estáticos del programa. Aquel espacio para
 * datos que es solicitado de forma dinámica a medida que el programa
 * se va ejecutando se conoce como memoria dinámica. En este ejemplo se
 * podrá ver como pedir memoria dinámica y el crecimiendo del espacio
 * de memoria que tiene asignada el proceso.
 * 
 * Compilar con:
 *  $ gcc -ansi -pedantic -Wall malloc.c -o malloc
 * 
 * Ejecutar con:
 *  $ ./malloc
 * 
 * Una vez que se ejecute las opciones son:
 *	B	para pedir 1 byte
 *	KB	para pedir 1 kilobyte
 *	MB	para pedir 1 megabyte
 *	GB	para pedir 1 gigabyte
 *	0	para salir del programa
 * 
 * Una vez ejecutado el programa verificar la memoria que usa con:
 *  $ pmap `pidof malloc`
 * Debería indicar algo como: total             3928K
 * Ingresar 3 veces MB en las opciones del menú del programa y volver a
 * verificar la memoria utilizada, ahora debería mostrar algo como:
 *  total             7012K (ahora el proceso ha solicitado más memoria)
 * 
 * Preguntas:
 * 1. La memoria que se está solicitando, ¿dónde es almacenada?
 * 2. ¿Cuál es la diferencia entre malloc y calloc?
 * 3. Al listar la memoria con pmap aparecen líneas como:
 *    00007fb25b168000   1524K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
 *    ¿a qué corresponden estás líneas?
 * 4. ¿Qué es un "memory leak"?
 * 5. Si descomenta la línea free(puntero); y verifica la memoria usando
 *    pmap al ir pidiendo memoria verá que después de ciertas
 *    solicitudes la memoria solicitada no aumenta, ¿a qué se debe esto?
 * 6. En algunos lenguajes, como Java, existe un "garbage collector",
 *    ¿en qué consiste? ¿qué ventajas entrega?
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define B 1
#define KB B*1024
#define MB KB*1024
#define GB MB*1024

int main (void) {
	/* variables */
	char tamanio[2];
	short int salir = 0;
	char *puntero = NULL;
	/* ciclo "infinito" para el menú */
	while(!salir) {
		/* solicitar tamaño de memoria a cargar */
		printf("B = 1 byte\n");
		printf("KB = 1 kilobyte\n");
		printf("MB = 1 megabyte\n");
		printf("GB = 1 gigabyte\n");
		printf("¿Cuánta memoria desea solicitar? (0 para salir): ");
		scanf("%s", tamanio);
		printf("\n");
		/* si es 0 se cierra el programa */
		if(!strcmp(tamanio, "0")) {
			salir = 1;
			continue;
		}
		/* pedir la memoria solicitada */
		if(!strcmp(tamanio, "B")) puntero = (char *) malloc(B);
		else if(!strcmp(tamanio, "KB")) puntero = (char *) malloc(KB);
		else if(!strcmp(tamanio, "MB")) puntero = (char *) malloc(MB);
		else if(!strcmp(tamanio, "GB")) puntero = (char *) malloc(GB);
		/* mostrar información del puntero */
		printf("Puntero creado = %ld\n", (long int)puntero);
		/* liberar memoria del puntero */
/*		free(puntero); */
		/* salto de línea para separar ejecuciones */
		printf("\n");
	}
	return EXIT_SUCCESS;
}

mmap.c
/**
 * Pendiente...
 */
sbrk.c
/**
 * Pendiente...
 */

10_Módulos_en_Linux <>

01_Hola_Mundo <>

Makefile
# Makefile para el módulo holamundo.ko
obj-m = holamundo.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.o *.order *.symvers .tmp* .holamundo*
holamundo.c
/**
 * Ejemplo de Hola Mundo, versión módulo de Linux.
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Un módulo corresponde a código que será añadida en tiempo de
 * ejecución al área de memoria del sistema operativo, por lo cual los
 * módulos funcionarán en modo núcleo.
 * 
 * Para poder compilar un módulo se requieren las cabeceras del núcleo,
 * en un sistema Debian GNU/Linux (o Ubuntu) las instalamos con:
 * 
 *  # apt-get install linux-headers-`uname -r`
 * 
 * Teniendo el archivo Makefile y este fuente en el mismo directorio, se
 * compila con:
 *  $ make
 * 
 * Cargar con:
 *  # insmod holamundo.ko
 * Verificar que cargó:
 *  $ dmesg | tail
 *  [...]
 *  [12334.283059] Hola Mundo!
 * 
 * Descargar con: 
 *  # rmmod holamundo.ko
 * Verificar que descargó:
 *  $ dmesg | tail
 *  [...]
 *  [12446.529927] Chao Mundo!
 */

#include <linux/module.h>	/* Requerido por todos los módulos */
#include <linux/init.h>		/* Para usar module_init y module_exit */
#include <linux/kernel.h>	/* Para usar printk */

/* Información del módulo */
MODULE_AUTHOR("Esteban De La Fuente Rubio");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Ejemplo de Hola Mundo!");

/* Prototipos de funciones */
static int module_load (void);
static void module_unload (void);

/* Mapeo de funciones para carga y descarga */
module_init(module_load);	/* Carga con insmod */
module_exit(module_unload);	/* Descarga con rmmod */
 
/* Implementación de las funciones */

static int module_load (void) {
     printk(KERN_INFO "Hola Mundo!\n");
     return 0;			/* 0 ok, !0 error en la carga */
}

static void module_unload (void) {
     printk(KERN_INFO "Chao Mundo!\n");
}

02_Tarea_dispositivo_memory_2012-2 <>

Makefile
# Makefile para el módulo memory.ko
obj-m = memory.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.o *.order *.symvers .tmp* .memory*
memory.c
/**
 * Ejemplo de módulo para un dispositivo virtual
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Para ver como compilar el módulo y cargarlo revisar ejemplo de módulo
 * holamundo.c que encontrará disponible en:
 * http://cursos.delaf.cl/cursos/sistemas-operativos/codigo#01holamundo
 * 
 * En /dev se encuentran los dispositivos que el sistema operativo puede
 * manipular. El ejemplo creará un dispositivo /dev/memory sobre el cual
 * y utilizando este módulo se podrá escribir y luego rescatar lo que se
 * haya escrito.
 * 
 * Ejemplo de uso:
 *  # echo hola mundo > /dev/memory
 *  # cat /dev/memory
 *  hola mundo
 * 
 * Existen básicamente dos tipos de dispositivos:
 *  - De caracter (identificados por una "c" al hacer ls -l): son
 *    dispositivos que son accesados, típicamente, en orden secuencial.
 *    No utilizan buffers y se accede directamente al dispositivo.
 *    Se escriben y leen bytes.
 *    Son más simples de trabajar que los de bloque (con read y write).
 *  - De bloques (identificados por una "b" al hacer ls -l): son
 *    dispositivos que deben poder ser accesados de forma aleatoria.
 *    Utilizan buffering, por lo cual se deben acceder a través de una
 *    cache.
 *    Se escriben y leen bloques de datos.
 *    Son más complicados de utilizar que los de caracter.
 * http://www.cab.u-szeged.hu/linux/doc/khg/node18.html
 * 
 * Los números mayores y menores se pueden observar entre el grupo y la
 * fecha, ejemplo:
 * 
 * brw-rw---T 1 root disk 8, 0 jun 14 10:30 /dev/sda
 * 
 *  - Número mayor 8, usado para identificar el módulo que procesará el
 *    dispositivo.
 *  - Número menor 0, usado para identificar diferentes dispositivos que
 *    son atendidos por el mismo módulo.
 * http://www.makelinux.net/ldd3/chp-3-sect-2
 * 
 * Para facilidad de la programación el dispositivo /dev/memory será un
 * dispositivo de tipo caracter y tendrá un tamaño fijo, de tal forma
 * que se deberá pasar una cantidad limitada de caracteres para
 * almacenar. Adicionalmente, y para disminuir la complejidad del
 * ejercicio, no se realizarán accesos concurrentes al dispositivo, o
 * sea, solo un comando echo se ejecutará al mismo tiempo sobre
 * /dev/memory, lo mismo para cat.
 * 
 * echo hará uso de la llamada a sistema write para escribir
 * cat hará uso de la llamada a sistema read para leer
 *
 * Por lo anterior se deberán implementar las funciones que se mapearan
 * a las llamadas a sistema write y read.
 * 
 * Para crear el dispositivo de caracter con número mayor 70 y menor 0
 * utilizar:
 *  # mknod /dev/memory c 70 0
 *  $ ls -lh /dev/memory 
 *  crw-r--r-- 1 root root 70, 0 nov  8 22:56 /dev/memory
 * 
 * Antes de poder utilizar echo y cat sobre el dispositivo recordar
 * cargar el módulo (previa compilación del mismo), algo como:
 *  $ make
 *  # insmod memory.ko
 *  $ dmesg
 *  [...]
 *  [ 6684.586869] Cargando modulo memory
 *  [ 6684.586879] Modulo memory cargado correctamente
 *  [ 6684.586883] Numero mayor en uso para el dispositivo: 70
 * 
 * Nota: este ejemplo está incompleto a propósito, el estudiante deberá
 * completar el código de tal forma que el módulo funcione como se ha
 * explicado.
 */

// Bibliotecas
#include <linux/module.h>	// Requerido por todos los módulos
#include <linux/init.h>		// module_init y module_exit
#include <linux/kernel.h>	// printk y KERN_INFO
#include <linux/fs.h>		// struct file_operations
#include <linux/slab.h>		// kmalloc
#include <asm/uaccess.h>	// copy_to_user y copy_from_user

// Definiciones
#define BUFFER_SIZE 100		// Tamaño máximo del buffer (dispositivo)

// Información del módulo
MODULE_AUTHOR("Esteban De La Fuente Rubio");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Ejemplo simple de un buffer");
MODULE_SUPPORTED_DEVICE("/dev/memory");

// Prototipos de funciones
static int module_load (void);
static void module_unload (void);
int device_open (struct inode *inode, struct file *filp);
int device_release (struct inode *inode, struct file *filp);
ssize_t device_read (struct file *filp, char *buf, size_t length, loff_t *offset);
ssize_t device_write (struct file *filp, const char *buf, size_t length, loff_t *offset);

// Estructura con funciones para manejar el dispositivo
// Se hace un mapeo entre las llamadas a sistema y las funciones definidas
struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write
};

// Mapeo de funciones para carga y descarga
module_init(module_load);	// Carga con insmod
module_exit(module_unload);	// Descarga con rmmod

// Variables globales
short major = 70;			// Número mayor que identifica el módulo
char *buffer = NULL;		// Este ES el dispositivo virtual
char *pBuffer = NULL;		// Puntero para recorrer el buffer
 
/**
 * Función llamada al cargar el módulo:
 *  - Registra el dispositivo de caracter con register_chrdev
 *  - Verifica que no existió error al registrar
 *  - Solicita memoria para el buffer con kmalloc en el área del núcleo
 *  - Verifica que no hubo error al solicitar memoria
 *  - Inicializa el buffer en 0 con memset
 *  - Genera mensaje de tipo KERN_INFO
 * @return 0 en caso de éxito, -1 en caso de error
 */
static int module_load (void) {
	// mensaje del kernel
	printk(KERN_INFO "Cargando modulo memory\n");
	// registrar el dispositivo de caracter
	// http://www.fsl.cs.sunysb.edu/kernel-api/re941.html
	// [...]
	// verificar que no ha habido error (retorno negativo) al registrar
	// el dispositivo
	// [...]
	// solicitar memoria para el buffer
	// http://www.fiveanddime.net/man-pages/kmalloc.9.html
	// [...]
	// en caso de que no se haya asignado memoria error
	// [...]
	// inicializar en ceros la memoria
	// $ man memset
	// [...]
	// mensaje indicando que el modulo se cargó
	printk(KERN_INFO "Modulo memory cargado correctamente\n");
	printk(KERN_INFO "Numero mayor en uso para el dispositivo: %d\n", major);
	// retornar todo ok
	return 0;
}

/**
 * Función llamada al descargar el módulo
 *  - Desregistra el dispositivo de caracter con unregister_chrdev
 *  - Libera la memoria solicitada para el buffer con kfree
 *  - Genera mensaje de tipo KERN_INFO
 */
static void module_unload (void) {
	// desregistrar
	// [...]
	// liberar memoria
	// [...]
	// mensaje indicando que se descargó el módulo
	printk(KERN_INFO "Modulo memory descargado\n");
}

/**
 * Función que abre el dispositivo:
 *  - Pone el puntero del buffer al inicio del buffer
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo que se abrirá
 * @return Siempre 0, ya que no se hace nada especial
 */
int device_open (struct inode *inode, struct file *filp) {
	// [...]
	return 0;
}

/**
 * Función que abre el dispositivo
 * No se hace nada, ya que el dispositivo esta en RAM
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo abierto
 * @return Siempre 0, ya que no se hace nada especial
 */
int device_release (struct inode *inode, struct file *filp) {
	return 0;
}

/**
 * Función que lee desde el dispositivo:
 *  - Verifica que el puntero este en una posición válida
 *  - Copia desde el dispositivo virtual al buffer entregado por el usuario
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso (no es de fiar, para copiar usar put_user)
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes leídos desde el dispositivo
 */
ssize_t device_read (struct file *filp, char *buff, size_t length, loff_t *offset) {
	int copied = 0;
	// si no hay más datos entonces es fin de archivo (EOF)
	// [...]
	// copiar
	// [...]
	printk(KERN_INFO "Se han leido %d bytes\n", copied);
	return copied;
}

/**
 * Función que escribe en el dispositivo
 * Copia desde el buffer entregado por el usuario al dispositivo (sobre escribiendo)
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes escritos en el dispositivo
 */
ssize_t device_write (struct file *filp, const char *buff, size_t length, loff_t *offset) {
	int copied = 0;
	memset(buffer, 0, BUFFER_SIZE);
	// copiar
	// [...]
	printk(KERN_INFO "Se han escrito %d bytes\n", copied);
	return copied;
}

03_Solución_tarea_dispositivo_memory_2012-2 <>

Makefile
# Makefile para el módulo memory.ko
obj-m = memory.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.o *.order *.symvers .tmp* .memory*
memory.c
/**
 * Ejemplo de módulo para un dispositivo virtual
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Para ver como compilar el módulo y cargarlo revisar ejemplo de módulo
 * holamundo.c que encontrará disponible en:
 * http://cursos.delaf.cl/cursos/sistemas-operativos/codigo#01holamundo
 * 
 * En /dev se encuentran los dispositivos que el sistema operativo puede
 * manipular. El ejemplo creará un dispositivo /dev/memory sobre el cual
 * y utilizando este módulo se podrá escribir y luego rescatar lo que se
 * haya escrito.
 * 
 * Ejemplo de uso:
 *  # echo hola mundo > /dev/memory
 *  # cat /dev/memory
 *  hola mundo
 * 
 * Existen básicamente dos tipos de dispositivos:
 *  - De caracter (identificados por una "c" al hacer ls -l): son
 *    dispositivos que son accesados, típicamente, en orden secuencial.
 *    No utilizan buffers y se accede directamente al dispositivo.
 *    Se escriben y leen bytes.
 *    Son más simples de trabajar que los de bloque (con read y write).
 *  - De bloques (identificados por una "b" al hacer ls -l): son
 *    dispositivos que deben poder ser accesados de forma aleatoria.
 *    Utilizan buffering, por lo cual se deben acceder a través de una
 *    cache.
 *    Se escriben y leen bloques de datos.
 *    Son más complicados de utilizar que los de caracter.
 * http://www.cab.u-szeged.hu/linux/doc/khg/node18.html
 * 
 * Los números mayores y menores se pueden observar entre el grupo y la
 * fecha, ejemplo:
 * 
 * brw-rw---T 1 root disk 8, 0 jun 14 10:30 /dev/sda
 * 
 *  - Número mayor 8, usado para identificar el módulo que procesará el
 *    dispositivo.
 *  - Número menor 0, usado para identificar diferentes dispositivos que
 *    son atendidos por el mismo módulo.
 * http://www.makelinux.net/ldd3/chp-3-sect-2
 * 
 * Para facilidad de la programación el dispositivo /dev/memory será un
 * dispositivo de tipo caracter y tendrá un tamaño fijo, de tal forma
 * que se deberá pasar una cantidad limitada de caracteres para
 * almacenar. Adicionalmente, y para disminuir la complejidad del
 * ejercicio, no se realizarán accesos concurrentes al dispositivo, o
 * sea, solo un comando echo se ejecutará al mismo tiempo sobre
 * /dev/memory, lo mismo para cat.
 * 
 * echo hará uso de la llamada a sistema write para escribir
 * cat hará uso de la llamada a sistema read para leer
 *
 * Por lo anterior se deberán implementar las funciones que se mapearan
 * a las llamadas a sistema write y read.
 * 
 * Para crear el dispositivo de caracter con número mayor 70 y menor 0
 * utilizar:
 *  # mknod /dev/memory c 70 0
 *  $ ls -lh /dev/memory 
 *  crw-r--r-- 1 root root 70, 0 nov  8 22:56 /dev/memory
 * 
 * Antes de poder utilizar echo y cat sobre el dispositivo recordar
 * cargar el módulo (previa compilación del mismo), algo como:
 *  $ make
 *  # insmod memory.ko
 *  $ dmesg
 *  [...]
 *  [ 6684.586869] Cargando modulo memory
 *  [ 6684.586879] Modulo memory cargado correctamente
 *  [ 6684.586883] Numero mayor en uso para el dispositivo: 70
 */

// Bibliotecas
#include <linux/module.h>	// Requerido por todos los módulos
#include <linux/init.h>		// module_init y module_exit
#include <linux/kernel.h>	// printk y KERN_INFO
#include <linux/fs.h>		// struct file_operations
#include <linux/slab.h>		// kmalloc
#include <asm/uaccess.h>	// copy_to_user y copy_from_user

// Definiciones
#define BUFFER_SIZE 100		// Tamaño máximo del buffer (dispositivo)

// Información del módulo
MODULE_AUTHOR("Esteban De La Fuente Rubio");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Ejemplo simple de un buffer");
MODULE_SUPPORTED_DEVICE("/dev/memory");

// Prototipos de funciones
static int module_load (void);
static void module_unload (void);
int device_open (struct inode *inode, struct file *filp);
int device_release (struct inode *inode, struct file *filp);
ssize_t device_read (struct file *filp, char *buf, size_t length, loff_t *offset);
ssize_t device_write (struct file *filp, const char *buf, size_t length, loff_t *offset);

// Estructura con funciones para manejar el dispositivo
// Se hace un mapeo entre las llamadas a sistema y las funciones definidas
struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write
};

// Mapeo de funciones para carga y descarga
module_init(module_load);	// Carga con insmod
module_exit(module_unload);	// Descarga con rmmod

// Variables globales
short major = 70;			// Número mayor que identifica el módulo
char *buffer = NULL;		// Este ES el dispositivo virtual
char *pBuffer = NULL;		// Puntero para recorrer el buffer
 
/**
 * Función llamada al cargar el módulo:
 *  - Registra el dispositivo de caracter con register_chrdev
 *  - Verifica que no existió error al registrar
 *  - Solicita memoria para el buffer con kmalloc en el área del núcleo
 *  - Verifica que no hubo error al solicitar memoria
 *  - Inicializa el buffer en 0 con memset
 *  - Genera mensaje de tipo KERN_INFO
 * @return 0 en caso de éxito, -1 en caso de error
 */
static int module_load (void) {
	short status;
	// mensaje del kernel
	printk(KERN_INFO "Cargando modulo memory\n");
	// registrar el dispositivo de caracter
	// http://www.fsl.cs.sunysb.edu/kernel-api/re941.html
	status = register_chrdev (
		major,		// número mayor
		"memory",	// nombre para los dispositivos
		&fops		// operaciones asociadas al dispositivo (el "mapeo")
	);
	// verificar que no ha habido error (retorno negativo) al registrar
	// el dispositivo
	if(status < 0) {
		printk(KERN_INFO "A ocurrido un error al ejecutar register_chrdev\n");
		return status;
	}
	// solicitar memoria para el buffer
	// http://www.fiveanddime.net/man-pages/kmalloc.9.html
	buffer = kmalloc (
		BUFFER_SIZE,	// Tamaño de la memoria solicitada
		GFP_KERNEL		// Solicita memoria en el espacio del núcleo
	);
	// en caso de que no se haya asignado memoria error
	if(buffer==NULL) {
		printk(KERN_INFO "Ha ocurrido un error al asignar memoria con kmalloc\n");
		return -1;
	}
	// inicializar en ceros la memoria
	// $ man memset
	memset(buffer, 0, BUFFER_SIZE);
	// mensaje indicando que el modulo se cargó
	printk(KERN_INFO "Modulo memory cargado correctamente\n");
	printk(KERN_INFO "Numero mayor en uso para el dispositivo: %d\n", major);
	// retornar todo ok
	return 0;
}

/**
 * Función llamada al descargar el módulo
 *  - Desregistra el dispositivo de caracter con unregister_chrdev
 *  - Libera la memoria solicitada para el buffer con kfree
 *  - Genera mensaje de tipo KERN_INFO
 */
static void module_unload (void) {
	// desregistrar
	unregister_chrdev(major, "memory");
	// liberar memoria
	kfree(buffer);
	// mensaje indicando que se descargó el módulo
	printk(KERN_INFO "Modulo memory descargado\n");
}

/**
 * Función que abre el dispositivo:
 *  - Pone el puntero del buffer al inicio del buffer
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo que se abrirá
 * @return Siempre 0, ya que no se hace nada especial
 */
int device_open (struct inode *inode, struct file *filp) {
	pBuffer = buffer;
	return 0;
}

/**
 * Función que abre el dispositivo
 * No se hace nada, ya que el dispositivo esta en RAM
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo abierto
 * @return Siempre 0, ya que no se hace nada especial
 */
int device_release (struct inode *inode, struct file *filp) {
	/*pBuffer = NULL;*/
	return 0;
}

/**
 * Función que lee desde el dispositivo:
 *  - Verifica que el puntero este en una posición válida
 *  - Copia desde el dispositivo virtual al buffer entregado por el usuario
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso (no es de fiar, para copiar usar put_user)
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes leídos desde el dispositivo
 */
ssize_t device_read (struct file *filp, char *buff, size_t length, loff_t *offset) {
	int copied = 0;
	// si no hay más datos entonces es fin de archivo (EOF)
	if (!(*pBuffer)) return 0;
	// copiar
	while (length && *pBuffer)  {
		put_user(*(pBuffer++), buff++);
		length--;
		copied++;
	}
	printk(KERN_INFO "Se han leido %d bytes\n", copied);
	return copied;
}

/**
 * Función que escribe en el dispositivo
 * Copia desde el buffer entregado por el usuario al dispositivo (sobre escribiendo)
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes escritos en el dispositivo
 */
ssize_t device_write (struct file *filp, const char *buff, size_t length, loff_t *offset) {
	int copied = 0;
	memset(buffer, 0, BUFFER_SIZE);
	// copiar
	while(length && copied<BUFFER_SIZE) {
		buffer[copied] = buff[copied];
		copied++;
		length--;
	}
	printk(KERN_INFO "Se han escrito %d bytes\n", copied);
	return copied;
}

04_Tarea_dispositivo_memory_2013-1 <>

Makefile
# Makefile para el módulo memory.ko
obj-m = memory.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.o *.order *.symvers .tmp* .memory*
memory.c
/**
 * Ejemplo de módulo para un dispositivo virtual
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Para ver como compilar el módulo y cargarlo revisar ejemplo de módulo
 * holamundo.c que encontrará disponible en:
 * http://cursos.delaf.cl/cursos/sistemas-operativos/codigo#01holamundo
 * 
 * En /dev se encuentran los dispositivos que el sistema operativo puede
 * manipular. Para este ejemplo se crearán varios dispositivo /dev/memoryX,
 * donde la X>=0, sobre los cuales y utilizando este módulo se podrá escribir
 * y luego rescatar lo que se haya escrito. Cada dispositivo /dev/memoryX tendrá
 * su propio número menor, donde si es diferente será tratado como un buffer
 * diferente.
 * 
 * Ejemplo de uso:
 *  # echo hola mundo > /dev/memory0
 *  # echo chao mundo > /dev/memory1
 *  # cat /dev/memory0
 *  hola mundo
 *  # cat /dev/memory1
 *  chao mundo
 * 
 * Existen básicamente dos tipos de dispositivos:
 *  - De caracter (identificados por una "c" al hacer ls -l): son
 *    dispositivos que son accesados, típicamente, en orden secuencial.
 *    No utilizan buffers y se accede directamente al dispositivo.
 *    Se escriben y leen bytes.
 *    Son más simples de trabajar que los de bloque (con read y write).
 *  - De bloques (identificados por una "b" al hacer ls -l): son
 *    dispositivos que deben poder ser accesados de forma aleatoria.
 *    Utilizan buffering, por lo cual se deben acceder a través de una
 *    cache.
 *    Se escriben y leen bloques de datos.
 *    Son más complicados de utilizar que los de caracter.
 * http://www.cab.u-szeged.hu/linux/doc/khg/node18.html
 * 
 * Los números mayores y menores se pueden observar entre el grupo y la
 * fecha, ejemplo:
 * 
 * brw-rw---T 1 root disk 8, 0 jun 14 10:30 /dev/sda
 * 
 *  - Número mayor 8, usado para identificar el módulo que procesará el
 *    dispositivo.
 *  - Número menor 0, usado para identificar diferentes dispositivos que
 *    son atendidos por el mismo módulo.
 * http://www.makelinux.net/ldd3/chp-3-sect-2
 * 
 * Para facilidad de la programación el dispositivo /dev/memoryX será un
 * dispositivo de tipo caracter y tendrá un tamaño fijo, de tal forma
 * que se deberá pasar una cantidad limitada de caracteres para
 * almacenar. Adicionalmente, y para disminuir la complejidad del
 * ejercicio, no se realizarán accesos concurrentes al dispositivo, o
 * sea, solo un comando echo se ejecutará al mismo tiempo sobre un
 * /dev/memoryX, lo mismo para cat.
 * 
 * echo hará uso de la llamada a sistema write para escribir
 * cat hará uso de la llamada a sistema read para leer
 *
 * Se deberá modificar el código de tal forma de que permita utilizar un buffer
 * por cada número menor que pueda tener un dispositivo. No puede poner un
 * límite a la cantidad de dispositivos con número menor diferente (o buffers).
 * Se recomienda revisar el código "03_Solución_tarea_dispositivo_memory_2012-2"
 * disponible en:
 * http://cursos.delaf.cl/cursos/sistemas-operativos/codigo
 * 
 * Para crear el dispositivo de caracter con número mayor 70 y menor 0
 * utilizar:
 *  # mknod /dev/memory0 c 70 0
 *  $ ls -lh /dev/memory0 
 *  crw-r--r-- 1 root root 70, 0 nov  8 22:56 /dev/memory0
 * De la misma forma crear más dispositivos con otros números menores.
 * 
 * Antes de poder utilizar echo y cat sobre el dispositivo recordar
 * cargar el módulo (previa compilación del mismo), algo como:
 *  $ make
 *  # insmod memory.ko
 *  $ dmesg
 *  [...]
 *  [ 6684.586869] Cargando modulo memory
 *  [ 6684.586879] Modulo memory cargado correctamente
 *  [ 6684.586883] Numero mayor en uso para el dispositivo: 70
 */

/*
Resumen de comando para descargar, compilar, cargar y verificar:
clear; sudo rmmod memory; make clean all; sudo insmod memory.ko; dmesg | tail
*/

/* Bibliotecas */
#include <linux/module.h>	/* requerido por todos los módulos */
#include <linux/init.h>		/* module_init y module_exit */
#include <linux/kernel.h>	/* printk y KERN_INFO */
#include <linux/fs.h>		/* struct file_operations */
#include <linux/slab.h>		/* kmalloc */
#include <asm/uaccess.h>	/* copy_to_user y copy_from_user */

/* Definiciones */
#define DEV_MAJOR 70		/* número mayor que identifica el módulo */
#define BUFFER_SIZE 100		/* tamaño máximo del buffer (dispositivo) */
#define DEV_MAXS ((128*1024)/(BUFFER_SIZE)) /* límite de kmalloc es 128 kB */

/* Información del módulo */
MODULE_AUTHOR ("Esteban De La Fuente Rubio");
MODULE_LICENSE ("GPL");
MODULE_DESCRIPTION ("Ejemplo de múltiples buffers virtuales");
MODULE_SUPPORTED_DEVICE ("/dev/memory*");

/* Prototipos de funciones */
static int module_load (void);
static void module_unload (void);
int device_open (struct inode *inode, struct file *filp);
int device_release (struct inode *inode, struct file *filp);
ssize_t device_read (struct file *filp, char *buf, size_t length,
			loff_t *offset);
ssize_t device_write (struct file *filp, const char *buf, size_t length,
			loff_t *offset);

/* Estructura con funciones para manejar el dispositivo. Se hace un mapeo entre
las llamadas a sistema y las funciones definidas */
struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write
};

/* Mapeo de funciones para carga y descarga */
module_init (module_load);	/* Carga con insmod */
module_exit (module_unload);	/* Descarga con rmmod */

/* Variables globales */
char *buffer = NULL;		/* este ES el dispositivo virtual */
char *pBuffer = NULL;		/* puntero para recorrer el buffer */
int devs_actual_count = 0;	/* cantidad actual de dispositivos */
 
/**
 * Función llamada al cargar el módulo:
 *  - Registra el dispositivo de caracter con register_chrdev
 *  - Verifica que no existió error al registrar
 *  - Genera mensaje de tipo KERN_INFO
 * @return 0 en caso de éxito, -1 en caso de error
 */
static int module_load (void)
{
	/* variable para el estado devuelto por register_chrdev */
	short status = -1;
	/* mensaje del kernel */
	printk (KERN_INFO "Cargando modulo memory\n");
	/* registrar el dispositivo de caracter
	http://www.fsl.cs.sunysb.edu/kernel-api/re941.html */
	/* [...] */
	/* verificar que no ha habido error (retorno negativo) al registrar el
	dispositivo */
	/* [...] */
	/* mensaje indicando que el modulo se cargó */
	printk (KERN_INFO "Modulo memory cargado correctamente\n");
	printk (KERN_INFO "Numero mayor en uso para el dispositivo: %d\n",
	       DEV_MAJOR);
	/* retornar todo ok */
	return 0;
}

/**
 * Función llamada al descargar el módulo
 *  - Desregistra el dispositivo de caracter con unregister_chrdev
 *  - Libera la memoria solicitada para el buffer con kfree
 *  - Genera mensaje de tipo KERN_INFO
 */
static void module_unload (void)
{
	/* desregistrar */
	/* [...] */
	/* liberar memoria */
	/* [...] */
	/* mensaje indicando que se descargó el módulo */
	printk (KERN_INFO "Modulo memory descargado\n");
}

/**
 * Función que abre el dispositivo:
 * Pone el puntero del buffer al inicio del buffer para el número menor
 * solicitado.
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo que se abrirá
 * @return 0 en caso de éxito, -1 en caso de error
 */
int device_open (struct inode *inode, struct file *filp)
{
	/* buffer auxiliar, si no hay memoria suficiente en el buffer para el
	número menor solicitado se deberá pedir más, copiar y luego liberar,
	este buffer temporal permite guardar la memoria solicitada para crecer
	*/
	/* [...] */
	/* obtener número menor del dispositivo al que se está accediendo
	http://www.makelinux.net/ldd3/chp-3-sect-3 */
	/* [...] */
	/* si el número menor es mayor que los permitidos error */
	/* [...] */
	/* si el puntero es null se inicializa vacío para que soporte a lo menos
	hasta el número menor minor solicitado */
	if (buffer == NULL) {
		/* solicitar memoria para los buffers hasta al menos el número
		menor en el espacio de memoria del kernel
		http://www.fiveanddime.net/man-pages/kmalloc.9.html */
		/* [...] */
		/* en caso de que no se haya asignado memoria error */
		/* [...] */
		/* inicializar en ceros la memoria: $ man memset */
		/* [...] */
		/* actualizar el conteo de dispositivos disponibles */
		/* [...] */
	}
	/* aquí estamos seguro que el buffer está creado */
	/* se debe verificar ahora que exista espacio en el buffer para el
	número menor solicitado, si no hay espacio se debe pedir nuevo espacio y
	copiar los datos del buffer actual al nuevo espacio liberando el
	anterior (se usa buffer auxiliar para la asignación temporal de la
	nueva memoria) */
	if (devs_actual_count < (minor + 1)) {
		/* pedir nuevo espacio de memoria para el nuevo buffer */
		/* [...] */
		/* en caso de que no se haya asignado memoria error */
		/* [...] */
		/* inicializar en ceros la memoria: $ man memset */
		/* [...] */
		/* copiar lo del buffer antiguo al nuevo buffer: $ man memcpy */
		/* [...] */
		/* liberar memoria del antiguo buffer */
		/* [...] */
		/* actualizar puntero del buffer al nuevo buffer */
		/* [...] */
		/* actualizar el conteo de dispositivos disponibles */
		/* [...] */
	}
	/* aquí estamos seguros que hay espacio en el buffer para el número
	menor solicitado */
	/* poner el puntero al inicio del buffer del número menor para realizar
	la operación que se haya solicitado con posterioridad por echo o cat */
	/* [...] */
	/* todo ha ido ok */
	return 0;
}

/**
 * Función que libera el dispositivo
 * No se hace nada, ya que el dispositivo esta en RAM
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo abierto
 * @return Siempre 0, ya que no se hace nada especial
 */
int device_release (struct inode *inode, struct file *filp)
{
	return 0;
}

/**
 * Función que lee desde el dispositivo:
 *  - Verifica que el puntero este en una posición válida
 *  - Copia desde el dispositivo virtual al buffer entregado por el usuario
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso (no es de fiar)
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes leídos desde el dispositivo
 */
ssize_t device_read (struct file *filp, char *buff, size_t length,
			loff_t *offset)
{
	int copied = 0;
	/* si no hay más datos entonces es fin de archivo (EOF) */
	if (!(*pBuffer)) return 0;
	/* copiar */
	/* [...] */
	/* mensaje de lo leído y retornar */
	printk(KERN_INFO "Se han leido %d bytes\n", copied);
	return copied;
}

/**
 * Función que escribe en el dispositivo
 * Copia desde el buffer entregado por el usuario al dispositivo (sobreescribe)
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso
 * @param length Cantidad de bytes que se escribiran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes escritos en el dispositivo
 */
ssize_t device_write (struct file *filp, const char *buff, size_t length,
			loff_t *offset)
{
	int copied = 0;
	memset(pBuffer, 0, BUFFER_SIZE);
	/* copiar */
	/* [...] */
	/* mensaje de lo escrito y retornar */
	printk(KERN_INFO "Se han escrito %d bytes\n", copied);
	return copied;
}
test.sh
#/bin/bash
#
# Test de tarea 3 Sistemas Operativos (2013-1)
# by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
#
# Ejecutar pruebas con: $ bash test.sh
# Dejar todo limpio con: $ bash test.sh clean
#

# hasta que número menor crear dispositivos (desde 0)
# además se creará uno extra de número menor igual a 0
MINOR_MAX=5

# número mayor que se utilizará
MAJOR=70

# limpiar pantalla
clear

# borrar dispositivos si existen
echo "Borrando $(($MINOR_MAX+1)) dispositivos (si existen)..."
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	rm -f memory$MINOR
done

# limpiar directorio
echo "Limpiando código compilado del módulo (si existe)..."
make clean > /dev/null

# descargar módulo si ya está cargado
echo "Descargando módulo (si está cargado)..."
if [ `lsmod | grep memory | wc -l` -eq 1 ]; then
	sudo rmmod memory
fi

# si solo se pidió limpiar se detiene ejecución aquí
if [ "$1" = "clean" ]; then
	exit
fi

# crear dispositivos
echo "Creando $(($MINOR_MAX+1)) dispositivos..."
for MINOR in `seq 0 $MINOR_MAX`; do
	sudo mknod memory$MINOR c $MAJOR $MINOR
done
sudo mknod memory$(($MINOR+1)) c $MAJOR 0

# ajustar propietario de los dispositivos recién creados (para evitar sudo)
echo "Cambiando propietario de los dispositivos a `whoami`..."
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	sudo chown `whoami`: memory$MINOR
done

# compilar módulo
echo "Compilando módulo..."
make > /dev/null

# cargar módulo
echo "Cargando módulo..."
sudo insmod memory.ko

# ejecutar pruebas
echo ""
echo "Ejecutando pruebas:"
echo ""

# prueba 1
echo "1. Escribiendo 'Soy X' en cada dispositivo, con X={0...$((MINOR_MAX+1))}"
echo "   Se espera que memory0 tenga lo mismo que memory$((MINOR_MAX+1)), todos"
echo "   los demás dispositivos deberán tener contenido diferente."
echo ""
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo "Soy $MINOR" > memory$MINOR
done
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo -n "      memory$MINOR: "
	cat memory$MINOR
done
echo ""

# prueba 2
echo "2. Escribiendo 'Ahora seré memoryX' en los dispositivos memoryX, con"
echo "   X={0...$((MINOR_MAX-2))}. Se espera que desde memory0 a"
echo "   memory$((MINOR_MAX-2)) este el nuevo contenido, en los otros debe"
echo "   estar el contenido antiguo. Notar que memory$((MINOR_MAX+1)) debe"
echo "   cambiar, a pesar de que no se ha tocado."
echo ""
for MINOR in `seq 0 $((MINOR_MAX-2))`; do
	echo "Ahora seré memory$MINOR" > memory$MINOR
done
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo -n "      memory$MINOR: "
	cat memory$MINOR
done
echo ""

05_Solución_tarea_dispositivo_memory_2013-1 <>

Makefile
# Makefile para el módulo memory.ko
obj-m = memory.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.o *.order *.symvers .tmp* .memory*
memory.c
/**
 * Ejemplo de módulo para un dispositivo virtual
 * by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
 * 
 * Para ver como compilar el módulo y cargarlo revisar ejemplo de módulo
 * holamundo.c que encontrará disponible en:
 * http://cursos.delaf.cl/cursos/sistemas-operativos/codigo#01holamundo
 * 
 * En /dev se encuentran los dispositivos que el sistema operativo puede
 * manipular. Para este ejemplo se crearán varios dispositivo /dev/memoryX,
 * donde la X>=0, sobre los cuales y utilizando este módulo se podrá escribir
 * y luego rescatar lo que se haya escrito. Cada dispositivo /dev/memoryX tendrá
 * su propio número menor, donde si es diferente será tratado como un buffer
 * diferente.
 * 
 * Ejemplo de uso:
 *  # echo hola mundo > /dev/memory0
 *  # echo chao mundo > /dev/memory1
 *  # cat /dev/memory0
 *  hola mundo
 *  # cat /dev/memory1
 *  chao mundo
 * 
 * Existen básicamente dos tipos de dispositivos:
 *  - De caracter (identificados por una "c" al hacer ls -l): son
 *    dispositivos que son accesados, típicamente, en orden secuencial.
 *    No utilizan buffers y se accede directamente al dispositivo.
 *    Se escriben y leen bytes.
 *    Son más simples de trabajar que los de bloque (con read y write).
 *  - De bloques (identificados por una "b" al hacer ls -l): son
 *    dispositivos que deben poder ser accesados de forma aleatoria.
 *    Utilizan buffering, por lo cual se deben acceder a través de una
 *    cache.
 *    Se escriben y leen bloques de datos.
 *    Son más complicados de utilizar que los de caracter.
 * http://www.cab.u-szeged.hu/linux/doc/khg/node18.html
 * 
 * Los números mayores y menores se pueden observar entre el grupo y la
 * fecha, ejemplo:
 * 
 * brw-rw---T 1 root disk 8, 0 jun 14 10:30 /dev/sda
 * 
 *  - Número mayor 8, usado para identificar el módulo que procesará el
 *    dispositivo.
 *  - Número menor 0, usado para identificar diferentes dispositivos que
 *    son atendidos por el mismo módulo.
 * http://www.makelinux.net/ldd3/chp-3-sect-2
 * 
 * Para facilidad de la programación el dispositivo /dev/memoryX será un
 * dispositivo de tipo caracter y tendrá un tamaño fijo, de tal forma
 * que se deberá pasar una cantidad limitada de caracteres para
 * almacenar. Adicionalmente, y para disminuir la complejidad del
 * ejercicio, no se realizarán accesos concurrentes al dispositivo, o
 * sea, solo un comando echo se ejecutará al mismo tiempo sobre un
 * /dev/memoryX, lo mismo para cat.
 * 
 * echo hará uso de la llamada a sistema write para escribir
 * cat hará uso de la llamada a sistema read para leer
 * 
 * Para crear el dispositivo de caracter con número mayor 70 y menor 0
 * utilizar:
 *  # mknod /dev/memory0 c 70 0
 *  $ ls -lh /dev/memory0 
 *  crw-r--r-- 1 root root 70, 0 nov  8 22:56 /dev/memory0
 * De la misma forma crear más dispositivos con otros números menores.
 * 
 * Antes de poder utilizar echo y cat sobre el dispositivo recordar
 * cargar el módulo (previa compilación del mismo), algo como:
 *  $ make
 *  # insmod memory.ko
 *  $ dmesg
 *  [...]
 *  [ 6684.586869] Cargando modulo memory
 *  [ 6684.586879] Modulo memory cargado correctamente
 *  [ 6684.586883] Numero mayor en uso para el dispositivo: 70
 */

/*
Resumen de comando para descargar, compilar, cargar y verificar:
clear; sudo rmmod memory; make clean all; sudo insmod memory.ko; dmesg | tail
*/

/* Bibliotecas */
#include <linux/module.h>	/* requerido por todos los módulos */
#include <linux/init.h>		/* module_init y module_exit */
#include <linux/kernel.h>	/* printk y KERN_INFO */
#include <linux/fs.h>		/* struct file_operations */
#include <linux/slab.h>		/* kmalloc */
#include <asm/uaccess.h>	/* copy_to_user y copy_from_user */

/* Definiciones */
#define DEV_MAJOR 70		/* número mayor que identifica el módulo */
#define BUFFER_SIZE 100		/* tamaño máximo del buffer (dispositivo) */
#define DEV_MAXS ((128*1024)/(BUFFER_SIZE)) /* límite de kmalloc es 128 kB */

/* Información del módulo */
MODULE_AUTHOR ("Esteban De La Fuente Rubio");
MODULE_LICENSE ("GPL");
MODULE_DESCRIPTION ("Ejemplo de múltiples buffers virtuales");
MODULE_SUPPORTED_DEVICE ("/dev/memory*");

/* Prototipos de funciones */
static int module_load (void);
static void module_unload (void);
static int device_open (struct inode *inode, struct file *filp);
static int device_release (struct inode *inode, struct file *filp);
static ssize_t device_read (struct file *filp, char *buf, size_t length,
			loff_t *offset);
static ssize_t device_write (struct file *filp, const char *buf, size_t length,
			loff_t *offset);

/* Estructura con funciones para manejar el dispositivo. Se hace un mapeo entre
las llamadas a sistema y las funciones definidas */
static struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write
};

/* Mapeo de funciones para carga y descarga */
module_init (module_load);	/* Carga con insmod */
module_exit (module_unload);	/* Descarga con rmmod */

/* Variables globales */
static char *buffer = NULL;		/* este ES el dispositivo virtual */
static char *pBuffer = NULL;		/* puntero para recorrer el buffer */
static int devs_actual_count = 0;	/* cantidad actual de dispositivos */
 
/**
 * Función llamada al cargar el módulo:
 *  - Registra el dispositivo de caracter con register_chrdev
 *  - Verifica que no existió error al registrar
 *  - Genera mensaje de tipo KERN_INFO
 * @return 0 en caso de éxito, -1 en caso de error
 */
static int module_load (void)
{
	/* variable para el estado devuelto por register_chrdev */
	short status = -1;
	/* mensaje del kernel */
	printk (KERN_INFO "Cargando modulo memory\n");
	/* registrar el dispositivo de caracter
	http://www.fsl.cs.sunysb.edu/kernel-api/re941.html */
	status = register_chrdev (
		DEV_MAJOR,	/* número mayor */
		"memory",	/* nombre para los dispositivos */
		&fops		/* operaciones asociadas al dispositivo */
	);
	/* verificar que no ha habido error (retorno negativo) al registrar el
	dispositivo */
	if(status < 0) {
		printk (KERN_INFO "A ocurrido un error al ejecutar "
			"register_chrdev\n");
		return status;
	}
	/* mensaje indicando que el modulo se cargó */
	printk (KERN_INFO "Modulo memory cargado correctamente\n");
	printk (KERN_INFO "Numero mayor en uso para el dispositivo: %d\n",
	       DEV_MAJOR);
	/* retornar todo ok */
	return 0;
}

/**
 * Función llamada al descargar el módulo
 *  - Desregistra el dispositivo de caracter con unregister_chrdev
 *  - Libera la memoria solicitada para el buffer con kfree
 *  - Genera mensaje de tipo KERN_INFO
 */
static void module_unload (void)
{
	/* desregistrar */
	unregister_chrdev (DEV_MAJOR, "memory");
	/* liberar memoria */
	kfree(buffer);
	/* mensaje indicando que se descargó el módulo */
	printk (KERN_INFO "Modulo memory descargado\n");
}

/**
 * Función que abre el dispositivo:
 * Pone el puntero del buffer al inicio del buffer para el número menor
 * solicitado.
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo que se abrirá
 * @return 0 en caso de éxito, -1 en caso de error
 */
static int device_open (struct inode *inode, struct file *filp)
{
	/* buffer auxiliar, si no hay memoria suficiente en el buffer para el
	número menor solicitado se deberá pedir más, copiar y luego liberar,
	este buffer temporal permite guardar la memoria solicitada para crecer
	*/
	char *auxBuffer = NULL;
	/* obtener número menor del dispositivo al que se está accediendo
	http://www.makelinux.net/ldd3/chp-3-sect-3 */
	int minor =  iminor(inode);
	/* si el número menor es mayor que los permitidos error */
	if (minor >= DEV_MAXS) {
		printk (KERN_INFO "Error tratando de acceder al numero menor "
			"%d\n", minor);
		return -1;
	}
	/* si el puntero es null se inicializa vacío para que soporte a lo menos
	hasta el número menor minor solicitado */
	if (buffer == NULL) {
		/* solicitar memoria para los buffers hasta al menos el número
		menor en el espacio de memoria del kernel
		http://www.fiveanddime.net/man-pages/kmalloc.9.html */
		printk (KERN_INFO "Solicitando memoria para crear %d buffers "
			"\n", (minor + 1));
		buffer = kmalloc (
			BUFFER_SIZE * (minor + 1),
			GFP_KERNEL
		);
		/* en caso de que no se haya asignado memoria error */
		if (buffer==NULL) {
			printk (KERN_INFO "Ha ocurrido un error al asignar"
				"memoria con kmalloc\n");
			return -1;
		}
		/* inicializar en ceros la memoria: $ man memset */
		memset (buffer, 0, BUFFER_SIZE * (minor + 1));
		/* actualizar el conteo de dispositivos disponibles */
		devs_actual_count = minor + 1;
	}
	/* aquí estamos seguro que el buffer está creado */
	/* se debe verificar ahora que exista espacio en el buffer para el
	número menor solicitado, si no hay espacio se debe pedir nuevo espacio y
	copiar los datos del buffer actual al nuevo espacio liberando el
	anterior (se usa buffer auxiliar para la asignación temporal de la
	nueva memoria) */
	if (devs_actual_count < (minor + 1)) {
		/* pedir nuevo espacio de memoria para el nuevo buffer */
		printk (KERN_INFO "Solicitando memoria para ampliar el buffer "
			"de %d a %d\n", devs_actual_count, (minor + 1));
		auxBuffer = kmalloc (
			BUFFER_SIZE * (minor + 1),
			GFP_KERNEL
		);
		/* en caso de que no se haya asignado memoria error */
		if (auxBuffer==NULL) {
			printk (KERN_INFO "Ha ocurrido un error al asignar"
				"memoria con kmalloc\n");
			return -1;
		}
		/* inicializar en ceros la memoria: $ man memset */
		memset (auxBuffer, 0, BUFFER_SIZE * (minor + 1));
		/* copiar lo del buffer antiguo al nuevo buffer: $ man memcpy */
		memcpy (auxBuffer, buffer, BUFFER_SIZE * devs_actual_count);
		/* liberar memoria del antiguo buffer */
		kfree(buffer);
		/* actualizar puntero del buffer al nuevo buffer */
		buffer = auxBuffer;
		/* actualizar el conteo de dispositivos disponibles */
		devs_actual_count = minor + 1;
	}
	/* aquí estamos seguros que hay espacio en el buffer para el número
	menor solicitado */
	/* poner el puntero al inicio del buffer del número menor para realizar
	la operación que se haya solicitado con posterioridad por echo o cat */
	pBuffer = buffer + (minor * BUFFER_SIZE);
	/* todo ha ido ok */
	return 0;
}

/**
 * Función que libera el dispositivo
 * No se hace nada, ya que el dispositivo esta en RAM
 * @param inode Información sobre el archivo
 * @param filp Representación del archivo abierto
 * @return Siempre 0, ya que no se hace nada especial
 */
static int device_release (struct inode *inode, struct file *filp)
{
	return 0;
}

/**
 * Función que lee desde el dispositivo:
 *  - Verifica que el puntero este en una posición válida
 *  - Copia desde el dispositivo virtual al buffer entregado por el usuario
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso (no es de fiar)
 * @param length Cantidad de bytes que se leeran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes leídos desde el dispositivo
 */
static ssize_t device_read (struct file *filp, char *buff, size_t length,
			loff_t *offset)
{
	int copied = 0;
	/* si no hay más datos entonces es fin de archivo (EOF) */
	if (!(*pBuffer)) return 0;
	/* copiar */
	while (length && *pBuffer)  {
		put_user(*(pBuffer++), buff++);	/* buff no es de fiar, por eso
						se copia utilizando put_user */
		length--;
		copied++;
	}
	/* mensaje de lo leído y retornar */
	printk(KERN_INFO "Se han leido %d bytes\n", copied);
	return copied;
}

/**
 * Función que escribe en el dispositivo
 * Copia desde el buffer entregado por el usuario al dispositivo (sobreescribe)
 * Sobre las estructuras de datos: http://www.tldp.org/LDP/tlk/ds/ds.html
 * @param filp Representación del archivo abierto
 * @param buff Apunta al espacio de direcciones del proceso
 * @param length Cantidad de bytes que se escribiran
 * @param offset Desde que byte se leera
 * @return Cantidad de bytes escritos en el dispositivo
 */
static ssize_t device_write (struct file *filp, const char *buff, size_t length,
			loff_t *offset)
{
	int copied = 0;
	memset(pBuffer, 0, BUFFER_SIZE);
	/* copiar */
	while(length && copied<BUFFER_SIZE) {
		pBuffer[copied] = buff[copied];
		copied++;
		length--;
	}
	/* mensaje de lo escrito y retornar */
	printk(KERN_INFO "Se han escrito %d bytes\n", copied);
	return copied;
}
test.sh
#/bin/bash
#
# Test de tarea 3 Sistemas Operativos (2013-1)
# by Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl)
#
# Ejecutar pruebas con: $ bash test.sh
# Dejar todo limpio con: $ bash test.sh clean
#

# hasta que número menor crear dispositivos (desde 0)
# además se creará uno extra de número menor igual a 0
MINOR_MAX=5

# número mayor que se utilizará
MAJOR=70

# limpiar pantalla
clear

# borrar dispositivos si existen
echo "Borrando $(($MINOR_MAX+1)) dispositivos (si existen)..."
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	rm -f memory$MINOR
done

# limpiar directorio
echo "Limpiando código compilado del módulo (si existe)..."
make clean > /dev/null

# descargar módulo si ya está cargado
echo "Descargando módulo (si está cargado)..."
if [ `lsmod | grep memory | wc -l` -eq 1 ]; then
	sudo rmmod memory
fi

# si solo se pidió limpiar se detiene ejecución aquí
if [ "$1" = "clean" ]; then
	exit
fi

# crear dispositivos
echo "Creando $(($MINOR_MAX+1)) dispositivos..."
for MINOR in `seq 0 $MINOR_MAX`; do
	sudo mknod memory$MINOR c $MAJOR $MINOR
done
sudo mknod memory$(($MINOR+1)) c $MAJOR 0

# ajustar propietario de los dispositivos recién creados (para evitar sudo)
echo "Cambiando propietario de los dispositivos a `whoami`..."
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	sudo chown `whoami`: memory$MINOR
done

# compilar módulo
echo "Compilando módulo..."
make > /dev/null

# cargar módulo
echo "Cargando módulo..."
sudo insmod memory.ko

# ejecutar pruebas
echo ""
echo "Ejecutando pruebas:"
echo ""

# prueba 1
echo "1. Escribiendo 'Soy X' en cada dispositivo, con X={0...$((MINOR_MAX+1))}"
echo "   Se espera que memory0 tenga lo mismo que memory$((MINOR_MAX+1)), todos"
echo "   los demás dispositivos deberán tener contenido diferente."
echo ""
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo "Soy $MINOR" > memory$MINOR
done
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo -n "      memory$MINOR: "
	cat memory$MINOR
done
echo ""

# prueba 2
echo "2. Escribiendo 'Ahora seré memoryX' en los dispositivos memoryX, con"
echo "   X={0...$((MINOR_MAX-2))}. Se espera que desde memory0 a"
echo "   memory$((MINOR_MAX-2)) este el nuevo contenido, en los otros debe"
echo "   estar el contenido antiguo. Notar que memory$((MINOR_MAX+1)) debe"
echo "   cambiar, a pesar de que no se ha tocado."
echo ""
for MINOR in `seq 0 $((MINOR_MAX-2))`; do
	echo "Ahora seré memory$MINOR" > memory$MINOR
done
for MINOR in `seq 0 $((MINOR_MAX+1))`; do
	echo -n "      memory$MINOR: "
	cat memory$MINOR
done
echo ""
Última modificación de esta página fue el Miércoles 18 de Abril del 2018 a las 12:04