linux etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
linux etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

16 Mart 2020 Pazartesi

Linux Sinyalleri

Not : Bu konu ile ilgili olarak POSIX Sinyalleri başlıklı yazıya göz atabilirsiniz.

Pending Signal Nedir ?
Eğer bir signal handler çalıştırılıyorsa aynı sinyalden 1 veya daha fazla gelse bile signal handler bir nevi bloke olduğu için tekrar çalıştırılmaz.

Yeni sinyaller için pending bayrağı kaldırılır. Bayrak bir sayaç değildir, yalnızca pending signal olduğunu belirtir. Çalışan signal handler bitince, pending bayrağı havadaysa signal handler bir kere daha çağırılır.


SIGILL (4)
Açıklaması şöyle
SIGILL is the signal for an illegal instruction at the processor, which happens very rarely. The default action after receiving SIGILL is terminating the program and writing a core dump. The signal ID of SIGILL is 4. You encounter SIGILL very rarely, and I have absolutely no idea how to generate it in your code except via sudo kill -s 4 <pid>.
C ile bu sinyali oluşturmak için şöyle yaparız.
main=6;
veya şöyle yaparız.
const main=6;
SIGBUS (7)
Bu sinyalin SIGSEGV'den farkı biraz anlamını kaybetmiş. Bence şöyle aklımda tutmayı daha uygun buldum. Eğer bir sanal adres varsa ve bu sanal adres izin verilen adres alanı dışındaysa bu hatayı alırız. Aşağıdaki örnek bu durumu gösteriyor. Dokunulmaması gereken bir alana dokunduğu için SIGBUS hatası veriyor.
void test(void *a)
{
    asm("mov %0, %%rbp\n\t"
        "mov 0(%%rbp), %%rdx\n\t"
        : : "r"(a) : "rbp", "rdx");
}

int main()
{
    test((void *)0x706a2e3630332d69);
    return 0;
}

SIGPIPE (13)
Kapatılmış olan pipe veya sockete veri yazmaya çalışınca gönderilen sinyal. Karşıdaki uygulamanın haber vermeden kapandığını anlamamızı sağlar.

SIGTERM (15)
SIGTERM uygulamaya nazikçe artık kapanması gerektiğini söylemektir. Java - Process.destroy() source code for Linux sorusunda da anlatıldığı gibi Process.destroy() metodu çağırılınca kapatılmak istenen uygulamaya bu sinyali gönderir. Aslında Java dokümanında "The subprocess represented by this Process object is forcibly terminated." denmesine rağmen benim anladığım kadarıyla bu sinyal göz ardı edilebilir.

SIGCHLD (17)
Bir uygulama kapanırken kendisini yaratan uygulamaya SIGCHLD sinyalini de gönderir. Aşağıda bunu gösteren bir şekil var.

Buradaki soruya verilen cevapta fork/exec ile çalıştırılan bir uygulamayı beklemek için örnek kod verilmiş.
Gerçi bence bu sinyalin esas kullanım alanı yukarıdaki örnekteki gibi child uygulamanın bitmesini beklemek değil de burada söylendiği gibi arka planda çalıştırılan bir uygulamanın bittiğinin algılanması olmalı.
Burada ise aynı bir kabukta olduğu gibi arka planda çalışan uygulama bitince zombie olmaması için waitpid() metodu ile bitiş sonucunun alınması örneği var.
Eğer bir üst uygulama çalıştırdığı alt uygulamanın sonucunu beklemeden kapanırsa, alt uygulama Linux'ta init tarafından sahiplenilir.

Bir başka açıklama ise burada.

SIGSTOP ve SIGCONT (18-19)
SIGSTOP bir uygulamayı duraklatmak için SIGCONT ise devam ettirmek için kullanılır. Bu sinyaller durdurulamazlar.
The SIGSTOP signal instructs the operating system to stop a process for later resumption.
Örnek:
kill -19 `cat /var/run/mypidfile` ; kill -18 `cat /var/run/mypidfile`

SIGTTIN (21)
Controlling terminalden farklı bir gruptaki uygulama terminale okuma/yazma işlemi başlatırsa bu sinyali alır ve suspend edilir.
SIGSYS (31)
Bad system call anlamına gelir.
Sinyaller ve Stack
Burada yazdığına göre sinyaller uygulamanın stack'i içinde çalıştırılıyor. Ancak arzu edilirse ayrı bir stack tanımlamak ta mümkün. Aslında cümlede küçük bir yanlışlık var. Process'ler stack sahibi değildirler. Thread'ler bir stack'e sahiptir. Cümleyi thread stack diye okursak sorun kalmıyor.
By default, the signal handler is invoked on the normal process stack.
It is possible to arrange that the signal handler uses an alternate stack;
see sigaltstack(2) for a discussion of how to do this and when it might
be useful.

