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