krytii fajla (a takzhe periodicheski cherez
nekotorye promezhutki vremeni) eta kopiya budet zapisana obratno na disk. Struktura
I-uzla v pamyati - struct inode - opisana v fajle <sys/inode.h>, a na diske - struct
dinode - v fajle <sys/ino.h>.
A. Bogatyrev, 1992-95 - 237 - Si v UNIX
staraya programma exec novaya programma
ruid -->----------------->---> ruid
uid -->--------*-------->---> uid (new)
|
vypolnyaemyj fajl
i_uid (st_uid)
Kak vidno iz etoj shemy, real'nyj identifikator hozyaina processa nasleduetsya. |ffek-
tivnyj identifikator obychno takzhe nasleduetsya, za isklyucheniem odnogo sluchaya: esli v
kodah dostupa fajla (i_mode) vystavlen bit S_ISUID (set-uid bit), to znachenie polya
u_uid v novom processe stanet ravno znacheniyu i_uid fajla s programmoj:
/* ... vo vremya exec ... */
p_suid = u_uid; /* spasti */
if( i_mode & S_ISUID ) u_uid = i_uid;
if( i_mode & S_ISGID ) u_gid = i_gid;
t.e. effektivnym vladel'cem processa stanet vladelec fajla. Zdes' gid - eto identi-
fikatory gruppy vladel'ca (kotorye tozhe est' i u fajla i u processa, prichem u pro-
cessa - real'nyj i effektivnyj).
Zachem vse eto nado? Vo-pervyh zatem, chto PRAVA processa na dostup k kakomu-libo
fajlu proveryayutsya imenno dlya effektivnogo vladel'ca processa. T.e. naprimer, esli
fajl imeet kody dostupa
mode = i_mode & 0777;
/* rwx rwx rwx */
i vladel'ca i_uid, to process, pytayushchijsya otkryt' etot fajl, budet "proekzamenovan" v
takom poryadke:
if( u_uid == 0 ) /* super user */
to dostup razreshen;
else if( u_uid == i_uid )
proverit' kody (mode & 0700);
else if( u_gid == i_gid )
proverit' kody (mode & 0070);
else proverit' kody (mode & 0007);
Process mozhet uznat' svoi parametry:
unsigned short uid = geteuid(); /* u_uid */
unsigned short ruid = getuid(); /* u_ruid */
unsigned short gid = getegid(); /* u_gid */
unsigned short rgid = getuid(); /* u_rgid */
a takzhe ustanovit' ih:
setuid(newuid); setgid(newgid);
Rassmotrim vyzov setuid. On rabotaet tak (u_uid - otnositsya k processu, izdavshemu
etot vyzov):
if( u_uid == 0 /* superuser */ )
u_uid = u_ruid = p_suid = newuid;
else if( u_ruid == newuid || p_suid == newuid )
u_uid = newuid;
else neudacha;
Pole p_suid pozvolyaet set-uid-noj programme vosstanovit' effektivnogo vladel'ca,
kotoryj byl u nee do exec-a.
A. Bogatyrev, 1992-95 - 238 - Si v UNIX
Vo-vtoryh, vse eto nado dlya sleduyushchego sluchaya: pust' u menya est' nekotoryj fajl
BASE s hranyashchimisya v nem sekretnymi svedeniyami. YA yavlyayus' vladel'cem etogo fajla i
ustanavlivayu emu kody dostupa 0600 (chtenie i zapis' razresheny tol'ko mne). Tem ne
menee, ya hochu dat' drugim pol'zovatelyam vozmozhnost' rabotat' s etim fajlom, odnako
kontroliruya ih deyatel'nost'. Dlya etogo ya pishu programmu, kotoraya vypolnyaet nekotorye
dejstviya s fajlom BASE, pri etom proveryaya zakonnost' etih dejstvij, t.e. pozvolyaya
delat' ne vse chto popalo, a lish' to, chto ya v nej predusmotrel, i pod zhestkim kontro-
lem. Vladel'cem fajla PROG, v kotorom hranitsya eta programma, takzhe yavlyayus' ya, i ya
zadayu etomu fajlu kody dostupa 0711 (rwx--x--x) - vsem mozhno vypolnyat' etu programmu.
Vse li ya sdelal, chtoby pozvolit' drugim pol'zovat'sya bazoj BASE cherez programmu (i
tol'ko nee) PROG? Net!
Esli kto-to drugoj zapustit programmu PROG, to effektivnyj identifikator pro-
cessa budet raven identifikatoru etogo drugogo pol'zovatelya, i programma ne smozhet
otkryt' moj fajl BASE. CHtoby vse rabotalo, process, vypolnyayushchij programmu PROG, dol-
zhen rabotat' kak by ot moego imeni. Dlya etogo ya dolzhen vyzovom chmod libo komandoj
chmod u+s PROG
dobavit' k kodam dostupa fajla PROG bit S_ISUID.
Posle etogo, pri zapuske programmy PROG, ona budet poluchat' effektivnyj identi-
fikator, ravnyj moemu identifikatoru, i takim obrazom smozhet otkryt' i rabotat' s
fajlom BASE. Vyzov getuid pozvolyaet vyyasnit', kto vyzval moyu programmu (i zanesti
eto v protokol, esli nado).
Programmy takogo tipa - ne redkost' v UNIX, esli vladel'cem programmy (fajla ee
soderzhashchego) yavlyaetsya superpol'zovatel'. V takom sluchae programma, imeyushchaya bit dos-
tupa S_ISUID rabotaet ot imeni superpol'zovatelya i mozhet vypolnyat' nekotorye dejst-
viya, zapreshchennye obychnym pol'zovatelyam. Pri etom programma vnutri sebya delaet vsyaches-
kie proverki i periodicheski sprashivaet paroli, to est' pri rabote zashchishchaet sistemu ot
durakov i prednamerennyh vreditelej. Prostejshim primerom sluzhit komanda ps, kotoraya
schityvaet tablicu processov iz pamyati yadra i raspechatyvaet ee. Dostup k fizicheskoj
pamyati mashiny proizvoditsya cherez fajl-psevdoustrojstvo /dev/mem, a k pamyati yadra -
/dev/kmem. CHtenie i zapis' v nih pozvoleny tol'ko superpol'zovatelyu, poetomu prog-
rammy "obshchego pol'zovaniya", obrashchayushchiesya k etim fajlam, dolzhny imet' bit set-uid.
Otkuda zhe iznachal'no berutsya znacheniya uid i ruid (a takzhe gid i rgid) u pro-
cessa? Oni berutsya iz processa registracii pol'zovatelya v sisteme: /etc/getty. |tot
process zapuskaetsya na kazhdom terminale kak process, prinadlezhashchij superpol'zovatelyu
(u_uid==0). Snachala on zaprashivaet imya i parol' pol'zovatelya:
#include <stdio.h> /* cc -lc_s */
#include <pwd.h>
#include <signal.h>
struct passwd *p;
char userName[80], *pass, *crpass;
extern char *getpass(), *crypt();
...
/* Ne preryvat'sya po signalam s klaviatury */
signal (SIGINT, SIG_IGN);
for(;;){
/* Zaprosit' imya pol'zovatelya: */
printf("Login: "); gets(userName);
/* Zaprosit' parol' (bez eha): */
pass = getpass("Password: ");
/* Proverit' imya: */
if(p = getpwnam(userName)){
/* est' takoj pol'zovatel' */
crpass = (p->pw_passwd[0]) ? /* esli est' parol' */
crypt(pass, p->pw_passwd) : pass;
if( !strcmp( crpass, p->pw_passwd))
break; /* vernyj parol' */
}
printf("Login incorrect.\a\n");
}
signal (SIGINT, SIG_DFL);
A. Bogatyrev, 1992-95 - 239 - Si v UNIX
Zatem on vypolnyaet:
// ... zapis' informacii o vhode pol'zovatelya v sistemu
// v fajly /etc/utmp (kto rabotaet v sisteme sejchas)
// i /etc/wtmp (spisok vseh vhodov v sistemu)
...
setuid( p->pw_uid ); setgid( p->pw_gid );
chdir ( p->pw_dir ); /* GO HOME! */
// eti parametry budut unasledovany
// interpretatorom komand.
...
// nastrojka nekotoryh peremennyh okruzheniya envp:
// HOME = p->pw_dir
// SHELL = p->pw_shell
// PATH = nechto po umolchaniyu, vrode :/bin:/usr/bin
// LOGNAME (USER) = p->pw_name
// TERM = schityvaetsya iz fajla
// /etc/ttytype po imeni ustrojstva av[1]
// Delaetsya eto kak-to podobno
// char *envp[MAXENV], buffer[512]; int envc = 0;
// ...
// sprintf(buffer, "HOME=%s", p->pw_dir);
// envp[envc++] = strdup(buffer);
// ...
// envp[envc] = NULL;
...
// nastrojka kodov dostupa k terminalu. Imya ustrojstva
// soderzhitsya v parametre av[1] funkcii main.
chown (av[1], p->pw_uid, p->pw_gid);
chmod (av[1], 0600 ); /* -rw------- */
// teper' dostup k dannomu terminalu imeyut tol'ko
// voshedshij v sistemu pol'zovatel' i superpol'zovatel'.
// V sluchae smerti interpretatora komand,
// kotorym zamenitsya getty, process init sojdet
// s sistemnogo vyzova ozhidaniya wait() i vypolnit
// chown ( etot_terminal, 2 /*bin*/, 15 /*terminal*/ );
// chmod ( etot_terminal, 0600 );
// i, esli terminal chislitsya v fajle opisaniya linij
// svyazi /etc/inittab kak aktivnyj (metka respawn), to
// init perezapustit na etom_terminale novyj
// process getty pri pomoshchi pary vyzovov fork() i exec().
...
// zapusk interpretatora komand:
execle( *p->pw_shell ? p->pw_shell : "/bin/sh",
"-", NULL, envp );
V rezul'tate on stanovitsya processom pol'zovatelya, voshedshego v sistemu. Takovym zhe
posle exec-a, vypolnyaemogo getty, ostaetsya i interpretator komand p->pw_shell (obychno
/bin/sh ili /bin/csh) i vse ego potomki.
Na samom dele, v opisanii registracii pol'zovatelya pri vhode v sistemu, sozna-
tel'no bylo dopushcheno uproshchenie. Delo v tom, chto vse to, chto my pripisali processu
getty, v dejstvitel'nosti vypolnyaetsya dvumya programmami: /etc/getty i /bin/login.
Snachala process getty zanimaetsya nastrojkoj parametrov linii svyazi (t.e. termi-
nala) v sootvetstvii s ee opisaniem v fajle /etc/gettydefs. Zatem on zaprashivaet imya
pol'zovatelya i zamenyaet sebya (pri pomoshchi sisvyzova exec) processom login, peredavaya
emu v kachestve odnogo iz argumentov poluchennoe imya pol'zovatelya.
Zatem login zaprashivaet parol', nastraivaet okruzhenie, i.t.p., to est' imenno on
proizvodit vse operacii, privedennye vyshe na sheme. V konce koncov on zamenyaet sebya
interpretatorom komand.
Takoe razdelenie delaetsya, v chastnosti, dlya togo, chtoby schitannyj parol' v slu-
chae opechatki ne hranilsya by v pamyati processa getty, a unichtozhalsya by pri ochistke
A. Bogatyrev, 1992-95 - 240 - Si v UNIX
pamyati zavershivshegosya processa login. Takim obrazom parol' v istinnom, nezashifrovan-
nom vide hranitsya v sisteme minimal'noe vremya, chto zatrudnyaet ego podsmatrivanie
sredstvami elektronnogo ili programmnogo shpionazha. Krome togo, eto pozvolyaet izme-
nyat' sistemu proverki parolej ne izmenyaya programmu inicializacii terminala getty.
Imya, pod kotorym pol'zovatel' voshel v sistemu na dannom terminale, mozhno uznat'
vyzovom standartnoj funkcii
char *getlogin();
|ta funkciya ne proveryaet uid processa, a prosto izvlekaet zapis' pro dannyj terminal
iz fajla /etc/utmp.
Nakonec otmetim, chto vladelec fajla ustanavlivaetsya pri sozdanii etogo fajla
(vyzovami creat ili mknod), i polagaetsya ravnym effektivnomu identifikatoru sozdayu-
shchego processa.
di_uid = u_uid; di_gid = u_gid;
6.8.4. Napishite programmu, uznayushchuyu u sistemy i raspechatyvayushchuyu: nomer processa,
nomer i imya svoego vladel'ca, nomer gruppy, nazvanie i tip terminala na kotorom ona
rabotaet (iz peremennoj okruzheniya TERM).
6.9. Blokirovka dostupa k fajlam.
V bazah dannyh neredko vstrechaetsya situaciya odnovremennogo dostupa k odnim i tem
zhe dannym. Dopustim, chto v nekotorom fajle hranyatsya dannye, kotorye mogut chitat'sya i
zapisyvat'sya proizvol'nym chislom processov.
- Dopustim, chto process A izmenyaet nekotoruyu oblast' fajla, v to vremya kak process
B pytaetsya prochest' tu zhe oblast'. Itogom takogo sorevnovaniya mozhet byt' to,
chto process B prochtet nevernye dannye.
- Dopustim, chto process A izmenyaet nekotoruyu oblast' fajla, v to vremya kak process
C takzhe izmenyaet tu zhe samuyu oblast'. V itoge eta oblast' mozhet soderzhat'
nevernye dannye (chast' - ot processa A, chast' - ot C).
YAsno, chto trebuetsya mehanizm sinhronizacii processov, pozvolyayushchij ne puskat'
drugoj process (processy) chitat' i/ili zapisyvat' dannye v ukazannoj oblasti. Meha-
nizmov sinhronizacii v UNIX sushchestvuet mnozhestvo: ot semaforov do blokirovok oblastej
fajla. O poslednih my i budem tut govorit'.
Prezhde vsego otmetim, chto blokirovki fajla nosyat v UNIX neobyazatel'nyj harakter.
To est', programma ne ispol'zuyushchaya vyzovov sinhronizacii, budet imet' dostup k dannym
bez kakih libo ogranichenij. Uvy. Takim obrazom, programmy, sobirayushchiesya korrektno
pol'zovat'sya obshchimi dannymi, dolzhny vse ispol'zovat' - i pri tom odin i tot zhe -
mehanizm sinhronizacii: zaklyuchit' mezhdu soboj "dzhentl'menskoe soglashenie".
6.9.1. Blokirovka ustanavlivaetsya pri pomoshchi vyzova
flock_t lock;
fcntl(fd, operation, &lock);
Zdes' operation mozhet byt' odnim iz treh:
F_SETLK
Ustanavlivaet ili snimaet zamok, opisyvaemyj strukturoj lock. Struktura flock_t
imeet takie polya:
short l_type;
short l_whence;
off_t l_start;
size_t l_len;
long l_sysid;
pid_t l_pid;
l_type
tip blokirovki:
A. Bogatyrev, 1992-95 - 241 - Si v UNIX
F_RDLCK - na chtenie;
F_WRLCK - na zapis';
F_UNLCK - snyat' vse zamki.
l_whence, l_start, l_len
opisyvayut segment fajla, na kotoryj stavitsya zamok: ot tochki
lseek(fd,l_start,l_whence); dlinoj l_len bajt. Zdes' l_whence mozhet byt':
SEEK_SET, SEEK_CUR, SEEK_END. l_len ravnoe nulyu oznachaet "do konca fajla". Tak
esli vse tri parametra ravny 0, to budet zablokirovan ves' fajl.
F_SETLKW
Ustanavlivaet ili snimaet zamok, opisyvaemyj strukturoj lock. Pri etom, esli
zamok na oblast', peresekayushchuyusya s ukazannoj uzhe kem-to ustanovlen, to sperva
dozhdat'sya snyatiya etogo zamka.
Pytaemsya | Net Uzhe est' uzhe est'
postavit' | chuzhih zamok zamok
zamok na | zamkov na READ na WRITE
-----------|---------------------------------------------------------------
READ | chitat' chitat' zhdat';zaperet';chitat'
WRITE | zapisat' zhdat';zaperet';zapisat' zhdat';zaperet';zapisat'
UNLOCK | otperet' otperet' otperet'
- Esli kto-to chitaet segment fajla, to drugie tozhe mogut ego chitat' svobodno, ibo
chtenie ne izmenyaet fajla.
- Esli zhe kto-to zapisyvaet fajl - to vse ostal'nye dolzhny dozhdat'sya okonchaniya
zapisi i razblokirovki.
- Esli kto-to chitaet segment, a drugoj process sobralsya izmenit' (zapisat') etot
segment, to etot drugoj process obyazan dozhdat'sya okonchaniya chteniya pervym.
- V moment, oboznachennyj kak otperet' - budyatsya processy, zhdushchie razblokirovki, i
rovno odin iz nih poluchaet dostup (mozhet ustanovit' svoyu blokirovku). Poryadok -
kto iz nih budet pervym - voobshche govorya ne opredelen.
F_GETLK
Zaprashivaem vozmozhnost' ustanovit' zamok, opisannyj v lock.
- Esli my mozhem ustanovit' takoj zamok (ne zaperto nikem), to v strukture lock
pole l_type stanovitsya ravnym F_UNLCK i pole l_whence ravnym SEEK_SET.
- Esli zamok uzhe kem-to ustanovlen (i vyzov F_SETLKW zablokiroval by nash process,
privel by k ozhidaniyu), my poluchaem informaciyu o chuzhom zamke v strukturu lock.
Pri etom v pole l_pid zanositsya identifikator processa, sozdavshego etot zamok, a
v pole l_sysid - identifikator mashiny (poskol'ku blokirovka fajlov podderzhiva-
etsya cherez setevye fajlovye sistemy).
Zamki avtomaticheski snimayutsya pri zakrytii deskriptora fajla. Zamki ne nasledu-
yutsya porozhdennym processom pri vyzove fork.
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
char DataFile [] = "data.xxx";
char info [] = "abcdefghijklmnopqrstuvwxyz";
#define OFFSET 5
#define SIZE 12
#define PAUSE 2
int trial = 1;
int fd, pid;
char buffer[120], myname[20];
void writeAccess(), readAccess();
A. Bogatyrev, 1992-95 - 242 - Si v UNIX
void fcleanup(int nsig){
unlink(DataFile);
printf("cleanup:%s\n", myname);
if(nsig) exit(0);
}
int main(){
int i;
fd = creat(DataFile, 0644);
write(fd, info, strlen(info));
close(fd);
signal(SIGINT, fcleanup);
sprintf(myname, fork() ? "B-%06d" : "A-%06d", pid = getpid());
srand(time(NULL)+pid);
printf("%s:started\n", myname);
fd = open(DataFile, O_RDWR|O_EXCL);
printf("%s:opened %s\n", myname, DataFile);
for(i=0; i < 30; i++){
if(rand()%2) readAccess();
else writeAccess();
}
close(fd);
printf("%s:finished\n", myname);
wait(NULL);
fcleanup(0);
return 0;
}
A. Bogatyrev, 1992-95 - 243 - Si v UNIX
void writeAccess(){
flock_t lock;
printf("Write:%s #%d\n", myname, trial);
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = (off_t) OFFSET;
lock.l_len = (size_t) SIZE;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\twrite:%s locked\n", myname);
sprintf(buffer, "%s #%02d", myname, trial);
printf ("\twrite:%s \"%s\"\n", myname, buffer);
lseek (fd, (off_t) OFFSET, SEEK_SET);
write (fd, buffer, SIZE);
sleep (PAUSE);
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\twrite:%s unlocked\n", myname);
trial++;
}
void readAccess(){
flock_t lock;
printf("Read:%s #%d\n", myname, trial);
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = (off_t) OFFSET;
lock.l_len = (size_t) SIZE;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\tread:%s locked\n", myname);
lseek(fd, (off_t) OFFSET, SEEK_SET);
read (fd, buffer, SIZE);
printf("\tcontents:%s \"%*.*s\"\n", myname, SIZE, SIZE, buffer);
sleep (PAUSE);
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLKW, &lock) <0)
perror("F_SETLKW");
printf("\tread:%s unlocked\n", myname);
trial++;
}
A. Bogatyrev, 1992-95 - 244 - Si v UNIX
Issleduya vydachu etoj programmy, vy mozhete obnaruzhit', chto READ-oblasti mogut perekry-
vat'sya; no chto nikogda ne perekryvayutsya oblasti READ i WRITE ni v kakoj kombinacii.
Esli idet chtenie processom A - to zapis' processom B dozhdetsya razblokirovki A (chtenie
- ne budet dozhidat'sya). Esli idet zapis' processom A - to i chtenie processom B i
zapis' processom B dozhdutsya razblokirovki A.
6.9.2.
UNIX SVR4 imeet eshche odin interfejs dlya blokirovki fajlov: funkciyu lockf.
#include <unistd.h>
int lockf(int fd, int operation, size_t size);
Operaciya operation:
F_ULOCK
Razblokirovat' ukazannyj segment fajla (eto mozhet snimat' odin ili neskol'ko
zamkov).
F_LOCK
F_TLOCK
Ustanovit' zamok. Pri etom, esli uzhe imeetsya chuzhoj zamok na zaprashivaemuyu
oblast', F_LOCK blokiruet process, F_TLOCK - prosto vydaet oshibku (funkciya vozv-
rashchaet -1, errno ustanavlivaetsya v EAGAIN).
- Ozhidanie otpiraniya/zapiraniya zamka mozhet byt' prervano signalom.
- Zamok ustanavlivaetsya sleduyushchim obrazom: ot tekushchej pozicii ukazatelya chteniya-
zapisi v fajle fd (chto ne pohozhe na fcntl, gde poziciya zadaetsya yavno kak para-
metr v strukture); dlinoj size. Otricatel'noe znachenie size oznachaet otschet ot
tekushchej pozicii k nachalu fajla. Nulevoe znachenie - oznachaet "ot tekushchej pozicii
do konca fajla". Pri etom "konec fajla" ponimaetsya imenno kak konec, a ne kak
tekushchij razmer fajla. Esli fajl izmenit razmer, zapertaya oblast' vse ravno
budet prostirat'sya do konca fajla (uzhe novogo).
- Zamki, ustanovlennye processom, avtomaticheski otpirayutsya pri zavershenii pro-
cessa.
F_TEST
Proverit' nalichie zamka. Funkciya vozvrashchaet 0, esli zamka net; -1 v protivnom
sluchae (zaperto).
Esli ustanavlivaetsya zamok, perekryvayushchijsya s uzhe ustanovlennym, to zamki ob容dinya-
yutsya.
bylo: ___________#######____######__________
zaprosheno:______________##########______________
stalo: ___________#################__________
Esli snimaetsya zamok s oblasti, pokryvayushchej tol'ko chast' zablokirovannoj prezhde,
ostatok oblasti ostaetsya kak otdel'nyj zamok.
bylo: ___________#################__________
zaprosheno:______________XXXXXXXXXX______________
stalo: ___________###__________####__________
6.10. Fajly ustrojstv.
Prostranstvo diskovoj pamyati mozhet sostoyat' iz neskol'kih fajlovyh sistem (v
dal'nejshem FS), t.e. logicheskih i/ili fizicheskih diskov. Kazhdaya fajlovaya sistema
imeet drevovidnuyu logicheskuyu strukturu (katalogi, podkatalogi i fajly) i imeet svoj
kornevoj katalog. Fajly v kazhdoj FS imeyut svoi sobstvennye I-uzly i sobstvennuyu ih
numeraciyu s 1. V nachale kazhdoj FS zarezervirovany:
A. Bogatyrev, 1992-95 - 245 - Si v UNIX
- blok dlya zagruzchika - programmy, vyzyvaemoj apparatno pri vklyuchenii mashiny (zag-
ruzchik zapisyvaet s diska v pamyat' mashiny programmu /boot, kotoraya v svoyu oche-
red' zagruzhaet v pamyat' yadro /unix);
- superblok - blok zagolovka fajlovoj sistemy, hranyashchij razmer fajlovoj sistemy (v
blokah), razmer bloka (512, 1024, ...), kolichestvo I-uzlov, nachalo spiska svo-
bodnyh blokov, i drugie svedeniya ob FS;
- nekotoraya nepreryvnaya oblast' diska dlya hraneniya I-uzlov - "I-fajl".
Fajlovye sistemy ob容dinyayutsya v edinuyu drevovidnuyu ierarhiyu operaciej montirovaniya -
podklyucheniya kornya fajlovoj sistemy k kakomu-to iz katalogov-"list'ev" dereva drugoj
FS.
Fajly v ob容dinennoj ierarhii adresuyutsya pri pomoshchi dvuh sposobov:
- imen, zadayushchih put' v dereve katalogov:
/usr/abs/bin/hackIt
bin/hackIt
./../../bin/vi
(etot sposob prednaznachen dlya programm, pol'zuyushchihsya fajlami, a takzhe pol'zova-
telej);
- vnutrennih adresov, ispol'zuemyh programmami yadra i nekotorymi sistemnymi prog-
rammami.
Poskol'ku v kazhdoj FS imeetsya sobstvennaya numeraciya I-uzlov, to fajl v ob容dinennoj
ierarhii dolzhen adresovat'sya DVUMYA parametrami:
- nomerom (kodom) ustrojstva, soderzhashchego fajlovuyu sistemu, v kotoroj nahoditsya
iskomyj fajl: dev_t i_dev;
- nomerom I-uzla fajla v etoj fajlovoj sisteme: ino_t i_number;
Preobrazovanie imeni fajla v ob容dinennoj fajlovoj ierarhii v takuyu adresnuyu paru
vypolnyaet v yadre uzhe upominavshayasya vyshe funkciya namei (pri pomoshchi prosmotra katalo-
gov):
struct inode *ip = namei(...);
Sozdavaemaya eyu kopiya I-uzla v pamyati yadra soderzhit polya i_dev i i_number (kotorye na
samom diske ne hranyatsya!).
Rassmotrim nekotorye algoritmy raboty yadra s fajlami. Nizhe oni privedeny chisto
shematichno i v sil'nom uproshchenii. Formaty vyzova (i oformlenie) funkcij ne soot-
vetstvuyut formatam, ispol'zuemym na samom dele v yadre; verny lish' nazvaniya funkcij.
Opushcheny proverki na korrektnost', podschet ssylok na struktury file i inode, bloki-
rovka I-uzlov i kesh-buferov ot odnovremennogo dostupa, i mnogoe drugoe.
Pust' my hotim otkryt' fajl dlya chteniya i prochitat' iz nego nekotoruyu informaciyu.
Vyzovy otkrytiya i zakrytiya fajla imeyut shemu (chast' ee budet ob座asnena pozzhe):
#include <sys/types.h>
#include <sys/inode.h>
#include <sys/file.h>
int fd_read = open(imyaFajla, O_RDONLY){
int fd; struct inode *ip; struct file *fp; dev_t dev;
u_error = 0; /* errno v programme */
// Najti fajl po imeni. Sozdaetsya kopiya I-uzla v pamyati:
ip = namei(imyaFajla, LOOKUP);
// namei mozhet vydat' oshibku, esli net takogo fajla
if(u_error) return(-1); // oshibka
// Vydelyaetsya struktura "otkrytyj fajl":
fp = falloc(ip, FREAD);
// fp->f_flag = FREAD; otkryt na chtenie
A. Bogatyrev, 1992-95 - 246 - Si v UNIX
// fp->f_offset = 0; RWptr
// fp->f_inode = ip; ssylka na I-uzel
// Vydelit' novyj deskriptor
for(fd=0; fd < NOFILE; fd++)
if(u_ofile[fd] == NULL ) // svoboden
goto done;
u_error = EMFILE; return (-1);
done:
u_ofile[fd] = fp;
// Esli eto ustrojstvo - inicializirovat' ego.
// |to funkciya openi(ip, fp->f_flag);
dev = ip->i_rdev;
if((ip->i_mode & IFMT) == IFCHR)
(*cdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
else if((ip->i_mode & IFMT) == IFBLK)
(*bdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
return fd; // cherez u_rval1
}
close(fd){
struct file *fp = u_ofile[fd];
struct inode *ip = fp->f_inode;
dev_t dev = ip->i_rdev;
if((ip->i_mode & IFMT) == IFCHR)
(*cdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);
else if((ip->i_mode & IFMT) == IFBLK)
(*bdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);
u_ofile[fd] = NULL;
// i udalit' nenuzhnye struktury iz yadra.
}
Teper' rassmotrim funkciyu preobrazovaniya logicheskih blokov fajla v nomera fizicheskih
blokov v fajlovoj sisteme. Dlya etogo preobrazovaniya v I-uzle fajla soderzhitsya tablica
adresov blokov. Ona ustroena dovol'no slozhno - ee nachalo nahoditsya v uzle, a prodol-
zhenie - v neskol'kih blokah v samoj fajlovoj sisteme (ustrojstvo eto mozhno uvidet' v
primere "Fragmentirovannost' fajlovoj sistemy" v prilozhenii). My dlya prostoty budem
predpolagat', chto eto prosto linejnyj massiv i_addr[], v kotorom n-omu logicheskomu
bloku fajla otvechaet bno-tyj fizicheskij blok fajlovoj sistemy:
bno = ip->i_addr[n];
Esli fajl yavlyaetsya interfejsom ustrojstva, to etot fajl ne hranit informacii v logi-
cheskoj fajlovoj sisteme. Poetomu u ustrojstv net tablicy adresov blokov. Vmesto
etogo, pole i_addr[0] ispol'zuetsya dlya hraneniya koda ustrojstva, k kotoromu privodit
etot special'nyj fajl. |to pole nosit nazvanie i_rdev, t.e. kak by sdelano
#define i_rdev i_addr[0]
(na samom dele ispol'zuetsya union). Ustrojstva byvayut bajto-orientirovannye, obmen s
kotorymi proizvoditsya po odnomu bajtu (kak s terminalom ili s kommunikacionnym por-
tom); i blochno-orientirovannye, obmen s kotorymi vozmozhen tol'ko bol'shimi porciyami -
blokami (primer - disk). To, chto fajl yavlyaetsya ustrojstvom, pomecheno v pole tip
fajla
ip->i_mode & IFMT
A. Bogatyrev, 1992-95 - 247 - Si v UNIX
odnim iz znachenij: IFCHR - bajtovoe; ili IFBLK - blochnoe. Algoritm vychisleniya nomera
bloka:
ushort u_pboff; // smeshchenie ot nachala bloka
ushort u_pbsize; // skol'ko bajt nado ispol'zovat'
// ushort - eto unsigned short, smotri <sys/types.h>
// daddr_t - eto long (disk address)
daddr_t bmap(struct inode *ip,
off_t offset, unsigned count){
int sz, rem;
// vychislit' logicheskij nomer bloka po pozicii RWptr.
// BSIZE - eto razmer bloka fajlovoj sistemy,
// eta konstanta opredelena v <sys/param.h>
daddr_t bno = offset / BSIZE;
// esli BSIZE == 1 Kb, to mozhno offset >> 10
u_pboff = offset % BSIZE;
// eto mozhno zapisat' kak offset & 01777
sz = BSIZE - u_pboff;
// stol'ko bajt nado vzyat' iz etogo bloka,
// nachinaya s pozicii u_pboff.
if(count < sz) sz = count;
u_pbsize = sz;
Esli fajl predstavlyaet soboj ustrojstvo, to translyaciya logicheskih blokov v fizicheskie
ne proizvoditsya - ustrojstvo predstavlyaet soboj "syroj" disk bez fajlov i katalogov,
t.e. obrashchenie proishodit srazu po fizicheskomu nomeru bloka:
if((ip->i_mode & IFMT) == IFBLK) // block device
return bno; // raw disk
// inache provesti pereschet:
rem = ip->i_size /*dlina fajla*/ - offset;
// eto ostatok fajla.
if( rem < 0 ) rem = 0;
// fajl koroche, chem zakazano nami:
if( rem < sz ) sz = rem;
if((u_pbsize = sz) == 0) return (-1); // EOF
// i, sobstvenno, zamena logich. nomera na fizich.
return ip->i_addr[bno];
}
Teper' rassmotrim algoritm read. Parametry, nachinayushchiesya s u_..., na samom dele pere-
dayutsya kak staticheskie cherez vspomogatel'nye peremennye v u-area processa.
read(int fd, char *u_base, unsigned u_count){
unsigned srccount = u_count;
struct file *fp = u_ofile[fd];
struct inode *ip = fp->f_inode;
struct buf *bp;
daddr_t bno; // ocherednoj blok fajla
// dev - ustrojstvo,
// interfejsom kotorogo yavlyaetsya fajl-ustrojstvo,
// ili na kotorom raspolozhen obychnyj fajl.
dev_t dev = (ip->i_mode & (IFCHR|IFBLK)) ?
A. Bogatyrev, 1992-95 - 248 - Si v UNIX
ip->i_rdev : ip->i_dev;
switch( ip->i_mode & IFMT ){
case IFCHR: // bajto-orientirovannoe ustrojstvo
(*cdevsw[major(dev)].d_read)(minor(dev));
// prochie parametry peredayutsya cherez u-area
break;
case IFREG: // obychnyj fajl
case IFDIR: // katalog
case IFBLK: // blochno-orientirovannoe ustrojstvo
do{
bno = bmap(ip, fp->f_offset /*RWptr*/, u_count);
if(u_pbsize==0 || (long)bno < 0) break; // EOF
bp = bread(dev, bno); // block read
iomove(bp->b_addr + u_pboff, u_pbsize, B_READ);
Funkciya iomove kopiruet dannye
bp->b_addr[ u_pboff..u_pboff+u_pbsize-1 ]
iz adresnogo prostranstva yadra (iz bufera v yadre) v adresnoe prostranstvo processa po
adresam
u_base[ 0..u_pbsize-1 ]
to est' peresylaet u_pbsize bajt mezhdu yadrom i processom (u_base popadaet v iomove
cherez staticheskuyu peremennuyu). Pri zapisi vyzovom write(), iomove s flagom B_WRITE
proizvodit obratnoe kopirovanie - iz pamyati processa v pamyat' yadra. Prodolzhim:
// prodvinut' schetchiki i ukazateli:
u_count -= u_pbsize;
u_base += u_pbsize;
fp->f_offset += u_pbsize; // RWptr
} while( u_count != 0 );
break;
...
return( srccount - u_count );
} // end read
Teper' obsudim nekotorye mesta etogo algoritma. Snachala posmotrim, kak proishodit
obrashchenie k bajtovomu ustrojstvu. Vmesto adresov blokov my poluchaem kod ustrojstva
i_rdev. Kody ustrojstv v UNIX (tip dev_t) predstavlyayut soboj paru dvuh chisel, nazy-
vaemyh mazhor i minor, hranimyh v starshem i mladshem bajtah koda ustrojstva:
#define major(dev) ((dev >> 8) & 0x7F)
#define minor(dev) ( dev & 0xFF)
Mazhor oboznachaet tip ustrojstva (disk, terminal, i.t.p.) i privodit k odnomu iz draj-
verov (esli u nas est' 8 terminalov, to ih obsluzhivaet odin i tot zhe drajver); a
minor oboznachaet nomer ustrojstva dannogo tipa (... kazhdyj iz terminalov imeet minory
0..7). Minory obychno sluzhat indeksami v nekotoroj tablice struktur vnutri vybrannogo
drajvera. Mazhor zhe sluzhit indeksom v pereklyuchatel'noj tablice ustrojstv. Pri etom
blochno-orientirovannye ustrojstva vybirayutsya v odnoj tablice - bdevsw[], a bajto-
orientirovannye - v drugoj - cdevsw[] (sm. <sys/conf.h>; imena tablic oznachayut
block/character device switch). Kazhdaya stroka tablicy soderzhit adresa funkcij,
vypolnyayushchih nekotorye predopredelennye operacii sposobom, zavisimym ot ustrojstva.
Sami eti funkcii realizovany v drajverah ustrojstv. Argumentom dlya etih funkcij
obychno sluzhit minor ustrojstva, k kotoromu proizvoditsya obrashchenie. Funkciya v
A. Bogatyrev, 1992-95 - 249 - Si v UNIX
drajvere ispol'zuet etot minor kak indeks dlya vybora konkretnogo ekzemplyara ust-
rojstva dannogo tipa; kak indeks v massive upravlyayushchih struktur (soderzhashchih tekushchee
sostoyanie, rezhimy raboty, adresa funkcij preryvanij, adresa ocheredej dannyh i.t.p.
kazhdogo konkretnogo ustrojstva) dlya dannogo tipa ustrojstv. |ti upravlyayushchie struktury
razlichny dlya raznyh tipov ustrojstv (i ih drajverov).
Kazhdaya stroka pereklyuchatel'noj tablicy soderzhit adresa funkcij, vypolnyayushchih ope-
racii open, close, read, write, ioctl, select. open sluzhit dlya inicializacii ust-
rojstva pri pervom ego otkrytii (++ip->i_count==1) - naprimer, dlya vklyucheniya motora;
close - dlya vyklyucheniya pri poslednem zakrytii (--ip->i_count==0). U blochnyh ust-
rojstv polya dlya read i write ob容dineny v funkciyu strategy, vyzyvaemuyu s parametrom
B_READ ili B_WRITE. Vyzov ioctl prednaznachen dlya upravleniya parametrami raboty ust-
rojstva. Operaciya select - dlya oprosa: est' li postupivshie v ustrojstvo dannye (nap-
rimer, est' li v clist-e vvoda s klaviatury bajty? sm. glavu "|krannye biblioteki").
Vyzov select primenim tol'ko k nekotorym bajtoorientirovannym ustrojstvam i setevym
portam (socket-am). Esli dannoe ustrojstvo ne umeet vypolnyat' takuyu operaciyu, to
est' zapros k etoj operacii dolzhen vernut' v programmu oshibku (naprimer, operaciya
read neprimenima k printeru), to v pereklyuchatel'noj tablice soderzhitsya special'noe
imya funkcii nodev; esli zhe operaciya dopustima, no yavlyaetsya fiktivnoj (kak write dlya
/dev/null) - imya nulldev. Obe eti funkcii-zaglushki predstavlyayut soboj "pustyshki":
{}.
Teper' obratimsya k blochno-orientirovannym ustrojstvam. UNIX ispol'zuet vnutri
yadra dopolnitel'nuyu buferizaciyu pri obmenah s takimi ustrojstvami|-. Ispol'zovannaya
nami vyshe funkciya bp=bread(dev,bno); proizvodit chtenie fizicheskogo bloka nomer bno s
ustrojstva dev. |ta operaciya obrashchaetsya k drajveru konkretnogo ustrojstva i vyzyvaet
chtenie bloka v nekotoruyu oblast' pamyati v yadre OS: v odin iz kesh-buferov (cache,
"zapasat'"). Zagolovki kesh-buferov (struct buf) organizovany v spisok i imeyut polya
(sm. fajl <sys/buf.h>):
b_dev
kod ustrojstva, s kotorogo prochitan blok;
b_blkno
nomer fizicheskogo bloka, hranyashchegosya v bufere v dannyj moment;
b_flags
flagi bloka (sm. nizhe);
b_addr
adres uchastka pamyati (kak pravilo v samom yadre), v kotorom sobstvenno i hranitsya
soderzhimoe bloka.
Buferizaciya blokov pozvolyaet sisteme ekonomit' chislo obrashchenij k disku. Pri obrashche-
nii k bread() snachala proishodit poisk bloka (dev,bno) v tablice kesh-buferov. Esli
blok uzhe byl ranee prochitan v kesh, to obrashcheniya k disku ne proishodit, poskol'ku
kopiya soderzhimogo diskovogo bloka uzhe est' v pamyati yadra. Esli zhe bloka eshche net v
kesh-buferah, to v yadre vydelyaetsya chistyj bufer, v zagolovke emu propisyvayutsya nuzhnye
znacheniya polej b_dev i b_blkno, i blok schityvaetsya v bufer s diska vyzovom funkcii
bp->b_flags |= B_READ; // rod raboty: prochitat'
(*bdevsw[major(dev)].d_startegy)(bp);
// bno i minor - berutsya iz polej *bp
iz drajvera konkretnogo ustrojstva.
Kogda my chto-to izmenyaem v fajle vyzovom write(), to izmeneniya na samom dele
proishodyat v kesh-buferah v pamyati yadra, a ne srazu na diske. Pri zapisi v blok bufer
pomechaetsya kak izmenennyj:
b_flags |= B_DELWRI; // otlozhennaya zapis'
____________________
|- Sleduet otlichat' etu sistemnuyu buferizaciyu ot buferizacii pri pomoshchi biblioteki
stdio. Biblioteka sozdaet bufer v samom processe, togda kak sistemnye vyzovy imeyut
bufera vnutri yadra.
A. Bogatyrev, 1992-95 - 250 - Si v UNIX
i na disk nemedlenno ne zapisyvaetsya. Izmenennye bufera fizicheski zapisyvayutsya na
disk v takih sluchayah:
- Byl sdelan sistemnyj vyzov sync();
- YAdru ne hvataet kesh-buferov (ih chislo ogranicheno). Togda samyj staryj bufer (k
kotoromu dol'she vsego ne bylo obrashchenij) zapisyvaetsya na disk i posle etogo
ispol'zuetsya dlya drugogo bloka.
- Fajlovaya sistema byla otmontirovana vyzovom umount;
Ponyatno, chto ne izmenennye bloki obratno na disk iz buferov ne zapisyvayutsya (t.k. na
diske i tak soderzhatsya te zhe samye dannye). Dazhe esli fajl uzhe zakryt close, ego
bloki mogut byt' eshche ne zapisany na disk - zapis' proizojdet lish' pri vyzove sync.
|to oznachaet, chto izmenennye bloki zapisyvayutsya na disk "massirovanno" - po mnogu
blokov, no ne ochen' chasto, chto pozvolyaet optimizirovat' i samu zapis' na disk: sorti-
rovkoj blokov mozhno dostich' minimizacii peremeshcheniya magnitnyh golovok nad diskom.
Otslezhivanie samyh "staryh" buferov proishodit za schet reorganizacii spiska
zagolovkov kesh-buferov. V bol'shom uproshchenii eto mozhno predstavit' tak: kak tol'ko k
bloku proishodit obrashchenie, sootvetstvuyushchij zagolovok perestavlyaetsya v nachalo spiska.
V itoge samyj "passivnyj" blok okazyvaetsya v hvoste - on to i pereispol'zuetsya pri
nuzhde.
"Podvisanie" fajlov v pamyati yadra znachitel'no uskoryaet rabotu programm, t.k.
rabota s pamyat'yu gorazdo bystree, chem s diskom. Esli blok nado schitat'/zapisat', a on
uzhe est' v keshe, to real'nogo obrashcheniya k disku ne proishodit. Zato, esli sluchitsya
sboj pitaniya (ili kto-to neakkuratno vyklyuchit mashinu), a nekotorye bufera eshche ne byli
sbrosheny na disk - to chast' izmenenij v fajlah budet poteryana. Dlya prinuditel'noj
zapisi vseh izmenennyh kesh-buferov na disk sushchestvuet sisvyzov "sinhronizacii" soder-
zhimogo diskov i pamyati
sync(); // synchronize
Vyzov sync delaetsya raz v 30 sekund special'nym sluzhebnym processom /etc/update,
zapuskaemym pri zagruzke sistemy. Dlya raboty s fajlami, kotorye dolzhny gar