if((stf.st_mode & S_IFMT) == S_IFREG){ /* Proverka nalichiya svobodnogo mesta v kataloge dirname */ struct statvfs fs; char tmpbuf[MAXPATHLEN+1]; if(dirname == NULL){ /* To 'to' - eto imya fajla, a ne kataloga */ strcpy(tmpbuf, to); if(s = strrchr(tmpbuf, '/')){ if(*tmpbuf != '/' || s != tmpbuf){ /* Imena "../xxx" * i vtoroj sluchaj: * absolyutnye imena ne v korne, * to est' ne "/" i ne "/xxx" */ *s = '\0'; }else{ /* "/" ili "/xxx" */ if(s[1]) s[1] = '\0'; } dirname = tmpbuf; } else dirname = "."; } if(statvfs(dirname, &fs) >= 0){ size_t size = (geteuid() == 0 ) ? /* Dostupno superpol'zovatelyu: bajt */ fs.f_frsize * fs.f_bfree : /* Dostupno obychnomu pol'zovatelyu: bajt */ fs.f_frsize * fs.f_bavail; if(size < stf.st_size){ error("Not enough free space on %s: have %lu, need %lu", dirname, size, stf.st_size); close(fdin); return (-5); } } } if((fdout = creat(to, stf.st_mode)) < 0){ error("Can't create %s", to); close(fdin); return (-6); } else { fchmod(fdout, stf.st_mode); fchown(fdout, stf.st_uid, stf.st_gid); } A. Bogatyrev, 1992-95 - 211 - Si v UNIX while (n = read (fdin, iobuf, sizeof iobuf)) { if(n < 0){ error ("read error"); code = (-7); goto done; } if(write (fdout, iobuf, n) != n) { error ("write error"); code = (-8); goto done; } } done: close (fdin); close (fdout); /* Proverit': sootvetstvuet li rezul'tat ozhidaniyam */ if(stat(to, &stt) >= 0 && (stt.st_mode & S_IFMT) == S_IFREG){ if(stf.st_size < stt.st_size){ error("File has grown at the time of copying"); } else if(stf.st_size > stt.st_size){ error("File too short, target %s removed", to); unlink(to); code = (-9); } } return code; } int main(int argc, char *argv[]){ int i, code = 0; progname = argv[0]; if(argc < 3){ error("Usage: %s from... to", argv[0]); return 1; } for(i=1; i < argc-1; i++) code |= copyFile(argv[argc-1], argv[i]) < 0 ? 1 : 0; return code; } Vozvrashchaemaya struktura struct statvfs soderzhit takie polya (v chastnosti): Tipa long: f_frsize razmer bloka f_blocks razmer fajlovoj sistemy v blokah f_bfree svobodnyh blokov (dlya superpol'zovatelya) f_bavail svobodnyh blokov (dlya vseh ostal'nyh) f_files chislo I-nodes v fajlovoj sisteme f_ffree svobodnyh I-nodes (dlya superpol'zovatelya) f_favail svobodnyh I-nodes (dlya vseh ostal'nyh) Tipa char * f_basetype tip fajlovoj sistemy: ufs, nfs, ... A. Bogatyrev, 1992-95 - 212 - Si v UNIX Po dva znacheniya dano potomu, chto operacionnaya sistema rezerviruet chast' fajlovoj sis- temy dlya ispol'zovaniya TOLXKO superpol'zovatelem (chtoby administrator smog raspihat' fajly v sluchae perepolneniya diska, i imel rezerv na eto). ufs - eto UNIX file system iz BSD 4.x 6.4. Signaly. Processy v UNIX ispol'zuyut mnogo raznyh mehanizmov vzaimodejstviya. Odnim iz nih yavlyayutsya signaly. Signaly - eto asinhronnye sobytiya. CHto eto znachit? Snachala ob®yasnim, chto takoe sinhronnye sobytiya: ya dva raza v den' podhozhu k pochtovomu yashchiku i proveryayu - net li v nem pochty (sobytij). Vo-pervyh, ya proizvozhu opros - "net li dlya menya sobytiya?", v programme eto vyglyadelo by kak vyzov funkcii oprosa i, mozhet byt', ozhidaniya sobytiya. Vo-vtoryh, ya znayu, chto pochta mozhet ko mne prijti, poskol'ku ya podpisalsya na kakie-to gazety. To est' ya predvaritel'no zakazyval eti sobytiya. Shema s sinhronnymi sobytiyami ochen' rasprostranena. Kassir sidit u kassy i ozhi- daet, poka k nemu v okoshechko ne zaglyanet klient. Poezd periodicheski proezzhaet mimo svetofora i ostanavlivaetsya, esli gorit krasnyj. Funkciya Si passivno "spit" do teh por, poka ee ne vyzovut; odnako ona vsegda gotova vypolnit' svoyu rabotu (obsluzhit' klienta). Takoe ozhidayushchee zakaza (sobytiya) dejstvuyushchee lico nazyvaetsya server. Posle vypolneniya zakaza server vnov' perehodit v sostoyanie ozhidaniya vyzova. Itak, esli sobytie ozhidaetsya v special'nom meste i v opredelennye momenty vremeni (izdaetsya nekij vyzov dlya OPROSA) - eto sinhronnye sobytiya. Kanonicheskij primer - funkciya gets, kotoraya zaderzhit vypolnenie programmy, poka s klaviatury ne budet vvedena stroka. Bol'shinstvo ozhidanij vnutri sistemnyh vyzovov - sinhronny. YAdro OS vystu- paet dlya programm pol'zovatelej v roli servera, vypolnyayushchego sisvyzovy (hotya i ne tol'ko v etoj roli - yadro inogda predprinimaet i aktivnye dejstviya: peredacha proces- sora drugomu processu cherez opredelennoe vremya (rezhim razdeleniya vremeni), ubivanie processa pri oshibke, i.t.p.). Signaly - eto asinhronnye sobytiya. Oni prihodyat neozhidanno, v lyuboj moment vre- meni - vrode telefonnogo zvonka. Krome togo, ih ne trebuetsya zakazyvat' - signal processu mozhet postupit' sovsem bez povoda. Analogiya iz zhizni takova: chelovek sidit i pishet pis'mo. Vdrug ego oklikayut posredi frazy - on otvlekaetsya, otvechaet na vop- ros, i vnov' prodolzhaet prervannoe zanyatie. CHelovek ne ozhidal etogo oklika (byt' mozhet, on gotov k nemu, no on ne oziralsya po storonam special'no). Krome togo, sig- nal mog postupit' kogda on pisal 5-oe predlozhenie, a mog - kogda 34-oe. Moment vre- meni, v kotoryj proizojdet preryvanie, ne fiksirovan. Signaly imeyut nomera, prichem ih kolichestvo ogranicheno - est' opredelennyj spisok dopustimyh signalov. Nomera i mnemonicheskie imena signalov perechisleny v include- fajle <signal.h> i imeyut vid SIGnechto. Dopustimy signaly s nomerami 1..NSIG-1, gde NSIG opredeleno v etom fajle. Pri poluchenii signala my uznaem ego nomer, no ne uznaem nikakoj inoj informacii: ni ot kogo postupil signal, ni chto ot nas hotyat. Prosto "zvonit telefon". CHtoby poluchit' dopolnitel'nuyu informaciyu, nash process dolzhen vzyat' ee iz drugogo izvestnogo mesta; naprimer - prochest' zakaz iz nekotorogo fajla, ob imeni kotorogo vse nashi programmy zaranee "dogovorilis'". Signaly processu mogut postupat' tremya putyami: - Ot drugogo processa, kotoryj yavno posylaet ego nam vyzovom kill(pid, sig); gde pid - identifikator (nomer) processa-poluchatelya, a sig - nomer signala. Poslat' signal mozhno tol'ko rodstvennomu processu - zapushchennomu tem zhe pol'zova- telem. - Ot operacionnoj sistemy. Sistema mozhet posylat' processu ryad signalov, signali- ziruyushchih ob oshibkah, naprimer pri obrashchenii programmy po nesushchestvuyushchemu adresu ili pri oshibochnom nomere sistemnogo vyzova. Takie signaly obychno prekrashchayut nash process. - Ot pol'zovatelya - s klaviatury terminala mozhno nazhimom nekotoryh klavish poslat' signaly SIGINT i SIGQUIT. Sobstvenno, signal posylaetsya drajverom terminala pri poluchenii im s klaviatury opredelennyh simvolov. Tak mozhno prervat' zaciklivshu- yusya ili nadoevshuyu programmu. Process-poluchatel' dolzhen kak-to otreagirovat' na signal. Programma mozhet: A. Bogatyrev, 1992-95 - 213 - Si v UNIX - proignorirovat' signal (ne otvetit' na zvonok); - perehvatit' signal (snyat' trubku), vypolnit' kakie-to dejstviya, zatem prodolzhit' prervannoe zanyatie; - byt' ubitoj signalom (zvonok byl podkreplen broskom granaty v okno); V bol'shinstve sluchaev signal po umolchaniyu ubivaet process-poluchatel'. Odnako process mozhet izmenit' eto umolchanie i zadat' svoyu reakciyu yavno. |to delaetsya vyzovom signal: #include <signal.h> void (*signal(int sig, void (*react)() )) (); Parametr react mozhet imet' znachenie: SIG_IGN signal sig budet otnyne ignorirovat'sya. Nekotorye signaly (naprimer SIGKILL) nevozmozhno perehvatit' ili proignorirovat'. SIG_DFL vosstanovit' reakciyu po umolchaniyu (obychno - smert' poluchatelya). imya_funkcii Naprimer void fr(gotsig){ ..... } /* obrabotchik */ ... signal (sig, fr); ... /* zadanie reakcii */ Togda pri poluchenii signala sig budet vyzvana funkciya fr, v kotoruyu v kachestve argumenta sistemoj budet peredan nomer signala, dejstvitel'no vyzvavshego ee - gotsig==sig. |to polezno, t.k. mozhno zadat' odnu i tu zhe funkciyu v kachestve reakcii dlya neskol'kih signalov: ... signal (sig1, fr); signal(sig2, fr); ... Posle vozvrata iz funkcii fr() programma prodolzhitsya s prervannogo mesta. Pered vyzovom funkcii-obrabotchika reakciya avtomaticheski sbrasyvaetsya v reakciyu po umolchaniyu SIG_DFL, a posle vyhoda iz obrabotchika snova vosstanavlivaetsya v fr. |to znachit, chto vo vremya raboty funkcii-obrabotchika mozhet prijti signal, kotoryj ub'et programmu. Privedem spisok nekotoryh signalov; polnoe opisanie posmotrite v dokumentacii. Kolonki tablicy: G - mozhet byt' perehvachen; D - po umolchaniyu ubivaet process (k), ignoriruetsya (i); C - obrazuetsya damp pamyati processa: fajl core, kotoryj zatem mozhet byt' issledovan otladchikom adb; F - reakciya na signal sbrasyvaetsya; S - posylaetsya obychno sistemoj, a ne yavno. signal G D C F S smysl SIGTERM + k - + - zavershit' process SIGKILL - k - + - ubit' process SIGINT + k - + - preryvanie s klavish SIGQUIT + k + + - preryvanie s klavish SIGALRM + k - + + budil'nik SIGILL + k + - + zapreshchennaya komanda SIGBUS + k + + + obrashchenie po nevernomu SIGSEGV + k + + + adresu SIGUSR1, USR2 + i - + - pol'zovatel'skie SIGCLD + i - + + smert' potomka - Signal SIGILL ispol'zuetsya inogda dlya emulyacii komand s plavayushchej tochkoj, chto proishodit primerno tak: pri obnaruzhenii "zapreshchennoj" komandy dlya otsutstvuyu- shchego processora "plavayushchej" arifmetiki apparatura daet preryvanie i sistema posylaet processu signal SIGILL. Po signalu vyzyvaetsya funkciya-emulyator plavayu- shchej arifmetiki (podklyuchaemaya k vypolnyaemomu fajlu avtomaticheski), kotoraya i obrabatyvaet trebuemuyu komandu. |to mozhet proishodit' mnogo raz, imenno poetomu A. Bogatyrev, 1992-95 - 214 - Si v UNIX reakciya na etot signal ne sbrasyvaetsya. - SIGALRM posylaetsya v rezul'tate ego zakaza vyzovom alarm() (sm. nizhe). - Signal SIGCLD posylaetsya processu-roditelyu pri vypolnenii processom-potomkom sisvyzova exit (ili pri smerti vsledstvie polucheniya signala). Obychno process- roditel' pri poluchenii takogo signala (esli on ego zakazyval) reagiruet, vypol- nyaya v obrabotchike signala vyzov wait (sm. nizhe). Po-umolchaniyu etot signal igno- riruetsya. - Reakciya SIG_IGN ne sbrasyvaetsya v SIG_DFL pri prihode signala, t.e. signal igno- riruetsya postoyanno. - Vyzov signal vozvrashchaet staroe znachenie reakcii, kotoroe mozhet byt' zapomneno v peremennuyu vida void (*f)(); a potom vosstanovleno. - Sinhronnoe ozhidanie (sisvyzov) mozhet inogda byt' prervano asinhronnym sobytiem (signalom), no ob etom nizhe. Nekotorye versii UNIX predostavlyayut bolee razvitye sredstva raboty s signalami. Opishem nekotorye iz sredstv, imeyushchihsya v BSD (v drugih sistemah oni mogut byt' smode- lirovany drugimi sposobami). Pust' u nas v programme est' "kriticheskaya sekciya", vo vremya vypolneniya kotoroj prihod signalov nezhelatelen. My mozhem "zamorozit'" (zablokirovat') signal, otlozhiv moment ego postupleniya do "razmorozki": | sighold(sig); zablokirovat' signal | : KRITICHESKAYA :<---processu poslan signal sig, SEKCIYA : no on ne vyzyvaet reakciyu nemedlenno, | : a "visit", ozhidaya razresheniya. | : sigrelse(sig); razblokirovat' |<----------- sig | nakopivshiesya signaly dohodyat, | vyzyvaetsya reakciya. Esli vo vremya blokirovki processu bylo poslano neskol'ko odinakovyh signalov sig, to pri razblokirovanii postupit tol'ko odin. Postuplenie signalov vo vremya blokirovki prosto otmechaetsya v special'noj bitovoj shkale v pasporte processa (primerno tak): mask |= (1 << (sig - 1)); i pri razblokirovanii signala sig, esli sootvetstvuyushchij bit vystavlen, to prihodit odin takoj signal (sistema vyzyvaet funkciyu reakcii). To est' sighold zastavlyaet prihodyashchie signaly "nakaplivat'sya" v special'noj maske, vmesto togo, chtoby nemedlenno vyzyvat' reakciyu na nih. A sigrelse razreshaet "nako- pivshimsya" signalam (esli oni est') prijti i vyzyvaet reakciyu na nih. Funkciya sigset(sig, react); analogichna funkcii signal, za isklyucheniem togo, chto na vremya raboty obrabotchika sig- nala react, prihod signala sig blokiruetsya; to est' pered vyzovom react kak by dela- etsya sighold, a pri vyhode iz obrabotchika - sigrelse. |to znachit, chto esli vo vremya raboty obrabotchika signala pridet takoj zhe signal, to programma ne budet ubita, a "zapomnit" prishedshij signal, i obrabotchik budet vyzvan povtorno (kogda srabotaet sigrelse). Funkciya sigpause(sig); vyzyvaetsya vnutri "ramki" sighold(sig); ... sigpause(sig); ... sigrelse(sig); A. Bogatyrev, 1992-95 - 215 - Si v UNIX i vyzyvaet zaderzhku vypolneniya processa do prihoda signala sig. Funkciya razreshaet prihod signala sig (obychno na nego dolzhna byt' zadana reakciya pri pomoshchi sigset), i "zasypaet" do prihoda signala sig. V UNIX standarta POSIX dlya upravleniya signalami est' vyzovy sigaction, sigproc- mask, sigpending, sigsuspend. Posmotrite v dokumentaciyu! 6.4.1. Napishite programmu, vydayushchuyu na ekran fajl /etc/termcap. Perehvatyvajte sig- nal SIGINT, pri poluchenii signala zaprashivajte "Prodolzhat'?". Po otvetu 'y' - pro- dolzhit' vydachu; po 'n' - zavershit' programmu; po 'r' - nachat' vydavat' fajl s nachala: lseek(fd,0L,0). Ne zabud'te zanovo pereustanovit' reakciyu na SIGINT, poskol'ku posle polucheniya signala reakciya avtomaticheski sbrasyvaetsya. #include <signal.h> void onintr(sig){ /* sig - nomer signala */ signal (sig, onintr); /* vosstanovit' reakciyu */ ... zapros i dejstviya ... } main(){ signal (SIGINT, onintr); ... } Signal preryvaniya mozhno ignorirovat'. |to delaetsya tak: signal (SIGINT, SIG_IGN); Takuyu programmu nel'zya prervat' s klaviatury. Napomnim, chto reakciya SIG_IGN sohranya- etsya pri prihode signala. 6.4.2. Sistemnyj vyzov, nahodyashchijsya v sostoyanii ozhidaniya kakogo-to sobytiya (read zhdushchij nazhatiya knopki na klaviature, wait zhdushchij okonchaniya processa-potomka, i.t.p.), mozhet byt' prervan signalom. Pri etom sisvyzov vernet znachenie "oshibka" (-1) i errno stanet ravno EINTR. |to pozvolyaet nam pisat' sistemnye vyzovy s vystavleniem tajma- uta: esli sobytie ne proishodit v techenie zadannogo vremeni, to zavershit' ozhidanie i prervat' sisvyzov. Dlya etoj celi ispol'zuetsya vyzov alarm(sec), zakazyvayushchij posylku signala SIGALRM nashej programme cherez celoe chislo sec sekund (0 - otmenyaet zakaz): #include <signal.h> void (*oldaction)(); int alarmed; /* prozvonil budil'nik */ void onalarm(nsig){ alarmed++; } ... /* ustanovit' reakciyu na signal */ oldaction = signal (SIGALRM, onalarm); /* zakazat' budil'nik cherez TIMEOUT sek. */ alarmed = 0; alarm ( TIMEOUT /* sec */ ); sys_call(...); /* zhdet sobytiya */ // esli nas sbil signal, to po signalu budet // eshche vyzvana reakciya na nego - onalarm if(alarmed){ // sobytie tak i ne proizoshlo. // vyzov prervan signalom t.k. isteklo vremya. }else{ alarm(0); /* otmenit' zakaz signala */ // sobytie proizoshlo, sisvyzov uspel // zavershit'sya do istecheniya vremeni. } signal (SIGALRM, oldaction); Napishite programmu, kotoraya ozhidaet vvoda s klaviatury v techenie 10 sekund. Esli nichego ne vvedeno - pechataet "Net vvoda", inache - pechataet "Spasibo". Dlya vvoda mozhno ispol'zovat' kak vyzov read, tak i funkciyu gets (ili getchar), poskol'ku A. Bogatyrev, 1992-95 - 216 - Si v UNIX funkciya eta vse ravno vnutri sebya izdaet sistemnyj vyzov read. Issledujte, kakoe znachenie vozvrashchaet fgets (gets) v sluchae preryvaniya ee sistemnym vyzovom. /* Kopirovanie standartnogo vvoda na standartnyj vyvod * s ustanovlennym tajm-autom. * |to pozvolyaet ispol'zovat' programmu dlya chteniya iz FIFO-fajlov * i s klaviatury. * Nebol'shaya modifikaciya pozvolyaet ispol'zovat' programmu * dlya kopirovaniya "rastushchego" fajla (t.e. takogo, kotoryj v * nastoyashchij moment eshche prodolzhaet zapisyvat'sya). * Zamechanie: * V DEMOS-2.2 signal NE sbivaet chtenie iz FIFO-fajla, * a poluchenie signala otkladyvaetsya do vyhoda iz read() * po uspeshnomu chteniyu informacii. Pol'zujtes' open()-om * s flagom O_NDELAY, chtoby poluchit' trebuemyj effekt. * * Vyzov: a.out /dev/tty * * Po motivam knigi M.Dansmura i G.Dejvisa. */ #define WAIT_TIME 5 /* zhdat' 5 sekund */ #define MAX_TRYS 5 /* maksimum 5 popytok */ #define BSIZE 256 #define STDIN 0 /* deskriptor standartnogo vvoda */ #define STDOUT 1 /* deskriptor standartnogo vyvoda */ #include <signal.h> #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> char buffer [ BSIZE ]; extern int errno; /* kod oshibki */ void timeout(nsig){ signal( SIGALRM, timeout ); } void main(argc, argv) char **argv;{ int fd, n, trys = 0; struct stat stin, stout; if( argc != 2 ){ fprintf(stderr, "Vyzov: %s fajl\n", argv[0]); exit(1); } if((fd = !strcmp(argv[1],"-")? STDIN : open(argv[1],O_RDONLY)) < 0){ fprintf(stderr, "Ne mogu chitat' %s\n", argv[1]); exit(2); } /* Proverit', chto vvod ne sovpadaet s vyvodom, * hardcat aFile >> aFile * krome sluchaya, kogda vyvod - terminal. * Takaya proverka polezna dlya programm-fil'trov (STDIN->STDOUT), * chtoby isklyuchit' porchu ishodnoj informacii */ fstat(fd, &stin); fstat(STDOUT, &stout); if( !isatty(STDOUT) && stin.st_ino == stout.st_ino && stin.st_dev == stout.st_dev ){ fprintf(stderr, "\aVvod == vyvodu, vozmozhno poteryana informaciya v %s.\n",argv[1]); exit(33); } A. Bogatyrev, 1992-95 - 217 - Si v UNIX signal( SIGALRM, timeout ); while( trys < MAX_TRYS ){ alarm( WAIT_TIME ); /* zakazat' signal cherez 5 sek */ /* i zhdem vvoda ... */ n = read( fd, buffer, BSIZE ); alarm(0); /* otmenili zakaz signala */ /* (hotya, vozmozhno, on uzhe poluchen) */ /* proveryaem: pochemu my slezli s vyzova read() ? */ if( n < 0 && errno == EINTR ){ /* My byli sbity signalom SIGALRM, * kod oshibki EINTR - sisvyzov prervan * nekim signalom. */ fprintf( stderr, "\7timed out (%d raz)\n", ++trys ); continue; } if( n < 0 ){ /* oshibka chteniya */ fprintf( stderr, "read error.\n" ); exit(4); } if( n == 0 ){ /* dostignut konec fajla */ fprintf( stderr, "Dostignut EOF.\n\n" ); exit(0); } /* kopiruem prochitannuyu informaciyu */ write( STDOUT, buffer, n ); trys = 0; } fprintf( stderr, "Vse popytki provalilis'.\n" ); exit(5); } Esli my hotim, chtoby sisvyzov ne mog preryvat'sya signalom, my dolzhny zashchitit' ego: #include <signal.h> void (*fsaved)(); ... fsaved = signal (sig, SIG_IGN); sys_call(...); signal (sig, fsaved); ili tak: sighold(sig); sys_call(...); sigrelse(sig); Signalami mogut byt' prervany ne vse sistemnye vyzovy i ne pri vseh obstoyatel'stvah. 6.4.3. Napishite funkciyu sleep(n), zaderzhivayushchuyu vypolnenie programmy na n sekund. Vospol'zujtes' sistemnym vyzovom alarm(n) (budil'nik) i vyzovom pause(), kotoryj zaderzhivaet programmu do polucheniya lyubogo signala. Predusmotrite restart pri poluche- nii vo vremya ozhidaniya drugogo signala, nezheli SIGALRM. Sohranyajte zakaz alarm, sde- lannyj do vyzova sleep (alarm vydaet chislo sekund, ostavsheesya do zaversheniya predydu- shchego zakaza). Na samom dele est' takaya STANDARTNAYA funkciya. Otvet: A. Bogatyrev, 1992-95 - 218 - Si v UNIX #include <sys/types.h> #include <stdio.h> #include <signal.h> int got; /* prishel li signal */ void onalarm(int sig) { printf( "Budil'nik\n" ); got++; } /* signal poluchen */ void sleep(int n){ time_t time(), start = time(NULL); void (*save)(); int oldalarm, during = n; if( n <= 0 ) return; got = 0; save = signal(SIGALRM, onalarm); oldalarm = alarm(3600); /* Uznat' staryj zakaz */ if( oldalarm ){ printf( "Byl zakazan signal, kotoryj pridet cherez %d sek.\n", oldalarm ); if(oldalarm > n) oldalarm -= n; else { during = n = oldalarm; oldalarm = 1; } } printf( "n=%d oldalarm=%d\n", n, oldalarm ); while( n > 0 ){ printf( "alarm(%d)\n", n ); alarm(n); /* zakazat' SIGALRM cherez n sekund */ pause(); if(got) break; /* inache my sbity s pause drugim signalom */ n = during - (time(NULL) - start); /* proshlo vremeni */ } printf( "alarm(%d) pri vyhode\n", oldalarm ); alarm(oldalarm); /* alarm(0) - otmena zakaza signala */ signal(SIGALRM, save); /* vosstanovit' reakciyu */ } void onintr(int nsig){ printf( "Signal SIGINT\n"); signal(SIGINT, onintr); } void onOldAlarm(int nsig){ printf( "Zvonit staryj budil'nik\n"); } void main(){ int time1 = 0; /* 5, 10, 20 */ setbuf(stdout, NULL); signal(SIGINT, onintr); signal(SIGALRM, onOldAlarm); alarm(time1); sleep(10); if(time1) pause(); printf("CHao!\n"); } A. Bogatyrev, 1992-95 - 219 - Si v UNIX 6.4.4. Napishite "chasy", vydayushchie tekushchee vremya kazhdye 3 sekundy. #include <signal.h> #include <time.h> #include <stdio.h> void tick(nsig){ time_t tim; char *s; signal (SIGALRM, tick); alarm(3); time(&tim); s = ctime(&tim); s[ strlen(s)-1 ] = '\0'; /* obrubit' '\n' */ fprintf(stderr, "\r%s", s); } main(){ tick(0); for(;;) pause(); } 6.5. ZHizn' processov. 6.5.1. Kakie klassy pamyati imeyut dannye, v kakih segmentah programmy oni raspolo- zheny? char x[] = "hello"; int y[25]; char *p; main(){ int z = 12; int v; static int w = 25; static int q; char s[20]; char *pp; ... v = w + z; /* #1 */ } Otvet: Peremennaya Klass pamyati Segment Nachal'noe znachenie x static data/DATA "hello" y static data/BSS {0, ..., 0} p static data/BSS NULL z auto stack 12 v auto stack ne opredeleno w static data/DATA 25 q static data/BSS 0 s auto stack ne opredeleno pp auto stack ne opredeleno main static text/TEXT Bol'shimi bukvami oboznacheny segmenty, hranimye v vypolnyaemom fajle: DATA - eto inicializirovannye staticheskie dannye (kotorym prisvoeny nachal'nye znache- niya). Oni pomeshchayutsya kompilyatorom v fajl v vide gotovyh konstant, a pri zapuske programmy (pri ee zagruzke v pamyat' mashiny), prosto kopiruyutsya v pamyat' iz fajla. BSS (Block Started by Symbol) - neinicializirovannye staticheskie dannye. Oni po umolchaniyu imeyut nachal'noe zna- chenie 0 (NULL, "", '\0'). |ta pamyat' raspisyvaetsya nulyami pri zapuske prog- rammy, a v fajle hranitsya lish' ee razmer. A. Bogatyrev, 1992-95 - 220 - Si v UNIX TEXT - segment, soderzhashchij mashinnye komandy (kod). Hranyashchayasya v fajle vypolnyaemaya programma imeet takzhe zagolovok - v nem v chastnosti soderzhatsya razmery perechislennyh segmentov i ih mestopolozhenie v fajle; i eshche - v samom konce fajla - tablicu imen. V nej soderzhatsya imena vseh funkcij i peremennyh, ispol'zuemyh v programme, i ih adresa. |ta tablica ispol'zuetsya otladchikami adb i sdb, a takzhe pri sborke programmy iz neskol'kih ob®ektnyh fajlov programmoj ld. Prosmotret' ee mozhno komandoj nm imyaFajla Dlya ekonomii diskovogo prostranstva etu tablicu chasto udalyayut, chto delaetsya komandoj strip imyaFajla Razmery segmentov mozhno uznat' komandoj size imyaFajla Programma, zagruzhennaya v pamyat' komp'yutera (t.e. process), sostoit iz 3x segmentov, otnosyashchihsya neposredstvenno k programme: stack - stek dlya lokal'nyh peremennyh funkcij (avtomaticheskih peremennyh). |tot seg- ment sushchestvuet tol'ko u vypolnyayushchejsya programmy, poskol'ku otvedenie pamyati v steke proizvoditsya vypolneniem nekotoryh mashinnyh komand (poetomu opisanie avto- maticheskih peremennyh v Si - eto na samom dele vypolnyaemye operatory, hotya i ne s tochki zreniya yazyka). Segment steka avtomaticheski rastet po mere nadobnosti (esli my vyzyvaem novye i novye funkcii, otvodyashchie peremennye v steke). Za etim sledit apparatura dispetchera pamyati. data - segment, v kotoryj skleeny segmenty staticheskih dannyh DATA i BSS, zagruzhennye iz fajla. |tot segment takzhe mozhet izmenyat' svoj razmer, no delat' eto nado yavno - sistemnymi vyzovami sbrk ili brk. V chastnosti, funkciya malloc() dlya raz- meshcheniya dinamicheski otvodimyh dannyh uvelichivaet razmer etogo segmenta. text - eto vypolnyaemye komandy, kopiya segmenta TEXT iz fajla. Tak stroka s metkoj #1 soderzhitsya v vide mashinnyh komand imenno v etom segmente. Krome togo, kazhdyj process imeet eshche: proc - eto rezidentnaya chast' pasporta processa v tablice processov v yadre operacion- noj sistemy; user - eto 4-yj segment processa - nerezidentnaya chast' pasporta (u-area). K etomu segmentu imeet dostup tol'ko yadro, no ne sama programma. Pasport processa byl podelen na 2 chasti tol'ko iz soobrazhenij ekonomii pamyati v yadre: kontekst processa (tablica otkrytyh fajlov, ssylka na I-uzel tekushchego kataloga, tab- lica reakcij na signaly, ssylka na I-uzel upravlyayushchego terminala, i.t.p.) nuzhen yadru tol'ko pri obsluzhivanii tekushchego aktivnogo processa. Kogda aktiven drugoj process - eta informaciya v pamyati yadra ne nuzhna. Bolee togo, esli process iz-za nehvatki mesta v pamyati mashiny byl otkachan na disk, eta informaciya takzhe mozhet byt' otkachana na disk i podkachana nazad lish' vmeste s processom. Poetomu kontekst byl vydelen v otdel'nyj segment, i segment etot podklyuchaetsya k adresnomu prostranstvu yadra lish' pri vypolne- nii processom kakogo-libo sistemnogo vyzova (eto podklyuchenie nazyvaetsya "pereklyuchenie konteksta" - context switch). CHetyre segmenta processa mogut raspolagat'sya v pamyati mashiny ne obyazatel'no podryad - mezhdu nimi mogut lezhat' segmenty drugih processov. Shema sostavnyh chastej processa: P R O C E S S tablica processov: pasport v yadre segmenty v pamyati struct proc[] ####---------------> stack 1 #### data 2 text 3 kontekst: struct user 4 A. Bogatyrev, 1992-95 - 221 - Si v UNIX Kazhdyj process imeet unikal'nyj nomer, hranyashchijsya v pole p_pid v strukture proc|-. V nej takzhe hranyatsya: adresa segmentov processa v pamyati mashiny (ili na diske, esli process otkachan); p_uid - nomer vladel'ca processa; p_ppid - nomer processa-roditelya; p_pri, p_nice - prioritety processa; p_pgrp - gruppa processa; p_wchan - ozhidaemoe processom sobytie; p_flag i p_stat - sostoyanie processa; i mnogoe drugoe. Struktura proc opredelena v include-fajle <sys/proc.h>, a struktura user - v <sys/user.h>. 6.5.2. Sistemnyj vyzov fork() (vilka) sozdaet novyj process: kopiyu processa, izdav- shego vyzov. Otlichie etih processov sostoit tol'ko v vozvrashchaemom fork-om znachenii: 0 - v novom processe. pid novogo processa - v ishodnom. Vyzov fork mozhet zavershit'sya neudachej esli tablica processov perepolnena. Prostejshij sposob sdelat' eto: main(){ while(1) if( ! fork()) pause(); } Odno gnezdo tablicy processov zarezervirovano - ego mozhet ispol'zovat' tol'ko super- pol'zovatel' (v celyah zhiznesposobnosti sistemy: hotya by dlya togo, chtoby zapustit' programmu, ubivayushchuyu vse eti processy-varvary). Vyzov fork sozdaet kopiyu vseh 4h segmentov processa i vydelyaet porozhdennomu pro- cessu novyj pasport i nomer. Inogda segment text ne kopiruetsya, a ispol'zuetsya pro- cessami sovmestno ("razdelyaemyj segment") v celyah ekonomii pamyati. Pri kopirovanii segmenta user kontekst porozhdayushchego processa nasleduetsya porozhdennym processom (sm. nizhe). Provedite opyt, dokazyvayushchij chto porozhdennyj sistemnym vyzovom fork() process i porodivshij ego - ravnopravny. Povtorite neskol'ko raz programmu: #include <stdio.h> int pid, i, fd; char c; main(){ fd = creat( "TEST", 0644); if( !(pid = fork())){ /* syn: porozhdennyj process */ c = 'a'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Syn %d okonchen\n", getpid()); exit(0); } /* else process-otec */ c = 'A'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Roditel' %d processa %d okonchen\n", getpid(), pid ); } V fajle TEST my budem ot sluchaya k sluchayu poluchat' stroki vida aABbCcDdEe ili AaBbcdCDEe chto govorit o tom, chto pervym "prosnut'sya" posle fork() mozhet lyuboj iz dvuh proces- sov. Esli zhe opyt daet ustojchivo stroki, nachinayushchiesya s odnoj i toj zhe bukvy - znachit ____________________ |- Process mozhet uznat' ego vyzovom pid=getpid(); A. Bogatyrev, 1992-95 - 222 - Si v UNIX v dannoj realizacii sistemy odin iz processov vse zhe zapuskaetsya ran'she. No ne stoit ispol'zovat' etot effekt - pri perenose na druguyu sistemu ego mozhet ne byt'! Dannyj opyt osnovan na sleduyushchem svojstve sistemy UNIX: pri sistemnom vyzove fork() porozhdennyj process poluchaet vse otkrytye porozhdayushchim processom fajly "v nas- ledstvo" - eto sootvetstvuet tomu, chto tablica otkrytyh processom fajlov kopiruetsya v process-potomok. Imenno tak, v chastnosti, peredayutsya ot otca k synu standartnye kanaly 0, 1, 2: porozhdennomu processu ne nuzhno otkryvat' standartnye vvod, vyvod i vyvod oshibok yavno. Iznachal'no zhe oni otkryvayutsya special'noj programmoj pri vashem vhode v sistemu. do vyzova fork(); tablica otkrytyh fajlov processa 0 ## ---<--- klaviatura 1 ## --->--- displej 2 ## --->--- displej ... ## fd ## --->--- fajl TEST ... ## posle fork(); PROCESS-PAPA PROCESS-SYN 0 ## ---<--- klaviatura --->--- ## 0 1 ## --->--- displej ---<--- ## 1 2 ## --->--- displej ---<--- ## 2 ... ## ## ... fd ## --->--- fajl TEST ---<--- ## fd ... ## | ## ... *--RWptr-->FAJL Ssylki iz tablic otkrytyh fajlov v processah ukazyvayut na struktury "otkrytyj fajl" v yadre (sm. glavu pro fajly). Takim obrazom, dva processa poluchayut dostup k odnoj i toj zhe strukture i, sledovatel'no, imeyut obshchij ukazatel' chteniya/zapisi dlya etogo fajla. Poetomu, kogda processy "otec" i "syn" pishut po deskriptoru fd, oni pol'zuyutsya odnim i tem zhe ukazatelem R/W, t.e. informaciya ot oboih processov zapisyvaetsya posle- dovatel'no. Na principe nasledovaniya i sovmestnogo ispol'zovaniya otkrytyh fajlov osnovan takzhe sistemnyj vyzov pipe. Porozhdennyj process nasleduet takzhe: reakcii na signaly (!!!), tekushchij katalog, upravlyayushchij terminal, nomer vladel'ca processa i gruppu vladel'ca, i.t.p. Pri sistemnom vyzove exec() (kotoryj zamenyaet programmu, vypolnyaemuyu processom, na programmu iz ukazannogo fajla) vse otkrytye kanaly takzhe dostayutsya v nasledstvo novoj programme (a ne zakryvayutsya). 6.5.3. Process-kopiya eto horosho, no ne sovsem to, chto nam hotelos' by. Nam hochetsya zapustit' programmu, soderzhashchuyusya v vypolnyaemom fajle (naprimer a.out). Dlya etogo sushchestvuet sistemnyj vyzov exec, kotoryj imeet neskol'ko raznovidnostej. Rassmotrim tol'ko dve: char *path; char *argv[], *envp[], *arg0, ..., *argn; execle(path, arg0, arg1, ..., argn, NULL, envp); execve(path, argv, envp); Sistemnyj vyzov exec zamenyaet programmu, vypolnyaemuyu dannym processom, na programmu, zagruzhaemuyu iz fajla path. V dannom sluchae path dolzhno byt' polnym imenem fajla ili imenem fajla ot tekushchego kataloga: /usr/bin/vi a.out ../mybin/xkick A. Bogatyrev, 1992-95 - 223 - Si v UNIX Fajl dolzhen imet' kod dostupa "vypolnenie". Pervye dva bajta fajla (v ego zago- lovke), rassmatrivaemye kak short int, soderzhat tak nazyvaemoe "magicheskoe chislo" (A_MAGIC), svoe dlya kazhdogo tipa mashin (smotri include-fajl <a.out.h>). Ego pomeshchaet v nachalo vypolnyaemogo fajla redaktor svyazej ld pri komponovke programmy iz ob®ektnyh fajlov. |to chislo dolzhno byt' pravil'nym, inache sistema otkazhetsya zapuskat' prog- rammu iz etogo fajla. Byvaet neskol'ko raznyh magicheskih chisel, oboznachayushchih raznye sposoby organizacii programmy v pamyati. Naprimer, est' variant, v kotorom segmenty text i data skleeny vmeste (togda text ne razdelyaem mezhdu processami i ne zashchishchen ot modifikacii programmoj), a est' - gde dannye i tekst nahodyatsya v razdel'nyh adresnyh prostranstvah i zapis' v text zapreshchena (apparatno). Ostal'nye argumenty vyzova - arg0, ..., argn - eto argumenty funkcii main novoj programmy. Vo vtoroj forme vyzova argumenty ne perechislyayutsya yavno, a zanosyatsya v mas- siv. |to pozvolyaet formirovat' proizvol'nyj massiv strok-argumentov vo vremya raboty programmy: char *argv[20]; argv[0]="ls"; argv[1]="-l"; argv[2]="-i"; argv[3]=NULL; execv( "/bin/ls", argv); libo execl( "/bin/ls", "ls","-l","-i", NULL): V rezul'tate etogo vyzova tekushchaya programma zavershaetsya (no ne process!) i vmesto nee zapuskaetsya programma iz zadannogo fajla: segmenty stack, data, text staroj programmy unichtozhayutsya; sozdayutsya novye segmenty data i text, zagruzhaemye iz fajla path; otvo- ditsya segment stack (pervonachal'no - ne ochen' bol'shogo razmera); segment user sohra- nyaetsya ot staroj programmy (za isklyucheniem reakcij na signaly, otlichnyh ot SIG_DFL i SIG_IGN - oni budut sbrosheny v SIG_DFL). Zatem budet vyzvana funkciya main novoj programmy s argumentami argv: void main( argc, argv ) int argc; char *argv[]; { ... } Kolichestvo argumentov - argc - podschitaet sama sistema. Stroka NULL ne podschityva- etsya. Process ostaetsya tem zhe samym - on imeet tot zhe pasport (tol'ko adresa segmentov izmenilis'); tot zhe nomer (pid); vse otkrytye prezhnej programmoj fajly ostayutsya otk- rytymi (s temi zhe deskriptorami); tekushchij katalog takzhe nasleduetsya ot staroj prog- rammy; signaly, kotorye ignorirovalis' eyu, takzhe budut ignorirovat'sya (ostal'nye sbrasyvayutsya v SIG_DFL). Zato "sushchnost'" processa podvergaetsya pererozhdeniyu - on vypolnyaet teper' inuyu programmu. Takim obrazom, sistemnyj vyzov exec os