2011-04-29 20 views
7

Quiero ser interrumpido en frecuencias que son potencias de diez, por lo que habilitar interrupciones desde/dev/rtc no es ideal. Me gustaría dormir 1 milisegundo o 250 microsegundos entre interrupciones.¿Cómo obtengo las interrupciones periódicas más precisas en tiempo real en Linux?

Habilitar las interrupciones periódicas de/dev/hpet funciona bastante bien, pero parece que no funciona en algunas máquinas. Obviamente no puedo usarlo en máquinas que en realidad no tienen un HPET. Pero no puedo hacer que funcione en algunas máquinas que también tienen acceso como fuente de reloj. Por ejemplo, en un Core 2 Quad, el programa de ejemplo incluido en la documentación del kernel falla en HPET_IE_ON cuando se configura para sondear.

Sería mejor utilizar la interfaz itimer proporcionada por Linux en lugar de interactuar directamente con el controlador del dispositivo de hardware. Y en algunos sistemas, el temporizador proporciona interrupciones periódicas que son más estables a lo largo del tiempo. Es decir, dado que el hpet no puede interrumpir exactamente a la frecuencia que deseo, las interrupciones comienzan a desviarse del tiempo de la pared. Pero veo que algunos sistemas duermen mucho más tiempo (más de 10 milisegundos) de lo que deberían con un temporizador.

Aquí hay un programa de prueba que usa itimer para las interrupciones. En algunos sistemas, solo imprimirá una advertencia de que durmió aproximadamente 100 microsegundos más o menos durante el tiempo objetivo. En otros, imprimirá lotes de advertencia de que durmió 10+ milisegundos durante el tiempo objetivo. Compilar con -lrt y correr con chrt sudo -f 50 [nombre]

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <error.h> 
#include <errno.h> 
#include <sys/ioctl.h> 
#include <sys/types.h> 
#include <sys/time.h> 
#include <time.h> 
#include <signal.h> 
#include <fcntl.h> 
#define NS_PER_SECOND 1000000000LL 
#define TIMESPEC_TO_NS(aTime) ((NS_PER_SECOND * ((long long int) aTime.tv_sec)) \ 
    + aTime.tv_nsec) 

int main() 
{ 
    // Block alarm signal, will be waited on explicitly 
    sigset_t lAlarm; 
    sigemptyset(&lAlarm); 
    sigaddset(&lAlarm, SIGALRM ); 
    sigprocmask(SIG_BLOCK, &lAlarm, NULL); 

    // Set up periodic interrupt timer 
    struct itimerval lTimer; 
    int lReceivedSignal = 0; 

    lTimer.it_value.tv_sec = 0; 
    lTimer.it_value.tv_usec = 250; 
    lTimer.it_interval = lTimer.it_value; 

    // Start timer 
    if (setitimer(ITIMER_REAL, &lTimer, NULL) != 0) 
    { 
     error(EXIT_FAILURE, errno, "Could not start interval timer"); 
    } 
    struct timespec lLastTime; 
    struct timespec lCurrentTime; 
    clock_gettime(CLOCK_REALTIME, &lLastTime); 
    while (1) 
    { 
     //Periodic wait 
     if (sigwait(&lAlarm, &lReceivedSignal) != 0) 
     { 
      error(EXIT_FAILURE, errno, "Failed to wait for next clock tick"); 
     } 
     clock_gettime(CLOCK_REALTIME, &lCurrentTime); 
     long long int lDifference = 
      (TIMESPEC_TO_NS(lCurrentTime) - TIMESPEC_TO_NS(lLastTime)); 
     if (lDifference > 300000) 
     { 
      fprintf(stderr, "Waited too long: %lld\n", lDifference ); 
     } 
     lLastTime = lCurrentTime; 
    } 
    return 0; 
} 
+0

Esto puede ser un error del kernel. Mi ejemplo de itimer parece funcionar bien en todas las máquinas que usan 2.6.32 pero no en 2.6.35 o 2.6.38. – Matt

Respuesta

4

Independientemente del mecanismo de tiempo que utiliza, que se reduce a una combinación de cambios en el estado de ejecución de su tarea, cuando se invoca el planificador del núcleo (usualmente 100 o 1000 veces por segundo) y contención de CPU con otros procesos.

El mecanismo que he encontrado que logra el "mejor" momento en Linux (Windows también) es hacer lo siguiente:

  1. lugar el proceso en un Shielded CPU
  2. Tener el proceso inicialmente dormir por 1 ms. Si se encuentra en una CPU blindada, su proceso debe activarse en el límite de ticks del planificador del sistema operativo
  3. Utilice directamente el RDTSC o CLOCK_MONOTONIC para capturar la hora actual. Use esto como el tiempo cero para calcular los tiempos absolutos de activación para todos los períodos futuros. Esto ayudará a minimizar la deriva con el tiempo. No se puede eliminar por completo ya que el tiempo de hardware fluctúa con el tiempo (problemas térmicos y similares) pero es un buen comienzo.
  4. Cree una función de suspensión que duerma 1ms menos que el tiempo de activación absoluta objetivo (ya que es lo más preciso que puede ser el planificador de sistema operativo) y queme la CPU en un ciclo cerrado comprobando continuamente el valor RDTSC/CLOCK_REALTIME.

Se necesita un poco de trabajo, pero puede obtener muy buenos resultados con este enfoque. Puede encontrar una pregunta relacionada que puede consultar en here.

+2

Linux ahora puede programar tareas siempre que estén listas en lugar de confiar en el planificador que ejecuta cada jiffy (creo que esta es la opción NO_HZ). El método HPET (bloqueo en una llamada de lectura) funciona bien para el cambio de contexto a una tarea de alta prioridad en tiempo real cada 250 microsegundos si el método HPET funciona en absoluto. El método itimer generalmente funciona para hacer lo mismo. En algunas máquinas, funciona todo el tiempo. En otros, por lo general funciona (no estoy inundado de mensajes de advertencia), pero a veces espero 1MS +. – Matt

4

He tenido el mismo problema con una configuración de setitimer(). El problema es que su proceso está programado por la política SCHED_OTHER en el nivel de prioridad estática 0 de forma predeterminada. Esto significa que está en un grupo con todos los demás procesos y deciden las prioridades dinámicas. En el momento en que hay un poco de carga del sistema, obtienes latencias.

La solución es utilizar la llamada al sistema sched_setscheduler(), aumentar su prioridad estática a al menos uno y especificar la política SCHED_FIFO. Causa una mejora dramática.

#include <sched.h> 
... 
int main(int argc, char *argv[]) 
{ 
    ... 
    struct sched_param schedp; 
    schedp.sched_priority = 1; 
    sched_setscheduler(0, SCHED_FIFO, &schedp); 
    ... 
} 

Tienes que ejecutar la sesión como root para poder hacer esto. La alternativa es usar el programa chrt para hacer lo mismo, pero debe conocer el PID de su proceso RT.

sudo chrt -f -p 1 <pid> 

Ver la publicación de mi blog sobre el here.

Cuestiones relacionadas