Daemon Oluşturma
Daemon prosesler, direkt olarak kullanıcının kontrolünde çalışmayan, arka planda (background) hizmet veren proseslerdir. Genellikle, sistem açılırken başlatılıp kapanana kadar çalışmaktadırlar. Sistemimizde çok sayıda daemon çalışmaktadır, birkaç bilindik deamon örneği aşağıdaki gibidir.
- crond: Komutların belirlenen zamanda çalışmasını sağlar.
- sshd: Uzak makinalardan sisteme oturum açılmasına olanak sağlar.
- httpd: Web sayfalarını sunar.
- nfsd: Ağ üzerinden dosya paylaşımını sağlar.
Not: Daemon prosesler, bir zorunluluk olmamasına karşın, genellikle d harfi ile sonlanacak şekilde isimlendirilirler.
Bir prosesin daemon olarak çalışabilmesi için aşağıdaki gibi bir yol izlenebilir.
- Ayar dosyalarının okunması veya gerekli sistem kaynaklarının elde edilmesi gibi başlangıç işlemleri, proses gerçek anlamda daemon olmadan önce yapılmalıdır. Bu sayede, alınan hatalar kullanıcıya bildirilebilir ve proses uygun bir hata kodu ile sonlandırılabilir.
- Üst prosesi (parent process) init olan, arka planda çalışan bir proses oluşturulmalıdır. Bu amaçla, proses içinde önce fork işlemi yapılarak bir alt proses oluşturulmalı, sonrasında üst proses exit ile sonlandırılmalıdır.
- Yoluna devam eden proses içinde setsid fonksiyonu çağrılarak yeni bir oturum (session) açılmalı ve prosesin terminal (controlling terminal) ile ilişkisi kesilmelidir.
- Üst prosesten miras alınan tüm açık dosya betimleyicileri (file desriptor) kapatılmalı.
- Standart giriş, çıkış ve hata mesajları /dev/null aygıtına yönlendirilmeli.
- Prosesin çalışma dizini değiştirilmeli.
Daemon oluşturmanın detaylarına geçmeden önce, yukarıda da bahsettiğimiz oturum kavramına değinmek istiyoruz.
Oturum (Session)
Kullanıcılar, bir terminal üzerinden sisteme giriş yaptıktan sonra (login) kabuk programı (shell) üzerinden birçok uygulamayı çalıştırabilmektedirler. Kullanıcı sistemden çıktığında bu prosesler kapatılmalıdır. İşletim sistemi bu prosesleri, oturum (session) ve proses grupları şeklinde gruplandırır. Her bir oturum proses gruplarından oluşmaktadır. Bu durumu aşağıdaki gibi tasvir edebiliriz.
Oturumdaki proseslerin girdilerini aldıkları ve çıktılarını gönderdikleri terminal kontrol terminali (controlling terminal) olarak isimlendirilmektedir. Bir kontrol terminali aynı anda yalnız bir tane oturum ile ilişkili olabilir. Bir oturumun ve içindeki proses gruplarının kimlik (ID) numaraları bulunmaktadır, bu kimlik numaraları oturum ve proses grup liderlerinin proses kimlik numaralarıdır (PID). Bir alt proses üst prosesiyle aynı grubu paylaşmaktadır. Pipe mekanizmasıyla haberleştirilen proseslerde ilk proses, proses grup lideri olmaktadır. Basit bir örnek üzerinden bu duruma daha yakından bakalım.
test.c:
#include <stdio.h>
int main() {
getchar();
return 0;
}
$ gcc -otest test.c
Hangi terminal üzerinde olduğumuza baktıktan sonra, uygulamayı çalıştırabiliriz.
$ tty
/dev/pts/24
$ ./test
Uygulamamız önplanda çalıştığından, başka bir terminal üzerinden prosesimizle ilgili bilgilere aşağıdaki gibi ulaşabiliriz.
$ ps -C test -o "pid ppid pgid sid tty command"
PID PPID PGID SID TT COMMAND
8788 8703 8788 8703 pts/24 ./test
ps çıktısındaki kısaltmalar ve anlamları aşağıdaki gibidir.
Kısaltma | Anlamı |
---|---|
PID | Proses Kimliği (Process ID) |
PPID | Üst Proses Kimliği (Parent Process ID) |
PGID | Proses Grup Kimliği (Process Group ID) |
SID | Oturum Kimliği (Session ID) |
TT | Terminal |
Prosesimizin, proses ve grup kimliklerinin aynı olduğunu görüyoruz, bu durumda proses kendi grubunun lideri durumundadır. Oturum kimlik değerinin ise 8703 olduğunu görmekteyiz. Prosesimizin oturum kimliği, oturum liderinin kimlik değeri (PID) olduğundan, bu kimliğin hangi prosese ait olduğunu aşağıdaki gibi bulabiliriz.
$ ps -jp 8703
PID PGID SID TTY TIME CMD
8703 8703 8703 pts/24 00:00:00 bash
Terminale giriş yaptıktan sonra ilk çalışan kabuk prosesisin oturum lideri olduğunu görüyoruz. Kabuk prosesisin tüm kimlik değerlerinin aynı olduğuna dikkat ediniz.
Şimdi bir prosesi nasıl deamon yapabileceğimize daha yakından bakalım.
Daemon Proses Oluşturma
GNU C kütüphanesi, daemon isimli bir fonksiyon barındırmasına karşın bu fonksiyon POSIX standartlarında yer almamaktadır. daemon fonksiyon kodu, glibc ana dizininde misc/daemon.c dosyasında bulunmaktadır. Burada benzer bir fonksiyonu nasıl oluşturabileceğimizi inceleyeceğiz. Bu amaçla aynı arayüze sahip _daemon isimli bir fonksiyonu adım adım oluşturacağız. Daemon olarak çalışacak uygulama kodunu test.c, daemon fonksiyonunu adım adım oluşturacağımız kodu ise daemon.c olarak isimlendirelim.
#include <stdio.h>
int _daemon(int, int);
int main() {
getchar();
_daemon(0, 0);
getchar();
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <linux/limits.h>
int _daemon(int nochdir, int noclose) {
pid_t pid;
int i;
pid = fork ( );
if (pid == -1)
return -1;
else if (pid != 0) {
exit (EXIT_SUCCESS);
}
return 0;
}
Daemon oluşturmak için yapılması gerekenleri maddelerken, üst prosesi init olan arka planda çalışan bir prosese ihtiyacımız olduğundan bahsetmiştik. Şu haliyle _daemon
kodumuz, bir alt proses oluşturmakta ve sonrasında üst prosesi öldürmektedir. Bu durumda yeni prosesimiz init prosesinin alt prosesi olacak ve arkaplanda çalışmaya devam edecektir. Uygulamanın hemen sonlanmasını engellemek için test.c içinde getchar fonksiyonlarını kullandık. Şimdi uygulamayı derleyip prosesin _deamon çağrılmadan önce ve sonrasındaki durumunu inceleyelim.
$ gcc -otest test.c daemon.c
Uygulamayı çalıştıralım herhangi bir tuşa basmaksızın başka bir terminale geçelim.
$ ./test
Prosesimizle ilgili değerlerin aşağıdaki gibi olduğunu görmekteyiz. Bu durumda henüz _daemon fonksiyonu çağrılmamış durumdadır.
$ ps -C test -o "pid ppid pgid sid tty stat command"
PID PPID PGID SID TT STAT COMMAND
8947 8703 8947 8703 pts/24 S+ ./test
STAT alanına baktığımızda prosesimizin çalışabilir durumda ama çizelge dışında bir olayın gerçekleşmesini bekleğini ve önplanda çalıştığını görüyoruz. STAT alanıyla ilgili bazı kısaltmalar ve anlamları aşağıdaki gibidir.
Kısaltma | Anlamı |
---|---|
S | Bir olayın gerçekleşmesi için uykuda bekleniyor |
T | Uygulama durdurulmuş |
s | Oturum lideri |
+ | Uygulama önplanda çalışıyor |
Uygulamamızın üst prosesinin beklediğimiz üzere kabuk olduğunu görmekteyiz.
$ ps -jp 8703
PID PGID SID TTY TIME CMD
8703 8703 8703 pts/24 00:00:00 bash
Şimdi uygulamamızı çalıştırdığımız terminale dönelim ve _daemon fonksiyonunun çağrılması için bir enter tuşuna basalım. Tekrar diğer terminal üzerinde proses bilgilerine bakalım.
$ ps -C test -o "pid ppid pgid sid tty stat command"
PID PPID PGID SID TT STAT COMMAND
9004 5842 9003 8703 pts/24 S ./test
İlk olarak STAT alanında + karakterini görmediğimiz için yeni alt prosesin arkaplanda çalıştığını söyleyebiliriz. Şimdi prosesin üst prosesinin kim olduğunu bakalım.
$ ps -jp 5842
PID PGID SID TTY TIME CMD
5842 5842 5842 ? 00:00:00 upstart
Artık prosesimizin üst prosesisin, geleneksel init yerine kullanılan, upstart prosesi olduğunu görmekteyiz (Ubuntu kullanıyorsanız).
Şimdi bir sonraki adıma geçebiliriz. Bir sonraki adımda yeni bir oturum açılması ve prosesin kontrol terminaliyle ilişkisinin kesilmesi gerektiğini söylemiştik. Bu amaçla setsid fonksiyonu kullanılmaktadır. _daemon fonksiyonumuza bu çağrıyı ekleyelim. Eklenecek kod parçası aşağıdaki gibidir.
if (setsid() == -1)
return -1;
_daemon çağrılmadan önceki durumu incelediğimiz için artık test.c kodundaki ilk getchar fonksiyonunu kaldırabiliriz.
test.c:
#include <stdio.h>
int _daemon(int, int);
int main() {
_daemon(0, 0);
getchar();
return 0;
}
Uyulamayı yeniden derleyelim çalıştırdıktan sonra incelemelerimizi yaptığımız terminale geçelim. Prosesimizin yeni durumu aşağıdaki gibidir.
$ ps -C test -o "pid ppid pgid sid tty stat command"
PID PPID PGID SID TT STAT COMMAND
9090 5842 9090 9090 ? Ss ./test
TT alanındaki ?
işareti prosesimizin artık bir terminale bağlı olmadığını göstermektedir. Bu durumda terminalin sonlanması durumunda prosesimiz yoluna devam edecek veya terminal üzerinden herhangi bir sinyal gönderilemeyecektir. Prosesimiz PID, PGID ve SID değerlerinin aynı olduğuna dikkat ediniz, prosesimiz artık oturum lideri durumundadır.
Sonrasında geçirdiğimiz argümanın değerine göre, çalışılan dizini root dizin olarak değiştiriyoruz. _daemon fonksiyonuna aşağıdaki kod parçasını ekleyebilirsiniz.
if (!nochdir) {
if (chdir("/") == -1)
return -1;
}
Bir sonraki adımda ise, geçirilen argümana göre, tüm dosya betimleyicileri kapatılabilmektedir. _daemon fonksiyonuna aşağıdaki kodu ekliyoruz.
#define NR_OPEN 1024
if (!noclose) {
for (i = 0; i < NR_OPEN; i++)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
Tüm dosya betimleyicileri kapatıldıktan sonra, daemon tarafında açılan yeni dosyalar sırasıyla 0
, 1
ve 2
dosya betimleyicileri ile gösterilecektir. Bu durumda örneğin kod içindeki printf komutları 2. açılan dosyaya yönlenmiş olacaktır. Bunu önlemek için ilk 3 betimleyicinin /dev/null aygıtını göstermesi sağlanmıştır. Yeni bir dosya açıldığında betimleyici olarak dosya betimleyici tablosundaki en küçük değerin verileceği garanti altına alınmıştır. Bu durumda open çağrısından sonra /dev/null aygıtı için 0 numaralı betimleyici tahsis edilecektir. dup fonksiyonlarıyla da sıradaki 1 ve 2 numaralı betimleyicilerin /dev/null aygıtını göstermesi sağlanmıştır. Bu durumda _daemon fonksiyonunun son hali aşağıdaki gibi olacaktır.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <linux/limits.h>
#define NR_OPEN 1024
int _daemon(int nochdir, int noclose) {
pid_t pid;
int i;
pid = fork ( );
if (pid == -1)
return -1;
else if (pid != 0) {
exit (EXIT_SUCCESS);
}
if (setsid() == -1)
return -1;
if (!nochdir) {
if (chdir("/") == -1)
return -1;
}
if (!noclose) {
for (i = 0; i < NR_OPEN; i++)
close (i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
return 0;
}
Örnek olarak, sshd uygulamasının daemon olarak çalışmaya başlatıldığı kod parçası aşağıdaki gibidir.
...
if (!(debug_flag || inetd_flag || no_daemon_flag)) {
int fd;
if (daemon(0, 0) < 0)
fatal("daemon() failed: %.200s", strerror(errno));
/* Disconnect from the controlling tty. */
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
if (fd >= 0) {
(void) ioctl(fd, TIOCNOTTY, NULL);
close(fd);
}
}
...