Procesų ir signalų valdymas

 

Šiame skyriuje panagrinėsime PERL kalbos, kaip UNIX sisteminio programavimo įrankio, galimybes. Kaip žinia, viena iš svarbiausių posistemių UNIX operacinėje sistemoje yra atminties ir procesų valdymo posistemė. Ji kontroliuoja, sinchronizuoja procesus, atlieka duomenų perdavimą tarp procesų, paskirsto sisteminius resursus procesams.

Kai iš komandinės eilutės paleidžiama programa, UNIX aplinka (shell) paprastai sukuria naują procesą, vykdantį programą. Šis procesas tampa procesu vaiku, o aplinka - procesu tėvu. Taigi procesai glaudžiai surišti vienas su kitu. Jie sudaro medžio struktūrą, turinčią šakninį init procesą. Procesas panaudodamas sisteminius kreipinius (system call) sukuria kitus procesus vaikus, o signalų pagalba valdo juos. Kiekvienas procesas identifikuojamas pagal PID (Process ID), o jo ryšys su procesu tėvu nustatomas pagal PPID (Parent Process ID). Nauja programa paprastai startuoja taip: procesas tėvas sisteminio kreipinio fork pagalba kuria savo paties kopiją, po to panaudojant kreipinį exec, proceso tėvo programa keičiama nauja. Kuriant naujus procesus galioja paveldimumo taisyklės. Paprastai paveldima: nuorodas į failus, failų sukūrimo kaukę (umask), katalogą, vartotojo ID, aplinkos kintamuosius.

Procesų panaudojimo pavyzdys

Programos vykdymo metu reikia sužinoti katalogo failų, turinčių plėtinius .txt, sąrašą. Tam tikslui naudojant sisteminius kreipinius fork ir exec arba system kuriamas naujas procesas-vaikas, atlieka filtravimo veiksmą. Funkcijos pipe pagalba sudarius ryšį tarp tėvo ir vaiko procesų ir filtruotų failų sąrašas atsiunčiamas procesui tėvui t. y. į pagrindinę programą.


Keletas sisteminių kreipinių:

Sisteminis kreipinys

Atliekama funkcija

fork

Naujo proceso sukūrimas, dubliuojant (klonuojant) kviečiantįjį procesą

exec

Sisteminių kreipinių šeima, kurios kiekvienas narys atlieka tą pačią funkciją t. y. transformuoja procesą, perdengdamas jo atminties erdvę nauja programa. Šios šeimos nariai skiriasi tik argumentų sąrašo formavimo tvarka.

wait

Procesų sinchronizavimas. Leidžia procesui palaukti, kol pasibaigs kitas procesas.

waitpid

Procesas laukia, kol darbą baigs procesas vaikas.

exit

Nutraukiamas proceso vykdymas.

 

Kai procesas vaikas naikinamas, jo užimama atmintis grąžinama operacinei sistemai, bet procesų lentelė nėra atlaisvinama. Tai leidžia procesui tėvui patikrinti, kokiu statusu vaikas užbaigė darbą. Procesas, kuris buvo sunaikintas, bet dėl tam tikrų priežasčių nepašalintas iš procesų lentelės, vadinamas procesu zombiu.


Procesų valdymas

 

Naujas procesas sukuriamas ir programa paleidžiama vykdymui ne vien tik sisteminių kreipinių pagalba. Tam tikslui galima panaudoti kai kurias  PERL funkcijas bei konstrukcijas. Panagrinėkime jas.

Paprasčiausias būdas paleisti naują procesą - tai naudoti funkciją system(). Jos sintaksė:

   system ("komandos_pavadinimas [argumentai]");

Funkcija grąžina 0, jei viskas praėjo sėkmingai ir nenulinę reikšmę, jei įvyko klaida. Naujas procesas iš PERL programos paveldi tris standartinius I/O failų deskriptorius (STDIN, STDOUT, STDERR), todėl informacijos įvedimas ar išvedimas atliekamas į tuos pačius įrenginius, kaip ir PERL programos. Pavyzdžiai:

   system("date");
   system("date > dabar") && die "Neįmanoma sukurti failo: dabar";
 

