PDA

View Full Version : [c]Temporizzazione ad elevata precisione in linux


mastoo
11-08-2007, 16:18
ho scritto questo programma che dovrebbe eseguire eseguire delle istruzioni di output
temporizzate in modo preciso.

#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>


int main(void)
{
int i;
struct timeval now;
struct timeval after;


if (ioperm(0x80,1,1) )
{
perror("ioperm(0x80)");
exit(1);
}
if (ioperm(0x378,1,1) )
{
perror("ioperm(0x378) ");
exit(1);
}

while(1)
{
gettimeofday(&now, NULL);

outb(1,0x378);
usleep (1500);//primo sleep
outb(0,0x378);
usleep (30000);

gettimeofday(&after, NULL);
printf("%d\n",after.tv_usec-now.tv_usec);
}

return 0;

}

ma la temporizzazione non è delle migliori infatti l'output è

36218
31586
31864
34117
31589
31577
31749
31569
31572
31586
32310
37199
34089
31578
31574
31571
33305
32306
31591
31570
33859
33916


che non è di 31500 usec.
come posso aumentare la precisione soprattutto del primo usleep, l'altro ha poca importanza ma il primo deve essere preciso al massimo.
ho provato anche a eseguire tante istruzioni inb(80) ma il risultato è peggiore.
grazie per le risposte

cionci
11-08-2007, 16:51
Credo che il modo migliore sia un'attesa attiva controllando continuamente il tempo, magari intervallata da delle usleep molto piccole per non impallare il sistema (tipo 20 microsecondi).

71104
11-08-2007, 17:43
uhm, comunque faccia il multitasking del sistema operativo ci sarà sempre; interessante notare come la maggior parte dei valori sia poco maggiore di 31500, tranne ogni tanto che salta un po' di più; in quei punti evidentemente ci sono dei process switch verso processi che tardano a rilasciare la CPU perché hanno da lavorare. credo che la soluzione migliore sia alzare al massimo la priorità del processo (non so come si faccia su Linux), e poi casomai riabbassarla dopo il primo intervallo, che hai detto che è il più importante.

ilsensine
11-08-2007, 22:34
che non è di 31500 usec.
Come puoi pretendere una granularità di 500us se lo scheduler viaggia a 1khz?

Il tuo usleep(1500) è decisamente critico, non puoi ottenerlo così. La tecnica da adottare dipende da quanta tolleranza puoi permetterti sui 1500us.

ilsensine
11-08-2007, 22:36
e già che ci sei, puoi sostituire l'accesso diretto alle porte di i/o (outb, ecc.) con il driver ppdev.

eliano
11-08-2007, 22:45
googlando ho trovato una pagina con spunti interessanti:
http://www.pluto.it/files/ildp/HOWTO/IO-Port-Programming/IO-Port-Programming-4.html

71104
12-08-2007, 00:46
e già che ci sei, puoi sostituire l'accesso diretto alle porte di i/o (outb, ecc.) con il driver ppdev. mi stavo giusto chiedendo come fosse possibile che quella cosa funzionasse... Linux permette ai programmi user-mode di accedere direttamente alle porte di I/O?

mastoo
12-08-2007, 10:27
Come puoi pretendere una granularità di 500us se lo scheduler viaggia a 1khz?

Il tuo usleep(1500) è decisamente critico, non puoi ottenerlo così. La tecnica da adottare dipende da quanta tolleranza puoi permetterti sui 1500us.

la tolleranza è di +o- 12 us.
ho letto sulla guida di eliano di impostare il processo in soft realtime con sched_setscheduler() per utilizzare nanosleep
ma non so come fare.

cionci
12-08-2007, 10:35
12 microsecondi è davvero poco...imho ti servirebbe un kernel realtime.
L'unico modo per farlo imho è un'attesa attiva in quel secondo e mezzo...

do
{
gettimeofday(&after, NULL);
}
while(after.tv_usec - now.tv_usec < 1488);

Sfruttiamo la tolleranza per anticiparci un po' ;)
E' comunque importante, come ti hanno detto, impostare l'applicazione ad una priorità molto alta.

