Sinyal Yakalama ve Gönderme
Sinyal Yakalama
Uygulama tarafından yakalanabilmesine izin verilen sinyaller, ilgili fonksiyonlar kullanılarak uygulama içerisinde yakalanıp işlenecek hale getirilir. Bunun için temel olarak, yakalamak istediğimiz sinyalleri belirlemek ve bu sinyalleri işleyecek callback fonksiyonlarını hazırlamamız gerekir.
Sinyal işleme sürecinde aynı callback fonksiyonunu birden fazla sinyal için kullanmamız da mümkündür. Asenkron olarak çağırılacak callback fonksiyonunun prototipi aşağıdaki gibidir:
typedef void (*sighandler_t)(int);
Parametre olarak gelen int
değeri, sinyalin numarasını gösterecektir.
Yakalamak istediğimiz sinyal ve karşılayacak callback fonksiyonunu belirtmek için signal
veya sigaction
fonksiyonları kullanılır. Kullanım prototipleri aşağıdaki gibidir:
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
signal
kullanımı artık önerilmemektedir, bunun yerine daha fazla kontrol sunan sigaction
kullanımı tercih edilmelidir. Zaten günümüzde glibc içerisindeki signal gerçekleştirimi de sigaction fonksiyonunu sarmalayan bir yapıdan ibarettir.
Aşağıdaki örnek uygulamayı sample.c
adında kaydediniz.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
static int exit_flag = 0;
static void handler (int signum)
{
if (signum == SIGTERM) {
printf("TERM signal received, exiting program\n");
exit_flag = 1;
} else {
printf("Not a TERM signal: %d (%s)\n", signum, strsignal(signum));
}
}
int main (int argc, char *argv[])
{
struct sigaction act;
memset (&act, '\0', sizeof(act));
act.sa_handler = &handler;
if (sigaction(SIGTERM, &act, NULL) < 0) {
perror ("sigaction");
return 1;
}
if (sigaction(SIGINT, &act, NULL) < 0) {
perror ("sigaction");
return 1;
}
while (!exit_flag) usleep(100000);
return 0;
}
Sonrasında aşağıdaki gibi derleme işlemini yapıp çalıştıralım:
$ gcc -o sample sample.c
$ ./sample
Uygulamayı çalıştırdıktan sonra CTRL-C tuş kombinasyonu ile INT sinyali ürettiğimizde sigaction
ile belirttiğimiz callback fonksiyonumuzun devreye girdiğini, TERM sinyali dışında bir sinyal geldiği için uyarı mesajı verip çalışmasına devam ettiğini görmekteyiz.
Başka bir konsoldan uygulamamıza kill
komutu ile TERM sinyalini gönderdiğimizde ise beklediğimiz gibi exit_flag
değişkeninin değerinin 1 yapılmak suretiyle sonlandığını görmekteyiz. Bu şekilde uygulama sonlandığında çıkış değerinin de $? kabuk değişkeninde beklediğimiz üzere 0 olduğunu görüyoruz:
$ ./sample
$ (diger konsoldan) kill -s SIGTERM $(pidof sample)
TERM signal received, exiting program
$ echo $?
0
Bu noktada şu soruyu kendimize soralım: Test uygulamamız çalışıyorken diğer konsoldan uygulama içerisinde işlediğimiz TERM ve INT sinyalleri dışında, örneğin USR2 sinyalini gönderirsek ne olur?
$ ./sample
$ (diger konsoldan) kill -s SIGUSR2 $(pidof sample)
User defined signal 2
$ echo $?
140
Yukarıda görüldüğü üzere, uygulamamız USR1 sinyalini aldığında, User defined signal 2 mesajıyla sonlandı. Bu bizim uygulama içerisinden bastırdığımız bir mesaj değil. Ayrıca uygulamanın çıkış değeri önceki örnekte olduğu gibi 0 değil, 140 oldu.
Uygulamada nasıl işleneceği düzenlenmiş olmayan bir sinyal geldiğinde, ilgili sinyalin önceki bölümde vermiş olduğumuz tabloda yer alan öntanımlı eylemi gerçekleşir. SIGUSR2 sinyalinin öntanımlı eylemi de uygulamayı durdurmak olduğu için uygulamamız sonlandı. Eğer öntanımlı eylemi yoksay şeklinde olan örneğin SIGURG sinyalini göndermiş olsaydık, uygulamamız çalışmasına bir şey olmamış gibi devam edecekti:
$ ./sample
$ (diger konsoldan) kill -s SIGURG $(pidof sample)
Bu bilgiler ışığında, uygulamalarımızda sinyallere karşı önlem almadığımızda, beklenmeyen bir sinyalin bize ulaşması nedeniyle kontrolsüz sonlanmalara yol açacağını rahatlıkla söyleyebiliriz.
Sinyal Gönderme
Uygulama içerisinden sinyal göndermek için prototipi aşağıda belirtilen kill
fonksiyonu kullanılır:
int kill(pid_t pid, int sig);
Fonksiyon basitçe parametre olarak verilmiş olan PID değerine sahip uygulamaya istenen sinyalin gönderimini sağlamaktadır. İşlemin gerçekleşebilmesi için ilgili uygulamaya sinyal gönderebilme yetkisinin olması gerekir. Örnek olarak A kullanıcısı B kullanıcısının uygulamalarına sinyal gönderemez, ancak kendi sahip olduğu uygulamalara sinyal gönderebilir.
Sistemdeki root kullanıcısı veya Linux Capabilities API üzerinden CAP_KILL kabiliyetine sahip olan uygulamalar ise herhangi bir uygulamaya sinyal gönderebilirler.
Uygulama dışından genel amaçlı sinyal gönderme işlemleri için kill komutunu kullanabilirsiniz.
Uygulama içerisinden bir başka uygulama yerine, çalışan uygulamanın kendisine sinyal gönderilmek istendiğinde raise
ve abort
fonksiyonları kullanılır.