Först och främst måste sägas att det inte går att garantera user mode
processer exakt kontroll avseende timing eftersom Linux är ett
multiprocess system. Din process kan bli utskyfflad under vad som helst
mellan 10 millisekunder upp till några sekunder (om belastningen är hög).
Detta spelar emellertid ingen roll för flertalet program som använder
I/O-portar. För att reducera effekterna, kan du med hjälp av kommandot
nice ge din process hög prioritet. Se nice(2)
manualen eller använd
real-time scheduling enligt nedan.
Om du behöver bättre tidsprecision än vad normala user-mode processer
kan ge, så finns vissa förberedelser för 'user-mode real time' support.
Linux 2.x kärnor har 'soft real time support', se manualen för
sched_setscheduler(2)
. Det finns en speciell kärna som stöder hård
realtid, se
http://luz.cs.nmt.edu/~rtlinux/
för ytterligare information om detta.
sleep()
och usleep()
Låt oss börja med de lätta funktionsanropen. För att fördröja flera
sekunder, är det troligtvis bäst att använda sleep()
. Fördröjningar på
10-tals millisekunder (ca 10 ms verkar vara minimum) görs med usleep()
.
Dessa funktioner frigör CPU för andra processer, så att ingen
CPU-tid går förlorad. Se manualerna sleep(3)
och usleep(3)
.
Om fördröjningar är på mindre än 50 ms ( beror på din processor och dess
belastning), tar det onödigt mycket tid att släppa CPUn, därför att det för
Linux scheduler (för x86 arkitekturen) vanligtvis tar minst 10-30
millisekunder innan den återger din process kontrollen. Beroende på
detta fördröjer usleep(3)
något mer än vad du specificerar i dina
parametrar, och alltid minst ca 10 ms.
I 2.0.x serien av Linuxkärnor finns ett nytt systemanrop: nanosleep()
(se nanosleep(2)
manualen), som möjliggör så korta fördröjningar som
ett par mikrosekunder eller mer.
Vid fördröjningar på mindre än 2 ms, om (och endast om) din process är
satt till soft real time scheduling (med sched_setscheduler()
),
använder nanosleep()
en vänteloop, i annat fall frigörs CPU på
samma sätt som med usleep()
.
Vänteloopen använder udelay()
(en intern kernelfunktion som används
av många 'kernel drivers'), och loopens längd beräknas med hjälp av
BogoMips värdet (det är bara denna sorts hastighet BogoMips värdet
mäter noggrant).
Se hur det fungerar i /usr/include/asm/delay.h
Ett annat sätt att fördröja ett fåtal mikrosekunder är att använda port I/O.
Läsning eller skrivning på port 0x80 (se ovan hur man gör) tar nästan precis
1 mikrosekund oberoende av processortyp och hastighet. Du kan göra det
upprepade gånger om du vill vänta ett antal mikrosekunder. Skrivning på
denna port torde inte ha några skadliga sidoeffekter på någon standardmaskin
och vissa 'kerneldrivers' använder denna metod.
det är på detta sättet {in|out}[bw]_p()
normalt gör sin fördröjning.
(se asm
io.h)/.
Flertalet port I/O instruktioner i adressområdet 0-0x3ff tar nästan exakt 1
mikrosekund, så om du t.ex. använder parallellporten direkt, gör bara några
extra inb()
från porten för att skapa fördröjning.
Om man känner till processortyp och klockhastighet, kan man hårdkoda korta fördröjningar med vissa assemblerinstruktioner (men kom ihåg, processen kan skyfflas ut när som helst, så fördröjningarna kan ibland bli längre). Tabellen nedan ger några exempel. För en 50MHz processor tar en klockcykel 20 ns.
Instruktion i386 klock cykler i486 klock cykler
nop 3 1
xchg %ax,%ax 3 3
or %ax,%ax 2 1
mov %ax,%ax 2 1
add %ax,0 2 1
tyvärr känner jag inte till Pentium; förmodligen nära i486. Jag hittar ingen instruktion som tar EN klockcykel i i386. Använd en-cykel instruktioner om du kan, annars kanske pipelinen i moderna processortyper förkortar tiden.
Instruktionerna nop
och xchg
i tabellen bör inte ha några
sidoeffekter. Övriga modifierar statusregistret, men det bör inte betyda
något eftersom gcc detekterar detta. nop
är ett bra val.
Om du vill använda dem, skriv call asm("instruktion")
i ditt program.
Syntaxen ge i tabellen ovan. Vill du göra multipla instruktioner i en
asm()
-sats, så separera med semikolon. Till exempel exekveras i satsen
asm(" nop; nop; nop; nop")
fyra nop
instruktioner,
som fördröjer fyra klockcykler med i486 eller pentium (eller 12 cykler
med i386).
asm()
översätts av gcc till inline assembler kod, så det blir inget
overhead med funktionsanrop.
Kortare fördröjningar än en klockcykel är inte möjligt med x86 arkitekturen.
Med Pentium kan du erhålla antalet klockcykler som gått sedan senaste uppstart med hjälp av följande C kod:
extern __inline__ unsigned long long int rdtsc()
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
Du kan polla värdet för hur många cykler som helst.
För att mäta tider med en sekunds upplösning, är det nog enklast att
använda time()
. Krävs bättre noggrannhet, ger gettimeofday()
cirka en mikrosekunds upplösning (men se ovan angående 'scheduling',
utskyffling). För Pentium är rdtsc
kodfragmentet ovan noggrant
till en klockcykel.
Om din process skall ha en signal efter en viss tid, så använd
setitimer()
eller alarm()
. Se manualsidorna.