Búsqueda de sitios web

Mecanismos de temporizador con C y Linux


Obtenga un control detallado sobre sus funciones de sincronización utilizando Linux y C.

Los mecanismos de temporizador le permiten programar el kernel del sistema operativo para que notifique a una aplicación cuando haya transcurrido un tiempo predeterminado. Normalmente los utilizará proporcionando dos datos. Primero, deberá especificar cuánto tiempo debe tomar el cronómetro antes de notificar. En segundo lugar, deberá preparar una función de devolución de llamada para actuar cuando se produzca esa notificación.

Enfoque tradicional de los temporizadores

Los mecanismos de temporizador en los sistemas basados en Linux y Unix han evolucionado para satisfacer diversas necesidades. Diferentes enfoques pueden ayudarle a resolver diferentes tipos de problemas. Sin embargo, a menudo verás que la primera versión del mecanismo alarm() todavía está en uso.

La función de alarma es la forma más sencilla de utilizar un temporizador; aquí está su prototipo:

unsigned int alarm(unsigned int seconds);

Con este método, solo puede especificar el tiempo en segundos completos. Cuando se acaba el tiempo, el sistema operativo envía la señal SIGALRM a su aplicación. Para procesar la expiración del temporizador en su aplicación, también debe definir una función de devolución de llamada.

A continuación se muestra un ejemplo de una función de manejo de señales:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h> 
  
void timer_callback(int signum)
{
    time_t now = time(NULL);
    printf("Signal %d caught on %li", signum, now);
}
 
int main()
{
    signal(SIGALRM, timer_callback);
    alarm(1);
    sleep(3);
    return 0;
}

Este código genera una señal SIGALRM después de 1 segundo. Si desea aumentar el retraso del temporizador a cinco segundos, simplemente llame a alarm(5). Para detener el cronómetro, pase un valor de 0: alarma(0).

Cuando se acabe el tiempo, el temporizador que utilice no se reiniciará periódicamente. Por ejemplo, si desea retrasar un segundo más, debe reiniciar el mecanismo con otra llamada a alarm().

A pesar de su facilidad de uso, este método tiene algunas desventajas:

  • Sólo un temporizador a la vez.
  • No hay soporte de temporizador periódico.
  • Sólo puedes dar el período de tiempo en múltiplos de segundos completos.
  • No hay forma de saber cuánto tiempo queda en un temporizador.

Guarde el código de muestra proporcionado anteriormente como alarm.c. Cuando lo compilas y lo ejecutas, el programa llamará a la función timer_callback después de un segundo. Luego esperará los dos segundos restantes debido a la línea sleep(3) y luego finalizará.

$ gcc -o alarm alarm.c
$ time ./alarm
Signal 14 caught on 1653490465
real 0m1.004s
user 0m0.000s
sys 0m0.003s

La razón para utilizar el comando de tiempo es poder ver los tiempos. Pero si nos fijamos en el resultado, el tiempo total de ejecución no es de tres segundos. Esto se debe a la señal SIGALRM de alarm(1) cuando transcurre el primer segundo, mientras que la syscall causada por el modo de suspensión(3) La función se está ejecutando. Cuando llega esta señal, interrumpe la llamada al sistema iniciada para sleep(3).

Usar un temporizador de intervalos

El mecanismo del temporizador de intervalos estuvo disponible por primera vez en la versión 4.2 BSD. Posteriormente fue estandarizado por POSIX. Sus principales ventajas sobre el método tradicional de temporizador basado en alarm() son:

  • Proporciona resolución de microsegundos.
  • Permite controlar la medición del tiempo con más detalle a través de tres modos diferentes.
  • Es posible configurarlo una vez y hacerlo funcionar periódicamente.
  • Es posible saber cuánto tiempo está presente en un momento dado.

Los prototipos de funciones utilizados para las operaciones del temporizador de intervalos son los siguientes:

#include <sys/time.h>
 
int setitimer(int which, const struct itimerval *newValue, struct itimerval *oldValue);
int getitimer(int which, struct itimerval *value);
 
struct itimerval
{
    struct timeval itInterval; // next value
    struct timeval itValue; // current value
};
 
