Timer etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Timer etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

13 Ocak 2017 Cuma

Gömülü Proje - Fixed Rate Timer

Giriş
Amacımız belli bir süreden önce (örneğin 30 saniye) yenilenmesi gereken nesneleri yönetmek. Eğer belirtilen süre içinde yenilenmezse o nesne için kurulan TimerHandler nesne için belirtilen TimerKey ile çağrılır. Eğer nesne yenilenirse timer bir kere daha 30 saniye sonrasına kurulur.

FixedRateTimerManager nesnesi Component veya Manager içinde yaşar. Bu nesneler ana thread tarafından çalıştırılır. Böylece timer nesnesi de sürülmüş olur.
Main Thread-->Component -->Manager -->FixedRateTimerManager

Tasarım
TimerKey, ITimerHandler, FixedRateTimer, FixedRateTimerManager sınıflarından oluşur.

TimerKey
3 tane unsigned int alan bir yapı olsun. Bu yapı tasarımdaki önemli noktalardan birisi.

Bazı tasarımlarda, timer key alanı olmadan çalışır. Örneğin amaç 30 saniyede bir liste üzerinde yürümektir. Bu amaç için key alanı saklamak gerekmez.

Bazı tasarımlarda, her timer nesnesinin bir ID alanı vardır. Timer tetiklenince ID bilgisini de verir. Kod yazan kişi ID üzerinden haricen yönettiği state bilgisine erişir ve bir işlem yapar. Ancak bu da zor bir tasarım çünkü timer ile haricen tutulan "map" veri yapısını birlikte yönetmek gerekir.

Benim seçtiğim yöntemde timer tetiklenirken state bilgisini de sağlıyor. Böylece her şey daha kolay bir hale geliyor. State her zaman int tipi olduğu için sorun olmadı.

Bir diğer yöntem ise state bilgisini handler içinde saklamak. Handler yaşam döngüsü heap üzerinde olabilir. Timer bitince handler da silinebilir. Ancak gömülü ortamda malloc/new yapmamak için bu yöntemi seçmedim.

ITimerHandler
ITimerHandler isimli bir arayüzümüz olsun. Bu arayüzün TimerKey nesnesi alan OnTimeOut metodu olsun.
void OnTimeOut (TimerKey& key);

FixedRateTimer
Intrusive bir yapı. pNextTimer, pPrevTimer,isPeriodic,ExpirationTime,isActive alanları olsun.

FixedRateTimerManager
FixedRateTimerManager isimli bir sınıfımız olsun. Bu sınıf tüm FixedRateTimer nesnelerini iki veri yapısı kullanarak yönetsin.

İlk yapı bir "double linked list" olsun. Bu yapı nesneleri absolute timeout zamanına göre sıralı saklasın. Yani bitme zamanı en yakın olan nesne en başta,  bitme zamanı en uzak olan nesne ise en sonda olsun. Absolute time kullanabilmek için sistemde sürekli artan bir monotonic zaman kaynağı olması yeterli.

İkinci yapı ise bir "hashmap" olsun. Belirtilen TimerKey değerine sahip TimerHandler nesnesi silinebilsin, tekrar kurulabilsin.

FixedRateTimerManager belirtilen bir TimerKey nesnesi için bir TimerHandler eklerken her iki yapıyı da kullansın.

FixedRateTimerManager yönettiği belli bir TimerKey için yeniden kurma imkanı sağlasın. Yeniden kurulan timer listenin en sonuna taşınır. Aslında burası en kritik nokta. Tüm nesnelerin aynı timeout süresini kullandığını bildiğim için liste içinde dolaşarak timer nesnesini doğru yere yerleştirmekle uğraşmıyorum. Yani liste kendiliğinden sıralı oluyor.
Elimdeki liste şöyle olsun
A (30) , B (60) , C (60)
Şu anki saat ise 30 olsun. A nesnesi tekrar kurulunca liste şöyle olur.
B (60), C (60), A (90)

FixedRateTimerManager (TimeSpan& rate);
Kullanılacak sabit süre belirtilir.

void RunTimers () metodu çağrılınca liste dolaşılır.  Biten FixedRateTimer nesnesi tetiklenir. Eğer nesne periyodik ise bir sonraki tetiklenme zamanı aranir ve listenin en sonuna taşınır. Eğer nesne "single shot" ise listeden silinir.

bool StopTimer (TimerKey & key);
Belirtilen anahtar değerine sahip timer listeden silinir.

bool StartOrRescheduleTimer (TimerKey & key, ITimerHandler* pHandler);
Eğer belirtilen timer yoksa yeni bir timer başlatılır. Varsa 30 saniye sonrası için tekrar kurulur.


27 Şubat 2016 Cumartesi

Timer