cionci
12-08-2007, 10:45
Se ti serve:
#include <sched.h>
int set_real-time_priority(void)
{
struct sched_param schp;
/*
* set the process to real-time privs
*/
memset(&schp, 0, sizeof(schp));
schp.sched_priority = sched_get_priority_max(SCHED_FIFO);

if (sched_setscheduler(0, SCHED_FIFO, &schp) != 0) {
perror("sched_setscheduler");
return -1;
}

return 0;

}
Nota...stai attento che mentre testi l'applicazione sarebbe bene non abilitare questa cosa oppure abilitarla e disabilitarla solo quando serve, perché se sbagli qualcosa ti si pianta il sistema.

mastoo
12-08-2007, 10:49
avevo gia provato cosi ma gli effetti sono gli stessi
pero non ho provato ad aumentare la priorita perche non so come si fa.
il fatto è per quello che sto facendo e importante che non ci siano impulsi di durata parecchio elevata tipo 1800 .
e poi che almeno la media sia di 1500 perche gli impulsi sono sempre piu grandi di 1500 e mai piu piccoli

mastoo
12-08-2007, 11:05
aumentando la priorita va leggermente meglio
21538
22604
21538
21815
21556
21537
-978447
21546
21537
21834
22585
21523
22257
22056
21541
22360
21532
21516
22425
21555
21523
23637
21538
21519
21574
22909
21817
21526
21537
23236
21549
21535
ma ancora non basta.
cos' è il kernel realtime?

vegeta83ssj
12-08-2007, 11:08
cos' è il kernel realtime?

https://www.rtai.org/
Me l'hanno fatto usare all'università per dei progetti di Controlli Automatici! ;)

Ciauz

cionci
12-08-2007, 11:22
aumentando la priorita va leggermente meglio
Ci fai vedere lo stato del codice con questa ultima versione ?

