2013-08-20 32 views
10

Muszę wykonać dokładny pomiar czasu do poziomu 1 nas, aby zmienić godzinę w cyklu pracy fali pwm.Precyzyjne czasy linuksowe - co decyduje o rozdzielczości clock_gettime()?

Tło

mi stosując Gumstix nad wodą COM (https://www.gumstix.com/store/app.php/products/265/), który ma jeden rdzeń ARM procesor Cortex-A8 pracującego z 499.92 bogomipsy (strona Gumstix podnosi się do 1 GHz z 800MHz zalecane) według/proc/cpuinfo. System operacyjny jest wersją systemu Angstrom Image systemu Linux opartą na jądrze w wersji 2.6.34 i jest dostępny na platformie Gumstix Water COM.

Problem

Zrobiłem sporo czytania o dokładnej synchronizacji w systemie Linux (i próbowałem większość z nich) i konsensus wydaje się, że za pomocą clock_gettime() i przedstawieniu CLOCK_MONOTONIC jest najlepszy sposób na zrobienie tego. (Chciałbym użyć rejestru RDTSC do synchronizacji, ponieważ mam jeden rdzeń z minimalnymi możliwościami oszczędzania energii, ale to nie jest procesor Intela.) A więc tutaj jest część nieparzysta, podczas gdy clock_getres() zwraca 1, sugerując rozdzielczość przy 1 ns , rzeczywiste testy czasowe sugerują minimalną rozdzielczość 30517ns lub (nie może to być zbieg okoliczności) dokładnie czas między tikąpami zegara 32,768 kHz. Oto co mam na myśli:

// 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); 
} 

(Nie przejmować podwójnego odlewu, tv_sec w time_t i tv_nsec jest długa.)

skompilować z:

gcc soExample.c -o runSOExample -lrt 

biegać z:

./runSOExample 

Po skomentowaniu spacji, jak pokazano, wynikiem jest 0ns lub 30517ns, z których większość to 0ns. Prowadzi to mnie do przekonania, że ​​CLOCK_MONOTONIC jest aktualizowana na 32,768 kHz, a większość czasu zegar nie został zaktualizowany przed drugim wywołaniem clock_gettime(), a w przypadkach, gdy wynik jest 30517ns, zegar został zaktualizowany między połączeniami.

Kiedy robię to samo na moim komputerze programistycznym (AMD FX (tm) -6100 sześciordzeniowy procesor pracujący z częstotliwością 1,4 GHz) minimalne opóźnienie jest bardziej stałe 149-151ns bez zer.

Więc porównajmy te wyniki z szybkościami procesora. W przypadku programu Gumstix ten 30517ns (32,768 kHz) oznacza 15298 cykli procesora 499,93 MHz. Dla mojego komputera dev, 150ns odpowiada 210 cyklom procesora 1,4 GHz.

Z clock_nanosleep (wywołania) Odkomentowano średnie wyniki są następujące: Gumstix: Średnia wartość = 213623, a wynik różni się w górę i w dół, o wielokrotność tej rozdzielczości minutowym 30517ns Dev Komputer: 57710-68065 NS z brak wyraźnego trendu. W przypadku komputera dev oczekuję, że rozdzielczość rzeczywiście będzie na poziomie 1 ns, a zmierzone ~ 150ns naprawdę jest czasem, jaki upłynął między dwoma wywołaniami clock_gettime().

Moje pytania są następujące: Co decyduje o minimalnej rozdzielczości? Dlaczego rozdzielczość komputera dev 30000X jest lepsza niż Gumstix, gdy procesor działa tylko ~ 2,6X szybciej? Czy istnieje sposób na zmianę częstotliwości aktualizacji i miejsca CLOCK_MONOTONIC? W jądrze?

Dzięki! Jeśli potrzebujesz więcej informacji lub wyjaśnienia, zapytaj.

+1

Zastanawiam się. Czy jakiś sprzęt nie byłby potrzebny do przechwytywania cykli z zegara procesora? Przynajmniej na tym poziomie precyzji. Być może Gumstix tego nie ma? (Mówię tylko o rejestrze, który może liczyć kleszcze i przytrzymać je przed przetoczeniem). – Jiminion

+0

