Basit Timer Yapıları
Geleneksel Yaklaşım: alarm()
Timer kullanımının en basit yolu, prototipi aşağıda verilmiş olan alarm
fonksiyonunu kullanmaktır:
unsigned int alarm (unsigned int seconds);
Bu yöntemle ancak saniye çözünürlüğünde zaman belirtmek mümkündür. Zaman sona erdiğinde işletim sistemi uygulamaya SIGALRM
sinyalini gönderir. Zamanlayıcının dolduğunu uygulamada işleyebilmek için bu sinyali işleyecek callback fonksiyonu da tanımlanmış olmalıdır.
Aşağıda 1 saniyelik zamanlayıcı kurulması ve sinyal işleyici fonksiyonun tanımlanması örneklenmiştir:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
void timer_callback (int signum)
{
time_t now = time(NULL);
printf ("Signal %d caught on: %li\n", signum, now);
}
int main ()
{
signal(SIGALRM, timer_callback);
alarm(1);
sleep(3);
return 0;
}
Bu şekilde kurulmuş olan bir timer her 1 saniyede uygulamaya sinyal gönderecektir. Eğer belirli bir eylem olduğunda timer aralığını örnek olarak 5 saniyeye çıkarmak isterseniz, alarm(5)
şeklinde fonksiyonu yeniden kullanmanız yeterli olacaktır.
Timer'ı durdurmak isterseniz, alarm(0)
şeklinde 0 parametresi ile kullanmanız yeterli olacaktır. Bu şekilde durdurulmuş bir timer, 0'dan büyük bir değer verilerek fonksiyon tekrar çağrılırsa yeniden başlatılacaktır.
Zaman dolduğunda kullandığınız timer periyodik olarak tekrar baştan başlamayacaktır. Örnek olarak her 1 saniyede callback fonksiyonunuzun çağrılmasını istiyorsanız, timer dolduğunda tekrar alarm(1)
şeklinde mekanizmayı başlatmanız gerekmektedir.
Alarm fonksiyonu üzerinden örneklediğimiz bu yapı ancak çok basit senaryolarda tercih edilmelidir (bu yüzden sinyal yakalama kısmını da Sinyaller bölümünde işlediğimiz sigaction()
yöntemiyle değil signal()
fonksiyonuyla yaptık). Kullanım kolaylığına rağmen bu yöntemin başlıca dezavantajları:
- aynı anda sadece tek bir timer olması
- periyodik timer desteği olmaması
- timer çözünürlüğünün sadece saniyenin katları cinsinden verilebilmesi
- timer için kalan sürenin ne kadar olduğunu öğrenmenin bir yolu olmaması
- event loop mekanizması içerisinden kullanımına yönelik bir destek sunmaması
olarak sıralanabilir.
Şimdi tekrar yukarıda verdiğimiz örneğe geri dönelim. Uygulamayı alarm.c
adıyla kaydedip, derleyip çalıştırdığımızda 1 saniye geçince timer_callback fonksiyonu çağrılacak, sleep(3)
satırı nedeniyle kalan 2 saniye boyunca bekleyecek ve düzgün bir şekilde sonlanacak mıdır?
$ gcc -o alarm alarm.c
$ time ./alarm
Signal 14 caught on: 1427030338
real 0m1.001s
user 0m0.000s
sys 0m0.000s
Süreleri ölçebilmek için uygulamayı time
komutunu vererek çalıştırdık. Fakat görüleceği üzere toplam çalışma süresi 3 saniye değil, toplamda 1 saniye şeklinde gerçekleşti. Neden?
Daha önceki Sistem Çağrıları ve Sinyaller konularında değindiğimiz maddeleri hatırlayınız. sleep(3)
fonksiyonunun yol açtığı sistem çağrısı çalışıyorken 1. saniye dolduğunda alarm(1)
'den kaynaklanan SIGALRM
sinyali geldiğinde, sleep(3)
için başlatılan sistem çağrısı kesintiye uğradı.
sleep()
fonksiyonu sistem çağrısı tarafından kesintiye uğradığında saniye cinsinden kalan zamanı geri döndürür. Zaman tamamen bitip normal şekilde geri döndüğünde ise 0 döndürür. Bu özelliği dikkate alarak, global errno
değişkeni ile de INTERRUPT
durumu olup olmadığını test ederek uygulamamızı aşağıdaki gibi daha güvenli hale getirebiliriz. Ek olarak alarm sinyalini işlediğimiz timer_callback fonksiyonunda ilgili alarmı tekrar kurup periyodik çalışmasını sağladığımıza ve while döngüsüne girmeden önce yarım saniyelik ek bir bekleme koyduğumuza dikkat ediniz:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include "../common/debug.h"
void timer_callback (int signum)
{
time_t now = time(NULL);
printf ("Signal %d caught on: %li\n", signum, now);
alarm(1);
}
int main ()
{
unsigned int remaining = 3;
signal(SIGALRM, timer_callback);
alarm(1);
usleep(500000);
while ( (remaining = sleep(remaining)) != 0) {
if (errno == EINTR) {
debugf("sleep interrupted by signal");
} else {
errorf("sleep error: %s", strerror(errno));
}
}
return 0;
}
Şimdi uygulama çıktısına tekrar göz atalım:
$ time ./alarm Signal 14 caught on: 1427033478 debug: sleep interrupted by signal (main alarm.c:23) Signal 14 caught on: 1427033479 debug: sleep interrupted by signal (main alarm.c:23) Signal 14 caught on: 1427033480 debug: sleep interrupted by signal (main alarm.c:23) Signal 14 caught on: 1427033481 real 0m4.001s user 0m0.000s sys 0m0.000s
Bu defa uygulamamız yaklaşık 4 saniye kadar çalışıp sonlandı. Oysa beklentimiz 3.5 saniye kadar sürmesi idi. Uygulama çıktısını daha dikkatli inceleyip nedenini yorumlamaya çalışınız.
Interval Timer Kullanımı
Interval timer mekanizması ilk olarak 4.2BSD versiyonunda görülmüş sonradan POSIX tarafından standardize edilmiştir.
Geleneksel alarm()
tabanlı timer yöntemine oranla temel avantajları aşağıdaki şekilde sıralanabilir:
- mikrosaniye seviyesinde çözünürlük sağlar
- zaman ölçümünü 3 farklı mod üzerinden daha detaylı kontrol etme imkanı verir
- bir defa ayarlayıp, periyodik olarak çalışmasını sağlamak mümkündür
- herhangi bir anda ne kadar zaman kaldığı sorgulanabilir
Interval timer işlemleri için kullanılan fonksiyon prototipleri aşağıdaki gibidir:
#include <sys/time.h>
int setitimer (int which, const struct itimerval *new_value,
struct itimerval *old_value)
int getitimer (int which, struct itimerval *value)
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
long tv_sec;
long tv_usec;
};
Interval timer kurmak istendiğinde setitimer
fonksiyonunun 2. parametresine, istemiş olduğumuz timer ile ilgili zaman bilgilerini, ilk çalıştığında istenen süre (veri yapısının it_value
elemanı), sonraki çalışmalarında istenen süre (veri yapısının it_interval
elemanı) biçiminde vermemiz gereklidir.
Örnek olarak, önce 1 saniye ardından 300 milisaniyede bir uygulamamızı haberdar edecek bir interval timer aşağıdaki şekilde kurulabilir:
struct itimerval new_timer;
struct itimerval old_timer;
new_timer.it_value.tv_sec = 1;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &new_timer, &old_timer);
Fonksiyonun 3. parametresine verilen itimerval
türündeki değişken adresine, yeni değerler ayarlanmadan önce aktif durumda olan bir interval timer var ise onun değerleri aktarılır.
Interval timer mekanizmasıyla 3 farklı tipte timer kurulması mümkündür. Kullanılacak timer tipi setitimer()
fonksiyonunun ilk parametresinde belirtilir.
Timer Tipi | Sinyal | Açıklama |
---|---|---|
ITIMER_REAL | SIGALRM |
Uygulamanın harcadığı zamandan bağımsız, toplam geçen zaman üzerinden hesaplanır |
ITIMER_VIRTUAL | SIGVTALRM |
Uygulamanın sadece kullanıcı kipinde çalıştığı zaman üzerinden hesaplanır |
ITIMER_PROF | SIGPROF |
Uygulamanın hem kullanıcı kipinde harcadığı zaman, hem de yapmış olduğu sistem çağrıları içerisinde harcamış olduğu zamanın toplamı üzerinden hesaplanır. Pratikte uygulama içerisinde ITIMER_VIRTUAL ile birlike ayrı ayrı kurulup, uygulamanın kullanıcı kipi ve çekirdek kipinde ayrı ayrı ne kadar zaman harcadığını hesaplamakta kullanılır. |
Yukarıdaki tabloyu incelediğimizde, ITIMER_REAL tipinde bir interval timer kurduğumuzda, tıpkı alarm()
fonksiyonu ile kurmuş olduğumuz timer mekanizmasında olduğu gibi, SIGALRM sinyali ile uygulamamızı uyandıracağını görebiliriz. Dolayısıyla bu şekilde bir interval timer ile alarm()
fonksiyonunun aynı uygulamada kullanımı, her ne kadar sinyalin işlendiği fonksiyonda getitimer()
fonksiyonuyla kalan zaman üzerinde ikinci bir kontrol yapılmak suretiyle çözüm üretmek mümkün olsa da kafa karıştıracak ve yönetimi güçleştirecektir. Bu nedenle aynı anda kullanımı önerilmez.
Önceki örneğimizi interval timer kullanacak şekilde aşağıdaki gibi değiştirip çalıştıralım:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include "../common/debug.h"
void timer_callback (int signum)
{
struct timeval now;
gettimeofday(&now, NULL);
printf ("Signal %d caught on: %li.%03li\n", signum, now.tv_sec, now.tv_usec / 1000);
}
int main ()
{
unsigned int remaining = 3;
struct itimerval new_timer;
struct itimerval old_timer;
new_timer.it_value.tv_sec = 1;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &new_timer, &old_timer);
signal(SIGALRM, timer_callback);
while ( (remaining = sleep(remaining)) != 0) {
if (errno == EINTR) {
debugf("sleep interrupted by signal");
} else {
errorf("sleep error: %s", strerror(errno));
}
}
return 0;
}
Yukarıdaki kodda öncelikle 1 saniye, sonra da 300 milisaniyede bir interval timer kurulmuş ve tıpkı ilk örneğimizdeki gibi, toplam çalışma süresi 3 saniye olacak şekilde sleep()
fonksiyonunun sinyal tarafından kesintiye uğraması durumu ele alınmış. Örnek kodu interval.c
isminde kaydedip derleyelim ve çalıştıralım:
$ gcc -o interval interval.c $ time ./interval Signal 14 caught on: 1427042689.280 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042689.580 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042689.880 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042690.180 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042690.480 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042690.780 debug: sleep interrupted by signal (main interval.c:34) Signal 14 caught on: 1427042691.080 debug: sleep interrupted by signal (main interval.c:34) ...
Uygulamamız timer ilk çalıştıktan sonra çıktıdan da anlaşılacağı üzere tam da beklediğimiz gibi 300 milisaniyede bir callback fonksiyonumuzu çağırdı. Ancak biraz daha beklenildiğinde uygulamanın sonlanmadığı ve 300 milisaniyede bir callback fonksiyonunu çalıştırmaya devam ettiği görülecektir. Interval değerini 600 milisaniye yaptığınızda ise uygulamanın sonlandığını görebilirsiniz.
sleep()
fonksiyonu etrafında düşünerek bu davranışın nedenlerini bulmaya çalışınız.