.Net
Windows'ta sistem saati elle değiştirilince, kurulu bekleyen timer'lar kendilerini yeniden kurmazlar. Dolayısıyla karışıklıklar olabilir. Durumu düzeltmek için SystemEvents.TimeChanged event'i dinlenerek timer'ları tekrar kurmak gerekebilir.

System.Windows.Forms.Timer
Konuyu Timer Sınıfları yazısına taşıdım.

System.Timers.Timer
Konuyu Timer Sınıfları yazısına taşıdım.

System.Threading.Timer
Konuyu Timer Sınıfları yazısına taşıdım.

Java
Konuyu Java Timer Sınıfları yazısına taşıdım.

QT
QTimer aslında sadece her QObject sınfının sahip olduğu QObject::startTimer yeteneğini daha rahat kullanmamızı sağlayan bir sınıf. Timers başlıklı QT dokümantasyonunu okumakta fayda var.

POSIX
Posix ile timer yaratmak için bir kaç yöntem var.

alarm() yöntemi
alarm kullanırsak tek thread'li bir timer elde ederiz. Alarmı her seferinden tekrar kurmak gerekir. Örnek'te 3 defa 1 saniye aralıkla çalan bir alarm var.

timer_create yöntemi
timer_create(), timer_settime(),timer_connet(),timer_delete() beraber kullanılıyorlar.İlki timer nesnesini yaratır, ikincisi başlatır, sonuncusu ise timer nesnesini kapatır.

timer_create() metodunun imzası aşağıda.
int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
Bu metod ile kullanılan timer_t çoğu derleyicide void * olarak tanımlı. Bu yüzden timer_delete() ile silinmesi gerekiyor.
typedef void * __timer_t;
typedef __timer_t timer_t;

timer_create metodu signal'ler ile beraber kullanılabilir veya kullanılmayabilir.

signal olmadan kullanımı
timer_connect() metodu POSIX'te yok. Bu yöntem vxWorks'te işe yarıyor.
sigevent alanına NULL geçiyoruz. Çağırılacak metod timer_connect() ile belirtiliyor. Örnek:
timer_t polltimerID;
struct itimerspec poll_time;
poll_time.it_value.tv_sec = 0;
poll_time.it_value.tv_nsec= 1000; 

poll_time.it_interval.tv_sec = 0;
poll_time.it_interval.tv_nsec= 1000; // execute it every 1us

if(timer_create (CLOCK_REALTIME, NULL, &polltimerID))
    printf("problem in timer_create(): %s",strerror(errno));

if(timer_connect (polltimerID,MyPollFunction,0))
    printf("problem in timer_connect(): %s",strerror(errno));

if(timer_settime (polltimerID, 0, &poll_time, NULL))
    printf("problem in timer_settime(): %s",strerror(errno));

signal ile kullanımı

timer_create metodunu SIGEV_SIGNAL ile kullanırsak tek thread'li bir timer elde ederiz. Eğer SIGEV_THREAD ile kullanırsak timer'ın her çalışmasında yeni bir thread içinde çalışmasını sağlarız.

setitimer
Şu satırı dahil ederiz.
#include <sys/time.h>
Bu metod eski. timer_settime kullanılmalı. Kabaca şöyle yaparız.
#include <sys/time.h>
#include <signal.h>

void alarmhandler(){
    printf("\nTimer triggered");
}

void main(){
    struct itimerval timerval;
    signal(SIGALRM, alarmhandler);
    timerval.it_value.tv_sec = 1;
    timerval.it_value.tv_usec = 0;
    timerval.it_interval.tv_sec = 30;
    timerval.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &timerval, 0);
    pause();    
}
Çağrı hata varsa -1 döner. Virtual timer olarak ta kullanılabilir. Bu timer sadece uygulama çalışırken etkindir.
setitimer(ITIMER_VIRTUAL, &timerval, NULL);
getitimer ile kalan değer alınabilir.
getitimer(ITIMER_VIRTUAL, &timerval);
timer_settime
Bu metod timer_create ile yaratılmış bir timer'ı başlatır. Başlatmadan önce delay ve interval atanması gerekir. Bu işi yapmak için aşağıdaki gibi bir metod kullanılabilir. Örnek:

void itimerspec(struct itimerspec *tsp, int seconds){
    tsp->it_value.tv_sec = seconds;
    tsp->it_value.tv_nsec = 0;
    tsp->it_interval.tv_sec = seconds;
    tsp->it_interval.tv_nsec = 0;
}

Periodic Timer - SIGEV_THREAD ile
//Thread function to be invoked when the periodic timer expires
void timeoutHandler(sigval_t info)
{
}