Po przeczytaniu, które już napisałeś, być może już widziałeś *** [ten link] (http: // gallinazo.flightgear.org/technology/gumstix-overo-rc-servos-and-pwm-signal-generation/)***, ale na wszelki wypadek publikuj. Niektóre materiały są istotne. – ryyker

+0

Mając dokładnie ten sam problem. Wiele wielokrotności 30517ns. Używam Overo Tide. Czy kiedykolwiek rozwiązałeś ten problem? – Ryan

Odpowiedz

1

Nie mam gumki na rękę, ale wygląda na to, że twój zegar jest wolny. run:

$ dmesg | grep clocksource

Jeśli wrócisz

[ 0.560455] Switching to clocksource 32k_counter

To może wyjaśniać, dlaczego twój zegar jest tak powolny.

W najnowszych jądrach znajduje się katalog /sys/devices/system/clocksource/clocksource0 z dwoma plikami: available_clocksource i current_clocksource. Jeśli posiadasz ten katalog, spróbuj przejść do innego źródła, podając jego nazwę do drugiego pliku.

7

Rozumiem, że różnica między dwoma środowiskami (Gumstix i komputer Dev) może zależeć od czasu, z którego korzystają.

skomentowane nanosleep() sprawa:

Używasz clock_gettime() dwukrotnie. Aby dać pewne pojęcie o tym, co to clock_gettime() będzie ostatecznie dostać mapowane do (w jądrze):

clock_gettime -> clock_get() -> -> ktime_get_ts posix_ktime_get_ts() -> timekeeping_get_ns() -> zegar-> odczyt()

zegar-> odczyt() zasadniczo odczytuje wartość licznika dostarczoną przez sterownik zegara i odpowiadającą jej godzinę. Prosta różnica z zapamiętaną wartością licznika w przeszłości i aktualnej wartości licznika, a następnie matematyką konwersji nanosekundy, pozwoli uzyskać nanosekundy i zaktualizuje struktur danych w kernelu.

Na przykład, jeśli masz zegar HPET, który podaje zegar 10 MHz, licznik h/w zostanie zaktualizowany o 100 ns.

Powiedzmy, na pierwszym Zegarmistrzostwo> read(), pojawi się wartość licznika X.

Linux Czas utrzymywania struktur danych będzie czytać tę wartość X, uzyskać różnicę „D'porównaniu do niektórych stara zapamiętana wartość licznika. Zrób trochę matematyki konwersji "D" do nanosekund "n", zaktualizuj strukturę danych o "n" Wydaj tę nową wartość czasu do przestrzeni użytkownika.

Po wydaniu drugiego zegara-> read(), ponownie odczyta licznik i zaktualizuje czas. Teraz, dla licznika HPET, licznik jest aktualizowany co 100ns, a zatem różnica ta jest zgłaszana do przestrzeni użytkownika.

Teraz, Zamień ten zegar HPET na wolny zegar 32.768 KHz. Teraz licznik clock-> read() zostanie zaktualizowany dopiero po 30517 ns sekundach, więc jeśli drugie wywołanie funkcji clock_gettime() nastąpi przed tym okresem, otrzymasz 0 (co stanowi większość przypadków), aw niektórych przypadkach Twoje drugie wywołanie funkcji zostanie umieszczone po zwiększeniu o 1, czyli 30517 ns. Stąd czasami wartość 30517 ns.

odkomentowane nanosleep() sprawa: Prześledźmy clock_nanosleep() przez monotonicznych zegarów:

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

Do_nanosleep() po prostu położy bieżące zadanie w stanie PRZERWANIA, zaczeka na wygaśnięcie timera (czyli 1 ns), a następnie ponownie ustawia bieżące zadanie w stanie RUNNING. Widzisz, teraz jest wiele czynników, głównie wtedy, gdy wątek jądra (a tym samym proces przestrzeni użytkownika) zostanie ponownie zaplanowany. W zależności od systemu operacyjnego zawsze będziesz musiał zmierzyć się z pewnym opóźnieniem, gdy dokonasz przełącznika kontekstu i to właśnie obserwujemy przy średnich wartościach.

Teraz Twoje pytania:

Co decyduje, że minimalny rozmiar?

Myślę, że rozdzielczość/precyzja twojego systemu będzie zależeć od używanego sprzętu czasowego (zakładając, że twój system operacyjny jest w stanie zapewnić tę dokładność procesowi przestrzeni użytkownika).

* Dlaczego rozdzielczość komputera dev 30000X lepiej niż Gumstix gdy procesor pracuje tylko ~ 2.6x szybciej? *

Przepraszam, tęskniłem za tobą tutaj. Jak to jest 30000x szybciej? Dla mnie wygląda to na coś 200x szybszego (30714 ns/150 ns ~ 200X?). Ale tak, jak rozumiem, szybkość procesora może, ale nie musi, być związana z rozdzielczością/precyzją zegara. Tak więc to założenie może mieć rację w niektórych architekturach (gdy korzystasz z TSC H/W), może jednak zawieść w innych (używając HPET, PIT itp.).

Czy istnieje sposób na zmianę częstotliwości aktualizacji i miejsca CLOCK_MONOTONIC? W jądrze?

zawsze można zajrzeć do kodu jądra po szczegóły (tak to wyglądałem). w Linuksie kodu jądra, poszukaj tych plików źródłowych i dokumentacja:

  1. kernel/POSIX-timers.c
  2. kernel/hrtimer.c
  3. Documentation/timery/hrtimers.txt