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);
        }
}
...

results matching ""

    No results matching ""