> Oturum("Session") Kavramı: Proses Gruplarının oluşturduğu topluluğa Oturum denir. Bir Oturum içerisinde bir adet ön plan("foreground") proses grubu olurken, birden fazla arka plan("background") proses grubu olabilir. İşte "shell" programı üzerinden program çalıştırırken komut satırı ifadesi olarak "&" kullanırsak, artık o program arka planda çalışacak ve "shell" programı onu "wait" ile beklemeyecektir. İşte böylesi programlar, o Oturum içerisinde arka plan("background") olarak kabul edilirler. Eğer komut satırı ifadesi olarak "&" kullanmadan program çalıştırırsak, artık o program ön plan("foreground") olacak ve "shell" programı onu "wait" ile bekleyecektir. "shell" programından "Ctrl+c" ve/veya "Ctrl+delete" yaparsak, ön plandaki proses grubuna ilgili sinyaller gönderilir. Diğer yandan Proses Gruplarından arka planda olanları ön plana, ön planda olanı da arka plana çekebiliriz. Zaten Oturum kavramı da "shell" gibi terminal programları için düşünülen kavramlardır. Öte yandan Oturum'ların da bir ID değeri vardır ki bu değer aslında Oturum'u hayata getiren, yani lider konumunda olan, Proses Grubunun ID değeridir. Bir diğer deyişle Oturum'u hayata getiren proses grubu lider konumunda oluyor ve grubun ID değeri, o oturumun da ID değeri oluyor. Özetle; -> "shell", bir adet proses grubu oluşturur ve kendisini bu grubun lideri yapar. -> Yine "shell", bir oturum oluşturur ve kendisinin içinde bulunduğu grubu, yani yukarıda oluşturduğu grubu, o oturumun lideri yapar. -> Yine "shell", "&" ile biten komutlara ilişkin proses grup(lar)ı oluşturur ve bu grupları da yukarıda oluşturduğu oturumun arka plan proses grubu olarak belirler. Tabii prosesin kendisinin bulunduğu proses grubu ön plan proses grubu olur. -> Yine "shell", "&" ile bitmeyen komuta ilişkin bir proses grubu oluşturur, onu içinde bulunduğu oturumun ön plan proses grubu olarak belirler. Tabii prosesin kendisinin bulunduğu proses grubu arka plan proses gruplarından birisi olur. Böylece bir "t" anında bir oturum içerisinde birden fazla arka plan proses grubu mevcut olabilirken, bir tane ön plan proses grubu olur. * Örnek 1, // Terminal - I : "bash" üzerinden aşağıdaki kodları çalıştıralım ve programların sonsuza kadar çalıştığını varsayalım. ./sample & ./mample // Terminal - II: İkinci bir terminal üzerinden aşağıdaki komutu çalıştıralım. "-t pts/0" ifadesi "Terminal - I" e // ilişkindir. "ps -o pid, pgid, sid, cmd, -t pts/0" Bu komut çağrısı sonucunda "Terminal - II" nin çıktısı şöyle olacaktır; PID PGID SID CMD 31684 31684 31684 bash 52630 52630 31684 ./mample 52632 52632 31684 ./sample Görüleceği üzere "Terminal - I" olan "bash" programı "mample" ve "sample" için birer proses grubu oluşturmuş ve bu programları o proses gruplarının lideri yapmıştır. Daha sonra bu proses gruplarını içeren bir Oturum oluşturmuştur. Her ne kadar yukarıdaki çıktıdan bakıldığında hangi proses gruplarının ön plan, hangilerinin arka plan olduğu anlaşılamasa da "./mample" arka plan, "./sample" ise ön plandır. Diğer yandan bir Oturum mutlak suretle bir terminal sürücüsüyle bağlantılı olmalıdır. İşte "Ctrl+C" ve/veya "Ctrl+Delete" yaptığımız zaman gönderilen sinyal, iş bu terminal sürücüsünden gönderilir. Bu terminal sürücüsüne ise "controlling terminal" denir. Pekiyi bizler bir oturumun ID değerini nasıl elde edebiliriz? Bu noktada devreye "getsid" fonksiyonu devreye girmektedir. >> "getsid" : Fonksiyonun prototipi aşağıdaki gibidir. #include pid_t getsid(pid_t pid); Fonksiyon parametre olarak bir "process ID" değeri alır. Eğer bu değer "0" olarak girilirse, fonksiyonu çağıran proses ele alınır. Hata durumunda "-1" ile geri döner ve "errno" değişkenini uygun değere çeker. * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* # OUTPUT # Parent > Process ID: 252 Parent > Grandparent Process ID: 251 Parent > Process Group ID: 252 Parent > Process Season ID: 252 Child > Process ID: 256 Child > Grandparent Process ID: 252 Child > Process Group ID: 252 Child > Process Season ID: 252 */ pid_t pid, pgid; if ((pid = fork()) == -1) exit_sys("fork"); if (pid != 0) { /* Parent Process */ printf("Parent > Process ID: %jd\n", (intmax_t)getpid()); printf("Parent > Grandparent Process ID: %jd\n", (intmax_t)getppid()); printf("Parent > Process Group ID: %jd\n", (intmax_t)getpgrp()); printf("Parent > Process Season ID: %jd\n", (intmax_t)getsid(0)); if (waitpid(pid, NULL, 0) == -1) exit_sys("waitpid"); } else { /* Child Process */ sleep(1); printf("Child > Process ID: %jd\n", (intmax_t)getpid()); printf("Child > Grandparent Process ID: %jd\n", (intmax_t)getppid()); printf("Child > Process Group ID: %jd\n", (intmax_t)getpgrp()); printf("Child > Process Season ID: %jd\n", (intmax_t)getsid(0)); } return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi bizler nasıl oturum oluştururuz? Tabii ki "setsid" fonksiyonunu kullanarak. >> "setsid" : Fonksiyonun prototipi aşağıdaki gibidir. #include pid_t setsid(void); Burada fonksiyon şu işlemleri yerine getirmektedir; -> Yeni bir oturum, bu oturum içerisinde de yeni bir proses grubu oluşturur. -> Oluşturulan iş bu oturum ve proses grubunun lideri ise iş bu fonksiyonu çağıran prosestir. Herhangi bir hata durumunda fonksiyon "-1" ile geri döner ve "errno" değişkenini uygun değere çeker. Örneğin, bu fonksiyonu çağıran proses içinde bulunduğu proses grubunun lideriyse fonksiyon başarısız olacaktır. Yani "shell" üzerinden çağrıdığımız prosesler direkt olarak "setsid" çağrılıyorsa, fonksiyon çağrısı başarısız olacaktır. Bunun "work-around" yöntemi ise önce "fork" yapmak ve alt proseste bu fonksiyon çağrısı yapmaktır. Tipik olarak bu fonksiyon, "shell" tarafından işin başında çağrılır. Böylelikle hem bir proses grubu hem de bir oturum oluşturulmuş olur ki bunların lideri de "shell" programıdır, eğer bir komut "shell" üzerinden henüz çalıştırılmamışsa. Velevki bir komut "&" ile bitmeyen komutlar "shell" üzerinden çalıştırılırsa, bu komuta ilişkin proses(ler) için yeni bir proses grubu oluşturacak ve bu grudu da oturumun ön plan proses grubu olarak atayacaktır. * Örnek 1.0, #include #include #include void exit_sys(const char *msg); int main(void) { /* # OUTPUT # setsid: Operation not permitted */ if (setsid() == -1) exit_sys("setsid"); printf("%s\n", "Hello World"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 1.1, #include #include #include void exit_sys(const char *msg); int main(void) { /* # OUTPUT # Hello World */ pid_t pid; if ((pid = fork()) == -1) exit_sys("fork"); if (pid != 0) exit(EXIT_SUCCESS); if (setsid() == -1) exit_sys("setsid"); printf("%s\n", "Hello World"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Buraya kadar bizler önce proses grubu, sonrasında da bir oturum oluşturmayı gördük. Pekiyi bizler iş bu oturumu bir terminal sürücüsüyle nasıl ilişkilendirebiliriz? Şöyleki; "open" fonksiyonu ile, "0_NOCTTY" bayrağını kullanmadan, bir terminal aygıt sürücüsü açmak. Eğer o anda hedef terminal aygıt sürücüsü başka bir oturuma bağlıysa, o bağlantı kopartılacaktır. * Örnek 1, #include #include #include #include int main(void) { /* # OUTPUT # Hello World */ pid_t pid; if ((pid = fork()) == -1) _exit(EXIT_FAILURE); if (pid != 0) _exit(EXIT_SUCCESS); if (setsid() == -1) _exit(EXIT_FAILURE); // Dosya betimleyicilerini kapatıyoruz. // Toplamda 1024 olduğu varsayılmıştır. for (int i = 0; i < 1024; ++i) close(i); if (open("/dev/tty3", O_RDONLY) == -1) _exit(EXIT_FAILURE); if (open("/dev/tty3", O_WRONLY) == -1) _exit(EXIT_FAILURE); if (dup(1) == -1) _exit(EXIT_FAILURE); // Artık aşağıdaki yazı "tty3" terminal ekranında // gözükecektir. printf("%s\n", "Hello World"); return 0; } Eğer bizler ön plan proses grubunun hangisi olduğunu merak ediyorsak veya bir proses grubunu ön plan proses grubu yapmak istiyorsak, sırasıyla "tcgetpgrp" ve "tcsetpgrp" isimli fonksiyonları çağırmalıyız. >> "tcgetpgrp" ve "tcsetpgrp" : Fonksiyonların prototipi aşağıdaki gibidir. #include pid_t tcgetpgrp(int fildes); int tcsetpgrp(int fildes, pid_t pgid_id); Fonksiyonlar terminal aygıt sürücüsüne ilişkin dosya betimleyiciler ile çalışmaktadır. Bu konudaki bir diğer husus da şudur; arka planda çalışmakta olan proses grupları, içinde bulundukları oturuma ilişkin terminalden okuma/yazma yapmak istediklerinde: -> İlk önce ilgili proses grubuna "SIGTTIN" sinyali gönderilir. Bu sinyalin varsayılan eylemi proseslerin durdurulması yönündedir. Yani o gruptaki bütün prosesler durdurulur. -> İlgili terminal programından "fg" komutu ile iş bu proses grubunu ön plan proses grubu olarak atanır. -> Daha sonra ilgili proses grubuna "SIGCONT" sinyali gönderilerek prosesler yeniden çalışır duruma getirilir. * Örnek 1, Aşağıdaki programı bir terminal üzerinden çalıştırınız. 10 saniye sonra, ikinci bir terminal vasıtasıyla "ps -l" komutunu çalıştırın. Çıktı ekranında aşağıdaki prosesin "status" bilgisi için "terminated" yazdığı görülecektir. #include #include int main(void) { printf("Sleep in 10 seconds...\n"); sleep(10); // 10 saniye boyunca bloke edilecek. printf("Sleep ended.\n"); int ch; ch = getchar(); putchar(ch); return 0; } * Örnek 2, Arka planda çalışmak olan bir proses okuma yapmak istediğinde "SIGTTIN" sinyali gönderilir ve proses durdurulur. Bu durumda bir "signal_handler" tanımlayarak, hata kodunun bir dosyaya yapılmasını mümkün kılabiliriz. Eğer yeniden başlatılamayan bir sistem fonksiyonu içerisinde bulunuyorsak, ilgili sistem fonksiyonu "EINTR" hata koduyla geri dönecektir. Aşağıdaki programı "&" ifadesiyle birliktte çalıştırmayı unutmayınız. #include #include #include #include #include void sig_handler(int sig); void exit_sys(const char* msg); FILE* g_f; int main(void) { /* # log.txt # SIGTTIN occured! getchar terminated by signal!... */ if ((g_f = fopen("log.txt", "w")) == NULL) exit_sys("fopen"); struct sigaction sa; sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if(sigaction(SIGTTIN, &sa, NULL) == -1) exit_sys("sigaction"); sleep(5); // 5 saniye bekledikten sonra ---> (1) // ---> (1) : "getchar" çağrısı sırasında bir sistem fonksiyonu // içerisinde olacağız. Bu nedenden dolayı ilgili sistem fonksiyonu // "EINTR" hata koduyla geri dönecektir. if (getchar() == EOF && errno == EINTR) fprintf(g_f, "getchar terminated by signal!...\n"); sleep(1); fclose(g_f); return 0; } void sig_handler(int sig) { fprintf(g_f, "SIGTTIN occured!\n"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Terminal, Oturum ve Proses Grupları hakkındaki diğer hususlarda şöyledir; -> Bir terminal ekranı kapatıldığında, yani ekranın sağ üst köşesindeki "x" tuşuna basarak, terminal aygıt sürücüsü, o terminalin ilişkin olduğu oturumda bulunan bütün proseslere "SIGHUP" sinyali gönderir. Bu sinyalin varsayılan davranışı ise prosesin sonlandırılması yönündedir. Eğer bu sinyali "ignore" olarak ele alırsak terminal üzerinden yapacağımız "read" işlemleri("read" fonksiyon çağrısı) "0" ile geri dönerken, "write" işlemleri ise ("write" fonksiyon çağrısı) "EIO" hata kodu ile başarısız olmaktadır. Burada ilgili prosesin ön ya da arka planda çalışıyor olması önemsizdir. Dolayısıyla biz bir "shell" ekranını kapatırsak, o ekran üzerinden çalıştırılmış bütün prosesler de sonlanacaktır. Ayrıca burada şöyle de bir nüans vardır; anımsanacağı üzere arka planda çalışan bir proses terminal üzerinden okuma yapmak istediğinde kendisine "SIGTTIN" sinyali gönderilir ki bu sinyalin varsayılan davranışı ilgili prosesi "stop" duruma getirmesidir. İşte terminal ekranı kapatıldığında bu tip "stop" durumdaki proseslere de yine "SIGHUP" sinyali gönderilir ancak "stop" durumdakiler sadece "SIGKILL" sinyalini işlediklerinden, gönderilen "SIGHUP" sinyali "pending" durumda bekletilir ve prosese iletilmez. İşte bu problemi gidermek için bazı kabuk programları "SIGHUP" sinyaline ek olarak "SIGCONT" sinyali de gönderilir. Böylece ilgili proses önce tekrar çalışır hale getirilir, sonra "SIGHUP" sinyalini alması sağlanır ve en sonunda başlar başlamaz sonlanması sağlanır. -> Bir terminalinden "exit" komutu ile çıkarsak ön plan proses gruplarına yine "SIGHUP" sinyali gönderilir. Arka plandakilerin de o terminal ile bağlantısı kopar. Örneğin, "bash" programında "exit" ile çıkmak istersek ilk önce arka plan proses gruplarına ilişkin uyarı mesajı verir. İkinci kez "exit" komutunun çağrılmasıyla o proses gruplarına da "SIGHUP" sinyali gönderir. Tabii "stop" durumdakiler için ayrıca "SIGCONT" sinyali de gönderilir. -> Bir terminal kapatıldığında ya da "exit" ile çıkıldığında, o terminalde çalışan programların çalışmasına devam etmesi isteniyorsa, bunun için "nohup" ve "disown" isimli komutlardan faydalanmak gerekir. "nohup" komutu çalıştırdığı programın "SIGHUP" sinyalini "ignore" eder, "disown" ise prosesi oturumdan koparır. Örneğin: $ nohup ./sample & Artık terminal kapatılsa bile bu prosesler çalışmaya devam edecektir. "nohup" komutu "stdout" dosyasını "nohup.out" isimli bir dosyaya yönlendirmektedir. -> Bir proses grubundaki her bir prosesin üst prosesi, o alt proses ile aynı proses grubundaysa ya da o alt proses ile aynı oturumda değilse, böyle proses gruplarına "Öksüz Proses Grupları (Orphan Process Groups)" denir. Bir diğer deyişle eğer bir proses grubundaki en az bir prosesin üst prosesi kendisiyle aynı oturumda ancak farklı bir proses grubundaysa, o proses grubu "Öksüz Proses Grupları(Orphan Process Groups)" DEĞİLDİR. Bu tanımdan yola çıkarak "shell" programının içinde bulunduğu proses grubu da aslında bir "Orphan Process Group" biçimindedir. Çünkü "shell" programını çalıştıran üst proses aslında farklı bir oturumdadır. Öte yandan bir proses grubu öksüz hale geldiğinde, eğer o proses grubu içerisinde "stop" durumda olan bir proses varsa, öksüz hale gelmiş olan bu proses grubundaki proseslere "SIGHUP" ve "SIGCONT" sinyalleri de gönderilir. Diğer yandan böylesi öksüz proses grupları terminalden okuma işlemi yapmak istediklerinde "read" fonksiyonu "EIO" değeriyle başarısız olurken, "write" fonksiyonları ise terminal aygıt sürücüsünün "-tostop" özelliği kapalıysa normal olarak terminale yazmakta ancak açıksa "EIO" değeriyle başarısız olur. Örneğin, üst prosesi sonlanmış bir alt proses ile terminalden "read" yapmaya çalışırsak başarısız olacağız. Ancak terminale "write" yapmaya çalışırsak, "-tostop" özelliğine göre, ya yazdıklarımız ekrana çıkacak ya da "EIO" değeriyle başarısız olacaktır. Bir diğer yandan öksüz proses gruplarına, arka planda çalışıp çalışmadığına bakılmaksızın, terminalden "read" yaptığında "SIGTTIN" sinyalinin, "write" yaptığında ise "SIGTTOU" sinyalinin gönderilmediğine, terminalin o anki "-tostop" ayarına bakılmaksızın, DİKKAT EDİNİZ. Son olarak kabuk programları, terminal bağlantısı koptuğunda ya da terminal penceresi kapatıldığında, öksüz proses gruplarına "SIGHUP" sinyali gönderilmeyecek, dolayısıyla bu öksüz proses grubundaki prosesler hayatlarına DEVAM EDECEKTİR. Bu konular hakkında ayrıntılı açıklamalar için "Advanced Programming in the UNIX Environment by W. Richard Stevens".