Práctica 2: Realización de una Alarma Temporizada - Diseño de Sistemas Operativos - U.L.P.G.C.

Página creada Paloma Bricaller
 
SEGUIR LEYENDO
Práctica 2:
Realización de una Alarma
       Temporizada
   Diseño de Sistemas Operativos – U.L.P.G.C.

                                        David Jesús Horat Flotats
                                      Enrique Fernández Perdomo
Práctica 2: Realización de una Alarma Temporizada                                                                      David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                                                       Enrique Fernández Perdomo

                                                                Índice
I. Explicación del Programa desarrollado.................................................................... 2
    Programa Principal........................................................................................................................ 2
          Variables
          Tarea que realiza
          Parámetros
          Movimiento de la bola
    Señales........................................................................................................................................... 9
          Alarma (en Tiempo Real)
          Interrupción
    Lanzamiento de Procesos.............................................................................................................12
          Uso de fork
          Uso de exec
          Uso de waitpid
          Uso de exit
    Control de la Sección Crítica, con Pipes......................................................................................16
          Definición de Pipe
          Manejo de Pipe
          Problema de la Sección Crítica
          Solución a la Sección Crítica

II. Compilación y Ejecución del programa (Makefile)................................................20
    Explicación y Reglas del Makefile.............................................................................................. 20
    Ejecución del programa............................................................................................................... 22
          Paso de Parámetros

III.Anexo – Código Fuente..........................................................................................28
    alarma.c........................................................................................................................................28
    Makefile....................................................................................................................................... 30

                                                                         1
Práctica 2: Realización de una Alarma Temporizada                                    David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                     Enrique Fernández Perdomo

       Explicación del Programa desarrollado
 Programa Principal
     Variables
       Las variables definidas son de dos tipos fundamentales: globales y locales a la función main.
La utilidad de las variables globales radica en el hecho de que serán usadas por funciones a las que
se llama en el momento de producirse las señales, como la alarma, de modo que no pueden pasarse
por parámetro.

       Las variables globales son las siguientes:
    1. Tubería para el control de la concurrencia, mediante el uso de pipes (int tuberia[2]).
    2. Tiempo de duración de la alarma (int tiempo_alarma = 4).

       Las variables locales son las siguientes:
    1. Variables para controlar el movimiento de la bola:
        1. Columna inicial, en la que empieza la bola (int columna = 0).
        2. Columna máxima a la que puede llegar la bola (int max_columna = 40).
        3. Paso, que indica el número de columnas que avanza la bola en cada iteración de
    muestreo de la misma (int paso = 1).
        4. Velocidad de la bola, que se controla con el número de iteraciones de un bucle, de modo
    que mientras menor sea el valor de esta variable mayor será la velocidad y viceversa, aspecto
    que debe tenerse en cuenta al asignarle un valor (int velocidad = 30000000).
    2. Variable para tomar el valor de las lecturas y escrituras de las tuberías (int valor).
    3. Variable para la realización de iteraciones, y tareas auxiliares (int i).
    4. Indicación de señales creadas (int senal_creada = 0;).
    5. Ristra que representa la bola (char *bola = "o";).
    6. Estructuras de las señales para la implementación con SIGACTION (struct sigaction
senal_alarma, senal_int;).

       Se puede observar que la mayoría de las variables son inicializadas en el momento de su
definición, de modo que así se tendrán valores por defecto para las mismas.

                                                    2
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

     Tarea que realiza
       El programa principal hará un uso intensivo de la salida por pantalla. Para ello se hace uso
de la función printf. El uso de esta función obliga a incluir la librería . Una alternativa al
uso de las funciones de la librería stdio es el uso de librerías especiales para el manejo de la
impresión en el terminal. Destacan las librerías conio y ncurses. La librería ncurses es amplísima y
de gran utilidad para el manejo del terminal de Linux y la impresión de cadenas de caracteres en el
mismo. El problema de la librería ncurses, que se incluiría en nuestro programa como ,
es que maneja una pantalla propia, distinta a la que usa la función printf. Hasta aquí no hay
problema, pero si se usa la función printf a la vez que se emplean función de la librería ncurses,
como move y addch, una pantalla moverá a la otra y el resultado será desastroso; además, da un
aspcto de concurrencia incontrolada, del todo indeseable para cualquier programa serio. Finalmente,
la salida del programa para imprimir por pantalla se hará con la función printf.

       El programa desarrollado imprimirá en pantalla un bola que se moverá rebotando de derecha
a izquierda, en una línea de la pantalla; sin tener en cuenta el efecto que tendrá el muestreo de la
fecha, hasta que se introduzca ésta con la alarma. En realidad, se hará una emulación de la bola con
un carácter. No obstante, el programa permitirá que dicha emulación de la bola pueda ser incluso
una ristra de cualquier dimensión (en principio mayor que la unidad, es decir, que al menos sea un
carácter).

       El programa principal main realizará una serie de tareas principales o pasos, que se indican y
comentan a continuación:

    Paso 1: Parámetros de entrada
       El programa principal, una vez compilado y construido, en el momento de ejecutarse
