Do spisu tresci tematu 1

1.5.2 Sygnaly - struktury i funkcje,

Stan uspienia procesu

Spis tresci


Wprowadzenie

Sygnaly sluza do zawiadamiania procesow o wystapieniu pewnych sytuacji wyjatkowych. Jedyna cecha sygnalu odebranego przez proces jest jego numer. Niemozliwe jest stwierdzenie nadawcy sygnalu, liczby wyslanych sygnalow, ani zadnej dodatkowej informacji na jego temat. Stad nazywanie sygnalow mechanizmem komunikacji jest lekka przesada. Za to doskonale nadaje sie on do informowania procesu o sytuacji wyjatkowej (patrz Sygnaly-opis).


Dzialanie

Proces w momencie otrzymania sygnalu moze znajdowac sie w roznych stanach, przez co roznorodne sa takze jego reakcje na sygnal. Jesli sygnal, ktory jest wysylany, jest zablokowany, to zdarzenia, ktore wymieniam dalej, sa wstrzymywane az do wykonania przez proces odblokowania sygnalu. Gdy proces jest w stanie TASK_INSTERRUPTIBLE jest on budzony z kolejki oczekiwania i jego stan jest ustawiany na TASK_RUNNING (patrz Procesy-stany). W przypadku gdy proces znajduje sie w trybie uzytkownika i jest gotowy do wykonania lub wykonywany TASK_RUNNING (patrz Procesy-stany) to spodziewane, sekwencyjne wykonywanie procesu jest przerywane i wykonywana jest akcja zwiazana z tym sygnalem (patrz Sygnaly-spis). Po zakonczeniu wykonywania akcji zwiazanej z sygnalem proces powraca do miejsca, w ktorym jego dzialanie zostalo przerwane. Gdy proces jest w stanie TASK_RUNNING lecz znajduje sie w trybie jadra (wykonuje jakas funkcje systemowa) to akcja zwiazana z sygnalem jest wykonywana po powrocie z funkcji systemowej a przed wznowieniem programu. Struktury i funkcje, ktore sa tutaj opisane realizuja podana specyfikacje.


Typy i struktury danych


Wprowadzenie

Wiekszosc typow i struktur danych zwiazanych z sygnalami jest zdefiniowana w pliku /asm/signal.h. Sa tam takze zdefiniowane stale zwiazane z numerami sygnalow (patrz Sygnaly-spis). Procz tego znajdziemy tam stale zwiazane ze zdefiniowanymi typami i strukturami danych.


Typ sigset_t

Definicja typu sigset_t z pliku asm/signal.h:

typedef unsigned long sigset_t; /* co najmniej 32 bity */

Typ sigset_t sluzy do trzymania zbioru sygnalow (kazdemu sygnalowi odpowiada dokladnie jeden bit). Jak widac jest to liczba co najmniej 32-bitowa, stad stala ilosci sygnalow definiowana jest w nastepujacy sposob:

#define _NSIG         32
#define NSIG  _NSIG

Typ __sighandler_t

Typ __sighandler_t jest to typ funkcji obslugi przerwania zdefiniowany w pliku asm/signal.h nastepujaco:

typedef void (*__sighandler_t)(int);

Zdefiniowane sa takze stale, standardowe wartosci zmiennych tego typu:

#define SIG_DFL ((__sighandler_t)0)  /* domyslna obsluga sygnalu  */
#define SIG_IGN ((__sighandler_t)1)  /* ignorowanie sygnalu */
#define SIG_ERR ((__sighandler_t)-1) /* blad (przy powrocie z funkcji signal) */

Struktura sigaction

Oto dokladna definicja struktury sigaction z pliku asm/signals.h:

