Nästa Föregående Innehållsförteckning

4. Högupplösande timing

4.1 Fördröjningar

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.

nanosleep()

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

Fördröjningar med port I/O

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 asmio.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.

Att fördröja med assemblerinstruktioner

Om man känner till processortyp och klockhastighet, kan man hårdkoda korta fördröjningar med vissa assembler­instruktioner (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.

rdtsc() för Pentium

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.

4.2 Att mäta tid

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 kod­fragmentet ovan noggrant till en klockcykel.

Om din process skall ha en signal efter en viss tid, så använd setitimer() eller alarm(). Se manualsidorna.


Nästa Föregående Innehållsförteckning