struct sigevent sev;
memset(&sev,0,sizeof(struct sigevent));
//notify via thread
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = timeoutHandler;
sev.sigev_notify_attributes = NULL;
sev.sigev_value.sival_int =20;
sev.sigev_value.sival_ptr = &timer1;//Allows handler to get ID of this timer
// Create timer based on the current system time (CLOCK_REALTIME)
timer_t timer1;
if (timer_create(CLOCK_REALTIME, &sev, &timer1) != 0)
{
    //Error
}

struct itimerspec ts;
//Set delay
ts.it_value.tv_sec =1;//Delay 1 second
ts.it_value.tv_nsec = 0;
//Set interval
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 100000000;//Expire every 10th of a second

if (-1 ==timer_settime(timer1,0,&ts,NULL))
{
    //Error
}
//2 saniye bekle
sleep(2);
//Timer'ı kapatmak için
timer_delete(timer1);

Absolute Timer - SIGEV_SIGNAL ile
Absolute timer, gelecekteki bir zamanda çalışmasını istediğimiz timer anlamına geliyor.Örneği buradan aldım

//Signal handler function to be invoked when the absolute timer expires
void sig_handler (int val)
{
    printf("The timer expired and has entered signal handler: Value: %d\n", val);
}
int main()
{
    int Ret;

    struct sigevent sig;
    sig.sigev_notify = SIGEV_SIGNAL;
    sig.sigev_signo = SIGUSR1;
    signal(SIGUSR1, sig_handler);
   
    //create a new timer.
    timer_t timerid; //Stores the ID of the timer
    Ret = timer_create(CLOCK_REALTIME, &sig, &timerid);
    if (Ret == 0)
    {
        struct timespec newtime; //Stores the current system time
        struct itimerspec in, out; //Input values for the timer
        memset(&newtime, 0, sizeof (struct timespec));
        clock_gettime(CLOCK_REALTIME, &newtime);
        in.it_value.tv_sec = newtime.tv_sec + 10;
        in.it_value.tv_nsec = 0;
        in.it_interval.tv_sec = 0;
        in.it_interval.tv_nsec = 0;
       
        //issue the absolute timer request here.
        Ret = timer_settime(timerid, 1, &in, &out);
        if(Ret == 0)
            sleep(11);
        else
            printf("timer_settime() failed with %d\n", errno);
        //delete the timer.
        timer_delete(timerid);
    }
    else
        printf("timer_create() failed with %d\n", errno);
    return Ret;
}
Win32
WIN32 Timer'ları yazısına taşıdım.

Linux
prctl
Bu metod ile timerların gruplanması kontrol ediliyor. Gecikmeyi azaltmak için aşağıdaki gibi yapılabilir.
prctl(PR_SET_TIMERSLACK, 1);

timerfd_settime
Bu metod ile select veya poll ile dinlenebilen bir timer yaratılabilir. Örnek:

//create timer
int timer_fd  =  timerfd_create(CLOCK_MONOTONIC , 0); //non-settable clock or
int timer_fd  =  timerfd_create(CLOCK_REALTIME, 0); //settable system-wide clock

//periodic timer
struct itimerspec time_period = {{10,0},{10,0}};
result = timerfd_settime(timer_fd, 0, &time_period, NULL);
Bir başka periodic timer örneği:
int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
struct itimerspec timspec;
bzero(&timspec, sizeof(timspec));
timspec.it_interval.tv_sec = 0;
timspec.it_interval.tv_nsec = nanosecondInterval;
timspec.it_value.tv_sec = 0;
timspec.it_value.tv_nsec = 1;

timerfd_settime(timerfd, 0, &timspec, 0);

//absolute timer
struct itimerspec new_value;
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
new_value.it_value.tv_sec = now.tv_sec + 10;//expire after 10 seconds
new_value.it_interval.tv_sec = 0;//interval
new_value.it_value.tv_nsec = now.tv_nsec;
          
timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL);

//select or read fd now
//...

High Resolution Timer
Buradaki örnekte hrtimer_init(), hrtimer_start() metodları gösterilmiş.

Kendi Timer Sınıfımız
Buradaki soruda kendi timer sınıfımızı yazmak istersek, ne yapabileceğimiz kabaca anlatılmış.

Tek thread'li gömülü bir yazılım yapıyorsak timer'ı çalıştıran kod öncelik olarak döngüde en başta bir yerde olmalı.

Bir diğer örnekte ise Timer Wheel yapısı anlatılmış.

Timer ve Dictionary
Bazen bir nesne için bayatlama timer'ı tutmak gerekir. Nesnenin anahtar alanı ile timer'ı eşleştiren bir Dictionary yaratılır. Nesne güncellendikçe Dictionary'den Timer bulunur ve tekrar kurulur. Nesne silinmek istenirse önce Timer nesnesi daha sonra nesne silinir.

TimeoutHandler çağrıldığı zaman nesnenin anahtar değerini de veriyor olması gerekir.