How to get the most accurate real-time periodic interrupts in Linux?

I want to be interrupted at frequencies that have ten, so turning on interrupts from / dev / rtc is not ideal. I would like to sleep 1 millisecond or 250 ฮผs between interrupts.

Enabling periodic interrupts from / dev / hpet works very well, but it doesn't seem to work on some machines. Obviously, I can't use it on machines that don't actually have HPET. But I can't get it to work on some machines that have hpet available as a synchronization source. For example, on Core 2 Quad, the sample program included in the kernel documentation cannot be executed using HPET_IE_ON when configured to poll.

It would be better to use the itimer interface provided by Linux, instead of interacting directly with the device driver. And on some systems, itimer provides periodic interrupts that are more stable over time. That is, since hpet cannot be interrupted with exactly the frequency I want, interrupts begin to drift from the wall time. But I see that some systems sleep longer (10+ milliseconds) than they should use itimer.

Here's a test program using itimer for interrupts. On some systems, it will print only one warning that it has slept for about 100 microseconds or so during the target time. In other cases, he gives a batch of warnings that he slept for 10+ milliseconds during the target time. Compile with -lrt and run using sudo chrt -f 50 [name]

#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; } 
+7
source share
2 answers

Regardless of the synchronization mechanism used, this boils down to a combination of changes in the status of the task when the kernel scheduler is called (usually 100 or 1000 times per second), as well as cpu rivalry with other processes.

The mechanism I found that provides the โ€œbestโ€ time on Linux (Windows) should do the following:

  • Put the process on a shielded processor
  • First, the initial sleep in 1 ms. If on a shielded processor your process should wake up right on the border of the OS scheduler ticker
  • Use either RDTSC directly or CLOCK_MONOTONIC to fix the current time. Use this as zero time to calculate the absolute awakening time for all future periods. This will help minimize drift over time. It cannot be completely ruled out, since hardware timing varies over time (thermal problems and the like), but this is a pretty good start.
  • Create a sleep function that sleeps 1 ms below the target absolute wake-up time (as the OS scheduler could most accurately be) and then burns the CPU in a hard cycle, constantly checking the RDTSC / CLOCK_REALTIME value.

It takes some work, but you can get pretty good results using this approach. The relevant question you can look at can be found here .

+4
source

I had the same problem with setting bare setitimer (). The problem is that your process is scheduled under the SCHED_OTHER policy at a static priority level of 0 by default. This means that you are in a pool with all other processes and decide dynamic priorities. At a time when there is some kind of system load, you get delays.

The solution is to use the sched_setscheduler () system call, increase the static priority to at least one, and specify the SCHED_FIFO policy. This leads to significant improvement.

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

You need to run root so that this can be done. An alternative is to use the chrt program to do the same, but you must know the PID of your RT process.

 sudo chrt -f -p 1 <pid> 

See my blog about him here .

+4
source

All Articles