permitirá la rececpción de parámetros desde el terminal, para que la configuración del misma sea
mayor y más flexible. De este modo la función main tiene la forma:
        int main(int argn, char *argv[]) {

       Estos parámetros permiten el control sobre aspectos como el carácter o ristra a usar para
emular la bola, el número de columnas por el que se moverá ésta, el tipo de implementación para las

                                                    3
Práctica 2: Realización de una Alarma Temporizada                                    David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                     Enrique Fernández Perdomo

señales, el tiempo de duración de la alarma para imprimir la fecha y la velocidad de la bola (medida
en iteraciones de un bucle). Todos los parámetros se explican en detalle en el apartado Parámetros.

    Paso 2: Inicializaciones
       Una vez se han tomado todos los parámetros, si es que se han introducido éstos, se procederá
a la inicialización de determinadas variables y señales. En el caso de no indicarse ciertos
parámetros, se usarán valores por defecto. En el caso de las señales, se usará la implementación con
la función signal, por defecto, de modo que debe hacerse explícitamente se no se indica nada.
      // · Si no se han creado las señales, se usa SIGNAL por defecto
      if (!senal_creada) {
            signal(SIGALRM, muestra_fecha);
            signal(SIGINT, termina);
      }

       Puede verse como se usa la variable senal_creada, que se inicializa a 0:
      int senal_creada = 0; // Indica si se han creado las señales

       Si se indica algo por parámetros se pondrá a 1, de modo que no se tendrá que hacer uso de la
inicialización por defecto; este paso, en el código, se encuentra antes de la inicialización, porque en
ocasiones es necesario y en otras no, en función de los parámetros de entrada.
             }else if (strcmp(argv[p],"-s") == 0) {
                    // · Tipo de implementación de señales
                    senal_creada = 1;

       En las inicializaciones propiamente dichas, se hacen dos tareas: crear e inicializar la tubería
o pipe, para el control de la concurrencia (sección crítica) y activar la alarma.
      // · Creación e inicialización del pipe
      pipe(tuberia);
      write(tuberia[1], &valor, sizeof(int)); fflush(NULL);
      // · Alarma
      alarm(tiempo_alarma);

    Paso 3: Bucle de muestreo de la bola
       Se trata de un bulce infinito en el que se estará controlando el muestreo de la bola y que en
cada iteración del bucle la bola se moverá una posición, por lo general una columna.
while(1) {/*...*/}

                                                        4
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

        Paso 3.1: Pausa entre movimientos de la bola
       Para que el movimiento de la bola sea más realista, tiene un control de velocidad con un
bucle con un número de iteraciones controlado por la variable velocidad.
for(i=0; i
Práctica 2: Realización de una Alarma Temporizada                                 David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                  Enrique Fernández Perdomo

pasa la opción --help, que es la ayuda, que indica como usar el programa, en cuanto al modo en que
se le deben pasar los parámetros:
     if ((argn == 2) && (strcmp(argv[1],"--help") == 0)) {
           printf("Modo de empleo: ./alarma [OPCIÓN]\n");
           printf("\t-b bola; Ristra que representa la bola\n");
           printf("\t-c columna_máxima; Columna máxima a la que llega la bola\n");
           printf("\t-s SIGNAL|SIGACTION; Implementación de señales (SIGNAL por
defecto)\n");
           printf("\t-t segundos; Tiempo entre muestreo de la fecha\n");
           printf("\t-v iteraciones; Velocidad de la bola\n");
           exit(0);
     }

       Las opciones serán, por tanto, las siguientes:
       -b bola --> Ristra que representa la bola; si tiene espacios o caracteres especiales debe
ponerse entre comillas dobles. Se contendrá en la variable char *bola = "o";, que por defecto
valdrá "o".
       -c columna_máxima --> Columna máxima a la que llega la bola, que será de tipo entero. Se
contendrá en la variable int max_columna = 40;, que por defecto valdrá 40.
       -s SIGNAL|SIGACTION --> Indica el tipo de implementación de las señales, que puede ser
SIGNAL o SIGACTION. Según su valor se hará uso de una u otra implementación, y por defecto
SIGNAL.
       -t segundos --> Tiempo entre muestreo de la fecha, medido en segundos, que es la duración
de la alarma. Se contendrá en la variable global int tiempo_alarma = 4;, que por defecto tiene el
valor 4.
       -v iteraciones --> Indica la velocidad de la bola, pero indicando el número de iteraciones, de
modo que su valor es inversamente proporcional a la velocidad de la bola. Se contendrá en la
variable int velocidad = 30000000;, que por defecto vale 30000000.

       Si el número de parámetros es erróneo y no se trata de la opción --help, se tendrá un error.
      if ((argn%2) == 0) {
            printf("alarma: Forma de uso incorrecta\n");
            printf("Pruebe: ./alarma --help\n");
            exit(0);
      }

       Para tomar los parámetros, se hará uso de un bucle, en función del número de parámetros,
iterando la mitad de ellos, pues uno dice el tipo y otro el valor.

                                                    6
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

int p; for(p=1; p
Práctica 2: Realización de una Alarma Temporizada                                David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                 Enrique Fernández Perdomo

    1. Dibujar espacios blancos antes de la posición de la bola.
for(i=0; i
Práctica 2: Realización de una Alarma Temporizada                                   David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                    Enrique Fernández Perdomo

columna anterior a la columna máxima.

          La ejecución del programa principal tal cual (sin uso de señales), proporcionaría la siguiente
salida:

[enrique@adsl p2]$ ./alarma
  o

          Aunque no puede verse, la bola simulado con la o se movería de un lado a otro rebotando en
función de los márgenes definidos.

 Señales
          En Linux, los programas responderán a señales o llamadas. Éstas pueden declararse para ser
atendidas por la función que deseemos o bien se atenderán de la forma por defecto. En general,
cuando no se declara una señal, ésta se tratará por el Sistema Operativo y lo que hará es cerrar el
programa. Por ello deben declararse las señales que sean susceptibles de ser recibidas por nuestro
programa. Por otro lado, la señal KILL es la única que no podremos definir, ya que de lo contrario
podríamos controlarla con una función que no obligara a que nuestro programa fuese “matado”.

          Para el control de señales existen dos técnicas diferentes:

      1. Declaración con la función signal.
          Para cada señal que deseemos atender, bastará crear una función que lo haga e indicarlo de la
siguiente forma:
signal(SEÑAL, FUNCIÓN);

          Por ejemplo, para la señal de alarma (que tiene por valor el número 14, contenido en la
macro SIGALRM), que controlaremos con la función muestra_fecha, haremos uso de la siguiente
instrucción:
signal(SIGALRM, muestra_fecha);

          En el caso de la alarma también será necesario llamar a la función alarm, posteriormente, de

                                                     9
Práctica 2: Realización de una Alarma Temporizada                               David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                Enrique Fernández Perdomo

la forma:
alarm(tiempo_alarma);

       Sin embargo, para la mayoría de señales basta con hacer uso de la función signal, nada más.
Como consideración adicional hay que indicar que es recomendable usar las macros de los nombres
de las señales en lugar de los números de las mismas, pues pudieran variar sus valores numéricos de
unas versiones a otras del kernel de Linux.

    2. Estructuras sigaction.
       En este caso debemos declarar una estructura de tipo sigaction para cada señal que deseemos
definir como tratar o atender.
struct sigaction senal_alarma, senal_int;

       Esta forma es más compleja y tediosa que la declaración con la función signal, pero permite
mucho más control. A continuación podemos ver los principales campos de esta estructura y las
funciones de apoyo de las mismas, comentando la funcionalidad de cada una de ellas.

       Consultando el man de sigaction podemos ver que la estructura sigaction se define así:
            struct sigaction {
               void (*sa_handler)(int);
               void (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t sa_mask;
               int sa_flags;
               void (*sa_restorer)(void);
            }

       El campo más importante es el primero, sa_handler, en el que se indica la función que
manejará la señal. A continuación se comentan los campos más importantes, incluyendo éste.

    1. sa_handler: especifica la acción que se va a asociar con signum y puede ser SIG_DFL para
la acción predeterminada, SIG_IGN para no tener en cuenta la señal, o un puntero a una función
manejadora para la señal.
    2. sa_flags: especifica un conjunto de opciones que modifican el comportamiento del
proceso de manejo de señal. Se forma por la aplicación del operador de bits OR a cero o más de las
constantes que admite. Por defecto actuará de forma normal, aunque si se desea puede asignársele 0

                                                    10
Práctica 2: Realización de una Alarma Temporizada                                David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                 Enrique Fernández Perdomo

o bien hacer uso de la función sigemptyset que se comenta más adelante.
    3. sa_mask: da una máscara de señales que deberían bloquearse durante la ejecución del
manejador de señal. Además, la señal que lance el manejador será bloqueada, a menos que se
activen las opciones SA_NODEFER o SA_NOMASK. Por defecto actuará de forma normal,
aunque si se desea puede asignársele 0 o bien hacer uso de la función sigemptyset que se comenta
más adelante.

       La función sigemptyset tiene la forma int sigemptyset(sigset_t *conjunto);. Con ella se
inicia el conjunto de señales dado por conjunto al conjunto vacío, con todas las señales fuera del
conjunto. Por lo general puede asginarse 0 y se obtiene un resultado similar o bien no hacer nada.

       Finalmente, y lo más importante para que la atención a la señal tenga efecto, debe hacerse
uso de la función sigaction, que tiene la siguiente forma:
       int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

       La llamada al sistema sigaction se emplea para cambiar la acción tomada por un proceso
cuando recibe una determinada señal. El parámetro signum especifica la señal y puede ser
cualquiera válida salvo SIGKILL o SIGSTOP. Si act no es nulo, la nueva acción para la señal
signum se instala como act. Si oldact no es nulo, la acción anterior se guarda en oldact.

       Por ejemplo, en el caso de la señal de alarma, con sigaction haremos uso de las siguientes
sentencias de inicialización o configuración de la estructura:
                        senal_alarma.sa_handler = muestra_fecha;          // Acción a Realizar
                        sigaction(SIGALRM, &senal_alarma, NULL);

     Alarma (en Tiempo Real)
       En Linux puede conseguirse tiempo real, del orden de segundos, con la señal o llamada
ALARM. Para ello, usaremos cualquiera de las dos técnicas antes mencionadas para la declaración
de la señal y luego declararemos el tiempo de la alarma con:
alarm(tiempo_alarma);

       La variable tiempo_alarma contiene los segundos de duración de la alarma, de modo que el
kernel nos enviará dicha señal a nuestro programa pasados exactamente los segundos indicados en

                                                    11
Práctica 2: Realización de una Alarma Temporizada                                    David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                     Enrique Fernández Perdomo

dicha variable (en tiempo real). Hay que tener en cuenta que esto sólo indica que envíe dicha señal
una vez, por lo que una vez se reciba la señal y se haga la tarea desea, habrá que volver a indicar al
kernel que nos vuelva a enviar otra señal, volviendo a poner en el código lo mismo:
alarm(tiempo_alarma);

       Esta segunda llamada se hará dentro del proceso de gestión de la alarma, que visto al estilo
del signal se indicó de la siguiente forma:
signal(SIGALRM, muestra_fecha);

       Con sigaction se haría de la siguiente forma:
                        struct sigaction senal_alarma;
                        senal_alarma.sa_handler = muestra_fecha;           // Acción a Realizar
                        sigaction(SIGALRM, &senal_alarma, NULL);

       De este modo, la función muestra_fecha es la que vuelve a pedir la alarma, una vez se ha
realizado la tarea deseada en dicha función.

     Interrupción
       Adicionalmente a la alarma, como el programa desarrollado es un bucle infinito que hace un
uso intensivo de la pantalla, para poder finalizarlo hay que enviarle la señal SIGINT, que no es más
que escribir Ctrl+C desde el terminal en que se está ejecutando el programa. Esto provoca la
finalización brusca del programa, de modo que hemos optado por controlar dicha señal con la
función termina, para que finaliza mostrando un mensaje de finalización:
void termina() {
    printf("El programa ha finalizado correctamente.\n");
    exit(0);
}

       Con signal declaramos la atención a la señal SIGINT de la siguiente forma:
signal(SIGINT, termina);

       Mientras que con sigaction lo haremos de la siguiente forma:
                        struct sigaction senal_int;
                        senal_int.sa_handler = termina; // Acción a Realizar
                        sigaction(SIGINT, &senal_int, NULL);

                                                    12
Práctica 2: Realización de una Alarma Temporizada                                   David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                    Enrique Fernández Perdomo

 Lanzamiento de Procesos
       Uso de fork
           El programa que maneja la alarma tiene la tarea de crear un proceso que muestra la fecha,
para lo cual hará uso del programa date, existente en el sistema en la ruta /bin/date. Esto se hará
con una de las funciones de tipo exec, pero previamente se requiere que se cree un proceso hijo, que
ejecute la función muestra_fecha, que es la que se llama cuando se produce la alarma, pues al
lanzar el exec, el proceso desaparece y es sustituido por el proceso que ejecuta el date.

           De este modo, usaremos la llamada al sistema fork, para que se cuando se esté ejecutando la
función muestra_fecha por el proceso principal o padre, se cree una copia idéntica de este proceso,
pero que será el proceso hijo. El proceso hijo lanzará el exec para que se ejecute el date y muestre la
fecha, mientras que el padre seguirá haciendo uso del recurso pantalla, terminando la ejecución de la
función muestra_fecha.

           El código, resumido, que permite esto es el siguiente:
       int pid;
       switch (pid = fork()) {
             case -1:      // fork() falla
                  printf("No se pudo crear otro proceso\n");
                  exit(1);
             case 0:       // Hilo hijo
                    /* ... */
              default:          // Hilo padre
                    /* ... */
       }

           En el switch se lanza el fork, de modo que a partir de ese momento existen dos procesos, el
padre y el hijo, que son idénticos, pero con una ligera diferencia: el valor de la variable pid vale 0
para el hijo y para el padre toma un valor mayor que 0, que es el identificador del proceso (pid) del
hijo. Con esta distinción, el hijo ejecutará el código del case 0:, y el padre el del default:.

           En el caso de que la función fork falle, es decir, no pueda duplicar el proceso, devolverá -1
en la variable pid, y se mostrará un mensaje de error y se abortará la función, según se ve en el case
-1:.

                                                     13
Práctica 2: Realización de una Alarma Temporizada                                   David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                    Enrique Fernández Perdomo

       Lo que hace el proceso hijo y el padre, puede verse en la explicación de las llamadas al
sistema exec y waitpid, que se muestran a continuación.

     Uso de exec
       La función exec permite lanzar un programa, pasándole argumentos si los tubiere. Esta
función provoca la desaparición del programa que ejecutaba el proceso que lanza la función exec y
todo su espacio de trabajo es ocupado por el del programa llamado por la función exec, es decir, es
como si el programa que llama a exec muriese, por lo que debe hacer por el proceso hijo creado con
el fork, según se vió en el apartado Uso de fork.

       El código que ejecutará el proceso hijo (el que está dentro del case 0:) y que hace uso de la
llamada al sistema execl (que es una de las muchas variantes del exec) para lanzar el programa date,
para que muestre la fecha, es (en este caso no se muestra el control de la sección crítica con pipes,
por claridad):
                 execl("/bin/date","fecha",NULL);

       Los parámetros de la función execl, de forma genérica son:
       execl(PROGRAMA, NOMBRE_PROGRAMA, ARG1, ARG2, ..., ARGn, NULL);

       Esto quiere decir que primero se indica el programa a ejecutar, que en nuestro caso es el
date, y pasamos la ruta completa /bin/date. Luego se pone el nombre del programa, que en
principio no tiene gran utilidad y luego la lista de parámetros, que deben ser de tipo char * (es decir,
ristras), terminando con un NULL; esto hace que los argumentos formen un vector de ristras, que
sería de tipo char *argv[], que es el típico parámetro de entrada en las funciones main, para tomar
todos los argumentos, y que está precidido por el parámetro int argn, que indica el número de
argumentos, incluyendo el nombre del programa.

       Al ejecutarse el execl muere o desaparece el proceso hijo, por lo que ni siquiera es necesario
un break; al final del case 0:; lo mismo ocurre si pusiéramos un exit, pues nunca se ejecutaría,
salvo que el execl fallara. El espacio de trabajo pasa a ser ocupado por el programa date. Cuando
este programa finaliza su tarea, consistente en el muestreo de la fecha, por pantalla, se encargará de

                                                    14
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

indicar la finalización suya y, por tanto, la del proceso hijo que lanzó el execl, quedando indicado el
estado de finalización del mismo, útil para la llamada al sistema waitpid, que se ve a continuación.

     Uso de waitpid
       El proceso padre se encargará de ver que el proceso hijo finaliza correctamente y en tal caso
liberará su espacio de trabajo, sólo cuando el hijo haya terminado su tarea. La llamada al sistema
waitpid es la que permite esto. El código que ejecutará el proceso padre, dentro del default:, será
(obviando el control de la sección crítica con pipes):
                 waitpid(pid,&estado,WNOHANG);
                 alarm(tiempo_alarma);

       Se habrá declarado la variable estado, para recoger el estado del proceso hijo, pero no será
necesario testear su valor, pues supondremos que es correcto, si bien es necesario pasarla por
referencia como segundo argumento de la función waitpid. El primer parámetro es el identificador
del proceso hijo (pid) por el que debe esperarse; también valdría poner -1 para esperar por cualquier
proceso hijo, pero como sólo hay uno, no tiene sentido. El último parámetro es indicador del
comportamiento del waitpid. Como por defecto, waitpid retendría el proceso padre en ese punto, lo
que supondría un control intrínseco de la sección crítica, hacemos que el waitpid no sea bloqueante,
con la opción WNOHANG. De esta forma, el proceso padre sigue su ejecución, y cuando el hijo
termine, surgirá el efecto del waitpid, lo cual no significa que el contador de programa del proceso
padre vuelva poner justo en el waitpid, sino que simplemente se liberará el espacio de trabajo del
proceso hijo cuando éste haya terminado.

       Por otro lado, el proceso padre deberá volver a activar la alarma, para que el kernel vuelva a
avisarle.
alarm(tiempo_alarma);

     Uso de exit
       La llamada al sistema exit es la que permite que un proceso termine, y como parámetro
permite que se indica un número entero indicativo del estado de terminación del proceso, para que
el padre pueda testear dicho estado de finalización y actuar en consecuencia.

       Se hace uso del exit, en el case -1:, cuando el fork falla, mostrando un mensaje de erro y

                                                    15
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

finalizando el proceso con:
exit(1);

       También en la función termina, que se llama por la señal SIGINT, se hace la finalizaicón
con el exit:
exit(0);

       En el primer caso se devuelve 1, indicando error, mientras que en el segundo caso se
devuelve 0, que por convenio suele ser el estado correcto de finalización de un proceso.

 Control de la Sección Crítica, con Pipes
     Definición de Pipe
       Un pipe crea un par de descriptores de ficheros, que apuntan a un nodo-í de una tubería, y
los pone en el vector de dos elementos apuntado por descf. Así, descf[0] es para lectura, descf[1]
es para escritura. Esto se consigue facilmente declarando un vector de elementos y usando la
función pipe:
int tuberia[2];
pipe(tuberia);

       Una versión más compleja son las FIFOS, o pipes con nombre, que permiten el uso de las
mismas entre programas que no estén relacionados, es decir, que no sean hijos del padre en que se
declarán y crean los pipes. Pero como en nuestro caso todos los procesos están relacionados, pues
uno es el padre y el otro es el hijo, no habrá problema con el uso de los pipes.

       El primer paso, para que la tubería funcione bien, es inicializarla introduciendo algo en la
misma, para que así el primer proceso que quiera entrar en la sección crítica pueda hacerlo, por lo
que prácticamente siempre se hará seguidamente a la creación del pipe, lo siguiente:
write(tuberia[1], &valor, sizeof(int)); fflush(NULL);

       Esta simplemente provoca la escritura en el pipe. Es necesario el uso de la función fflush
para que las escrituras o lecturas del pipe se hagan efectivas. Con NULL se indica que se escriba en
todo los ficheros abiertos, vaciando los bufferes de lectura/escritura. Si no se usa el fflush, podrían
tener problemas.

                                                    16
Práctica 2: Realización de una Alarma Temporizada                                 David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                  Enrique Fernández Perdomo

       Aunque esto ya es parte del manejo de los pipes, a continuación se comenta claramente
como deben manejar, enfocando dicho manejo al control de la sección crítica como problema
genérico de programación.

     Manejo de Pipe
       Los pipes crean un descritor de fichero de lectura y otro de escritura, de modo que podremos
hacer uso de las funciones read y write para leer y escribir en el pipe, respectivamente. Los
descriptores será el primer parámetro de estas dos funciones.

       En nuestro caso tenemos los siguientes descriptores:
                   1. Descriptor de lectura --> tuberia[0]
                   2. Descriptor de escritura --> tuberia[1]

       Para leer del pipe usaremos:
read(tuberia[0], &valor, sizeof(int)); fflush(NULL);

       Para escribir en el pipe usaremos:
write(tuberia[1], &valor, sizeof(int)); fflush(NULL);

       El funcionamiento del pipe es muy simple: si no hay nada escrito en el pipe, al leer se
bloqueará el programa hasta que se escriba algo. Por otro lado, si se lee algo del pipe, éste se
vaciará. No obstante, para que esto funcione así, es necesario que se escriba y lee en igual cantidad,
por lo que se escribe y lee simplemente el tamaño de un entero (sizeof(int)), lo cual se indica en el
tercer parámetro. Por otro lado, aunque no se use para nada el valor escrito o devuelto, éste debe
indicarse, que en nuestro caso se hace con la variable valor, pasado por referencia como segundo
parámetro. Adicionalmente, simpre se hace fflush(NULL); por seguridad.

       Si leemos del pipe al entrar en la sección crítica, el pipe se vaciará, de modo que si otro
proceso va acceder a la sección crítica, cuando lea del pipe quedará bloqueado y no podrá entrar en
la sección crítica. Cuando un proceso salga de la sección crítica, escribirá en el pipe, de modo que
los procesos bloqueados leyendo del pipe, podrán entrar en la sección crítica. El proceso que entrará

                                                    17
Práctica 2: Realización de una Alarma Temporizada                                    David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                     Enrique Fernández Perdomo

será el primero que lea del pipe, por lo que pudiera existir inanición si hay muchos procesos
esparando por entrar en la sección crítica, pero si solo hay un proceso esperando no existe inanición,
como ocurre en nuestro caso. El esqueleto de código sería, por tanto, el siguiente:
read(tuberia[0], &valor, sizeof(int)); fflush(NULL);
/* SECCIÓN CRÍTICA */
write(tuberia[1], &valor, sizeof(int)); fflush(NULL);

       Este esqueleto sería válido y necesario para todos los procesos que quiesieran entrar en la
sección crítica.

     Problema de la Sección Crítica
       En nuesto programa, el problema de la sección crítica es debido al uso de la pantalla por
ambos procesos. De este modo, lo que se debe hacer es controlar que no escriban los dos procesos a
la vez en la pantalla, pues en tal caso lo que escribe uno y otro se entrelazaría.

       Para evitar este problema se hace uso de pipes para el control de la sección crítica, que es el
acceso o uso del recurso pantalla. La solución básica es el uso del esqueleto genérico visto
anteriormente, pero en el siguiente apartado se comenta claramente como es la solución en nuestro
programa, pues difiere ligeramente.

     Solución a la Sección Crítica
       En el programa principal o padre, que hace un uso intensivo de la pantalla, se rodea el uso de
la pantalla, que es la sección crítica, por la lectura y escritura en el pipe, teniendo el siguiente
código, en el que se muestra sombreado de gris la sección crítica (el uso de la pantalla):
           read(tuberia[0], &valor, sizeof(int)); fflush(NULL);
           printf("\r");
           for(i=0; i
Práctica 2: Realización de una Alarma Temporizada                                    David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                     Enrique Fernández Perdomo

acceso a la sección crítica, antes de lanzar el programa date, pero luego, como muere, no podrá
liberar la sección crítica. La solución se consigue haciendo que le proceso padre se encargue de
liberar la sección crítica.

         Así, el proceso padre, justo después del waitpid, liberará la sección crítica, ya que el proceso
hijo no puede. Aquí puede verse como el waitpid debe no ser bloqueante, es decir, tener la opción
WNOHANG, pues si no, no sería necesario el control de salida de la sección crítica, si bien habría
que hacerlo a la entrada, como ahora, para que no imprima la fecha mientras el proceso padre está
imprimiendo en pantalla, y luego a la salida, después del waitpid, como ahora, para que el proceso
principal no se bloquee al entrar en la sección crítica. Si no se usa WNOHANG, basta poner 0 en su
lugar.

         El código, por tanto, del proceso padre e hijo, que es compartido por ambos, si bien uno
ejecuta una parte y el otro otra parte, es e de la función muestra_fecha, y es el siguiente (para el
control de la sección crítica, mostrada sombreada de gris):
            case 0:      // Hilo hijo
                 read(tuberia[0], &valor, sizeof(int)); fflush(NULL);
                 execl("/bin/date","fecha",NULL);
            default:      // Hilo padre
                 waitpid(pid,&estado,WNOHANG);
                 write(tuberia[1], &valor, sizeof(int)); fflush(NULL);

         Hecho todo esto, el programa producirá la siguiente salida, haciendo uso de los parámetros
por defecto, una vez compilado y construido como se indica en el apartado Compilación y
Ejecución del programa (Makefile).
[enrique@adsl p2]$ ./alarma
           o            mié mar 23 15:37:05 WET 2005
                   o    mié mar 23 15:37:09 WET 2005
  o                     mié mar 23 15:37:13 WET 2005
               o        El programa ha finalizado correctamente.

                                                      19
Práctica 2: Realización de una Alarma Temporizada                               David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                Enrique Fernández Perdomo

        Compilación y Ejecución del programa
                                          (Makefile)
        El programa desarrollado tiene su código fuente en el fichero alarma.c y se acompaña de un
fichero Makefile para se compilación, construcción y ejecución más automática; además de limpiar
ficheros creados en tales procesos.

        A continuación se comenta el fichero Makefile, que define una serie de reglas que podrán
lanzarse desde el terminal con el comando make REGLA. Igualmente se comenta como ejecutar el
programa desarrollado, así como el paso de parámetros al mismo.

 Explicación y Reglas del Makefile
        El fichero Makefile define las siguientes reglas:

    1. Compilar
        Tiene la forma:
compilar: limpiar
    $(COMPILADOR) $(FUENTES) -c $(OPCIONES)

        Lo primero que hace es llamar a la regla limpiar y luego compila el programa con el
COMPILADOR que se indica, para todos los ficheros FUENTES que se indique y además, con las
OPCIONES deseadas. Existe una regla alias de ésta que es compile. Su resultado es el siguiente:
[enrique@adsl p2]$ make compilar
rm -f alarma.o alarma
gcc alarma.c -c -Wall -g
[enrique@adsl p2]$ ls
alarma.c alarma.o Makefile

    2. Construir
        Tiene la forma:
construir: compilar
              $(COMPILADOR) $(NOMBRE).o -o $(NOMBRE)

                                                    20
Práctica 2: Realización de una Alarma Temporizada                              David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                               Enrique Fernández Perdomo

        Lo primero que hace es llamar a la regla compilar y luego construye el ejecutable, con el
COMPILADOR indicado y con el NOMBRE proporcionado. Su resultado es el siguiente:
[enrique@adsl p2]$ make construir
rm -f alarma.o alarma
gcc alarma.c -c -Wall -g
gcc alarma.o -o alarma
[enrique@adsl p2]$ ls
alarma alarma.c alarma.o Makefile Memoria

    3. Ejecutar
        Tiene la forma:
ejecutar: construir
               ./$(NOMBRE) $(ARGUMENTOS)

        Lo primero que hace es llamar a la regla construir y luego ejecuta al programa NOMBRE
con los ARGUMENTOS indicados. Por norma general querremos ejecutar el programa, de modo
que podemos usar directamente el comando make run, que producirá el siguiente resultado:
[enrique@adsl p2]$ make run
rm -f alarma.o alarma
gcc alarma.c -c -Wall -g
gcc alarma.o -o alarma
./alarma -b "o" -c 40 -s SIGNAL -t 4 -v 30000000
           o               mié mar 23 15:41:07 WET 2005
                    o      mié mar 23 15:41:11 WET 2005
           o               El programa ha finalizado correctamente.

        Puede verse como limpia, compila, construye y ejectua el programa alarma pasando una
serie de argumentos por defecto.

    4. Limpiar
        Tiene la forma:
limpiar:
               $(RM) $(OBJETOS) $(EJECUTABLES)

                                                         21
Práctica 2: Realización de una Alarma Temporizada                                 David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                  Enrique Fernández Perdomo

       El resultado de limpiar puede verse a continuación:
[enrique@adsl p2]$ ls
alarma alarma.c alarma.o Makefile
[enrique@adsl p2]$ make clean
rm -f alarma.o alarma
[enrique@adsl p2]$ ls
alarma.c Makefile

       Simplemente borra con el comando RM los ficheros OBJETOS y EJECUTABLES, que
habrán creado las otras reglas.

       Las variables creadas son las siguientes:
COMPILADOR = gcc
OPCIONES = -Wall -g
NOMBRE = alarma
FUENTES = $(NOMBRE).c
OBJETOS = $(NOMBRE).o
EJECUTABLES = $(NOMBRE)
TIPO_BOLA = "o"
COLUMNA_MAX = 40
TIPO_SIGNAL = SIGNAL
TIEMPO_ALARMA = 4
VELOCIDAD = 30000000
ARGUMENTOS = -b $(TIPO_BOLA) -c $(COLUMNA_MAX) -s $(TIPO_SIGNAL)\
        -t $(TIEMPO_ALARMA) -v $(VELOCIDAD)
RM = rm -f

       La variable ARGUMENTOS se compone de los argumentos que puede recibir el programa,
que se explican en el siguiente apartado.

 Ejecución del programa
       Si simplemente ejecutamos el programa con ./alarma se hará uso de los valores por
defectos, que hay dentro del código; en el caso del Makefile siempre puede alterarse modificando el
propio fichero Makefile. Para tener mayor versatilidad, podemos hacer uso del paso de parámetros,
que se explican a continuación, pues de lo contrario, por defecto se tomarán los siguientes valores:
Bola = “o”
Columna máxima = 40

                                                    22
Práctica 2: Realización de una Alarma Temporizada                                David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                 Enrique Fernández Perdomo

Tipo de señal = SIGNAL
Tiempo de alarma = 4
Velocidad = 30000000

     Paso de Parámetros
       Existen 5 parámetros distintos, a parte de la ayuda con --help. Dichos parámetros tiene una
abreviatura para indicarlos y poner seguidamente el valor, de modo que no importa el orden de los
mismos. Son los siguientes, si bien ya se comentaron en el apartado Parámetros.
       -b --> Ristra que emula la bola.
       -c --> Columna máxima.
       -s --> Tipo de implementación de las señales (con SIGNAL o con SIGACTION).
       -t --> Tiempo de la alarma.
       -v --> Velocidad de la bola; medida de forma inversa, como iteraciones de un bucle.

       A continuación se muestran algunos ejemplos de ejecución:

    1. Modificación de la bola (-b).
       En el primer caso simplemente emulamos la bola con *, poniendo -b “*”.
[enrique@adsl p2]$ ./alarma -b "*"
        *                mié mar 23 15:42:21 WET 2005
                       * mié mar 23 15:42:25 WET 2005
                  *      El programa ha finalizado correctamente.

       Hay que indicar que el sentido de la variable max_columna (que controla la columna
máxima) es el número de columnas que recorre el primer caracter de la ristra que emula la bola,
pues así, se mueve lo deseado, si bien el último caracter de la ristra llegara más allá de la columna
indicada por max_columna, lo cual se observará viendo el siguiente ejemplo, donde la columna de
escritura de la fecha está más a la derecha, en concreto 3 columnas más allá, pues se emula la bola
con “hola”, que tiene 3 caracteres de más que la unidad.

[enrique@adsl p2]$ ./alarma -b hola
         hola                mié mar 23 15:42:41 WET 2005
                      hola   mié mar 23 15:42:45 WET 2005

                                                       23
Práctica 2: Realización de una Alarma Temporizada                                           David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                            Enrique Fernández Perdomo

      hola                     mié mar 23 15:42:49 WET 2005
              hola             mié mar 23 15:42:53 WET 2005
                        hola   El programa ha finalizado correctamente.

          Finalmente, recordamos que no son necesarias las comillas, pero para ciertos caracteres son
necesarias, y para ristras con espacios en blanco también, como se ve a continuación:
[enrique@adsl p2]$ ./alarma -b "Esta es la bola: ()"
         Esta es la bola: ()               mié mar 23 15:44:18 WET 2005
                          Esta es la bola: () mié mar 23 15:44:22 WET 2005
                  Esta es la bola: ()      El programa ha finalizado correctamente.

          Los caracteres que requieren las comillas son aquellos con un significado especial en el
terminal, ya que se sustituirán por otras cadenas. Por ejemplo:
          * --> indica cualquier fichero/carpeta, luego toma el valor del primer fichero/carpeta del
directorio por orden alfabético.
          ~ --> toma el valor del directorio de trabajo del usuario.
          Etcétera.

      2. Modificación de la columna máxima (-c).
          Se reduce o aumenta el número de columnas por los que se mueve la bola, como se ve en los
siguientes ejemplos, en los que se reduce la columna máxima a 5 y 1, respectivamente; hay que
recordar que la primera columna del terminal es la 0 y el valor de la columna máxima debe ser
mayor que 0, pues de lo contrario fallará la simulación de la bola rebotando.
[enrique@adsl p2]$ ./alarma -c 5
    o mié mar 23 15:46:31 WET 2005
 o mié mar 23 15:46:35 WET 2005
o     mié mar 23 15:46:39 WET 2005
    o El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -c 1
o mié mar 23 15:46:49 WET 2005
o El programa ha finalizado correctamente.

      3. Modificación del tipo se señal (-s).
          A continuación se muestra la ejecución del programa usando señales implementadas con

                                                          24
Práctica 2: Realización de una Alarma Temporizada                                         David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                          Enrique Fernández Perdomo

SIGNAL y con SIGACTION, respectivamente. Y se observa que el comportamiento no precesente
diferencias aparentes.
[enrique@adsl p2]$ ./alarma -s SIGNAL
              o                          mié mar 23 15:48:01 WET 2005
                                       o mié mar 23 15:48:05 WET 2005
                               o         El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -s SIGACTION
                       o                 mié mar 23 15:48:13 WET 2005
                               o         mié mar 23 15:48:17 WET 2005
o                                        El programa ha finalizado correctamente.

        4. Modificación del tiempo de la alarma (-t).
              Si alteramos la velocidad de la alarma, la frecuencia con la que se muestra la fecha varía.
Así, en los siguientes ejemplos puede verse claramente (se ha puesto 1, 4 y 7 segundos
respectivamente):
[enrique@adsl p2]$ ./alarma -t 1
o                                        mié mar 23 15:49:09 WET 2005
    o                                    mié mar 23 15:49:10 WET 2005
         o                               mié mar 23 15:49:11 WET 2005
               o                         mié mar 23 15:49:12 WET 2005
                       o                 mié mar 23 15:49:13 WET 2005
                               o         El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -t 4
                   o                     mié mar 23 15:49:23 WET 2005
                                   o     mié mar 23 15:49:27 WET 2005
                           o             El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -t 7
                                       o mié mar 23 15:49:39 WET 2005
          o                              mié mar 23 15:49:46 WET 2005
                       o                 El programa ha finalizado correctamente.

        5. Modificación de la velocidad de la bola (-v).
              La velocidad de la bola no puede apreciarse en las siguientes capturas, pero si pueden
comentarse algunas conclusiones, aunque éstas también dependerán de la máquina en que se ejecute
el programa.

                                                                       25
Práctica 2: Realización de una Alarma Temporizada                                   David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                    Enrique Fernández Perdomo

[enrique@adsl p2]$ ./alarma -v 0
 o                           mié mar 23 15:50:18 WET 2005
 o                           El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -v 1000000
         o                   mié mar 23 15:50:39 WET 2005
                            o El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -v 3000000
     o                       mié mar 23 15:50:55 WET 2005
             o               El programa ha finalizado correctamente.
[enrique@adsl p2]$ ./alarma -v 30000000
                   o         mié mar 23 15:51:01 WET 2005
                   o         El programa ha finalizado correctamente.

             Con 0 se tiene la velocidad más rápida y con 30000000 la más lenta, en la cual se aprecian
saltos. Con 3000000 se ve un movimiento fluido y rápido pero más o menos perceptible.

             Algunos ejemplos más, de ejecución, haciendo uso de combinaciones de parámetros, se
muestran a continuación:
      1. Ejemplo 1
             Se pone un tiempo de alarma de 1 segundo (-t 1), una velocidad de la bola rápida pero
perceptible (-v 3000000) y se emula la bola con el carácter ¬.
[enrique@adsl p2]$ ./alarma -t 1 -v 3000000 -b ¬
                           ¬ mié mar 23 15:53:56 WET 2005
 ¬                            mié mar 23 15:53:57 WET 2005
                           ¬ mié mar 23 15:53:58 WET 2005
                       ¬      mié mar 23 15:53:59 WET 2005
                       ¬      mié mar 23 15:54:00 WET 2005
         ¬                    mié mar 23 15:54:01 WET 2005
  ¬                           mié mar 23 15:54:02 WET 2005
                       ¬      El programa ha finalizado correctamente.

      2. Ejemplo 2
             Se pone un tiempo de alarma de 1 segundo (-t 1), una velocidad de la bola rápida pero
perceptible (-v 3000000), se emula la bola con la ristra “DSO” y se limita la columna máxima a la
3, de modo que el primer carácter que emula la bola (D) sólo podrá llegar a la columna 3 como

                                                            26
Práctica 2: Realización de una Alarma Temporizada                                David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                 Enrique Fernández Perdomo

máxima (podrá estar en las columnas 0, 1, 2 y 3); y el último carácter (O) podrá llegar a la columna
5, pues la ristra tiene 2 caracteres más que la unidad (3 + 2 = 5).
[enrique@adsl p2]$ ./alarma -t 1 -v 3000000 -b "DSO" -c 3
 DSO mié mar 23 15:56:11 WET 2005
DSO mié mar 23 15:56:12 WET 2005
 DSO mié mar 23 15:56:13 WET 2005
DSO mié mar 23 15:56:14 WET 2005
 DSO mié mar 23 15:56:16 WET 2005
 DSO mié mar 23 15:56:16 WET 2005
DSO mié mar 23 15:56:17 WET 2005
  DSOEl programa ha finalizado correctamente.

                                                       27
Práctica 2: Realización de una Alarma Temporizada                                          David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                           Enrique Fernández Perdomo

                          Anexo – Código Fuente
    alarma.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int tuberia[2], tiempo_alarma = 4;

void muestra_fecha() {
    int pid, estado, valor;
    switch (pid = fork()) {
          case -1:       // fork() falla
                printf("No se pudo crear otro proceso\n");
                exit(1);
          case 0:        // Hilo hijo
                  // · Imprime fecha con el programa date (controlando sección crítica con pipe)
                 read(tuberia[0], &valor, sizeof(int)); fflush(NULL);
                 execl("/bin/date","fecha",NULL);
            default:      // Hilo padre
                  // Permite liberar el hilo hijo del árbol de procesos
                  waitpid(pid,&estado,WNOHANG);
                  write(tuberia[1], &valor, sizeof(int)); fflush(NULL);
                  alarm(tiempo_alarma);
      }
}

void termina() {
    printf("El programa ha finalizado correctamente.\n");
    exit(0);
}

int main(int argn, char *argv[]) {
     int columna = 0, max_columna = 40, paso = 1, velocidad = 30000000, valor, i;
     int senal_creada = 0; // Indica si se han creado las señales
     char *bola = "o";
     struct sigaction senal_alarma, senal_int;

      // PASO 1: Parámetros de entrada
      // · Ayuda (--help)
     if ((argn == 2) && (strcmp(argv[1],"--help") == 0)) {
           printf("Modo de empleo: ./alarma [OPCIÓN]\n");
           printf("\t-b bola; Ristra que representa la bola\n");
           printf("\t-c columna_máxima; Columna máxima a la que llega la bola\n");
           printf("\t-s SIGNAL|SIGACTION; Implementación de señales (SIGNAL por
defecto)\n");
           printf("\t-t segundos; Tiempo entre muestreo de la fecha\n");

                                                        28
Práctica 2: Realización de una Alarma Temporizada                                   David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                    Enrique Fernández Perdomo

            printf("\t-v iteraciones; Velocidad de la bola\n");
            exit(0);
     }
     // · Uso Incorrecto
     if ((argn%2) == 0) {
           printf("alarma: Forma de uso incorrecta\n");
           printf("Pruebe: ./alarma --help\n");
           exit(0);
     }
     // · Toma de Parámetros
     int p; for(p=1; p
Práctica 2: Realización de una Alarma Temporizada                                  David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.                                   Enrique Fernández Perdomo

      // PASO 3: Bucle de muestreo de la bola
      while(1) {
            // PASO 3.1: Pausa entre movimientos de la bola
            // (sleep(1), solo funciona para segundos enteros)
            for(i=0; i
Práctica 2: Realización de una Alarma Temporizada              David J. Horat Flotats
Diseño de Sistemas Operativos – U.L.P.G.C.               Enrique Fernández Perdomo

ejecutar: construir
     ./$(NOMBRE) $(ARGUMENTOS)

limpiar:
     $(RM) $(OBJETOS) $(EJECUTABLES)

####################
# Reglas en Inglés #
####################

compile: compilar
build: construir
run: ejecutar
clean: limpiar

                                                    31
También puede leer