struct timeval
{
    long tv_sec;
    long tv_usec;
};

Si desea configurar un temporizador de intervalos, deberá utilizar la estructura itimerval. Deberá pasar un valor usando esta estructura como segundo argumento a la función settimer.

Por ejemplo, un temporizador de intervalos que notificará a su aplicación durante 1 segundo y luego cada 300 milisegundos se puede configurar de la siguiente manera:

struct itimerval newTimer;
struct itimerval oldTimer;
 
newTimer.itValue.tv_sec = 1;
newTimer.itValue.tv_usec = 0;
 
newTimer.itInterval.tv_sec = 0;
newTimer.itInterval.tv_usec = 300 * 1000;
 
setitimer(ITIMER_REAL, &newTimer, &oldTimer);

Si hay un temporizador de intervalo activo antes de que se establezcan los nuevos valores, sus valores se transfieren a la dirección de la variable del tipo itimerval dada al tercer parámetro de la función.

Puede configurar tres tipos diferentes de temporizadores con el mecanismo del temporizador de intervalos. Especifique el tipo de temporizador en el primer parámetro de setimer():

Timer Type

Signal

Explanation

ITIMER_REAL

SIGALRM

Independent of the time spent by the application, calculated over the total elapsed time.

ITIMER_VIRTUAL

SIGVTALRM

Calculated over the time the application is running in user mode only.

ITIMER_PROF

SIGPROF

Calculated over the sum of the time spent by the application in both user and system modes.

Puede ver en esta tabla que el tipo ITIMER_REAL envía una señal SIGALRM, al igual que la función alarm().

Usar un temporizador de intervalos y una alarma() en la misma aplicación resultará confuso. Aunque puedes realizar una segunda comprobación del tiempo restante con gettimer(), no tiene sentido utilizarlos simultáneamente.

A continuación se muestra un ejemplo de cómo definir la función del controlador de señales con el encabezado de depuración:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include "./debug.h"
 
void timer_callback(int signum)
{
    struct timeval now;
    gettimeofday(&now, NULL);
    printf("Signal %d caught on %li.%03li ", signum, now.tv_sec, now.tv_usec / 1000);
}
  
int main()
{
    unsigned int remaining = 3; 
 
    struct itimerval new_timer;
    struct itimerval old_timer;
  
    new_timer.it_value.tv_sec = 1;
    new_timer.it_value.tv_usec = 0;
    new_timer.it_interval.tv_sec = 0;
    new_timer.it_interval.tv_usec = 300 * 1000; 
 
    setitimer(ITIMER_REAL, &new_timer, &old_timer);
    signal(SIGALRM, timer_callback); 
 
    while (sleep(remaining) != 0)
    {
        if (errno == EINTR)
            debugf("sleep interrupted by signal");
        else
            errorf("sleep error %s", strerror(errno));
    }
 
    return 0;
}

El código anterior utiliza la función sleep() para esperar tres segundos. Durante este tiempo se ejecuta un cronómetro de intervalos, primero durante un segundo y luego en un intervalo de 300 milisegundos.

Para una mejor comprensión, guarde y compile el código de muestra con el nombre interval.c:

$ gcc -o interval interval.c
$ time ./interval
Signal 14 caught on 1653493614.325
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.625
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.925
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.225
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.525
...

Como puede ver en el resultado después de que se ejecuta el temporizador, llama a la función de devolución de llamada cada 300 milisegundos.

Sin embargo, después de esperar un poco más, notarás que la aplicación no finaliza. Continúa ejecutando la función de devolución de llamada cada 300 milisegundos. Si aumenta el valor del intervalo en milisegundos, verá que la aplicación finaliza. Esto se debe al área de uso de la función sleep().

Importancia del uso de temporizadores en Linux

Especialmente para aplicaciones en tiempo real, el mecanismo del temporizador es de gran importancia. Esta también es una solución utilizada para optimizar el rendimiento. Incluso puedes usarlo para medir el tiempo de actividad o la latencia de tu aplicación. Es importante utilizar mecanismos de cronómetro para realizar un seguimiento del tiempo transcurrido y de los eventos de transición de tiempo.

Artículos relacionados: