Newsgroup: it.comp.os.linux.development
Titolo: Ogni 10 secondi un'operazione

Devo eseguire ogni 10 secondi una determinata operazione, non molto 
lunga (si tratta di aggiungere in un file di log un dato che chiedo 
sulla porta seriale).
La cadenza di 10 secondi deve essere di questo tipo:
   00:00:00
   00:00:10
   00:00:20
   ...
e non
   00:00:03
   00:00:13
   00:00:23
   ...
Questo indipendentemente dall'istante in cui il programma viene lanciato 
(cioè se viene messo in esecuzione alle 00:00:03, la prima operazione 
verrà effettuata alle 00:00:10).
Siccome l'intervallo tra un'operazione e l'altro è dell'ordine dei 
secondi, non posso usare cron che, da quello che ho capito, gestisce 
risoluzioni fino al minuto e non meno.
Non posso neanche utilizzare delle pause di 10 secondi tra un'operazione 
e l'altra perchè, dopo un po' di tempo (l'applicazione dovrà funzionare 
ininterrottamente per mesi/anni), ci sarà un disallineamento tra gli 
istanti teorici e quelli reali. In altre parole potrei avere qualcosa di 
questo tipo:
   00:00:00
   00:00:10
   ...
   06:59:50
   07:00:01
   07:00:11
   ...
Devo, quindi, per forza legarmi alla data corrente del sistema (tenuta 
sincronizzata con un orario di riferimento preciso in altro modo).
A questo punto mi vengono in mente due strade.
All'esecuzione richiedo il tempo attuale mediante la funzione time() che 
mi ritorna un numero che rappresenta il numero dei secondi da una data 
di riferimento remota. Tolgo il resto della divisione per 10 e sommo 10 
per ottenere il successivo istante in cui devo fare l'operazione. 
Attendo fino quando il valore di time() sia maggiore di questo istante 
atteso.
---
   t = time ();
   t = 10 * (t / 10);
   while (time () < t)
     sleep (FD_SETSIZE, NULL, NULL, NULL, tv);
---
Per evitare di impegnare continuamente la CPU con la funzione time(), 
mediante la sleep() mi metto in attesa per mezzo secondo.
Cosa succede, però, se si cambia la data di sistema mettendola un anno 
prima, per esempio? Il programma rimane bloccato per un anno ad 
aspettare quello che lui crede essere l'istante successivo dell'operazione.
Per risolvere questo problema dovrei ricordarmi la data precedente e, se 
troppo diversa, ricominciare...
Un altro sistema è quello di leggere continuamente l'istante time() fin 
quando il suo valore è divisibile per 10.
---
   while (time() % 10)
     ;
   <operazione>
---
  Se l'operazione è troppo breve, rischio di eseguirla più volte per lo 
stesso secondo, quindi:
---
   old_t = 0;
   while (((t = time()) % 10) && (10 * (t / 10) != old_t))
     ;
   t = 10 * (t / 10);
   old_t = t;
   <operazione>
---
Questo procedimento funzionerebbe tranquillamente anche se la data di 
sistema viene cambiata ad un valore molto diverso da quello attuale.
Ma che succede se la CPU è impegnata durante il secondo divisibile per 
10? Non rischio di saltare un'operazione in questo caso? Nel primo 
metodo ciò non succedeva perchè facevo un test di disuguaglianza e non 
di uguaglianza come in questo caso.
Inoltre mi secca dover fare quel ciclo while() di attesa che impegna 
risorse della CPU (con la funzione time() e qualche calcolo) 
semplicemente per aspettare un dato istante noto.
Vi vengono in mente altre soluzioni?

 Vi vengono in mente altre soluzioni?