Sinyal Set Metodları
Sinyal setini sıfırlayan ve değer atayan sigemptyset,sigfillset metodlarıdır.

sigemptyset
Bu metod verilen seti sıfırlar. Aynı memset gibi çalışır.Metodun imzası aşağıdaki gibidir.
int sigemptyset(sigset_t *set);  
Örnek:
sigset_t ss;
sigemptyset(&ss);

sigfillset


sigaddset
sigdelset
sigismember

Sinyal Yakalamak için Kullanılabilecek Metodlar
signal
Sinyali default handler'ına atamak için örnek:
signal(SIGINT, SIG_DFL);
SIG_DFL default handling demek.

sigaction
sigaction yazısına taşıdım.

sa_handler alanının kullanımı

sa_handler alanına SIG_IGN değerini atayarak sinyalin dikkate alınmaması sağlanabilir. Örnek:
//set struct action to ignore signal
struct sigaction action;    
action.sa_handler=SIG_IGN;//handler set to ignore the signal
action.sa_flags=0;
//registeration 
sigaction(SIGINT,&action,0);

sa_handler alanına SIG_DFL değerini atayarak sinyalin default handler'ının çağırılması sağlanabilir.

Bu kullanım şeklinde signal handler metoduna sinyalin numarası gelir.
Örnek:

void h(int sig)
{
}

struct sigaction s;
s.sa_handler = h;
sigemptyset (&s.sa_mask);
s.sa_flags = SA_RESTART; //ve diğer seçenekler
int r = sigaction (SIGUSR1, &s, NULL);
sa_sigaction alanının kullanımı
sigaction yazısına taşıdım.

Örnek:

void handler (int sig, siginfo_t * info, void * a)
{
    //Kullanıcı tarafından gönderilen sinyal
    if (info->si_code == SI_USER) {
    }
}

struct sigaction s;
s.sa_sigsion = handler;
s.sa_flags = SA_SIGINFO;
sigemptyset (&s.sa_mask);
sigaction (SIGRTMIN, &s, NULL);

sa_mask alanının kullanımı
sigaction yazısına taşıdım.

sig_atomic_t
Aşağıda shared bir sig_atomic_t kodu örneği var.
volatile sig_atomic_t interrupted=false; 
...
void signal_handler(int s)
{
   // ...
   interrupted=true;
}
...
while (!converged && !interrupted)
{
     // Perform computations
}
// Save file properly

Sinyalleri Bloke Etmek
SIG_SETMASK ilk listedeki sinyalleri bloke eder, ikinci listedekileri ise bloke edilmekten çıkarır. Eğer ikinci liste NULL verilirse önceden bloke edilmiş sinyalleri temizler ve sadece ilk listedekileri bloke eder.
Örnek:
sigset_t allSigs;
sigfillset(&allSigs);
sigprocmask(SIG_SETMASK, &allSigs, NULL);



22 Temmuz 2019 Pazartesi

fork() sistem çağrısı

Not : Linux Kernel Development okunması gereken bir kitap.

Giriş
Bir uygulama code segment,data segment,stack segment ve heap segment alanlarından oluşur. fork() sistem çağrısı ile yeni uygulama için code segment hariç diğer tüm segmentlerin kopyası alınır.

Aşağıda bir uygulamanın kopyası alınırken hangi konulara dikkat edilmesi gerektiğine dair notlarım var.

fork kodlaması
fork() sistem çağrısı yapıldıktan sonra iki farklı uygulama aynı kodu çalıştırmaya devam ederler. parent uygulama ve child uygulama'nın nereden devam ettiği aşağıdaki şekilde gösteriliyor. parent uygulama için fork() sistem çağrısı >0 sonucunu alır. Child uygulama içinse 0 sonucunu alır
enter image description here

