ushchestvlyaet
vyzov funkcii main, nahodyashchejsya v drugoj programme, peredavaya ej svoi argumenty v
kachestve vhodnyh.
Sistemnyj vyzov exec mozhet ne udat'sya, esli ukazannyj fajl path ne sushchestvuet,
libo vy ne imeete prava ego vypolnyat' (takie kody dostupa), libo on ne yavlyaetsya
vypolnyaemoj programmoj (nevernoe magicheskoe chislo), libo slishkom velik dlya dannoj
mashiny (sistemy), libo fajl otkryt kakim-nibud' processom (naprimer eshche zapisyvaetsya
kompilyatorom). V etom sluchae prodolzhitsya vypolnenie prezhnej programmy. Esli zhe
vyzov uspeshen - vozvrata iz exec ne proishodit voobshche (poskol'ku upravlenie pereda-
etsya v druguyu programmu).
Argument argv[0] obychno polagayut ravnym path. Po nemu programma, imeyushchaya nes-
kol'ko imen (v fajlovoj sisteme), mozhet vybrat' CHTO ona dolzhna delat'. Tak programma
/bin/ls imeet al'ternativnye imena lr, lf, lx, ll. Zapuskaetsya odna i ta zhe prog-
ramma, no v zavisimosti ot argv[0] ona dalee delaet raznuyu rabotu.
Argument envp - eto "okruzhenie" programmy (sm. nachalo etoj glavy). Esli on ne
zadan - peredaetsya okruzhenie tekushchej programmy (nasleduetsya soderzhimoe massiva, na
kotoryj ukazyvaet peremennaya environ); esli zhe zadan yavno (naprimer, okruzhenie skopi-
rovano v kakoj-to massiv i chast' peremennyh podpravlena ili dobavleny novye peremen-
nye) - novaya programma poluchit novoe okruzhenie. Napomnim, chto okruzhenie mozhno pro-
chest' iz predopredelennoj peremennoj char **environ, libo iz tret'ego argumenta funk-
cii main (sm. nachalo glavy), libo funkciej getenv().
A. Bogatyrev, 1992-95 - 224 - Si v UNIX
Sistemnye vyzovy fork i exec ne skleeny v odin vyzov potomu, chto mezhdu fork i
exec v processe-syne mogut proishodit' nekotorye dejstviya, narushayushchie simmetriyu
processa-otca i porozhdennogo processa: ustanovka reakcij na signaly, perenapravlenie
vvoda/vyvoda, i.t.p. Smotri primer "interpretator komand" v prilozhenii. V MS DOS, ne
imeyushchej parallel'nyh processov, vyzovy fork, exec i wait skleeny v odin vyzov spawn.
Zato pri etom prihoditsya delat' perenapravleniya vvoda-vyvoda v porozhdayushchem processe
pered spawn, a posle nego - vosstanavlivat' vse kak bylo.
6.5.4. Zavershit' process mozhno sistemnym vyzovom
void exit( unsigned char retcode );
Iz etogo vyzova ne byvaet vozvrata. Process zavershaetsya: segmenty stack, data, text,
user unichtozhayutsya (pri etom vse otkrytye processom fajly zakryvayutsya); pamyat', koto-
ruyu oni zanimali, schitaetsya svobodnoj i v nee mozhet byt' pomeshchen drugoj process.
Prichina smerti otmechaetsya v pasporte processa - v strukture proc v tablice processov
vnutri yadra. No pasport eshche ne unichtozhaetsya! |to sostoyanie processa nazyvaetsya
"zombi" - zhivoj mertvec.
V pasport processa zanositsya kod otveta retcode. |tot kod mozhet byt' prochitan
processom-roditelem (tem, kto sozdal etot process vyzovom fork). Prinyato, chto kod 0
oznachaet uspeshnoe zavershenie processa, a lyuboe polozhitel'noe znachenie 1..255 oznachaet
neudachnoe zavershenie s takim kodom oshibki. Kody oshibok zaranee ne predopredeleny:
eto lichnoe delo processov otca i syna - ustanovit' mezhdu soboj kakie-to soglasheniya po
etomu povodu. V staryh programmah inogda pisalos' exit(-1); |to nekorrektno - kod
otveta dolzhen byt' neotricatelen; kod -1 prevrashchaetsya v kod 255. CHasto ispol'zuetsya
konstrukciya exit(errno);
Programma mozhet zavershit'sya ne tol'ko yavno vyzyvaya exit, no i eshche dvumya sposo-
bami:
- esli proishodit vozvrat upravleniya iz funkcii main(), t.e. ona konchilas' - to
vyzov exit() delaetsya neyavno, no s nepredskazuemym znacheniem retcode;
- process mozhet byt' ubit signalom. V etom sluchae on ne vydaet nikakogo koda
otveta v process-roditel', a vydaet priznak "process ubit".
6.5.5. V dejstvitel'nosti exit() - eto eshche ne sam sistemnyj vyzov zaversheniya, a
standartnaya funkciya. Sam sistemnyj vyzov nazyvaetsya _exit(). My mozhem pereoprede-
lit' funkciyu exit() tak, chtoby po okonchanii programmy proishodili nekotorye dejstviya:
void exit(unsigned code){
/* Dobavlennyj mnoj dopolnitel'nyj operator: */
printf("Zakonchit' rabotu, "
"kod otveta=%u\n", code);
/* Standartnye operatory: */
_cleanup(); /* zakryt' vse otkrytye fajly.
* |to standartnaya funkciya |= */
_exit(code); /* sobstvenno sisvyzov */
}
int f(){ return 17; }
void main(){
printf("aaaa\n"); printf("bbbb\n"); f();
/* potom otkommentirujte eto: exit(77); */
}
Zdes' funkciya exit vyzyvaetsya neyavno po okonchanii main, ee podstavlyaet v programmu
kompilyator. Delo v tom, chto pri zapuske programmy exec-om, pervym nachinaet vypol-
nyat'sya kod tak nazyvaemogo "startera", podkleennogo pri sborke programmy iz fajla
/lib/crt0.o. On vyglyadit primerno tak (v dejstvitel'nosti on napisan na assemblere):
... // vychislit' argc, nastroit' nekotorye parametry.
main(argc, argv, envp);
exit();
A. Bogatyrev, 1992-95 - 225 - Si v UNIX
ili tak (vzyato iz proekta GNU|-|-):
int errno = 0;
char **environ;
_start(int argc, int arga)
{
/* OS and Compiler dependent!!!! */
char **argv = (char **) &arga;
char **envp = environ = argv + argc + 1;
/* ... vozmozhno eshche kakie-to inicializacii,
* napodobie setlocale( LC_ALL, "" ); v SCO UNIX */
exit (main(argc, argv, envp));
}
Gde dolzhno byt'
int main(int argc, char *argv[], char *envp[]){
...
return 0; /* vmesto exit(0); */
}
Adres funkcii _start() pomechaetsya v odnom iz polej zagolovka fajla formata a.out kak
adres, na kotoryj sistema dolzhna peredat' upravlenie posle zagruzki programmy v
pamyat' (tochka vhoda).
Kakoj kod otveta popadet v exit() v etih primerah (esli otsutstvuet yavnyj vyzov
exit ili return) - nepredskazuemo. Na IBM PC v vyshenapisannom primere etot kod raven
17, to est' znacheniyu, vozvrashchennomu poslednej vyzyvavshejsya funkciej. Odnako eto ne
kakoe-to special'noe soglashenie, a sluchajnyj effekt (tak uzh ustroen kod, sozdavaemyj
etim kompilyatorom).
6.5.6. Process-otec mozhet dozhdat'sya okonchaniya svoego potomka. |to delaetsya sistem-
nym vyzovom wait i nuzhno po sleduyushchej prichine: pust' otec - eto interpretator komand.
Esli on zapustil process i prodolzhil svoyu rabotu, to oba processa budut predprinimat'
popytki chitat' vvod s klaviatury terminala - interpretator zhdet komand, a zapushchennaya
programma zhdet dannyh. Komu iz nih budet postupat' nabiraemyj nami tekst - nepreds-
kazuemo! Vyvod: interpretator komand dolzhen "zasnut'" na to vremya, poka rabotaet
porozhdennyj im process:
int pid; unsigned short status;
...
if((pid = fork()) == 0 ){
/* porozhdennyj process */
... // perenapravleniya vvoda-vyvoda.
... // nastrojka signalov.
exec(....);
perror("exec ne udalsya"); exit(1);
}
/* inache eto porodivshij process */
while((pid = wait(&status)) > 0 )
printf("Okonchilsya syn pid=%d s kodom %d\n",
pid, status >> 8);
printf( "Bol'she net synovej\n");
____________________
|= _cleanup() zakryvaet fajly, otkrytye fopen()om, "vytryahaya" pri etom dannye, na-
koplennye v buferah, v fajl. Pri avarijnom zavershenii programmy fajly vse ravno zak-
ryvayutsya, no uzhe ne yavno, a operacionnoj sistemoj (v vyzove _exit). Pri etom soder-
zhimoe nedosbroshennyh buferov budet uteryano.
____________________
|-|- GNU - programmy, rasprostranyaemye v ishodnyh tekstah iz Free Software Founda-
A. Bogatyrev, 1992-95 - 226 - Si v UNIX
wait priostanavlivaet|- vypolnenie vyzvavshego processa do momenta okonchaniya lyubogo iz
porozhdennyh im processov (ved' mozhno bylo zapustit' i neskol'kih synovej!). Kak
tol'ko kakoj-to potomok okonchitsya - wait prosnetsya i vydast nomer (pid) etogo
potomka. Kogda nikogo iz zhivyh "synovej" ne ostalos' - on vydast (-1). YAsno, chto
processy mogut okanchivat'sya ne v tom poryadke, v kotorom ih porozhdali. V peremennuyu
status zanositsya v special'nom vide kod otveta okonchivshegosya processa, libo nomer
signala, kotorym on byl ubit.
#include <sys/types.h>
#include <sys/wait.h>
...
int status, pid;
...
while((pid = wait(&status)) > 0){
if( WIFEXITED(status)){
printf( "Process %d umer s kodom %d\n",
pid, WEXITSTATUS(status));
} else if( WIFSIGNALED(status)){
printf( "Process %d ubit signalom %d\n",
pid, WTERMSIG(status));
if(WCOREDUMP(status)) printf( "Obrazovalsya core\n" );
/* core - obraz pamyati processa dlya otladchika adb */
} else if( WIFSTOPPED(status)){
printf( "Process %d ostanovlen signalom %d\n",
pid, WSTOPSIG(status));
} else if( WIFCONTINUED(status)){
printf( "Process %d prodolzhen\n",
pid);
}
}
...
Esli kod otveta nas ne interesuet, my mozhem pisat' wait(NULL).
Esli u nashego processa ne bylo ili bol'she net zhivyh synovej - vyzov wait nichego
ne zhdet, a vozvrashchaet znachenie (-1). V napisannom primere cikl while pozvolyaet dozh-
dat'sya okonchaniya vseh potomkov.
V tot moment, kogda process-otec poluchaet informaciyu o prichine smerti potomka,
pasport umershego processa nakonec vycherkivaetsya iz tablicy processov i mozhet byt'
pereispol'zovan novym processom. Do togo, on hranitsya v tablice processov v sostoya-
nii "zombie" - "zhivoj mertvec". Tol'ko dlya togo, chtoby kto-nibud' mog uzat' status
ego zaversheniya.
Esli process-otec zavershilsya ran'she svoih synovej, to kto zhe sdelaet wait i
vycherknet pasport? |to sdelaet process nomer 1: /etc/init. Esli otec umer ran'she
processov-synovej, to sistema zastavlyaet process nomer 1 "usynovit'" eti processy.
init obychno nahoditsya v cikle, soderzhashchem v nachale vyzov wait(), to est' ozhidaet
____________________
tion (FSF). Sredi nih - C++ kompilyator g++ i redaktor emacs. Smysl slov GNU - "gen-
erally not UNIX" - proekt byl osnovan kak protivodejstvie nachavshejsya kommercializacii
UNIX i zakrytiyu ego ishodnyh tekstov. "Sdelat' kak v UNIX, no luchshe".
|- "ZHivoj" process mozhet prebyvat' v odnom iz neskol'kih sostoyanij: process ozhidaet
nastupleniya kakogo-to sobytiya ("spit"), pri etom emu ne vydelyaetsya vremya processora,
t.k. on ne gotov k vypolneniyu; process gotov k vypolneniyu i stoit v ocheredi k proces-
soru (poskol'ku processor vypolnyaet drugoj process); process gotov i vypolnyaetsya pro-
cessorom v dannyj moment. Poslednee sostoyanie mozhet proishodit' v dvuh rezhimah -
pol'zovatel'skom (vypolnyayutsya komandy segmenta text) i sistemnom (processom byl izdan
sistemnyj vyzov, i sejchas vypolnyaetsya funkciya v yadre). Ozhidanie sobytiya byvaet tol'ko
v sistemnoj faze - vnutri sistemnogo vyzova (t.e. eto "sinhronnoe" ozhidanie). Neak-
tivnye processy ("spyashchie" ili zhdushchie resursa processora) mogut byt' vremenno otkachany
na disk.
A. Bogatyrev, 1992-95 - 227 - Si v UNIX
okonchaniya lyubogo iz svoih synovej (a oni u nego vsegda est', o chem my pogovorim pod-
robnee chut' pogodya). Takim obrazom init zanimaetsya chistkoj tablicy processov, hotya
eto ne edinstvennaya ego funkciya.
Vot shema, poyasnyayushchaya zhiznennyj cikl lyubogo processa:
|pid=719,csh
|
if(!fork())------->--------* pid=723,csh
| | zagruzit'
wait(&status) exec("a.out",...) <-- a.out
: main(...){ s diska
: |
:pid=719,csh | pid=723,a.out
spit(zhdet) rabotaet
: |
: exit(status) umer
: }
prosnulsya <---prosnis'!--RIP
|
|pid=719,csh
Zamet'te, chto nomer porozhdennogo processa ne obyazan byt' sleduyushchim za nomerom rodi-
telya, a tol'ko bol'she nego. |to svyazano s tem, chto drugie processy mogli sozdat' v
sisteme novye processy do togo, kak nash process izdal svoj vyzov fork.
6.5.7. Krome togo, wait pozvolyaet otslezhivat' ostanovku processa. Process mozhet
byt' priostanovlen pri pomoshchi posylki emu signalov SIGSTOP, SIGTTIN, SIGTTOU,
SIGTSTP. Poslednie tri signala posylaet pri opredelennyh obstoyatel'stvah drajver
terminala, k primeru SIGTSTP - pri nazhatii klavishi CTRL/Z. Prodolzhaetsya process
posylkoj emu signala SIGCONT.
V dannom kontekste, odnako, nas interesuyut ne sami eti signaly, a drugaya shema
manipulyacii s otslezhivaniem statusa porozhdennyh processov. Esli ukazano yavno, sis-
tema mozhet posylat' processu-roditelyu signal SIGCLD v moment izmeneniya statusa lyubogo
iz ego potomkov. |to pozvolit processu-roditelyu nemedlenno sdelat' wait i nemedlenno
otrazit' izmenenie sostoyanie processa-potomka v svoih vnutrennih spiskah. Dannaya
shema programmiruetsya tak:
void pchild(){
int pid, status;
sighold(SIGCLD);
while((pid = waitpid((pid_t) -1, &status, WNOHANG|WUNTRACED)) > 0){
dorecord:
zapisat'_informaciyu_ob_izmeneniyah;
}
sigrelse(SIGCLD);
/* Reset */
signal(SIGCLD, pchild);
}
...
main(){
...
/* Po signalu SIGCLD vyzyvat' funkciyu pchild */
signal(SIGCLD, pchild);
...
glavnyj_cikl;
}
Sekciya s vyzovom waitpid (raznovidnost' vyzova wait), prikryta paroj funkcij
sighold-sigrelse, zapreshchayushchih prihod signala SIGCLD vnutri etoj kriticheskoj sekcii.
A. Bogatyrev, 1992-95 - 228 - Si v UNIX
Sdelano eto vot dlya chego: esli process nachnet modificirovat' tablicy ili spiski v
rajone metki dorecord:, a v etot moment pridet eshche odin signal, to funkciya pchild
budet vyzvana rekursivno i tozhe popytaetsya modificirovat' tablicy i spiski, v kotoryh
eshche ostalis' nezavershennymi perestanovki ssylok, elementov, schetchikov. |to privedet k
razrusheniyu dannyh.
Poetomu signaly dolzhny prihodit' posledovatel'no, i funkcii pchild vyzyvat'sya
takzhe posledovatel'no, a ne rekursivno. Funkciya sighold otkladyvaet dostavku signala
(esli on sluchitsya), a sigrelse - razreshaet dostavit' nakopivshiesya signaly (no esli ih
prishlo neskol'ko odnogo tipa - vse oni dostavlyayutsya kak odin takoj signal. Otsyuda -
cikl vokrug waitpid).
Flag WNOHANG - oznachaet "ne zhdat' vnutri vyzova wait", esli ni odin iz potomkov
ne izmenil svoego sostoyaniya; a prosto vernut' kod (-1)". |to pozvolyaet vyzyvat'
pchild dazhe bez polucheniya signala: nichego ne proizojdet. Flag WUNTRACED - oznachaet
"vydavat' informaciyu takzhe ob ostanovlennyh processah".
6.5.8. Kak uzhe bylo skazano, pri exec vse otkrytye fajly dostayutsya v nasledstvo
novoj programme (v chastnosti, esli mezhdu fork i exec byli perenapravleny vyzovom dup2
standartnye vvod i vyvod, to oni ostanutsya perenapravlennymi i u novoj programmy).
CHto delat', esli my ne hotim, chtoby nasledovalis' vse otkrytye fajly? (Hotya by
potomu, chto bol'shinstvom iz nih novaya programma pol'zovat'sya ne budet - v osnovnom
ona budet ispol'zovat' lish' fd 0, 1 i 2; a yachejki v tablice otkrytyh fajlov processa
oni zanimayut). Vo-pervyh, nenuzhnye deskriptory mozhno yavno zakryt' close v promezhutke
mezhdu fork-om i exec-om. Odnako ne vsegda my pomnim nomera deskriptorov dlya etoj
operacii. Bolee radikal'noj meroj yavlyaetsya total'naya chistka:
for(f = 3; f < NOFILE; f++)
close(f);
Est' bolee elegantnyj put'. Mozhno pometit' deskriptor fajla special'nym flagom,
oznachayushchim, chto vo vremya vyzova exec etot deskriptor dolzhen byt' avtomaticheski zakryt
(rezhim file-close-on-exec - fclex):
#include <fcntl.h>
int fd = open(.....);
fcntl (fd, F_SETFD, 1);
Otmenit' etot rezhim mozhno tak:
fcntl (fd, F_SETFD, 0);
Zdes' est' odna tonkost': etot flag ustanavlivaetsya ne dlya struktury file - "otkrytyj
fajl", a neposredstvenno dlya deskriptora v tablice otkrytyh processom fajlov (massiv
flagov: char u_pofile[NOFILE]). On ne sbrasyvaetsya pri zakrytii fajla, poetomu nas
mozhet ozhidat' syurpriz:
... fcntl (fd, F_SETFD, 1); ... close(fd);
...
int fd1 = open( ... );
Esli fd1 okazhetsya ravnym fd, to deskriptor fd1 budet pri exec-e zakryt, chego my yavno
ne ozhidali! Poetomu pered close(fd) polezno bylo by otmenit' rezhim fclex.
6.5.9. Kazhdyj process imeet upravlyayushchij terminal (short *u_ttyp). On dostaetsya pro-
cessu v nasledstvo ot roditelya (pri fork i exec) i obychno sovpadaet s terminalom, s
na kotorom rabotaet dannyj pol'zovatel'.
Kazhdyj process otnositsya k nekotoroj gruppe processov (int p_pgrp), kotoraya
takzhe nasleduetsya. Mozhno poslat' signal vsem processam ukazannoj gruppy pgrp:
kill( -pgrp, sig );
Vyzov
kill( 0, sig );
posylaet signal sig vsem processam, ch'ya gruppa sovpadaet s gruppoj posylayushchego
A. Bogatyrev, 1992-95 - 229 - Si v UNIX
processa. Process mozhet uznat' svoyu gruppu:
int pgrp = getpgrp();
a mozhet stat' "liderom" novoj gruppy. Vyzov
setpgrp();
delaet sleduyushchie operacii:
/* U processa bol'she net upravl. terminala: */
if(p_pgrp != p_pid) u_ttyp = NULL;
/* Gruppa processa polagaetsya ravnoj ego id-u: */
p_pgrp = p_pid; /* new group */
V svoyu ochered', upravlyayushchij terminal tozhe imeet nekotoruyu gruppu (t_pgrp). |to znache-
nie ustanavlivaetsya ravnym gruppe processa, pervym otkryvshego etot terminal:
/* chast' procedury otkrytiya terminala */
if( p_pid == p_pgrp // lider gruppy
&& u_ttyp == NULL // eshche net upr.term.
&& t_pgrp == 0 ){ // u terminala net gruppy
u_ttyp = &t_pgrp;
t_pgrp = p_pgrp;
}
Takim processom obychno yavlyaetsya process registracii pol'zovatelya v sisteme (kotoryj
sprashivaet u vas imya i parol'). Pri zakrytii terminala vsemi processami (chto byvaet
pri vyhode pol'zovatelya iz sistemy) terminal teryaet gruppu: t_pgrp=0;
Pri nazhatii na klaviature terminala nekotoryh klavish:
c_cc[ VINTR ] obychno DEL ili CTRL/C
c_cc[ VQUIT ] obychno CTRL/\
drajver terminala posylaet sootvetstvenno signaly SIGINT i SIGQUIT vsem processam
gruppy terminala, t.e. kak by delaet
kill( -t_pgrp, sig );
Imenno poetomu my mozhem prervat' process nazhatiem klavishi DEL. Poetomu, esli process
sdelal setpgrp(), to signal s klaviatury emu poslat' nevozmozhno (t.k. on imeet svoj
unikal'nyj nomer gruppy != gruppe terminala).
Esli process eshche ne imeet upravlyayushchego terminala (ili uzhe ego ne imeet posle
setpgrp), to on mozhet sdelat' lyuboj terminal (kotoryj on imeet pravo otkryt') uprav-
lyayushchim dlya sebya. Pervyj zhe fajl-ustrojstvo, yavlyayushchijsya interfejsom drajvera termina-
lov, kotoryj budet otkryt etim processom, stanet dlya nego upravlyayushchim terminalom. Tak
process mozhet imet' kanaly 0, 1, 2 svyazannye s odnim terminalom, a preryvaniya polu-
chat' s klaviatury drugogo (kotoryj on sdelal upravlyayushchim dlya sebya).
Process registracii pol'zovatelya v sisteme - /etc/getty (nazvanie proishodit ot
"get tty" - poluchit' terminal) - zapuskaetsya processom nomer 1 - /etc/init-om - na
kazhdom iz terminalov, zaregistrirovannyh v sisteme, kogda
- sistema tol'ko chto byla zapushchena;
- libo kogda pol'zovatel' na kakom-to terminale vyshel iz sistemy (interpretator
komand zavershilsya).
V sil'nom uproshchenii getty mozhet byt' opisan tak:
void main(ac, av) char *av[];
{ int f; struct termio tmodes;
for(f=0; f < NOFILE; f++) close(f);
/* Otkaz ot upravlyayushchego terminala,
* osnovanie novoj gruppy processov.
*/
setpgrp();
/* Pervonachal'noe yavnoe otkrytie terminala */
A. Bogatyrev, 1992-95 - 230 - Si v UNIX
/* Pri etom terminal av[1] stanet upr. terminalom */
open( av[1], O_RDONLY ); /* fd = 0 */
open( av[1], O_RDWR ); /* fd = 1 */
f = open( av[1], O_RDWR ); /* fd = 2 */
// ... Schityvanie parametrov terminala iz fajla
// /etc/gettydefs. Tip trebuemyh parametrov linii
// zadaetsya metkoj, ukazyvaemoj v av[2].
// Zapolnenie struktury tmodes trebuemymi
// znacheniyami ... i ustanovka mod terminala.
ioctl (f, TCSETA, &tmodes);
// ... zapros imeni i parolya ...
chdir (domashnij_katalog_pol'zovatelya);
execl ("/bin/csh", "-csh", NULL);
/* Zapusk interpretatora komand. Gruppa processov,
* upravl. terminal, deskriptory 0,1,2 nasleduyutsya.
*/
}
Zdes' posledovatel'nye vyzovy open zanimayut posledovatel'nye yachejki v tablice otkry-
tyh processom fajlov (poisk kazhdoj novoj nezanyatoj yachejki proizvoditsya s nachala tab-
licy) - v itoge po deskriptoram 0,1,2 otkryvaetsya fajl-terminal. Posle etogo desk-
riptory 0,1,2 nasleduyutsya vsemi potomkami interpretatora komand. Process init zapus-
kaet po odnomu processu getty na kazhdyj terminal, kak by delaya
/etc/getty /dev/tty01 m &
/etc/getty /dev/tty02 m &
...
i ozhidaet okonchaniya lyubogo iz nih. Posle vhoda pol'zovatelya v sistemu na kakom-to
terminale, sootvetstvuyushchij getty prevrashchaetsya v interpretator komand (pid processa
sohranyaetsya). Kak tol'ko kto-to iz nih umret - init perezapustit getty na sootvetst-
vuyushchem terminale (vse oni - ego synov'ya, poetomu on znaet - na kakom imenno termi-
nale).
6.6. Truby i FIFO-fajly.
Processy mogut obmenivat'sya mezhdu soboj informaciej cherez fajly. Sushchestvuyut
fajly s neobychnym povedeniem - tak nazyvaemye FIFO-fajly (first in, first out), vedu-
shchie sebya podobno ocheredi. U nih ukazateli chteniya i zapisi razdeleny. Rabota s takim
fajlom napominaet protalkivanie sharov cherez trubu - s odnogo konca my vtalkivaem dan-
nye, s drugogo konca - vynimaem ih. Operaciya chteniya iz pustoj "truby" proiostanovit
vyzov read (i izdavshij ego process) do teh por, poka kto-nibud' ne zapishet v FIFO-
fajl kakie-nibud' dannye. Operaciya pozicionirovaniya ukazatelya - lseek() - neprime-
nima k FIFO-fajlam. FIFO-fajl sozdaetsya sistemnym vyzovom
#include <sys/types.h>
#include <sys/stat.h>
mknod( imyaFajla, S_IFIFO | 0666, 0 );
gde 0666 - kody dostupa k fajlu. Pri pomoshchi FIFO-fajla mogut obshchat'sya dazhe nerodst-
vennye processy.
Raznovidnost'yu FIFO-fajla yavlyaetsya bezymyannyj FIFO-fajl, prednaznachennyj dlya
obmena informaciej mezhdu processom-otcom i processom-synom. Takoj fajl - kanal svyazi
kak raz i nazyvaetsya terminom "truba" ili pipe. On sozdaetsya vyzovom pipe:
int conn[2]; pipe(conn);
Esli by fajl-truba imel imya PIPEFILE, to vyzov pipe mozhno bylo by opisat' kak
A. Bogatyrev, 1992-95 - 231 - Si v UNIX
mknod("PIPEFILE", S_IFIFO | 0600, 0);
conn[0] = open("PIPEFILE", O_RDONLY);
conn[1] = open("PIPEFILE", O_WRONLY);
unlink("PIPEFILE");
Pri vyzove fork kazhdomu iz dvuh processov dostanetsya v nasledstvo para deskriptorov:
pipe(conn);
fork();
conn[0]----<---- ----<-----conn[1]
FIFO
conn[1]---->---- ---->-----conn[0]
process A process B
Pust' process A budet posylat' informaciyu v process B. Togda process A sdelaet:
close(conn[0]);
// t.k. ne sobiraetsya nichego chitat'
write(conn[1], ... );
a process B
close(conn[1]);
// t.k. ne sobiraetsya nichego pisat'
read (conn[0], ... );
Poluchaem v itoge:
conn[1]---->----FIFO---->-----conn[0]
process A process B
Obychno postupayut eshche bolee elegantno, perenapravlyaya standartnyj vyvod A v kanal
conn[1]
dup2 (conn[1], 1); close(conn[1]);
write(1, ... ); /* ili printf */
a standartnyj vvod B - iz kanala conn[0]
dup2(conn[0], 0); close(conn[0]);
read(0, ... ); /* ili gets */
|to sootvetstvuet konstrukcii
$ A | B
zapisannoj na yazyke SiSHell.
Fajl, vydelyaemyj pod pipe, imeet ogranichennyj razmer (i poetomu obychno celikom
osedaet v buferah v pamyati mashiny). Kak tol'ko on zapolnen celikom - process, pishu-
shchij v trubu vyzovom write, priostanavlivaetsya do poyavleniya svobodnogo mesta v trube.
|to mozhet privesti k vozniknoveniyu tupikovoj situacii, esli pisat' programmu neakku-
ratno. Pust' process A yavlyaetsya synom processa B, i pust' process B izdaet vyzov
wait, ne zakryv kanal conn[0]. Process zhe A ochen' mnogo pishet v trubu conn[1]. My
poluchaem situaciyu, kogda oba processa spyat:
A potomu chto truba perepolnena, a process B nichego iz nee ne chitaet, tak kak zhdet
okonchaniya A;
B potomu chto process-syn A ne okonchilsya, a on ne mozhet okonchit'sya poka ne dopishet
svoe soobshchenie.
Resheniem sluzhit zapret processu B delat' vyzov wait do teh por, poka on ne prochitaet
VSYU informaciyu iz truby (ne poluchit EOF). Tol'ko sdelav posle etogo close(conn[0]);
A. Bogatyrev, 1992-95 - 232 - Si v UNIX
process B imeet pravo sdelat' wait.
Esli process B zakroet svoyu storonu truby close(conn[0]) prezhde, chem process A
zakonchit zapis' v nee, to pri vyzove write v processe A, sistema prishlet processu A
signal SIGPIPE - "zapis' v kanal, iz kotorogo nikto ne chitaet".
6.6.1. Otkrytie FIFO fajla privedet k blokirovaniyu processa ("zasypaniyu"), esli v
bufere FIFO fajla pusto. Process zasnet vnutri vyzova open do teh por, poka v bufere
chto-nibud' ne poyavitsya.
CHtoby izbezhat' takoj situacii, a, naprimer, sdelat' chto-nibud' inoe poleznoe v
eto vremya, nam nado bylo by oprosit' fajl na predmet togo - mozhno li ego otkryt'?
|to delaetsya pri pomoshchi flaga O_NDELAY u vyzova open.
int fd = open(filename, O_RDONLY|O_NDELAY);
Esli open vedet k blokirovke processa vnutri vyzova, vmesto etogo budet vozvrashcheno
znachenie (-1). Esli zhe fajl mozhet byt' nemedlenno otkryt - vozvrashchaetsya normal'nyj
deskriptor so znacheniem >=0, i fajl otkryt.
O_NDELAY yavlyaetsya zavisimym ot semantiki togo fajla, kotoryj my otkryvaem. K
primeru, mozhno ispol'zovat' ego s fajlami ustrojstv, naprimer imenami, vedushchimi k
posledovatel'nym portam. |ti fajly ustrojstv (porty) obladayut tem svojstvom, chto
odnovremenno ih mozhet otkryt' tol'ko odin process (tak ustroena realizaciya funkcii
open vnutri drajvera etih ustrojstv). Poetomu, esli odin process uzhe rabotaet s por-
tom, a v eto vremya vtoroj pytaetsya ego zhe otkryt', vtoroj "zasnet" vnutri open, i
budet dozhidat'sya osvobozhdeniya porta close pervym processom. CHtoby ne zhdat' - sleduet
otkryvat' port s flagom O_NDELAY.
#include <stdio.h>
#include <fcntl.h>
/* Ubrat' bol'she ne nuzhnyj O_NDELAY */
void nondelay(int fd){
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY);
}
int main(int ac, char *av[]){
int fd;
char *port = ac > 1 ? "/dev/term/a" : "/dev/cua/a";
retry: if((fd = open(port, O_RDWR|O_NDELAY)) < 0){
perror(port);
sleep(10);
goto retry;
}
printf("Port %s otkryt.\n", port);
nondelay(fd);
printf("Rabota s portom, vyzovi etu programmu eshche raz!\n");
sleep(60);
printf("Vse.\n");
return 0;
}
Vot protokol:
A. Bogatyrev, 1992-95 - 233 - Si v UNIX
su# a.out & a.out xxx
[1] 22202
Port /dev/term/a otkryt.
Rabota s portom, vyzovi etu programmu eshche raz!
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
Vse.
Port /dev/cua/a otkryt.
Rabota s portom, vyzovi etu programmu eshche raz!
su#
6.7. Nelokal'nyj perehod.
Teper' pogovorim pro nelokal'nyj perehod. Standartnaya funkciya setjmp pozvolyaet
ustanovit' v programme "kontrol'nuyu tochku"|-, a funkciya longjmp osushchestvlyaet pryzhok v
etu tochku, vypolnyaya za odin raz vyhod srazu iz neskol'kih vyzvannyh funkcij (esli
nado)|=. |ti funkcii ne yavlyayutsya sistemnymi vyzovami, no poskol'ku oni realizuyutsya
mashinno-zavisimym obrazom, a ispol'zuyutsya chashche vsego kak reakciya na nekotoryj signal,
rech' o nih idet v etom razdele. Vot kak, naprimer, vyglyadit restart programmy po
preryvaniyu s klaviatury:
#include <signal.h>
#include <setjmp.h>
jmp_buf jmp; /* kontrol'naya tochka */
/* prygnut' v kontrol'nuyu tochku */
void onintr(nsig){ longjmp(jmp, nsig); }
main(){
int n;
n = setjmp(jmp); /* ustanovit' kontrol'nuyu tochku */
if( n ) printf( "Restart posle signala %d\n", n);
signal (SIGINT, onintr); /* reakciya na signal */
printf("Nachali\n");
...
}
setjmp vozvrashchaet 0 pri zapominanii kontrol'noj tochki. Pri pryzhke v kontrol'nuyu
tochku pri pomoshchi longjmp, my okazyvaemsya snova v funkcii setjmp, i eta funkciya vozv-
rashchaet nam znachenie vtorogo argumenta longjmp, v etom primere - nsig.
Pryzhok v kontrol'nuyu tochku ochen' udobno ispol'zovat' v algoritmah perebora s
vozvratom (backtracking): libo - esli otvet najden - pryzhok na pechat' otveta, libo -
esli vetv' perebora zashla v tupik - pryzhok v tochku vetvleniya i vybor drugoj al'terna-
tivy. Pri etom mozhno delat' pryzhki i v rekursivnyh vyzovah odnoj i toj zhe funkcii: s
bolee vysokogo urovnya rekursii v vyzov bolee nizkogo urovnya (v etom sluchae jmp_buf
luchshe delat' avtomaticheskoj peremennoj - svoej dlya kazhdogo urovnya vyzova funkcii).
____________________
|- V nekotorom bufere zapominaetsya tekushchee sostoyanie processa: polozhenie vershiny
steka vyzovov funkcij (stack pointer); sostoyanie vseh registrov processora, vklyuchaya
registr adresa tekushchej mashinnoj komandy (instruction pointer).
|= |to dostigaetsya vosstanovleniem sostoyaniya processa iz bufera. Izmeneniya, prois-
shedshie za vremya mezhdu setjmp i longjmp v staticheskih dannyh ne otmenyayutsya (t.k. oni
ne sohranyalis').
A. Bogatyrev, 1992-95 - 234 - Si v UNIX
6.7.1. Perepishite sleduyushchij algoritm pri pomoshchi longjmp.
#define FOUND 1 /* otvet najden */
#define NOTFOUND 0 /* otvet ne najden */
int value; /* rezul'tat */
main(){ int i;
for(i=2; i < 10; i++){
printf( "probuem i=%d\n", i);
if( test1(i) == FOUND ){
printf("otvet %d\n", value); break;
}
}
}
test1(i){ int j;
for(j=1; j < 10 ; j++ ){
printf( "probuem j=%d\n", j);
if( test2(i,j) == FOUND ) return FOUND;
/* "skvoznoj" return */
}
return NOTFOUND;
}
test2(i, j){
printf( "probuem(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Godyatsya (%d,%d)\n", i,j);
value = j; return FOUND;
}
return NOTFOUND;
}
Vot otvet, ispol'zuyushchij nelokal'nyj perehod vmesto cepochki return-ov:
#include <setjmp.h>
jmp_buf jmp;
main(){ int i;
if( i = setjmp(jmp)) /* posle pryzhka */
printf("Otvet %d\n", --i);
else /* ustanovka tochki */
for(i=2; i < 10; i++)
printf( "probuem i=%d\n", i), test1(i);
}
test1(i){ int j;
for(j=1; j < 10 ; j++ )
printf( "probuem j=%d\n", j), test2(i,j);
}
test2(i, j){
printf( "probuem(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Godyatsya (%d,%d)\n", i,j);
longjmp(jmp, j + 1);
}
}
Obratite vnimanie, chto pri vozvrate otveta cherez vtoroj argument longjmp my pribavili
1, a pri pechati otveta my etu edinicu otnyali. |to sdelano na sluchaj otveta j==0,
chtoby funkciya setjmp ne vernula by v etom sluchae znachenie 0 (priznak ustanovki kont-
rol'noj tochki).
6.7.2. V chem oshibka?
#include <setjmp.h>
A. Bogatyrev, 1992-95 - 235 - Si v UNIX
jmp_buf jmp;
main(){
g();
longjmp(jmp,1);
}
g(){ printf("Vyzvana g\n");
f();
printf("Vyhozhu iz g\n");
}
f(){
static n;
printf( "Vyzvana f\n");
setjmp(jmp);
printf( "Vyhozhu iz f %d-yj raz\n", ++n);
}
Otvet: longjmp delaet pryzhok v funkciyu f(), iz kotoroj uzhe proizoshel vozvrat upravle-
niya. Pri perehode v telo funkcii v obhod ee zagolovka ne vypolnyayutsya mashinnye komandy
"prologa" funkcii - funkciya ostaetsya "neaktivirovannoj". Pri vozvrate iz vyzvannoj
takim "nelegal'nym" putem funkcii voznikaet oshibka, i programma padaet. Moral': v
funkciyu, kotoraya NIKEM NE VYZVANA, nel'zya peredavat' upravlenie. Obratnyj pryzhok -
iz f() v main() - byl by zakonen, poskol'ku funkciya main() yavlyaetsya aktivnoj, kogda
upravlenie nahoditsya v tele funkcii f(). T.e. mozhno "prygat'" iz vyzvannoj funkcii v
vyzyvayushchuyu: iz f() v main() ili v g(); i iz g() v main();
-- --
| f | stek prygat'
| g | vyzovov sverhu vniz
| main | funkcij mozhno - eto sootvetstvuet
---------- vykidyvaniyu neskol'kih
verhnih sloev steka
no nel'zya naoborot: iz main() v g() ili f(); a takzhe iz g() v f(). Mozhno takzhe
sovershat' pryzhok v predelah odnoj i toj zhe funkcii:
f(){ ...
A: setjmp(jmp);
...
longjmp(jmp, ...); ...
/* eto kak by goto A; */
}
6.8. Hozyain fajla, processa, i proverka privelegij.
UNIX - mnogopol'zovatel'skaya sistema. |to znachit, chto odnovremenno na raznyh
terminalah, podklyuchennyh k mashine, mogut rabotat' raznye pol'zovateli (a mozhet i odin
na neskol'kih terminalah). Na kazhdom terminale rabotaet svoj interpretator komand,
yavlyayushchijsya potomkom processa /etc/init.
6.8.1. Teper' - pro funkcii, pozvolyayushchie uznat' nekotorye dannye pro lyubogo pol'zo-
vatelya sistemy. Kazhdyj pol'zovatel' v UNIX imeet unikal'nyj nomer: identifikator
pol'zovatelya (user id), a takzhe unikal'noe imya: registracionnoe imya, kotoroe on nabi-
raet dlya vhoda v sistemu. Vsya informaciya o pol'zovatelyah hranitsya v fajle
/etc/passwd. Sushchestvuyut funkcii, pozvolyayushchie po nomeru pol'zovatelya uznat' registra-
cionnoe imya i naoborot, a zaodno poluchit' eshche nekotoruyu informaciyu iz passwd:
A. Bogatyrev, 1992-95 - 236 - Si v UNIX
#include <stdio.h>
#include <pwd.h>
struct passwd *p;
int uid; /* nomer */
char *uname; /* reg. imya */
uid = getuid();
p = getpwuid( uid );
...
p = getpwnam( uname );
|ti funkcii vozvrashchayut ukazateli na staticheskie struktury, skrytye vnutri etih funk-
cij. Struktury eti imeyut polya:
p->pw_uid identif. pol'zovatelya (int uid);
p->pw_gid identif. gruppy pol'zovatelya;
i ryad polej tipa char[]
p->pw_name registracionnoe imya pol'zovatelya (uname);
p->pw_dir polnoe imya domashnego kataloga
(kataloga, stanovyashchegosya tekushchim pri vhode v sistemu);
p->pw_shell interpretator komand
(esli "", to imeetsya v vidu /bin/sh);
p->pw_comment proizvol'naya uchetnaya informaciya (ne ispol'zuetsya);
p->pw_gecos proizvol'naya uchetnaya informaciya (obychno FIO);
p->pw_passwd zashifrovannyj parol' dlya vhoda v
sistemu. Istinnyj parol' nigde ne hranitsya vovse!
Funkcii vozvrashchayut znachenie p==NULL, esli ukazannyj pol'zovatel' ne sushchestvuet (nap-
rimer, esli zadan nevernyj uid). uid hozyaina dannogo processa mozhno uznat' vyzovom
getuid, a uid vladel'ca fajla - iz polya st_uid struktury, zapolnyaemoj sistemnym vyzo-
vom stat (a identifikator gruppy vladel'ca - iz polya st_gid). Zadanie: modificirujte
nash analog programmy ls, chtoby on vydaval v tekstovom vide imya vladel'ca kazhdogo
fajla v kataloge.
6.8.2. Vladelec fajla mozhet izmenit' svoemu fajlu identifikatory vladel'ca i gruppy
vyzovom
chown(char *imyaFajla, int uid, int gid);
t.e. "podarit'" fajl drugomu pol'zovatelyu. Zabrat' chuzhoj fajl sebe nevozmozhno. Pri
etoj operacii bity S_ISUID i S_ISGID v kodah dostupa k fajlu (sm. nizhe) sbrasyvayutsya,
poetomu sozdat' "Troyanskogo konya" i, sdelav ego hozyainom superpol'zovatelya, poluchit'
neogranichennye privelegii - ne udastsya!
6.8.3. Kazhdyj fajl imeet svoego vladel'ca (pole di_uid v I-uzle na diske ili pole
i_uid v kopii I-uzla v pamyati yadra|-). Kazhdyj process takzhe imeet svoego vladel'ca
(polya u_uid i u_ruid v u-area). Kak my vidim, process imeet dva parametra, oboznacha-
yushchie vladel'ca. Pole ruid nazyvaetsya "real'nym identifikatorom" pol'zovatelya, a uid -
"effektivnym identifikatorom". Pri vyzove exec() zamenyaetsya programma, vypolnyaemaya
dannym processom:
____________________
|- Pri otkrytii fajla i voobshche pri lyuboj operacii s fajlom, v tablicah yadra zavo-
ditsya kopiya I-uzla (dlya uskoreniya dostupa, chtoby postoyanno ne obrashchat'sya k disku).
Esli I-uzel v pamyati budet izmenen, to pri za