{
    local $ENV{"PATH"} = "/bin:/usr/bin:/usr/ucb";
    system "grep fred bedrock >output";              # į faila output išvedama eilutės, kuriose yra žodis fred
}

system "grep 'fred flintstone' buffaloes";           # naudojamas shell'as
system "grep","fred flintstone","buffaloes";         # nenaudojamas shell'as

 

Kitas būdas kaip paleisti procesą - tai naudoti atvirkščias kabutes ` ` . Kaip ir funkcijos system atveju, komandos vykdymo metu PERL programa pristabdoma ir laukia, kol procesas-vaikas t.y. komanda baigs darbą. Pavyzdžiai:

$now = "the time is now ".`date`;                    # išveda tekstą ir datą

foreach $_ (`who`) {                                 # $_ priskiriama po vieną eilutę iš who
    ($kas,$kur,$kada) = /(\S+)\s+(\S+)\s+(.*)/;
    print "$kas on $kur at $kada\n";
}

Naują procesą galima sukurti naudojant funkciją open, panašiai kaip kuriant failinį deskriptorių. Galima sukurti procesą - failinį deskriptorių, kuris past nuskaitytų informaciją arba iš jo būtų nuskaitoma. Sintaksė

   open(FILEHANDLE,"[|] komandos_pavadinimas [|]");

Pavyzdžiai:

open(WHOPROC, "who|");               # | esantis argumento gale rodo, kad procesas paruoštas nuskaitymui
@whosaid = <WHOPROC>;	             # kintamajam @whosaid priskiriama komandos who išvedimas
open(LPR,"|lpr -Pslatewriter");      # | esantis arg.pradžioje rodo, kad procesas laukia įvedimo
print LPR @rockreport;		     
close(LPR);			     # naikinamas procesas
open (WHO,"who|");
open (LPR,"|lpr -Pslatewriter");
while (<WHO>) {
    unless (/fred/) {                # neišvedinėti fred
        print LPR $_;
    }
}
close WHO;
close LPR;


Procesai gali būti valdomi sisteminiais kreipiniais tokiais kaip: fork, exec, wait, waitpid, exit.  Panagrinėkime pavyzdžius:

if (!defined($kid_pid = fork())) {	# klonuojamas procesas tėvas
    die "cannot fork: $!";
} elsif ($kid_pid) {                	# procesas tėvas žino vaiko PID, todėl $kid_pid != 0
    waitpid($kid_pid, 0);
    print "Aš tėvas,vaiko PID= $child_pid \n";
} else {
    exec("date");  		      	# procesui vaikui $kid_pid = 0, vykdoma komanda date
} 

 


Signalai

Signalai tai pranešimai, kuriuos procesas arba operacinės sistemos branduolys siunčia kitam procesui. Siganalai siunčiami įvykus kažkokiam įvykiui (pvz. proceso sunaikinimas, pertraukimas su Ctrl-C) arba atsiradus klaidoms ar konfliktams (pvz. konfliktinis resursų panaudojimas). Tokie signalai yra iš sisteminiai, tačiau vartotojas (jei jsi turi pakankamai teisių) gali pats pasiųsti signalą procesui. Siganlai yra numeruojami, tačiau jie turi ir vardus.  Žemiau pateikta keletas svarbesnių signalų.
 

Signalas

Atliekama funkcija

SIGINT Šis signalas gaunamas paspaudus klavišus Ctrl-C. Jis reiškia, kad procesas turi nutraukti savo darbą.
SIGQUIT Šis signalas gaunamas paspaudus klavišus Ctrl-\. Jis generuoja core dump.
SIGTERM Šis signalas siunčia UNIX komanda kill, kai nėra nurodomas kitas  signalo tipas. Šiuo signalu prašoma proceso nutraukti darbą.
SIGPIPE Šį signalą siunčia branduolys, kai procesas bando rašyti į kanalą arba lizda, o kanalo kitame gale esantis procesas jau uždaręs komunikacijų sesiją arba iš vis nebeegzistuoja.
SIGHUP Šis signalas siunčiamas procesui reikalaujant, kad būtų perstartuota programa arba iš naujo nuskaitytas konfigūracinis failas (pvz. po pakeitimų)
SIGCHLD Šis signalas siunčiamas procesui tėvui, kai procesas vaikas sustoja ("pasikabina") arba baigia darbą.

 