/* jedna struktura sigaction dla kazdego sygnalu  */
struct sigacion {
  _sighandler_t *sa_handler;   /* funkcja obslugi sygnalu */
  sigset_t       sa_mask;
  unsigned long  sa_flags;
  void         (*sa_restorer)(void);
};
Dodatkowe wyjasnienia: 
sa_handler
Funkcja obslugi sygnalu (patrz typ _sighandler_t).
sa_mask
Jest to maska sygnalow ktore maja byc zablokowane zaraz po wolaniu funkcji obslugi a przed rozpoczeciem jej wykonywania (patrz flaga SA_NOMASK)
sa_flags
Jest to pole flag decydujacych o sposobie obslugi przerwania. Chwilowo dostepne sa nastepujace flagi (stale zdefiniowane w pliku asm/signals.h):
SA_NOCLDSTOP
decyduje czy zawiadamiac rodzica procesu o jego zatrzymaniu (patrz funkcja notifyparent)
SA_SHIRQ
dla przerwan dzielonych przez PCI i EISA
SA_STACK
nie jest jeszcze zaimplementowane, ale w przyszlosci pozwoli na uzywanie oddzielnego stosu do funkcji obslugi sygnalu poprzez uzycie pola sa_restorer (obecnie nieuzywanego) w strukturze sigaction jako wskaznika do stosu
SA_RESTART
flaga do otrzymania sygnalow restartujacych (w przypadku przerwania dzialania funkcji systemowej przez sygnal z ustawiona flaga funkcja ta jest ponawiana)(ta flaga byla kiedys domyslna)
SA_INTERRUPT
uzywana jedynie wewnetrznie przez system (obsluga urzadzen zewenetrznych) w przypadku handlerow przerwan sprzetowych
SA_NOMASK
po wywolaniu handlera nastepne sygnaly nie sa maskowane (ani ten sygnal ani maska sa_mask)
SA_ONESHOT
po wywolaniu funkcji obslugi sygnalu lecz przed rozpoczeciem jego wykonywania pole sa_handler struktury sigaction jest ustawiane na 0 (obsluga domyslna)
SA_PROBE
to samo co SA_ONESHOT
SA_SAMPLE_RANDOM
to samo co SA_RESTART
sa_restorer
Obecnie przestarzale, nieuzywane pole, ale kiedys moze (patrz flaga SA_STACK).


Pola w strukturze procesu

Wprowadzenie

Struktura procesu zostala dokladnie opisana w proces-struktura procesu. Jednak wiele pol w strukturze procesu dotyczy sygnalow dlatego zostana one tutaj wymienione z krotkim opisem. Struktura procesu jest zdefiniowana w pliku include/linux/shed.h.

Pole signal

Pole to jest zadeklarowane:

unsigned long signal;

Maska sygnalow otrzymanych przez proces, ale jeszcze nie obsluzonych. Kazdemu sygnalowi odpowiada jeden bit liczby 32-bitowej. Dlatego niemozliwe jest stwierdzenie ile sygnalow danego rodzaju otrzymal proces. Pole to pokazuje czy proces otrzymal co najmniej jeden sygnal o danym numerze. Z opisu wynika ze proces nie zawsze reaguje natychmiast na przyjscie sygnalu. Ma to miejsce na przyklad gdy dany sygnal jest blokowany (patrz sygnaly-dzialanie).

Pole blocked

Pole to jest zadeklarowane:

unsigned long blocked;

Maska sygnalow aktualnie blokowanych przez proces (ich obsluga jest odwlekana). Zapalony bit odpowiadajacy danemu sygnalowi oznacza ze ow sygnal jest blokowany. Mozliwe jest blokowanie wszystkich sygnalow procz SIGKILL i SIGSTOP.

Pole sig

Pole to jest zadeklarowane:

struct signal_struct *sig;

gdzie struktura signal_struct jest zadeklarowana w pliku include/linux/shed.h nastepujaco:

struct signal_struct {
  int count;   /* licznik dowiazan do struktury */
  struct sigaction action[32];  /* akcje zwiazane z poszczegolnymi sygnalami */
};

Pole sig zawiera informacje o akcjach podejmowanych przez proces przy otrzymaniu sygnalu. Jak widac w strukturze signal_struct jest licznik dowiazan do tej struktury-count (handlery sygnalow moga byc wspoldzielone przez kilka procesow a wlasciwie watkow - patrz funkcja-clone). Procz tego struktura zawiera tablice, w ktorej pojedynczy element opisuje akcje zwiazana z sygnalem (patrz struktura-sigaction).

Pole exit_signal

Pole to jest zadeklarowane:

int exit_signal;

Pole to zawiera numer sygnalu, ktory ma byc wyslany do rodzica procesu w momencie jego smierci (standardowo wynosi SIGCHLD).


Funkcje wewnetrzne jadra i ich implementacja


