2013-08-20 3 views
10

Ich brauche präzise Zeitsteuerung für die 1 us-Ebene zu Zeit eine Änderung im Tastverhältnis einer PWM-Welle.Precise Linux Timing - Was bestimmt die Auflösung von clock_gettime()?

Hintergrund

ich eine Gumstix über Wasser COM bin mit (https://www.gumstix.com/store/app.php/products/265/), die einen Single-Core-ARM-Cortex-A8-Prozessor bei 499,92 BogoMIPS läuft (die Gumstix Seite mit 800Mhz bis 1GHz behauptet bis empfohlen) nach/proc/cpuinfo. Das Betriebssystem ist eine Angstrom-Image-Version von Linux, die auf der Kernel-Version 2.6.34 basiert, und es ist auf der Gumstix Water COM verfügbar.

Das Problem

Ich habe ziemlich viel lesen über präzises Timing in Linux getan (und haben die meisten es versucht) und der Konsens scheint zu sein, dass clock_gettime mit() und Referenzierung CLOCK_MONOTONIC ist die beste Weg, es zu tun. (Ich hätte gerne das RDTSC-Register für das Timing verwendet, da ich einen Kern mit minimalen Energiesparfunktionen habe, aber dies ist kein Intel-Prozessor.) Hier ist der ungerade Teil, während clock_getres() 1 zurückgibt, was eine Auflösung von 1 ns nahelegt , tatsächliche Timing-Tests deuten auf eine minimale Auflösung von 30517ns oder (es kann nicht Zufall sein) genau die Zeit zwischen einem 32.768KHz Clock-Ticks. Hier ist, was ich meine:

// Stackoverflow example 
#include <stdio.h> 
#include <time.h>  

#define SEC2NANOSEC 1000000000 

int main(int argc, const char* argv[]) 
{    
    // //////////////// Min resolution test ////////////////////// 
    struct timespec resStart, resEnd, ts; 
    ts.tv_sec = 0; // s 
    ts.tv_nsec = 1; // ns 
    int iters = 100; 
    double resTime,sum = 0;  
    int i; 
    for (i = 0; i<iters; i++) 
    { 
     clock_gettime(CLOCK_MONOTONIC, &resStart);  // start timer 
     // clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts); 
     clock_gettime(CLOCK_MONOTONIC, &resEnd);  // end timer 
     resTime = ((double)resEnd.tv_sec*SEC2NANOSEC + (double)resEnd.tv_nsec 
        - ((double)resStart.tv_sec*SEC2NANOSEC + (double)resStart.tv_nsec); 
     sum = sum + resTime; 
     printf("resTime = %f\n",resTime); 
    }  
    printf("Average = %f\n",sum/(double)iters); 
} 

(ärgern Sie sich nicht über den Doppelguss, tv_sec in einem time_t und tv_nsec ist eine lange.)

Compile mit:

gcc soExample.c -o runSOExample -lrt 

Run mit:

./runSOExample 

mit dem Nanosleep kommentiert, wie gezeigt, ist das Ergebnis entweder 0ns oder 30517ns mit den meisten Wesen 0ns. Dies führt mich zu der Annahme, dass CLOCK_MONOTONIC bei 32,768 kHz aktualisiert wird und die meiste Zeit die Uhr nicht aktualisiert wurde, bevor der zweite clock_gettime() -Aufruf erfolgt, und in Fällen, in denen das Ergebnis 30517ns beträgt, wurde die Uhr zwischen Anrufen aktualisiert.

Wenn ich dasselbe auf meinem Entwicklungscomputer (AMD FX (tm) -6100 Six-Core Prozessor mit 1,4 GHz) mache, ist die minimale Verzögerung eine konstantere 149-151ns ohne Nullen.

Also vergleichen wir diese Ergebnisse mit den CPU-Geschwindigkeiten. Für den Gumstix entsprechen 30517 ns (32,768 kHz) 15298 Zyklen der 499,93 MHz CPU. Für meinen Entwicklungscomputer entspricht 150ns 210 Zyklen der 1,4 GHz CPU.

Mit dem clock_nanosleep() -Aufruf unkommentiert Die durchschnittlichen Ergebnisse sind folgende: Gumstix: Avg-Wert = 213.623, und das Ergebnis verändert sich, oben und unten, um Vielfache von jener min Auflösung von 30517ns Dev Computer: 57.710 bis 68.065 ns mit kein klarer Trend. Im Falle des Entwicklungscomputers erwarte ich, dass die Auflösung tatsächlich auf dem 1-ns-Niveau liegt, und die gemessenen ~ 150 ns sind wirklich die Zeit, die zwischen den zwei clock_gettime() - Aufrufen verstrichen ist.

Also meine Fragen sind diese: Was bestimmt diese minimale Auflösung? Warum ist die Auflösung des Dev-Computers 30000X besser als die des Gumstix, wenn der Prozessor nur ~ 2,6-mal schneller läuft? Gibt es eine Möglichkeit zu ändern, wie oft CLOCK_MONOTONIC aktualisiert wird und wo? Im Kernel?

Danke! Wenn Sie weitere Informationen oder Erläuterungen benötigen, fragen Sie einfach.

+1

Ich frage mich nur. Wäre nicht irgendeine Art von Hardware erforderlich, um Zyklen vom CPU-Takt zu erfassen? Zumindest auf dieser Ebene der Präzision. Vielleicht hat der Gumstix das nicht? (Ich spreche gerade über ein Register, das Zecken zählen kann und sie halten, bevor es rollt.) – Jiminion

+0

Mit dem ganzen Lesen sagten Sie, dass Sie getan haben, haben Sie möglicherweise schon gesehen *** [dieser Link] (http: // gallinazo.flightgear.org/technology/gumstix-overo-rcservos-and-pwm-signal-generation/)***, aber auf jeden Fall nur für den Fall. Ein Teil des Materials ist relevant. – ryyker

+0

Das gleiche Problem haben. Vielfache von 30517ns. Ich benutze eine Overo Tide. Hast du jemals dieses Problem gelöst? – Ryan

Antwort

1

Ich habe kein Gumstix zur Hand, aber es sieht so aus, als ob Ihre Taktquelle langsam ist. Lauf:

$ dmesg | grep clocksource

Wenn Sie zurück

[ 0.560455] Switching to clocksource 32k_counter

bekommen Dies könnte erklären, warum Ihre Uhr so ​​langsam ist.

In den letzten Kernel gibt es ein Verzeichnis /sys/devices/system/clocksource/clocksource0 mit zwei Dateien: available_clocksource und current_clocksource. Wenn Sie dieses Verzeichnis haben, versuchen Sie, zu einer anderen Quelle zu wechseln, indem Sie den Namen in die zweite Datei übertragen.

7

Wie ich verstehe, kann der Unterschied zwischen zwei Umgebungen (Gumstix und Ihr Dev-Computer) der zugrunde liegende Timer h/w sie verwenden.

kommentiert nanosleep() Fall:

Sie verwenden clock_gettime (zweimal). Um Ihnen eine ungefähre Vorstellung davon, was diese clock_gettime() wird letztlich erhalten abgebildet (im Kernel):

clock_gettime -> clock_get() -> posix_ktime_get_ts -> ktime_get_ts() -> timekeeping_get_ns() -> Uhr-> lesen()

Uhr-> lesen() liest grundsätzlich den Wert des Zählers von zugrunde liegenden Timer-Treiber und entsprechende h/w. Ein einfacher Unterschied zwischen dem gespeicherten Wert des Zählers in der Vergangenheit und dem aktuellen Zählerwert und dann der Umwandlung in Nanosekunden ergibt die verstrichenen Nanosekunden und aktualisiert die Zeitmessdatenstrukturen im Kernel.

Wenn Sie zum Beispiel einen HPET-Timer haben, der Ihnen einen 10-MHz-Takt gibt, wird der h/w-Zähler mit 100 ns Zeitintervall aktualisiert.

Lassen Sie uns sagen, auf dem ersten Takt-> read(), können Sie einen Zählerwert von X erhalten

Linux Zeitnehmung Datenstrukturen diesen Wert von X lesen, erhalten die Differenz ‚D'im Vergleich zu einigen alter gespeicherter Zählerwert. Führen Sie eine Gegen-Differenz 'D' zu Nanosekunden 'n' Umrechnungsmathematik aus, aktualisieren Sie die Datenstruktur um 'n' . Geben Sie diesen neuen Zeitwert für den Benutzerbereich ein.

Wenn der zweite Takt-> read() ausgegeben wird, liest er den Zähler erneut und aktualisiert die Zeit. Jetzt, für einen HPET-Timer, wird dieser Zähler alle 100ns aktualisiert und Sie werden sehen, dass dieser Unterschied dem Benutzer-Space gemeldet wird.

Jetzt ersetzen wir diesen HPET-Timer durch einen langsamen 32,768 KHz-Takt. Jetzt wird der Zähler von clock-> read() nur nach 30517 ns Sekunden aktualisiert, wenn Sie also zweitens clock_gettime() vor diesem Punkt aufrufen, erhalten Sie 0 (was die Mehrzahl der Fälle ist) und in einigen Fällen , Ihr zweiter Funktionsaufruf wird platziert, nachdem der Zähler um 1 erhöht wurde, dh 30517 ns sind abgelaufen. Daher der Wert von 30517 ns manchmal.

unkommentiert nanosleep() Fall: ist die clock_nanosleep Let trace() für monotone Uhren:

clock_nanosleep() -> Nsleep -> common_nsleep() -> hrtimer_nanosleep() -> do_nanosleep()

do_nanosleep() wird einfach die aktuelle Aufgabe in den Zustand INTERRUPTIBLE versetzen, wird auf den Ablauf des Timers warten (was 1 ns ist) und dann die aktuelle Aufgabe wieder in den Zustand RUNNING setzen. Sie sehen, es gibt viele Faktoren jetzt beteiligt, vor allem, wenn Ihr Kernel-Thread (und damit der User-Space-Prozess) erneut geplant wird. Abhängig von Ihrem Betriebssystem haben Sie immer eine gewisse Latenz, wenn Sie einen Kontextwechsel durchführen, und dies beobachten wir mit den Durchschnittswerten.

jetzt Ihre Fragen:

Was die minimale Auflösung bestimmt?

Ich denke, die Auflösung/Genauigkeit Ihres Systems hängt von der zugrunde liegenden Timer-Hardware ab (vorausgesetzt, Ihr Betriebssystem ist in der Lage, diese Genauigkeit für den Benutzerraumprozess bereitzustellen).

* Warum ist die Auflösung des Dev-Computers 30000X besser als die des Gumstix, wenn der Prozessor nur ~ 2,6-mal schneller läuft? *

Entschuldigung, ich habe dich hier vermisst. Wie ist es 30000x schneller? Für mich sieht es aus wie etwas 200x schneller (30714 ns/150 ns ~ 200X?). Aber wie auch immer, wie ich es verstehe, CPU-Geschwindigkeit kann oder darf nicht mit der Timer-Auflösung/Präzision zu tun haben. Also, diese Annahme kann in einigen Architekturen richtig sein (wenn Sie TSC H/W verwenden), kann jedoch in anderen fehlschlagen (mit HPET, PIT usw.).

Gibt es eine Möglichkeit zu ändern, wie oft CLOCK_MONOTONIC aktualisiert wird und wo? Im Kernel?

Sie können immer in den Kernel-Code für Details schauen (so habe ich es untersucht). Im Linux-Kernel-Code, suchen diese Quelldateien und Dokumentation:

  1. kernel/Posix-timers.c
  2. kernel/hrtimer.c
  3. Dokumentation/Timer/hrtimers.txt
Verwandte Themen