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.