Wprowadzenie

Istnieja cztery glowne funkcje systemowe zwiazane z obsluga sygnalow, ktore nie sa dostepne dla zwyklych procesow uzytkownika, ale za to sa uzywane czesto w roznych miejscach w jadrze systemu.


Funkcja send_sig()

Funkcja ta jest zdefiniowana w /kernel/exit.c. Sluzy do wyslania sygnalu przez aktualny proces.

DEFINICJA: int send_sig( unsigned long sig, struct task_struct * p, int priv )
    WYNIK: 0        w przypadku sukcesu, lub gdy proces docelowy jest zombie lub sygnal 0
           -EINVAL  gdy p==0 lub sig>32
           -EPERM   gdy nie ma przywilejow (priv==0) i
                        to nie jest sygnal SIGCONT lub nie jest z tej samej sesji co p i
                        p->suid i p->uid sa rozne od current->uid i current->euid
                        (maja roznych wlascicieli)  i
                        proces wysylajacy nie ma praw zarzadcy systemu 

Pierwszym argumentem funkcji jest numer sygnalu, ktory ma byc wyslany (musi sie zawierac w przedziale 1..32). Argument p jest wskaznikiem do struktury procesu procesu, do ktorego ma byc wyslany sygnal. Gdy argument trzeci jest niezerowy wtedy niezaleznie od praw aktualnego procesu (proces wysylajacy sygnal) nie bedzie mu zabronione wyslanie sygnalu.

Funkcja rozpoczyna swoje dzialanie od sprawdzenia poprawnosci argumentow i czy aktualny proces (current) moze wyslac sygnal do procesu p. Z definicji funkcji wynika iz wyslanie sygnalu jest dozwolone (funkcja nie zwraca -EPERM) gdy jest spelniony jeden z nastepujacych warunkow:

Nastepnie sprawdzane jest czy sygnal sig to jeden z sygnalow budzacych (SIGKILL lub SIGCONT). Jesli tak i proces jest zatrzymany (TASK_STOPPED)(patrz proces-stany) to proces jest budzony. Procz tego ustawiany jest kod wyjscia procesu na 0 (exit_code) i zerowane sa w polu signal procesu bity odpowiadajace sygnalom ktore moglyby zatrzymac proces (sa to: SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU)(patrz sygnaly-spis).

Jesli natomiast sygnal sig to jeden z sygnalow, ktore moglyby spowodowac zatrzymanie procesu (sa to: SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU)(patrz sygnaly-spis) to w polu signal procesu p zerowany jest bit odpowiadajacy za sygnal budzacy SIGCONT (patrz sygnaly-spis).

Jesli sygnal sig nie jest blokowany ani proces p nie jest sledzony (tj. gdy sygnal moze byc obsluzony natychmiast) to: gdy obsluga sygnalu to ignorowanie(SIG_IGN)(patrz typ-sighandler) i sygnal to SIGCHLD (patrz sygnaly-spis) nastepuje powrot z funkcji z rezultatem 0. Podobnie jest w przypadku gdy obsluga sygnalu jest domyslna (SIG_DFL)(patrz typ-sighandler) a sygnal to jeden z: SIGCONT,SIGCHLD,SIGWINCH,SIGURG.

Nastepnie gdy jeszcze nie zakonczono funkcji w polu signal procesu docelowego p ustawiany jest bit odpowiadajacy za sygnal wysylany sig. Dodatkowo gdy proces jest w stanie TASK_INTERRUPTIBLE (patrz proces-stany) i sygnal nie jest blokowany, proces p jest budzony. Funkcja zwraca 0.

Funkcja force_sig()

- sluzy do bezwarunkowego wysylania sygnalu.

DEFINICJA: void force_sig( unsigned long sig, struct task_struct *p )
    WYNIK: brak

Pierwszym argumentem funkcji jest numer wysylanego sygnalu. p to wskaznik do struktury procesu procesu docelowego.

Gdy proces docelowy jeszcze istnieje, w polu signal (patrz proces-struktura) jego struktury jest ustawiany bit odpowiadajacy za sygnal sig, a w polu blocked ten sam bit jest zerowany (to zapewni, ze sygnal zostanie obsluzony od razu po pobraniu procesu do wykonania). Gdy obsluga sygnalu jest ustawiona na ignorowanie (SIG_IGN) to przestawiana jest na obsluge domyslna (SIG_DFL)(patrz typ-sighandler). W przypadku gdy proces p jest w stanie TASK_INTERRUPTIBLE jest on budzony.


