Linux I/O port programming mini-HOWTO: Mierzenie czasu z dużą dokładnością
Następna strona
Poprzednia strona
Spis treści
4. Mierzenie czasu z dużą dokładnością
4.1 Opóźnienia
Przede wszystkim trzeba stwierdzić że z powodu wielozadaniowej natury Linuxa
nie można zagwarantować że procesy w trybie użytkownika będą mieć dokładną
kontrolę czasu. Twój proces może zostać rozpoczęty w każdej chwili i może mu to
zająć od 10 milisekund do kilku sekund (na bardzo obciążonych systemach). Jednakże,
dla większości aplikacji używających portów I/O nie ma to większego znaczenia.
Aby zminimalizować to zjawisko możesz nadać swemu procesowi wyższy priorytet
(zobacz nice(2) w podręczniku man) lub używać zarządzania procesami w czasie rzeczywistym
(patrz niżej)
Jeśli chcesz berdziej precyzyjnie odmierzać czas niż pozwalają ci na to procesy
w trybie użytkownika, możesz skorzystać ze wsparcia dla procesów użytkownika
w czasie rzeczywistym. Jądra 2.x.x mają niewielkie wsparcie dla czasu rzeczywistego;
zobacz sched_setscheduler(2) w podręczniku man. Jest również specjalne jądro które ma
zaawansowane wsparcie dla czasu rzeczywistego. Zobacz
http://luz.cs.nmt.edu/~rtlinux
aby uzyskać więcej informacji na ten temat.
Opóźnianie za pomocą funkcji sleep() i usleep()
Zacznijmy teraz od łatwiejszych wywołań. Jeśli chcesz opóźnień rzędu sekund to
najpewniej jest użyć sleep(). Dla opóźnień rzędu przynajmniej dziesiątek milisekund
(10ms wydaje się być najmniejszym możliwym opóźnieniem) powinieneś użyć usleep().
Funkcje te oddają czas innym procesom a więc czas procesora nie jest marnowany. Zajrzyj
do sleep(3) i usleep(3) w podręczniku man po szczegóły dotyczące tych funkcji.
Jeśli potrzebujesz opóźnień mniejszych niż 50 milisekund (w zależności od prędkości
procesora i samego komputera oraz obciążenia systemu) oddawanie czasu procesora
zajmuje zbyt wiele czasu ponieważ linuxowy zarządca procesów (w architekturze x86)
zwykle zabiera około 10-30 milisekund zanim zwróci kontrolę do twojego procesu.
Z tego powodu dla małych opóźnień usleep() zwykle opóźnia o trochę więcej czasu niż
podajesz w parametrze a przynajmniej około 10 ms.
Opóźnianie za pomocą funkcji nanosleep()
W serii jąder 2.0.x znajduje się nowa funkcja systemowa nanosleep()
(zobacz nanosleep(2) w podręczniku man) która pozwala opóźniać lub zatrzymywać
proces na krótkie okresy czasu (kilka mikrosekund lub więcej).
Dla uzyskania opóźnień <=2ms, jeśli (i tylko wtedy) twój proces jest ustawiony na zarządzanie z wsparciem dla czasu rzeczywistego
(poprzez sched_setscheduler()), nanosleep() używa pętli opóźniającej; w przeciwnym razie zatrzymuje
proces tak jak usleep().
Pętla opóźniająca używa udelay() (wewnętrznej funkcji jądra użtwanej przez wiele jego modułów)
a długość pętli jest obliczana na podstawie wartości BogoMips
(prędkość tego rodzaju pętli opóźniającej jest jedną z rzeczy którą BogoMips dokładnie
mierzy) Zobacz /usr/include/asm/delay.h po szczegóły odnośnie działania tego mechanizmu.
Opóźnianie za pomocą operacji I/O na porcie
Inną metodą opóźniania o niewielkie ilości mikrosekund są operacje I/O na portach.
Czytanie/zapisywanie jakiegokolwiek bajtu z/do portu 0x80 (patrz wyżej jak to się robi)
powinno dać opóźnienie prawie dokładnie 1 mikrosekundy bez względu na typ procesora
i jego prędkość. Możesz tak robić wiele razy aby uzyskać opóźnienie rzędu kilku mikrosekund.
Sposób ten nie powinien wywoływać żadnych szkodliwych efektów ubocznych na żadnym standardowym
komputerze (nawet niektóre moduły jądra go używają). W ten sposób uzyskują opóźnienie funkcje
{in|out}[bw]_p() (zobacz asm/io.h).
W zasadzie, instrukcje I/O na większości portów w obszarze 0-0x3ff zbierają prawie
dokładnie 1 mikrosekundę, więc jeśli na przykład używasz bezpośrednio portu równoległego
po prostu wykonaj kilka razy inb() z tego portu aby uzyskać opóźnienie.
Opóźnianie za pomocą instrukcji asemblerowych
Jeśli znasz typ procesora i prędkośc zegara w komputerze na którym będzie działał
twój program, możesz na stałe uzyskać krótsze opóźnienia poprzez zastosowanie pewnych
rozkazów asemblerowych (pamiętaj jednak że twój proces może się zacząć w dowolnym momencie
wobec czego opóźnienia te mogą być większe od czasu do czasu) W tabeli poniżej wewnętrzna
prędkość procesora określa liczbę cykli np dla procesora 50MHz (np. 486DX-50 lub 486DX2-50)
jeden cykl zegara zajmuje 1/50000000 sekundy. (200 nanosekund).
Instrukcja cykle zegara na 386 cykle zegara na 486
nop 3 1
xchg %ax,%ax 3 3
or %ax,%ax 2 1
mov %ax,%ax 2 1
add %ax,0 2 1
(Przykro mi ale niewiele wiem o Pentium. Pewnie podobnie do 486. Nie mogę znaleźć żadnej
instrukcji która zajmowała by tylko jeden cykl zegara na 386. Jeśli możesz, używaj
instrukcji jednocyklowych, w przeciwnym razie architektura potokowa (pipelining)
używana w nowoczesnych procesorach może dać jeszcze krótsze czasy)
Rozkazy nop i xchg z tabeli powyżej nie powinny powodować żadnych skutków ubocznych. Reszta
może modyfikować rejestr znaczników ale nie powinno mieć to znaczenia bo gcc powinno
to wykryć. Użycie nop jest dobrym wyborem.
Aby skorzystać z powyższego, trzeba w swoim programie wywołać funkcję
asm("instrukcja") Składnia instrukcji jest taka sama jak w tabeli powyżej. Jeśli
chcesz użyć kilku instrukcji w jednym wywołaniu funkcji asm rozdziel je średnikami.
Na przykład asm("nop ; nop ; nop ; nop") wywoła cztery razy instrukcję nop opóźniając
nasz program o 4 cykle na 486 lub Pentium (lub 12 cykli na 386)
Funkcja asm() jest przez gcc tłumaczona na wstawkę w asemblerze a więc nie ma zagrożenia
przekroczenia limitu wywołań funkcji.
Opóźnienia krótsze niż jeden cykl zegara nie są możliwe w architekturze Intel i386.
Instrukcja rdtsc w Pentium
Jeśli masz Pentium, możesz odczytać ilość cykli zegara które upłynęły od ostatniego
uruchomienia komputera. Robi się to za pomocą takiego kodu w C:
extern __inline__ unsigned long long int rdtsc()
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
Możesz odczytywać tą wartość w celu opóźniania o dowolną ilość cykli.
4.2 Mierzenie czasu
Dla czasów rzędu sekundy prawdopodobnie najłatwiej będzie użyć funkcji time().
Dla bardziej dokładnych pomiarów: gettimeofday() jest dokładne co do mikrosekundy
(ale zobacz wyżej o zarządzaniu procesami) Dla Pentium fragment kodu rdtsc powyżej
jest dokładny co do cykla zegarowego.
Jeśli chcesz aby twój proces dostawał jakiś sygnał po jakimś czasie, użyj settimer()
lub alarm(). Zajrzyj do stron podręcznika man dotyczących tych funkcji.
Następna strona
Poprzednia strona
Spis treści
Wyszukiwarka
Podobne podstrony:
io programming pl 11io programming pl 1io programming pl 2IO Programming pl (3)IO Programming pl (2)io programming pl 10io programming pl 9io programming pl 3io programming pl 6io programming pl 8io programming pl 7IO Programming plio programming pl 5TK IO[pdf][PL] Pobrany z torrenty org ® nfo nfoCOMPACT IO PRESENTATION PLamd102 io pl09io port programming 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46a 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46aacu 250 io pl14więcej podobnych podstron