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