Funkcja do_signal()

- sluzy do obslugi komunikatow ktore przyszly do procesu aktualnego (current), ale jeszcze nie zostaly obsluzone (ustawione bity na polu signal-patrz pole-signal). Funkcja ta jest wolana jedynie w momencie wyjscia z funkcji systemowej a takze w funkcji systemowej sigsuspend(). Wynika z tego, ze sygnaly sa obslugiwane jedynie w momencie wyjscia z funkcji systemowej.

DEFINICJA: int do_signal( unsigned long oldmask, struct pt_regs *regs )
    WYNIK: 0 w przypadku gdy nie obsluzono zadnego sygnalu
           1 gdy obsluzono jeden sygnal

Pierwszym argumentem jest stara maska sygnalow (sprzed wywolania handlera). Drugi argument to zachowane na stosie wartosci rejestrow procesora sprzed wywolania funkcji systemowej.

Dzialanie funkcji mozna przedstawic za pomoca nastepujacego pseudoalgorytmu:

{
   while (istnieje jeszcze nie obsluzony sygnal nie blokowany)
   {
     signr=numer takiego sygnalu i bit sygnalu zerowany;
     sa=struktura sigaction odpowiadajaca za sygnal signr;
     if ( ( proces jest sledzony )&&( signr to nie SIGKILL ) )
      {
         stan procesu ustalany na TASK_STOPPED , kod wyjscia /*exit_code*/ na signr;
         notify_parent(current);
         shedule();
         if(!(signr=aktualny kod wyjscia procesu /*exit_code*/))
           continue;
         kod wyjscia procesu /* exit code */ = 0;
         if( signr = SIGSTOP )
           continue;
         if( sygnal signr jest blokowany )
           ustawiany jest bit tego sygnalu /*w polu signal procesu*/ , continue;
         sa=struktura sigaction odpowiadajaca za sygnal signr;
      }
     if ( w sa handler jest ustawiony na ignorowanie )
     {
        if ( signr to nie SIGCHLD )
          continue;
        while ( sys_waitpid(-1,NULL,WNOHANG) > 0) ;
         /* wszystkie dzieci zombie usuwane */
        continue;
     }
     if ( w sa handler syganalu ustawiony na obsluge domyslna )
     {
        if ( czy to proces init )
          continue;
        switch (signr) {
          case SIGCONT: case SIGCHLD: case SIGWINCH:
            continue;
          case SIGTSTP: case SIGTTIN: case SIGTTOU:
            if ( grupa procesu jest osierocona /* is_orphaned_pgrp */ )
              continue;
          case SIGSTOP:
            if ( proces jest sledzony )
              continue;
            stan procesu ustawiany na TASK_STOPPED, kod wyjscia na signr;
            if ( czy obsluga sygnalu SIGCHLD u rodzica
                 ma ustawione zawiadamianie o zatrzymaniu dziecka )
              notify_parent();
            schedule();
            continue;
          case SIGQUIT: case SIGILL: case SIGTRAP:
          case SIGABRT: case SIGFPE: case SIGSEGV:
            zrzut na dysk obrazu procesu /*CORE DUMP*/;
          default:
            signal|=bit sygnalu numer ( signr & 0x7f );
            w strukturze procesu ustawiona flaga zabicia przez sygnal;
            do_exit(signr);
        } /* case */
     } /* if */
    obsluga sygnalu signr handlerem sa z maska oldmask i rejestrami regs;
    return 1;
   }
   return 0;
}

Funkcja notify_parent()

- wolana gdy nalezy poinformowac rodzica jakiegos procesu o jego zatrzymaniu lub zakonczeniu.

DEFINICJA: void notify_parent( struct task_struct *tsk )
    WYNIK: brak

Argumentem funkcji jest struktura procesu, ktory zmienil stan i chce poinformowac rodzica.

Funkcja rozpoczyna swoje dzialanie od sprawdzania czy rodzicem procesu jest Init. Jesli tak, to pole exit_signal procesu tsk jest ustawiane na SIGCHLD.