taglio un po' tutto.
Innanzitutto, ciclare con una sleep a mezzo secondo (o anche ad un
secondo, se non hai bisogno di grossa precisione) va bene.
Cambierei la condizione di innesco, però.
I secondi 10,20,30, ecc. sono nient'altro che (time()%10)==0
Quindi farei un ciclo del tipo
while (true) {
   struct timeval tv;
   tv.tv_sec = 0;
   tv.tv_usec = 500000; //500 ms
   select (FD_SETSIZE,NULL,NULL,NULL,&tv);
   if (time()%10!=0) {
      //Non ci interessa, aspettiamo altri 500ms
      continue;
   }
   //Qui eseguiamo l'operazione, invocare break per uscire
   <operazione>
   //Dobbiamo essere sicuri di non eseguire
   //due volte nello stesso secondo, quindi
   //aspettiamo 2 secondi (ne avremmo altri 8)
   sleep(2);
}
Alternativamente al break per uscire dal ciclo while, puoi definire una
variabile intera (fgKill ad esempio) impostata a 0 all'inizio che viene
messa a 1 quando vuoi che l'esecuzione sia l'ultima.
Il ciclo while diventa quindi
while (fgKill==0) {
.....
}
e un po' più pulito.
PSK

 Vi vengono in mente altre soluzioni?
Puoi provare ad usare /dev/rtc.  La documentazione nel file rtc.txt 
(nella directory Documentation del kernel) è scarna, ma IMO puoi fare 
quello che ti serve.
Ciao
Giovanni
-- 
   A computer is like an air conditioner,
   it stops working when you open Windows.
   Registered Linux user #337974  < 
http://giovanni.homelinux.net/
 >

 }
Tu hai risolto il problema di non eseguire più volte la stessa
operazione
per lo stesso secondo con la sleep(2), io l'avevo risolto con old_t.
Effettivamente con il tuo metodo evito una variabile e metto in pausa
il
processo liberando la CPU.
Ma direi che è meglio usare la select(), piuttosto che la sleep.
Il problema di questo metodo, come dicevo nell'OP, è che, se la CPU
è particolarmente impegnata proprio durante il secondo "buono" (il
tutto gira su un'architettura
molto minimale), rischio di saltare l'operazione per quel secondo.

 Vi vengono in mente altre soluzioni?
Non sono espertissimo, ma secondo me ti conviene usare setitimer() oppure 
alert(), e poi ti metti in attesa del segnale ALRM.
Con setitimer() mi pare che il timer ricominci da solo dopo la scadenza, 
ma forse e` meglio se leggi il manuale. :)
In questo modo il processo si risveglierebbe solo quando riceve il segnale 
(ogni 10 secondi) invece che ogni mezzo secondo, con il vantaggio di 
lasciare la macchina piu` scarica, e in piu` che la sua priorita` 
aumenterebbe e quindi verrebbe eseguito piu` rapidamente allo scadere del 
timer.
Bye.

 alert(), e poi ti metti in attesa del segnale ALRM.
Il problema di setitimer() è che si basa solo sul clock di sistema che
è svincolato
dall'RTC. Dopo un anno di funzionamento, è molto probabile che i tempi
in cui farei
le operazioni non sono più
  00:00:00
  00:00:10
  ...
ma
  00:00:01
  00:00:11
  ...
cioè se il clock di sistema (cioè l'interrupt di 1 secondo che
gestisce le pause tra cui
quelle di setitimer()) va più lentamente dell'orologio.
Per evitare ciò devo per forza legare l'esecuzione dell'operazione al
clock di sistema che
mi preoccuperei di mantenere sincronizzato.

 Vi vengono in mente altre soluzioni?
il fatto è che in un sistema non real-time non credo ci sia la possibilità di 
garantire quello che tu chiedi.
-- 
SF
Games are very educational. Scrabble teaches us vocabulary, Monopoly teaches 
us cash-flow management, and Dungeons & Dragons teaches us to loot dead bodies.

 le operazioni non sono più
Ogni tot operazioni (un migliaio) distruggi il timer precedente e lo 
reimposti in base al RTC? :)
Oppure intercetti un altro segnale (SIG1) che fa questa operazione, e 
quando sincronizzi il clock mandi quel segnale alla tua applicazione che 
si reimposta.
La butto li`, non ho mai fatto niente del genere.
Bye.

 garantire quello che tu chiedi.