Procesas, gavęs signalą, turi į jį reaguoti. Reakcija į sisteminius signalus paprastai yra vienoda - t.y. nutraukiamas proceso darbas, tuo tarpu į kitus signalus procesai paga nutylėjima nereaguoja. Galima pakeisti procesų reagavimo į signalus veiksmus, keisdami asociatyvinio masyvo %SIG kintamuosius. Pavyzdžiui pakeiskime proceso reagavimo veiksmus į signalą SIGINT ir "gražiai" užbaikime programos darbą, o ne papraščiausiai nutraukime.

sub my_sigint_catcher {            # Paprogramė gaudanti signalą
    $saw_sigint = 1;               # kintamasis - žymė
}

 

$saw_sigint = 0;                   # pradedant darbą žymė prilyginama 0
$SIG{'INT'} = 'my_sigint_catcher'; # registruojama nauja signalo SIGINT funkcija
foreach (@huge_array) {
                                   # atliekami dideli skaičiavimai
                                   # labai dideli ir ilgi skaičiavimai
                                   # atsibodo laukti ir spaudžiam Ctrl+C
    if ($saw_sigint) {             # $saw_sigint = 1
                                   # atliekami užbaigiamieji veiksmai
        last;                      # ir tik tuomet nutraukiama programa
    }
}
$SIG{'INT'} = 'DEFAULT';           # atstatoma default SIGINT reikšmė

 

Operacinė sistema vietoj signalų pavadinimų naudoja jų numerius, tačiau žmogui paprasčiau atsiminti pavadinimus, todėl PERL programose galima naudoti signalų pavadinimus, nes PERL kompiliatorius automatiškai atlieka vardų transliavimą į numerius.

kill(2,234,237);           # siunčiamas SIGINT į 234 ir 237
kill ('INT', 234, 237);    # tas pats

Sisteminių kreipinių ir signalų naudojimas yra profesionalaus programavimo požymis, nes dauguma reikalingų veiksmų paleidžiant komandas iš PERL programų galima atlikti naudojant funkciją system.


Pavyzdžiai

#!/usr/bin/perl -w
# Informacijos perdavimas tarp giminingų procesų
# Sisteminių kvietimų pipe ir fork panaudojimas informacijos perdavimui iš tėvo vaikui

use IO::Handle;
pipe(READER, WRITER);
WRITER->autoflush(1);

if ($pid = fork) {
    close READER;
    print WRITER "Tevo PID $$ siuncia ....\n";
    close WRITER;
    waitpid($pid,0);
} else {
    die "fork klaida: $!" unless defined $pid;
    close WRITER;
    chomp($line = <READER>);
    print "Vaiko PID $$ skaito ....: `$line'\n";
    close READER;  
    exit;
}
 

# Vienos programos pakeitimas kita vykdymo metu
exec("archive *.data")
    or die "Negaliu paleisti programos archive: $!\n";

 

# Norint sukurti naują procesą ir skaityti jo išvedamą informaciją, naudojama konstrukcija
# Atkreipkit į simbolį |
$pid = open(README, "programa argumentai |") or die "Neįmanoma sukurti proceso: $!\n";
while (<README>) {
    # ...
}
close(README) or die "Neįmanoma uždaryti kanalo: $!\n";

 

# Norint sukurti naują procesą ir rašyti į jį daroma taip:
# Atkreipkit į simbolį |
$pid = open(WRITEME, "| programa argumentai") or die "Neįmanoma sukurti proceso: $!\n";
print WRITEME "data\n";
close(WRITEME) or die "Neįmanoma uždaryti kanalo: $!\n";

 

#!/usr/bin/perl
# Programa veikianti panašiai kaip UNIX cat komanda
# Apibrėžiamas naujai STDOUT ir procesas vaikas filtruoja STDIN į STDOUT
number();                   # filtruojamas STDOUT

while (<>) {                
    print;
} 
close STDOUT;               # uždaromi procesai vaikai
exit;

sub number {
    my $pid;
    return if $pid = open(STDOUT, "|-");
    die "Neįmanoma sukurti proceso: $!" unless defined $pid;
    while (<STDIN>) { printf "%d: > %s", $., $_ } 
    exit;
} 
 

 

Į turinį