Po tym do procesu rodzica wysylany jest sygnal exit_signal (funkcja send_sig z przywilejami ustawionymi). Nastepnie kolejka czekajacych na smierc tego procesu jest budzona.


Funkcje systemowe

Wprowadzenie

Funkcje systemowe opisane ponizej wystepuja w zrodlach pod podanymi nazwami z dodanymi przedrostkami sys_. Wiele z nich korzysta z funkcji wewnetrznych jadra podanych powyzej.

Funkcja kill()

- sluzy do wysylania sygnalu.

DEFINICJA: int kill( int pid, int sig )
    WYNIK: 0 w przypadku sukcesu
          -1 gdy nastapil blad; wtedy errno przybiera nastepujace wartosci:
                                EINVAL  bledny parametr (nr sygnalu)
                                ESRCH   nie znaleziony proces do zabicia
                                EPERM   peoces nie posiada praw do wykonania tej funkcji

Drugim argumentem funkcji jest numer sygnalu, ktory ma byc wyslany do procesu lub grupy procesow opisanych przez pierwszy argument. W zaleznosci od wartosci argumentu pid zmienia sie adresat:

Niezaleznie od rodzaju parametrow funkcja korzysta z funkcji jadra send_sig z parametrem perm rownym 0. Stad blad EPERM wystapi tylko wtedy, gdy wystapilby w przypadku wywolania funkcji send_sig.

Funkcja sigsuspend()

- sluzy do ustawiania maski sygnalow blokowanych i oczekiwania na nadejscie ktoregos z sygnalow nieblokowanych.

DEFINICJA: int sigsuspend( unsigned long set )
    WYNIK: -1 z errno ustawionym na EINTR

Argumentem funkcji jest maska blokowania sygnalow.

Dzialanie funkcji:

{
   mask=aktualna maska blokawania sygnalow;
   ustaw maske blokowania sygnalow na set;
   while (1)
    {
      ustaw stan procesu na TASK_INTERRUPTIBLE;
      schedule();
      if ( do_signal(mask,...) )
        return -1(EINTR);
    }
}

Funkcja sigprocmask()

- sluzy do ustawiania maski blokowania sygnalow.

DEFINICJA: int sigprocmask( int how, sigset_t *set , sigset_t *oset )
    WYNIK: 0  w przypadku sukcesu
          -1  gdy wystapil blad; ustawia ERRNO na:
                                 EINVAL gdy how mialo wartosc nieznanej opcji;

Pierwszym argumentem funkcji jest numer sposobu ustawiania maski. Moze przybrac wartosc jednej ze stalych zdefiniowanych w asm/signal.h:

#define SIG_BLOCK   0 /* blokowanie sygnalow */
#define SIG_UNBLOCK 1 /* odblokowywanie sygnalow */
#define SIG_SETMASK 2 /* ustawianie nowej maski */

Argument set jest wskaznikiem na nowa maske blokowania sygnalow. Gdy wskaznik oset jest niezerowy to jest tam wpisywana stara maska blokowania syganlow sprzed wywolania tej funkcji.

Funkcja sgetmask()

- pobiera maske blokowania sygnalow.

DEFINICJA: int sig( )
    WYNIK: maska blokowania sygnalow

Funkcja ssetmask()

- ustawia maske blokowania sygnalow.

DEFINICJA: int ssetmask( int newmask )
    WYNIK: poprzednia maska blokowania sygnalow

Funkcja sigpending()

pobiera zbior wstrzymywanych (przez maske blokowania) sygnalow .

DEFINICJA: int sigpending( sigset_t *set )
    WYNIK: 0   w przypadku sukcesu
          -1   gdy nastapil blad zapisu pod adres set

Zbior blokowanych sygnalow jest wstawiany pod set.

Funkcja signal()

ustawia nowa procedure obslugi sygnalu.

DEFINICJA: _sighandler_t signal( int signum , _sighandler_t handler )
    WYNIK: poprzedni handler obslugi tego sygnalu w przypadku sukcesu
           SIG_ERR   gdy nastapil blad

Argument signum oznacza numer sygnalu, a handler to nowa procedura obslugi sygnalu (patrz typ _sighandler_t).

Funkcja sigaction()

ustawia nowa akcje zwiazana z danym sygnalem.