ilsensine
12-08-2007, 11:24
la tolleranza è di +o- 12 us.
ho letto sulla guida di eliano di impostare il processo in soft realtime con sched_setscheduler() per utilizzare nanosleep
ma non so come fare.
Un kernel realtime può aiutare, come può aiutare usare il kernel ufficiale con scheduler SCHED_FIFO. Il tuo problema però è anche un altro, ovvero come ottenere uno sleep di 1500us. La usleep non aiuta (come non aiuta la nanosleep, che tra l'altro è utilizzata internamente dalla usleep), in quanto è legata al tick dello scheduler; per generare l'intervallo in maniera precisa mi vengono in mente due strade:
- busy loop per 1500 us: è la soluzione più semplice (e forse la più efficace, in congiunzione con SCHED_FIFO). La misurazione dell'intervallo dipende però dalla velocità del processore.
- utilizzo del driver rtc: unitamente a un kernel realtime e allo scheduler realtime, potresti avvicinarti a tolleranze della decina di us.
Come vedi, non è un problema banale.

cionci
12-08-2007, 11:26
Ci sarebbe anche il kernel low-latency che magari potrebbe sarti una mano...dipende da che distro hai, ad esempio su ubuntu si può installare da apt.

cionci
12-08-2007, 11:28
IMHO ci sono anche altri problemi di misura...hai detto che l'importante sono i primi 1500 us, ma te stai misurando i 1500 us + i 20K us...è chiaro che le approssimazioni aumentano.
Fai quindi una misura separata dei due intervalli.

mastoo
12-08-2007, 11:37
Un kernel realtime può aiutare, come può aiutare usare il kernel ufficiale con scheduler SCHED_FIFO. Il tuo problema però è anche un altro, ovvero come ottenere uno sleep di 1500us. La usleep non aiuta (come non aiuta la nanosleep, che tra l'altro è utilizzata internamente dalla usleep), in quanto è legata al tick dello scheduler; per generare l'intervallo in maniera precisa mi vengono in mente due strade:
- busy loop per 1500 us: è la soluzione più semplice (e forse la più efficace, in congiunzione con SCHED_FIFO). La misurazione dell'intervallo dipende però dalla velocità del processore.
- utilizzo del driver rtc: unitamente a un kernel realtime e allo scheduler realtime, potresti avvicinarti a tolleranze della decina di us.
Come vedi, non è un problema banale.

per busy loop tu intendi un ciclo di instruzioni inutili tipo nop che perdono tempo?
ma in questo caso lo scheduler non puo interrompere comunque il processo lo stesso .
io avevo provato con tanti inb(0x80) che avevano una durata fissa di 2 us devo riprovare con il processo ad alta priorità

mastoo
12-08-2007, 11:50
il codice è questo

#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>
#include <string.h>

#include <sched.h>
int set_real_time_priority(void);


int main(void)
{
int i;
struct timeval now;
struct timeval after;


if (ioperm(0x80,1,1) )
{
perror("ioperm(0x80)");
exit(1);
}
if (ioperm(0x378,1,1) )
{
perror("ioperm(0x378) ");
exit(1);
}
set_real_time_priority();


for(i=0;i<50;i++)
{
gettimeofday(&now, NULL);
outb(1,0x378);
usleep(1480);
gettimeofday(&after, NULL);
printf("%d\n",after.tv_usec-now.tv_usec);
outb(0,0x378);
usleep (20000);

}


return 0;

}




int set_real_time_priority(void)
{
struct sched_param schp;
/*
* set the process to real-time privs
*/
memset(&schp, 0, sizeof(schp));
schp.sched_priority = sched_get_priority_max(SCHED_FIFO);

if (sched_setscheduler(0, SCHED_FIFO, &schp) != 0) {
perror("sched_setscheduler");
return -1;
}

return 0;

}

l'output questo
1537
1525
1511
1511
1521
1511
1512
1510
1525
1522
1511
1512
1511
1510
1534
1510
1513
1511
1522
1521
1509
1525
1529
1511
1528
1509
1518
1510
1524
1528
1509
1511
1510
1524
1521
1614
1510
1511
1523
1532
1510
1520
1529
1511
1526
1511
1524
1527
1524
1521
prima vi avevo dato l'output sbagliato .
ora ci siamo quasi se con questo busy loop si puo miglorare ancora bene
se no mi accontento.

cionci
12-08-2007, 11:51
Già meglio ;)

cionci
12-08-2007, 11:53
Prova con l'attesa attiva che ti avevo scritto sopra al posto della sleep...

cionci
12-08-2007, 12:00
Fai così:

gettimeofday(&now, NULL);
outb(1,0x378);
usleep(1480); /* da sostituire con l'attesa attiva */
outb(0,0x378);
gettimeofday(&after, NULL);
printf("%d\n",after.tv_usec-now.tv_usec);
usleep (20000);

Nota inoltre che le misurazioni ti converrebbe farle con un oscilloscopio e non tramite software...questo per un semplice motivo: su tempi così piccoli anche il tempo di esecuzione della gettimeofday influisce abbastanza...

mastoo
12-08-2007, 12:20
Prova con l'attesa attiva che ti avevo scritto sopra al posto della sleep...
grazie cionci
ecco un tempo spaccato al microsecondo
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>
#include <string.h>

#include <sched.h>
int set_real_time_priority(void);


int main(void)
{
int i;
struct timeval now;
struct timeval after;


if (ioperm(0x80,1,1) )
{
perror("ioperm(0x80)");
exit(1);
}
if (ioperm(0x378,1,1) )
{
perror("ioperm(0x378) ");
exit(1);
}
set_real_time_priority();


for(i=0;i<100;i++)
{
gettimeofday(&now, NULL);
outb(1,0x378);
//usleep(1500);
do
{
gettimeofday(&after, NULL);
}
while(after.tv_usec - now.tv_usec < 1500);
gettimeofday(&after, NULL);
printf("%d\n",after.tv_usec-now.tv_usec);
outb(0,0x378);
usleep (20000);

}


return 0;

}




int set_real_time_priority(void)
{
struct sched_param schp;
/*
* set the process to real-time privs
*/
memset(&schp, 0, sizeof(schp));
schp.sched_priority = sched_get_priority_max(SCHED_FIFO);

if (sched_setscheduler(0, SCHED_FIFO, &schp) != 0) {
perror("sched_setscheduler");
return -1;
}

return 0;

}
guardate che precisione
piu di cosi si non si puo

1500
1501
1500
1500
1500
1500
1501
1501
1500
1500
1500
1501
1500
1500
1501
1500
1501
1500
1501
1500
1500
1501
1500
1500
1500
1501
1501
1501
1500
1500
1500
1500
1500
1500
1501
1500
1501
1501
1500
1500
1500
1501
1500
1500
1501
1501
1500
1500
1501
1500
1500
1500
1500
1501
1501
1501
1500
1501
1500
1500
1500
1501
1500
1500
1500
1501
1500
1500
1501
1500
1500
1500
1501
1500
1500
1500
1501
1500
1500
1501
1501
1501
1500
1501
1500
1500
1500
1500
1500
1501
1500
1500
1501
1500
1500
1501
1501
1501
1500
1500

prima in questo modo senza mettere la massima priorita non funzionava cosi preciso
forse perche lo scheduler interrompeva quando chiamavo gettimeofday

grazie a tutti

cionci
12-08-2007, 12:35
prima in questo modo senza mettere la massima priorita non funzionava cosi preciso
forse perche lo scheduler interrompeva quando chiamavo gettimeofday

Il processo veniva interrotto per la fine del timeslice o per permettere l'esecuzione di processi a maggiore priorità.
Comunque metti l'ultima getimeofday e la printf dopo la outb...e riprova.
Io sostituirei 1490 a 1500 nell'attesa attiva. Comunque prova e vediamo cosa ottieni.

mastoo
12-08-2007, 12:59
Il processo veniva interrotto per la fine del timeslice o per permettere l'esecuzione di processi a maggiore priorità.
Comunque metti l'ultima getimeofday e la printf dopo la outb...e riprova.
Io sostituirei 1490 a 1500 nell'attesa attiva. Comunque prova e vediamo cosa ottieni.
si ho provato ma senza la printf si blocca tutto come avevi detto tu prima,
probabilmente la printf accedendo al sistema operativo attiva lo scheduler e non si blocca.
pero la print f io la devo togliere cosi come getimeofday perche altrimenti il secondo ritardo non e piu di 20 us.

perche devo mettere 1490?

cionci
12-08-2007, 13:55
La gettimeofday che dovevi spostare era questa:

gettimeofday(&now, NULL);
outb(1,0x378);
do
{
gettimeofday(&after, NULL);
}
while(after.tv_usec - now.tv_usec < 1500);
outb(0,0x378);
gettimeofday(&after, NULL);
printf("%d\n",after.tv_usec-now.tv_usec);
usleep (20000);

Metti 1500 e 1490 e prova il codice.
Per la seconda usleep puoi comunque sfruttare un meccanismo simile alla prima attesa, ma inserendo nel mezzo al ciclo anche una usleep molto corta...ad esempio 10us.

eliano
12-08-2007, 23:33
Potresti, per cercare di aggirare il blocco, utilizzare una usleep leggermente più breve di 1500 e riempire il tempo restante con un busy loop: questo dovrebbe concedere al SO il tempo per le sue operazioni e permettere a te di avvicinarti alla precisione necessaria.

ilsensine
12-08-2007, 23:48
per busy loop tu intendi un ciclo di instruzioni inutili tipo nop che perdono tempo?
ma in questo caso lo scheduler non puo interrompere comunque il processo lo stesso .

Non se usi SCHED_FIFO, come hai potuto constatare.

Anche una soluzione ibrida come suggerito da eliano può funzionare, ma è più facile sforare se non usi un kernel RT.
Perché non dai una occhiata anche a /dev/rtc? E' fatto apposta per queste cose...

mastoo
13-08-2007, 11:01
va be con il loop funziona in modo preciso , quindi per adesso va bene.
ho risolto anche il fatto che il programma si bloccava in alcuni casi,perche era dovuto
al calcolo del tempo passato

gettimeofday(&now, NULL);
outb(1,0x378);
do
{
gettimeofday(&after, NULL);
}
while(after.tv_usec - now.tv_usec < 1500);
...

in questo modo se il ciclo si trovava a cavallo di un secondo non terminava mai.
ho risolto cosi

gettimeofday(&now, NULL);
outb(pin,0x378);

do
{
gettimeofday(&after, NULL);
}
while(((after.tv_sec - now.tv_sec)*1000000+(after.tv_usec - now.tv_usec)) <1500);


devo provare /dev/rtc ho trovato una piccola guida.

cionci
13-08-2007, 11:10
Come ti è stato suggerito sopra, potresti effettuare una sleep di 500 us (o 1000) prima di fare l'attesa attiva, in questo modo rendi accessibile il processore prima che il tuo processo si risvegli.

Per la seconda attesa puoi operare allo stesso modo: fai una usleep di 19000 us (o 19500) e poi fai l'attesa attiva fino a 20000.