fork Process ID yerine BaşkaBir Şey Dönseydi Diyenler
Açıklaması şöyle.
Of course, the Right Thing would be to have fork() return a file descriptor, not a process ID.
Soru
Meraklısı şu soruyu çözebilir. Toplamda kaç tane process yaratılır ?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
  int i;
  for (i = 0; i < 4; i++){
    fork();
  }
  return 0;
}
fork ve fail durumu
Açıklaması şöyle.
On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.
fork() nihayetinde bir sistem çağrısı olduğu için başarısız olabilir. Kodlarken bu durumu göz önünde bulundurmak lazım. Örnek'te fork() sistem çağrısının < 0 değeri dönebileceği de göz önüne alınmış. Çağrı başarısız olursa errno hata kodu atanır. Hata koduna bakara sebep anlaşılabilir. Bazı genel sebepler yetersiz bellek olması (ENOMEM), kullanıcının process üst sınıra erişmesi (EAGAIN) gibi şeyler olabilir.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
  int pid;
  pid=fork();

  if(pid<0)/* why is this here? */
  {
    fprintf(stderr, "Fork failed");
    exit(-1);
  }
  else if (pid == 0)
  {
    printf("Printed from the child process\n");
  }
  else
  {
    printf("Printed from the parent process\n");
    wait(pid);
  }
}
fork ve threadler
fork ve thread yazısına taşıdım.

fork ve heap

fork() çağrısından sonra her uygulama aynı değerlere sahip, farklı sayfalara sahip olurlar.

Not 1: Gerçekte fork() işlemini optimize etmek için bir çok işletim sistemi Copy-on-write mekanizmasını kullanmaktadır. Ancak ben bu yazıda bu mekanizmayı göz ardı ettim.

Not 2 : Copy-on-write mekanizaması sayesinde vfork() ve fork() arasında da belirgin bir fark kalmadı diye anlıyorum. what is the difference between fork() and vfork()? başlıklı yazı da bunu teyit ediyor.

Aşağıdaki şekli buradan aldım. Görüldüğü gibi code segment aynı yere işaret ederken, heap sayfaları farklı yerlere işaret ediyorlar.

fork ve heap,stack ve data segmentteki program değişkenleri
Program içinde kullanılan değişkenler fork() çağrısından sonra farklı sayfaları kullansalar bile halen aynı sanal adresleri kullanmaya devam ediyorlar. Böylece değişkene pointer varsa bile bir problem çıkmıyor. Örneğin aşağıdaki pointer kullanan kod parçası fork() yapıldıktan sonra bile sorunsuz çalışıyor.
int *p = &a;
a = a - 5;
printf("%d", *p)
Bir başka örnek şöyle
int i=0;
pid_t pid;

puts("Hello, World!");
puts("");

pid = fork();

if(pid)

    i=42;

printf("%p\n", &i);
printf("%d\n", i);
puts("");
Çıktıda değişken adresleri aynı iken değerlerinin farklı olduğunu görürüz.
Hello, World!

0x7fffc2490278
42

0x7fffc2490278
0

fork ve session
fork ile yaratılan yeni uygulama, kendini yaratan uygulama ile aynı terminale bağlıdır. Eğer yeni uygulamayı daemon haline getirmek istersek burada yazıldığı gibi  fork() ve setsid() metodlarını çağırmak gerekir.

fork ve orphan (yetim) kalma sonucu session değişmesi
Eğer ana uygulama, fork ile yaratılan yeni uygulamanın bitmesini beklemeden çıkarsa, yeni uygulama oprhan (yetim) kaldığı için "init" uygulamasının çocuğu olur. Yani yeni uygulama setsit() metodunu çağırmasa bile, session bilgisi değişir. Aşağıdaki şekilde "init"'in nasıl herşeyin atası olduğu görülebilir.

Şekilden de görüldüğü gibi init uygulamasının PID'si 1'dir. Buradaki örnekte bir child uygulamanın yetim kalmadan önce ve sonra getpid() metodunu çağırarak farklı PID numaraları aldığı görülebilir.

fork ve PID
Child uygulama, yeni bir PID (Process ID) numarası alır. Process ID değerini almak için getpid(), parent process id değerini almak için de getppid() metodları kullanılabilir.



Distinction between processes and threads in Linux sorusuna verilen şu cevaba dikkat etmek lazım.
What's considered a PID in the POSIX sense of "process", on the other hand, is called a "thread group ID" or "TGID" in the kernel.
Dolayısıyla PID ve TGID aslında aynı şeyler.


fork ve wait
Creating child processes/killing processes in C/UNIX sorusundan aldığım örnekte parent uygulamanın child'ı nasıl beklediğini görmek mümkün.

WIFEXITED
Bu bir macro ve wait(&child) çağrısından sonra, child uygulamanın nasıl çıktığını anlamamıza yarıyor. Eğer child normal çıkmışsa true dönüyor.