DEFINICJA: int sigaction( int signum , const struct sigaction *action ,
                                             struct sigaction *oldaction)
    WYNIK: 0   w przypadku sukcesu
          -1   gdy nastapil blad (errno=EINVAL)

Ustawia nowa akcje action zwiazana z sygnalem signum. Stara akcja zwiazana z tym sygnalem jest wpisywana na oldaction. Oba parametry sa typu struct sigaction.

Funkcja sigreturn()

jest wywolywana przy powrocie z procedury obslugi handlera sygnalu.

DEFINICJA: int sigreturn( )
    WYNIK: brak

Funkcja powinna byc wywolywana przy powrocie z kazdej procedury obslugi sygnalu.


Stan uspienia procesu


Wprowadzenie

Biorac pod uwage definicje stanu uspienia z ksiazki Bacha (patrz Bibliografia) najblizszy temu opisowi wydaje sie stan TASK_INTERRUPTIBLE (patrz proces-stany). Jest to taki stan zatrzymania procesu, ktory moze byc przerwany przez sygnal. Procesy przebywajace w tym stanie sa w wiekszosci przypadkow zatrzymane na strukturze kolejki oczekiwania (struct wait_queue). Do usypiania i budzenia calych kolejek oczekiwania sluza funkcje opisane ponizej.


Struktura wait_queue

Definicja typu sigset_t z pliku linux/wait.h:

struct wait_queue {
 struct task_struct * task;
 struct wait_queue  * next;
};

Na pojedynczej strukturze tego typu sa wstrzymywane wszystkie procesy, ktore maja byc obudzone w przypadku wystapienia zdarzenia z pewnej grupy zdarzen.

Funkcja wake_up()

Funkcja wake_up jest zaimplementowana w pliku linux/kernel/sched.c.

DEFINICJA: void wake_up( struct wait_queue **q )
    WYNIK: brak

Funkcja budzi wszystkie procesy znajdujace sie na kolejce oczekiwania a bedace w stanie TASK_INTERRUPTIBLE lub TASK_UNINTERRUPTIBLE. Budzenie procesu polega na ustawieniu stanu procesu na TASK_RUNNING i wlozeniu go do kolejki procesow gotowych. O usuniecie z kolejki oczekiwania zadba sam proces obudzony.

Funkcja wake_up_interruptible()

Funkcja wake_up_interruptible jest zaimplementowana w pliku linux/kernel/sched.c.

DEFINICJA: void wake_up_interruptible( struct wait_queue **q )
    WYNIK: brak

Funkcja budzi wszystkie procesy znajdujace sie na kolejce oczekiwania a bedace w stanie TASK_INTERRUPTIBLE. Budzenie procesu polega na ustawieniu stanu procesu na TASK_RUNNING i wlozeniu go do kolejki procesow gotowych. O usuniecie z kolejki oczekiwania zadba sam proces obudzony (czyli jak wyzej).

Funkcja interruptible_sleep_on()

Funkcja interruptible_sleep_on jest zaimplementowana w pliku linux/kernel/sched.c.

DEFINICJA: void interruptible_sleep_on( struct wait_queue **q )
    WYNIK: brak

Funkcja usypia proces aktualny na kolejce q ustawiajac jego stan na TASK_INTERRUPTIBLE.

Funkcja rozpoczyna dzialanie od dodania procesu aktualnego do kolejki q i ustawieniu stanu procesu na TASK_INTERRUPTIBLE. Nastepnie wywolywana jest funkcja schedule() zmiany procesu aktualnego. Po obudzeniu procesu jest on usuwany z kolejki q.

Funkcja sleep_on()

Funkcja sleep_on jest zaimplementowana w pliku linux/kernel/sched.c.

DEFINICJA: void sleep_on( struct wait_queue **q )
    WYNIK: brak

Funkcja usypia proces aktualny na kolejce q ustawiajac jego stan na TASK_UNINTERRUPTIBLE (patrz wyzej).


Bibliografia

  1. Pliki zrodlowe Linuxa 2.0.20:
  2. W. Richard Stevens: Programowanie zastosowan sieciowych w systemie UNIX
  3. Maurice J. Bach: Budowa systemu operacyjnego UNIX

Autor: Rafal Bledzinski