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