Examen_LabII_Febrero_2003_Solucion

Anuncio
Apellidos
Nombre
Universidad de Alcalá. Escuela Politécnica. Dpto. Automática. 22-02-2003
Examen de Laboratorio de Informática Industrial II
El examen puntúa de 0 a 4. Ponga apellidos y nombre en mayúsculas en las hojas.
Preguntas breves
Responda en el espacio reservado a las cuestiones planteadas. Sea breve y conciso.
0,125 puntos por pregunta.
1) ¿Cuál es la diferencia entre una cabecera de C y una biblioteca? Indicar de qué se componen
y para qué sirven.
Las cabeceras son archivos de texto con código C que contienen principalmente prototipos
de funciones, declaraciones de variables y estructuras, macros y funciones en línea. Sirven
para que el compilador conozca el significado de los nombres de las palabras no reservadas
que use el programador, sobre todo el tipo de datos.
Las bibliotecas son archivos compuestos por varios módulos de código compilado
(habitualmente código máquina). Sirven para incorporar código ya generado y depurado a
nuestra aplicación sin necesidad de rescribirlo.
2) ¿Cómo se le indica a un compilador de C que emplee una biblioteca concreta?
Mediante opciones del compilador. En el caso de gcc, se usaría el modificador –l seguido
por el nombre efectivo de la biblioteca. Por ejemplo, para incluir libpthreads.a,
usaríamos:
$ gcc –lpthreads …
3) ¿Qué es y para qué sirve el preprocesador de C?
Es el programa encargado de resolver las directivas del preprocesador de los archivos
fuente de C. Estas son todas las líneas que comienzan por # e indican inclusiones, macros,
y compilación condicional, principalmente. El preprocesador genera un archivo de código C
“limpio” que no contiene tales líneas, que será pasado al núcleo del compilador.
4) ¿Cuál es la misión principal del enlazador?
Crear un programa ejecutable (o una biblioteca de enlace dinámico), a partir de las
funciones que busca en los archivos de código objeto y bibliotecas que se le indiquen.
5) ¿Para qué se utilizan las llamadas al sistema?
Para comunicar a las aplicaciones con el núcleo del Sistema Operativo. De esta forma entre
otras cosas pueden hacer las necesarias peticiones de recursos para su funcionamiento.
6) Llamar a printf ¿Implica hacer una llamada al sistema? En caso afirmativo ¿Cuál podría
ser? Justificar la respuesta.
1
Sí, ya que accede al dispositivo de salida estándar para enviar texto. La escritura en
dispositivos se realiza por medio de la llamada al sistema write, y en este caso usando el
descriptor del flujo de salida estándar stdout 1.
7) La siguiente pregunta vale por dos:
8) ¿Qué archivo se pasará internamente al compilador si usamos el código fuente siguiente? (El
archivo resultante de aplicar el modificador –E a gcc)
#define NDEBUG
#define INFO
int Divide(int a, int b)
{
#ifndef NDEBUG
if(b == 0)
{
perror("El divisor es 0!!!\n");
return 0;
}
#endif
#ifdef INFO
printf("El resultado de la división es %d\n", a / b);
#endif
return a / b;
}
int Divide(int a, int b)
{
printf("El resultado de la división es %d\n", a / b);
return a / b;
}
9) En la compilación de programas… ¿Qué utilidad encuentra en habilitar la compatibilidad
estricta con ISO C?
Aumenta la portabilidad ya que permite compilarla en cualquier sistema que disponga de
un compilador que cumpla con esta norma (que es la referencia).
10) Pregunta de observación: ¿Cuál es la finalidad de las advertencias (warnings) que muestran
los compiladores? ¿Cuál es la diferencia con los errores?
Los warnings se tratan de construcciones que el compilador acepta pero advierten de
potenciales errores que podrían aparecer bien en tiempo de ejecución o bien en fases
futuras de la compilación.
El error se produce porque el compilador ha encontrado un código que no puede compilar
al no adaptarse a sus reglas internas, por lo que la existencia de errores impide la
generación del archivo compilado.
11) En la depuración de programas… ¿Qué es un breakpoint? ¿Qué es un watchpoint? Aclarar
las diferencias entre ambos.
El breakpoint o punto de interrupción es un punto del código en donde el programa en
depuración se detendrá para permitir observar detenidamente el estado de la aplicación,
avanzar detenidamente, etc.
El watchpoint o punto de comprobación es una parada de la ejecución condicionada a que
se verifique cierta expresión del programa. Por ejemplo “detener cuando la variable num
sea mayor de 10”.
2
12) ¿Qué dos opciones de ejecución paso a paso nos brindan los depuradores? Explicar
brevemente.
Línea a línea del programa entrando en subrutinas de las que se disponga de información
de depuración. Una vez que se ha entrado se sigue con la ejecución línea a línea.
Línea a línea del programa considerando las subrutinas como una línea más. En este caso
las subrutinas se ejecutan de una vez.
13) ¿Qué utilidad tienen los descriptores de fichero en Unix?
Son los identificadores de archivo que utilizamos en algunas llamadas al sistema y
llamadas a biblioteca para referenciarlos inequívocamente y poder realizar operaciones
como leer o escribir. En Unix son números enteros positivos.
14) ¿Qué tipo de ficheros se almacenan en /dev?
Archivos de dispositivo que contienen el nombre lógico de la mayoría de los controladores
de dispositivo del sistema. Esta forma permite tratar a los dispositivos como si se trataran
de archivos, por lo que se puede utiliza la misma interfaz básica (open, close, read,
write,…).
15) ¿Qué quiere decir “redirigir la salida estándar”?
Hacer que el archivo de salida estándar sea otro diferente al original stdout. La salida es
habitualmente la consola de texto desde donde comenzó la aplicación, con la redirección
podemos hacer que la salida vaya a cualquier otro archivo como otra consola, u otros
ficheros de disco.
16) ¿Cuáles son las ventajas y los inconvenientes de utilizar llamadas al sistema directamente?
Citar al menos una de cada.
Ventajas: Escasa sobrecarga, tamaño, y velocidad. Además se permite acceder a funciones
avanzadas no disponibles en bibliotecas.
Desventajas: Poca portabilidad, dificultad en la escritura de los programas.
3
Completar el siguiente programa respondiendo sólo dentro de las cajas:
(2 puntos)
• A continuación aparece un programa en el que un hilo Servidor (productor) deposita datos en la
variable global g_ficha para que otros hilos Cliente la procesen.
• Cuando el Servidor genere un nuevo dato se quedará esperando a que un Cliente lo consuma. Una
vez que esto suceda, podrá generar otro dato.
• Sólo un cliente puede acceder a g_ficha simultáneamente, después de lo cual se considera que la
variable es inválida hasta que el Servidor haya generado otro dato.
• Cada vez que el usuario teclea [C] se crea un nuevo cliente que hará hasta 20 intentos en diferentes
instantes por ser el que consuma el dato. Si consiguiera acceder en alguna ocasión al dato, no
debería volver a hacer más intentos, terminando su ejecución.
• Aunque el dato al que los clientes quieren acceder estuviera siendo utilizado por otro cliente o fuera
inválido, el hilo no se bloquearía.
• Si el usuario presiona la tecla [Q], cuando se consuma el último dato generado la aplicación
terminará.
•
•
•
No todas las cajas han de ser rellenadas.
Lea primero el código tal cual está. Una vez comprendido puede empezar a plantear la solución.
Se puede resolver con tan solo dos mútex.
Literales a usar:
pthread_mutex_t
PTHREAD_MUTEX_INITIALIZER
EBUSY
int pthread_mutex_init (pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
int pthread_mutex_destroy (pthread_mutex_t *mutex);
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t *mutex);
4
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
struct sFicha
{
char sexo;
int num;
};
void* HiloCliente(void *arg);
void* HiloServidor(void *arg);
// Declarar las variables que sean necesarias
int g_salir = 0;
struct sFicha g_ficha;
pthread_mutex_t exmut_1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t exmut_2 = PTHREAD_MUTEX_INITIALIZER;
int main()
{
// Declarar las variables que sean necesarias
char c = 0;
pthread_t thServidor = 0, thID = 0;
// Introducir el código que sea necesario
pthread_mutex_lock(&exmut_1);
pthread_mutex_lock(&exmut_2);
printf("Introduzca 'C' para crear un nuevo hilo consumidor.\n");
pthread_create(&thServidor, NULL, HiloServidor, NULL);
do
{
c = getchar();
if (c == 'c' || c == 'C')
{
// Crear un nuevo hilo de tipo Cliente
pthread_create(&thID, NULL, HiloCliente, NULL);
}
} while (c != 'q' && c != 'Q');
g_salir = 1;
// Sólo se debería salir cuando el último dato generado haya sido procesado
pthread_mutex_lock(&exmut_1);
pthread_mutex_unlock(&exmut_2);
pthread_join(thServidor, (void**) NULL);
return 0;
}
5
void* HiloCliente(void *arg)
{
int cont;
pthread_detach(pthread_self());
printf("Se ha creado un nuevo hilo con ID=%d\n", pthread_self());
for (cont = 0; cont < 20; cont++)
{
// Simular que el cliente está haciendo procesamiento
usleep(rand() % 4000000 + 500000);
// Intentar acceder al recurso
if (pthread_mutex_trylock(&exmut_1) == 0)
{
// En este caso simplificado el acceso consiste en mostrar la información
printf("El hilo %d ha recibido la información:\n\t'%c'\t%d\n",
pthread_self(), g_ficha.sexo, g_ficha.num);
// Si se ha conseguido acceder, hay que salir del bucle
pthread_mutex_unlock(&exmut_2);
}
return 0;
}
void* HiloServidor(void *arg)
{
struct sFicha ficha;
while(g_salir == 0)
{
// Simulación de que el servidor tarde en generar un nuevo dato
usleep(rand() % 4000000 + 500000);
ficha.sexo = rand() & 1 ? 'H' : 'M';
ficha.num = rand();
// g_ficha es un recurso compartido
g_ficha = ficha;
pthread_mutex_unlock(&exmut_1);
pthread_mutex_lock(&exmut_2);
}
return 0;
}
6
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
struct sFicha
{
char sexo;
int num;
};
void* HiloCliente(void *arg);
void* HiloServidor(void *arg);
// Declarar las variables que sean necesarias
int g_salir = 0;
pthread_mutex_t exmut_1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t exmut_2 = PTHREAD_MUTEX_INITIALIZER;
struct sFicha g_ficha;
int main()
{
// Declarar las variables que sean necesarias
char c = 0;
pthread_t thServidor = 0, thID = 0;
// Introducir el código que sea necesario
printf("Introduzca 'C' para crear un nuevo hilo consumidor.\n");
pthread_mutex_lock(&exmut_1);
pthread_mutex_lock(&exmut_2);
pthread_create(&thServidor, NULL, HiloServidor, NULL);
do
{
c = getchar();
if (c == 'c' || c == 'C')
{
// Crear un nuevo hilo de tipo Cliente
pthread_create(&thID, NULL, HiloCliente, NULL);
}
} while (c != 'q' && c != 'Q');
g_salir = 1;
pthread_mutex_lock(&exmut_1);
pthread_mutex_unlock(&exmut_2);
pthread_join(thServidor, (void**) NULL);
return 0;
}
7
void* HiloCliente(void *arg)
{
int cont;
pthread_detach(pthread_self());
printf("Se ha creado un nuevo hilo con ID=%d\n", pthread_self());
for (cont = 0; cont < 20; cont++)
{
// Simular que el cliente está haciendo procesamiento
usleep(rand() % 4000000 + 500000);
// Intentar acceder al recurso
if (pthread_mutex_trylock(&exmut_1) == 0)
{
printf("El hilo %d ha recibido la información:\n\t'%c'\t%d\n",
pthread_self(), g_ficha.sexo, g_ficha.num);
pthread_mutex_unlock(&exmut_2);
break;
}
}
return 0;
}
void* HiloServidor(void *arg)
{
struct sFicha ficha;
while(g_salir == 0)
{
// Generar un nuevo dato
usleep(rand() % 4000000 + 500000);
ficha.sexo = rand() & 1 ? 'H' : 'M';
ficha.num = rand();
g_ficha = ficha;
pthread_mutex_unlock(&exmut_1);
pthread_mutex_lock(&exmut_2);
}
return 0;
}
8
Descargar