funkcii, pozvolyayushchej raspechatat' vse polya srazu (odnako takaya funkciya mozhet byt' napisana vami dlya konkretnogo tipa struk- tur). Takzhe ne sushchestvuet formata dlya scanf(), kotoryj vvodil by strukturu celikom. Vvodit' mozhno tol'ko po chastyam - kazhdoe pole otdel'no. 5.3. Napishite programmu, kotoraya po nomeru mesyaca vozvrashchaet obshchee chislo dnej goda vplot' do etogo mesyaca. 5.4. Peredelajte predydushchuyu programmu takim obrazom, chtoby ona po napisannomu buk- vami nazvaniyu mesyaca vozvrashchala obshchee chislo dnej goda vplot' do etogo mesyaca. V prog- ramme ispol'zujte funkciyu strcmp(). 5.5. Peredelajte predydushchuyu programmu takim obrazom, chtoby ona zaprashivala u pol'zo- vatelya den', mesyac, god i vydavala obshchee kolichestvo dnej v godu vplot' do dannogo dnya. Mesyac mozhet oboznachat'sya nomerom, nazvaniem mesyaca ili ego abbreviaturoj. 5.6. Sostav'te strukturu dlya uchetnoj kartoteki sluzhashchego, kotoraya soderzhala by sle- duyushchie svedeniya: familiyu, imya, otchestvo; god rozhdeniya; domashnij adres; mesto raboty, dolzhnost'; zarplatu; datu postupleniya na rabotu. 5.7. CHto pechataet programma? struct man { char name[20]; int salary; } workers[] = { { "Ivanov", 200 }, { "Petrov", 180 }, { "Sidorov", 150 } }, *wptr, chief = { "nachal'nik", 550 }; main(){ struct man *ptr, *cptr, save; ptr = wptr = workers + 1; cptr = &chief; save = workers[2]; workers[2] = *wptr; *wptr = save; wptr++; ptr--; ptr->salary = save.salary; printf( "%c %s %s %s %s\n%d %d %d %d\n%d %d %c\n", *workers[1].name, workers[2].name, cptr->name, ptr[1].name, save.name, wptr->salary, chief.salary, (*ptr).salary, workers->salary, wptr - ptr, wptr - workers, *ptr->name ); } Otvet: S Petrov nachal'nik Sidorov Sidorov 180 550 150 150 2 2 I 5.8. Razberite sleduyushchij primer: #include <stdio.h> struct man{ A. Bogatyrev, 1992-95 - 177 - Si v UNIX char *name, town[4]; int salary; int addr[2]; } men[] = { { "Vasya", "Msc", 100, { 12, 7 } }, { "Grisha", "Len", 120, { 6, 51 } }, { "Petya", "Rig", 140, { 23, 84 } }, { NULL, "" , -1, { -1, -1 } } }; main(){ struct man *ptr, **ptrptr; int i; ptrptr = &ptr; *ptrptr = &men[1]; /* men+1 */ printf( "%s %d %s %d %c\n", ptr->name, ptr->salary, ptr->town, ptr->addr[1], ptr[1].town[2] ); (*ptrptr)++; /* kopiruem *ptr v men[0] */ men[0].name = ptr->name; /* (char *) #1 */ strcpy( men[0].town, ptr->town ); /* char [] #2 */ men[0].salary = ptr->salary; /* int #3 */ for( i=0; i < 2; i++ ) men[0].addr[i] = ptr->addr[i]; /* massiv #4 */ /* raspechatyvaem massiv struktur */ for(ptr=men; ptr->name; ptr++ ) printf( "%s %s %d\n", ptr->name, ptr->town, ptr->addr[0]); } Obratite vnimanie na takie momenty: 1) Kak proizvoditsya rabota s ukazatelem na ukazatel' (ptrptr). 2) Pri kopirovanii struktur otdel'nymi polyami, polya skalyarnyh tipov (int, char, long, ..., ukazateli) kopiruyutsya operaciej prisvaivaniya (sm. stroki s pometkami #1 i #3). Polya vektornyh tipov (massivy) kopiruyutsya pri pomoshchi cikla, poele- mentno peresylayushchego massiv (stroka #4). Stroki (massivy bukv) peresylayutsya standartnoj funkciej strcpy (stroka #2). Vse eto otnositsya ne tol'ko k polyam struktur, no i k peremennym takih tipov. Struktury mozhno takzhe kopirovat' ne po polyam, a celikom: men[0]= *ptr; 3) Zapis' argumentov funkcii printf() lesenkoj pozvolyaet luchshe videt', kakomu for- matu sootvetstvuet kazhdyj argument. 4) Pri raspechatke massiva struktur my pechataem ne opredelennoe ih kolichestvo (rav- noe razmeru massiva), a pol'zuemsya ukazatelem NULL v pole name poslednej struk- tury kak priznakom konca massiva. 5) V pole town my hranim stroki iz 3h bukv, odnako vydelyaem dlya hraneniya massiv iz 4h bajt. |to neobhodimo potomu, chto stroka "Msc" sostoit ne iz 3h, a iz 4h baj- tov: 'M','s','c','\0'. Pri rabote so strukturami i ukazatelyami bol'shuyu pomoshch' mogut okazat' risunki. Vot kak (naprimer) mozhno narisovat' dannye iz etogo primera (massiv men izobrazhen ne ves'): A. Bogatyrev, 1992-95 - 178 - Si v UNIX --ptr-- --ptrptr-- ptr | * |<------|---* | ---|--- ---------- | / =========men[0]== / men:|name | *---|-----> "Vasya" | |---------------| | |town |M|s|c|\0| | |---------------| | |salary| 100 | | |---------------| | |addr | 12 | 7 | \ ----------------- \ =========men[1]== \-->|name | *---|-----> "Grisha" ............ 5.9. Sostav'te programmu "spravochnik po tablice Mendeleeva", kotoraya po nazvaniyu himicheskogo elementa vydavala by ego harakteristiki. Tablicu inicializirujte massivom struktur. 5.10. Pri zapisi dannyh v fajl (da i voobshche) ispol'zujte struktury vmesto massivov, esli elementy massiva imeyut raznoe smyslovoe naznachenie. Ne vosprinimajte strukturu prosto kak sredstvo ob®edineniya dannyh raznyh tipov, ona mozhet byt' i sredstvom ob®e- dineniya dannyh odnogo tipa, esli eto dobavlyaet osmyslennosti nashej programme. CHem ploh fragment? int data[2]; data[0] = my_key; data[1] = my_value; write(fd, (char *) data, 2 * sizeof(int)); Vo-pervyh, togda uzh luchshe ukazat' razmer vsego massiva srazu (hotya by na tot sluchaj, esli my izmenim ego razmer na 3 i zabudem popravit' mnozhitel' s 2 na 3). write(fd, (char *) data, sizeof data); Kstati, pochemu my pishem data, a ne &data? (otvet: potomu chto imya massiva i est' ego adres). Vo-vtoryh, elementy massiva imeyut raznyj smysl, tak ne ispol'zovat' li tut strukturu? struct _data { int key; int value; } data; data.key = my_key; data.value = my_value; write(fd, &data, sizeof data); 5.11. CHto napechataet sleduyushchaya programma? Narisujte raspolozhenie ukazatelej po okon- chanii dannoj programmy. #include <stdio.h> struct lnk{ char c; A. Bogatyrev, 1992-95 - 179 - Si v UNIX struct lnk *prev, *next; } chain[20], *head = chain; add(c) char c; { head->c = c; head->next = head+1; head->next->prev = head; head++; } main(){ char *s = "012345"; while( *s ) add( *s++ ); head->c = '-'; head->next = (struct lnk *)NULL; chain->prev = chain->next; while( head->prev ){ putchar( head->prev->c ); head = head->prev; if( head->next ) head->next->prev = head->next->next; } } 5.12. Napishite programmu, sostavlyashchuyu dvunapravlennyj spisok bukv, vvodimyh s klavi- atury. Konec vvoda - bukva '\n'. Posle tret'ej bukvy vstav'te bukvu '+'. Udalite pyatuyu bukvu. Raspechatajte spisok v obratnom poryadke. Oformite operacii vstavki/udaleniya kak funkcii. |lement spiska dolzhen imet' vid: struct elem{ char letter; /* bukva */ char *word; /* slovo */ struct elem *prev; /* ssylka nazad */ struct elem *next; /* ssylka vpered */ }; struct elem *head, /* pervyj element spiska */ *tail, /* poslednij element */ *ptr, /* rabochaya peremennaya */ *prev; /* predydushchij element pri prosmotre */ int c, cmp; ... while((c = getchar()) != '\n' ) Insert(c, tail); for(ptr=head; ptr != NULL; ptr=ptr->next) printf("bukva %c\n", ptr->letter); Pamyat' luchshe otvodit' ne iz massiva, a funkciej calloc(), kotoraya analogichna funkcii malloc(), no dopolnitel'no raspisyvaet vydelennuyu pamyat' bajtom '\0' (0, NULL). Vot funkcii vstavki i udaleniya: extern char *calloc(); /* sozdat' novoe zveno spiska dlya bukvy c */ struct elem *NewElem(c) char c; { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); /* calloc avtomaticheski obnulyaet vse polya, * v tom chisle prev i next */ p->letter = c; return p; } A. Bogatyrev, 1992-95 - 180 - Si v UNIX /* vstavka posle ptr (obychno - posle tail) */ Insert(c, ptr) char c; struct elem *ptr; { struct elem *newelem = NewElem(c), *right; if(head == NULL){ /* spisok byl pust */ head=tail=newelem; return; } right = ptr->next; ptr->next = newelem; newelem->prev = ptr; newelem->next = right; if( right ) right->prev = newelem; else tail = newelem; } /* udalit' ptr iz spiska */ Delete( ptr ) struct elem *ptr; { struct elem *left=ptr->prev, *right=ptr->next; if( right ) right->prev = left; if( left ) left->next = right; if( tail == ptr ) tail = left; if( head == ptr ) head = right; free((char *) ptr); } Napishite analogichnuyu programmu dlya spiska slov. struct elem *NewElem(char *s) { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); p->word = strdup(s); return p; } void DeleteElem(struct elem *ptr){ free(ptr->word); free(ptr); } Uslozhnenie: vstavlyajte slova v spisok v alfavitnom poryadke. Ispol'zujte dlya etogo funkciyu strcmp(), prosmatrivajte spisok tak: struct elem *newelem; if (head == NULL){ /* spisok pust */ head = tail = NewElem(novoe_slovo); return; } /* poisk mesta v spiske */ for(cmp= -1, ptr=head, prev=NULL; ptr; prev=ptr, ptr=ptr->next ) if((cmp = strcmp(novoe_slovo, ptr->word)) <= 0 ) break; Esli cikl okonchilsya s cmp==0, to takoe slovo uzhe est' v spiske. Esli cmp < 0, to takogo slova ne bylo i ptr ukazyvaet element, pered kotorym nado vstavit' slovo novoe_slovo, a prev - posle kotorogo (prev==NULL oznachaet, chto nado vstavit' v nachalo spiska); t.e. slovo vstavlyaetsya mezhdu prev i ptr. Esli cmp > 0, to slovo nado doba- vit' v konec spiska (pri etom ptr==NULL). head ==> "a" ==> "b" ==> "d" ==> NULL | | prev "c" ptr A. Bogatyrev, 1992-95 - 181 - Si v UNIX if(cmp == 0) return; /* slovo uzhe est' */ newelem = NewElem( novoe_slovo ); if(prev == NULL){ /* v nachalo */ newelem->next = head; newelem->prev = NULL; head->prev = newelem; head = newelem; } else if(ptr == NULL){ /* v konec */ newelem->next = NULL; newelem->prev = tail; tail->next = newelem; tail = newelem; } else { /* mezhdu prev i ptr */ newelem->next = ptr; newelem->prev = prev; prev->next = newelem; ptr ->prev = newelem; } 5.13. Napishite funkcii dlya raboty s kompleksnymi chislami struct complex { double re, im; }; Naprimer, slozhenie vyglyadit tak: struct complex add( c1, c2 ) struct complex c1, c2; { struct complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; } struct complex a = { 12.0, 14.0 }, b = { 13.0, 2.0 }; main(){ struct complex c; c = add( a, b ); printf( "(%g,%g)\n", c.re, c.im ); } 5.14. Massivy v Si nel'zya prisvaivat' celikom, zato struktury - mozhno. Inogda ispol'zuyut takoj tryuk: strukturu iz edinstvennogo polya-massiva typedef struct { int ai[5]; } intarray5; intarray5 a, b = { 1, 2, 3, 4, 5 }; i teper' zakonno a = b; Zato dostup k yachejkam massiva vyglyadit teper' menee izyashchno: A. Bogatyrev, 1992-95 - 182 - Si v UNIX a.ai[2] = 14; for(i=0; i < 5; i++) printf( "%d\n", a.ai[i] ); Takzhe nevozmozhno peredat' kopiyu massiva v kachestve fakticheskogo parametra funkcii. Dazhe esli my napishem: typedef int ARR16[16]; ARR16 d; void f(ARR16 a){ printf( "%d %d\n", a[3], a[15]); a[3] = 2345; } void main(void){ d[3] = 9; d[15] = 98; f(d); printf("Now it is %d\n", d[3]); } to poslednij printf napechataet "Now it is 2345", poskol'ku v f peredaetsya adres mas- siva, no ne ego kopiya; poetomu operator a[3]=2345 izmenyaet ishodnyj massiv. Obojti eto mozhno, ispol'zovav tot zhe tryuk, poskol'ku pri peredache struktury v kachestve para- metra peredaetsya uzhe ne ee adres, a kopiya vsej struktury (kak eto i prinyato v Si vo vseh sluchayah, krome massivov). 5.15. Naposledok upomyanem pro bitovye polya - elementy struktury, zanimayushchie tol'ko chast' mashinnogo slova - tol'ko neskol'ko bitov v nem. Razmer polya v bitah zadaetsya konstrukciej :chislo_bitov. Bitovye polya ispol'zuyutsya dlya bolee kompaktnogo hraneniya informacii v strukturah (dlya ekonomii mesta). struct XYZ { /* bitovye polya dolzhny byt' unsigned */ unsigned x:2; /* 0 .. 2**2 - 1 */ unsigned y:5; /* 0 .. 2**5 - 1 */ unsigned z:1; /* YES=1 NO=0 */ } xyz; main(){ printf("%u\n", sizeof(xyz)); /* == sizeof(int) */ xyz.z = 1; xyz.y = 21; xyz.x = 3; printf("%u %u %u\n", xyz.x, ++xyz.y, xyz.z); /* Znachenie bitovogo polya beretsya po modulyu * maksimal'no dopustimogo chisla 2**chislo_bitov - 1 */ xyz.y = 32 /* maksimum */ + 7; xyz.x = 16+2; xyz.z = 11; printf("%u %u %u\n", xyz.x, xyz.y, xyz.z); /* 2 7 1 */ } Pole shiriny 1 chasto ispol'zuetsya v kachestve bitovogo flaga: vmesto #define FLAG1 01 #define FLAG2 02 #define FLAG3 04 int x; /* slovo dlya neskol'kih flagov */ x |= FLAG1; x &= ~FLAG2; if(x & FLAG3) ...; ispol'zuetsya struct flags { unsigned flag1:1, flag2:1, flag3:1; } x; x.flag1 = 1; x.flag2 = 0; if( x.flag3 ) ...; A. Bogatyrev, 1992-95 - 183 - Si v UNIX Sleduet odnako uchest', chto mashinnyj kod dlya raboty s bitovymi polyami bolee slozhen i zanimaet bol'she komand (t.e. medlennee i dlinnee). K bitovym polyam nel'zya primenit' operaciyu vzyatiya adresa "&", u nih net adresov i smeshchenij! 5.16. Primer na ispol'zovanie struktur s polem peremennogo razmera. CHast' peremen- noj dliny mozhet byt' lish' odna i obyazana byt' poslednim polem struktury. Vnimanie: eto programmistskij tryuk, ispol'zovat' ostorozhno! #include <stdio.h> #define SZ 5 extern char *malloc(); #define VARTYPE char struct obj { struct header { /* postoyannaya chast' */ int cls; int size; /* razmer peremennoj chasti */ } hdr; VARTYPE body [1]; /* chast' peremennogo razmera: v opisanii rovno ODIN element massiva */ } *items [SZ]; /* ukazateli na struktury */ #define OFFSET(field, ptr) ((char *) &ptr->field - (char *)ptr) int body_offset; /* sozdanie novoj struktury */ struct obj *newObj( int cl, char *s ) { char *ptr; struct obj *op; int n = strlen(s); /* dlina peremennoj chasti (shtuk VARTYPE) */ int newsize = sizeof(struct header) + n * sizeof(VARTYPE); printf("[n=%d newsize=%d]\n", n, newsize); /* newsize = (sizeof(struct obj) - sizeof(op->body)) + n * sizeof(op->body); Pri ispol'zovanii etogo razmera ne uchityvaetsya, chto struct(obj) vyrovnena na granicu sizeof(int). No v chastnosti sleduet uchityvat' i to, na granicu chego vyrovneno nachalo polya op->body. To est' samym pravil'nym budet newsize = body_offset + n * sizeof(op->body); */ /* otvesti massiv bajt bez vnutrennej struktury */ ptr = (char *) malloc(newsize); /* nalozhit' poverh nego strukturu */ op = (struct obj *) ptr; op->hdr.cls = cl; op->hdr.size = n; strncpy(op->body, s, n); return op; } A. Bogatyrev, 1992-95 - 184 - Si v UNIX void printobj( struct obj *p ) { register i; printf( "OBJECT(cls=%d,size=%d)\n", p->hdr.cls, p->hdr.size); for(i=0; i < p->hdr.size; i++ ) putchar( p->body[i] ); putchar( '\n' ); } char *strs[] = { "a tree", "a maple", "an oak", "the birch", "the fir" }; int main(int ac, char *av[]){ int i; printf("sizeof(struct header)=%d sizeof(struct obj)=%d\n", sizeof(struct header), sizeof(struct obj)); { struct obj *sample; printf("offset(cls)=%d\n", OFFSET(hdr.cls, sample)); printf("offset(size)=%d\n", OFFSET(hdr.size, sample)); printf("offset(body)=%d\n", body_offset = OFFSET(body, sample)); } for( i=0; i < SZ; i++ ) items[i] = newObj( i, strs[i] ); for( i=0; i < SZ; i++ ){ printobj( items[i] ); free( items[i] ); items[i] = NULL; } return 0; } 5.17. Napishite programmu, realizuyushchuyu spisok so "stareniem". |lement spiska, k kotoromu obrashchalis' poslednim, nahoditsya v golove spiska. Samyj staryj element vytesnyaetsya k hvostu spiska i v konechnom schete iz spiska udalyaetsya. Takoj algoritm ispol'zuet yadro UNIX dlya keshirovaniya blokov fajla v operativnoj pamyati: bloki, k kotorym chasto byvayut obrashcheniya osedayut v pamyati (a ne na diske). /* Spisok strok, uporyadochennyh po vremeni ih dobavleniya v spisok, * t.e. samaya "svezhaya" stroka - v nachale, samaya "drevnyaya" - v konce. * Stroki pri postuplenii mogut i povtoryat'sya! Po podobnomu principu * mozhno organizovat' buferizaciyu blokov pri obmene s diskom. */ #include <stdio.h> extern char *malloc(), *gets(); #define MAX 3 /* maksimal'naya dlina spiska */ int nelems = 0; /* tekushchaya dlina spiska */ struct elem { /* STRUKTURA |LEMENTA SPISKA */ char *key; /* Dlya blokov - eto celoe - nomer bloka */ struct elem *next; /* sleduyushchij element spiska */ /* ... i mozhet chto-to eshche ... */ } *head; /* golova spiska */ void printList(), addList(char *), forget(); A. Bogatyrev, 1992-95 - 185 - Si v UNIX void main(){ /* Vvedite a b c d b a c */ char buf[128]; while(gets(buf)) addList(buf), printList(); } /* Raspechatka spiska */ void printList(){ register struct elem *ptr; printf( "V spiske %d elementov\n", nelems ); for(ptr = head; ptr != NULL; ptr = ptr->next ) printf( "\t\"%s\"\n", ptr->key ); } /* Dobavlenie v nachalo spiska */ void addList(char *s) { register struct elem *p, *new; /* Analiz - net li uzhe v spiske */ for(p = head; p != NULL; p = p->next ) if( !strcmp(s, p->key)){ /* Est'. Perenesti v nachalo spiska */ if( head == p ) return; /* Uzhe v nachale */ /* Udalyaem iz serediny spiska */ new = p; /* Udalyaemyj element */ for(p = head; p->next != new; p = p->next ); /* p ukazyvaet na predshestvennika new */ p->next = new->next; goto Insert; } /* Net v spiske */ if( nelems >= MAX ) forget(); /* Zabyt' starejshij */ if((new = (struct elem *) malloc(sizeof(struct elem)))==NULL) goto bad; if((new->key = malloc(strlen(s) + 1)) == NULL) goto bad; strcpy(new->key, s); nelems++; Insert: new->next = head; head = new; return; bad: printf( "Net pamyati\n" ); exit(13); } /* Zabyt' hvost spiska */ void forget(){ struct elem *prev = head, *tail; if( head == NULL ) return; /* Spisok pust */ /* Edinstvennyj element ? */ if((tail = head->next) == NULL){ tail=head; head=NULL; goto Del; } for( ; tail->next != NULL; prev = tail, tail = tail->next ); prev->next = NULL; Del: free(tail->key); free(tail); nelems--; } A. Bogatyrev, 1992-95 - 186 - Si v UNIX  * 6. Sistemnye vyzovy i vzaimodejstvie s UNIX. *  V etoj glave rech' pojdet o processah. Skompilirovannaya programma hranitsya na diske kak obychnyj netekstovyj fajl. Kogda ona budet zagruzhena v pamyat' komp'yutera i nachnet vypolnyat'sya - ona stanet processom. UNIX - mnogozadachnaya sistema (mul'tiprogrammnaya). |to oznachaet, chto odnovre- menno mozhet byt' zapushcheno mnogo processov. Processor vypolnyaet ih v rezhime razdeleniya vremeni - vydelyaya po ocheredi kvant vremeni odnomu processu, zatem drugomu, tret'emu... V rezul'tate sozdaetsya vpechatlenie parallel'nogo vypolneniya vseh proces- sov (na mnogoprocessornyh mashinah parallel'nost' istinnaya). Processam, ozhidayushchim nekotorogo sobytiya, vremya processora ne vydelyaetsya. Bolee togo, "spyashchij" process mozhet byt' vremenno otkachan (t.e. skopirovan iz pamyati mashiny) na disk, chtoby osvobo- dit' pamyat' dlya drugih processov. Kogda "spyashchij" process dozhdetsya sobytiya, on budet "razbuzhen" sistemoj, pereveden v rang "gotovyh k vypolneniyu" i, esli byl otkachan - budet vozvrashchen s diska v pamyat' (no, mozhet byt', na drugoe mesto v pamyati!). |ta procedura nosit nazvanie "svopping" (swapping). Mozhno zapustit' neskol'ko processov, vypolnyayushchih programmu iz odnogo i togo zhe fajla; pri etom vse oni budut (esli tol'ko special'no ne bylo predusmotreno inache) nezavisimymi drug ot druga. Tak, u kazhdogo pol'zovatelya, rabotayushchego v sisteme, ime- etsya svoj sobstvennyj process-interpretator komand (svoya kopiya), vypolnyayushchij prog- rammu iz fajla /bin/csh (ili /bin/sh). Process predstavlyaet soboj izolirovannyj "mir", obshchayushchijsya s drugimi "mirami" vo Vselennoj pri pomoshchi: a) Argumentov funkcii main: void main(int argc, char *argv[], char *envp[]); Esli my naberem komandu $ a.out a1 a2 a3 to funkciya main programmy iz fajla a.out vyzovetsya s argc = 4 /* kolichestvo argumentov */ argv[0] = "a.out" argv[1] = "a1" argv[2] = "a2" argv[3] = "a3" argv[4] = NULL Po soglasheniyu argv[0] soderzhit imya vypolnyaemogo fajla iz kotorogo zagruzhena eta programma|-. b) Tak nazyvaemogo "okruzheniya" (ili "sredy") char *envp[], produblirovannogo takzhe v predopredelennoj peremennoj extern char **environ; Okruzhenie sostoit iz strok vida "IMYAPEREMENNOJ=znachenie" Massiv etih strok zavershaetsya NULL (kak i argv). Dlya polucheniya znacheniya pere- mennoj s imenem IMYA sushchestvuet standartnaya funkciya char *getenv( char *IMYA ); Ona vydaet libo znachenie, libo NULL esli peremennoj s takim imenem net. c) Otkrytyh fajlov. Po umolchaniyu (neyavno) vsegda otkryty 3 kanala: VVOD V Y V O D FILE * stdin stdout stderr sootvetstvuet fd 0 1 2 svyazan s klaviaturoj displeem ____________________ |- Imenno eto imya pokazyvaet komanda ps -ef #include <stdio.h> main(ac, av) char **av; { execl("/bin/sleep", "Take it easy", "1000", NULL); } A. Bogatyrev, 1992-95 - 187 - Si v UNIX |ti kanaly dostayutsya processu "v nasledstvo" ot zapuskayushchego processa i svyazany s displeem i klaviaturoj, esli tol'ko ne byli perenapravleny. Krome togo, prog- ramma mozhet sama yavno otkryvat' fajly (pri pomoshchi open, creat, pipe, fopen). Vsego programma mozhet odnovremenno otkryt' do 20 fajlov (schitaya standartnye kanaly), a v nekotoryh sistemah i bol'she (naprimer, 64). V MS DOS est' eshche 2 predopredelennyh kanala vyvoda: stdaux - v posledovatel'nyj kommunikacionnyj port, stdprn - na printer. d) Process imeet unikal'nyj nomer, kotoryj on mozhet uznat' vyzovom int pid = getpid(); a takzhe uznat' nomer "roditelya" vyzovom int ppid = getppid(); Processy mogut po etomu nomeru posylat' drug drugu signaly: kill(pid /* komu */, sig /* nomer signala */); i reagirovat' na nih signal (sig /*po signalu*/, f /*vyzyvat' f(sig)*/); e) Sushchestvuyut i drugie sredstva kommunikacii processov: semafory, soobshcheniya, obshchaya pamyat', setevye kommunikacii. f) Sushchestvuyut nekotorye drugie parametry (kontekst) processa: naprimer, ego tekushchij katalog, kotoryj dostaetsya v nasledstvo ot processa-"roditelya", i mozhet byt' zatem izmenen sistemnym vyzovom chdir(char *imya_novogo_kataloga); U kazhdogo processa est' svoj sobstvennyj tekushchij rabochij katalog (v otlichie ot MS DOS, gde tekushchij katalog odinakov dlya vseh zadach). K "prochim" harakteristi- kam otnesem takzhe: upravlyayushchij terminal; gruppu processov (pgrp); identifikator (nomer) vladel'ca processa (uid), identifikator gruppy vladel'ca (gid), reakcii i maski, zadannye na razlichnye signaly; i.t.p. g) Izdaniya drugih zaprosov (sistemnyh vyzovov) k operacionnoj sisteme ("bogu") dlya vypolneniya razlichnyh "vneshnih" operacij. h) Vse ostal'nye dejstviya proishodyat vnutri processa i nikak ne vliyayut na drugie processy i ustrojstva ("miry"). V chastnosti, odin process NIKAK ne mozhet polu- chit' dostup k pamyati drugogo processa, esli tot ne pozvolil emu eto yavno (meha- nizm shared memory); adresnye prostranstva processov nezavisimy i izolirovany (ravno i prostranstvo yadra izolirovano ot pamyati processov). Operacionnaya sistema vystupaet v kachestve kommunikacionnoj sredy, svyazyvayushchej "miry"-processy, "miry"-vneshnie ustrojstva (vklyuchaya terminal pol'zovatelya); a takzhe v kachestve rasporyaditelya resursov "Vselennoj", v chastnosti - vremeni (po ocheredi vyde- lyaemogo aktivnym processam) i prostranstva (v pamyati komp'yutera i na diskah). My uzhe neodnokratno upominali "sistemnye vyzovy". CHto zhe eto takoe? S tochki zreniya Si-programmista - eto obychnye funkcii. V nih peredayut argumenty, oni vozvra- shchayut znacheniya. Vneshne oni nichem ne otlichayutsya ot napisannyh nami ili bibliotechnyh funkcij i vyzyvayutsya iz programm odinakovym s nimi sposobom. S tochki zhe zreniya realizacii - est' glubokoe razlichie. Telo funkcii-sisvyzova raspolozheno ne v nashej programme, a v rezidentnoj (t.e. postoyanno nahodyashchejsya v pamyati komp'yutera) upravlyayushchej programme, nazyvaemoj yadrom operacionnoj sistemy|-. ____________________ |- Sobstvenno, operacionnaya sistema harakterizuetsya naborom predostavlyaemyh eyu sis- temnyh vyzovov, poskol'ku vse koncepcii, zalozhennye v sisteme, dostupny nam tol'ko cherez nih. Esli my imeem dve realizacii sistemy s raznym vnutrennim ustrojstvom yader, no predostavlyayushchie odinakovyj interfejs sistemnyh vyzovov (ih nabor, smysl i povedenie), to eto vse-taki odna i ta zhe sistema! YAdra mogut ne prosto otlichat'sya, no i byt' postroennymi na sovershenno razlichnyh principah: tak obstoit delo s UNIX-ami na odnoprocessornyh i mnogoprocessornyh mashinah. No dlya nas yadro - eto "chernyj yashchik", polnost'yu opredelyaemyj ego povedeniem, t.e. svoim interfejsom s programmami, no ne vnutrennim ustrojstvom. Vtorym parametrom, harakterizuyushchim OS, yavlyayutsya for- maty dannyh, ispol'zuemye sistemoj: formaty dannyh dlya sisvyzovov i format informacii v razlichnyh fajlah, v tom chisle format oformleniya vypolnyaemyh fajlov (format dannyh v fizicheskoj pamyati mashiny v etot spisok ne vhodit - on zavisim ot realizacii i ot pro- cessora). Kak pravilo, programma pishetsya tak, chtoby ispol'zovat' soglasheniya, prinya- tye v dannoj sisteme, dlya chego ona prosto vklyuchaet ryad standartnyh include-fajlov s opisaniem etih formatov. Imena etih fajlov takzhe mozhno otnesti k interfejsu sistemy. A. Bogatyrev, 1992-95 - 188 - Si v UNIX Sam termin "sistemnyj vyzov" kak raz oznachaet "vyzov sistemy dlya vypolneniya dejst- viya", t.e. vyzov funkcii v yadre sistemy. YAdro rabotaet v privelegirovannom rezhime, v kotorom imeet dostup k nekotorym sistemnym tablicam|=, registram i portam vneshnih ustrojstv i dispetchera pamyati, k kotorym obychnym programmam dostup apparatno zapreshchen (v otlichie ot MS DOS, gde vse tablicy yadra dostupny pol'zovatel'skim programmam, chto sozdaet razdol'e dlya virusov). Sistemnyj vyzov proishodit v 2 etapa: snachala v pol'- zovatel'skoj programme vyzyvaetsya bibliotechnaya funkciya-"koreshok", telo kotoroj napi- sano na assemblere i soderzhit komandu generacii programmnogo preryvaniya. |to - glav- noe otlichie ot normal'nyh Si-funkcij - vyzov po preryvaniyu. Vtorym etapom yavlyaetsya reakciya yadra na preryvanie: 1. perehod v privelegirovannyj rezhim; 2. razbiratel'stvo, KTO obratilsya k yadru, i podklyuchenie u-area etogo processa k adresnomu prostranstvu yadra (context switching); 3. izvlechenie argumentov iz pamyati zaprosivshego processa; 4. vyyasnenie, CHTO zhe hotyat ot yadra (odin iz argumentov, nevidimyj nam - eto nomer sistemnogo vyzova); 5. proverka korrektnosti ostal'nyh argumentov; 6. proverka prav processa na dopustimost' vypolneniya takogo zaprosa; 7. vyzov tela trebuemogo sistemnogo vyzova - eto obychnaya Si-funkciya v yadre; 8. vozvrat otveta v pamyat' processa; 9. vyklyuchenie privelegirovannogo rezhima; 10. vozvrat iz preryvaniya. Vo vremya sistemnogo vyzova (shag 7) process mozhet "zasnut'", dozhidayas' nekotorogo sobytiya (naprimer, nazhatiya knopki na klaviature). V eto vremya yadro peredast upravle- nie drugomu processu. Kogda nash process budet "razbuzhen" (sobytie proizoshlo) - on prodolzhit vypolnenie shagov sistemnogo vyzova. Bol'shinstvo sistemnyh vyzovov vozvrashchayut v programmu v kachestve svoego znacheniya priznak uspeha: 0 - vse sdelano, (-1) - sisvyzov zavershilsya neudachej; libo nekotoroe soderzhatel'noe znachenie pri uspehe (vrode deskriptora fajla v open(), i (-1) pri neu- dache. V sluchae neudachnogo zaversheniya v predopredelennuyu peremennuyu errno zanositsya nomer oshibki, opisyvayushchij prichinu neudachi (kody oshibok predopredeleny, opisany v include-fajle <errno.h> i imeyut vid Echtoto). Zametim, chto pri UDACHE eta peremennaya prosto ne izmenyaetsya i mozhet soderzhat' lyuboj musor, poetomu proveryat' ee imeet smysl lish' v sluchae, esli oshibka dejstvitel'no proizoshla: #include <errno.h> /* kody oshibok */ extern int errno; extern char *sys_errlist[]; int value; if((value = sys_call(...)) < 0 ){ printf("Error:%s(%d)\n", sys_errlist[errno], errno ); exit(errno); /* prinuditel'noe zavershenie programmy */ } ____________________ Povedenie vseh programm v sisteme vytekaet iz povedeniya sistemnyh vyzovov, koto- rymi oni pol'zuyutsya. Dazhe to, chto UNIX yavlyaetsya mnogozadachnoj sistemoj, neposredst- venno vytekaet iz nalichiya sistemnyh vyzovov fork, exec, wait i specifikacii ih funk- cionirovaniya! To zhe mozhno skazat' pro yazyk Si - mobil'nost' programmy zavisit v osnovnom ot nabora ispol'zuemyh v nej bibliotechnyh funkcij (i, v men'shej stepeni, ot dialekta sa- mogo yazyka, kotoryj dolzhen udovletvoryat' standartu na yazyk Si). Esli dve raznye sis- temy predostavlyayut vse eti funkcii (kotorye mogut byt' po-raznomu realizovany, no dolzhny delat' odno i to zhe), to programma budet kompilirovat'sya i rabotat' v oboih sistemah, bolee togo, rabotat' v nih odinakovo. |= Takim kak tablica processov, tablica otkrytyh fajlov (vseh vmeste i dlya kazhdogo processa), i.t.p. A. Bogatyrev, 1992-95 - 189 - Si v UNIX Predopredelennyj massiv sys_errlist, hranyashchijsya v standartnoj biblioteke, soderzhit stroki-rasshifrovku smysla oshibok (po-anglijski). Posmotrite opisanie funkcii per- ror(). 6.1. Fajly i katalogi. 6.1.1. Ispol'zuya sistemnyj vyzov stat, napishite programmu, opredelyayushchuyu tip fajla: obychnyj fajl, katalog, ustrojstvo, FIFO-fajl. Otvet: #include <sys/types.h> #include <sys/stat.h> typeOf( name ) char *name; { int type; struct stat st; if( stat( name, &st ) < 0 ){ printf( "%s ne sushchestvuet\n", name ); return 0; } printf("Fajl imeet %d imen\n", st.st_nlink); switch(type = (st.st_mode & S_IFMT)){ case S_IFREG: printf( "Obychnyj fajl razmerom %ld bajt\n", st.st_size ); break; case S_IFDIR: printf( "Katalog\n" ); break; case S_IFCHR: /* bajtoorientirovannoe */ case S_IFBLK: /* blochnoorientirovannoe */ printf( "Ustrojstvo\n" ); break; case S_IFIFO: printf( "FIFO-fajl\n" ); break; default: printf( "Drugoj tip\n" ); break; } return type; } 6.1.2. Napishite programmu, pechatayushchuyu: svoi argumenty, peremennye okruzheniya, infor- maciyu o vseh otkrytyh eyu fajlah i ispol'zuemyh trubah. Dlya etoj celi ispol'zujte sistemnyj vyzov struct stat st; int used, fd; for(fd=0; fd < NOFILE; fd++ ){ used = fstat(fd, &st) < 0 ? 0 : 1; ... } Programma mozhet ispol'zovat' deskriptory fajlov s nomerami 0..NOFILE-1 (obychno 0..19). Esli fstat dlya kakogo-to fd vernul kod oshibki (<0), eto oznachaet, chto dannyj deskriptor ne svyazan s otkrytym fajlom (t.e. ne ispol'zuetsya). NOFILE opredeleno v include-fajle <sys/param.h>, soderzhashchem raznoobraznye parametry dannoj sistemy. 6.1.3. Napishite uproshchennyj analog komandy ls, raspechatyvayushchij soderzhimoe tekushchego kataloga (fajla s imenem ".") bez sortirovki imen po alfavitu. Predusmotrite chtenie kataloga, ch'e imya zadaetsya kak argument programmy. Imena "." i ".." ne vydavat'. Format kataloga opisan v header-fajle <sys/dir.h> i v "kanonicheskoj" versii vyg- lyadit tak: katalog - eto fajl, sostoyashchij iz struktur direct, kazhdaya opisyvaet odno imya fajla, vhodyashchego v katalog: A. Bogatyrev, 1992-95 - 190 - Si v UNIX struct direct { unsigned short d_ino; /* 2 bajta: nomer I-uzla */ char d_name[DIRSIZ]; /* imya fajla */ }; V semejstve BSD format kataloga neskol'ko inoj - tam zapisi imeyut raznuyu dlinu, zavi- syashchuyu ot dliny imeni fajla, kotoroe mozhet imet' dlinu ot 1 do 256 simvolov. Imya fajla mozhet sostoyat' iz lyubyh simvolov, krome '\0', sluzhashchego priznakom konca imeni i '/', sluzhashchego razdelitelem. V imeni dopustimy probely, upravlyayushchie simvoly (no ne rekomenduyutsya!), lyuboe chislo tochek (v otlichie ot MS DOS, gde dopustima edinstvennaya tochka, otdelyayushchaya sobstvenno imya ot suffiksa (rasshireniya)), razresheny dazhe nepechatnye (t.e. upravlyayushchie) simvoly! Esli imya fajla imeet dlinu 14 (DIRSIZ) simvolov, to ono ne okanchivaetsya bajtom '\0'. V etom sluchae dlya pechati imeni fajla vozmozhny tri podhoda: 1. Vyvodit' simvoly pri pomoshchi putchar()-a v cikle. Cikl preryvat' po indeksu rav- nomu DIRSIZ, libo po dostizheniyu bajta '\0'. 2. Skopirovat' pole d_name v drugoe mesto: char buf[ DIRSIZ + 1 ]; strncpy(buf, d.d_name, DIRSIZ); buf[ DIRSIZ ] = '\0'; |tot sposob luchshij, esli imya fajla nado ne prosto napechatat', no i zapomnit' na budushchee, chtoby ispol'zovat' v svoej programme. 3. Ispol'zovat' takuyu osobennost' funkcii printf(): #include <sys/types.h> #include <sys/dir.h> struct direct d; ... printf( "%*.*s\n", DIRSIZ, DIRSIZ, d.d_name ); Esli fajl byl stert, to v pole d_ino zapisi kataloga budet soderzhat'sya 0 (imenno poetomu I-uzly numeruyutsya nachinaya s 1, a ne s 0). Pri udalenii fajla soderzhimoe ego (bloki) unichtozhaetsya, I-uzel osvobozhdaetsya, no imya v kataloge ne zatiraetsya fizi- cheski, a prosto pomechaetsya kak stertoe: d_ino=0; Katalog pri etom nikak ne uplotnya- etsya i ne ukorachivaetsya! Poetomu imena s d_ino==0 vydavat' ne sleduet - eto imena uzhe unichtozhennyh fajlov. Pri sozdanii novogo imeni (creat, link, mknod) sistema prosmatrivaet katalog i pereispol'zuet pervyj ot nachala svobodnyj slot (yachejku kataloga) gde d_ino==0, zapi- syvaya novoe imya v nego (tol'ko v etot moment staroe imya-prizrak okonchatel'no ischeznet fizicheski). Esli pustyh mest net - katalog udlinyaetsya. Lyuboj katalog vsegda soderzhit dva standartnyh imeni: "." - ssylka na etot zhe katalog (na ego sobstvennyj I-node), ".." - na vyshelezhashchij katalog. U kornevogo kataloga "/" oba etih imeni ssylayutsya na nego zhe samogo (t.e. soderzhat d_ino==2). Imya kataloga ne soderzhitsya v nem samom. Ono soderzhitsya v "roditel'skom" kataloge ... Katalog v UNIX - eto obychnyj diskovyj fajl. Vy mozhete chitat' ego iz svoih prog- ramm. Odnako nikto (vklyuchaya superpol'zovatelya|=) ne mozhet zapisyvat' chto-libo v kata- log pri pomoshchi write. Izmeneniya soderzhimogo katalogov vypolnyaet tol'ko yadro, otvechaya na zaprosy v vide sistemnyh vyzovov creat, unlink, link, mkdir, rmdir, rename, mknod. Kody dostupa dlya kataloga interpretiruyutsya sleduyushchim obrazom: w zapis' S_IWRITE. Oznachaet pravo sozdavat' i unichtozhat' v kataloge imena fajlov pri ____________________ |= Superpol'zovatel' (superuser) imeet uid==0. |to "privelegirovannyj" pol'zova- tel', kotoryj imeet pravo delat' VSE. Emu dostupny lyubye sisvyzovy i fajly, nesmotrya na kody dostupa i.t.p. A. Bogatyrev, 1992-95 - 191 - Si v UNIX pomoshchi etih vyzovov. To est': pravo sozdavat', udalyat' i pereimenovyvat' fajly v kataloge. Otmetim, chto dlya pereimenovaniya ili udaleniya fajla vam ne trebuetsya imet' dostup po zapisi k samomu fajlu - dostatochno imet' dostup po zapisi k katalogu, soderzhashchemu ego imya! r chtenie S_IREAD. Pravo chitat' katalog kak obychnyj fajl (pravo vypolnyat' opendir, sm. nizhe): blagodarya etomu my mozhem poluchit' spisok imen fajlov, soderzhashchihsya v kataloge.