No, il mio obiettivo non è quello di essere preciso con i tempi, cosa
importante
nei sistemi operativi real-time.
Ogni tanto potrei anche eseguire l'operazione 1-2 secondi dopo
l'istante teorico se
il sistema è occupato, quindi non mi interessano delle risposte di
tipo real-time.
La cosa importante è che mediamente io vada ad eseguire l'operazione
ogni
10 secondi. Cioè dopo 10 anni di funzionamento (vabbè, ho esagerato)
l'istante
dell'operazione sia sempre vicino all'istante teorico.

 verrŕ effettuata alle 00:00:10).
ricavo dal codice che tempo fa ho postato nel NG del Perl
http://groups.google.it/group/it.comp.lang.perl/msg/c5b151eed7d4f2ca?hl=it&dmode=source
Una modifica che forse fa per te.
da quello che ho capito devi correggere gli eventuali scostamenti
dall' ascissa temporale, per essere sempre in fase con dei punti cardinali.
Per quanto possano essere precisi gli orologi, tali sistemi implicano la
periodica correzione dell' errore.
un esempio in Perl:
--- file 
temporizzatore.pl
 ---
#!/usr/bin/perl
sub temporizzata
{
  $dataora = gmtime(time());
  print "\n",$dataora,"n";
  $SIG{'ALRM'} = \&temporizzata;
  alarm(10 - (time() % 10));
}
$SIG{'ALRM'} = \&temporizzata;
alarm(10 - (time() % 10));
$inputstring = <STDIN>;
_END_
--- fine file 
temporizzatore.pl
 ---
lo esegui con
perl 
temporizzatore.pl
e lo fermi premendo ENTER o con ^C
Usa la combinazione signal() e alarm() e puoi semplicemente
tradurlo in C o adattarlo ai tuoi scopi.
Il mio suggerimento e' tutto in:
alarm(10 - (time() % 10));
che si riassume in "sistema, mandami un SIGALRM tra il tempo
congruo a time attuale + 10 secondi in modo che possa eseguire
la subroutine temporizzata"
E' questo che chiedevi?
Ciao
Roberto.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

   print "\n",$dataora,"n";
Come al solito 
newsland.it
 mi ha eliminato una \
doveva essere:
print "\n",$dataora,"\n";


 E' questo che chiedevi?
A pensarci bene, sleep() e' implementata con SIGALRM come dice
man 3 sleep
(potrebbe ...)
quindi, visto che tu l' hai suggerita, il principio si puo' applicare
anche all' approccio con sleep:
perl -e 'while(1){sleep(10 - (time() % 10)); print "\n",time(),"\n"}'


 atteso.
....
lo avevi gia' fatto. quindi non ho capito il problema.
Scusate.


 lo avevi gia' fatto. quindi non ho capito il problema.
Il problema è nel cambio data. Se, mentre aspetto l'istante "01/01/2009 
00:00:00", qualcuno cambia data al "01/01/2008 00:00:00", il mio 
programma è bloccato per un anno!
Penso che utilizzerò sleep() per addormentare (quindi senza caricare la 
CPU) il processo un numero di secondi pari a quello rimanente per 
arrivare al successivo istante.
   while (1) {
     t = time ();
     if (t % 10)
       sleep (10 * (t / 10) + 10 - t);
       <eseguo l'operazione>
       sleep (1);
   }
Sicuramente, con il primo sleep(), farò un'attesa che varia da 0 a 9 
secondi, indipendentemente dall'eventuale cambiamento di data.
Dopo l'operazione faccio un'altra sleep() di 1 secondo per evitare di 
rifare l'operazione più volte per lo stesso secondo utile.
E' una cosa che non mi piace di questo sistema, ma dovrebbe funzionare.

 E' una cosa che non mi piace di questo sistema, ma dovrebbe funzionare.
Ad essere sincero non piace neanche a me :)
Ciao
Roberto.


 Ad essere sincero non piace neanche a me :)
E vabbè, ma non ci sono alternative, mi sembra...