WIFSIGNALED
Bu bir macro ve wait(&child) çağrısından sonra, child uygulamanın nasıl çıktığını anlamamıza yarıyor. Eğer child uygulama handle etmediği bir signal yüzünden çıkmışsa true dönüyor.
wait(&child);
if(WIFSIGNALLED(status)){}//exited due to signal not handled

fork ve wait ile beklememe

Burada "double fork" yaparak, ana uygulamanın child uygulamanın sonucunu beklememesi gösterilmiş. Örnek:
Yukarıdaki yetim (orphan) kalma konusunda da açıklandığı gibi, annesi ölen bir uygulama, init'e bağlanır. init ise bir şekilde kendine bağlanan uygulamalar için wait() metodunu çağırıyor.

fork ve waitpid
waitpid yazısına taşıdım.

fork ve file descriptor
fork() çağrısından sonra her uygulama aynı file descriptorlarına sahip olurlar. Aynı dosyaya yazarlarsa birbirlerini etkileyebilirler. Bu konuyu Linux Dosya Sistemi başlıklı yazıdaki "Open File Table" veri yapısını anlatan bölümde görebilirsiniz. Eğer child uygulamanın file descriptorlarını görmesini istemiyorsak How to fork process without inheriting handles? sorusundaki çözüme bakabiliriz.
Burada da benzer bir açıklama var.


fork ve timer'lar
how to see if any timer thread is running or not başlıklı soruda timer'ların fork() çağrısından sonra yeni yaratılan uygulamaya geçmediği yazıyor.

fork ve exec ailesine ait sistem çağrıları
exec ailesine ait sistem çağrıları için exec ailesi başlıklı yazıya bakabilirsiniz.

C++/C ile gelen kolaylıklar

Aşağıda anlatılan her iki yöntemde de çalıştırılmak istenen program yeni bir shell içinde çalıştırılıyor.

1. std::system()
std::system yazısına taşıdım.

2. popen() 
popen metodu yazısına taşıdım.

3. posix_spawn
Bu metodu hiç kullanmadım. Burada bir kullanım örneği var. fork() + exec () çağrısı yapmak yerine tek bir çağrı ile bir uygulamayı başlatmayı mümkün kılıyor.

dup ve dup2
fork() ile sürekli kullanılan dup ve dup2 sistem çağrılarına da kısaca bakmak lazım.

dup2(f1,f2) ile f2 akımı, f1 akımına yönlendirilebiliyor. Daha sonra f1 kapatılıyor. f1'in kapatılması şart değil ancak, sistemde gereksiz yere kaynak ayrılmış olur, dolayısıyla kapatmak daha iyi. Aşağıda C kütüphanesindeki dup2 açıklaması var.

Örnek:

Örneğin error stream'i output stream'e yönlendirmek için aşağıdaki komutu kullanırız.
2>&1
Shell dup veya dup2 çağrılarından birini kullanarak error stream'i output stream'e yönlendirir ve her şey output stream'e yazılır. Output stream ise tmp1 dosyasına yönlendirildiği için tüm çıktı dosyaya yazılmış olur.
 ls existing-file non-existing-file > tmp1  2>&1
dup2'nin sınırı
Buradaki soruda da açıklandığı gibi dup2'ye ikinci parametre olarak verilebilecek fd akımınının bir üst sınırı bulunuyor. Bu üst sınır getrlimit() metodunun döndürdüğü struct rlimit'in rlim_cur alanından alınablir.


getpgrp
getpgrp() işin içine birden çok uygulama girince, bazen gereken metodlardan. Uygulamanın grubunu döndürür.
pid_t getpgid (pid_t pid );
Eğer parametre olarak 0 geçersek, kendi grup numaramızı alırız.
Örnek:


setpgid
setpgid ile uygulamanın grubu atanır.
pid_t setpgid (pid_t pid , pid_t pgrpId )
Aşağıdaki şekilde farklı gruplar görülebilir.

Eğer çağrının ilk parametresini 0 geçersek, kendi grup numaramızı atarız. Kendimize ait yeni bir grup başlatmak istersek, ikinci parametreye yine kendi program id numaramızı geçebiliriz. Örnek:
process->start(getenv("SHELL"), QStringList() << "-i");
setpgid(process->pid(),0);
Controlling terminal'in grubundan farklı bir gruptaki uygulama, terminale okuma/yazma yapmaya kalkarsa SIGTTIN sinyali alır ve suspend edilir.

Çok emin olmamakla beraber controlling terminal ile aynı gruptaki tüm uygulamalar Ctrl+C'ye basılınca SIGINT sinyalini alırlar.