Programov´ an´ı v UNIXu (NSWI015) verze: 5. u ´nora 2015 (c) 2011 – 2015 Vladim´ır Kotal (c) 2005 – 2011 Jan Pechanec (c) 1999 – 2004 Martin Beran
SISAL MFF UK, Malostransk´e n´am. 25, 118 00 Praha 1
• Toto jsou ofici´ aln´ı materi´ aly pro v´ yuku pˇredmˇetu Programov´ an´ı v UNIXu (k´ od pˇredmˇetu NSWI015) na Matematicko-fyzik´aln´ı fakultˇe Univerzity Karlovy. • Tyto materi´ aly jsou uveˇrejnˇeny pod licenc´ı Creative Commons BY-NC-SA 3.0. Soubory k uk´ azkov´ ym pˇr´ıklad˚ um uveden´e v´ yˇse jsou Public Domain, pokud v nich nen´ı uvedeno jinak. • Tento pˇredmˇet je 2/1, cviˇcen´ı bude jednou za dva t´ ydny. • Vˇsechny informace, kter´e budete potˇrebovat, a materi´aly k pˇredn´aˇsce jsou na http://mff.devnull.cz/, vˇcetnˇe aktu´aln´ı verze tˇechto pozn´amkov´ ych slajd˚ u (http://mff.devnull.cz/pvu/slides/). Dˇ r´ıve, neˇ z zaˇ cnete materi´ aly pouˇ z´ıvat, pˇ resvˇ edˇ cte se, ˇ ze nen´ı novˇ ejˇ s´ı verze! • Uk´ azkov´e pˇr´ıklady jsou na http://mff.devnull.cz/pvu/src/, pokud se budu v pozn´ amk´ ach odkazovat na konkr´etn´ı pˇr´ıklad, link zde uveden´ y slouˇz´ı jako koˇrenov´ y adres´ aˇr. • Je potˇreba se zapsat na cviˇcen´ı v ISu. • Z´ apoˇcet je za z´ apoˇctov´ y program. • Zkouˇska m´ a p´ısemnou a u ´stn´ı ˇc´ast. • Zkouˇset se bude to, co bude odpˇredneseno (kromˇe t´emat na vyplnˇen´ı pˇr´ıpadnˇe zbyl´eho ˇcasu). Vˇetˇsina informac´ı je ve slajdech, ale ˇrada d˚ uleˇzit´ ych podrobnost´ı m˚ uˇze chybˇet.
1
• Pˇredpoklady: – Uˇzivatelsk´ a znalost UNIXu, programov´an´ı v shellu na u ´rovni pˇredn´aˇsky ´ ,,Uvod do UNIXu” (NSWI095) – Znalost jazyka C – Znalost z´ akladn´ıch pojm˚ u teorie operaˇcn´ıch syst´em˚ u • Tento text je pr˚ ubˇeˇznˇe doplˇ nov´an, ChangeLog zaˇc´ın´a na stranˇe 250. Upozornˇen´ı na chyby pros´ım pos´ılejte na
[email protected].
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
• Budeme se zab´ yvat hlavnˇe principy UNIXu a programov´an´ım pro UNIX pouze v jazyce C. • Pˇ redn´ aˇ ska je pˇ rev´ aˇ znˇ e o syst´ emov´ ych vol´ an´ıch, tj. rozhran´ım mezi uˇ zivatelsk´ ym prostorem a j´ adrem. • Pˇri popisu API se budeme drˇzet normy Single UNIX Specification, version 3 (SUSv3). Syst´emy, kter´e tuto specifikaci podporuj´ı, mohou pouˇz´ıvat oznaˇcen´ı UNIX 03. V souˇcasn´e dobˇe (10/2011) jsou to posledn´ı verze syst´em˚ u Solaris, AIX, HP-UX a Mac OS X na r˚ uzn´ ych architektur´ach (http://www. opengroup.org/openbrand/register/xy.htm). • Pro konkr´etn´ı pˇr´ıklady budu pouˇz´ıvat vˇetˇsinou syst´emy FreeBSD a Solaris.
2
Obsah • u ´ vod, v´ yvoj UNIXu a C, program´ atorsk´ e n´ astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
Literatura v ˇ ceˇ stinˇ e 1. Skoˇcovsk´ y, L.: Principy a probl´ emy operaˇ cn´ıho syst´ emu UNIX. Science, 1993 2. Skoˇcovsk´ y, Ludˇek: UNIX, POSIX, Plan9. L. Skoˇcovsk´ y, Brno, 1998 3. Jelen, Milan: UNIX V - programov´ an´ı v syst´ emu. Grada, Praha 1993 4. Linux - Dokumentaˇ cn´ı projekt. Computer Press, 1998; http://www.cpress.cz/knihy/linux ˇ e 5. Herout, Pavel: Uˇ cebnice jazyka C. 2 d´ıly. Kopp, Cesk´ Budˇejovice, 2004 (4., respektive 2. pˇrepracovan´e vyd´an´ı)
3
Ohlednˇ e unixu doporuˇ cuji sp´ıˇ se literaturu v anglick´ em jazyce. 1. Vˇsestrann´ y u ´vod do UNIXu, ale dost struˇcn´a; Skoˇcovsk´ y je autorem v´ıce ˇcesk´ ych knih o unixu, ale dnes jsou jiˇz v´ıce nebo m´enˇe zastaral´e. 2. Pokroˇcilejˇs´ı pohled, ale pˇredpokl´ad´a pˇredbˇeˇzn´e znalosti, m´ısty tˇeˇzko straviteln´ a. 3. Programov´ an´ı v C pro UNIX System V, pr´ace se soubory a s procesy, System V IPC, nepopisuje napˇr. vl´akna a s´ıtˇe. 4. O Linuxu bylo samozˇrejmˇe v ˇceˇstinˇe vyd´ano mnoho dalˇs´ıch knih. 5. Vynikaj´ıc´ı knihy o jazyce C.
Literatura - design a principy syst´ emu 1. Uresh Vahalia: UNIX Internals: The New Frontiers. Prentice Hall; 1st edition, 1995 2. Bach, Maurice J.: The Design of the UNIX Operating System. Prentice Hall, 1986 3. McKusick, M. K., Neville-Neil, G. V.: The Design and Implementation of the FreeBSD Operating System. Addison-Wesley, 2004 4. McDougall, R.; Mauro, J.: Solaris Internals. Prentice Hall; 2nd edition, 2006. 5. Linux Documentation Project. http://tldp.org/
Tyto knihy se zab´ yvaj´ı stavbou unixu, pouˇzit´ ymi algoritmy, strukturami apod., nejsou to kniho o programov´ an´ı pod t´ımto syst´emem. 1. Skvˇel´ a kniha, zab´ yv´ a se obecn´ ymi myˇslenkami UNIXu a porovn´av´a syst´emy SVR4.2, 4.4BSD, Solarix 2.x a Mach. 12/2005 mˇelo vyj´ıt druh´e, doplnˇen´e vyd´ an´ı. Po nˇekolika letech ˇcek´an´ı pˇrestalo druh´e vyd´an´ı figurovat v listingu na amazon.com, a tedy se budeme muset zˇrejmˇe sm´ıˇrit s t´ım, ˇze nevyjde. 2. Klasick´ a kniha o UNIXu, popis struktury a funkc´ı j´adra UNIX System V Rel. 2, ˇc´ asteˇcnˇe i 3; pˇrestoˇze je to kniha z dneˇsn´ıho pohledu jiˇz zastaral´a, lze ji poˇr´ ad jednoznaˇcnˇe doporuˇcit, protoˇze to je jedna z nejlepˇs´ıch knih, co byla kdy o UNIXu naps´ ana. V roce 1993 vyˇsel ˇcesk´ y pˇreklad, Principy operaˇ cn´ıho syst´ emu UNIX, SAS.
4
3. Popis struktury a funkc´ı j´ adra FreeBSD 5.2; tato kniha navazuje na klasickou knihu The Design and Implementation of the 4.4 BSD Operating System od stejn´eho autora (resp. jeden ze ˇctyˇr, uveden´ y jako prvn´ı). 4. Nejlepˇs´ı kniha o operaˇcn´ım syst´emu Solaris. Obsahuje podrobn´e informace o tom, jak tento syst´em funguje vˇcetnˇe nejnovˇejˇs´ıch vˇec´ı z verze 10 jako jsou z´ ony, Crypto Framework, DTrace, Least Privilege model a dalˇs´ı. 5. Domovsk´ a strana Linux dokumentaˇcn´ıho projektu.
Literatura - programov´ an´ı 1. Stevens, W. R., Rago, S. A.: Advanced Programming in UNIX(r) Environment. Addison-Wesley, 2nd edition, 2005. 2. Rochkind, M. J.: Advanced UNIX Programming, Addison-Wesley; 2nd edition, 2004 3. Stevens, W. R., Fenner B., Rudoff, A. M.: UNIX Network Programming, Vol. 1 – The Sockets Networking API. Prentice Hall, 3rd edition, 2004 4. Butenhof, D. R.: Programming with POSIX Threads, Addison-Wesley; 1st edition, 1997 5. unixov´e specifikace, viz http://www.unix.org 6. manu´ alov´e str´ anky (zejm. sekce 2, 3)
1. Pravdˇepodobnˇe nen´ı lepˇs´ı knihy o programov´an´ı pod unixem (neobsahuje s´ıt’ov´e programov´ an´ı, to je v knize 3). 2. Aktualizovan´e vyd´ an´ı dalˇs´ı z klasick´ ych knih o programov´an´ı pod unixem. Obsahuje i s´ıt’ov´e programov´ an´ı a aˇc samozˇrejmˇe nen´ı tak podrobn´a jako spojen´ı knih 1 a 3, m˚ uˇze to b´ yt nˇekdy naopak v´ yhodou. Tuto knihu jednoznaˇcnˇe doporuˇcuji, pokud chcete nˇeco kupovat a chcete m´ıt pro zaˇc´atek jen jednu knihu. Pokud bych ji mˇel charakterizovat jednou vˇetou, bylo by to, ˇze autor vid´ı a dok´ aˇze uk´ azat souvislosti, coˇz je velmi vz´acn´a vlastnost. 3. Klasick´ a kniha o s´ıt’ov´em programov´an´ı, jedna z nejlepˇs´ıch k tomuto t´ematu; existuje i druh´ y d´ıl UNIX Network Programming, Volume 2: Interprocess Communications, kter´a se zab´ yv´a komunikac´ı mezi procesy (roury, POSIX IPC, System V IPC, synchronizace vl´aken, RPC). 4. Velmi dobr´ a a podrobn´ a kniha o programov´an´ı s vl´akny. 5. Domovsk´ a str´ anka posledn´ıch specifikac´ı rozhran´ı UNIXu. 5
6. Podrobn´ y popis jednotliv´ ych funkc´ı (v Linuxu bˇeˇznˇe ne zcela dostaˇcuj´ıc´ı; manu´ alov´e str´ anky v tomto syst´emu jsou ˇcasto horˇs´ı kvality neˇz u syst´em˚ u ostatn´ıch). 7. Kniha, kter´ a se neveˇsla na slajd a kter´a pˇrekraˇcuje obsah t´eto pˇredn´aˇsky: Gallmeister, B. R.: POSIX.4 Programmers Guide: Programming for the Real World, O’Reilly; 1st edition, 1995. V´ yborn´a kniha s kr´asnou ob´ alkou o real-time rozˇs´ıˇren´ıch POSIXu. Viz tak´e strany 161 a 165. . . . a spousta dalˇs´ıch knih, online dokumentac´ı a internetov´ ych zdroj˚ u, posledn´ı dobou vych´ az´ı pomˇernˇe hodnˇe knih o Linuxu, zamˇeˇren´ ych na pouˇz´ıv´an´ı i programov´ an´ı. . . . jdˇete na http://www.amazon.com/ a zadejte kl´ıˇcov´e slovo “unix”. Pokud byste z Amazonu nˇeco kupovali, dejte pozor na to, ˇze mnoho knih m´a aktualizovan´ a vyd´ an´ı i po nˇekolika m´alo letech, nˇekdy i levnˇejˇs´ı neˇz ta p˚ uvodn´ı, kter´ a jsou vˇsak st´ ale na skladu a v on-line nab´ıdce; tak at’ zbyteˇcnˇe nekoup´ıte starˇs´ı vyd´ an´ı neˇz to aktu´ aln´ı. Nav´ıc se vyplat´ı zkontrolovat i u pˇr´ısluˇsn´eho vydavatelstv´ı, ˇze nen´ı v brzk´e dobˇe napl´anov´ano vyd´an´ı nov´e – tato informace nˇekdy na Amazonu je, nˇekdy ne. . . . na Amazonu se m˚ uˇze vyplatit nakoupit knihy z druh´e ruky, protoˇze jsou ˇcasto v´ yraznˇe levnˇejˇs´ı neˇz knihy nov´e. Probl´em je, ˇze vˇetˇsinou nen´ı moˇzn´e je poslat ˇ ale mus´ı v´ pˇr´ımo do CR, am je nˇekdo pˇriv´ezt.
Literatura - historie UNIXu • Peter Salus: A Quarter Century of UNIX, Addison-Wesley; 1st edition (1994) • Libes D., Ressler, S.: Life With Unix: A Guide for Everyone, Prentice Hall (1989) • Open Sources: Voices from the Open Source Revolution, kapitola Twenty Years of Berkeley Unix From AT&T-Owned to Freely Redistributable; O’Reilly (1999); on-line na webu: http://oreilly.com/openbook/opensources/book/index.html . . . mnoho materi´ al˚ u na webu; ˇcasto vˇsak obsahuj´ıc´ı ne zcela pˇresn´e informace
• Kapitola o BSD Unixu z Open Sources napsan´a Marshallem Kirk McKusickem je opravdu v´ yborn´ a.
6
(Pre)historie UNIXu • 1925 – Bell Telephone Laboratories – v´ yzkum v komunikac´ıch (napˇr. 1947: transistor) v r´amci AT&T • 1965 – BTL s General Electric a MIT v´ yvoj OS Multics (MULTIplexed Information and Computing System) • 1969 – Bell Labs opouˇst´ı projekt, Ken Thompson p´ıˇse assembler, z´ akladn´ı OS a syst´em soubor˚ u pro PDP-7 • 1970 – Multi-cs ⇒ Uni-cs ⇒ Uni-x • 1971 – UNIX V1, a portov´an na PDP-11 • prosinec 1971 – prvn´ı edice UNIX Programmer’s Manual
• AT&T = American Telephone and Telegraph Company • Multics byl syst´em, kter´ y v´ yznamnˇe ovlivnil dalˇs´ı v´ yvoj operaˇcn´ıch syst´em˚ u. Obsahoval mnoho v t´e dobˇe inovativn´ıch myˇslenek, z nichˇz ale ne vˇsechny byly pˇrij´ım´ any kladnˇe. V´ yznamnˇe ovlivnil pr´avˇe UNIX, kter´ y mnoho myˇslenek pˇrevzal a jeho nedostatky se naopak snaˇzil napravit. Hlavn´ı rozd´ıl byl asi ten, ˇze UNIX byl navrˇzen jako mnohem jednoduˇsˇs´ı syst´em, neˇz Multics. • po odchodu BTL z projektu Multics prodala GE svoji poˇc´ıtaˇcovou divizi firmˇe Honeywell vˇcetnˇe projektu Multics, kter´ y se pak pod jej´ı patronac´ı d´ ale aktivnˇe vyv´ıjel (virtu´ aln´ı pamˇet’, multiprocesory, . . . ), aˇz do roku 1985. Posledn´ı instalace Multics-u fungovala na kanadsk´em Ministerstvu obrany (Canadian Department of National Defence) a syst´em byl napˇr´ıklad jeˇstˇe aktivnˇe pouˇz´ıv´ an pro vojensk´e operace bˇehem v´alky v Persk´em z´alivu. Definitivn´ı shutdown byl proveden 31. ˇr´ıjna 2000. V´ıce informac´ı je moˇzn´e nal´ezt na http://www.multicians.org. • pˇred poˇc´ atkem pr´ ace na v´ yvojov´em prostˇred´ı pro PDP-7 napsal Thompson program Space Travel, kter´ y byl vyvinut na jin´em prostˇred´ı (Honeywell 635) a na p´ asce pˇrenesen na PDP-7. • celkem bylo 10 edic´ı tohoto manu´alu, koresponduj´ıc´ı deseti verz´ım UNIXu vznikl´ ych v BTL. • UNIX V1 nemˇel vol´ an´ı pipe !!!
7
• manu´ al pro verzi 1: http://man.cat-v.org/unix-1st/ Stoj´ı za to nahl´ednout, jak jeho struktura ovlivnila vzhled dneˇsn´ıch manu´alov´ ych str´anek. • za povˇ simnut´ı stoj´ı, ˇ ze UNIX je zhruba o 10 let starˇ s´ı neˇ z DOS • syst´em Multics mˇel 9 hlavn´ıch c´ıl˚ u, jak pops´ano v ˇcl´anku Introduction and Overview of the Multics System z roku 1965. Za nejzaj´ımavˇejˇs´ı c´ıl bych povaˇzoval poˇzadavek na nepˇreruˇsovan´ y bˇeh syst´emu. • Multics byl napsan´ y v jazyce PL/I (Programming Language #1), tedy dˇr´ıve neˇz byl UNIX pˇrepsan´ y do C ! • Multicsu byl v roce 1980 udˇelen jako prvn´ımu syst´emu level B2. Po nˇekolik let to byl jedin´ y syst´em s t´ımto bezpeˇcnost´ım levelem. • GE byla zaloˇzena v roce 1892 slouˇcen´ım dvou spoleˇcnost´ı, z nichˇz jedna byla Edison General Electric Company zaloˇzen´a roku 1879 Thomasem Alvou Edisonem (vyn´ alezce ˇz´ arovky, filmov´e kamery, . . . ); v souˇcasn´e dobˇe jej´ı dceˇrinn´e spoleˇcnosti pokr´ yvaj´ı mnoho oblast´ı, vˇcetnˇe dod´avky jednoho ze dvou typ˚ u motor˚ u pro Airbus 380 nebo bankovnictv´ı. • PDP = Programmed Data Processor. Prvn´ı typ, PDP-1, se prod´avala za $120.000 v dobˇe, kdy se jin´e poˇc´ıtaˇce prod´avaly za ceny pˇres mili´on. To byla tak´e strategie fy DEC - pojem computer tehdy znamenal drahou vˇec, potˇrebuj´ıc´ı s´ al a t´ ym lid´ı, kter´ y se o to vˇsechno bude starat. Proto DEC sv´e maˇsiny nenaz´ yval poˇc´ıtaˇci, ale pravˇe slovem PDPs. • PDP-11 je legend´ arn´ı maˇsina od firmy DEC, postupnˇe vznikaly verze PDP1 az PDP-16, kromˇe PDP-2, PDP-13. Existuj´ı PDP-11 syst´emy, kter´e jeˇstˇe dnes bˇeˇz´ı, a tak´e firmy, kter´e pro nˇe vyr´abˇej´ı n´ahradn´ı d´ıly.
8
Historie UNIXu, pokraˇ cov´ an´ı • u ´nor 1973 – UNIX V3 obsahoval cc pˇrekladaˇc (jazyk C byl vytvoˇren Dennisem Ritchiem pro potˇreby UNIXu) • ˇr´ıjen 1973 – UNIX byl pˇredstaven veˇrejnosti ˇcl´ankem The UNIX Timesharing System na konferenci ACM • listopad 1973 – UNIX V4 pˇ reps´ an do jazyka C • 1975 – UNIX V6 byl prvn´ı verz´ı UNIXu bˇeˇznˇe k dost´an´ı mimo BTL • 1979 – UNIX V7, pro mnoh´e “the last true UNIX”, obsahoval uucp, Bourne shell; velikost kernelu byla pouze 40KB !!! • 1979 – UNIX V7 portov´an na 32-bitov´ y VAX-11 • 1980 – Microsoft pˇr´ıch´ az´ı s XENIXem, kter´ y je zaloˇzen´ y na UNIXu V7
• ACM = Association for Computing Machinery, zaloˇzena 1947. UNIX pˇredstavili Ken Thompson a Dennis Ritchie. • akt pˇ reps´ an´ı UNIXu do jazyka C byl moˇ zn´ a nejv´ yznamnˇ ejˇ s´ım momentem v historii tohoto syst´ emu ⇒ UNIX mohl b´ yt mnohem jednoduˇseji portov´ an na jin´e architektury • na verzi 6 je zaloˇzena legend´ arn´ı kniha A commentary on the Unix Operating System, jej´ıˇz autorem je John Lions. • Microsoft neprod´ aval XENIX pˇr´ımo, ale licencoval ho OEM v´ yrobc˚ um (Original Equipment Manufacturer) jako byl Intel, SCO a jin´ı. Jin´e firmy pak XENIX d´ ale portovaly na 286 (Intel) a 386 (SCO, 1987). Na webu je moˇzn´e naj´ıt zaj´ımav´e informace popisuj´ıc´ı tuto dobu a tehdy kladn´ y vztah Microsoftu k UNIXu. • UNIX V7 mˇel cca 188 tis´ıc ˇr´adek zdrojov´eho k´odu v cca 1100 souborech (zjiˇstˇeno pomoc´ı find, wc a awk pˇres soubory se jm´enem *.[cshy]). • pokud v´ as v´ıce zaj´ım´ a historie unixu, pod´ıvejte se na Wikipedii na heslo “unix” a skrz odkazy m´ ate na dlouho co ˇc´ıst. • V roce 1973 byl UNIX v´ıceuˇzivatelsk´ y syst´em (konta s hesly) s podporou pro multiprocessing s ochranou proces˚ u a str´ankov´an´ım. Mˇel sign´aly, roury, hierarchick´ y syst´em soubor˚ u s mount pointy, souborov´a pr´ava (User/group/other r/w/x), hard linky, zaˇr´ızen´ı pˇr´ıstupn´a jako soubory. Kernel byl naps´an v jazyku C a v pamˇeti zab´ıral jeho obraz 26 Kilobyt˚ u. Pro pr´aci se soubory slouˇzily syst´emov´ a vol´ an´ı open(), close(), read(), write(), seek(), 9
pipe(). K manipulaci s procesy vol´an´ı fork(), exec(), wait(), exit(). Celkem bylo 48 syscall˚ u, z nich existuje 35 do dneˇsn´ı doby.
Divergence UNIXu • pol. 70. let – uvolˇ nov´ an´ı UNIXu na univerzity: pˇredevˇs´ım University of California v Berkeley • 1979 – z UNIX/32V (zm´ınˇen´ y port na VAX) poskytnut´eho do Berkeley se vyv´ıj´ı BSD Unix (Berkeley Software Distribution) verze 3.0; posledn´ı verze 4.4 v roce 1993 • 1982 AT&T, vlastn´ık BTL, m˚ uˇze vstoupit na trh poˇc´ıtaˇc˚ u (zak´ az´ ano od roku 1956) a pˇr´ıch´az´ı s verz´ı System III (1982) aˇz V.4 (1988) – tzv. SVR4 • vznikaj´ı UNIX International, OSF (Open Software Foundation), X/OPEN, . . . • 1991 – Linus Torvalds zah´ajil v´ yvoj OS Linux, verze j´adra 1.0 byla dokonˇcena v r. 1994
• UNIX je univerz´ aln´ı operaˇcn´ı syst´em funguj´ıc´ı na ˇsirok´e ˇsk´ale poˇc´ıtaˇc˚ u od embedded a handheld syst´em˚ u (Linux), pˇres osobn´ı poˇc´ıtaˇce aˇz po velk´e servery a superpoˇc´ıtaˇce. • UNIX V3 = UNIX verze 3, UNIX V.4 = system 5 release 4 atd., tj. UNIX V3 != SVR3. • UNIX System III tedy nen´ı UNIX V3; v t´eto dobˇe (pozdn´ı 70. l´eta) bylo v BTL nˇekolik skupin, kter´e pˇr´ısp´ıvaly do v´ yvoje UNIXu. Vx verze byly vyv´ıjeny v r´ amci Computer Research Group, dalˇs´ı skupiny byly Unix System Group (USG), Programmer’s WorkBench (PWB). Dalˇs´ı vˇetv´ı UNIXu byl Columbus UNIX t´eˇz v r´ amci BT. Na tˇechto r˚ uzn´ ych verz´ıch je pr´avˇe zaloˇzena verze System III. Z´ ajemce o v´ıce informac´ı odkazuji na web. • UNIX se rozˇstˇepil na dvˇe hlavn´ı vˇetve: AT&T a BSD, jednotliv´ı v´ yrobci pˇrich´ azeli s vlastn´ımi modifikacemi. Jednotliv´ e klony od sebe navz´ ajem pˇ reb´ıraly vlastnosti. • System V R4 mˇel cca 1.5 milionu ˇr´adek zdrojov´eho k´odu v cca 5700 souborech (zjiˇstˇeno pomoc´ı find, wc a awk pˇres soubory se jm´enem *.[cshy]). • Univerzita v Berkeley z´ıskala jako jedna z prvn´ıch licenci UNIXu v roce 1974. Bˇehem nˇekolika let studenti (jedn´ım z nich byl Bill Joy, pozdˇejˇs´ı zakladatel firmy Sun Microsystems a autor C-shellu) vytvoˇrili SW bal´ık Berkeley Software Distribution (BSD) a prod´avali ho v roce 1978 za $50. Tyto poˇc´ateˇcn´ı 10
verze BSD obsahovaly pouze SW a utility (prvn´ı verze: Pascal pˇrekladaˇc, editor ex ), ne syst´em ani ˇz´ adn´e jeho zmˇeny. To pˇriˇslo aˇz s verz´ı 3BSD. verze 4BSD vznik´ a roku 1980 jiˇz jako projekt financovan´ y agenturou DARPA a veden´ y Billem Joyem. Trp´ı probl´emy spojen´ ymi s nedostateˇcn´ ym v´ ykonem a vznik´ a tak vyladˇen´ y syst´em 4.1BSD dod´avan´ y od roku 1981. • 4.1BSD mˇelo b´ yt p˚ uvodnˇe 5BSD, ale pot´e, co AT&T vzneslo n´amitky, ˇze by si z´ akazn´ıci mohli pl´est 5BSD se syst´emem System V, pˇreˇslo BSD na ˇc´ıslov´an´ı 4.xBSD. Bˇeˇznou vˇec´ı bylo, ˇze neˇz ps´at vlastn´ı k´od, v´ yvoj´aˇri z Berkeley se radˇeji nejdˇr´ıve pod´ıvali kolem, co je jiˇz hotov´e. Tak BSD napˇr´ıklad pˇrevzalo virtu´ aln´ı pamˇet’ z Machu a nebo NFS-kompatibiln´ı k´od vyvinut´ y na jedn´e kanadsk´e univerzitˇe. • v´ yrobci hardware dod´ avali varianty UNIXu pro sv´e poˇc´ıtaˇce a komercializace tak jeˇstˇe zhorˇsila situaci co t´ yˇce diverzifikace totoho syst´emu • v 80-t´ ych letech se proto zaˇcaly objevovat snahy o standardizaci. Standard ˇr´ık´ a, jak m´ a vypadat syst´em navenek (pro uˇzivatele, program´atora a spr´avce), nezab´ yv´ a se implementac´ı. C´ılem je pˇrenositelnost aplikac´ı i uˇzivatel˚ u. Vˇsechny syst´emy totiˇz z d´ alky vypadaly jako UNIX, ale pˇri bliˇzˇs´ım prozkoum´an´ı se liˇsily v mnoha d˚ uleˇzit´ ych vlastnostech. System V a BSD se napˇr. liˇsily v pouˇzit´em filesyst´emu, s´ıt’ov´e architektuˇre i v architektuˇre virtu´aln´ı pamˇeti. • kdyˇz v roce 1987 firmy AT&T a Sun (jehoˇz tehdejˇs´ı SunOS byl zaloˇzen´ y na BSD) spojily svoje u ´sil´ı na vyvinut´ı jednoho syst´emu, kter´ y by obsahoval to nejlepˇs´ı z obou vˇetv´ı, kromˇe nadˇsen´ ych reakc´ı to vzbudilo i strach u mnoha dalˇs´ıch v´ yrobc˚ u unixov´ ych syst´em˚ u, kteˇr´ı se b´ali, ˇze by to pro obˇe firmy znamenalo obrovskou komerˇcn´ı v´ yhodu. Vznik´a proto Open Software Foundation (nezamˇen ˇovat za FSF), a zakl´adaj´ıc´ımi ˇcleny byly mimo jin´e firmy Hewlett-Packard, IBM a Digital. Z toho vzeˇsl´ y syst´em OSF/1 ale nebyl pˇr´ıliˇs u ´spˇeˇsn´ y, a dod´ aval ho pouze Digital, kter´ y ho pˇrejmenoval na Digital UNIX. Zaj´ımavost´ı je, ˇze syst´em je postaven´ y na mikroj´adru Mach. Po akvizici Digitalu Compaqem byl syst´em pˇrejmenov´an na Tru64 a s t´ımto jm´enem je d´ale podporov´ an firmou Hewlett-Packard, kter´a se v roce 2002 s Compaqem spojila. Mezit´ım firmy AT&T a Sun kontrovaly zaloˇzen´ım UNIX International. Toto obdob´ı pˇrelomu 80-t´ ych a 90-t´ ych let se naz´ yv´a Unix Wars – boj o to, co bude “standardn´ım unixem”. • OSF a UI se staly velk´ ymi rivaly, ale velmi rychle se stˇretly s neˇcekan´ ym protivn´ıkem - s firmou Microsoft. • (1992) 386BSD zaloˇzen´e na Networking Release 2 ; Bill Jolitz vytvoˇril 6 chybˇej´ıc´ıch soubor˚ u a dal tak dohromady funkˇcn´ı BSD syst´em pro i386. Tento syst´em se stal z´ akladem syst´em˚ u NetBSD a FreeBSD (a dalˇs´ıch, z tˇechto dvou syst´em˚ u vych´ azej´ıc´ıch). • (1995) 4.4BSD-Lite Release 2, po kter´e n´asleduje rozpuˇstˇen´ı CSRG, kter´a skoro 20 let pilotovala v´ yvoj BSD vˇetve. V´ıce jiˇz zm´ınˇen´a kapitola o BSD Unixu.
11
Souˇ casn´ e UNIXy Hlavn´ı komerˇcn´ı unixov´e syst´emy: • Sun Microsystems: SunOS (nen´ı jiˇz d´ale vyv´ıjen), Solaris • Apple: Mac OS X • SGI: IRIX • IBM: AIX • HP: HP-UX, Tru64 UNIX (Compaq) • SCO: SCO Unix • Novell: UNIXware Open source: • FreeBSD, NetBSD, OpenBSD • Linux distribuce
• Striktnˇe technicky vzato se jako UNIX m˚ uˇze oznaˇcovat pouze syst´em, kter´ y proˇsel certifikac´ı Single Unix Specification. Z v´ yˇse uveden´eho seznamu by to byly 4 operaˇcn´ı syst´emy, kter´e byly registrov´any jako UNIX 03 na r˚ uzn´ ych architektur´ ach (http://www.opengroup.org/openbrand/register/). Ostatn´ı syst´emy, kter´e nebyly certifikov´any, se oznaˇcuj´ı jako Unix-like, aˇckoliv v mnoha pˇr´ıpadech splˇ nuj´ı vˇetˇsin˚ u poˇzadavk˚ u standardu. Bˇeˇznˇe se nicm´enˇe pouˇz´ıv´a oznaˇcen´ı Unix pro obˇe dvˇe skupiny. • kdyˇz jsem cca v roce 1998 projel nmapem vˇsechny DNS root servery, abych zjistil na kter´ ych syst´emech bˇeˇz´ı, bylo 80% z nich na SunOS/Solaris. IRIX je zase syst´em, kter´ y po mnoho let ovl´adal televizn´ı/filmov´ y pr˚ umysl (napˇr. Pixar studio kde na IRIXu vznikly filmy jako A Bug’s Life, Toy Story a dalˇs´ı). A na AIX napˇr´ıklad bˇeˇzel Deep Blue, paraleln´ı superpoˇc´ıtaˇc, kter´ y v roce 1997 porazil v ˇsesti fascinuj´ıc´ıch z´apasech 3.5 ku 2.5 u ´ˇraduj´ıc´ıho velmistra ˇsachu Garriho Kasparova. Jin´ ymi slovy – kaˇzd´ y syst´em m´a svoje u ´spˇechy. • jak jiˇz bylo zm´ınˇeno, Tru64 UNIX vych´az´ı z OSF/1 firmy DEC. Ta pˇredt´ım dod´ avala Ultrix, zaloˇzen´ y na BSD unixu. • OpenSolaris byl projekt vznikl´ y v ˇcervnu 2005 a byl zaloˇzen na podmnoˇzinˇe zdrojov´ ych text˚ u v´ yvojov´e verze Solarisu (kernel, knihovny, pˇr´ıkazy). Distribuce vzeˇsl´e z komunity jsou napˇr´ıklad LiveCD BeleniX, SchilliX a Nexenta. Pˇrestoˇze projekt OpenSolaris jiˇz d´ale nepokraˇcuje, existuje open source fork Illumos a na nˇem zaloˇzen´e distribuce SmartOS a OpenIndiana. • pozor na to, ˇze Linux je pouze j´adro, ne syst´em, na rozd´ıl tˇreba od FreeBSD. Illumos je nˇeco mezi t´ım, j´adro + drivery, z´akladn´ı pˇr´ıkazy a knihovny, 12
a v bin´ arn´ı podobˇe to m´ a nˇeco pˇres 100MB. V obou pˇr´ıpadech je tedy spr´ avn´e pouˇz´ıvat spojen´ı “Linux (Illumos) distribuce”, kdyˇz se bav´ıme o cel´em syst´emu. • kaˇzd´ a komerˇcn´ı varianta vych´azela z jednoho ze dvou hlavn´ıch syst´em˚ u – UNIX V nebo BSD, a pˇrid´ avala si sv´e vlastnosti. D´ıky mnoha verz´ım UNIXu tak vznik´ a velk´ y poˇcet r˚ uzn´ ych standard˚ u (strana 15). Nakonec se vˇetˇsina v´ yrobc˚ u shodla na nˇekolika z´akladn´ıch. • graf zn´ azorˇ nuj´ıc´ı historii unixov´ ych syst´em˚ u a z´avislosti mezi nimi na 19-ti A4 listech ve form´ atu PS/PDF je k nalezen´ı na http://www.levenez.com/unix/
13
Standardy UNIXu • SVID (System V Interface Definition) – ,,fialov´ a kniha”, kterou AT&T vydala poprv´e v roce 1985 – dnes ve verzi SVID3 (odpov´ıd´ a SVR4)
• POSIX (Portable Operating System based on UNIX) – s´erie standard˚ u organizace IEEE znaˇcen´ a P1003.xx, postupnˇe je pˇrej´ım´ a vrcholov´ y nadn´ arodn´ı org´ an ISO
• XPG (X/Open Portability Guide) – doporuˇcen´ı konsorcia X/Open, kter´e bylo zaloˇzeno v r. 1984 pˇredn´ımi v´ yrobci platforem typu UNIX
• Single UNIX Specification – standard organizace The Open Group, vznikl´e v roce 1996 slouˇcen´ım X/Open a OSF – dnes Version 3 (SUSv3), pˇredchoz´ı Version 2 (SUSv2) – splnˇen´ı je nutnou podm´ınkou pro uˇzit´ı obchodn´ıho n´ azvu UNIX
• z´ akladn´ı informace je, ˇze oblast standard˚ u t´ ykaj´ıc´ı se unixov´ ych syst´em˚ u je vˇec znaˇcnˇe sloˇzit´ a a na prvn´ı pohled velmi nepˇrehledn´a. • AT&T dovolila v´ yrobc˚ um naz´ yvat svoji komerˇcn´ı UNIX variantu “System V” pouze pokud splˇ novala podm´ınky standardu SVID. AT&T tak´e publikovala System V Verification Suite (SVVS), kter´e ovˇeˇrilo, zda dan´ y syst´em odpov´ıd´a standardu. • POSIX (Portable Operating System Interface) je standardizaˇcn´ı snaha organizace IEEE (Institute of Electrical and Electronics Engineers). • SUSv3 je spoleˇcn´ y standard The Open Group, IEEE (Std. 1003.1, 2003 Edition) a ISO (ISO/IEC 9945-2003). • Pro certifikaci operaˇcn´ıho syst´emu na Single Unix Specification je nutn´e aby syst´em (na dan´e architektuˇre, napˇr. 64-bit x86) proˇsel sadou test˚ u. V´ ysledky test˚ u jsou pak vyhodnoceny. Testy samotn´e jsou sdruˇzeny do tzv. test suites, coˇz jsou sady automatick´ ych test˚ u, kter´e projdou syst´em a zjist´ı jestli splˇ nuje rozhran´ı dan´e normou. Pro SUSv3 je takov´ ych test suites cca 10. • Rozhran´ı specifikovan´e normou POSIX.1-2008 se dˇel´ı na 4 z´akladn´ı skupiny: XSH (System Interfaces), XCU (Shell and Utilities), XBD (Base definitions). Z nich je co do poˇctu rozhran´ı nejobs´ahlejˇs´ı XSH, kter´a popisuje v´ıce neˇz 1000 rozhran´ı. • Skupiny rozhran´ı POSIXu spolu se skupinou Xcurses, kter´a je souˇc´ast´ı Single Unix Specification (ale nikoliv souˇc´ast POSIX b´aze v normˇe IEEE Std 1003.12001) zahrnuj´ı celkem 1742 rozhran´ı, kter´e tvoˇr´ı Single Unix Specification 14
(2003). Tabulky rozhran´ı SUS je moˇzn´e z´ıskat zde: http://www.unix.org/ version3/inttables.pdf • komerˇcn´ı UNIXy vˇetˇsinou sleduj´ı Single UNIX Specification, splnˇen´ı t´eto normy je pr´ avˇe podm´ınkou pro uˇzit´ı n´azvu UNIX (znaˇcka UNIX 98 odpov´ıd´a SUSv2, znaˇcka UNIX 03 odpov´ıd´a SUSv3). Je postavena na b´azi POSIXu. My se budeme drˇzet SUSv3. Popis datov´ ych struktur a algoritm˚ u j´adra v tomto materi´ alu bude vˇetˇsinou vych´azet ze System V Rel. 4. • na Solarisu je obs´ ahl´ a manu´ alov´a str´anka standards(5), kde m˚ uˇzete na jednom m´ıstˇe nal´ezt mnoho informac´ı t´ ykaj´ıc´ı se standard˚ u. Jednotliv´e pˇr´ıkazy splˇ nuj´ıc´ı danou normu jsou nav´ıc um´ıstˇeny do vyhrazen´ ych adres´aˇr˚ u. Napˇr. program tr je v adres´ aˇr´ıch /usr/xpg4/bin/ a /usr/xpg6/bin/, v kaˇzd´em je verze pˇr´ıkazu splˇ nuj´ıc´ı danou normu. Na pˇrep´ınaˇce a chov´an´ı dan´e normou se pak lze spolehnout napˇr. pˇri psan´ı shellov´ ych skript˚ u. • opˇet na Solarisu, pod´ıvejte se na hlaviˇckov´ y soubor /usr/include/sys/feature tests.h
POSIX • tvrzen´ı “tento syst´em je POSIX kompatibiln´ı” ned´av´a ˇz´adnou konkr´etn´ı informaci – asi podporuje POSIX1990 a moˇzn´a i nˇeco dalˇs´ıho (co?) • dotyˇcn´ y bud’ nev´ı co je POSIX nebo si mysl´ı, ˇze to nev´ıte vy • jedin´ a rozumn´ a reakce je ot´azka “jak´ y POSIX?” • POSIX je rodina standard˚ u • prvn´ım dokumentem je IEEE Std POSIX1003.1-1988, pozdˇeji po vzniku dalˇs´ıch rozˇs´ıˇren´ı neform´alnˇe odkazovan´ y jako POSIX.1 • posledn´ı verze POSIX.1 je IEEE Std 1003.1, 2004 Edition – obsahuje v sobˇe jiˇz i to, co dˇr´ıve definoval POSIX.2 (Shell and Utilities) a r˚ uzn´a, dˇr´ıve samostatn´a rozˇs´ıˇren´ı
• prvn´ım dokumentem je IEEE Std POSIX1003.1-1988, dˇr´ıve oznaˇcovan´ y prostˇe jako POSIX, pak odkazovan´ y jako POSIX.1, protoˇze POSIXem se nyn´ı m´ın´ı sada vz´ ajemnˇe souvisej´ıc´ıch standard˚ u. POSIX.1 v t´e dobˇe obsahoval programovac´ı API, tj. pr´ace s procesy, sign´aly, soubory, ˇcasovaˇci atd. S mal´ ymi zmˇenami byl pˇrevzat organizac´ı ISO (ISO 9945-1:1990 ), a je oznaˇcovan´ y i jako POSIX1990. IEEE oznaˇcen´ı je IEEE Std POSIX1003.1-1990. Tento standard byl s´ am o sobˇe velk´ yu ´spˇech, ale st´ale jeˇstˇe nespojoval t´abory System V a BSD, protoˇze v sobˇe napˇr´ıklad nezahrnoval BSD sockety nebo 15
IPC (semafory, zpr´ avy, sd´ılen´a pamˇet’) ze System V. Souˇc´ast´ı standardu je i “POSIX conformance test suite (PCTS)”, kter´ y je volnˇe k dispozici. • oznaˇcen´ı POSIX vymyslel Richard Stallman, tedy ˇclovˇek, kter´ y v roce 1983 zaloˇzil GNU projekt. • d˚ uleˇzit´ a roˇzˇs´ıˇren´ı k IEEE Std 1003.1-1990 (jsou souˇc´ast´ı IEEE Std 1003.1, 2004 Edition): – IEEE Std 1003.1b-1993 Realtime Extension, neform´alnˇe zn´am´ y jako POSIX.4, protoˇze to bylo jeho p˚ uvodn´ı oznaˇcen´ı pˇred pˇreˇc´ıslov´an´ım; j´ a budu toto rozˇs´ıˇren´ı nˇekdy tak´e naz´ yvat POSIX.4. Vˇetˇsina tohoto rozˇs´ıˇren´ı je nepovinn´ a, takˇze tvrzen´ı “syst´em podporuje POSIX.1b” m´a jeˇstˇe horˇs´ı vypov´ıdac´ı hodnotu neˇz “syst´em je POSIX kompatibiln´ı”, a to prakticky nulovou. Jedin´a povinn´a ˇc´ast POSIX.4 je mal´e doplnˇen´ı k sign´ al˚ um oproti POSIX1990. Je proto nutn´e vˇzdy uv´est, co z POSIX.4 je implementov´ ano – napˇr. sd´ılen´a pamˇet’, semafory, real-time sign´aly, zamyk´ an´ı pamˇeti, asynchronn´ı I/O, ˇcasovaˇce atd. – IEEE Std 1003.1c-1995 Threads, viz strana 219. – IEEE Std 1003.1d-1999 Additional Realtime Extensions – IEEE Std 1003.1j-2000 Advanced Realtime Extensions, viz strana 240. – ... • standardy POSIX je moˇzn´e nal´ezt na http://www.open-std.org/. HTML verze je volnˇe k prohl´ıˇzen´ı, za PDF verzi se plat´ı.
Jazyk C • t´emˇeˇr cel´ y UNIX je napsan´ y v C, pouze nejniˇzˇs´ı strojovˇe z´ avisl´ a ˇc´ ast v assembleru ⇒ pomˇernˇe snadn´a pˇrenositelnost • navrhl Dennis Ritchie z Bell Laboratories v roce 1972. • n´ asledn´ık jazyka B od Kena Thomsona z Bell Laboratories. • vytvoˇren jako prostˇredek pro pˇrenos OS UNIX na jin´e poˇc´ıtaˇce – siln´ a vazba na UNIX. • varianty jazyka: – p˚ uvodn´ı K&R C – standard ANSI/ISO C • u ´ spˇ ech jazyka C daleko pˇ res´ ahl u ´ spˇ ech samotn´ eho UNIXu
16
• CPL ⇒ BCPL ⇒ B (Thompson, interpret) ⇒ C • K&R C – jazyk C tak, jak je popsan´ y v klasick´e knize Brian W. Kernighan, Dennis M. Ritchie: The C Programming Language (Prentice-Hall, 1978). • v roce 1983 ANSI (American National Standards Institute) zformoval v´ ybor pro vytvoˇren´ı C standardu. Po dlouh´em a pracn´em procesu byl v roce 1989 standard koneˇcnˇe hotov, a je zn´am´ y nejˇcastˇeji jako “ANSI C”, pˇr´ıpadnˇe jako C89 (napˇr´ıklad pˇrekladaˇc pro tuto normu se v Sun Studiu jmenuje c89, jelikoˇz i to samotn´e jm´eno programu mus´ı b´ yt podle normy). Druh´e vyd´an´ı K&R knihy (1988) je jiˇz upraven´e pr´avˇe pro nadch´azej´ıc´ı ANSI C. V roce 1990 bylo ANSI C adoptov´ ano organizac´ı ISO jako ISO/IEC 9899:1990; tento C standard tak m˚ uˇze b´ yt nˇekdy oznaˇcov´an i jako C90. • se C standardem se pak nˇejakou dobu neh´ ybalo, aˇz na konci 90-t´ ych let proˇsel dalˇs´ı reviz´ı v r´ amci ISO a vznik´a ISO 9899:1999, ˇcastˇeji oznaˇcovan´ y jako C99. V roce 2000 pak naopak tento standard pˇrevzal ANSI. • rozd´ıly mezi C89 a C99 jsou mimo jin´e zahrnut´ı inline funkc´ı, definic´ı promˇenn´ ych napˇr´ıklad i do for konstrukce, jednoˇr´adkov´ ych koment´aˇr˚ u pomoc´ı //, nov´ ych funkc´ı jako snprintf apod. • specifikaci C standardu a mnoho dalˇs´ıch otevˇren´ ych standard˚ u je moˇzn´e nal´ezt na http://www.open-std.org/.
Form´ aty dat • poˇrad´ı bajt˚ u – z´ avis´ı na architektuˇre poˇc´ıtaˇce big endian: 0x11223344 =
11
22
33
44
addr +
0
1
2
3
–
little endian: 0x11223344 =
44
33
22
11
addr +
0
1
2
3
–
• ˇr´ adky textov´ ych soubor˚ u konˇc´ı v UNIXu znakem LF (nikoliv CRLF). Vol´ an´ı putc(’\n’) tedy p´ıˇse pouze jeden znak. • big endian – SPARC, MIPS, s´ıt’ov´e poˇrad´ı bajt˚ u • little endian – Intel
17
• velk´ y pozor na v´ ystupy program˚ u typu hexdump, kter´e defaultnˇe vypisuj´ı soubor v 16-ti bitov´ ych ˇc´ıslech, coˇz sv´ad´ı vidˇet soubor jinak, neˇz jak je opravdu zapsan´ y na disku; viz pˇr´ıklad (i386, FreeBSD). Prvn´ı ˇc´ıslo v souboru je znak ’i’, kter´ y ale reprezentuje niˇzˇs´ıch 8 bit˚ u 16-ti bitov´eho ˇc´ısla, takˇze pokud vyp´ıˇseme prvn´ı 2 bajty jako short integer, mus´ı b´ yt reprezentace znaku ’i’ (tj. ˇc´ıslo 69) aˇz za 6a. Obdobnˇe pro ’kl’. $ echo -n ijkl > test $ hexdump test 0000000 6a69 6c6b 0000004 je samozˇrejmˇe moˇzn´e pouˇz´ıt jin´ y form´at v´ ystupu: $ hexdump -C test 00000000 69 6a 6b 6c 00000004 • UNIX norma pˇr´ıkaz hexdump nem´a, ale definuje od (octal dump), takˇze zde je jeho hexdumpu ekvivalentn´ı form´at v´ ypisu na SPARCu (Solaris); vˇsimnˇete si zmˇeny oproti v´ ypisu pod FreeBSD! $ od -tx2 test 0000000 696a 6b6c 0000004
Deklarace a definice funkce • K&R – deklarace n´ avratov´ y_typ indentifik´ ator(); – definice n´ avratov´ y_typ indentifik´ ator(par [,par...]) typ par;... { /* tˇ elo funkce */ }
• ANSI – deklarace n´ avratov´ y_typ indentifik´ ator(typ par [,typ par...]); – definice n´ avratov´ y_typ indentifik´ ator(typ par [,typ par...]) { /* tˇ elo funkce */ }
18
• pouˇz´ıvejte pouze novˇejˇs´ı (ANSI) typ deklarac´ı a vˇzdy deklarujte prototypy funkc´ı, tj. inkludujte hlaviˇckov´e soubory. V´ yjimkou m˚ uˇze asi jen to, pokud budete pracovat s k´ odem, kter´ y byl napsan´ y podle K&R. • r˚ uzn´ ymi z´ apisy deklarac´ı se dost´av´ame rovnou i k r˚ uzn´ ym styl˚ um psan´ı zdrojov´ ych text˚ u. Nˇekter´e syst´emy to pˇr´ıliˇs neˇreˇs´ı (Linux), jin´e syst´emy maj´ı velmi striktn´ı pravidla pro psan´ı zdrojov´ ych text˚ u (napˇr. Solaris, viz on-line C Style and Coding Standards for SunOS : http://hub.opensolaris. org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf). Snad kaˇzd´ y UNIXov´ y syst´em m´ a program indent(1), kter´ y v´am pomoc´ı pˇrep´ınaˇc˚ u pˇreform´ atuje jak´ ykoli C zdrojov´ y text do poˇzadovan´eho v´ ystupu.
C style • vˇec zd´ anlivˇe podˇradn´ a, pˇritom extr´emnˇe d˚ uleˇzit´a – u ´prava zdrojov´ ych k´ od˚ u programu • mnoho zp˚ usob˚ u jak ano: int main(void) { char c; int i = 0; printf("%d\n", i); return (0); }
• u C stylu je nejd˚ uleˇzitˇejˇs´ı to, aby byl konzistentn´ı. Pokud skupina program´ ator˚ u pracuje na jednom projektu, nen´ı zas aˇz tak d˚ uleˇzit´e, na jak´em stylu se dohodnou (pokud je alespoˇ n trochu rozumn´ y), ale aby se dohodli. Jednotn´ y a dobˇre zvolen´ y styl ˇsetˇr´ı ˇcas a br´an´ı zbyteˇcn´ ym chyb´am.
19
C style (cont.) • mnoho zp˚ usob˚ u jak NE (tzv. assembler styl): int main(void) { int i = 0; char c; printf("%d\n", i); return (0); } • nebo (schizofrenn´ı styl): int main(void) { int i = 0; char c; if (1) printf("%d\n", i);i=2; return (0); }
• pamatujte na to, ˇze dobr´ y styl zdrojov´ ych k´od˚ u je i vizitkou program´atora. Kdyˇz se v r´ amci pˇrij´ımac´ıch pohovor˚ u odevzd´avaj´ı i uk´azkov´e k´ody, tak hlavn´ı d˚ uvod pˇrekvapivˇe nen´ı ten, aby se zjistilo, ˇze v´am dan´ y program funguje. ´ Uprava samotn´eho zdrojov´eho textu je jedn´ım z kriteri´ı, protoˇze to pˇrenesenˇe m˚ uˇze svˇedˇcit i o dalˇs´ıch skuteˇcnostech – nˇekdo napˇr. bude odhadovat, ˇze pokud p´ıˇsete neˇcist´ y a neupraven´ y k´od, tak jste moˇzn´a jeˇstˇe nepracovali na nˇeˇcem opravdu sloˇzit´em ˇci nˇeˇcem zahrnuj´ıc´ım spolupr´aci s v´ıce program´atory, protoˇze v tom pˇr´ıpadˇe je rozumnˇe ˇcist´ y k´od jednou z podm´ınek u ´spˇechu a jednou ze zkuˇsenost´ı, kter´e z takov´e spolupr´ace vych´azej´ı. Toto je samozˇrejmˇe zˇc´ asti subjektivn´ı n´ azor, ale ˇcist´ ym k´odem nic nezkaz´ıte, minim´alnˇe nekaz´ıte oˇci sv´ ym cviˇc´ıc´ım. • informace o C stylu pouˇz´ıvan´em pro zdrojov´e k´ody Solarisu, vˇcetnˇe skriptu, kter´ y v´ am zdroj´ aky zkontroluje a upozorn´ı na pˇr´ıpadn´e nedostatky, je moˇzn´e nal´ezt na http://mff.devnull.cz/pvu/common/cstyle.html. Pokud budete dodrˇzovat tento styl pˇri psan´ı zkouˇskov´ ych pˇr´ıklad˚ u, uˇsetˇr´ıte mi t´ım pr´aci pˇri vyhodnocov´ an´ı. Obecnˇe je dobr´e, abyste si zkusili, co je to ps´at k´od podle nˇejak´eho konkr´etn´ıho stylu, a ten pro dan´ y ,,projekt” dodrˇzet. • bohuˇzel ani v´ yˇse uveden´ y skript nen´ı vˇsemocn´ y. Je zaloˇzen´ y na kontrole pomoc´ı regul´ arn´ıch v´ yraz˚ u, takˇze tˇreba ten assembler styl ze slajdu projde bez chyby, protoˇze bez skuteˇcn´e anal´ yzy k´odu nen´ı moˇzn´e (?) kontrolovat spr´ avn´e odsazov´ an´ı. Vˇse ostatn´ı je na nˇem, dle cstyle skriptu, korektn´ı.
20
Utility cc, c99∗ , gcc†
pˇrekladaˇc C
CC, g++†
pˇrekladaˇc C++
ld
spojovac´ı program (linker)
ldd
pro zjistˇen´ı z´avislost´ı dynamick´eho objektu
cxref
∗
kˇr´ıˇzov´e odkazy ve zdrojov´ ych textech v C
∗
sccs , rcs,cvs
spr´ ava verz´ı zdrojov´eho k´odu
make∗
ˇr´ızen´ı pˇrekladu podle z´avislost´ı
ar
∗
spr´ ava knihoven objektov´ ych modul˚ u
dbx, gdb
†
debuggery
prof, gprof ∗
SUSv3
†
†
profilery
GNU
SUSv3 • standardn´ı pˇr´ıkaz vol´ an´ı kompil´atoru a linkeru C je c99 (podle ISO normy pro C z roku 1999) • cb (C program beautifier) nen´ı • pro spr´ avu verz´ı je sccs • debuggery a profilery nejsou
21
Konvence pro jm´ ena soubor˚ u *.c
jm´ena zdrojov´ ych soubor˚ u program˚ uvC
*.cc
jm´ena zdrojov´ ych soubor˚ u program˚ u v C++
*.h
jm´ena hlaviˇckov´ ych soubor˚ u (header˚ u)
*.o
pˇreloˇzen´e moduly (object files)
a.out
jm´eno spustiteln´eho souboru (v´ ysledek u ´spˇeˇsn´e kompilace)
/usr/include
koˇren stromu syst´emov´ ych header˚ u
/usr/lib/lib*.a
statick´e knihovny objektov´ ych modul˚ u
/usr/lib/lib*.so
um´ıstˇen´ı dynamick´ ych sd´ılen´ ych knihoven objektov´ ych modul˚ u
statick´ e knihovny – pˇri linkov´ an´ı se k´od funkc´ı pouˇzit´ ych z knihovny stane souˇc´ ast´ı v´ ysledn´eho spustiteln´eho programu. Dnes se uˇz moc nepouˇz´ıv´a. sd´ılen´ e knihovny – program obsahuje pouze odkaz na knihovnu, pˇri spuˇstˇen´ı programu se potˇrebn´e knihovny naˇctou do pamˇeti ze soubor˚ u *.so a pˇrilinkuj´ı. • dnes se vˇetˇsinou pouˇz´ıvaj´ı sd´ılen´e knihovny, protoˇze nezab´ıraj´ı tolik diskov´eho prostoru (knihovna je na disku jednou, nen´ı souˇc´ast´ı kaˇzd´eho spustiteln´eho souboru) a snadnˇeji se upgraduj´ı (staˇc´ı instalovat novou verzi knihovny, nen´ı tˇreba pˇrelinkovat programy). Posledn´ı verze Solarisu uˇz napˇr´ıklad v˚ ubec neobsahuje libc.a, d´ıky ˇcemuˇz jiˇz program´ator nem˚ uˇze vytvoˇrit statickou bin´ arku, aniˇz by nemˇel dostateˇcn´e znalosti syst´emu. • nˇekdy se bez statick´ ych knihoven neobejdeme. V nˇekter´ ych situac´ıch nen´ı moˇzn´e pouˇz´ıt knihovny dynamick´e, spustiteln´e soubory jsou takzvan´e standalone binaries a pouˇzit´ı naleznou napˇr´ıklad pˇri bootov´an´ı operaˇcn´ıho syst´emu.
22
Princip pˇ rekladu
util.c msg() { puts(); }
syst´emov´a knihovna
program a.out
main msg ??
main msg
util.o msg puts ??
linker
main() { msg(); }
objektov´e moduly main.o
pˇrekladaˇc
zdrojov´e moduly main.c
msg puts
puts
puts
• u sloˇzitˇejˇs´ıch program˚ u b´ yv´a zvykem rozdˇelit zdrojov´ y text programu do nˇekolika modul˚ u, kter´e obsahuj´ı pˇr´ıbuzn´e funkce a tyto moduly se pak mohou pˇrekl´ adat zvl´ aˇst’ (dokonce kaˇzd´ y modul m˚ uˇze b´ yt v jin´em jazyce a pˇrekl´ad´an jin´ ym pˇrekladaˇcem). V´ yhodou je jednak urychlen´ı pˇrekladu (pˇrekl´adaj´ı se vˇzdy jen moduly zmˇenˇen´e od posledn´ıho pˇrekladu) a jednak flexibilita (nˇekter´e moduly se mohou pouˇz´ıvat v r˚ uzn´ ych programech). Pro ˇr´ızen´ı pˇrekladu se obvykle pouˇz´ıv´ a utilita make. • pˇrekladaˇc jednotliv´e zdrojov´e moduly pˇreloˇz´ı do tvaru tzv. objektov´ych modul˚ u, jeˇz obsahuj´ı k´ od programu (vˇcetnˇe vol´an´ı lok´aln´ıch funkc´ı), ale nam´ısto vol´ an´ı extern´ıch funkc´ı obsahuj´ı jen tabulku jejich jmen. • po f´ azi pˇrekladu nastupuje spojovac´ı program (t´eˇz linker editor nebo loader ), kter´ y zkompletuje v´ ysledn´ y program vˇcetnˇe vyˇreˇsen´ı extern´ıch odkaz˚ u mezi moduly a syst´emov´ ymi knihovnami resp. mezi moduly navz´ajem. • pouˇzit´e statick´e knihovny jsou zkop´ırov´any do spustiteln´eho souboru. Na sd´ılen´e knihovny jsou ve spustiteln´em souboru pouze odkazy a linkuje je runtime linker pˇri kaˇzd´em spuˇstˇen´ı programu. V´ıce viz dynamick´ y linker na stranˇe 36. • pomoc´ı parametr˚ u linkeru lze urˇcit, zda se budou pouˇz´ıvat statick´e nebo dynamick´e knihovny. Zdrojov´ y k´od je v obou pˇr´ıpadech stejn´ y. Existuje i mechanismus (dlopen, dlsym. . . ), pomoc´ı kter´eho se za bˇehu programu vybere sd´ılen´ a knihovna a daj´ı se volat jej´ı funkce. T´ımto zp˚ usobem m˚ uˇzete tak´e zjistit, zda v syst´emu je pˇr´ıtomna pˇr´ısluˇsn´a funkcionalita a pokud ne, zachovat se podle toho. V´ıce na stranˇe 150. 23
Pˇ reklad jednoho modulu (preprocesor)
/usr/include/stdio.h int puts(char *s);
main.i int puts(char *s); preprocesor
main.c #include <stdio.h> #include ”mydef.h” main() { puts(MSG); }
mydef.h #define MSG ”Ahoj” extern int var a;
extern int var a; main() { puts("Ahoj"); }
• preprocesor prov´ ad´ı expanzi maker, ˇcten´ı vloˇzen´ ych (include) soubor˚ u a vynech´ av´ a koment´ aˇre. • v´ ystup preprocesoru lze z´ıskat pomoc´ı cc -E pˇr´ıpadnˇe pˇr´ımo zavol´an´ım cpp, nemus´ı to b´ yt ale vˇzdy tot´eˇz protoˇze nˇekter´e pˇrekladaˇce maj´ı preprocesor integrov´ an v sobˇe. Preprocesor m˚ uˇzete samozˇrejmˇe pouˇz´ıvat i pro jin´e projekty, kter´e s pˇrekladem zdrojov´ ych soubor˚ u v jazyce C nemus´ı m´ıt v˚ ubec nic spoleˇcn´eho. • pouˇzit´ı preprocesoru se m˚ uˇze velmi hodit v situaci, kdy potˇrebujete zas´ahnout do ciz´ıho k´ odu, pln´eho podm´ıneˇcn´ ych vkl´ad´an´ı r˚ uzn´ ych hlaviˇckov´ ych soubor˚ u a r˚ uzn´ ych definic z´ avisl´ ych na dan´ ych podm´ınk´ach. Pˇri hled´an´ı p˚ uvodce chyby v´ am pr´ avˇe m˚ uˇze hodnˇe pomoci samostatn´eho zpracov´an´ı vstupn´ıho souboru pomoc´ı preprocesuru, kde probl´em jiˇz vˇetˇsinou rozpozn´ate snadno. • cpp (nebo cc -E v´ am dok´ aˇze na standardn´ı chybov´ y v´ ystup zobrazit i cel´ y strom vkl´ adan´ ych soubor˚ u, coˇz opˇet pˇri podm´ıneˇcn´ ych pˇrekladech m˚ uˇze b´ yt velmi uˇziteˇcn´ a vˇec. Staˇc´ı pro to pouˇz´ıt volbu -H a pˇresmˇerovat v´ ystup do /dev/null ˇc´ımˇz dostanete pouze hierarchii vkl´adan´ ych hlaviˇckov´ ych soubor˚ u.
24
Pˇ reklad jednoho modulu (kompil´ ator) main.s
main.i
.globl main .type main, @function main: pushhl %ebp movl %esp, %ebp pushl $.LC0 call puts addl $4, %esp .L1: leave ret
extern int var a; main() { puts("Ahoj"); }
kompil´ator
int puts(char *s);
• obr´ azek v´ yˇse je pˇr´ıklad v´ ystupu pro i386 platformu (32-bit, AT&T syntax). • pˇreklad z C do assembleru • v´ ystup t´eto f´ aze pˇrekladu lze z´ıskat pomoc´ı cc -S.
25
Pˇ reklad jednoho modulu (assembler) main.s .globl main .type main, @function main: pushhl %ebp movl %esp, %ebp pushl $.LC0 call puts addl $4, %esp .L1: leave ret
assembler
main.o 457f 0000 0001 0000 00ec 0034
• opˇet pˇr´ıklad v´ ystupu pro i386 platformu (32-bit). • pˇreklad z assembleru do strojov´eho k´odu • objektov´ y modul je v´ ysledkem pˇr´ıkazu cc -c.
26
464c 0000 0003 0000 0000 0000
0101 0000 0001 0000 0000 0000
0001 0000 0000 0000 0000 0028
Kompil´ ator • vol´ an´ı: cc [options ] soubor ... • nejd˚ uleˇzitˇejˇs´ı pˇrep´ınaˇce: -o soubor jm´eno v´ ysledn´eho souboru -c
pouze pˇreklad (nelinkovat)
-E
pouze preprocesor (nepˇrekl´adat)
-l
slinkuj s pˇr´ısluˇsnou knihovnou
-Ljm´ eno
pˇridej adres´aˇr pro hled´an´ı knihoven z -l
-Olevel
nastaven´ı u ´rovnˇe optimalizace
-g
pˇreklad s ladic´ımi informacemi
-Djm´ eno
definuj makro pro preprocesor
-Iadres´ aˇ r
um´ıstˇen´ı #include soubor˚ u
• -l/-L jsou pˇrep´ınaˇce linker editoru, tj. kompil´ator pˇr´ısluˇsn´e informace pˇred´a, ale jsou pouˇz´ıv´ any tak ˇcasto, ˇze jsou vloˇzeny i do tohoto slajdu. • kompil´ ator a linker maj´ı mnoho dalˇs´ıch pˇrep´ınaˇc˚ u ovlivˇ nuj´ıc´ıch generovan´ y k´ od, vypisov´ an´ı varovn´ ych hl´aˇsen´ı nebo variantu jazyka (K&R/ANSI). Je tˇreba nastudovat dokumentaci konkr´etn´ıho produktu.
27
Pˇ reddefinovan´ a makra __FILE__, __LINE__, __DATE__, __TIME__, __cplusplus, apod. jsou standardn´ı makra kompil´atoru C/C++ unix vˇzdy definov´ano v Unixu mips, i386, sparc hardwarov´a architektura linux, sgi, sun, bsd klon operaˇcn´ıho syst´emu _POSIX_SOURCE, _XOPEN_SOURCE pˇreklad podle pˇr´ısluˇsn´e normy pro pˇreklad podle urˇcit´e normy by pˇred prvn´ım #include mˇel b´ yt ˇr´ adek s definic´ı n´ asleduj´ıc´ıho makra. Pak naˇctˇete unistd.h. UNIX 98
#define _XOPEN_SOURCE 500
SUSv3
#define _XOPEN_SOURCE 600
POSIX1990
#define _POSIX_SOURCE
• funguje to tak, ˇze pomoc´ı konkr´etn´ıch maker definujete co chcete (napˇr. POSIX SOURCE) a podle nastaven´ı jin´ ych maker (napˇr. POSIX VERSION) pak zjist´ıte, co jste dostali. Mus´ıte ale vˇzdy po nastaven´ı maker nainkludovat unistd.h a pouˇz´ıt spr´ avn´ y pˇrekladaˇc. Napˇr´ıklad se pokus´ıme pˇreloˇzit program basic-utils/standards.c , kter´ y vyˇzaduje SUSv3, na syst´emu podporuj´ıc´ım SUSv3 (Solaris 10), ale pˇrekladaˇcem, kter´ y podporuje pouze SUSv2 (SUSv3 pˇrekladaˇc je c99). Pozor, ˇze defaultn´ı chov´an´ı vaˇseho pˇrekladaˇce m˚ uˇze b´ yt klidnˇe pr´ avˇe to z c89. $ cat standards.c #define _XOPEN_SOURCE 600 /* you must #include at least one header !!! */ #include <stdio.h> int main(void) { return (0); } $ c89 basic-utils/standards.c "/usr/include/sys/feature_tests.h", line 336: #error: "Compiler or options invalid; UNIX 03 and POSIX.1-2001 applications require the use of c99" cc: acomp failed for standards.c • zdroj maker pro standard tedy m˚ uˇze b´ yt jiˇz na stranˇe 15 zmiˇ novan´ y hlaviˇckov´ y soubor feature tests.h na Solarisu. Bud’ ho najdete pˇr´ımo na nˇekter´em
28
z tˇechto syst´em˚ u nebo pˇres code browser OpenGrok na http://src.opensolaris. org/. • v dokumentaci konkr´etn´ıho kompil´atoru je moˇzn´e naj´ıt, kter´a dalˇs´ı makra se pouˇz´ıvaj´ı. Mnoˇzstv´ı maker je definov´ano tak´e v syst´emov´ ych hlaviˇckov´ ych souborech. • POSIX.1 v sobˇe zahrnuje ANSI C; tedy C89, ne C99 (o C standardech v´ıce na stranˇe 17). • co se t´ yˇce maker k jednotliv´ ym standard˚ um, velmi dobr´a je kapitola 1.5 v [Rochkind]. Tak´e doporuˇcuji C program basic-tools/suvreq.c k t´eto kapitole, kter´ y je moˇzn´e naj´ıt i na m´ ych str´ank´ach v sekci uk´azkov´ ych pˇr´ıklad˚ u. • mal´ y pˇr´ıklad na podm´ınˇen´ y pˇreklad: int main(void) { #ifdef unix printf("yeah\n"); #else printf("grr\n"); #endif return (0); } • pˇr´ıklad na pouˇzit´ı LINE
viz basic-tools/main LINE .c
Link editor (linker) • Vol´ an´ı: ld [options ] soubor ... cc [options ] soubor ... • Nejd˚ uleˇzitˇejˇs´ı pˇrep´ınaˇce: -o soubor
jm´eno v´ ysledn´eho souboru (default a.out)
-llib
linkuj s knihovnou liblib.so nebo liblib.a
-Lpath
cesta pro knihovny (-llib )
-shared
vytvoˇrit sd´ılenou knihovnu
-non shared
vytvoˇrit statick´ y program
29
• linker je program, kter´ y vezme typicky v´ıce objekt˚ u vygenerovan´ ych pˇrekladaˇcem a vytvoˇr´ı z nich bin´ arn´ı program, knihovnu nebo dalˇs´ı objekt vhodn´ y pro dalˇs´ı f´ azi linkov´ an´ı. • pozor na to, ˇze na r˚ uzn´ ych syst´emech se nˇekter´e pˇrep´ınaˇce mohou liˇsit, napˇr´ıklad ld na Solarisu nezn´a pˇrep´ınaˇce -shared a -non shared, je nutn´e pouˇz´ıt jin´e. • existuje pˇrep´ınaˇc -R, kter´ y umoˇzn ˇuje specifikovat cestu pro hled´an´ı knihoven za bˇehu (runtime). Tato cesta se m˚ uˇze liˇsit od cesty specifikovan´e pˇrep´ınaˇcem -L. • u mal´ ych program˚ u (v jednom souboru) lze prov´est pˇreklad a linkov´an´ı jedn´ım pˇr´ıkazem cc. U vˇetˇs´ıch program˚ u skl´adaj´ıc´ıch se z mnoha zdrojov´ ych soubor˚ u a knihoven se obvykle oddˇeluje pˇreklad a linkov´an´ı a cel´ y proces je ˇr´ızen utilitou make (strana 31).
ˇ ızen´ı pˇ R´ rekladu a linkov´ an´ı (make) • zdrojov´ e texty main.c #include "util.h" main() { msg(); }
util.h void msg();
util.c #include "util.h" msg() { puts(); }
• z´ avislosti
• soubor Makefile prog : main.o util.o main.c & cc -o prog main.o util.o main.o main.o : main.c util.h % & util.h prog cc -c main.c & % util.o : util.c util.h util.o % cc -c util.c util.c
. • program je moˇzn´e tak´e pˇreloˇzit a slinkovat jedn´ım vol´an´ım kompil´atoru, nebo definovat postup pˇrekladu a linkov´an´ı pomoc´ı shellov´eho skriptu. D˚ uvodem pro pouˇzit´ı make je to, ˇze vyhodnocuje z´avislosti mezi soubory a po zmˇenˇe ˇ y nˇekter´eho zdrojov´eho souboru pˇrekl´ad´a jenom to, co na nˇem z´avis´ı. Cast´ zp˚ usob pˇrekladu softwaru po aplikov´an´ı zmˇen zp˚ usobem “make clean; make all” je v situaci, kdy cel´ y pˇreklad trv´a minuty (des´ıtky minut, hodiny. . . ), trochu nevhodn´ y – pr´ avˇe proto je d˚ uleˇzit´e m´ıt dobˇre napsan´ y Makefile. • ˇr´ adek “prog : main.o util.o” definuje, ˇze se m´a nejprve rekurzivnˇe zajistit existence a aktu´ alnost soubor˚ u main.o a util.o. Pak se zkontroluje, zda 30
soubor (c´ıl) prog existuje a je aktu´aln´ı (datum posledn´ı modifikace souboru je mladˇs´ı neˇz main.o a util.o). Pokud ano, nedˇel´a se nic. Kdyˇz ne, provede se pˇr´ıkaz na n´ asleduj´ıc´ım ˇr´ adku. • make se spouˇst´ı typicky s parametrem urˇcuj´ıc´ı pˇr´ıˇsluˇsn´ y c´ıl (target); pˇri spuˇstˇen´ı bez parametr˚ u se vezme prvn´ı target. To b´ yv´a all, coˇz vˇetˇsinou podle unixov´e konvence pˇreloˇz´ı vˇse, co se pˇreloˇzit m´a. N´asleduje pak tˇreba spuˇstˇen´ı make s parametrem install apod. • make je samozˇrejmˇe univerz´aln´ı n´astroj, pouˇziteln´ y i jinde neˇz u pˇreklad˚ u zdrojov´ ych k´ od˚ u • pˇr´ıklad: basic-utils/Makefile01 . Pozor na to, ˇze pro nestandardn´ı jm´eno vstupn´ıho souboru je nutn´e pouˇz´ıt pˇrep´ınaˇc -f: “make -f Makefile01”.
Syntaxe vstupn´ıho souboru (make) • popis z´ avislost´ı c´ıle:
targets : [files ]
• prov´ adˇen´e pˇr´ıkazy:
command
• koment´ aˇr:
#comment
• pokraˇcovac´ı ˇr´ adek:
line-begin \ line-continuation
• Pozor na to, ˇ ze ˇ r´ adek s pˇ r´ıkazem zaˇ c´ın´ a tabul´ atorem, nikoliv mezerami. Kaˇzd´ y pˇr´ıkazov´ y ˇr´ adek se prov´ad´ı samostatn´ ym shellem, pokud je potˇreba prov´est v´ıce ˇr´ adk˚ u pomoc´ı jednoho shellu, mus´ı se vˇsechny aˇz na posledn´ı ukonˇcit backslashem (shell je dostane jako jeden ˇr´adek). Viz pˇr´ıklad, ve kter´em dva posledn´ı echo pˇr´ıkazy jsou souˇc´ast´ı jednoho if pˇr´ıkazu, kter´ y je spuˇstˇen samostatn´ ym shellem. D´ale si vˇsimnˇete, ˇze pokud m´ate pˇr´ıkaz na v´ıce ˇr´ adk˚ u, mus´ıte kaˇzd´ y ˇr´ adek ukonˇcit zpˇetn´ ym lom´ıtkem. $ cat basic-utils/Makefile02 all: @echo $$$$
31
@echo $$$$ @if true; then \ echo $$$$; \ echo $$$$; \ fi $ make -f Makefile02 5513 5514 5515 5515 • co se t´ yk´ a pouˇzit´ı zpˇetn´eho lom´ıtka, tak to funguje jako oddˇelovaˇc slov, a make m´ısto nˇej vloˇz´ı mezeru. Pˇr´ıklad: basic-utils/Makefile07 . • zdvojen´ım $ se potlaˇc´ı speci´ aln´ı v´ yznam dolaru (viz n´asleduj´ıc´ı slajd) • znak @ na zaˇc´ atku ˇr´ adku potlaˇc´ı jeho v´ ypis – make jinak standardnˇe vypisuje nejdˇr´ıve to, co bude vykon´ avat. • vyps´ an´ı vˇsech pˇrikaz˚ u kter´e bude make prov´adˇet bez toho aby je skuteˇcnˇe provedl lze dos´ ahnout pouˇzit´ım pˇrep´ınaˇce -n. • znak - na zaˇc´ atku ˇr´ adku zp˚ usob´ı ignorov´an´ı nenulov´e n´avratov´e hodnoty; jinak make vˇzdy v takov´e situaci zahl´as´ı chyby a okamˇzitˇe skonˇc´ı. Pˇr´ıklad: basic-utils/Makefile04 . • test1: false echo "OK" test2: -false echo "OK"
32
Makra (make) • definice makra: name = string • pokraˇcov´ an´ı vkl´ ad´ a mezeru • nedefinovan´ a makra jsou pr´azdn´a • nez´ aleˇz´ı na poˇrad´ı definic r˚ uzn´ ych maker • definice na pˇr´ıkazov´e ˇr´ adce: make target name =string • vyvol´ an´ı makra: $name (pouze jednoznakov´e name ), ${name } nebo $(name ) • syst´emov´e promˇenn´e jsou pˇr´ıstupn´e jako makra
• kdyˇz je stejn´e makro definov´ano v´ıcekr´at, plat´ı jeho posledn´ı definice, viz pˇr´ıklad basic-utils/Makefile03 . • makra nen´ı moˇzn´e definovat rekurzivnˇe, viz basic-utils/Makefile05 : $ cat basic-utils/Makefile05 M=value1 M=$(M) value2 all: echo $(M) $ make -f Makefile05 Variable M is recursive. • ˇcasto se pouˇz´ıvaj´ı r˚ uzn´e rozˇs´ıˇren´e verze make (napˇr. GNU, BSD), kter´e um´ı, podm´ınˇen´e sekce v Makefile, redefinice promˇenn´ ych, apod. • napsat Makefile kter´ y bude fungovat najednou pro r˚ uzn´e verze make nemus´ı b´ yt jednoduch´e, proto existuj´ı projekty jako je napˇr. GNU automake. Pro jednoduch´ y podm´ıneˇcn´ y pˇreklad v z´avislosti na syst´emu a kde se daj´ı oˇcek´avat r˚ uzn´e verze pˇr´ıkazu make, je moˇzn´e pouˇz´ıt napˇr´ıklad n´asleduj´ıc´ı k´od, kter´ y mi fungoval na vˇsech v nˇem zm´ınˇen´ ych syst´emech (znak ‘ je zpˇetn´ y apostrof, a ’ je norm´ aln´ı apostrof): CFLAGS=‘x=\‘uname\‘; \ if [ $${x} = FreeBSD ]; then \
33
echo elif [ echo elif [ echo fi‘
’-Wall’; \ $${x} = SunOS ]; then \ ’-v’; \ $${x} = Linux ]; then \ ’-Wall -g’; \
all: @echo "$(CFLAGS)" • v ostatn´ıch situac´ıch je vhodn´e, pˇr´ıpadnˇe nezbytn´e pouˇz´ıt programy typu autoconf nebo automake. Nˇekter´e make implementace maj´ı pˇr´ımo direktivy pro podm´ınˇen´e zpracov´ an´ı vstupn´ıho souboru, napˇr´ıklad make na BSD. Pˇr´ıklad: basic-utils/Makefile08.bsd . • pˇrep´ınaˇcem -e m˚ uˇzeme make donutit, aby ignoroval nastaven´ı promˇenn´ ych ve vstupn´ım souboru v pˇr´ıpadˇe, ˇze je definov´ana promˇenn´a prostˇred´ı stejn´eho jm´ena. Norm´ alnˇe make pˇreb´ır´a aktu´aln´ı nastaven´ı promˇenn´ ych prostˇred´ı jen v pˇr´ıpadˇe, ˇze dan´e promˇenn´e nejsou nastaven´e ve vstupn´ım souboru. Pˇr´ıklad: basic-utils/Makefile06 . • make je velmi siln´ y n´ astroj, staˇc´ı se pod´ıvat do syst´emov´ ych Makefile soubor˚ u jak´ehokoli unix-like syst´emu (najdˇete si na Wikipedii, co to znamen´a “unix-like”, pokud to nev´ıte). Typickou spoleˇcnou vlastnost´ı je to, ˇze neexistuje dokumentace, jak je dan´ y makefile framework postaven.
Dynamick´ y linker (loader) • pˇreklad vyˇzaduje vˇsechny potˇrebn´e dynamick´e knihovny pro kontrolu dosaˇzitelnosti pouˇzit´ ych symbol˚ u • sestaven´ı kompletn´ıho programu se ale provede aˇ z pˇ ri spuˇ stˇ en´ı. To je u ´kol pro dynamick´ y linker (run-time linker, loader ) • seznam dynamick´ ych knihoven zjist´ı ze sekce .dynamic • syst´em m´ a nastaveno nˇekolik cest, kde se automaticky tyto knihovny hledaj´ı • v sekci .dynamic je moˇzn´e dalˇs´ı cesty ke knihovn´am pˇridat pomoc´ı tag˚ u RUNPATH/RPATH • nalezen´e knihovny se pˇripoj´ı do pamˇet’ov´eho procesu pomoc´ı vol´ an´ı mmap() (bude pozdˇeji)
34
• proces spuˇstˇen´ı dynamicky slinkovan´eho programu prob´ıh´a takto: – kernel ve vol´ an´ı exec namapuje program do pamˇeti a zjist´ı, jak´ y dynamick´ y linker se m´ a pouˇz´ıt (viz d´ale) – kernel namapuje linker do pamˇet’ov´eho prostoru spouˇstˇen´eho programu a pak linkeru pˇred´ a kontrolu. Linker je program s´am o sobˇe – na Solarisu je ho moˇzn´e norm´ alnˇe spustit (m´a tedy funkci main) a jako parametr mu d´ at jm´eno programu. Moˇznost spouˇstˇet dynamick´ y linker z pˇr´ıkazov´eho ˇr´ adku je ale hlavnˇe pro experimentovan´ı s linkerem pˇri jeho v´ yvoji. – linker z hlaviˇcky programu zjist´ı, jak´e dynamick´e knihovny program pouˇz´ıv´ a, namapuje je do pamˇeti a zavol´a jejich inicializaˇcn´ı funkce, pokud existuj´ı. Mapuj´ı se vˇsechny nalezen´e z´avislosti kter´e nejsou nastaven´e jako lazy (strana 150), rekurz´ıvnˇe prohled´av´an´ım do ˇs´ıˇrky. V tomto poˇrad´ı se pak tak´e objekty prohled´avaj´ı pˇri hled´an´ı jednotliv´ ych symbol˚ u. – linker programu pˇred´ a ˇr´ızen´ı (tj. zavol´a funkci main) – proces m˚ uˇze i za bˇehu d´ale vyuˇz´ıvat dynamick´ y linker pomoc´ı vol´an´ı dlopen a spol.; k tomu se dostaneme na stranˇe 150 • je potˇreba si uvˇedomit, ˇze dynamick´ y linker zde nepracuje jako samostatn´ y proces pˇrestoˇze m´ a svoji vlastn´ı main funkci, jeho k´od se pouˇz´ıv´a v r´amci pamˇet’ov´eho prostoru procesu; program, linker a knihovny dohromady tvoˇ r´ı jeden proces. • n´ asleduj´ıc´ı pˇ r´ıkazy a pˇ r´ıklady se t´ ykaj´ı Solarisu. Pokud to nebude fungovat na jin´ ych syst´emech, tak maj´ı ekvivaletn´ı n´astroje s podobnou funkcionalitou. – seznam sekc´ı se zjist´ı pomoc´ı elfdump -c (GNU m´a pˇr´ıkazy objdump a elfread). O programov´ ych sekc´ıch bude v´ıce na stranˇe 139. – to, jak´ y dynamick´ y linker se pouˇzije, kernel zjist´ı ze sekce .interp, viz ”elfdump -i” a “ld -I”. To znamen´a, ˇze si m˚ uˇzete napsat vlastn´ı linker a pomoc´ı -I pro ld ho pak nastavit jako dynamick´ y linker pro v´ aˇs program. – dynamick´ a sekce se vyp´ıˇse pomoc´ı elfdump -d, dynamick´e knihovny jsou oznaˇcen´e tagem NEEDED – z´ avislosti na dynamick´ ych knihovn´ach je moˇzn´e pohodlnˇe zjistit pomoc´ı pˇr´ıkazu ldd (Solaris, Linux, BSD), kter´ y zjist´ı konkr´etn´ı cesty ke knihovn´ am. Tento pˇr´ıkaz ˇreˇs´ı z´avislosti rekurz´ıvnˇe a uvid´ıte tedy i nepˇr´ım´e z´ avislosti - tj. takov´e knihovny, kter´e jsou pouˇzit´e knihovnami, kter´e pˇr´ısluˇsn´ y program pouˇz´ıv´a pˇr´ımo. Zjistit co je pˇresnˇe z´avisl´e na ˇcem je moˇzn´e pomoc´ı volby -v. Na OS X je ekvivalentem ldd pˇr´ıkaz otool -L. – jak´e knihovny byly pˇri spuˇstˇen´ı nakonec pouˇzity m˚ uˇze b´ yt jin´e neˇz co uk´ aˇze pˇr´ıkaz ldd, a to tˇreba d´ıky mechanismu LD PRELOAD. Na Solarisu proto existuje pˇr´ıkaz pldd, kter´ y pomoc´ı ˇc´ısla procesu uk´aˇze z´avislosti konkr´etn´ıho procesu. Pˇr´ıklad na LD PRELOAD: pouˇzijte jiˇz zm´ınˇen´ y Makefile01, a pˇreloˇzte basic-utils/preload.c takto: “cc -shared -o libpreload.so preload.c”. Pak spust’te program, kter´ y zachyt´ı syst´emov´e vol´ an´ı close: “LD_PRELOAD=./libpreload.so ./a.out”.
35
– vˇetˇsinu zde uveden´ ych informac´ı naleznete v manu´alov´e str´ance pro dynamick´ y linker v Solarisu, ld.so.1(1), a v´ıce pak v Linker and Libraries Guide na docs.sun.com. Na FreeBSD se dynamick´ y linker naz´ yv´a ld-elf.so.1, v linuxov´ ych distribuc´ıch vˇetˇsinou ld-linux.so.1, na IRIXu rld atd. – dynamick´ y linker se typicky d´a konfigurovat pomoc´ı nastaven´ı promˇenn´ ych, napˇr´ıklad si zkuste na Solarisu spustit toto: LD_LIBRARY_PATH=/tmp LD_DEBUG=libs,detail date a pro zjiˇstˇen´ı vˇsech moˇznost´ı jak debugovat dynamick´ y linker pouˇzijte: LD_DEBUG=help date – pˇr´ıklad na to, kdy je potˇreba, aby linker hledal knihovny i jinde neˇz v defaultn´ıch adres´ aˇr´ıch (adres´aˇre specifikovan´e promˇennou LD LIBRARY PATH se prohled´ avaj´ı jako prvn´ı): $ cp /lib/libc.so.1 /tmp $ LD_LIBRARY_PATH=/tmp sleep 100 & [1] 104547 $ pldd 104547 104547: sleep 100 /tmp/libc.so.1 /usr/lib/locale/cs_CZ.ISO8859-2/cs_CZ.ISO8859-2.so.3 – Solaris m´ a velmi zaj´ımav´ y pˇr´ıkaz elfedit(1), pomoc´ı kter´eho m˚ uˇzete editovat metadata ELF objektu, napˇr´ıklad pˇrepsat jm´eno z´avisl´e knihovny, zmˇenit nastaven´ı RUNPATH atd. • Obecnˇe plat´ı, ˇze pouˇz´ıvat LD LIBRARY PATH k ovlivnˇen´ı bˇehu dynamick´eho linkeru pro nˇeco jin´eho neˇz ladˇen´ı dynamick´ ych knihoven pˇri v´ yvoji nebo pˇresunech knihoven mezi adres´aˇri nen´ı dobr´ y n´apad. Na internetu lze naj´ıt mnoˇzstv´ı ˇcl´ ank˚ u typu ”why is LD LIBRARY PATH evil ?”apod., napˇr. http: //xahlee.org/UnixResource dir/ /ldpath.html. Tato promˇenn´ a je typicky zneuˇz´ıvan´a ve startovac´ıch skriptech program˚ u, aby pˇredsunuly alternativn´ı seznam adres´aˇr˚ u kde hledat dynamick´e knihovny na kter´ ych program z´ avis´ı. To vˇetˇsinou proto, ˇze byl program nespr´avnˇe slinkov´ an a dynamick´ y linker by podle informac´ı v ELFu nedok´azal jinak knihovny naj´ıt. Typick´ y nechtˇen´ y side effect je ˇze program spust´ı jin´ y program kter´ y pouˇz´ıv´ a knihovnu stejn´eho jm´ena, ale ”d´ıky”tomu, ˇze se promˇenn´e prostˇred´ı dˇed´ı, tak dynamick´ y linker najde tuto knihovnu jako prvn´ı v ares´aˇri specifikovan´em pomoc´ı LD LIBRARY PATH. Tato knihovna m˚ uˇze b´ yt ale jin´e verze, a pak snadno m˚ uˇze doj´ıt k nˇeˇcemu co se vˇetˇsinou naz´ yv´a ”nedefinovan´e chov´ an´ı”(viz pˇr´ıklad u dalˇs´ıho slide o ABI).
36
API versus ABI API – Application Programming Interface • rozhran´ı pouˇzit´e pouze ve zdrojov´em k´odu • rozhran´ı zdroj´ aku v˚ uˇci syst´emu, knihovnˇe ˇci vlastn´ımu k´odu, tj. napˇr. exit(1), printf("hello\n") nebo my function(1, 2) • . . . aby se stejn´ y zdrojov´ y k´ od mohl pˇreloˇzit na vˇsech syst´emech podporuj´ıc´ı dan´e API ABI – Application Binary Interface • low-level rozhran´ı aplikace v˚ uˇci syst´emu, knihovnˇe ˇci jin´e ˇc´asti sama sebe • . . . aby se objektov´ y modul mohl pouˇz´ıt vˇsude tam, kde je podporov´ ano stejn´e ABI
• Pˇr´ıkladem API je tˇreba API definovan´e normou POSIX.1. • ABI definuje konvenci vol´ an´ı (to jak program pˇred´a parametry funkci a jak od n´ı pˇrevezme n´ avratovou hodnotu), jak´a jsou ˇc´ısla syst´emov´ ych vol´an´ı, jak se syst´emov´e vol´ an´ı provede ˇci form´at objektov´eho modulu a pˇrij´ıman´ ych argument˚ u, viz pˇr´ıklad dole. • API knihovny pak definuje mimo jin´e mnoˇzinu vol´an´ı kter´a jsou knihovnou definov´ ana, jejich parametry a typy tˇechto parametr˚ u. • n´ asledn´ a uk´ azka je pˇr´ıklad na to, kdy v´ yvoj´aˇr zmˇen´ı velikost argument˚ u v bajtech (tj. zmˇen´ı ABI knihovny), a nahrad´ı novou verz´ı tu starou. Vˇsimnˇete si, ˇze dynamick´ y linker toto nezjist´ı; nem´a totiˇz jak, ˇr´ıd´ı se podle jm´ena knihovny v dynamick´e sekci programu, a to se nezmˇenilo. Uveden´a zmˇena je sice i zmˇena v API a probl´em by se odstranil, kdybychom main.c znovu pˇreloˇzili se zmˇenˇen´ ym ˇr´ adkem deklarace funkce add. To je ale ˇcasto probl´em (pˇrekl´ adejte cel´ y syst´em jen kv˚ uli tomu), proto je tak d˚ uleˇzit´e dodrˇzovat zpˇetnou kompatibilitu v ABI u knihoven. V´ ysledek n´ asleduj´ıc´ıho pˇrekladu knihovny, programu a jeho spuˇstˇen´ı je jak bychom oˇcek´ avali (pouˇzit cc ze SunStudio, pro gcc pouˇzijte m´ısto -G volbu -shared; novˇejˇs´ı gcc nav´ıc neznaj´ı -R a je m´ısto toho nutn´e pouˇz´ıt -Xlinker -R .: $ cat main.c int my_add(int a, int b);
37
int main(void) { printf("%d\n", my_add(1, 2)); return (0); } $ cat add.c int my_add(int a, int b) { return (a + b); } $ cc -G -o libadd.so add.c $ cc -L. -ladd -R. main.c $ ./a.out 3 Nyn´ı ale pˇriˇsla dalˇs´ı verze knihovny se stejn´ ym jm´enem, a ve funkci my add nastala zmˇena v typu argument˚ u, kde m´ısto 4-bajtov´eho integeru se pouˇzije 64-bitov´ y celoˇc´ıseln´ y typ. Program ale o niˇcem nev´ı, nech´a se spustit a vr´at´ı chybnou hodnotu: $ cat add2.c int64_t my_add(int64_t a, int64_t b) { return (a + b); } $ cc -G -o libadd.so add2.c $ ./a.out -1077941135 • pˇr´ıklad: lib-abi/abi-main.c (koment´aˇr v souboru napov´ı jak pouˇz´ıt ostatn´ı soubory ve stejn´em adres´aˇri) • zde pak pˇrich´ az´ı ke slovu verzov´an´ı knihoven, tj. je nutn´e “nˇeco” zmˇenit tak, aby po instalaci nov´e knihovny neˇslo program spustit bez jeho rekompilace. • bin´ arn´ı nekompatibilita je napˇr´ıklad probl´em u OpenSSL. Vˇetve 0.9.x a 0.9.y nejsou ABI kompatibiln´ı. Konkr´etnˇe verze 0.9.7 a 0.9.8, v roce 2009 st´ale obˇe pouˇz´ıvan´e. Verze rozliˇsen´e p´ısmeny, tj. napˇr´ıklad 0.9.8a a 0.9.8g, jsou ABI kompatibiln´ı. Nˇekter´e syst´emy st´ale pouˇz´ıvaj´ı pouze 0.9.7 (FreeBSD 6.x, Solaris 10), jin´e jen 0.9.8 (Solaris 11 Express), dalˇs´ı integruj´ı obˇe vˇetve (r˚ uzn´e Linuxov´e distribuce). Probl´em je, m´ate-li napˇr´ıklad program pro Solaris 10 pouˇz´ıvaj´ıc´ı libcrypto.so knihovnu, kter´ y chcete pouˇz´ıvat i na Solaris 11 Express (to je jinak d´ıky zpˇetn´e bin´arn´ı kompatibilitˇe striktnˇe dodrˇzovan´e mezi ”major”verzemi Solarisu moˇzn´e - napˇr. program kter´ y bˇeˇzel na Solarisu 2.6 z roku 1997 m˚ uˇze bˇeˇzet na Solarisu 10 z roku 2009 bez nutnosti rekompilace - to se t´ yk´ a syst´emu a knihoven s n´ım dod´avan´ ych). Jedin´e spr´avn´e reˇsen´ı je zkompilovat pro nov´ y syst´em, pˇr´ıpadnˇe manu´alnˇe zkop´ırovat potˇrebn´e verze
38
knihoven, coˇz ale zdaleka nen´ı ide´aln´ı – program nebude fungovat s novˇe nainstalovan´ ym syst´emem, ale nikdo najednou nev´ı, proˇc to funguje na stejn´em syst´emu vedle, a kdyˇz se to zjist´ı tak je opˇet potˇreba manu´aln´ı z´asah, a pochybuji o tom, ˇze autor “ˇreˇsen´ı” bude instalovat opraven´e verze pˇri v´ yskytu bezpeˇcnostn´ıch chyb. Nekompatibilita 0.9.x verz´ı je d˚ uvodem, proˇc je v dynamick´e sekci knihovny i jej´ı cel´e ˇc´ıslo (bez p´ısmen, ta jak jiˇz v´ıme nejsou pro ABI kompatibilitu u OpenSSL d˚ uleˇzit´a), a d´ıky tomu je pak toto ˇc´ıslo uvedeno i v kaˇzd´em programu proti knihovnˇe slinkovan´emu: $ elfdump -d /usr/sfw/lib/libcrypto.so.0.9.8 | grep SONAME [7] SONAME 0x1 libcrypto.so.0.9.8 $ elfdump -d /usr/bin/ssh [1] NEEDED [3] NEEDED [5] NEEDED [7] NEEDED [9] NEEDED [10] NEEDED
| grep NEEDED 0x3c99 0x3cb1 0x3cc6 0x3d12 0x3cd9 0x3cfe
libsocket.so.1 libnsl.so.1 libz.so.1 libcrypto.so.0.9.8 libgss.so.1 libc.so.1
– pˇr´ıˇcinou zpˇetn´e nekompatibility OpenSSL verz´ı je to, ˇze z historick´ ych d˚ uvod˚ u jsou nˇekter´e pouˇz´ıvan´e struktury v hlaviˇckov´ ych souborech. Tyto struktury je ale nˇekdy nutn´e rozˇs´ıˇrit, napˇr´ıklad pˇri v´ yvoji nov´e funkcionality. T´ım nastane situace, ˇze program pˇreloˇzen´ y s verz´ı 0.9.7 by pˇredal novˇejˇs´ı knihovnˇe “menˇs´ı” strukturu, respektive nov´a knihovna by pˇristupovala ve star´e struktuˇre na poloˇzky, kter´e neexistuj´ı – a tedy by pˇristupovala k pamˇeti, kter´a programu nebyla pˇridˇelena. To m˚ uˇze zp˚ usobit p´ ad programu (pˇr´ıstup na nenamapovanou str´anku), m˚ uˇze to fungovat d´ ale (v dan´e pamˇeti je to, co se tam typicky oˇcek´av´a, napˇr´ıklad nula), nebo se to zaˇcne chovat “podivnˇe” (v pamˇeti bylo nˇeco, co v dan´e situaci oˇcek´ avan´e nebylo). Probl´em v OpenSSL je, ˇze nyn´ı jiˇz nen´ı technicky jednoduch´e z tˇechto struktur udˇelat intern´ı a navenek pracovat jen s transparentn´ımi referencemi objektov´ ym pˇr´ıstupem, coˇz by umoˇznilo dˇelat libovoln´e zmˇeny ve struktur´ach, aniˇz by to program ovlivnilo. – bˇeˇznˇe vidˇen´e ˇreˇsen´ı zp˚ usoben´e neznalost´ı vˇeci je vytvoˇrit symbolick´ y link, napˇr´ıklad na Solarisu 11 udˇelat 0.9.7 symlink na existuj´ıc´ı knihovnu ˇ y v´ verze 0.9.8. Cast´ ysledek je pak p´ad programu a u ´div autora symlinku. Nˇekdy to naopak funguje, protoˇze program n´ahodou nepouˇz´ıv´a pˇr´ısluˇsn´e struktury, a to je jasn´ ym d˚ ukazem pro akt´era, ˇze ˇreˇsen´ı mus´ı b´ yt spr´avn´e. M˚ uˇze se ale st´ at, ˇze program struktury nepouˇz´ıv´a pˇri konkr´etn´ım proveden´em testu, ale zaˇcne dˇelat probl´emy aˇz pˇri ˇziv´em nasazen´ı. Tady je jedin´ a rada – pokud si opravdu nejste jisti ˇze v´ıte, co dˇel´ate a nejste si jist´ı svoji detailn´ı znalost´ı k´odu programu i knihoven, vyhnˇete se tomu. Nebo riskujte, ale jiˇz v´ıte jak to m˚ uˇze skonˇcit. – zd´ anlivˇe jednoduch´e ˇreˇsen´ı dod´avat v´ıce verz´ı OpenSSL s jedn´ım syst´emem pˇrin´ aˇs´ı zase jin´e probl´emy – obt´ıˇznˇejˇs´ı v´ yvoj syst´emu, obt´ıˇznˇejˇs´ı spr´ avu syst´emu (pˇri v´ yskytu bezpeˇcnostn´ı chyby je ˇcasto nutn´e patchovat vˇsechny instalovan´e verze), probl´emy s nepˇr´ım´ ymi z´avislostmi obsahuj´ıc´ı v´ıce verz´ı dan´e knihovny apod. – upgrade verze OpenSSL v existuj´ıc´ım syst´emu je tak´e vˇec, kter´e je dobr´e se vyhnout, respektive toto tˇeˇzko vyˇreˇs´ıte vyd´an´ım patche pro existuj´ıc´ı syst´emy – uvaˇzte ˇze z´ akazn´ık pouˇz´ıv´a sv´e nebo j´ım koupen´e programy,
39
kter´e z´ avis´ı na existuj´ıc´ı verzi. A tu byste mu najednou upgradovali na verzi vyˇsˇs´ı, ABI nekompatibiln´ı. – typick´ ym pˇr´ıkladem, kdy se pouˇz´ıv´a transparentn´ı typ jako reference, coˇz umoˇzn ˇuje dalˇs´ı rozˇsiˇrov´an´ı pod n´ı leˇz´ıc´ı struktury bez rizika v´ yˇse uveden´ ych probl´em˚ u, je typ POSIX vl´aken. Struktura typu pthread t (strana 194) je intern´ı z´aleˇzitost´ı knihovny. Typicky je to integer, ale to by program´ atora nemˇelo v˚ ubec zaj´ımat. Samozˇrejmˇe souborov´ y deskriptor ˇci ˇc´ıslo procesu jsou podobn´e pˇr´ıpady, ale na pˇr´ıkladu vl´aken je to l´epe vidˇet.
Debugger dbx • Vol´ an´ı: dbx [ options ] [ program [ core ] ] • Nejbˇeˇznˇejˇs´ı pˇr´ıkazy: run [arglist ] where print expr set var = expr cont next, step stop condition trace condition command n help [name ] quit
start programu vypiˇs z´asobn´ık vypiˇs v´ yraz zmˇen ˇ hodnotu promˇenn´e pokraˇcov´an´ı bˇehu programu proved’ ˇr´adku (bez/s vnoˇren´ım do funkce) nastaven´ı breakpointu nastaven´ı tracepointu akce na breakpointu (pˇr´ıkazy n´asleduj´ı) n´apovˇeda ukonˇcen´ı debuggeru
• z´ akladn´ı ˇr´ adkov´ y symbolick´ y debugger, aby bylo moˇzn´e ho plnˇe vyuˇz´ıt, mus´ı b´ yt program pˇreloˇzen s ladic´ımi informacemi (cc -g). Ladˇen´ y program se startuje z debuggeru pˇr´ıkazem run, nebo se debugger pˇripoj´ı k jiˇz bˇeˇz´ıc´ımu procesu. Pomoc´ı dbx lze analyzovat i havarovan´ y program, kter´ y vygeneroval soubor core. • je moˇzn´e ho naj´ıt napˇr. na Solarisu, avˇsak na Linuxu a FreeBSD defaultnˇe nen´ı. • pro debugging se zdrojov´ ymi k´ody nestaˇc´ı pouˇz´ıt volbu -g, je z´aroveˇ n nutn´e m´ıt i zdroj´ aky a objektov´e moduly tam, kde byly pˇri pˇrekladu. To je typicky bˇeˇzn´ a situace, protoˇze lad´ıte na stroji, kde z´aroveˇ n i vyv´ıj´ıte. Pokud tomu tak nen´ı, je nutn´e si zdroj´ aky a objektov´e moduly zajistit, pokud k nim vede jin´ a cesta, lze pouˇz´ıt dbx pˇr´ıkaz pathmap. • gdb-kompatibiln´ı m´ od se spust´ı pˇres gdb on. Pokud v´as zaj´ım´a, jak´ y m´a
40
dbx ekvivalentn´ı pˇr´ıkaz ke konkr´etn´ımu gdb pˇr´ıkazu, pom˚ uˇze v´am help FAQ; hned prvn´ı ot´ azka je “A.1 Gdb does <something>; how do I do it in dbx?” • pokud nepouˇzijete pˇrep´ınaˇc -g, bude v´am dbx na Solarisu st´ale platn´ y, protoˇze zobraz´ı argumenty funkc´ı. U BSD syst´em˚ u a linuxov´ ych distribuc´ı -g pouˇz´ıt mus´ıte, jinak v´ am debuggery moc nepomohou. Kdy je to na Solarisu platn´e i bez -g je v vidˇet v pˇr´ıkladu debug/dbx.c . Pˇri kompilaci s gcc a pouˇzit´ı gdb neuk´ aˇze pˇr´ıkaz where parametry funkce crash() zat´ımco se Solaris Studio kompilerem a debuggerem dbx se parametry funkce vyp´ıˇs´ı, takˇze je vidˇet hodnota kter´ a se pˇriˇrazovala. • pˇr´ıklad: debug/coredump.c . Po pˇreloˇzen´ı a spuˇstˇen´ı program spadne a zanech´ a core dump. $ cc coredump.c $ ./a.out Segmentation Fault (core dumped) $ dbx ./a.out core Reading a.out core file header read successfully Reading ld.so.1 Reading libc.so.1 program terminated by signal SEGV (no mapping at the fault address) 0x08050a05: bad_memory_access+0x0015: movb %al,0x00000000(%edx) (dbx) where =>[1] bad_memory_access(0x8047ae8, 0x8047a44, ... [2] main(0x1, 0x8047a50, 0x8047a58, 0x8047a0c), at 0x8050a1b Vid´ıme, ve kter´e funkci to spadlo a je moˇzn´e se vypsat z´asobn´ık. Nevid´ıme ale pˇresnˇe ˇr´ adku k´ odu, kde nastal probl´em. Pro to je nutn´e pˇreloˇzit program s lad´ıc´ımi symboly, tj. “cc -g coredump.c”. Z´ajemce o v´ıce informac´ı o debugging pod unixem odkazuji na navazuj´ıc´ı pˇredn´aˇsku “Programov´an´ı v UNIXu II.” (NSWI138). $ cc -g coredump.c $ dbx ./a.out core Reading a.out core file header read successfully Reading ld.so.1 Reading libc.so.1 program terminated by signal SEGV (no mapping at the fault address) Current function is bad_memory_access 8 x[0] = ’\0’; (dbx)
41
GNU debugger gdb • Vol´ an´ı: gdb [ options ] [ program [ core ] ] • Nejbˇeˇznˇejˇs´ı pˇr´ıkazy: run [arglist ] bt print expr set var = expr cont next, step break condition help [name ] quit
start programu vypiˇs z´asobn´ık vypiˇs v´ yraz zmˇen ˇ hodnotu promˇenn´e pokraˇcov´an´ı bˇehu programu proved’ ˇr´adku (bez/s vnoˇren´ım do funkce) nastaven´ı breakpointu n´apovˇeda ukonˇcen´ı debuggeru
• GNU obdoba dbx. M´ od kompatibiln´ı s dbx spust´ıte pˇres -dbx. • na r˚ uzn´ ych platform´ ach existuj´ı i debuggery s grafick´ ym rozhran´ım, napˇr. ˇ workshop (Solaris), cvd (IRIX), xxgdb (GNU), ddd (GNU). Casto funguj´ı jako nadstavby nad dbx, gdb. • #include <stdio.h> int main(void) { printf("hello, world\n"); return (0); } $ cc -g main.c $ gdb -q a.out (gdb) break main Breakpoint 1 at 0x8048548: file main.c, line 4. (gdb) run Starting program: /share/home/jp/src/gdb/a.out Breakpoint 1, main () at main.c:4 4 printf("hello, world\n"); (gdb) next hello, world 5 return (0); (gdb) c Continuing. Program exited normally.
42
(gdb) q • debuggery jsou v´ yborn´ ymi pomocn´ıky pokud v´aˇs program konˇc´ı na chyby typu “segmentation error” – tj. kdyˇz zkus´ıte nekorektnˇe pˇristoupit do pamˇeti, napˇr´ıklad tam kde nem´ ate co dˇelat. Kdyˇz pˇri pˇrekladu pouˇzijete option -g, uk´ aˇze v´ am pak debugger pˇresnˇe ˇc´ıslo ˇr´adku, kde nastal probl´em. Konkr´etn´ı pˇr´ıklad (proˇc se vlastnˇe tento program chov´a jak se chov´a??? Hint: zkuste pˇreloˇzit na Solarisu pˇrekladaˇcem cc a spustit): $ cat -n main.c 1 int 2 main(void) 3 { 4 char *c = "hey world"; 5 c[0] = ’\0’; 6 return (0); 7 } } $ gcc -g main.c $ ./a.out Bus error (core dumped) $ gdb a.out a.out.core ... Core was generated by ‘a.out’. Program terminated with signal 10, Bus error. ... #0 0x080484e6 in main () at main.c:5 5 c[0] = ’\0’;
43
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
Standardn´ı hlaviˇ ckov´ e soubory (ANSI C) stdlib.h errno.h stdio.h ctype.h string.h time.h math.h setjmp.h assert.h stdarg.h limits.h signal.h
... ... ... ... ... ... ... ... ... ... ... ...
z´ akladn´ı makra a funkce oˇsetˇren´ı chyb vstup a v´ ystup pr´ ace se znaky pr´ ace s ˇretˇezci pr´ ace s datem a ˇcasem matematick´e funkce dlouh´e skoky ladic´ı funkce pr´ ace s promˇenn´ ym poˇctem parametr˚ u implementaˇcnˇe z´avisl´e konstanty oˇsetˇren´ı sign´al˚ u
44
• hlaviˇckov´ y soubor (header file) je soubor s deklaracemi funkc´ı (forward declaration), promˇenn´ ych a maker. Z pohledu preprocesoru je to obyˇcejn´ y soubor napsan´ y v jazyce C. • tyto hlaviˇ ckov´ e soubory nejsou specifick´ e pro UNIX. Jsou souˇ c´ ast´ı standardu ANSI C, kter´ y jak jiˇ z v´ıme (strana 17), je zahrnut v POSIX.1. Je ale d˚ uleˇ zit´ e si uvˇ edomit, ˇ ze tyto hlaviˇ ckov´ e soubory mus´ı podporovat kaˇ zd´ y syst´ em, kter´ y podporuje ANSI C, at’ jiˇ z podporuje POSIX.1 nebo ne. • pˇr´ısluˇsn´ y hlaviˇckov´ y soubor pro konkr´etn´ı funkci najdete v manu´alov´e str´ance dan´e funkce, toto je zaˇc´ atek manu´alov´e str´anky na Solarisu pro memcpy: Standard C Library Functions
memory(3C)
NAME memory, memccpy, memchr, memcmp, memcpy, memmove, memory operations
memset
-
SYNOPSIS #include <string.h> ... ... • jednotliv´ a makra obsaˇzen´ a v tˇechto souborech vˇetˇsinou nejsou vysvˇetlena, v´ yznam jednotliv´ ych maker je ale moˇzn´e si vyhledat v pˇr´ısluˇsn´ ych specifikac´ıch, kter´e jsou on-line. Na nˇekter´ ych syst´emech (Solaris) maj´ı jednotliv´e hlaviˇckov´e soubory svoji vlastn´ı manu´alovou str´anku (man stdlib.h). • makro assert je moˇzn´e z bˇehem kompilace odstranit pomoc´ı makra NDEBUG. Pˇr´ıklad: assert/assert.c . cat assert.c #include int main(void) { assert(1 == 0); return (13); } $ cc assert.c $ ./a.out Assertion failed: 1 == 0, file assert.c, line 6 Abort (core dumped) $ cc -DNDEBUG assert.c $ ./a.out $ echo $? 13
45
Standardn´ı hlaviˇ ckov´ e soubory (2) unistd.h
...
symbolick´e konstanty, typy a z´akladn´ı funkce
sys/types.h
...
datov´e typy
fcntl.h
...
ˇr´ıd´ıc´ı operace pro soubory
sys/stat.h
...
informace o souborech
dirent.h
...
proch´azen´ı adres´aˇr˚ u
sys/wait.h
...
ˇcek´ an´ı na synovsk´e procesy
sys/mman.h
...
mapov´an´ı pamˇeti
curses.h
...
ovl´ ad´an´ı termin´alu
regex.h
...
pr´ ace s regul´arn´ımi v´ yrazy
• tyto headery uˇz patˇr´ı do UNIXu. • zaj´ımav´e m˚ uˇze b´ yt pod´ıvat se do sys/types.h
46
Standardn´ı hlaviˇ ckov´ e soubory (3) semaphore.h
...
semafory (POSIX)
pthread.h
...
vl´ akna (POSIX threads)
sys/socket.h
...
s´ıt’ov´a komunikace
arpa/inet.h
...
manipulace se s´ıt’ov´ ymi adresami
sys/ipc.h
...
spoleˇcn´e deklarace pro System V IPC
sys/shm.h
...
sd´ılen´a pamˇet’ (System V)
sys/msg.h
...
fronty zpr´av (System V)
sys/sem.h
...
semafory (System V)
• dokonˇcen´ı nejd˚ uleˇzitˇejˇs´ıch UNIXov´ ych header˚ u. Existuj´ı samozˇrejmˇe jeˇstˇe dalˇs´ı.
47
Funkce main() • pˇri spuˇstˇen´ı programu je pˇred´ano ˇr´ızen´ı funkci main(). • int main (int argc, char *argv []); – argc . . . poˇcet argument˚ u pˇr´ıkazov´e ˇr´adky – argv . . . pole argument˚ u ∗ podle konvence je argv[0] jm´eno programu (bez cesty) ∗ posledn´ı prvek je argv[argc] == NULL
– n´ avrat z main() nebo vol´an´ı exit() ukonˇc´ı program – standardn´ı n´ avratov´e hodnoty EXIT_SUCCESS (0) a EXIT_FAILURE (1) ls -l / argc argv
3
argv[0] argv[1] argv[2] argv[3]
”ls” ”-l” ”/”
• prvn´ı parametr (typu int) ud´av´a poˇcet argument˚ u na pˇr´ıkazov´em ˇr´adku (vˇcetnˇe argumentu 0 – jm´ena programu) a druh´ y parametr (typu char**) je pole ukazatel˚ u na tyto ˇretˇezce. Za posledn´ım ˇretˇezcem je jeˇstˇe ukonˇcovac´ı NULL pointer – pozor na to, ˇze to je nˇeco jin´eho neˇz pr´azdn´ y ˇretˇezec. • argv[0] nˇekdy b´ yv´ a d˚ uleˇzit´ ym zdrojem pˇridan´e informace. Na Solarisu jsou napˇr´ıklad pˇr´ıkazy cp, mv a ln nalinkovan´e na stejn´ y soubor. Hodnota argv[0] pak urˇcuje, jakou funkci vlastnˇe proces m´a. Jin´ y pˇr´ıklad – pokud m´a shell prvn´ı znak z argv[0] nastaven na “-”, znamen´a to, ˇze se m´a chovat jako login shell (pod´ıvejte se tˇreba do manu´alov´e str´anky pro bash na sekci INVOCATION, pokud nev´ıte, co to je login shell). Ve v´ ypisu proces˚ u pak uvid´ıte “-bash”. Toto nen´ı souˇc´ ast UNIX specifikace pro sh, ale pouˇz´ıval to jiˇz Bourne shell na UNIXu V7 (1979) a ostatn´ı shelly to pˇrevzaly. • pˇri spuˇstˇen´ı programu pˇred´ a k´od dynamick´eho linkeru ˇr´ızen´ı funkci main, viz strana 36. Staticky slinkovan´emu programu se ˇr´ızen´ı pˇred´a pˇr´ımo. Nepˇr´ıtomnost main v programu zp˚ usob´ı chybu pˇri pˇrekladu na u ´rovni linkeru. T´eto funkci se pˇred´ a jm´eno spuˇstˇen´eho programu, argumenty z pˇr´ıkazov´e ˇr´ adky a pˇr´ıpadnˇe i promˇenn´e prostˇred´ı. Ukonˇcen´ı t´eto funkce znamen´a konec programu a n´ avratov´ a hodnota se pouˇzije jako k´od ukonˇcen´ı programu pro OS. Jinou moˇznost´ı ukonˇcen´ı programu je pouˇzit´ı funkce exit nebo _exit, kterou lze pouˇz´ıt kdykoliv, nejen ve funkci main. V C lze pouˇz´ıvat obˇe metody ukonˇcen´ı programu. • pˇred´ an´ı promˇenn´ ych prostˇred´ı tˇret´ım parametrem typu char** nen´ı souˇc´ast´ı normativn´ı ˇc´ asti C standardu, pouze informativn´ı. Pˇrekladaˇce to ale typicky
48
podporuj´ı. Varianta main s promˇenn´ ymi prostˇred´ı pak vypad´a takto: int main(int argc, char *argv [], char *envp []); • n´ avratov´ y typ funkce main by mˇel b´ yt vˇzdy int; pˇrekladaˇc si jinak bude stˇeˇzovat. Z t´ eto hodnoty se ale pouˇ zije pouze nejniˇ zˇ s´ıch 8 bit˚ u, a je to nez´ aporn´e ˇc´ıslo. Pozor na to, ˇze na rozd´ıl od konvence jazyka C n´ avratov´ a hodnota 0 m´ a v shellu v´ yznam true (´ uspˇech) a nenula v´ yznam false (ne´ uspˇech). Typick´ a konstrukce v shellu vypad´a takto: if ! prog; then echo "failure" else echo "success" fi Pˇr´ıklad: main/return-256.c . • nepouˇz´ıvejte proto ve funkci main return (-1) a nikde pak ani exit(-1), z toho vznikne n´ avratov´ a hodnota 255 a k´od je matouc´ı. Je v˚ ubec velmi vhodn´e pouˇz´ıvat pouze 0 a 1, pokud nen´ı z´asadn´ı d˚ uvod pro jin´e hodnoty, tˇreba ten ˇze jich potˇrebujete v´ıce – m˚ uˇzete se pod´ıvat napˇr´ıklad na manu´alovou str´anku pro passwd na Solarisu, sekce EXIT STATUS. Pˇr´ıklad: main/return-negative-1.c . • rozd´ıl mezi funkcemi exit a _exit je v tom, ˇze exit pˇred ukonˇcen´ım programu jeˇstˇe vypr´ azdn´ı (pomoc´ı knihovn´ı funkce fflush) a zavˇre streamy a vol´ a funkce zaregistrovan´e pomoc´ı atexit. V z´avislosti na syst´emu to mohou b´ yt i dalˇs´ı akce. Pro zaj´ımavost, napˇr´ıklad ve FreeBSD je exit syst´emov´e vol´an´ı a exit knihovn´ı funkce, ale na Solarisu jsou obˇe syst´emov´ ymi vol´an´ımi. Pˇr´ıklad: exit/exit.c • pˇr´ıklad: main/print-argv.c
49
Promˇ enn´ e prostˇ red´ı • seznam vˇsech promˇenn´ ych prostˇred´ı (environment variables) se pˇred´ av´ a jako promˇenn´ a extern char **environ; • je to pole ukazatel˚ u (ukonˇcen´e NULL) na ˇretˇezce ve tvaru: promˇ enn´ a =hodnota environ ”SHELL=/bin/bash”
environ[0] environ[1] environ[2] environ[3]
”DISPLAY=:0” ”LOGNAME=beran”
• shell pˇred´ av´ a spuˇstˇen´emu programu ty promˇenn´e, kter´e jsou oznaˇceny jako exportovan´e (Bourne-like shellech pˇr´ıkazem export variable ). Po zmˇenˇe obsahu jiˇz jednou exportovan´e promˇenn´e samozˇrejmˇe nen´ı potˇreba promˇennou znovu exportovat. Pˇr´ıkaz env v´am vyp´ıˇse aktu´aln´ı promˇenn´e prostˇred´ı. Abyste pˇridali promˇennou do prostˇred´ı spouˇstˇen´eho programu staˇc´ı prov´est pˇriˇrazen´ı na pˇr´ıkazov´e ˇr´ adce, aniˇz byste museli mˇenit prostˇred´ı vaˇseho shellu: $ date Sun Oct 7 13:13:58 PDT 2007 $ LC_TIME=fr date dimanche 7 octobre 2007 13 h 14 PDT nedivte se, pokud to na vaˇsem syst´emu takto fungovat nebude, v tom pˇr´ıpadˇe nem´ ate instalovan´ y bal´ık s francouzskou lokalizac´ı (coˇz je pravdˇepodobn´e). • pˇri nahrazen´ı aktu´ aln´ıho obrazu procesu obrazem jin´ ym se pˇred´av´a, pokud se neˇrekne jinak, synovsk´ ym proces˚ um cel´e pole environ automaticky. Je moˇzn´e ve vol´ an´ı pˇr´ısluˇsn´e varianty funkce exec pˇredat pole jin´e. • jak´e promˇenn´e prostˇred´ı konkr´etn´ı pˇr´ıkaz pouˇz´ıv´a (a jak je pouˇz´ıv´a) by mˇelo b´ yt v manu´ alov´e str´ ance. Typicky v sekci nazvan´e ENVIRONMENT nebo ENVIRONMENT VARIABLES • man napˇr´ıklad pouˇz´ıv´ a PAGER, vipw pak promˇennou EDITOR apod. • pokud je envp tˇret´ım parametrem funkce main, tak je to stejn´a hodnota co je v ukazateli environ. 50
• pˇr´ıklad: main/print-env.c (vˇcetnˇe pouˇzit´ı env pˇr´ıkazu zp˚ usobem, kter´ y vyˇcist´ı zdˇedˇen´e promˇenn´e prostˇred´ı). $ cc print-env.c $ env - XXX=yyy aaa=ABC ./a.out aaa=ABC XXX=yyy
Manipulace s promˇ enn´ ymi prostˇ red´ı • je moˇzn´e pˇr´ımo mˇenit promˇennou environ, SUSv3 to ale nedoporuˇcuje • char *getenv (const char *name ); – vr´ at´ı hodnotu promˇenn´e name • int putenv (char *string ); – vloˇz´ı string ve tvaru jm´ eno =hodnota do prostˇred´ı (pˇrid´a novou nebo modifikuje existuj´ıc´ı promˇennou) • zmˇeny se pˇren´ aˇsej´ı do synovsk´ ych proces˚ u • zmˇeny v prostˇred´ı syna samozˇrejmˇe prostˇred´ı otce neovlivn´ı • existuj´ı i funkce setenv() a unsetenv()
• u putenv se vloˇzen´ y ˇretˇezec stane souˇc´ast´ı prostˇred´ı (jeho pozdˇejˇs´ı zmˇena tak zmˇen´ı prostˇred´ı) a nesm´ıte proto pouˇz´ıvat retˇezce v automatick´ ych promˇenn´ ych, toto ˇreˇs´ı setenv, kter´ y hodnotu promˇenn´e zkop´ıruje. • d˚ uleˇzit´e je zapamatovat si, ˇze synovsk´ y proces zdˇed´ı v okamˇziku sv´eho vzniku od rodiˇce vˇsechny promˇenn´e prostˇred´ı, ale jak´akoliv manipulace s nimi v synovi je lok´ aln´ı a do otce se nepˇren´aˇs´ı. Kaˇzd´ y proces m´a svou kopii promˇenn´ ych, proto ani n´ asledn´a zmˇena prostˇred´ı otce nezmˇen´ı promˇenn´e jiˇz existuj´ıc´ıho potomka. • dalˇs´ı rozd´ıl mezi putenv a setenv je ten, ˇze v setenv mohu definovat, zda existuj´ıc´ı promˇennou chci nebo nechci pˇrepsat. putenv vˇzdy pˇrepisuje. • int main(void) { printf("%s\n", getenv("USER")); return (0);
51
} $ ./a.out jp • jelikoˇz promˇenn´ a environ je obyˇcejn´e pole ukazatel˚ u na ˇretˇezce, nen´ı nebˇeˇzn´e narazit na k´ od, kter´ y s t´ımto polem pracuje pˇr´ımo. Pozor vˇsak na to, ˇze v takov´em pˇr´ıpadˇe pak jiˇz nesm´ıte pouˇz´ıvat zde uveden´e funkce, jinak se m˚ uˇzete dostat do probl´em˚ u s jejich konkr´etn´ı implementac´ı. A hlavnˇe, nov´ y k´od takto nepiˇste, protoˇze norma SUSv3 pˇr´ımou pr´aci s t´ımto polem nedoporuˇcuje. • pˇr´ıklad: main/getenv.c • pozor na to, ˇze je rozd´ıl mezi nastaven´ım hodnoty promˇenn´e na pr´azdn´ y string a odmaz´ an´ım promˇenn´e ze seznamu promˇenn´ ych (pomoc´ı unsetenv).
Zpracov´ an´ı argument˚ u programu • obvykl´ y z´ apis v shellu: program -pˇ rep´ ınaˇ ce argumenty • pˇrep´ınaˇce tvaru -x nebo -x hodnota , kde x je jedno p´ısmeno nebo ˇc´ıslice, hodnota je libovoln´ y ˇretˇezec • nˇekolik pˇrep´ınaˇc˚ u lze slouˇcit dohromady: ls -lRa • argument ’--’ nebo prvn´ı argument nezaˇc´ınaj´ıc´ı ’-’ ukonˇcuje pˇrep´ınaˇce, n´ asleduj´ıc´ı argumenty nejsou povaˇzov´any za pˇrep´ınaˇce, i kdyˇz zaˇc´ınaj´ı znakem ’-’. • tento tvar argument˚ u poˇzaduje norma a lze je zpracov´avat automaticky funkc´ı getopt.
• argumenty lze samozˇrejmˇe zpracov´avat vlastn´ı funkc´ı, ale standardn´ı funkce je pohodlnˇejˇs´ı. • argumenty se typicky mohou opakovat, ale to m´a smysl jen v nˇekter´ ych situac´ıch • poˇrad´ı pˇrep´ınaˇc˚ u m˚ uˇze b´ yt d˚ uleˇzit´e a je na aplikaci, aby toto specifikovala • UNIX norma definuje pomoc´ı 13 pravidel velmi pˇresnˇe, jak by mˇely vypadat n´ azvy pˇr´ıkaz˚ u a form´ at pˇrep´ınaˇc˚ u. Napˇr´ıklad jm´eno pˇr´ıkazu by mˇelo b´ yt pouze mal´ ymi p´ısmeny, dlouh´e 2–9 znak˚ u, z pˇrenositeln´e znakov´e sady. Pˇrep´ınaˇce bez argument˚ u by mˇelo b´ yt moˇzn´e d´at do skupiny za jedn´ım znakem ’–’. Atd. 52
• pouˇz´ıvat ˇc´ıslice jako pˇrep´ınaˇce je zastaral´e; je to nˇekde v normˇe SUSv3, i kdyˇz j´ a to v n´ı nenaˇsel. • pozor na Linux a jeho (ponˇekud zvl´aˇstn´ı a nestandardn´ı) permutov´an´ı argument˚ u • pˇrep´ınaˇc -W by mˇel b´ yt rezervovan´ y pro vendor options, tj. pro nepˇrenositeln´a rozˇs´ıˇren´ı
Zpracov´ an´ı pˇ rep´ınaˇ c˚ u: getopt() int getopt(int argc, char *const argv [], const char *optstring ); extern char *optarg ; extern int optind, opterr, optopt ; • funkce dostane parametry z pˇr´ıkazov´eho ˇr´adku, pˇri kaˇzd´em vol´ an´ı zpracuje a vr´ at´ı dalˇs´ı pˇrep´ınaˇc. Pokud m´a pˇrep´ınaˇc hodnotu, vr´ at´ı ji v optarg. • kdyˇz jsou vyˇcerp´ any vˇsechny pˇrep´ınaˇce, vr´at´ı -1 a v optind je ˇc´ıslo prvn´ıho nezpracovan´eho argumentu. • moˇzn´e pˇrep´ınaˇce jsou zad´any v optstring, kdyˇz za znakem pˇrep´ınaˇce n´ asleduje ’:’, m´a pˇrep´ınaˇc povinnou hodnotu. • pˇri chybˇe (nezn´ am´ y pˇrep´ınaˇc, chyb´ı hodnota) vr´at´ı ’?’, uloˇz´ı znak pˇrep´ınaˇce do optopt a kdyˇz opterr nebylo nastaveno na nulu, vyp´ıˇse chybov´e hl´aˇsen´ı.
• obvykle se nejprve pomoc´ı getopt naˇctou pˇrep´ınaˇce a pak se vlastn´ımi prostˇredky zpracuj´ı ostatn´ı argumenty; ˇcasto jsou to jm´ena soubor˚ u. • je konvenc´ı, ˇze volby v parametru optstring jsou setˇr´ıdˇen´e.
53
Pˇ r´ıklad pouˇ zit´ı getopt() struct { int a, b; char c[128]; } opts; int opt; char *arg1; while((opt = getopt(argc, argv, "abc:")) != -1) switch(opt) { case ’a’: opts.a = 1; break; case ’b’: opts.b = 1; break; case ’c’: strcpy(opts.c, optarg); break; case ’?’: fprintf(stderr, "usage: %s [-ab] [-c Carg] arg1 arg2 ...\n", basename(argv[0])); break; } arg1 = argv[optind];
• dobr´ ym zvykem je pˇri detekov´an´ı nezn´am´eho pˇrep´ınaˇce nebo ˇspatn´eho z´apisu parametr˚ u programu vypsat struˇcnou n´apovˇedu, pˇr´ıpadnˇe s odkazem na podrobnˇejˇs´ı dokumentaci, a ukonˇcit program s chybou, tj. s nenulovou n´ avratovou hodnotou. • pˇr´ıklad tak´e ukazuje nebezpeˇcn´e pouˇzit´ı funkce strcpy • z pouˇzit´ı funkce getopt je vidˇet, ˇze je stavov´a. Zpracovat dalˇs´ı pole argument˚ u, pˇr´ıpadnˇe zaˇc´ıt opˇet od zaˇc´atku, je moˇzn´e nastaven´ım extern´ı promˇenn´e optreset na 1. • standardn´ı getopt zachov´ a poˇrad´ı pˇrep´ınaˇc˚ u pˇri zpracov´an´ı • pˇri pouˇzit´ı nedefinovan´eho pˇrep´ınaˇce funkce vyp´ıˇse chybu; to lze potlaˇcit nastaven´ım opterr na 0. • pˇr´ıklad: shellov´ y skript getopt/getopts.sh pˇrepsan´ y do jazyka C pomoc´ı getopt funkce, getopt/getopt.c
54
Dlouh´ y tvar pˇ rep´ınaˇ c˚ u • poprv´e se objevilo v GNU knihovnˇe libiberty: --jm´ eno nebo --jm´ eno=hodnota • argumenty se permutuj´ı tak, aby pˇrep´ınaˇce byly na zaˇc´atku, napˇr. ls * -l je tot´eˇz jako ls -l *, standardn´ı chov´an´ı lze doc´ılit nastaven´ım promˇenn´e POSIXLY_CORRECT. • zpracov´ avaj´ı se funkc´ı getopt long(), kter´a pouˇz´ıv´a pole struktur popisuj´ıc´ıch jednotliv´e pˇrep´ınaˇce: struct option { const char *name; /* jm´eno pˇrep´ınaˇce */ int has arg; /* hodnota: ano, ne, volitelnˇ e */ int *flag; /* kdyˇz je NULL, funkce vrac´ı val, jinak vrac´ı 0 a d´a val do *flag */ int val; /* n´ avratov´a hodnota */ };
verze jak se objevila ve FreeBSD (funkce getopt long nen´ı standarizovan´a), m´a n´ asleduj´ıc´ı vlastnosti: • pokud vˇsechny dlouh´e pˇrep´ınaˇce maj´ı nastaveny kr´atkou variantu ve val, je chov´ an´ı getopt long kompatibiln´ı s getopt • je moˇzn´e zad´ avat argument k dlouh´emu pˇrep´ınaˇci i s mezerou (napˇr´ıklad --color green) • pokud je nastaven flag, tak getopt long vrac´ı 0, ˇc´ımˇz se tyto dlouh´e pˇrep´ınaˇce bez kr´ atk´e varianty zpracuj´ı v jedn´e vˇetvi pˇr´ıkazu case • existuje i vol´ an´ı getopt long only, kter´e povoluje i dlouh´e pˇrep´ınaˇce uvozen´e jednou uvozovkou (-option) • funkci getopt long je moˇzn´e pouˇz´ıvat dvˇemi zp˚ usoby. Prvn´ı zp˚ usob je, ˇze kaˇzd´ y dlouh´ y pˇrep´ınaˇc m´ a koresponduj´ıc´ı kr´atk´ y – takto lze jednoduˇse pˇridat dlouh´e pˇrep´ınaˇce do existuj´ıc´ıho programu a je kompatibiln´ı s getopt. Druh´ y zp˚ usob umoˇzn ˇuje m´ıt samostatn´e dlouh´e pˇrep´ınaˇce. V tom pˇr´ıpadˇe funkce vrac´ı vˇzdy 0 (nekompatibilita s getopt) a promˇenn´a *flag se nastav´ı na val. • na konkr´etn´ım pˇr´ıkladu na n´asleduj´ıc´ı str´ance je vidˇet, jak to cel´e funguje
55
Dlouh´ e pˇ rep´ınaˇ ce (pokraˇ cov´ an´ı) int getopt long(int argc, char * const argv [], const char *optstring, const struct option *longopts, int *longindex ); • optstring obsahuje jednop´ısmenn´e pˇrep´ınaˇce, longopts obsahuje adresu pole struktur pro dlouh´e pˇrep´ınaˇce (posledn´ı z´ aznam pole obsahuje sam´e nuly) • pokud funkce naraz´ı na dlouh´ y pˇrep´ınaˇc, vrac´ı odpov´ıdaj´ıc´ı val nebo nulu (pokud flag nebyl NULL), jinak je chov´an´ı shodn´e s getopt. • do *longindex (kdyˇz nen´ı NULL) d´a nav´ıc index nalezen´eho pˇrep´ınaˇce v longopts.
• toto je upraven´ y pˇr´ıklad z manu´alov´e str´anky na FreeBSD: #include <stdio.h> #include #include int ch, fd, daggerset, bflag = 0; static struct option longopts[] = { { "buffy", no_argument, { "fluoride", required_argument, { "daggerset", no_argument, { NULL, 0,
NULL, NULL, &daggerset, NULL,
’b’ }, ’f’ }, 1 }, 0 }};
int main(int argc, char **argv) { while ((ch = getopt_long(argc, argv, "bf:", longopts, NULL)) != -1) switch (ch) { case ’b’: bflag = 1; break; case ’f’: if ((fd = open(optarg, O_RDONLY, 0)) == -1) printf("unable to open %s", optarg); break; case 0: if (daggerset) { printf("Buffy will use her dagger to " "apply fluoride to dracula’s teeth\n"); } break; default: printf("usage: ...\n"); }
56
argc -= optind; argv += optind; return 0; }
Struktura klasick´ eho OS UNIX uˇzivatelsk´y proces pˇreruˇsen´ı n´avrat
uˇzivatelsk´y reˇzim
rozhran´ı vol´an´ı j´adra reˇzim j´adra
subsyst´em ˇr´ızen´ı soubor˚ u buffery
subsyst´em ˇr´ızen´ı proces˚ u a pamˇeti
znakov´e blokov´e ovladaˇce
IPC pl´anovaˇc pˇridˇelov´an´ı pamˇeti
strojovˇe z´avisl´a vrstva ovl´ad´an´ı hardware hardware
• toto sch´ema je pˇrevzato z [Bach86], viz literatura. Zd˚ urazˇ nuje dva u ´stˇredn´ı pojmy v modelu syst´emu UNIX – soubory a procesy. V dneˇ sn´ı dobˇ e to vypad´ a velmi odliˇ snˇ e, ale pro n´ as staˇ c´ı tato z´ akladn´ı pˇ redstava. • UNIX rozliˇsuje dva reˇzimy bˇehu procesoru: uˇzivatelsk´y reˇzim a reˇzim j´ adra. V uˇzivatelsk´em reˇzimu nejsou pˇr´ıstupn´e privilegovan´e instrukce (napˇr. mapov´ an´ı pamˇeti, I/O, maskov´an´ı pˇreruˇsen´ı). Tyto dva reˇzimy mus´ı b´ yt podporov´ any na hardwarov´e u ´rovni (procesorem). • procesy bˇeˇz´ı obvykle v uˇzivatelsk´em reˇzimu, do reˇzimu j´adra pˇrech´az´ı bud’ instrukc´ı synchronn´ıho pˇreruˇsen´ı (trap) pro vol´an´ı sluˇzby j´adra, nebo na z´ akladˇe asynchronn´ıch pˇreruˇsen´ı (hodiny, I/O). D´ale se v reˇzimu j´adra oˇsetˇruj´ı v´ yjimeˇcn´e stavy procesoru (v´ ypadek str´anky, naruˇsen´ı ochrany pamˇeti, nezn´ am´ a instrukce apod.). Nˇekter´e speci´aln´ı akce jsou zajiˇst’ov´any syst´emov´ ymi procesy, kter´e bˇeˇz´ı celou dobu v reˇzimu j´adra. • klasick´e UNIXov´e j´ adro je tvoˇreno monolitick´ ym k´odem. P˚ uvodnˇe bylo potˇreba vygenerovat (tj. pˇreloˇzit ze zdrojov´ ych text˚ u a slinkovat) j´adro pˇri zmˇenˇe nˇekter´eho parametru nebo pˇrid´an´ı ovladaˇce zaˇr´ızen´ı. V novˇejˇs´ıch implementac´ıch je moˇzno nastavovat parametry j´adra, nˇekdy i za bˇehu, pomoc´ı syst´emov´ ych utilit bez nutnosti rekompilace j´adra. Modern´ı unixov´e syst´emy umoˇzn ˇuj´ı rozˇsiˇrovat k´ od j´ adra za bˇehu pomoc´ı tzv. modul˚ u j´adra (loadable kernel modules). Napˇr´ıklad syst´em FreeBSD 5.4-RELEASE m´a 392 takov´ ych modul˚ u. 57
• existuj´ı dva zp˚ usoby pr´ ace s perif´eriemi: blokov´a (block devices) a znakov´a zaˇr´ızen´ı (character, raw devices). Data z blokov´ ych zaˇr´ızen´ı (napˇr. disky) proch´ azej´ı pˇres vyrovn´ avac´ı pamˇeti (buffers) po bloc´ıch, znakov´a zaˇr´ızen´ı (napˇr. termin´ aly) umoˇzn ˇuj´ı pracovat s jednotliv´ ymi bajty a nepouˇz´ıvaj´ı vyrovn´ avac´ı pamˇet’. • j´ adro nen´ı samostatn´ y proces, ale je ˇc´ast´ı kaˇzd´eho uˇzivatelsk´eho procesu. Kdyˇz j´ adro nˇeco vykon´ av´ a, tak vlastnˇe proces, bˇeˇz´ıc´ı v reˇzimu j´adra, nˇeco prov´ ad´ı.
Procesy, vl´ akna, programy • proces je syst´emov´ y objekt charakterizovan´ y sv´ ym kontextem, identifikovan´ y jednoznaˇcn´ ym ˇc´ıslem (process ID, PID); jin´ ymi slovy ,,k´ od a data v pamˇeti” • vl´ akno (thread) je syst´emov´ y objekt, kter´ y existuje uvnitˇr procesu a je charakterizov´an sv´ ym stavem. Vˇsechna vl´akna jednoho procesu sd´ıl´ı stejn´ y pamˇet’ov´ y prostor kromˇe registr˚ u procesoru a z´ asobn´ıku; ,,linie v´ ypoˇctu”, ,,to, co bˇeˇz´ı” • program ... soubor pˇresnˇe definovan´eho form´atu obsahuj´ıc´ı instrukce, data a sluˇzebn´ı informace nutn´e ke spuˇstˇen´ı; ,,spustiteln´ y soubor na disku” ◦ pamˇ et’ se pˇridˇeluje proces˚ um. ◦ procesory se pˇridˇeluj´ı vl´ akn˚ um. ◦ vl´ akna jednoho procesu mohou bˇeˇzet na r˚ uzn´ ych procesorech.
• kontext je pamˇet’ov´ y prostor procesu, obsah registr˚ u a datov´e struktury j´ adra t´ ykaj´ıc´ı se dan´eho procesu • jinak ˇreˇceno – kontext procesu je jeho stav. Kdyˇz syst´em vykon´av´a proces, ˇr´ık´ a se, ˇze bˇeˇz´ı v kontextu procesu. J´adro (klasick´e) obsluhuje pˇreruˇsen´ı v kontextu pˇreruˇsen´eho procesu. • vl´ akna se dostala do UNIXu aˇz pozdˇeji, p˚ uvodnˇe v nˇem existovaly pouze procesy, kter´e mˇely z dneˇsn´ıho pohledu pouze jedno vl´akno. Moˇznost pouˇz´ıt v procesu v´ıce vl´ aken byla zavedena, protoˇze se uk´azalo, ˇze je vhodn´e m´ıt v´ıce paraleln´ıch lini´ı v´ ypoˇctu nad sd´ılen´ ymi daty. • pamˇet’ov´e prostory proces˚ u jsou navz´ajem izolovan´e, ale procesy spolu mohou komunikovat. Pozdˇeji se dozv´ıme, ˇze mohou i ˇc´asteˇcnˇe sd´ılet pamˇet’. • procesy jsou entity na u ´rovni j´adra, ale vl´akna mohou b´ yt ˇc´asteˇcnˇe nebo zcela implementov´ ana knihovn´ımi funkcemi. V pˇr´ıpadˇe implementace pomoc´ı
58
knihovn´ıch fuknc´ı to znamen´a, ˇze vl´akna nemus´ı j´adro v˚ ubec podporovat. S vl´ akny je spojena menˇs´ı reˇzie neˇz s procesy. • syst´emov´ y proces, kter´ y bˇeˇz´ı na pozad´ı obvykle po celou dobu bˇehu syst´emu a zajiˇst’uje nˇekter´e syst´emov´e sluˇzby (inetd, cron, sendmail. . . ) se naz´ yv´a d´emon (angl. daemon). Syst´em BSD tedy nem´a ve znaku ˇcerta, ale d´emona.
J´ adro, reˇ zimy, pˇ reruˇ sen´ı (klasick´ y UNIX) • procesy typicky bˇeˇz´ı v uˇzivatelsk´em reˇzimu • syst´emov´e vol´ an´ı zp˚ usob´ı pˇrepnut´ı do reˇzimu j´adra • proces m´ a pro kaˇzd´ y reˇzim samostatn´ y z´asobn´ık • j´ adro je ˇc´ ast´ı kaˇzd´eho uˇzivatelsk´eho procesu, nen´ı to samostn´ y proces (procesy) • pˇrepnut´ı na jin´ y proces se naz´ yv´a pˇrepnut´ı kontextu • obsluha pˇreruˇsen´ı se prov´ad´ı v kontextu pˇreruˇsen´eho procesu • klasick´e j´ adro je nepreemptivn´ı
• j´ adro nen´ı oddˇ elen´ a mnoˇ zina proces˚ u, bˇ eˇ z´ıc´ıch paralelnˇ e s uˇ zivatelsk´ ymi procesy, ale je ˇ c´ ast´ı kaˇ zd´ eho uˇ zivatelsk´ eho procesu. • pˇrechod mezi uˇzivatelsk´ ym reˇzimem a reˇzimem j´adra nen´ı pˇrepnut´ı kontextu – proces bˇeˇz´ı poˇr´ ad v tom sam´em • pˇreruˇsen´ y proces nemusel pˇreruˇsen´ı v˚ ubec zp˚ usobit • v reˇzimu j´ adra m˚ uˇze proces pˇristupovat i k adres´am j´adra, kter´a z uˇzivatelsk´eho reˇzimu pˇr´ıstupn´ a nejsou; takt´eˇz m˚ uˇze pˇristupovat k instrukc´ım (napˇr. instrukce manipuluj´ıc´ı se stavov´ ym registrem), jejichˇz vykon´an´ı v uˇzivatelsk´em reˇzimu vede k chybˇe • pˇreruˇsovac´ı rutina se nem˚ uˇze zablokovat, protoˇze t´ım by zablokovala proces; proces se totiˇz m˚ uˇze zablokovat jen ze sv´e vlastn´ı v˚ ule. Modern´ı unixy dnes pouˇz´ıvaj´ı interrupt vl´ akna, v jejichˇz kontextu se mohou drivery zablokovat. • to, ˇze klasick´e unixov´e j´ adro je nepreemptivn´ı znamen´a, ˇze jeden proces nem˚ uˇ ze zablokovat jin´ y proces
59
• pˇri obsluze pˇreruˇsen´ı se m˚ uˇze st´at, ˇze nastane dalˇs´ı pˇreruˇsen´ı. Pokud je jeho priorita vˇetˇs´ı, je procesorem pˇrijmuto. Posloupnost pˇrijmut´ ych pˇreruˇsen´ı je uchov´ ana v z´ asobn´ıku kontextov´ych vrstev. • u modern´ıch kernel˚ u je situace ˇ casto velmi rozd´ıln´ a – obsluha pˇ reruˇ sen´ı, preeptivnost kernelu atd.; k nˇ ekter´ ym vˇ ecem se moˇ zn´ a dostaneme pozdˇ eji bˇ ehem semestru
Vol´ an´ı sluˇ zeb a komunikace mezi procesy • UNIX proces
proces
proces
rozhran´ı vol´an´ı j´adra j´adro • distribuovan´ y OS uˇzivatelsk´y proces
uˇzivatelsk´y proces
server
server
server
j´adro
j´adro
j´adro
j´adro
j´adro
• pokud unixov´ y proces vyˇzaduje proveden´ı syst´emov´e sluˇzby, pomoc´ı syst´emov´eho vol´ an´ı pˇred´ a ˇr´ızen´ı j´adru. J´adro je kus k´odu sd´ılen´ y vˇsemi procesy (ovˇsem pˇr´ıstupn´ y jen pro ty, kter´e jsou pr´avˇe v reˇzimu j´adra). J´adro tedy nen´ı samostatn´ y privilegovan´ y proces, ale vˇzdy bˇeˇz´ı v r´amci nˇekter´eho procesu (toho, kter´ y poˇz´ adal j´ adro o sluˇzbu, nebo toho, kter´ y bˇeˇzel v okamˇziku pˇr´ıchodu pˇreruˇsen´ı). • komunikace mezi procesy v UNIXu je ˇreˇsena pomoc´ı syst´emov´ ych vol´an´ı, je tedy zprostˇredkovan´ a j´ adrem. • aby to nebylo tak jednoduch´e, mohou existovat syst´emov´e procesy (oznaˇcovan´e jako kernel threads), kter´e bˇeˇz´ı celou dobu v reˇzimu j´adra. Naprost´a vˇetˇsina syst´emov´ ych proces˚ u vˇsak bˇeˇz´ı v uˇzivatelsk´em reˇzimu a liˇs´ı se jen t´ım, ˇze maj´ı vˇetˇs´ı pˇr´ıstupov´ a pr´ava. Pl´anovaˇc proces˚ u pˇrep´ın´a mezi procesy a t´ım umoˇzn ˇuje bˇeh v´ıce proces˚ u souˇcasnˇe i na jednom procesoru. Na multiprocesorov´ ych poˇc´ıtaˇc´ıch pak funguje skuteˇcn´ y paralelismus proces˚ u a vl´aken (dokonce se proces m˚ uˇze pˇri pˇrepl´anov´an´ı dostat i na jin´ y procesor). • v distribuovan´em operaˇcn´ım syst´emu m´a j´adro obvykle formu mikroj´adra, tj. zajiˇst’uje pouze nejz´ akladnˇejˇs´ı sluˇzby ˇr´ızen´ı procesoru, pˇridˇelov´an´ı pamˇeti 60
a komunikace mezi procesy. Vyˇsˇs´ı syst´emov´e sluˇzby, kter´e jsou v UNIXu souˇc´ ast´ı j´ adra (napˇr. pˇr´ıstup k syst´emu soubor˚ u) jsou realizov´any speci´aln´ımi procesy (servery) bˇeˇz´ıc´ımi v uˇzivatelsk´em reˇzimu procesoru. J´adro pˇred´a poˇzadavek uˇzivatelsk´eho procesu pˇr´ısluˇsn´emu serveru, kter´ y m˚ uˇze bˇeˇzet i na jin´em uzlu s´ıtˇe. • dostupn´ ych mikrokernel˚ u je v dneˇsn´ı dobˇe mnoho. M˚ uˇzete zkusit napˇr´ıklad Minix (unix-like v´ yukov´ y syst´em), pˇr´ıpadnˇe syst´em HURD, kter´ y bˇeˇz´ı nad mikroj´ adrem Mach.
Syst´ emov´ a vol´ an´ı, funkce • v UNIXu se rozliˇsuj´ı syst´ emov´ a vol´ an´ı a knihovn´ı funkce. Toto rozliˇsen´ı dodrˇzuj´ı i manu´alov´e str´anky: sekce 2 obsahuje syst´emov´ a vol´ an´ı (syscalls), sekce 3 knihovn´ı funkce (library functions). – knihovn´ı funkce se vykon´avaj´ı v uˇzivatelsk´em reˇzimu, stejnˇe jako ostatn´ı k´ od programu. – syst´emov´ a vol´ an´ı maj´ı tak´e tvar vol´an´ı funkce. Pˇr´ısluˇsn´a funkce ale pouze zpracuje argumenty vol´an´ı a pˇred´a ˇr´ızen´ı j´ adru pomoc´ı instrukce synchronn´ıho pˇreruˇsen´ı. Po n´avratu z j´ adra funkce uprav´ı v´ ysledek a pˇred´a ho volaj´ıc´ımu. • standardy tyto kategorie nerozliˇsuj´ı – z hlediska program´atora je jedno, zda urˇcitou funkci provede j´adro nebo knihovna.
• zjednoduˇsenˇe lze ˇr´ıci, ˇze syst´emov´e vol´an´ı je funkce, kter´a jen uprav´ı sv´e argumenty do vhodn´e podoby, pˇrepne reˇzim procesoru a skuteˇcnou pr´aci nech´ a na j´ adru. Nakonec zase uprav´ı v´ ysledek. Knihovn´ı funkce m˚ uˇ ze a nemus´ı volat j´ adro, ale vˇ zdy sama dˇ el´ a nˇ ejakou netrivi´ aln´ı ˇ cinnost v uˇ zivatelsk´ em reˇ zimu. • v assembleru je moˇzn´e zavolat vol´an´ı j´adra pˇr´ımo • API j´ adra je definovan´e na u ´rovni vol´an´ı funkc´ı standardn´ı knihovny, nikoliv na u ´rovni pˇreruˇsen´ı a datov´ ych struktur pouˇz´ıvan´ ych tˇemito funkcemi pro pˇred´ an´ı ˇr´ızen´ı j´ adru. Mechanismus pˇrepnut´ı mezi uˇzivatelsk´ ym reˇzimem a reˇzimem j´ adra se totiˇz m˚ uˇze liˇsit nejen v z´avislosti na hardwarov´e platformˇe, ale i mezi r˚ uzn´ ymi verzemi syst´emu na stejn´em hardwaru.
61
N´ avratov´ e hodnoty syst´ emov´ ych vol´ an´ı • celoˇc´ıseln´ a n´ avratov´ a hodnota (int, pid t, off t, apod.) – >= 0 . . . operace u ´spˇeˇsnˇe provedena – == -1 . . . chyba • n´ avratov´ a hodnota typu ukazatel – != NULL . . . operace u ´spˇeˇsnˇe provedena – == NULL . . . chyba • po ne´ uspˇeˇsn´em syst´emov´em vol´an´ı je k´od chyby v glob´aln´ı promˇenn´e extern int errno; • u ´spˇeˇsn´e vol´ an´ı nemˇen´ı hodnotu v errno! Je tedy tˇreba nejprve otestovat n´ avratovou hodnotu a pak teprve errno. • chybov´e hl´ aˇsen´ı podle hodnoty v errno vyp´ıˇse funkce void perror(const char *s ); • textov´ y popis chyby s dan´ ym ˇc´ıslem vr´at´ı funkce char *strerror(int errnum );
• v Solarisu je hodnota errno ve skuteˇcnosti knihovnou libc definovan´a jako dereferencovan´ y pointer na integer (specifick´ y pro dan´ y userland thread) a hodnota se nastavuje ihned po v´ ystupu z instrukce pro syst´emov´e vol´an´ı. Napˇr. na i386 architektuˇre je hodnota errno po n´avratu z kernelu (po dokonˇcen´ı instrukce sysenter) uloˇzena v registru eax (pˇred vol´an´ım v n´ı bylo ˇc´ıslo syscallu). Je to tedy knihovna libc kdo je zodpovˇedn´ y za to, ˇze program uvid´ı spr´ avnou hodnotu errno. • funkce pro pr´ aci s vl´ akny pthread * nenastavuj´ı errno, ale vrac´ı bud’ nulu (´ uspˇech) nebo pˇr´ımo k´ od chyby. • pro nˇekter´ a vol´ an´ı m˚ uˇze m´ıt smysl i n´avratov´a hodnota -1. Pak je tˇreba nejprve nastavit errno = 0 a po n´avratu zkontrolovat, zda se errno zmˇenilo. Napˇr. funkce strtol vrac´ı pˇri chybˇe 0, coˇz je platn´a hodnota i pro spr´avn´ y v´ ysledek (a −1 je samozˇrejmˇe platn´ y v´ ysledek tak´e). • je tedy vˇzdy nutn´e si pˇreˇc´ıst manu´alovou str´anku pro pˇr´ıˇsluˇsn´e vol´an´ı nebo knihovn´ı funkci • pozn.: u ´spˇeˇsnost funkc´ı ze stdio.h je tˇreba testovat pomoc´ı int ferror(FILE *stream ), protoˇze jinak nelze rozliˇsit mezi chybou a koncem streamu. Vzhledem k tomu, ˇze tyto funkce nepouˇz´ıv´ame (kromˇe printf a fprintf na stdout, resp. stderr), nemˇeli byste ji potˇrebovat.
62
Skupina funkc´ı z err(3) • pomocn´e funkce pro v´ ypis chybov´ ych hl´aˇsen´ı a pˇr´ıpadn´e ukonˇcen´ı programu • m´ısto perror() a exit() v´am staˇc´ı jedna funkce • void err(int status, const char *fmt, ...); – vyp´ıˇse jm´eno programu, form´atovan´ y ˇretˇezec, a chybu podle aktu´ aln´ı hodnoty errno – ukonˇc´ı program s n´ avratovou hodnotou ze status • void warn(const char *fmt, ...); – stejn´e jako err(), ale program neukonˇc´ı • existuj´ı dalˇs´ı podobn´e funkce, viz manu´alov´a str´anka • funkce poch´ azej´ı ze 4.4BSD
• vˇetˇs´ı programy podobn´e funkce ˇcasto nepouˇz´ıvaj´ı, napˇr´ıklad proto, ˇze stejnˇe potˇrebuj´ı prov´est r˚ uzn´e ukonˇcovac´ı pr´ace pˇred skonˇcen´ım programu, takˇze maj´ı podobn´e funkce vlastn´ı. Pro jin´e programy jsou ale tyto funkce ide´aln´ı, protoˇze maj´ı rozumn´ y v´ ystup a ˇsetˇr´ı ˇr´adky vaˇseho k´odu. • tyto funkce nemus´ı b´ yt vˇsude, napˇr´ıklad nejsou v libc.so pro Solaris 10 (ale jsou v Solarisu 11). Jsou na BSD syst´emech a v linuxov´ ych distribuc´ıch. Tyto funkce nejsou souˇc´ ast´ı SUS, takˇze ani na certifikovan´ ych UNIX syst´emech se ˇ sen´ım je kontrolovat jejich existenci nelze spolehnout na jejich pˇr´ıtomnost. Reˇ v pˇredkompilaˇcn´ım skriptu a pˇr´ıpadnˇe m´ıt jejich verzi souˇc´ast´ı zdrojov´ ych k´ od˚ u programu pro pˇr´ıpad ˇze nebudou na c´ılov´em syst´emu nalezeny. • pˇr´ıklad (viz tak´e err/err.c ): #include <errno.h> #include <err.h> int main(void) { errno = 13; err(3, "ggr %s", "GRR"); printf("after err()\n"); return (0); }
63
$ ./a.out a.out: ggr GRR: Permission denied $ echo $? 3
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇ r´ıstupov´ a pr´ ava, perifern´ı zaˇ r´ızen´ı, syst´ em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
64
Uˇ zivatel´ e a skupiny beran:x:1205:106:Martin Beran:/home/beran:/bin/bash v´ yznam jednotliv´ ych pol´ı: uˇzivatelsk´e jm´eno, zahaˇsovan´e heslo (dnes uˇz v /etc/shadow), ˇc´ıslo uˇzivatele (UID); superuˇzivatel (root) m´ a UID 0, ˇc´ıslo prim´arn´ı skupiny (GID), pln´e jm´eno, domovsk´ y adres´ aˇr, login-shell sisal:*:106:forst,beran v´ yznam jednotliv´ ych pol´ı: jm´eno skupiny, heslo pro pˇrepnut´ı do skupiny, ˇc´ıslo skupiny (GID), seznam ˇclen˚ u skupiny
• informace o uˇzivatel´ıch v souborech /etc/passwd, a /etc/group jsou zpracov´ av´ any r˚ uzn´ ymi syst´emov´ ymi programy, napˇr. login (pˇrihl´aˇsen´ı do syst´emu na z´ akladˇe uˇzivatelsk´eho jm´ena a hesla) nebo su (zmˇena identity). J´ adro o tˇ echto souborech nic nev´ı, pouˇ z´ıv´ a pouze numerickou identifikaci uˇ zivatele a skupiny. • dnes jiˇz hesla nejsou z bezpeˇcnostn´ıch d˚ uvod˚ u pˇr´ımo v /etc/passwd, ale napˇr´ıklad v /etc/shadow, kter´ y bˇeˇzn´emu uˇzivateli pˇr´ıstupn´ y nen´ı, pouze uˇzivatel root m´ a pr´ avo pro ˇcten´ı a z´apis. Tedy pouze privilegovan´e programy jako login nebo sshd mohou z tohoto souboru ˇc´ıst nebo zapisovat (program passwd bˇeˇz´ı pod uˇzivatelem root d´ıky setuid bitu nastaven´em na souboru programu, viz pozn´amky na stranˇe 75). Takto jsou hesla separovan´ a od veˇrejnˇe pˇr´ıstupn´ ych informac´ı, nav´ıc soubor kter´ y hesla obsahuje, je ukl´ ad´ a v zahaˇsovan´e/zaˇsifrovan´e formˇe, takˇze nejsou pˇr´ımo ˇciteln´a. Na BSD syst´emech (napˇr. FreeBSD, Mac OS X) se m´ısto /etc/shadow pouˇz´ıv´a soubor /etc/master.passwd. • Soubor /etc/shadow je podobnˇe strukturovan´ y jako /etc/passwd. Jeden z´ aznam obsahuje vˇetˇsinou n´asleduj´ıc´ı poloˇzky (Solaris): uˇzivatelsk´e jm´eno, zahaˇsovan´e heslo (spolu s indik´atorem ˇze dan´ yu ´ˇcet je zablokovan´ y), posledn´ı modifikace hesla, minimum dn´ı poˇzadovan´ ych mezi zmˇenou hesla, maximum dn´ı po kter´e je heslo platn´e, poˇcet dn´ı po kter´ ych je uˇzivatel varov´an o expiraci hesla, poˇcet povolen´ ych dn´ı neaktivity uˇzivatele, absolutn´ı ˇcas expirace uˇzivatele, poˇcet ne´ uspˇeˇsy ´ch pokus˚ u o nalogov´an´ı tohoto uˇzivatele. • Hesla v /etc/shadow jsou uloˇzena takov´ ym zp˚ usobem, aby pokud se povede nˇekomu soubor z´ıskat, mˇel zt´ıˇzenou pr´aci pˇri odhadov´an´ı hesel. P˚ uvodn´ı 65
(cleartext) heslo se proˇzene jednosmˇernou kryptografickou funkc´ı (kter´a je nav´ıc parametrizovateln´ a, ˇc´ımˇz se v´ ypoˇcetn´ı a prostorov´a sloˇzitost pro u ´tok hrubou silou jeˇstˇe zv´ yˇs´ı) a uloˇz´ı se do /etc/shadow v t´eto formˇe. Ovˇeˇrov´an´ı hesla pak funguje tak ˇze, se na cleartext heslo aplikuje dan´a funkce s dan´ ymi parametry a v´ ysledek se porovn´a s polem hesla v /etc/shadow. Pokud jsou stejn´e, probˇehla autentizace u ´spˇeˇsnˇe. P˚ uvodnˇe navrˇzen´a funkce pˇrestala s rozvojem procesor˚ u staˇcit, takˇze se dnes pouˇz´ıvaj´ı funkce MD5, SHA1, Blowfish a dalˇs´ı. Poloˇzka hesla v /etc/shadow je pak vnitˇrnˇe strukturovan´a pomoc´ı speci´ aln´ıch znak˚ u, takˇze programy ovˇeˇruj´ıc´ı heslo v´ı, jakou funkci a s jak´ ymi parametry pouˇz´ıt. Vˇetˇsina syst´em˚ u umoˇzn ˇuje glob´aln´ı konfiguraci pouˇzit´ ych funkc´ı a jejich parametr˚ u. • existuj´ı i jin´e syst´emy, kter´e (nejen) pro autentizaci /etc/passwd nemus´ı v˚ ubec pouˇz´ıvat, napˇr´ıklad NIS (Network Information Service) nebo LDAP (Lightweight Directory Access Protocol). • skupina uˇzivatele uveden´ a v /etc/passwd se naz´ yv´a prim´ arn´ı. Tuto skupinovou identifikaci dostanou napˇr. soubory vytvoˇren´e procesy uˇzivatele. Dalˇs´ı skupiny, ve kter´ ych je uˇzivatel uveden v souboru /etc/group, jsou doplˇ nkov´e (supplementary) a rozˇsiˇruj´ı pˇr´ıstupov´a pr´ava uˇzivatele: skupinov´ y pˇr´ıstup je povolen ke vˇsem objekt˚ um, jejichˇz skupinov´ y vlastn´ık je roven bud’ prim´arn´ı, nebo jedn´e z doplˇ nkov´ ych skupin. • p˚ uvodnˇe mˇel v UNIXu kaˇzd´ y uˇzivatel vˇzdy aktivn´ı pouze jednu skupinovou identitu. Po nalogov´ an´ı byl ve sv´e prim´arn´ı skupinˇe, pro z´ısk´an´ı pr´av jin´e skupiny bylo tˇreba se do n´ı pˇrepnout pˇr´ıkazem newgrp (skupinov´a obdoba su, ˇr´ıd´ı se obsahem souboru /etc/group), kter´ y spustil nov´ y shell. • v novˇejˇs´ıch UNIXech nen´ı tˇreba pro pˇr´ıstup k soubor˚ um mˇenit prim´arn´ı skupinovou identitu procesu, pokud uˇzivatel patˇr´ı do potˇrebn´e skupiny. Zmˇena identity je nutn´ a, pouze kdyˇz chceme vytv´aˇret soubory s jinou skupinovou identitou, neˇz je prim´ arn´ı skupina uˇzivatele. Lok´alnˇe pro urˇcit´ y adres´aˇr toho lze dos´ ahnout nastaven´ım skupinov´eho vlastn´ıka adres´aˇre na poˇzadovanou skupinu a nastaven´ım bitu SGID v pˇr´ıstupov´ ych pr´avech adres´aˇre – to plat´ı pro syst´emy zaloˇzen´e na System V. U BSD staˇc´ı zmˇenit poˇzadovanou skupinu u adres´ aˇre. Pˇr´ıklad vytvoˇren´ı takov´eho adres´aˇre na Solarisu (pˇr´ıkaz chown mus´ı b´ yt proveden pod uˇzivatelem root): # mkdir mydir # chown :lidi mydir # ls -ald mydir drwxr-xr-x 2 root # cd mydir/ mydir # touch foo mydir # ls -ald foo -rw-r--r-1 root mydir # chmod g+s . mydir # ls -ald . drwxr-sr-x 2 root mydir # touch bar mydir # ls -ald bar -rw-r--r-1 root mydir #
lidi
4 Nov
2 20:01 mydir
root
0 Nov
2 20:01 foo
lidi
4 Nov
2 20:01 .
lidi
0 Nov
2 20:01 bar
66
• druh´ a poloˇzka v ˇr´ adc´ıch /etc/group obsahuje zak´odovan´e skupinov´e heslo pouˇz´ıvan´e pˇr´ıkazem newgrp, to se jiˇz dnes nepouˇz´ıv´a. Napˇr´ıklad na FreeBSD je pˇr´ıkaz newgrp pˇr´ıstupn´ y uˇz jen superuˇzivateli (kv˚ uli vol´an´ı setgroups).
Name service switch • dneˇsn´ı syst´emy nejsou omezeny na pouˇz´ıv´an´ı /etc/passwd a /etc/groups • syst´em pouˇz´ıv´ a datab´ aze (passwd, groups, protocols, . . . ) • data datab´ az´ı poch´ azej´ı ze zdroj˚ u (soubory, DNS, NIS, LDAP, ...) • soubor nsswitch.conf definuje jak´e datab´aze pouˇz´ıvaj´ı jak´e zdroje • knihovn´ı funkce toto samozˇrejmˇe mus´ı explicitnˇe podporovat • je moˇzn´e nˇekter´e zdroje kombinovat, napˇr´ıklad uˇzivatel se nejdˇr´ıve m˚ uˇze hledat v /etc/passwd a pot´e v NISu • poprv´e se objevilo v Solarisu, pot´e pˇrevzato dalˇs´ımi syst´emy
• syst´emy maj´ı typicky manu´ alovou str´anku nsswitch.conf(4), kde lze nal´ezt podrobnosti v z´ avislosti na konkr´etn´ım operaˇcn´ım syst´emu, vˇcetnˇe API, pomoc´ı kter´eho se pracuje s jednotliv´ ymi datab´azemi. Napˇr´ıklad, s datab´az´ı passwd pracuj´ı standardn´ı vol´an´ı getpwnam(3), getusernam(3) a dalˇs´ı - nen´ı proto potˇreba zpracov´ avat tyto soubory (datab´aze) sami. • zde je ˇc´ ast skuteˇcn´eho souboru nsswitch.conf ze stroje u-us: passwd: group:
files ldap files ldap
# You must also set up the /etc/resolv.conf file for DNS name # server lookup. See resolv.conf(4). hosts: files dns # Note that IPv4 addresses are searched for in all of the # ipnodes databases before searching the hosts databases. ipnodes: files dns networks: protocols: rpc: ethers:
files files files files 67
Z´ısk´ av´ an´ı u ´ daj˚ u o uˇ zivatel´ıch/skupin´ ach • struct passwd *getpwnam(const char *name) vrac´ı strukturu reprezentuj´ıc´ı uˇzivatele. • struct passwd *getpwuid(uid t uid) vrac´ı strukturu reprezentuj´ıc´ı uˇzivatele podle UID. • void setpwent(void) • void endpwent(void) • struct passwd *getpwent(void) slouˇz´ı k proch´ azen´ı poloˇzek v datab´azi uˇzivatel˚ u. setpwent nastav´ı pozici na zaˇc´ atek, getpwent z´ısk´a dalˇs´ı poloˇzku, endpwent dealokuje prostˇredky pouˇzit´e pro proch´azen´ı seznamu.
• tyto funkce funguj´ı nez´ avisle na tom jak z jak´e datab´aze byly z´ısk´any informace o dan´em uˇzivateli. • vˇsechny tyto funkce jsou souˇc´ast´ı POSIX 1003.1-2008 (sekce XSH) • setpwent je tˇreba zavolat pˇred prvn´ım vol´an´ım getpwent • analogicky exituj´ı funkce getgrnam a getgrent kter´e z´ısk´avaj´ı informace o skupin´ ach. • pro prohled´ av´ an´ı a v´ ypis databaz´ı lze pouˇz´ıt program getent. Napˇr. k nalezen´ı z´ aznamu uˇzivatele a skupiny root: $ getent passwd root root:x:0:0:Super-User:/root:/sbin/sh $ getent group root root::0:
68
Testov´ an´ı pˇ r´ıstupov´ ych pr´ av • uˇzivatel je identifikov´ an ˇc´ıslem uˇzivatele (UID) a ˇc´ısly skupin, do kter´ ych patˇr´ı (primary GID, supplementary GIDs). • tuto identifikaci dˇed´ı kaˇzd´ y proces dan´eho uˇzivatele. • soubor S m´ a vlastn´ıka (U IDS ) a skupinov´eho vlastn´ıka (GIDS ). • algoritmus testov´ an´ı pˇr´ıstupov´ ych pr´av pro proces P (U IDP , GIDP , SU P G) a soubor S(U IDS , GIDS ): Jestliˇze
pak P roces m´a v˚ uˇci Souboru
if(U IDP == 0)
. . . vˇsechna pr´ava
else if(U IDP == U IDS )
. . . pr´ava vlastn´ıka
else if(GIDP == GIDS || GIDS ∈ SU P G)
. . . pr´ava ˇclena skupiny . . . pr´ava ostatn´ıch
else
• procesy superuˇzivatele root mohou mˇenit svoji uˇzivatelskou a skupinovou identitu. Toho vyuˇz´ıv´ a napˇr. proces login, kter´ y bˇeˇz´ı jako root a po zkontrolov´ an´ı jm´ena a hesla spust´ı shell s uˇzivatelskou identitou (pomoc´ı vol´an´ı setuid – viz dalˇs´ı slajdy). • z algoritmu plyne, ˇze pro roota nen´ı relevantn´ı nastaven´ı pr´av (m´a vˇzdy neomezen´ y pˇr´ıstup). Pokud se shoduje uˇzivatel, nepouˇzij´ı se nikdy pr´ava skupiny nebo ostatn´ıch, i kdyˇz povoluj´ı v´ıce neˇz uˇzivatelsk´a pr´ava. Podobnˇe pr´ ava ostatn´ıch se nepouˇzij´ı, jestliˇze se shoduje skupinov´a identita. Tedy pokud m´ a m˚ uj soubor nastaveny pr´ ava ---rwxrwx, nemohu ho ˇ c´ıst, zapisovat ani spustit, dokud nastaven´ı pr´ av nezmˇ en´ım. • nˇekter´e syst´emy se odkl´ anˇej´ı od klasick´eho modelu, kdy mnoho proces˚ u bˇeˇzelo pod uˇzivatelem s UID 0 a pˇri bezpeˇcnostn´ı chybˇe v takov´e aplikaci ˇcasto u ´toˇcn´ık z´ıskal vl´ adu nad cel´ ym syst´emem a zav´adˇej´ı modely jako je least privilege model v Solarisu 10 nebo systrace v OpenBSD. • opakov´ an´ı z prvn´ıho roˇcn´ıku – aby uˇzivatel mohl smazat soubor, mus´ı m´ıt pr´ avo z´ apisu do dan´eho adres´ aˇ re, protoˇze to je ten “soubor”, co se mˇen´ı. Pr´ ava k mazan´ emu souboru nejsou podstatn´ a; to ˇze v´as shell upozorn´ı, ˇze maˇzete soubor, ke kter´emu nem´ate pr´avo z´apisu, je pouze vˇec toho shellu. Je to logick´e – pokud si nastav´ıte soubor jako read-only, shell usuzuje, ˇze ho asi norm´ alnˇe mazat nechcete. Viz pˇr´ıklad pod t´ımto odstavcem. Unixov´ e syst´ emy nemaj´ı delete-like operaci na soubor, smaz´an´ı souboru nastane automaticky tehdy, kdyˇz na soubor nen´ı ˇz´adn´ y odkaz z adres´aˇrov´e struktury, a nikdo soubor jiˇz nem´a otevˇren´ y.
69
$ whoami janp $ ls -ld janp-dir drwx-----2 janp staff 512 Mar 23 12:12 janp-dir/ $ ls -l janp-dir total 0 -rw-r--r-1 root root 0 Mar 23 12:11 root_wuz_here.txt $ rm janp-dir/root_wuz_here.txt rm: janp-dir/root_wuz_here.txt: override protection 644 (yes/no)? yes $ ls janp-dir/root_wuz_here.txt janp-dir/root_wuz_here.txt: No such file or directory • pokud ale root vytvoˇr´ı v adres´aˇri janp-dir sv˚ uj podadres´aˇr a tam vloˇz´ı sv˚ uj soubor, uˇzivatel janp uˇz nem˚ uˇze adres´aˇr janp-dir a jeho obsah smazat, protoˇze: – podadres´ aˇr nelze smazat protoˇze nen´ı pr´azdn´ y – a dan´ y soubor nelze smazat z toho d˚ uvodu, ˇze janp nen´ı vlastn´ıkem podadres´ aˇre. • existuje situace, kdy ani pr´ avo z´apisu (a execute) pro adres´aˇr nestaˇc´ı. To se pouˇz´ıv´ a u tmp adres´ aˇr˚ u, do kter´ ych m˚ uˇze kaˇzd´ y ps´at, ale nen´ı ˇz´adouc´ı situace, kdy by si uˇzivatel´e navz´ajem mazali soubory. K tomu se pouˇz´ıv´a tzv. sticky bit (01000). Syst´emy maj´ı vetˇsinou manu´alovou str´anku sticky, kde je funkce sticky bitu popsan´ a. Na v´ ypisu ls je oznaˇcovan´ y jako t: $ ls -ld /tmp drwxrwxrwt 7 root
root
515 Mar 23 12:22 /tmp
70
Re´ aln´ e a efektivn´ı UID/GID • u kaˇzd´eho procesu se rozliˇsuje: – re´ aln´ e UID (RUID) – skuteˇcn´ y vlastn´ık procesu – efektivn´ı UID (EUID) – uˇzivatel, jehoˇz pr´ava proces pouˇz´ıv´ a – uschovan´ e UID (saved SUID) – p˚ uvodn´ı efektivn´ı UID • podobnˇe se rozliˇsuje re´aln´e, efektivn´ı a uschovan´e GID procesu. • obvykle plat´ı RUID==EUID && RGID==EGID. • prop˚ ujˇ cov´ an´ı pr´ av . . . spuˇstˇen´ı programu s nastaven´ ym SUID (set user ID) bitem zmˇen´ı EUID a saved UID procesu na UID vlastn´ıka programu, RUID se nezmˇen´ı. • podobnˇe SGID bit ovlivˇ nuje EGID procesu. • pˇ ri kontrole pˇ r´ıstupov´ ych pr´ av se pouˇ z´ıvaj´ı vˇ zdy EUID, EGID a supplementary GIDs
• bity SUID a SGID se pouˇz´ıvaj´ı u program˚ u, kter´e potˇrebuj´ı vˇetˇs´ı pˇr´ıstupov´a pr´ ava, neˇz m´ a uˇzivatel, jenˇz je spouˇst´ı. Pˇr´ıkladem je program passwd, kter´ y mus´ı aktualizovat soubory /etc/passwd a /etc/shadow, kde ten prvn´ı nem˚ uˇze bˇeˇzn´ y uˇzivatel mˇenit a druh´ y z nich ani ˇc´ıst. Dalˇs´ı pˇr´ıklad je program su. Ten mus´ı m´ıt pr´ avo libovolnˇe zmˇenit uˇzivatelskou a skupinovou identitu, coˇz je privilegium proces˚ u s UID 0. • SUID a SGID programy by mˇely b´ yt peˇclivˇe naprogramov´any, aby dovolily pouze ty operace, pro kter´e jsou urˇceny, a neumoˇznily zneuˇz´ıt jejich privilegia pro neopr´ avnˇen´e akce (napˇr. spuˇstˇen´ı rootovsk´eho shellu). Zkuˇsenost ukazuje, ˇze tyto programy jsou jednou z nejˇcastˇejˇs´ıch pˇr´ıˇcin bezpeˇcnostn´ıch probl´em˚ u UNIXov´ ych syst´em˚ u. • z´ akladn´ım pravidlem pro SUID programy je: nepiˇ ste je pokud to nen´ı opravdu nezbytn´e. Je to typick´e m´ısto pro generov´an´ı bezpeˇcnostn´ıch chyb protoˇze dobˇre, tj. bezpeˇcnˇe, napsat sloˇzitˇejˇs´ı SUID program nen´ı jednoduch´e. • toto jsou pravidla pro zmˇ eny: – beˇzn´ y uˇzivatel nem˚ uˇze zmˇenit sv´e RUID nebo uschovan´e SUID (vyj´ımka je pˇri vol´ an´ı exec, viz strana 137) – proces m˚ uˇze vˇzdy zmˇenit sv´e EUID na to z RUID nebo z uschovan´eho UID. Toto zaruˇcuje, ˇze v SUID programu je moˇzn´e libovolnˇe mˇenit EUID mezi t´ım p˚ uvodn´ım kter´ ym proces z´ıskal pr´ava vlastn´ıka a mezi UID skuteˇcn´eho uˇzivatele kter´ y dan´ y proces spustil.
71
– root m˚ uˇ ze vˇ sechno, a kdyˇz zmˇen´ı RUID, tak se z´aroveˇ n zmˇen´ı i uchovan´e UID – nemˇelo by smysl mˇenit jen jedno z nich kdyˇz kter´ekoli m˚ uˇzete pouˇz´ıt pro nastaven´ı EUID.
Identifikace vlastn´ıka procesu • uid t getuid(void) vrac´ı re´ aln´e user ID volaj´ıc´ıho procesu. • uid t geteuid(void) vrac´ı efektivn´ı user ID volaj´ıc´ıho procesu. • gid t getgid(void) vrac´ı re´ aln´e group ID volaj´ıc´ıho procesu. • gid t getegid(void) vrac´ı efektivn´ı group ID volaj´ıc´ıho procesu. • int getgroups(int gidsz, gid t glist []) – do glist d´ a nejv´ yˇse gidsz supplementary group IDs volaj´ıc´ıho procesu a vr´ at´ı poˇcet vˇsech GIDs procesu.
• pro re´ aln´e UID je vol´ an´ı getuid, vol´an´ı getruid neexistuje • getgroups: kdyˇz gidsz == 0, jen vr´at´ı poˇcet skupin. Kdyˇz 0 < gidsz < #skupin, vr´ at´ı -1. • v UNIXu je mnoho typ˚ u jako uid_t, gid_t, size_t, apod. Vesmˇes jsou to celoˇc´ıseln´e typy, ˇcasto je najdete v /usr/include/sys/types.h • Solaris m´ a pˇr´ıkaz pcred, kter´ y jednoduˇse zobraz´ı informace o identifikaci procesu: $ pcred 5464 5464: e/r/suid=1993 e/r/sgid=110 groups: 33541 41331 110
72
Zmˇ ena vlastn´ıka procesu • int setuid(uid t uid ); – v procesu s EUID == 0 nastav´ı RUID, EUID i saved-SUID na uid – pro ostatn´ı procesy nastavuje jen EUID, a uid mus´ı b´ yt bud’ rovn´e RUID nebo uschovan´emu SUID • int setgid(gid t gid ); obdoba setuid, nastavuje group-IDs procesu. • int setgroups(int ngroups, gid t *gidset ) nastavuje supplementary GIDs procesu, m˚ uˇze b´ yt pouˇzito jen superuˇzivatelsk´ ym procesem.
• o nastaven´ı UID pro proces s EUID 0 viz tak´e pozn´amky na stranˇe 75. • co v´ yˇse uveden´e tedy znamen´a: proces s efektivn´ımi pr´avy superuˇzivatele m˚ uˇze libovolnˇe mˇenit identitu. Ostatn´ı procesory mohou pouze stˇr´ıdat sv´a re´ aln´ a a efektivn´ı pr´ ava. • program login vyuˇz´ıv´ a vol´ an´ı setuid • pokud chce process s UID == 0 zmˇenit svou identitu, mus´ı nejprve volat setgid a setgroups. Teprve pak lze zavolat setuid. Pˇri opaˇcn´em poˇrad´ı vol´ an´ı by proces po proveden´ı setuid uˇz nemˇel pr´ava na setgid a setgroups. • setgroups nen´ı uvedeno v UNIX 98 ani UNIX 03. • RUID/EUID jsou uloˇzen´e v z´aznamu tabulky proces˚ u pro pˇr´ısluˇsn´ y proces a z´ aroveˇ n v tzv. u-area (viz napˇr´ıklad [Bach]). EUID v tabulce proces˚ u se naz´ yv´ a jiˇz zm´ınˇen´e uschovan´e UID, neboli saved UID. Jak jiˇz bylo ˇreˇceno, uschovan´e UID se pouˇz´ıv´ a pro kontrolu, kdyˇz se proces chce vr´atit k EUID, se kter´ ym byl spuˇstˇen (po t´e, co doˇcasnˇe nastavil sv´e EUID na UID uˇzivatele, kter´ y proces spustil, tj. na RUID). • pokud tedy jako root vytvoˇr´ıte SUID program a v nˇem zavol´ate setuid pro jak´eholi UID mimo 0, jiˇz se v programu k EUID==0 nem˚ uˇzete vr´atit (je to logick´e – pˇredstavte si situaci, kdy se uˇzivatel loguje do syst´emu). V tom pˇr´ıpadˇe byste museli pouˇz´ıt vol´an´ı seteuid, kter´e nastavuje pouze EUID. • pˇr´ıklad: setuid/screate-file.c
73
Syst´ em soubor˚ u • adres´ aˇre tvoˇr´ı strom, spolu se soubory acyklick´ y graf (na jeden soubor m˚ uˇze existovat v´ıce odkaz˚ u). • kaˇzd´ y adres´ aˇr nav´ıc obsahuje odkaz na sebe ’.’ (teˇcka) a na nadˇrazen´ y adres´ aˇr ’..’ (dvˇe teˇcky). • pomoc´ı rozhran´ı syst´emu soubor˚ u se pˇristupuje i k dalˇs´ım entit´ am v syst´emu: – – – – – –
perifern´ı zaˇr´ızen´ı pojmenovan´e roury sokety procesy (/proc) pamˇet’ (/dev/mem, /dev/kmem) pseudosoubory (/dev/tty, /dev/fd/0,. . . )
• z pohledu j´ adra je kaˇzd´ y obyˇcejn´ y soubor pole bajt˚ u. • vˇsechny (i s´ıt’ov´e) disky jsou zapojeny do jednoho stromu.
• zaˇr´ızen´ı, soubory v /proc, termin´aly, pamˇet’ atd. jsou vˇsechno jeden typ soubor˚ u - speci´ aln´ı soubory. Dalˇs´ı typy soubor˚ u - regul´arn´ı soubor (hardlink), adres´ aˇr, pojmenovan´ a roura, socket, symbolick´ y link. • novˇe vytvoˇren´ y adres´ aˇr m´ a na sebe 2 odkazy – jeden z nadˇrazen´eho adres´aˇre, a jeden s´ am na sebe, ’.’: $ mkdir test $ ls -ld test drwx-----2 janp
staff
512 Mar 23 12:46 test
• root m˚ uˇze v nˇekter´ ych syst´emech strukturu adres´aˇr˚ u zacyklit, ale t´ım zmate utility pro proch´ azen´ı filesyst´emu; moc se cyklick´e struktury nepouˇz´ıvaj´ı. Symbolick´e linky na adres´ aˇre funguj´ı vˇsude. • pojmenovan´e roury (viz strana 100) lze pouˇz´ıt i mezi procesy, kter´e nejsou pˇr´ıbuzensky spˇr´ıznˇen´e. Jinak funguj´ı stejnˇe jako nepojmenovan´e roury. • zmiˇ novan´e sokety jsou v dom´enˇe UNIX, tj. slouˇz´ı pro komunikaci v r´amci jednoho syst´emu. Sokety z dom´eny INET, pˇres kter´e prob´ıh´a s´ıt’ov´a komunikace, se v syst´emu soubor˚ u neobjevuj´ı. S´ıt’ov´a komunikace zaˇc´ın´a na stranˇe 185. • debuggery pouˇz´ıvaj´ı pamˇet’ov´e obrazy proces˚ u dostupn´e v /proc. Ve vˇetˇsinˇe unix-like syst´em˚ u obsahuje podstrom /proc u ´daje o j´adru syst´emu a bˇeˇz´ıc´ıch procesech ve formˇe textov´ ych soubor˚ u. 74
• dneˇsn´ı modern´ı unixy m´ıvaj´ı speci´aln´ı filesyst´em devfs, jehoˇz obsah odr´aˇz´ı aktu´ aln´ı konfiguraci syst´emu co se t´ yˇce pˇripojen´ ych zaˇr´ızen´ı. Tj. napˇr. pˇri pˇripojen´ı USB sticku se v /dev objev´ı pˇr´ısluˇsn´e diskov´e zaˇr´ızen´ı. Po fyzick´em odpojen´ı zaˇr´ızen´ı odkaz z adres´aˇrov´e struktury opˇet zmiz´ı.
Jednotn´ y hierarchick´ y syst´ em soubor˚ u /
dev etc
usr
home tty
• svazek (angl. file system) je ˇc´ast souborov´eho syst´emu, kterou lze samostatnˇe vytvoˇrit, pˇripojit, zruˇsit... Kaˇzd´ y filesyst´em m˚ uˇze m´ıt jinou vnitˇrn´ı strukturu (s5, ufs, ext2, xfs, atd.) a m˚ uˇze b´ yt uloˇzen na lok´aln´ım disku nebo na jin´em poˇc´ıtaˇci a pˇr´ıstupn´ y po s´ıti (nfs, afs). • po startu j´ adra je pˇripojen´ y jen koˇrenov´ y filesyst´em, dalˇs´ı filesyst´emy se zapojuj´ı do hierarchie na m´ısta adres´aˇr˚ u pˇr´ıkazem mount. Tento pˇr´ıkaz je moˇzn´e spustit ruˇcnˇe (uˇzivatel root libovolnˇe, ostatn´ı pouze na nˇekter´ ych syst´emech a s omezen´ımi) nebo automaticky bˇehem inicializace syst´emu, kdy se typicky ˇr´ıd´ı obsahem souboru /etc/fstab. Pˇred zastaven´ım syst´emu se filesyst´emy odpojuj´ı pˇr´ıkazem umount. • dalˇs´ı moˇznost je pˇripojen´ı filesyst´emu na ˇz´adost pˇri prvn´ım pˇr´ıstupu a jeho odpojen´ı po urˇcit´e dobˇe neˇcinnosti. Tuto funkci zajiˇst’uje d´emon automounter (autofs, automount, amd). • UNIX nem´ a ˇz´ adn´e A, B, C, D. . . disky apod.
75
Typick´ a skladba adres´ aˇ r˚ u /bin
...
z´ akladn´ı syst´emov´e pˇr´ıkazy
/dev
...
speci´aln´ı soubory (zaˇr´ızen´ı, devices)
/etc
...
konfiguraˇcn´ı adres´aˇr
/lib
...
z´ akladn´ı syst´emov´e knihovny
/tmp
...
veˇrejn´ y adres´aˇr pro doˇcasn´e soubory
/home
...
koˇren domovsk´ ych adres´aˇr˚ u
/var/adm
...
administrativn´ı soubory (ne na BSD)
/usr/include
...
hlaviˇckov´e soubory pro C
/usr/local
...
lok´alnˇe instalovan´ y software
/usr/man
...
manu´alov´e str´anky
/var/spool
...
spool (poˇsta, tisk,...)
• v /bin, /lib, /sbin jsou pˇr´ıkazy a knihovny potˇrebn´e pˇri startu syst´emu, kdy je pˇripojen pouze koˇrenov´ y filesyst´em. Ostatn´ı pˇr´ıkazy a knihovny jsou typicky v /usr/bin, /usr/lib a /usr/sbin. /usr b´ yv´a ˇcasto samostatn´ y filesyst´em, ˇc´ımˇz jeho obsah nen´ı dostupn´ y bˇehem startu syst´emu. • s v sbin znaˇc´ı ,,system”, ne SUID. Jsou to bin´arky, u kter´ ych se pˇredpokl´ad´a, ˇze je bˇeˇzn´ y uˇzivatel nebude typicky potˇrebovat. • podstrom /usr obsahuje soubory, kter´e se nemˇen´ı pˇri bˇeˇzn´em provozu a nejsou z´ avisl´e na konkr´etn´ım poˇc´ıtaˇci. Proto by mˇel j´ıt sd´ılet read-only. Na sv´e stanici doma ho ale vˇetˇsinou budete m´ıt read-write. • /lib typicky obsahuje knihovny potˇrebn´e pro programy z koˇrenov´eho svazku. Pokud by vˇsechny knihovny byly v /usr/lib a /usr byl samostatn´ y svazek, probl´em s pˇripojen´ım /usr by kompletnˇe ochromil cel´ y syst´em, protoˇze by neˇslo spustit nic. Nˇekdy to syst´emy ˇreˇs´ı tak, ˇze koˇrenov´ y svazek obsahuje sadu z´ akladn´ıch program˚ u, kter´e jsou staticky slinkovan´e. Napˇr´ıklad FreeBSD m´a takov´e bin´ arky v adres´ aˇri /rescue, m´a ale i samostatn´e adres´aˇre /lib a /usr/lib. • v podstromu /var jsou data, kter´a se za provozu mˇen´ı a jsou specifick´a pro kaˇzd´ y poˇc´ıtaˇc. • r˚ uzn´e syst´emy i instalace jednoho syst´emu se ˇcasto liˇs´ı. • hier(7) na FreeBSD a Linuxu popisuje adres´aˇrovou hierarchii tohoto syst´emu, Solaris m´ a filesystem(5).
76
Pˇ r´ıstup k perifern´ım zaˇ r´ızen´ım • adres´ aˇr /dev obsahuje speci´aln´ı soubory zaˇr´ızen´ı. Proces otevˇre speci´ aln´ı soubor syst´emov´ ym vol´an´ım open() a d´ale komunikuje se zaˇr´ızen´ım pomoc´ı vol´an´ı read(), write(), ioctl(), apod. • speci´ aln´ı soubory se dˇel´ı na – znakov´ e . . . data se pˇren´ aˇs´ı pˇr´ımo mezi procesem a ovladaˇcem zaˇr´ızen´ı, napˇr. s´eriov´e porty – blokov´ e . . . data proch´ az´ı syst´emovou vyrovn´ avac´ı pamˇet´ı (buffer cache) po bloc´ıch pevnˇe dan´e velikosti, napˇr. disky
• speci´ aln´ı soubor identifikuje zaˇr´ızen´ı dvˇema ˇc´ısly – hlavn´ı (major) ˇ c´ıslo . . . ˇc´ıslo ovladaˇce v j´ adru – vedlejˇ s´ı (minor) ˇ c´ıslo . . . ˇc´ıslo v r´ amci jednoho ovladaˇce
• vyrovn´ avac´ı pamˇeti (cache) urychluj´ı perifern´ı operace. Pˇri ˇcten´ı se data hledaj´ı nejprve v bufferu. Teprve kdyˇz nejsou k dispozici, tak se ˇctou z disku. Pˇri pˇr´ıˇst´ım ˇcten´ı stejn´eho bloku jsou data v bufferu. Pˇri z´apisu se data uloˇz´ı do bufferu. Na disk je syst´em pˇrep´ıˇse pozdˇeji. Lze si vynutit i okamˇzit´ y z´apis dat na disk. Dnes jiˇz cache typicky nen´ı samostatn´a struktura, vˇse je zastˇreˇseno modulem vitru´ aln´ı pamˇeti. • disky v UNIXu jsou obvykle pˇr´ıstupn´e pˇres znakov´e (pouˇz´ıvan´e pˇri mkfs – vytvoˇren´ı svazku – a fsck – kontrola konzistence) i blokov´e rozhran´ı (pouˇz´ıvan´e pˇri norm´ aln´ım provozu syst´emu soubor˚ u). Nˇekter´e syst´emy (FreeBSD) ale uˇz v /dev v˚ ubec soubory pro blokov´a zaˇr´ızen´ı nemaj´ı, pouze znakov´a. • dˇr´ıve musel administr´ ator syst´emu po zmˇenˇe hardwarov´e konfigurace upravit obsah adres´ aˇre /dev skriptem MAKEDEV nebo ruˇcnˇe. Dnes (Linux, IRIX, FreeBSD, Solaris, . . . ) jiˇz speci´ aln´ı soubory dynamicky vznikaj´ı a zanikaj´ı podle toho, jak j´ adro detekuje pˇrid´ an´ı nebo odebr´an´ı hardwarov´ ych komponent (viz devfs na stranˇe 79). • okamˇzit´ y z´ apis na disk lze vynutit pˇres O DIRECT command ve vol´an´ı fcntl.
77
Fyzick´ e uloˇ zen´ı syst´ emu soubor˚ u • syst´ em soubor˚ u (svazek, filesystem) lze vytvoˇrit na: – odd´ılu disku (partition) – ˇca ´st disku, na jednom disku m˚ uˇze b´ yt v´ıce odd´ıl˚ u – logick´ em odd´ılu (logical volume) – takto lze spojit v´ıce odd´ıl˚ u, kter´e mohou b´ yt i na nˇekolika disc´ıch, do jednoho svazku.
• dalˇs´ı moˇznosti: striping, mirroring, RAID /home
/
disk 1
/usr
disk 2
disk 3
• v´ yraz syst´em soubor˚ u se pouˇz´ıv´a v nˇekolika v´ yznamech: – jeden filesyst´em, tj. to, co vyrob´ı pˇr´ıkaz mkfs – cel´ a hierarchie pˇripojen´ ych svazk˚ u v syst´emu (v´ ystup pˇr´ıkazu mount) – zp˚ usob organizace svazku (tj. typ fs) a tomu odpov´ıdaj´ıc´ı modul j´adra, kter´ y s daty manipuluje (UFS2, Ext3, XFS, ...) • striping je od slova stripe, ne strip; podle toho se tak´e vyslovuje. Znamen´a, ˇze za sebou n´ asleduj´ıc´ı bloky dat se ukl´adaj´ı paralelnˇe na r˚ uzn´e disky a t´ım se zvyˇsuje pˇrenosov´ a rychlost. • mirroring ukl´ ad´ a kopie dat v´ıce disk˚ u pro pˇr´ıpad hav´arie nˇekter´eho z nich. • paritn´ı disky: data se ukl´ adaj´ı na dva disky, na tˇret´ı se ukl´ad´a XOR prvn´ıch dvou, po hav´ arii libovoln´eho disku jsou vˇsechna data st´ale ˇciteln´a. • jednotliv´e u ´rovnˇe RAID (Redundant Array of Inexpensive Disks) zahrnuj´ı striping, mirroring a vyuˇzit´ı paritn´ıch disk˚ u. • na terminologii je tˇreba d´ at velk´ y pozor. Napˇr. to, co se v DOS svˇetˇe naz´ yv´a partition, se v BSD naz´ yv´ a slice. Tam jsou pak partitions definov´any v r´amci jednoho slice a v nich se vytv´aˇrej´ı filesyst´emy. • pokud v´ as to zaj´ım´ a nebo se s t´ım setk´av´ate v praxi, doporuˇcuji vaˇs´ı pozornosti ZFS, coˇz je filesyst´em a manaˇzer logick´ ych odd´ıl˚ u v jednom. Ze Solarisu se jiˇz dostal do FreeBSD od verze 7.0. M˚ uˇzete okamˇzitˇe zapomenout na jednotliv´e disky, co je d˚ uleˇzit´e je pouze celkov´a diskov´a kapacita v syst´emu.
78
Organizace syst´ emu soubor˚ u s5 blok ˇc. 0 zav´adˇec´ı blok (boot block) 1 superblok 2 oblast i-uzl˚ u (i-nodes)
0
9 12
... oblast datov´ych blok˚ u
• p˚ uvodn´ı UNIXov´ y syst´em soubor˚ u standardnˇe pouˇz´ıvan´ y do verze System V Release 3; v BSD se prim´ arnˇe pouˇz´ıval do verze 4.1 • vlastnosti: – bloky d´elky 512, 1024 nebo 2048 bajt˚ u – jedin´ y (neduplikovan´ y) superblok – datov´ y prostor pevnˇe rozdˇelen´ y na oblast i-uzl˚ u a oblast datov´ych blok˚ u – pˇri velikosti bloku 1024 bajt˚ u byla teoretick´a velikost filesyst´emu pˇres 16 GB • boot block – pro uloˇzen´ı zavadˇeˇce OSu • superblok – z´ akladn´ı informace o svazku: poˇcet blok˚ u pro i-uzly, poˇcet blok˚ u svazku, seznam voln´ ych blok˚ u (pokraˇcuje ve voln´ ych bloc´ıch), seznam voln´ ych i-uzl˚ u (po vyˇcerp´ an´ı se prohled´av´a tabulka i-uzl˚ u), z´amky pro seznamy voln´ ych blok˚ u a i-uzl˚ u, pˇr´ıznak modifikace pouˇz´ıvan´ y pro kontrolu korektn´ıho odpojen´ı svazku, ˇcas posledn´ı aktualizace, informace o zaˇr´ızen´ı • i-uzel – typ souboru, pˇr´ıstupov´a pr´ava, vlastn´ık, skupina, ˇcasy posledn´ıho pˇr´ıstupu, modifikace dat a modifikace i-uzlu, poˇcet odkaz˚ u na soubor, velikost souboru, 10 odkaz˚ u na datov´e bloky a 3 odkazy na nepˇr´ım´e bloky. Pozor na to, ˇze ˇcas vytvoˇren´ı souboru nen´ı uloˇzen. • maxim´ aln´ı velikost souboru: 2113674 blok˚ u, tj. pˇribliˇznˇe 1 GB pˇri pouˇzit´ı blok˚ u velikosti 512 B 79
• jm´ena soubor˚ u – max. 14 znak˚ u (14 + 2 = 16, tedy mocnina dvou a tedy bezprobl´emov´e uloˇzen´ı adres´aˇrov´ ych poloˇzek do blok˚ u) • pˇri pouˇzit´ı tohoto filesyst´emu byla v´ ykonnost disk˚ u vyuˇzita jen cca na 2% a rychlost ˇcten´ı byla v ˇr´ adu jen nˇekolika des´ıtek kilobajt˚ u za sekundu (!!!) • pro srovn´ an´ı – MS-DOS 2.0 z roku 1983 podporoval pouze FAT12, poˇc´ıtaj´ıc´ı s maxim´ aln´ı velikost´ı filesyst´emu 16 MB. Velikost svazku do 2 GB byla umoˇznˇena aˇz ve verzi 4.0 (1988); tato verze z´aroveˇ n zavedla diskov´e vyrovn´ avac´ı pamˇeti, tedy to, co UNIX m´a od sv´eho vzniku v roce 1970.
Navigace v adres´ aˇ rov´ e struktuˇ re /etc/passwd
i-nodes i-node 2
data
i-node 37
i-node 71
. ..
2 2
. ..
37 2
etc
37
passwd
71
root:x:0:...
• kdyˇz cesta zaˇc´ın´ a znakem ’/’, zaˇc´ın´a navigace v koˇrenov´em adres´aˇri, jinak zaˇcne v pracovn´ım adres´ aˇri procesu. • koˇrenov´ y adres´ aˇr m´ a typicky ˇc´ıslo 2. 0 je pro oznaˇcen´ı pr´azdn´eho uzlu a 1 byla dˇr´ıve pouˇz´ıvan´ a pro soubor, do kter´eho se vkl´adaly vadn´e bloky, aby je syst´em uˇz d´ ale nepouˇz´ıval. • cesta ve kter´e je v´ıce lom´ıtek za sebou je st´ale platn´a, tj. ///a///b///c je ekvivaletn´ı /a/b/c.
80
Linky hard link /var password
origin´al /etc 20
... 0
i-nodes
passwd
20
passwd
... ...
31
... ...
20 root:x:0:...
data
symbolick´y link /usr
31 ../etc/passwd
Hard linky lze vytv´ aˇret pouze v r´amci jednoho (logick´eho) filesyst´emu.
• to co “norm´ alnˇe” vid´ıte pˇri v´ ypisu adres´aˇr˚ u jsou vˇetˇsinou hardlinky, takˇze rozdˇelen´ı nen´ı na soubory, hardlinky a symbolick´e linky. Co se t´ yˇce soubor˚ u, jsou pouze hardlinky a symlinky. hardlink – – – – –
odkaz na stejn´ y i-uzel vlastnˇe druh´e jm´eno souboru nen´ı rozd´ıl mezi origin´alem a hardlinkem lze vytv´ aˇret jen v r´amci filesyst´emu nelze vytv´ aˇret pro adres´aˇre
symbolick´ y link (symlink, softlink) – pouze odkaz na skuteˇcnou cestu k souboru jin´eho typu (ls -l jej oznaˇcuje ’l’), tj. symbolick´ y link je typem odliˇsn´ y od bˇeˇzn´eho souboru a jeho data obsahuj´ı obyˇcejn´ y ˇretˇezec – jm´eno cesty, at’ jiˇz relativn´ı nebo absolutn´ı – odliˇsn´e chov´ an´ı pro origin´al a link (napˇr. pˇri maz´an´ı) – pozor na relativn´ı a absolutn´ı cesty pˇri pˇresouv´an´ı symbolick´eho linku – m˚ uˇze ukazovat i na adres´aˇr nebo na neexistuj´ıc´ı soubor • nejjednoduˇsˇs´ı zp˚ usob jak si ovˇeˇrit, zda dan´e dva linky ukazuj´ı na stejn´ y soubor na disku je pouˇz´ıt -i pˇrep´ınaˇc pˇr´ıkazu ls, kter´ y v´am uk´aˇze ˇc´ıslo indexov´eho uzlu.
81
$ ls -i /etc/passwd 172789 /etc/passwd
Vylepˇ sen´ı syst´ emu soubor˚ u • c´ıl: sn´ıˇzen´ı fragmentace soubor˚ u, omezen´ı pohybu hlav disku um´ıstˇen´ım i-uzl˚ u a datov´ ych blok˚ u bl´ıˇz k sobˇe • UFS (Unix File System), p˚ uvodnˇe Berkeley FFS (Fast File System) • ˇclenˇen´ı na skupiny cylindr˚ u, kaˇzd´a skupina obsahuje – kopii superbloku – ˇr´ıdic´ı blok skupiny – tabulku i-uzl˚ u – bitmapy voln´ ych i-uzl˚ u a datov´ ych blok˚ u – datov´e bloky • bloky velikosti 4 aˇz 8 kB, menˇs´ı ˇc´asti do fragment˚ u blok˚ u • jm´ena dlouh´ a 255 znak˚ u
• superblok v kaˇzd´e cylinder skupinˇe posunut tak, aby superbloky nebyly na stejn´e plotnˇe. • dalˇs´ı typy filesyst´em˚ u: UFS2, Ext3, ReiserFS, XFS, ZFS aj. • v http://mff.devnull.cz/pvu/common/docs/filesystems.ps je porovn´an´ı osmi r˚ uzn´ ych filesyst´em˚ u podle r˚ uzn´ ych implementaˇcn´ıch krit´eri´ı; nezahrnuje v sobˇe ale v´ yvoj posledn´ıch let. • UFS byl st´ ale 32-bitov´ y, coˇz se odr´aˇzelo na maxim´aln´ı d´elce souboru i na maxim´ aln´ı velikosti filesyst´emu. (Oznaˇcen´ı file syst´emu jako 32-bitov´eho znamen´ a, ˇze ˇc´ısla i-uzl˚ u jsou reprezentov´ana jako 32-bitov´a. To pak d´av´a teoretick´ y limit pro velikost filesyst´emu.) • ˇzurn´ alov´ an´ı (XFS, Ext3, ReiserFS) – snaha o zmenˇsen´ı nebezpeˇc´ı ztr´aty dat v pˇr´ıpadˇe hav´ arie, urychlen´ı zotaven´ı po hav´arii • ZFS – modern´ı 128-bitov´ y filesyst´em vyvinut´ y v Sun Microsystems, od Solarisu 10, ted’ jiˇz tak´e v FreeBSD 7.
82
V´ yvoj ve spr´ avˇ e adres´ aˇ rov´ ych poloˇ zek • maxim´ aln´ı d´elka jm´ena souboru 14 znak˚ u nebyla dostaˇcuj´ıc´ı • FFS – d´elka aˇz 255; kaˇzd´a poloˇzka z´aroveˇ n obsahuje i jej´ı d´elku • nov´e filesyst´emy pouˇz´ıvaj´ı pro vnitˇrn´ı strukturu adres´aˇr˚ u r˚ uzn´e varianty B-strom˚ u – v´ yraznˇe zrychluje pr´aci s adres´aˇri obsahuj´ıc´ı velk´e mnoˇzstv´ı soubor˚ u – XFS, JFS, ReiserFS, . . . • UFS2 zav´ ad´ı zpˇetnˇe kompatibiln´ı tzv. dirhash pro zrychlen´ı pˇr´ıstupu k adres´ aˇr˚ um s velk´ ym poˇctem soubor˚ u
• dirhash pracuje tak, ˇze pˇri prvn´ım pˇreˇcten´ı adres´aˇre se vytvoˇr´ı v pamˇeti hash struktura, n´ asledn´e pˇr´ıstupy do adres´aˇre jsou pak srovnateln´e se syst´emy pouˇz´ıvaj´ıc´ı B-stromy. T´ımto zp˚ usobem je dosaˇzeno zlepˇsen´ı bez toho, aby se mˇenila struktura filesyst´emu na disku. Takov´a vylepˇsen´ı ale nelze v implementaci filesys´emu dˇelat do nekoneˇcna, nakonec je typicky nutn´a zmˇena on-disk form´ atu pro adaptaci na v´ ykonostn´ı poˇzadavky (nebo pˇrechod na jin´ y filesyst´em). • mal´e soubory se ˇcasto ukl´ adaj´ı v i-nodech, ˇc´ımˇz se uˇsetˇr´ı dalˇs´ı pˇr´ıstupy na disk
83
Virtu´ aln´ı syst´ em soubor˚ u (Virtual File System) struct struct struct struct struct file file file file file *file f vnode f vnode f vnode f vnode VNODE
VNODE
VNODE
VNODE
VNODE
v data INODE
v data INODE
v data INODE
v data INODE
v data INODE s realvp
ufs vnodeops ufs file system
s5 vnodeops s5 file system
spec vnodeops spec file system
• FFS uveden´ y ve 4.2BSD byl historicky druh´ y unixov´ y filesyst´em. Nˇekteˇr´ı dodavatel´e unixov´ ych syst´em˚ u ho zaˇcali preferovat vzhledem k jeho lepˇs´ımu v´ ykonu a nov´ ym moˇznostem, jin´ y z˚ ust´avali d´ale u s5fs z d˚ uvodu zpˇetn´e kompatibility. To d´ ale prohlubovalo probl´em jiˇz tak nedostateˇcn´e interoperability mezi r˚ uzn´ ymi unixov´ ymi syst´emy. Nˇekter´ ym aplikac´ım nav´ıc plnˇe nevyhovoval ani jeden z tˇechto filesyst´em˚ u. Postupnˇe se tak´e objevovala potˇreba pracovat s ne-unixov´ ymi filesyst´emy, napˇr. FAT. A s rostouc´ı popularitou poˇc´ıtaˇcov´ ych s´ıt´ı se zvyˇsovala popt´avka po sd´ılen´ı soubor˚ u mezi jednotliv´ ymi syst´emy, coˇz mˇelo za n´ asledek vznik distribuovan´ ych filesyst´em˚ u – napˇr. NFS (Network File System). • vzhledem k v´ yˇse popsan´e situaci bylo jen ot´azkou ˇcasu, kdy dojde k fundament´ aln´ım zmˇen´ am v infrastruktuˇre syst´emu soubor˚ u, aby souˇcasnˇe podporoval v´ıce typ˚ u filesyst´em˚ u. Vzniklo nˇekolik r˚ uzn´ ych implementac´ı od r˚ uzn´ ych v´ yrobc˚ u, aˇz se nakonec de facto standardem stala VFS/vnode architektura od firmy Sun Microsystems. V dneˇsn´ı dobˇe prakticky vˇsechny unixov´e a unixlike syst´emy podporuj´ı VFS, i kdyˇz ˇcasto se vz´ajemnˇe nekompatibiln´ımi u ´pravami. VFS se poprv´e objevilo v roce 1985 v Solarisu 2.0; brzy bylo pˇrevzato BSD – FFS s podporou VFS se zaˇcal naz´ yvat UFS. • hlavn´ı myˇslenka: kaˇzd´emu otevˇren´emu souboru v syst´emu pˇr´ısluˇs´ı struktura file; to by vlastnˇe byl jeden slot v n´ami jiˇz zn´am´e syst´emov´e tabulce otevˇren´ ych soubor˚ u. Ta ukazuje na vnode (virtual node). Vnode obsahuje ˇc´ ast nez´ avislou na konkr´etn´ım syst´emu soubor˚ u a ˇc´ast z´avislou, coˇz m˚ uˇze b´ yt napˇr´ıklad struktura inode. Ta je specifick´a pro kaˇzd´ y typ souborov´eho syst´emu. Kaˇ zd´ y typ filesyst´ emu implementuje pevnˇ e danou
84
sadu funkc´ı pro jednotliv´ e operace nad soubory, na kterou se odkazuj´ı vˇsechny virtu´ aln´ı uzly odpov´ıdaj´ıc´ı dan´emu typu filesyst´emu. Tato sada funkc´ı tedy definuje vnode interface. Kdyˇz tedy zavol´ate napˇr´ıklad open, j´ adro zavol´ a pˇr´ısluˇsnou implementaci v z´avislosti na typu filesyst´emu (napˇr. z modulu ext2fs). Implementaˇcnˇe z´avisl´a ˇc´ast struktury vnode je pˇr´ıstupn´a pouze z funkc´ı pˇr´ısluˇsn´eho typu filesyst´emu; j´adro do n´ı tedy ,,nevid´ı” pˇr´ımo. Jak uvid´ıte na dalˇs´ım slajdu, existuje jeˇstˇe jedna sada funkc´ı, kter´a se t´ yka pr´ ace s filesyst´emy jako takov´ ymi. Ta pak definuje VFS interface. Tyto dvˇ e sady spoleˇ cnˇ e tvoˇr´ı vnode/VFS rozhran´ı, kter´emu se bˇeˇznˇe ˇr´ık´a jen VFS. • (nebudu zkouˇset) – u speci´ aln´ıch soubor˚ u je situace sloˇzitˇejˇs´ı, v SVR4 struktura file ukazuje na snode (shadow-special-vnode), kter´ y definuje operace se zaˇr´ızen´ım (pomoc´ı souborov´eho syst´emu spec) a prostˇrednictv´ım ukazatele aln´ y vnode pro operace se speci´aln´ım souborem; s realvp se odkazuje na re´ ten je potˇreba napˇr´ıklad pro kontrolu pr´av pˇr´ıstupu. Kaˇzd´emu zaˇr´ızen´ı m˚ uˇze odpov´ıdat v´ıce speci´ aln´ıch soubor˚ u, a tedy v´ıce snodes a pˇr´ısluˇsn´ ych re´aln´ ych vnodes. Vˇsechny takov´e snodes pro jedno zaˇr´ızen´ı maj´ı ukazatel s commonvp na jeden spoleˇcn´ y snode, to ale nen´ı na obr´azku zachyceno. Pˇri otevˇren´ı speci´ aln´ıho souboru se hled´ a v hash tabulce snodes otevˇren´ ych zaˇr´ızen´ı poloˇzka odpov´ıdaj´ıc´ı speci´ aln´ımu souboru (podle major a minor ˇc´ısla zaˇr´ızen´ı). Kdyˇz snode nen´ı nalezen, vytvoˇr´ı se nov´ y. Tento snode se pak pouˇz´ıv´a pˇri operac´ıch se zaˇr´ızen´ım. V´ıce viz napˇr´ıklad [Vahalia].
Hierarchie souborov´ ych syst´ em˚ u struct file f vnode
vfs mount list rootvfs VFS
v vfsp VNODE
VFS
vfs next vfs data
vfs data
root vnode super block
root vnode super block
vfs op
v op
vfs next
INODE
vnodeops vfs mountedhere
vfs op
vfs vnodecovered v vfsp
vfsops
VNODE
vfsops
v op INODE
vfssw[]
vsw vfsops
mount point in rootvfs
vnodeops
• struktura vfs obsahuje implementaˇcnˇe nez´avisl´e informace o filesyst´emu, nez´ avisl´e na konkr´etn´ım typu filesyst´emu, tedy podobnˇe jako vnode funguje 85
pro soubory. Tato struktura reprezentuje jeden konkr´etn´ı fyzick´ y filesyst´em, aktu´ alnˇe pˇrimontovan´ y do hierarchie soubor˚ u. V tomto v´azan´em seznamu tedy m˚ uˇze b´ yt v´ıce struktur stejn´eho typu souborov´eho syst´emu. • rootvfs – odkaz na root file system • vfsops – tabulka funkc´ı pro konkr´etn´ı typ syst´emu soubor˚ u • vfssw[] – pole odkaz˚ u na tabulky vfsops pro vˇsechny syst´emem podporovan´e typy filesyst´em˚ u, z tabulky se vyb´ır´a pˇri pˇripojov´an´ı svazku podle typu filesyst´emu zadan´eho pˇri vol´ an´ı mount • v vfsp – odkaz z vnode na filesyst´em (strukturu vfs), na kter´em leˇz´ı soubor reprezentovan´ y pomoc´ı vnode • v vfsmountedhere – pouze ve vnode, kter´ y reprezentuje mount point (adres´ aˇr, na kter´em je pˇripojen koˇren jin´eho filesyst´emu); odkazuje na strukturu vfs reprezentuj´ıc´ı pˇripojen´ y filesyst´em • v vnodecovered – odkaz na vnode adres´aˇre, na kter´em je filesyst´em pˇripojen
Otevˇ ren´ e soubory z pohledu j´ adra I. user file descriptor table file table : count 1 process A WRONLY
inode table
@ @ @ R count 1 @
Z Z Z Z
/etc/group
ZZ ~ count 2 3 RDONLY
Q
process B
- count 1
RDWR
Q QQ s count 3 : /etc/passwd
• toto je nejjednoduˇsˇs´ı pohled na tabulky v j´adˇre kter´e se t´ ykaj´ı soubor˚ u, je to pˇrevzato z [Bach]; dnes to je o nˇeco sloˇzitˇejˇs´ı, myˇslenka je ale poˇr´ad stejn´a. Realitˇe v´ıce se podobaj´ıc´ı obr´azek je na pˇr´ıˇst´ım slajdu. • kaˇzd´ y proces m´ a svoji tabulku souborov´ ych deskriptor˚ u (user file descriptor table)
86
• z t´eto tabulky je odkazovn´ ano na syst´emovou tabulku otevˇren´ ych soubor˚ u syst´emu (file table; ano, tato tabulka je pouze jedna). Zde je m´od otevˇren´ı souboru a tak´e aktu´ aln´ı pozice v souboru. • z tabulky otevˇren´ ych soubor˚ u je odkazov´ano do tabulky naˇcten´ ych inod˚ uv pamˇeti. Dnes jsou to tzv. vnodes – virtual nodes, ale to v t´eto chv´ıli nen´ı relevantn´ı. • tabulka otevˇren´ ych soubor˚ u syst´emu, kter´a vlastnˇe vytv´aˇr´ı o jednu u ´roveˇ n odkaz˚ u nav´ıc, je zde proto, aby r˚ uzn´e procesy mohly sd´ılet stejnou aktu´aln´ı pozici v souboru. • pˇri otevˇren´ı souboru pomoc´ı vol´an´ı open se vˇzdy alokuje nov´ y slot v tabulce deskriptor˚ u a tak´e v syst´emov´e tabulce otevˇren´ ych soubor˚ u (to je d˚ uleˇzit´e!). Sd´ılen´ı se pak v r´ amci jednoho procesu dos´ahne duplikac´ı deskriptor˚ u, kdy v´ıce deskriptor˚ u sd´ıl´ı stejn´ y slot v tabulce otevˇren´ ych soubor˚ u syst´emu nebo v pˇr´ıpadˇe r˚ uzn´ ych proces˚ u pak pomoc´ı vytvoˇren´ı nov´eho procesu pomoc´ı vol´ an´ı fork(), viz strana 144.
Otevˇ ren´ e soubory z pohledu j´ adra II. struct proc uf next
uf next
struct ufchunk
struct ufchunk
... 2 1 0
... 2 1 0
struct ufchunk
u proc
u first
struct user p cred
... 2 1 0 uf ofile[] struct cred
f cred
f next
struct file
struct file
struct file
struct file
struct file
VNODE
VNODE
VNODE
VNODE
f prev
VNODE
struct file *file
f vnode
• struktury proc a user vytv´ aˇr´ı j´adro pro kaˇzd´ y proces a drˇz´ı v nich sluˇzebn´ı informace o procesu. • struktura ufchunk obsahuje NFPCHUNK (obvykle 24) deskriptor˚ u soubor˚ u, po zaplnˇen´ı se alokuje dalˇs´ı ufchunk. • struktura file (otevˇren´ı souboru) obsahuje m´od souboru (otevˇren pro ˇcten´ı, z´ apis, atd.), poˇcet deskriptor˚ u, kter´e se na ni odkazuj´ı, ukazatel na vnode a
87
pozici v souboru. Jedno otevˇren´ı souboru m˚ uˇze b´ yt sd´ıleno v´ıce deskriptory, jestliˇze byl p˚ uvodn´ı deskriptor zkop´ırov´an, napˇr. vol´an´ım fork() nebo dup(). • struktura cred obsahuje uˇzivatelskou a skupinovou identitu procesu, kter´ y otevˇrel soubor. • jeden vnode odpov´ıdaj´ıc´ı jednomu souboru m˚ uˇze b´ yt sd´ılen nˇekolika strukturami file, pokud byl dan´ y soubor v´ıcekr´at otevˇren. • ne vˇsechny vnodes jsou asociov´any s tabulkou otevˇren´ ych soubor˚ u. Napˇr. pˇri spuˇstˇen´ı programu je potˇreba pˇristupovat do spustiteln´eho souboru a proto se alokuje vnode.
Oprava konzistence souborov´ eho syst´ emu • pokud nen´ı filesyst´em pˇred zastaven´ım syst´emu korektnˇe odpojen, mohou b´ yt data v nekonzistentn´ım stavu. • ke kontrole a opravˇe svazku slouˇz´ı pˇr´ıkaz fsck. Postupnˇe testuje moˇzn´e nekonzistence: – v´ıcen´ asobn´e odkazy na stejn´ y blok – odkazy na bloky mimo rozsah datov´e oblasti syst´emu soubor˚ u – ˇspatn´ y poˇcet odkaz˚ u na i-uzly – nespr´ avn´ a velikost soubor˚ u a adres´aˇr˚ u – neplatn´ y form´ at i-uzl˚ u – bloky kter´e nejsou obsazen´e ani voln´e – chybn´ y obsah adres´ aˇr˚ u – neplatn´ y obsah superbloku • operace fsck je ˇcasovˇe n´aroˇcn´a. • ˇzurn´ alov´e (napˇr. XFS v IRIXu, Ext3 v Linuxu) a transakˇcn´ı (ZFS) syst´emy soubor˚ u nepotˇrebuj´ı fsck.
• data se pˇrepisuj´ı na disky z vyrovn´avac´ıch pamˇet´ı se zpoˇzdˇen´ım. Uloˇzen´ı vˇsech vyrovn´ avac´ıch pamˇet´ı lze vynutit syst´emov´ ym vol´an´ım sync(). Periodicky vyrovn´ avac´ı pamˇeti ukl´ad´a zvl´aˇstn´ı syst´emov´ y proces (d´emon). • fsck kontroluje pouze metadata. pokud doˇslo k poruˇse dat samotn´ ych, nepozn´ a to, natoˇz aby s t´ım nˇeco mohl udˇelat. • zde je uk´ azka fsck na odpojen´ y filesyst´em: toor@shewolf:~# fsck /dev/ad0a ** /dev/ad0a ** Last Mounted on /mnt/flashcard ** Phase 1 - Check Blocks and Sizes ** Phase 2 - Check Pathnames
88
** ** ** 24
Phase 3 - Check Connectivity Phase 4 - Check Reference Counts Phase 5 - Check Cyl groups files, 8848 used, 12951 free (7 frags, 1618 blocks, 0.0% fragmentation)
Dalˇ s´ı zp˚ usoby zajiˇ stˇ en´ı konzistence filesyst´ emu • tradiˇcn´ı UFS – synchronn´ı z´apis metadat – aplikace vytv´ aˇrej´ıc´ı nov´ y soubor ˇcek´a na inicializaci inode na disku; tyto operace pracuj´ı rychlost´ı disku a ne rychlost´ı CPU – asynchronn´ı z´ apis ale ˇcastˇeji zp˚ usob´ı nekontistenci metadat • ˇreˇsen´ı probl´em˚ u s nekonzistenc´ı metadat na disku: – journalling – skupina na sobˇe z´avisl´ ych operac´ı se nejdˇr´ıve atomicky uloˇz´ı do ˇzurn´alu; pˇri probl´emech se pak ˇzurn´al m˚ uˇze “pˇrehr´ at” – bloky metadat se nejdˇr´ıve zap´ıˇs´ı do non-volatile pamˇeti – soft-updates – sleduje z´avislosti mezi ukazately na diskov´e struktury a zapisuje data na disk metodou write-back tak, ˇze data na disku jsou vˇzdy konzistentn´ı – ZFS je nov´ y filesyst´em v Solarisu, kter´ y pouˇz´ıv´a copy-on-write transakˇcn´ı model
• filesystem metadata = inodes, directories, free block maps • ext2 dokonce defaultnˇe pouˇz´ıv´a asynchronn´ı z´apis metadat a je pˇri pouˇz´ıt´ı synchronn´ıho z´ apisu v´ yraznˇe pomalejˇs´ı neˇz UFS • z´ avisl´e operace jsou napˇr´ıklad smaz´an´ı poloˇzky z adres´aˇre a smaz´an´ı diskov´eho inode. Pokud by se stalo, ˇze se nejdˇr´ıve smaˇze diskov´ y inode a pak teprve poloˇzka v adres´ aˇri, pˇri v´ ypadku mezi tˇemito dvˇemi operacemi vnik´a nekonzistence – link ukazuje na diskov´ y soubor, kter´ y neexistuje. Nen´ı probl´em se tomuto vyhnout pˇri synchronn´ım z´apisu metadat (v´ıme kdy a co zapisujeme, urˇcujeme tedy poˇrad´ı z´apisu), ale pˇri metodˇe write-back je jiˇz nutn´e ˇreˇsit z´ avislosti jednotliv´ ych blok˚ u na sebe, protoˇze pˇri klasick´e synchronizaci vyrovn´ avac´ıch pamˇet´ı na disk j´adro nezaj´ım´a, kter´ y blok se zap´ıˇse dˇr´ıv a kter´ y pozdˇeji. • ˇcasto jsou bloky na sobˇe z´ avisl´e ve smyˇcce. Soft updates dok´aˇze takovou smyˇcku rozb´ıt t´ım, ˇze provede roll-back a po z´apisu pak roll-forward • v´ ykon soft updates je srovnateln´ y v´ ykonu UFS filesyst´emu s asynchronn´ım z´ apisem metadat • teoreticky soft updates zaruˇcuj´ı, ˇze po rebootu nen´ı potˇreba pouˇz´ıt fsck, tj. ˇze filesyst´em bude v takov´em stavu, ˇze je moˇzn´e nabootovat. Je vˇsak nutn´e 89
pouˇz´ıt tzv. background fsck pro opravy nez´avaˇzn´ ych chyb – to je povaˇzov´ano st´ ale za velkou nev´ yhodu soft updates zvl´aˇstˇe s t´ım, jak rostou velikosti bˇeˇznˇe pouˇz´ıvan´ ych disk˚ u. Takovou chybou, kter´a nebr´an´ı nabootov´an´ı, ale je nutn´e ji odstranit je napˇr´ıklad blok, kter´ y je oznaˇcen jako pouˇzit´ y, ale ˇz´adn´ y soubor ho nepouˇz´ıv´ a. • soft updates nejsou vˇzdy doporuˇcov´any pro root filesyst´em. Probl´em je to, ˇze ztr´ ata metadat na root filesyst´emu (viz 30-ti sekundov´a perioda z´apisu) m˚ uˇze b´ yt v´ yraznˇe vˇetˇs´ı hrozbou zde neˇz na /usr, /home atd. Dalˇs´ı nev´ yhodou m˚ uˇze b´ yt i to, ˇze u soft updates mi smaz´an´ı velk´eho souboru hned neuvoln´ı m´ısto. • pˇr´ıklad: na bezpeˇcnou operaci rename potˇrebuju pˇri synchronn´ım z´apisu metadat 4 z´ apisy – zv´ yˇsen´ı poˇctu odkaz˚ u v inode, vytvoˇren´ı nov´e adres´aˇrov´e poloˇzky, smaz´ an´ı star´e, a opˇetn´e sn´ıˇzen´ı poˇctu odkaz˚ u v inode. Kdykoli by syst´em spadnul, nenastane nebezpeˇcn´a situace. Napˇr´ıklad 2 odkazy z adres´ aˇr˚ u na inode s referenˇcn´ım poˇctem 1 je probl´em, protoˇze po zruˇsen´ı jednoho odkazu to bude vypadat, ˇze soubor je st´ale na disku, kdyˇz uˇz byla jeho data d´ avno smaz´ ana. Nen´ı teˇzk´e si asi pˇredstavit, co by to mohlo znamenat v pˇr´ıpadˇe, ˇze soubor obsahoval opravdu d˚ uleˇzit´a data – napˇr´ıklad z´alohu. Opaˇcn´ a situace, tj. jeden odkaz na inode s referenc´ı 2 sice tak´e nen´ı spr´avn´a situace, ale neohroˇzuje to moˇznost filesyst´em namontovat a norm´alnˇe pouˇz´ıvat. V nejhorˇs´ım se stane, ˇze soubor vypad´a ˇze jiˇz na disku nen´ı a pˇritom st´ale existuje. U soft updates operace rename vytvoˇr´ı kruˇznici, protoˇze nejdˇr´ıve je potˇreba zapsat zv´ yˇsen´ı poˇctu referenc´ı, pak adres´aˇrov´e bloky a pot´e sn´ıˇzen´ı referenc´ı. A protoˇze zv´ yˇsen´ı/sn´ıˇzen´ı se t´ yk´a stejn´eho inode, tak je pˇri z´apisu tˇreba udˇelat roll-back na (ˇreknˇeme) 2, zapsat inode na disk, zapsat bloky adres´ ar˚ u a pak roll-forward na poˇcet referenc´ı 1. Pˇri t´eto akci je nad inodem drˇzen z´ amek, takˇze nikdo nenaˇcte starˇs´ı data. Je jednoduch´e uk´azat, ˇze nelze zapsat ˇz´ adn´ y z tˇech tˇr´ı blok˚ u v kruˇznici tak, jak to je na konci operace rename, ˇze je opravdu nutn´ y ten roll-back – mohli bychom uvaˇzovat, ˇze inode se vlastnˇe nezmˇenil a nen´ı tˇreba ˇreˇsit zda se m˚ uˇze/nem˚ uˇze zapsat; z´apis nov´eho adres´ aˇrov´eho odkazu bez zv´ yˇsen´ı poˇctu odkaz˚ u v inodu by n´as totiˇz mohl dostat pˇresnˇe do situace, kter´a je pops´ana o p´ar ˇr´adk˚ u v´ yˇse.
90
Pˇ r´ıstupov´ a pr´ ava vlastn´ık (u) skupina (g) ostatn´ı (o) z }| { z }| { z }| { nejvyˇsˇs´ı bit
4 2
suid sgid sticky
1
r w x
• SGID pro soubor bez pr´ava spuˇstˇen´ı pro skupinu v System V: kontrola z´ amk˚ u pˇri kaˇzd´em pˇr´ıstupu (mandatory locking) • sticky bit pro adres´ aˇre: pr´avo mazat a pˇrejmenov´avat soubory maj´ı jen vlastn´ıci soubor˚ u • SGID pro adres´ aˇr: nov´e soubory budou m´ıt stejnou skupinu jako adres´ aˇr (System V; u BSD syst´em˚ u to funguje jinak, viz pozn´ amky)
• SGID pro adres´ aˇre u BSD syst´em˚ u zp˚ usob´ı, ˇze soubory a podadres´aˇre vytvoˇren´e v tomto adres´ aˇri budou m´ıt stejn´eho majitele jako je majitel dan´eho adres´ aˇre. Nutn´ ym pˇredpokladem je d´ale to, ˇze dan´ y UFS filesyst´em mus´ı b´ yt namontov´ an s suiddir pˇr´ıznakem a v j´adru je option SUIDDIR (a to nen´ı default). Nav´ıc to nefunguje pro roota. Tato moˇznost existuje kv˚ uli Sambˇe a Nettalku. • sticky bit pro adres´ aˇre: pˇrejmenovat nebo smazat soubor m˚ uˇze jen jeho vlastn´ık (v nˇekter´ ych implementac´ıch staˇc´ı i pr´avo z´apisu do souboru), nestaˇc´ı pr´ avo z´ apisu do adres´ aˇre. Toto nastaven´ı se pouˇz´ıv´a pro veˇrejn´e adres´aˇre (napˇr. /tmp). • p˚ uvodnˇe mˇel sticky bit v´ yznam i pro spustiteln´e soubory: program s nastaven´ ym sticky bitem z˚ ustal po ukonˇcen´ı v pamˇeti a jeho opˇetovn´e spuˇstˇen´ı bylo rychlejˇs´ı. Dnes se sticky bit v tomto v´ yznamu uˇz nepouˇz´ıv´a. • nˇekter´e filesyst´emy (XFS, AFS, UFS2, ZFS) maj´ı tzv. access control lists (ACLs), kter´e dovoluj´ı jemnˇejˇs´ı pˇridˇelov´an´ı pr´av jednotliv´ ym uˇzivatel˚ um a skupin´ am.
91
API pro soubory • pˇred pouˇzit´ım mus´ı proces kaˇzd´ y soubor nejprve otevˇr´ıt vol´ an´ım open() nebo creat(). • otevˇren´e soubory jsou dostupn´e pˇres deskriptory soubor˚ u (file descriptors), ˇc´ıslovan´e od 0, v´ıce deskriptor˚ u m˚ uˇze sd´ılet jedno otevˇ ren´ı souboru (m´od ˇcten´ı/z´apis, ukazatel pozice) • standardn´ı deskriptory: – 0 . . . standardn´ı vstup (jen pro ˇcten´ı) – 1 . . . standardn´ı v´ ystup (jen pro z´apis) – 2 . . . chybov´ y v´ ystup (jen pro z´apis) • ˇcten´ı a z´ apis z/do souboru: read(), write() • zmˇena pozice: lseek(), zavˇren´ı: close(), informace: stat(), ˇr´ıdic´ı funkce: fcntl(), pr´ava: chmod(), . . .
• kaˇzd´ a funkce, kter´ a alokuje deskriptory (nejen open a creat, ale napˇr. i pipe, dup, socket) alokuje vˇzdy voln´e deskriptory s nejniˇzˇs´ım ˇc´ıslem. • proces dˇed´ı otevˇren´e soubory od rodiˇce, tyto soubory nemus´ı znovu otv´ırat. Obvykle (ale ne vˇzdy) proces dostane otevˇren´e alespoˇ n deskriptory 0, 1 a 2. • funkce ze souboru stdio.h (napˇr. fopen, fprintf, fscanf) a odkazy na soubory pomoc´ı ukazatele na FILE jsou definov´any ve standardn´ı knihovnˇe a pro svoj´ı ˇcinnost pouˇz´ıvaj´ı vol´an´ı j´adra (napˇr. open, write, read). My se nebudeme knihovnou stdio zab´ yvat.
92
Otevˇ ren´ı souboru: open() int open(const char *path, int oflag, ... ); • otevˇre soubor dan´ y jm´enem (cestou) path, vr´at´ı ˇc´ıslo jeho deskriptoru (pouˇzije prvn´ı voln´e), oflag je OR-kombinace pˇr´ıznak˚ u – O RDONLY/O WRONLY/O RDWR . . . otevˇr´ıt pouze pro ˇcten´ı / pouze pro z´ apis / pro ˇcten´ı i z´apis – O APPEND . . . pˇripojov´an´ı na konec – O CREAT . . . vytvoˇrit, kdyˇz neexistuje – O EXCL . . . chyba, kdyˇz existuje (pouˇzit´ı s O CREATE) – O TRUNC . . . zruˇsit pˇredchoz´ı obsah (pr´avo z´apisu nutn´e) – ... • pˇri O CREAT definuje tˇret´ı parametr mode pˇr´ıstupov´a pr´ava
• pˇri pouˇzit´ı O CREAT se mode jeˇstˇe modifikuje podle nastaven´ı aktu´aln´ı masky, kter´e se mˇen´ı vol´ an´ım umask. Defaultn´ı hodnota je typicky 022. Doporuˇcuji se zamyslet, zda ji ve vaˇsem shell profile skriptu nenastavit na 077. To ale nikdy nedˇelejte pro roota, jinak skonˇc´ıte s podivnˇe se chovaj´ıc´ım syst´emem – nainstalovan´ y software nebude moct spustit pod bˇeˇzn´ ym uˇzivatelem, to co dˇr´ıve fungovalo fungovat pˇrestane apod. • mode nem´ a defaultn´ı hodnotu, tj. vezme se to, co je na z´asobn´ıku i kdyˇz tento parametr nen´ı pˇr´ıtomen! Pˇr´ıznaky i m´od jsou uloˇzeny v syst´emov´e tabulce otevˇren´ ych soubor˚ u. • pokud se znovu pouˇzije dˇr´ıve vyuˇz´ıvan´a poloˇzka v tabulce deskriptor˚ u nebo v tabulce otevˇren´ ych soubor˚ u, vˇse potˇrebn´e se vynuluje (pozice v souboru, flagy deskriptoru, . . . ) • existuj´ı jeˇstˇe dalˇs´ı nastaviteln´e pˇr´ıznaky: – O SYNC (O DSYNC, O RSYNC – nen´ı na BSD) . . . operace se souborem skonˇc´ı aˇz po fyzick´em uloˇzen´ı dat (synchronized I/O) – O NOCTTY . . . pˇri otv´ır´ an´ı termin´alu procesem, kter´ y nem´a ˇr´ıdic´ı termin´ al, se tento termin´ al nestane ˇr´ıd´ıc´ım termin´alem procesu – O NONBLOCK . . . pokud nelze ˇcten´ı nebo z´apis prov´est okamˇzitˇe, vol´an´ı read/write skonˇc´ı s chybou m´ısto zablokov´an´ı procesu • v pˇr´ıstupov´ ych pr´ avech se vynuluj´ı ty bity, kter´e jsou nastaven´e pomoc´ı umask.
93
• pro ˇcten´ı a z´ apis nelze pouˇz´ıt O RDONLY | O WRONLY, protoˇze implementace pouˇzily 0 pro read-only flag. Norma proto definuje, ˇze aplikace mus´ı pouˇz´ıt pr´ avˇe jeden z tˇechto tˇr´ı flag˚ u. • je moˇzn´e otevˇr´ıt a z´ aroveˇ n vytvoˇrit soubor pro z´apis tak, ˇze jeho m´od z´apis nedovoluje. Pˇri pˇr´ıˇst´ım otevˇren´ı souboru se ale jiˇz tento m´od uplatn´ı pˇri kontrole pˇr´ıstupu a pro z´ apis jej otevˇr´ıt nep˚ ujde. Pro O TRUNC tak´e plat´ı, ˇze mus´ıte m´ıt pro dan´ y soubor pr´avo z´apisu.
Vytvoˇ ren´ı souboru int creat(const char *path, mode t mode ); • open() s pˇr´ıznakem O CREAT vytvoˇr´ı soubor, pokud jeˇstˇe neexistuje. V zadan´e hodnotˇe pˇr´ıstupov´ ych pr´av se vynuluj´ı bity, kter´e byly nastaveny pomoc´ı funkce mode t umask(mode t cmask ); • funkce je ekvivalentn´ı vol´an´ı open(path, O WRONLY|O CREAT|O TRUNC, mode); int mknod(const char *path, mode t mode, dev t dev ); • vytvoˇr´ı speci´ aln´ı soubor zaˇr´ızen´ı. int mkfifo(const char *path, mode t mode ); • vytvoˇr´ı pojmenovanou rouru.
• Vol´ an´ı vrac´ı nejniˇzˇs´ı deskriptor, kter´ y v dan´e chv´ıli nebyl otevˇren´ y pro proces • open dok´ aˇze otevˇr´ıt regul´ arn´ı soubor, zaˇr´ızen´ı i pojmenovanou rouru, ale vytvoˇrit dok´ aˇze jen regul´ arn´ı soubor; pro ostatn´ı typy soubor˚ u je nutn´e pouˇz´ıt zde uveden´ a speci´ aln´ı vol´ an´ı. • Test pomoc´ı pˇr´ıznaku O EXCL na to, zda soubor existuje, a jeho pˇr´ıpadn´e vytvoˇren´ı je atomick´ a operace. Toho se vyuˇz´ıv´a pˇri pouˇz´ıv´an´ı lock soubor˚ u. Ze slajdu je vidˇet, ˇze tento test je moˇzn´e prov´est jen pomoc´ı vol´an´ı open, ne creat. • Speci´ aln´ı soubory m˚ uˇze vytv´aˇret pouze root, protoˇze se pomoc´ı nich definuj´ı pˇr´ıstupov´ a pr´ ava k perifern´ım zaˇr´ızen´ım. • Povolen´e hodnoty pro mode je moˇzn´e nal´ezt vˇetˇsinou v manu´alov´e str´ankce pro chmod(2), a urˇcitˇe je naleznete i v hlaviˇckov´em souboru stat.h, kde mus´ı b´ yt podle normy definovan´e.
94
ˇ Cten´ ı a z´ apis soubor˚ u: read(), write() ssize t read(int fildes, void *buf, size t nbyte ); • z otevˇren´eho souboru s ˇc´ıslem deskriptoru fildes pˇreˇcte od aktu´ aln´ı pozice max. nbyte bajt˚ u dat a uloˇz´ı je od adresy buf. • vrac´ı poˇcet skuteˇcnˇe pˇreˇcten´ ych bajt˚ u (<= nbyte), 0 znamen´a konec souboru. ssize t write(int fildes, const void *buf, size t nbyte ); • do otevˇren´eho souboru s ˇc´ıslem deskriptoru fildes zap´ıˇse na aktu´ aln´ı pozici max. nbyte bajt˚ u dat uloˇzen´ ych od adresy buf. • vrac´ı velikost skuteˇcnˇe zapsan´ ych dat (<= nbyte).
• pro UNIX je kaˇzd´ y soubor posloupnost bajt˚ u bez dalˇs´ı vnitˇrn´ı struktury. • chov´ an´ı read a write z´ avis´ı na typu souboru (regul´arn´ı, zaˇr´ızen´ı, roura, soket) a na tom, zda je soubor v blokuj´ıc´ım nebo neblokuj´ıc´ım m´odu (flag O NONBLOCK pˇri otevˇren´ı souboru, viz strana 99). • pro obˇe vol´ an´ı existuje nˇekolik z´asadn´ıch rohov´ ych pˇr´ıpad˚ u a n´asleduj´ıc´ı informace jsou v´ yn ˇatkem z manu´ alov´ ych str´anek. Pokud si nejste jisti, doporuˇcuji se pod´ıvat pˇr´ımo do normy (POSIX 1003.1, sekce XSH, ˇc´ast System Interfaces). • vol´ an´ı read vr´ at´ı nenulov´ y poˇcet bajt˚ u menˇs´ı neˇz nbyte, pokud v souboru zb´ yv´ a m´enˇe neˇz nbyte bajt˚ u, nebo vol´an´ı bylo pˇreruˇseno sign´alem, nebo soubor je roura, zaˇr´ızen´ı ˇci soket a aktu´alnˇe je k dispozici m´enˇe neˇz nbyte bajt˚ u. Pˇri neexistenci dat se blokuj´ıc´ı read zablokuje, dokud se nˇejak´a data neobjev´ı, neblokuj´ıc´ı read vr´at´ı -1 a nastav´ı errno na EAGAIN. • vol´ an´ı write vr´ at´ı nenulov´ y poˇcet bajt˚ u menˇs´ı neˇz nbyte, jestliˇze se do souboru nevejde v´ıc dat (napˇr. zaplnˇen´ y disk), z´apis je pˇreruˇsen sign´alem nebo je nastaveno O_NONBLOCK a do roury, soketu nebo zaˇr´ızen´ı se vejde pouze ˇc´ast zapisovan´ ych dat. Bez O_NONBLOCK se ˇcek´a, dokud se nepodaˇr´ı zapsat vˇse. Pokud nelze aktu´ alnˇe zapsat nic, blokuj´ıc´ı write se zablokuje, dokud nen´ı moˇzn´e zapisovat, neblokuj´ıc´ı write vr´at´ı -1 a nastav´ı errno na EAGAIN. • d˚ uleˇ zit´ e v´ yjimky vzhledem k rour´ am jsou uvedeny na stranˇe 104. • kdyˇz read nebo write vr´ at´ı m´enˇe neˇz nbyte z d˚ uvodu chyby, opakovan´e vol´ an´ı t´eˇze funkce vr´ at´ı -1 a nastav´ı k´od chyby v errno. 95
• pˇreruˇsen´ı read, write sign´ alem dˇr´ıv, neˇz se podaˇr´ı pˇreˇc´ıst, resp. zapsat aspoˇ n jeden bajt, zp˚ usob´ı n´ avrat s hodnotou -1 a nastaven´ı errno na EINTR. Pozor na to, ˇze zde je rozd´ıl proti situaci, kdy pˇred doruˇcen´ım sign´alu podaˇr´ı pˇreˇc´ıst nebo zapsat alespoˇ n jeden bajt (viz v´ yˇse). • pˇr´ıznak otevˇren´ı souboru O_APPEND zajist´ı atomick´ y z´apis na konec souboru (pouze na lok´ aln´ım disku), tj. kaˇ zd´ y z´apis se provede na konec souboru (tzv. append-only soubor).
Uzavˇ ren´ı souboru: close() int close(int fildes ); • uvoln´ı deskriptor fildes, pokud to byl posledn´ı deskriptor, kter´ y odkazoval na otevˇren´ı souboru, zavˇre soubor a uvoln´ı z´ aznam o otevˇren´ı souboru. • kdyˇz je poˇcet odkaz˚ u na soubor 0, j´adro uvoln´ı data souboru. Tedy i po zruˇsen´ı vˇsech odkaz˚ u (jmen) mohou se souborem pracovat procesy, kter´e ho maj´ı otevˇren´ y. Soubor se smaˇze, aˇz kdyˇz ho zavˇre posledn´ı proces. • kdyˇz se zavˇre posledn´ı deskriptor roury, vˇsechna zb´ yvaj´ıc´ı data v rouˇre se zruˇs´ı. • pˇri skonˇcen´ı procesu se automaticky provede close() na vˇsechny deskriptory.
• Pokud proces potˇrebuje doˇcasn´ y soubor, m˚ uˇze ho vytvoˇrit, ihned smazat a pak s n´ım pracovat pˇres existuj´ıc´ı deskriptor (tento deskriptor lze pˇredat synovsk´ ym proces˚ um). Kdyˇz je takov´ y soubor zavˇren vˇsemi procesy, j´adro smaˇze jeho data z disku. • I operace close m˚ uˇze selhat. Napˇr. nˇekter´e filesyst´emy zapisuj´ı data na disk aˇz v okamˇziku zavˇren´ı souboru, kdyˇz z´apis skonˇc´ı chybou, close vr´at´ı -1. • Otev´ır´ an´ı soubor˚ u a nezav´ır´ an´ı je pˇri ukonˇcen´ı pr´ace s nimi vede k alokovan´e pamˇeti, kterou jiˇz nem´ ame jak uvolnit (ztratili jsme na ni odkaz) – situace kde takto pamˇet’ “ztr´ ac´ıme” se naz´ yv´a memory leak. Nen´ı to u ´plnˇe pˇresnˇe to, jak se typicky ch´ ape memory leak (pamˇet, na kterou jsme ztratili ukazatel), ale n´ asledky jsou podobn´e. • Pˇr´ıklad na jednoduch´ y cat(1) program: read/cat.c
96
Pˇ r´ıklad: kop´ırov´ an´ı soubor˚ u #include <sys/types.h> #include int main(int argc, char *argv[]) { char buf[4096]; int inf, outf; ssize t ilen; inf = open(argv[1], O RDONLY); outf = creat(argv[2], 0666); while ((ilen = read(inf, buf, 4096)) > 0) write(outf, buf, ilen); close(inf); close(outf); return (0); }
• Tento pˇr´ıklad je kompletn´ı! Staˇc´ı prov´est cut-and-paste, pˇreloˇzit a spustit. Nutno poznamenat, ˇze pro u ´ˇcely pouˇzit´ı pouze jednoho slajdu se netestuj´ı ˇz´ adn´e chyby, vˇcetnˇe ignorov´an´ı toho, ˇze uˇzivatel m˚ uˇze program spustit bez parametr˚ u, coˇz zp˚ usob´ı pokus o pˇr´ıstup do pamˇeti na m´ısto, kter´e neobsahuje oˇcek´ avan´ a data. To pak na nˇekter´ ych syst´emech m˚ uˇze zp˚ usobit core dump. • Je neefektivn´ı ˇc´ıst a zapisovat soubor po jednotliv´ ych bajtech, protoˇze kaˇzd´e syst´emov´e vol´ an´ı m´ a jist´ y overhead, nez´avisl´ y na velikosti zpracov´avan´eho bloku. Lepˇs´ı je proto najednou zpracov´avat rozumnˇe velk´e bloky, napˇr´ıklad 864KB. Pˇrechoz´ı pˇr´ıklad read/cat.c m´a pˇrep´ınaˇc -b pro nastaven´ı velikosti bloku pro ˇcten´ı; zkuste s (a bez) -b 1 a zmˇeˇrte pomoc´ı time(1). Uvid´ıte velk´ y rozd´ıl. • Pokud potˇrebujete pracovat s mal´ ymi ˇc´astmi/buffery, je lepˇs´ı pouˇz´ıt stream orintovan´e knihovn´ı funkce fopen, fread, . . . kter´e data vnitˇrnˇe bufferuj´ı. • Vˇsimnˇete si, ˇze vˇzdy zapisujeme jen tolik bajt˚ u, kolik jsme jich naˇcetli.
97
Pr´ ace s pojmenovanou rourou • nemus´ı b´ yt moˇzn´e vytvoˇrit FIFO na s´ıt’ov´em filesyst´emu (NFS, AFS) • je nutn´e zn´ at s´emantiku otev´ır´an´ı pojmenovan´e roury: – otevˇren´ı roury pouze pro ˇcten´ı se zablokuje do t´e doby, dokud se neobjev´ı zapisovatel (pokud jiˇz neexistuje) – otevˇren´ı roury pouze pro z´apis se zablokuje do t´e doby, dokud se neobjev´ı ˇcten´aˇr (pokud jiˇz neexistuje) – toto chov´ an´ı je moˇzn´e ovlivnit flagem O NONBLOCK • s´emantika ˇcten´ı a z´ apisu je jeˇstˇe o nˇeco sloˇzitˇejˇs´ı, vˇenujte velkou pozornost pozn´ amk´am pod t´ımto slajdem – a je to stejn´e jako u nepojmenovan´e roury
• Co se t´ yˇce vytvoˇren´ı pojmenovan´e roury, mluv´ıme zde o vol´an´ı mkfifo ze strany 100. Pouˇzit´ı nepojmenovan´e roury je popsan´e na stranˇe 143. ˇ aˇr je proces, kter´ • Cten´ y otevˇre soubor (i) pro ˇcten´ı, zapisovatel je proces, kter´ y otevˇre soubor (i) pro z´apis. • Je moˇzn´e otevˇr´ıt rouru pro z´apis i ˇcten´ı stejn´ ym procesem najednou, aniˇz by pˇredt´ım existoval ˇcten´ aˇr nebo zapisovatel. Proces pak m˚ uˇze do roury zapisovat a z opaˇcn´eho konce ˇc´ıst, co do roury napsal. • Pokud rouru nem´ a ˇz´ adn´ y proces otevˇrenou pro z´apis, pro okamˇzit´e vr´acen´ı deskriptoru pouze pro ˇcten´ı je nutn´e otevˇ r´ıt rouru s flagem O NONBLOCK, proces se jinak na rouˇre zablokuje ˇcek´an´ım na zapisovatele. Pozor ale na to, ˇze pokus o ˇ cten´ı z roury bez zapisovatele okamˇzitˇe vr´at´ı 0 jako indikaci konce souboru; proces se nezablokuje ˇ cek´ an´ım na zapisovatele, bez ohledu na to zda byl soubor otevˇren v blokovac´ım ˇci neblokovac´ım m´odu. Pˇri z´ apisu do roury bez ˇcten´ aˇre (tj. zapisovatel otevˇrel rouru jeˇstˇe v dobˇe, kdy ˇcten´ aˇr existoval, viz n´ asleduj´ıc´ı odstavec) poˇsle kernel procesu sign´al SIGPIPE (“broken pipe”): bash$ dd if=/dev/zero | date Sun Mar 2 01:03:38 CET 2008 bash$ echo ${PIPESTATUS[0]} 141 bash$ kill -l 141 PIPE
98
• v pˇr´ıpadˇe otev´ır´ an´ı pouze pro z´apis s O NONBLOCK bez existuj´ıc´ıho ˇcten´aˇre se vr´ at´ı chyba a errno se nastav´ı na ENXIO. Tato asymetrie je snahou, aby v rouˇre nebyla data, kter´ a nebudou v kr´atk´e dobˇe pˇreˇctena – syst´em nem´a zp˚ usob, jak uschov´ avat data v rouˇre bez ˇcasov´eho omezen´ı. Bez O NONBLOCK flagu se proces zablokuje ˇcek´an´ım na ˇcten´aˇre. Asymetri´ı je m´ınˇeno to, ˇze syst´emu nevad´ı ˇcten´ aˇri bez zapisovatel˚ u, ale nechce m´ıt zapisovatele bez ˇcten´ aˇr˚ u. I tato situace se m˚ uˇze st´at, viz pˇredchoz´ı odstavec, ale tam to jinak ˇreˇsit nelze. • z uveden´eho tedy vypl´ yv´ a, ˇze pokud chcete vytvoˇrit proces, kter´ y ˇcek´a na pojmenovan´e rouˇre a vykon´ av´ a poˇzadavky od r˚ uzn´ ych proces˚ u-zapisovatel˚ u (od kaˇzd´eho jeden), mus´ıte ji otevˇr´ıt s flagem O RDWR i kdyˇz do roury nehodl´ate zapisovat; jinak po prvn´ım zablokov´an´ı v open pˇri ˇcek´an´ı na otevˇren´ı roury zapisovatelem by dalˇs´ı vol´ an´ı read pro akceptov´an´ı dalˇs´ıho poˇzadavku typicky vr´ atilo 0, pokud by n´ ahodou roura nebyla mezit´ım pro z´apis otevˇrena dalˇs´ım procesem. y jako atomick´ y, tj. • z´ apis maxim´ aln´ı velikosti PIPE BUF (limits.h) je zaruˇcen´ data nesm´ı b´ yt proloˇzena daty jin´ ych zapisuj´ıc´ıch proces˚ u. Napˇr. v Solarisu 11 to je to 5120 bajt˚ u, ve FreeBSD 5.4 je to 512 bajt˚ u. Pokud zapisujete v´ıce, m˚ uˇze se to st´ at. Z´ aroveˇ n plat´ı, ˇze pokud se zapisuje m´enˇe neˇz PIPE BUF bajt˚ u, zap´ıˇsou se vˇzdy najednou, a pokud je nastaveno O NONBLOCK a nelze toho dos´ ahnout, vr´ at´ı se chyba. • roura nem´ a pozici v souboru, z´apis tak vˇzdy pˇrid´av´a na konec. • stejnˇe se vzhledem ke ˇcten´ı a z´apisu chov´a i nepojmenovan´a roura, viz strana 143.
Nastaven´ı pozice: lseek() off t lseek(int fildes, off t offset, int whence ); • nastav´ı pozici pro ˇcten´ı a z´apis v otevˇren´em souboru dan´em ˇc´ıslem deskriptoru fildes na hodnotu offset. • podle hodnoty whence se offset poˇc´ıt´a: – SEEK SET . . . od zaˇc´atku souboru – SEEK CUR . . . od aktu´aln´ı pozice – SEEK END . . . od konce souboru • vrac´ı v´ yslednou pozici poˇc´ıtanou od zaˇc´atku souboru. • lseek(fildes, 0, SEEK CUR) pouze vr´at´ı aktu´aln´ı pozici.
99
• Poˇc´ıt´ a se od 0, tj. offset 0 je prvn´ı bajt souboru. Tam, kde to m´a smysl, je moˇzn´e pro offset pouˇz´ıt i z´aporn´e ˇc´ıslo. Pˇr´ıklad: read/lseek.c . • Lze se pˇresunout i na pozici za koncem souboru. Pokud se pak provede z´apis, soubor se prodlouˇz´ı a v pˇreskoˇcen´e ˇc´asti budou sam´e nuly (samotn´e lseek nestaˇc´ı). Nˇekter´e filesyst´emy takov´e bloky cel´ ych nul pro u ´sporu m´ısta neukl´ adaj´ı. • Velikost souboru je moˇzn´e zjistit pomoc´ı lseek(fildes, 0, SEEK END). • Nejˇcastˇejˇs´ı operace s lseek jsou tˇ ri: nastaven´ı konkr´etn´ı pozice od zaˇc´atku souboru, nastaven´ı pozice na konec souboru a zjiˇstˇen´ı aktu´aln´ı pozice v souboru (0 spoleˇcnˇe se SEEK CUR) • Pˇri pouˇzit´ı lseek se ˇz´ adn´e I/O neprovede, tj. ˇz´adn´ y pˇr´ıkaz se nepoˇsle na ˇradiˇc disku. • lseek nemus´ı slouˇzit jen pro operace read a write, ale tak´e pro n´aslednou operaci lseek • Seekov´ an´ı a z´ apis m˚ uˇze v´est k probl´em˚ um se z´alohami. Pˇr´ıklad: read/big-file.c demonstruje, ˇze pˇresun souboru s ”d´ırami”(tzv. sparse file) m˚ uˇze v´est k n´ ar˚ ustu velikosti souboru co se t´ yˇce blok˚ u alokovan´ ych file syst´emem. Chov´an´ı z´ aleˇz´ı na kombinaci operaˇcn´ıho syst´emu, archivn´ıho programu a file syst´emu (a jejich verz´ı). Nˇekter´e programy maj´ı pˇrep´ınaˇce, kter´e umoˇzn´ı d´ıry zachovat (napˇr. dd s conv=sparse, tar s -S, rsync s --sparse, atd.). • Pozor na pˇrehozen´ı parametr˚ u. Druh´a ˇr´adka samostatnˇe vypad´a OK, ale m´a pˇrehozen´e parametry. SEEK SET je nav´ıc 0 a SEEK CUR je 1, takˇze v´as to nikam neposune a nic ˇspatn´eho se nestane, a o to je horˇs´ı to pak naj´ıt. lseek(fd, 1, SEEK_SET) lseek(fd, SEEK_SET, 1)
100
Zmˇ ena velikosti: truncate() int truncate(const char *path, off t length ); int ftruncate(int fildes, off t length ); • zmˇen´ı d´elku souboru zadan´eho cestou nebo ˇc´ıslem deskriptoru na poˇzadovanou hodnotu. • pˇri zkr´ acen´ı souboru zruˇs´ı nadbyteˇcn´a data. • standard ponech´ av´ a nespecifikovan´e, jestli funguje prodlouˇzen´ı souboru (s vyplnˇen´ım pˇridan´eho u ´seku nulami). Proto je lepˇs´ı k prodlouˇzen´ı souboru pouˇz´ıt: char buf = ’\0’; lseek(fildes, length - 1, SEEK_SET); write(fildes, &buf, 1);
• zruˇsit veˇsker´ y obsah souboru pˇri otevˇren´ı se d´a pˇr´ıznakem O TRUNC ve funkci open. • podrobnosti je tˇreba hledat v manu´alov´e str´ance, napˇr. ve FreeBSD v sekci BUGS nalezneme toto: Use of truncate to extend a file is not portable.
101
Duplikace deskriptoru: dup(), dup2() int dup(int fildes ); • duplikuje deskriptor fildes na prvn´ı voln´ y deskriptor, vr´at´ı nov´ y deskriptor, kter´ y odkazuje na stejn´e otevˇren´ı souboru. • ekvivalent fcntl(fildes, F DUPFD, 0); int dup2(int fildes, int fildes2 ); • duplikuje deskriptor fildes do deskriptoru fildes2. • ekvivalent close(fildes2); fcntl(fildes, F DUPFD, fildes2);
• prvn´ı voln´ y deskriptor se pouˇzije i u otev´ır´an´ı a vytv´aˇren´ı soubor˚ u, viz strany 99 a 100. • duplikovan´ y a p˚ uvodn´ı deskriptor sd´ıl´ı stejn´e otevˇren´ı souboru a tedy i aktu´ aln´ı pozici a m´ od ˇcten´ı/z´ apis. • ekvivalent pro dup2 nen´ı zcela ekvivalentn´ı, napˇr. pokud je fildes rovn´ y fildes2, tak se neprovede close(fildes2) a dup2 rovnou vr´at´ı fildes2. V´ıce viz POSIX 1003.1 (ˇc´ ast XSH, System interfaces).
102
Pˇ r´ıklad: implementace shellov´ eho pˇ resmˇ erov´ an´ı • $ program < in > out 2>> err close(0); open("in", O_RDONLY); close(1); open("out", O_WRONLY | O_CREAT | O_TRUNC, 0666); close(2); open("err", O_WRONLY | O_CREAT | O_APPEND, 0666); • $ program > out 2>&1 close(1); open("out", O_WRONLY | O_CREAT | O_TRUNC, 0666); close(2); dup(1);
• vˇsimnˇete si korespondence flagu O APPEND s >> • Dalˇs´ı pˇr´ıklad pouˇzit´ı dup uvid´ıme, aˇz se budeme zab´ yvat rourami. Prvn´ı pˇresmˇerov´ an´ı (bez stderr) je v read/redirect.c . Vol´an´ı execl v tomto pˇr´ıkladu nahrad´ı aktu´ aln´ı obraz bˇeˇz´ıc´ıho procesu obrazem programu pˇredan´eho jako prvn´ı argument. V´ıce o vol´an´ı execl je na stranˇe 137. • pro pochopen´ı toho jak funguje pˇresmˇerov´an´ı je dobr´e si nakreslit tabulku deskriptor˚ u v ˇcase a kam ”ukazuj´ı”. napˇr. pro druh´ y pˇr´ıklad (inici´aln´ı stav, stav po close(1) open("out", ...), koneˇcn´ y stav): +-------+ +-------+ +-------+ | 0 +-> stdin | 0 +-> stdin | 0 +-> stdin +-------+ +-------+ +-------+ | 1 +-> stdout ===> | 1 +-> "out" ===> | 1 +-> "out" +-------+ +-------+ +-------+ ^ | 2 +-> stderr | 2 +-> stderr | 2 +----/ +-------+ +-------+ +-------+ • Je potˇreba si d´ at pozor na stav deskriptor˚ u. Druh´ y pˇr´ıklad nebude fungovat, kdyˇz bude deskriptor 0 uzavˇren, protoˇze open vr´at´ı deskriptor 0 (prvn´ı voln´ y) a dup vr´ at´ı chybu (pokus o duplikaci uzavˇren´eho deskriptoru). Moˇzn´e ˇreˇsen´ı: close(1); if((fd = open("out", O WRONLY | O CREAT | O TRUNC, 0666)) == 0) dup(0);
103
close(2); dup(1); if(fd == 0) close(0); nebo fd = open("out", O WRONLY | O CREAT | O TRUNC, 0666); if(fd != 1) { dup2(fd, 1); close(fd); } dup2(1, 2);
ˇ ıdic´ı funkce soubor˚ R´ u a zaˇ r´ızen´ı: fcntl(), ioctl() int fcntl(int fildes, int cmd, ...); • slouˇz´ı pro duplikaci deskriptor˚ u, nastavov´an´ı z´amk˚ u, testov´an´ı a nastavov´ an´ı r˚ uzn´ ych pˇr´ıznak˚ u souboru. pˇr´ıklad: zavˇren´ı standardn´ıho vstupu pˇri spuˇstˇen´ı programu (vol´ an´ı typu exec) fcntl(0, F SETFD, FD CLOEXEC); int ioctl(int fildes, int request, ... ); • rozhran´ı pro ˇr´ıdic´ı funkce perifern´ıch zaˇr´ızen´ı • pouˇz´ıv´ a se jako univerz´aln´ı rozhran´ı pro ovl´ad´an´ı zaˇr´ızen´ı, kaˇzd´e zaˇr´ızen´ı definuje mnoˇzinu pˇr´ıkaz˚ u, kter´ ym rozum´ı.
• jak pozdˇeji uvid´ıme, i k socket˚ um se pˇristupuje pˇres souborov´e deskriptory, a je tedy moˇzn´e fnctl pouˇz´ıt i na nˇe, tˇreba pro nastaven´ı neblokuj´ıc´ıho socketu. V´ıce informac´ı viz strana 198. • moˇzn´e hodnoty cmd ve funkci fcntl: – F DUPFD . . . duplikace deskriptoru – F GETFD . . . zjiˇstˇen´ı pˇr´ıznak˚ u deskriptoru (FD CLOEXEC – uzavˇren´ı pˇri exec). FD CLOEXEC je jedin´ y flag pro deskriptory, definovan´ y v normˇe UNIX 03. – F SETFD . . . nastaven´ı pˇr´ıznak˚ u deskriptoru
104
– F GETFL . . . zjiˇstˇen´ı m´ odu ˇcten´ı/z´apis a pˇr´ıznak˚ u otevˇren´ı souboru (jako u open) u otevˇren´ı souboru (O APPEND, O DSYNC, – F SETFL . . . nastaven´ı pˇr´ıznak˚ O NONBLOCK, O RSYNC, O SYNC). Nemohu nastavit pˇr´ıznaky pro RO/RW a ani pˇr´ıznaky pro vytvoˇren´ı, zkr´acen´ı nebo exkluzivn´ı pˇr´ıstup k souboru. u – F GETLK, F SETLK, F SETLKW . . . nastavov´an´ı z´amk˚ • je d˚ uleˇzit´e si uvˇedomit, ˇze jsou dva druhy pˇr´ıznak˚ u – pˇr´ıznak(y) pro souborov´ y deskriptor a pˇr´ıznaky pro jedno konkr´etn´ı otevˇren´ı souboru – tj. pˇr´ıznaky jsou uloˇzen´e ve dvou r˚ uzn´ ych tabulk´ach. • perifern´ı zaˇr´ızen´ı podporuj´ı ˇcten´ı a z´apis dat pomoc´ı read, write a mapov´an´ı dat do pamˇeti (mmap), veˇsker´e dalˇs´ı operace se zaˇr´ızen´ım (napˇr. nastaven´ı parametr˚ u, zamˇcen´ı nebo eject) se dˇelaj´ı funkc´ı ioctl. • pˇri nastavov´ an´ı pˇr´ıznak˚ u nejdˇr´ıv vˇzdy zjistˇete, jak´e byly pˇredt´ım. I kdyˇz jste si jisti, ˇze v dan´e chv´ıli jsou nulov´e a je tedy moˇzn´e prov´est fcntl(fd, uˇzete vˇedˇet, co se m˚ uˇze zmˇenit (napˇr´ıklad o p´ar ˇr´adk˚ u v´ yˇse O APPEND), nem˚ nˇejak´ y flag pˇrid´ ate, aniˇz byste vˇedˇeli, ˇze jste ovlivnˇeni k´odem dole). Tedy vˇzdy pouˇzijte napˇr´ıklad toto: flags = fcntl(fd, F_GETFL); if (fcntl(fd, F_SETFL, flags | O_APPEND) == -1) ... . . . a podobnˇe pro odebr´ an´ı flagu – je ˇspatn´e ˇreˇsen´ı nastavit hodnot˚ u flag˚ u na nulu, m´ısto toho je tˇreba pouˇz´ıt bitov´eho jedniˇckov´eho doplˇ nku pˇr´ısluˇsn´eho flagu (flags & ~O APPEND).
105
Informace o souboru: stat() int stat(const char *path, struct stat *buf ); int fstat(int fildes, struct stat *buf ); • pro soubor zadan´ y cestou, resp. ˇc´ıslem deskriptoru, vr´at´ı strukturu obsahuj´ıc´ı informace o souboru, napˇr.: – st ino . . . ˇc´ıslo i-uzlu – st dev . . . ˇc´ıslo zaˇr´ızen´ı obsahuj´ıc´ıho soubor – st uid, st gid . . . vlastn´ık a skupina souboru – st mode . . . typ souboru a jeho pˇr´ıstupov´a pr´ava – st size, st blksize, st blocks . . . velikost souboru v bajtech, preferovan´a velikost bloku pro I/O a poˇcet blok˚ u – st atime, st mtime, st ctime . . . ˇcasy posledn´ıho pˇr´ıstupu, modifikace souboru a modifikace i-uzlu – st nlink . . . poˇcet odkaz˚ u na soubor
• update ˇcasu posledn´ıho pˇr´ıstupu k souboru lze na bˇeˇzn´ ych file syst´emech vypnout pomoc´ı optionu noatime pˇr´ıkazu mount. Hodit se to m˚ uˇze pro urychlen´ı, pokud se prov´ ad´ı ˇcten´ı velk´eho mnoˇzstv´ı soubor˚ u a nez´aleˇz´ı na ˇcasu pˇr´ıstupu na tˇechto souborech (napˇr. pˇri kompilaci). • Metadata jsou informace o souboru – tedy m´od, ˇcasy pˇr´ıstupu, d´elka, vlastn´ık a skupina atd. Nepatˇr´ı mezi nˇe skuteˇcn´a data souboru, a ani jm´eno, kter´e nen´ı uloˇzeno v r´ amci dan´eho souboru, ale v adres´aˇri ˇci v adres´aˇr´ıch. • Metadata je moˇzn´e pˇreˇc´ıst, i kdyˇz proces nem´a pr´ava pro ˇcten´ı obsahu souboru. • Touto funkc´ı nez´ısk´ am flagy deskriptoru ani flagy z pole tabulky otevˇren´ ych soubor˚ u v syst´emu, zde jde o informace ohlednˇe souboru uloˇzen´eho na pamˇet’ov´em m´ediu. • Co se stane kdyˇz se zavol´ a fstat na deskriptory 0,1,2 ? (pˇredpokl´ad´ame ˇze nejsou pˇresmˇerovan´e z norm´aln´ıho stavu) • st ctime nen´ı ˇcas vytvoˇren´ı souboru (creation time), ale ˇcas zmˇeny indexov´eho uzlu (change time) • Norma nespecifikuje poˇrad´ı poloˇzek ve struktuˇre ani nezakazuje pˇridat dalˇs´ı. • Pˇr´ıklad: stat/stat.c
106
Informace o souboru (2) • pro typ souboru jsou v <sys/stat.h> definov´any konstanty S_IFMT (maska pro typ), S_IFBLK (blokov´ y speci´aln´ı), S_IFCHR (znakov´ y speci´ aln´ı), S_IFIFO (FIFO), S_IFREG (obyˇcejn´ y), S_IFDIR (adres´ aˇr), S_IFLNK (symlink). • typ lze testovat pomoc´ı maker S_ISBLK(m), S_ISCHR(m), S_ISFIFO(m), S_ISREG(m), S_ISDIR(m), S_ISLNK(m). • konstanty pro pˇr´ıstupov´a pr´ava: S_IRUSR (ˇcten´ı pro vlastn´ıka), S_IWGRP (z´ apis pro skupinu), atd. int lstat(const char *path, struct stat *buf ); • kdyˇz je zkouman´ y soubor symlink, stat() vr´at´ı informace o souboru, na kter´ y ukazuje. Tato funkce vrac´ı informace o symlinku.
• typ a pr´ ava souboru jsou uloˇzena spoleˇcnˇe v st_mode, proto existuj´ı zmiˇ novan´ a makra. • S IFMT specifikuje tu ˇc´ ast bit˚ u, kter´e jsou vˇenovan´e typu souboru, makra pro jednotliv´e typy pak nejsou masky, ale hodnoty, takˇze test na typ souboru je nutn´e udˇelat takto: (st mode & S IFMT == S IFREG). Vˇsechna makra jsou v normˇe, takˇze jejich pouˇz´ıv´an´ım zaruˇc´ıme pˇrenositeln´ y k´od. • Pˇr´ıklad: stat/filetype.c
107
Nastaven´ı ˇ cas˚ u souboru int utime(const char *path, const struct utimbuf *times ); • nastav´ı ˇcas posledn´ı modifikace souboru a ˇcas posledn´ıho pˇr´ıstupu k souboru. • nelze zmˇenit ˇcas posledn´ı modifikace i-uzlu. • volaj´ıc´ı proces mus´ı m´ıt pr´avo z´apisu pro soubor.
• Tuto funkci pouˇz´ıvaj´ı hlavnˇe kop´ırovac´ı a archivaˇcn´ı programy, aby zajistily stejn´e ˇcasy kopie a origin´ alu (napˇr. tar nebo rsync). • Shellov´e rozhran´ı pro funkci utime pˇredstavuje pˇr´ıkaz touch. Mˇenit takto ˇcas modifikace i-uzlu ale nelze.
108
Test pˇ r´ıstupov´ ych pr´ av: access() int access(const char *path, int amode ); • otestuje, zda volaj´ıc´ı proces m´a k souboru path pr´ava dan´a OR-kombinac´ı konstant v amode: – R_OK . . . test pr´ ava na ˇcten´ı – W_OK . . . test pr´ ava na z´apis – X_OK . . . test pr´ ava na spuˇstˇen´ı – F_OK . . . test existence souboru • na rozd´ıl od stat(), v´ ysledek z´avis´ı na RUID a RGID procesu • toto vol´ an´ı se ned´ a pouˇz´ıt bezpeˇcnˇe, proto ho nikdy nepouˇz´ıvejte procesu
• Vol´ an´ı access aplikuje mechanismus testov´an´ı pˇr´ıstupov´ ych pr´av k zadan´emu souboru pro volaj´ıc´ı proces a vr´at´ı v´ ysledek. • Funkce access vol´ an´ı byla pro setuid proces, aby si mohl ovˇeˇrit, zda uˇzivatel bˇeˇz´ıc´ı dan´ y setuid proces by mˇel za norm´aln´ıch okolnost´ı k pˇr´ısluˇsn´emu souboru pˇr´ıstup. Z toho vypl´ yv´ a, ˇze toto vol´an´ı je security hole – mezi testem a n´ aslednou akc´ı se soubor m˚ uˇze zmˇenit tˇreba tak, ˇze p˚ uvodn´ı (m˚ uj) soubor smaˇzu a vytvoˇr´ım symbolick´ y link se stejn´ ym jm´enem na soubor, ke kter´emu bych jinak nemˇel pˇr´ıstup. Toto proces nem˚ uˇze nikdy oˇsetˇrit, takˇze vesele zmodifikuje soubor, kter´ y bych jako uˇzivatel nikdy nemohl zmˇenit. Spr´ avn´ ym ˇreˇsen´ım je vr´ atit se zp´atky k re´aln´ ym UID/GID a pˇr´ıstup rovnou vyzkouˇset. Pokud napˇr´ıklad dan´ y soubor otevˇreme, uˇz n´am ho nikdo pod rukama “nevymˇen´ı”.
109
Nastaven´ı pˇ r´ıstupov´ ych pr´ av int chmod(const char *path, mode t mode ); • zmˇen´ı pˇr´ıstupov´ a pr´ ava souboru path na hodnotu mode. • tuto sluˇzbu m˚ uˇze volat pouze vlastn´ık souboru nebo superuˇzivatel (root). int chown(const char *path, uid t owner, gid t group ); • zmˇen´ı vlastn´ıka a skupinu souboru path. Hodnota -1 znamen´a zachovat vlastn´ıka, resp. skupinu. • mˇenit vlastn´ıka m˚ uˇze jen superuˇzivatel, aby uˇzivatel´e nemohli obch´ azet nastaven´e quoty t´ım, ˇze sv´e soubory pˇredaj´ı nˇekomu jin´emu. • bˇeˇzn´ y uˇzivatel m˚ uˇze mˇenit skupinu sv´ ych soubor˚ u a mus´ı pˇritom patˇrit do c´ılov´e skupiny.
• parametr mode zde samozˇrejmˇe neobsahuje typ souboru, jako tomu je napˇr´ıklad u vol´ an´ı stat. Hodnoty mode viz chmod(2). • pokud nejsem vlastn´ık, nemohu celkem logicky zmˇenit m´od ani u souboru s nastaven´ ym pˇr´ıstupem rw-rw-rw• v nˇekter´ ych implementac´ıch m˚ uˇze vlastn´ık souboru pˇredat vlastnictv´ı nˇekomu jin´emu, napˇr. v IRIXu je chov´an´ı chown nastaviteln´e jako parametr j´ adra. • nen´ı bˇeˇzn´e volat funkci chmod z uˇzivatelsk´ ych aplikac´ı, na to se pouˇz´ıvaj´ı flagy u vol´ an´ı open, hlavn´ı pouˇzit´ı je v aplikaci chmod(1)
110
Manipulace se jm´ eny soubor˚ u int link(const char *path1, const char *path2 ); • vytvoˇr´ı nov´ y odkaz (poloˇzku adres´aˇre) path2 na soubor path1. Funguje pouze v r´ amci jednoho svazku. int unlink(const char *path ); • zruˇs´ı odkaz na soubor. Po zruˇsen´ı posledn´ıho odkazu na soubor a uzavˇren´ı souboru vˇsemi procesy je soubor smaz´an. int rename(const char *old, const char *new ); • zmˇen´ı jm´eno souboru (pˇresnˇe odkazu na soubor) z old na new. Funguje pouze v r´ amci jednoho svazku.
• opˇet zd˚ urazˇ nuji, ˇze unix nem´ a vol´ an´ı typu delete na soubory. Podrobnˇeji viz strana 73. • vol´ an´ı link vytv´ aˇr´ı hardlinky, tj. zobrazen´ı ze jm´ena souboru na ˇc´ıslo i-uzlu. ˇ ısla i-uzl˚ C´ u jsou jednoznaˇcn´a pouze v r´amci svazku, proto pro linky mezi filesyst´emy je nutn´e pouˇz´ıt symlinky. • parametr path2 nesm´ı existovat, tedy nelze takto pˇrejmenov´avat • unlink nefunguje na adres´ aˇre • shellov´ y pˇr´ıkaz mv pouˇz´ıv´ a rename pro pˇresuny v r´amci jednoho svazku. Pˇresun souboru mezi filesyst´emy vyˇzaduje nejprve soubor zkop´ırovat a pak smazat origin´ al vol´ an´ım unlink. • rename funguje nad symlinky, ne nad soubory, na kter´e symlink ukazuje • existuje i vol´ an´ı remove, viz strana 120.
111
Symbolick´ e linky int symlink(const char *path1, const char *path2 ); • vytvoˇr´ı symbolick´ y link path2 → path1. • c´ıl symbolick´eho linku m˚ uˇze b´ yt i na jin´em svazku, popˇr´ıpadˇe nemus´ı v˚ ubec existovat. int readlink(const char *path, char *buf, size t bufsize ); • do buf d´ a max. bufsize znak˚ u z cesty, na kterou ukazuje symlink path. • vr´ at´ı poˇcet znak˚ u uloˇzen´ ych do buf. • obsah buf nen´ı zakonˇcen nulou (znakem ’\0’)!
• Shellov´ y pˇr´ıkaz ln vol´ a symlink nebo link, podle toho, jestli je pouˇzit pˇrep´ınaˇc -s nebo ne. • Smaz´ an´ı hardlinku nesmaˇze soubor, pokud na nˇej vede jeˇstˇe jin´ y hardlink. Naopak soubor (poloˇzku adres´aˇre i data) je moˇzn´e smazat, i kdyˇz na nˇej ukazuj´ı nˇejak´e symlinky. • readlink je pouˇziteln´ y v situaci, pokud chci smazat soubor, na kter´ y dan´ y symlink ukazuje • bufsize se typicky d´ av´ a o 1 menˇs´ı neˇz velikost bufferu, to pro ukonˇcen´ı znakem ’\0’
112
Manipulace s adres´ aˇ ri int mkdir(const char *path, mode t mode ); • vytvoˇr´ı nov´ y pr´ azdn´ y adres´aˇr, kter´ y bude obsahovat pouze poloˇzky ’.’ a ’..’ int rmdir(const char *path ); • smaˇze adres´ aˇr path. Adres´aˇr mus´ı b´ yt pr´azdn´ y. DIR *opendir(const char *dirname ); struct dirent *readdir(DIR *dirp ); int closedir(DIR *dirp ); • slouˇz´ı k sekvenˇcn´ımu proch´azen´ı adres´aˇr˚ u. • struktura dirent obsahuje poloˇzky – d_ino . . . ˇc´ıslo i-uzlu – d_name . . . jm´eno souboru
• Poloˇzky adres´ aˇre nejsou nijak uspoˇr´ad´any, readdir je m˚ uˇze vracet v libovoln´em poˇrad´ı. V pˇr´ıpadˇe chyby se vrac´ı NULL jako pˇredt´ım a errno je nastaveno. Konec adres´ aˇre se signalizuje t´ım, ˇze readdir vr´at´ı NULL a errno nen´ı zmˇenˇeno. • readdir je stavov´ a funkce. Pro vr´acen´ı se na zaˇc´atek je moˇzn´e pouˇz´ıt funkci rewinddir. Pokud tuto funkci vol´ate z v´ıce vl´aken je nutn´e pouˇz´ıvat reentrantn´ı readdir r, protoˇze struktura dirent je statick´a. • V nˇekter´ ych implementac´ıch (napˇr. FreeBSD) lze adres´aˇr otevˇr´ıt pro ˇcten´ı (ne pro z´ apis) jako norm´ aln´ı soubor a ˇc´ıst ho pomoc´ı read, ale je tˇreba zn´at jeho vnitˇrn´ı organizaci. Proto je readdir pro zpracov´an´ı obsahu adres´aˇre lepˇs´ı neˇz read, kter´ y vrac´ı raw data adres´aˇre. Kromˇe toho, norma nevyˇzaduje, aby adres´ aˇr bylo moˇzn´e ˇc´ıst funkc´ı read, Linux to napˇr´ıklad nedovol´ı. • d ino nen´ı moc uˇziteˇcn´e, protoˇze v pˇr´ıpadˇe, kdy dan´ y adres´aˇr je mount point, tak ukazuje na adres´ aˇr, na kter´ y je dalˇs´ı filesyst´em namontov´an, ne na koˇren namontovan´eho filesyst´emu • Nˇekter´e syst´emy maj´ı ve struktuˇre dirent poloˇzku d type. Ta m˚ uˇze nab´ yvat hodnot DT REG, DT DIR, DT FIFO atd., viz manu´alov´a str´anka pro dirent. Byla to vˇec specifick´ a pro BSD a n´aslednˇe pˇrevzat´a i jin´ ymi syst´emy, napˇr´ıklad Linuxem. Nen´ı to souˇca´st´ı normy a tedy to nen´ı pˇ renositeln´ e. Pˇrenositeln´ y zp˚ usob je na kaˇzdou poloˇzku zavolat stat. • rmdir nefunguje na nepr´ azdn´ y adres´aˇr, mus´ıte sami smazat jeho obsah pˇred smaz´ an´ım adres´ aˇre. Nˇekdy lze v k´odu nal´ezt zoufal´e pokusy typu system("rm
113
-r xxx") coˇz m´ a svoje nev´ yhody (napˇr. z´avislost programu na shellu. D´ale pozor na sanitizaci promˇenn´ ych prostˇred´ı a jm´ena adres´aˇre). • Norma specifikuje i vol´ an´ı remove, kter´e se chov´a jako unlink pro regul´arn´ı soubory, a jako rmdir pro adres´aˇre.
Pˇ r´ıklad: proch´ azen´ı adres´ aˇ re int main(int argc, char *argv[]) { int i; DIR *d; struct dirent *de; for(i = 1; i < argc; i++) { d = opendir(argv[i]); while(de = readdir(d)) printf("%s\n", de->d_name); closedir(d); } return (0); }
• pˇr´ıkaz ls je zaloˇzen na takov´eto smyˇcce, nav´ıc prov´ad´ı napˇr. tˇr´ıdˇen´ı jmen soubor˚ u a zjiˇst’ov´ an´ı dalˇs´ıch informac´ı pomoc´ı stat. • konec adres´ aˇre se pozn´ a tak, ˇze readdir vr´at´ı NULL. To vˇsak vr´at´ı i v pˇr´ıpadˇe, pokud nastala chyba. V takov´em pˇr´ıpadˇe je k´od chyby v promˇenn´e errno, v pˇr´ıpadˇe prvn´ım je errno nezmˇenˇena. Proto by errno mˇelo b´ yt vˇzdy nastaven´e na nulu pˇred vol´ an´ım readdir. V pˇr´ıkladu tomu tak nen´ı, protoˇze z d˚ uvodu m´ alo m´ısta nekontrolujeme errno v˚ ubec. • pˇr´ıklad: readdir/readdir.c
114
Aktu´ aln´ı adres´ aˇ r procesu • kaˇzd´ y proces m´ a sv˚ uj aktu´aln´ı (pracovn´ı) adres´aˇr, v˚ uˇci kter´emu jsou ud´ av´ any relativn´ı cesty k soubor˚ um. Poˇc´ateˇcn´ı nastaven´ı pracovn´ıho adres´ aˇre se dˇed´ı od otce pˇri vzniku procesu. int chdir(const char *path ); int fchdir(int fildes ); • nastav´ı nov´ y pracovn´ı adres´aˇr procesu. char *getcwd(char *buf, size t size ); • uloˇz´ı absolutn´ı cestu k aktu´aln´ımu adres´aˇri do pole buf, jeho d´elka (size) mus´ı b´ yt aspoˇ n o 1 vˇetˇs´ı neˇz d´elka cesty.
• funkci fchdir se pˇred´ av´ a deskriptor z´ıskan´ y vol´an´ım open na adres´aˇr. • funkce getcwd m˚ uˇze vr´ atit jinou cestu neˇz tu, po kter´e jsme se do aktu´aln´ıho adres´ aˇre dostali, jesliˇze ˇc´ ast cesty v chdir byl symlink nebo doˇslo k pˇresunu (pˇrejmenov´ an´ı) nˇekter´eho adres´aˇre na cestˇe od koˇrene. U souˇcasn´ ych unix˚ u by se to ale uˇz st´ at nemˇelo. • na vˇetˇsinˇe modern´ıch unixov´ ych syst´em˚ u existuje funkce chroot, kter´a umoˇzn ˇuje zmˇenit koˇrenov´ y adres´ aˇr volaj´ıc´ıho procesu na dan´ y adres´aˇr. Je ˇcasto pouˇz´ıvan´ a v implementac´ı server˚ u k omezen´ı pˇr´ıstupu pouze na dan´ y podstrom file syst´emu (napˇr. FTP servery). Pokud se pouˇz´ıv´a pro zapezpeˇcen´ı, vyˇzaduje to zvl´ aˇstn´ı opatrnost, protoˇze existuj´ı cesty jak se dostat do p˚ uvodn´ıho stromu. Pokud je nutn´e z chroot prostˇred´ı spouˇstˇet programy, nast´avaj´ı dalˇs´ı komplikace s nutnost´ı replikace kritick´ ych syst´emov´ ych soubor˚ u do chroot prostˇred´ı (pokud nen´ı program upraven aby mˇel vˇse zabudovan´e v sobˇe). Tato funkce nen´ı souˇc´ast´ı aktu´aln´ı verze standardu (POSIX 1003.12008). – dalˇs´ı, mnohem obecnˇejˇs´ı, zp˚ usoby izolace proces˚ u se daj´ı nal´ezt v unixov´ ych syst´emech (jails v FreeBSD, zones v Solarisu, sandboxing v Mac OS X). Zpravidla se velmi liˇs´ı t´ım jak´e hranice dok´aˇz´ı vytyˇcit, k ˇcemu jsou urˇceny, zp˚ usobem administrace, . . . .
115
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇ stˇ en´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
116
Pamˇ et’ procesu v uˇ zivatelsk´ em reˇ zimu text data bss
nelze adresovat uˇziv. programem
{
z´asobn´ık oblast user
adresovateln´e uˇziv. programem
• kaˇzd´ y syst´em m˚ uˇze pouˇz´ıvat zcela jin´e rozdˇelen´ı adresov´eho prostoru procesu (a typicky tomu tak je). Konkr´etn´ı pˇr´ıklad je na n´asleduj´ıc´ım slajdu, a zobrazuje i sekce pro mmap a hromadu (heap). • text . . . k´ od programu • data . . . inicializovan´e promˇenn´e • sekce text a data jsou uloˇzeny ve spustiteln´em souboru • bss . . . neinicializovan´e promˇenn´e (bss poch´az´ı z assembleru IBM 7090 a znamen´ a ,,block started by symbol”). Za bˇehu programu tvoˇr´ı sekce data, bss a heap (nen´ı na obr´ azku) dohromady datov´e segmenty procesu. Velikost hromady lze mˇenit pomoc´ı syst´emov´ ych vol´an´ı brk a sbrk. • pozn´ amka – neinicializovan´e promˇenn´e jsou statick´e promˇenn´e, kter´e (pˇrekvapivˇe) nejsou inicializovan´e – tj. glob´aln´ı promˇenn´e nebo promˇenn´e definovan´e jako static ve funkc´ıch i mimo funkce. Jak v´ıte z pˇredn´aˇsek o jazyku C, vˇsechny tyto promˇenn´e jsou pˇri startu programu automaticky inicializovan´e nulami. Proto nen´ı nutn´e m´ıt jejich hodnotu v bin´arce. Jakmile ale nˇejakou z takov´ ych promˇenn´ ych inicializujete, bude jiˇz souˇc´ast´ı datov´eho segmentu programu na disku. • (uˇzivatelsk´y) z´ asobn´ık . . . lok´aln´ı nestatick´e promˇenn´e, parametry funkc´ı (na urˇcit´ ych architektur´ ach v dan´ ych modech - 32-bit x86), n´avratov´e adresy. Kaˇzd´ y proces m´ a dva z´ asobn´ıky, jeden pro uˇzivatelsk´ y reˇzim a jeden pro reˇzim j´ adra. Uˇzivatelsk´ y z´ asobn´ık procesu automaticky roste podle potˇreby
117
(neplat´ı, pokud se pouˇz´ıvaj´ı vl´akna, tam m´a nav´ıc kaˇzd´e vl´akno z´asobn´ık sv˚ uj). • oblast user (u-area) . . . obsahuje informace o procesu pouˇz´ıvan´e j´adrem, kter´e nejsou potˇrebn´e, kdyˇz je proces odloˇzen na disku (poˇcet otevˇren´ ych soubor˚ u, nastaven´ı oˇsetˇren´ı sign´ al˚ u, poˇcet segment˚ u sd´ılen´e pamˇeti, argumenty programu, promˇenn´e prostˇred´ı, aktu´aln´ı adres´aˇr, atd.). Tato oblast je pˇr´ıstupn´a pouze pro j´ adro, kter´e vˇzdy vid´ı pr´avˇe jednu u-oblast patˇr´ıc´ı pr´avˇe bˇeˇz´ıc´ımu procesu. Dalˇs´ı informace o procesu, kter´e j´adro m˚ uˇze potˇrebovat i pro jin´ y neˇz pr´ avˇe bˇeˇz´ıc´ı proces, nebo i kdyˇz je proces odloˇzen, jsou ve struktuˇre proc. Struktury proc pro vˇsechny procesy jsou st´ale rezidentn´ı v pamˇeti a viditeln´e v reˇzimu j´ adra.
Pˇ r´ıklad: Solaris 11 x86 32-bit
• z obr´ azku lze vyˇc´ıst nˇekolik dalˇs´ıch vˇec´ı: – maxim´ aln´ı velikost kernelu pro Solaris 11 x86 32-bit je 256 megabajt˚ u – mezi namapov´ an´ım kernelu a pamˇet´ı vyhrazenou pro mmap je voln´e m´ısto – z´ asobn´ık roste smˇerem k niˇzˇs´ım adres´am a jeho velikost je omezena na 128 megabajt˚ u • heap je ˇc´ ast pamˇeti, kterou si proces m˚ uˇze zvˇetˇsovat pomoc´ı vol´an´ı brk a sbrk, a je ˇcasto pouˇz´ıvan´ a funkc´ı malloc. Funguje to tak, ˇze malloc si postupnˇe zvˇetˇsuje hromadu podle potˇreby, a pˇriˇrazenou pamˇet’ internˇe spravuje a pˇridˇeluje ji procesu po ˇc´ astech. Kdyˇz tedy vol´ate free, neznamen´a to, ˇze vrac´ıte pamˇet’ syst´emu, ale pouze intern´ımu alok´atoru.
118
• oblast pro mmap se pouˇz´ıv´ a pro mapov´an´ı soubor˚ u, tedy i sd´ılen´ ych knihoven. Nˇekter´e alok´ atory pouˇz´ıvaj´ı internˇe i tuto pamˇet’, napˇr´ıklad pro vˇetˇs´ı kusy pamˇeti ˇz´ adan´e najednou. Je moˇzn´e exkluzivnˇe pouˇz´ıvat pouze mmap, pro aplikaci je to zcela transparentn´ı. Pˇri pouˇzit´ı mmap je moˇzn´e pamˇet’ syst´emu vracet (vol´ an´ım munmap), na rozd´ıl od implementace pomoc´ı brk/sbrk. • obr´ azek byl pˇrevzat z [McDougall-Mauro], a nen´ı na nˇem oblast pro neinicializovan´e promˇenn´e. Pokud si ale na tomto syst´emu nech´ate vypsat adresu jedn´e z nich, zjist´ıte, ˇze inicializovan´e i neinicializovan´e promˇenn´e sd´ıl´ı spoleˇcn´ y datov´ y segment, na obr´azku oznaˇcen´ y jako ,,executable – DATA”. Pˇr´ıklad: pmap/proc-addr-space.c • mapov´ an´ı kernelu nen´ı nutn´e, napˇr´ıklad u Solarisu na amd64 architektuˇre (tj. 64-bit) uˇz kernel do uˇzivatelsk´eho prostoru procesu mapov´an nen´ı. • brk ani sbrk nejsou souˇc´ ast´ı normy, pˇrenositeln´e aplikace by proto mˇely pouˇz´ıvat, pokud podobnou funkcionalitu potˇrebuj´ı, vol´an´ı mmap, viz strana 146.
Pamˇ et’ procesu v reˇ zimu j´ adra text j´adra data a bss j´adra (tabulky, promˇenn´e, apod.) struktura user beˇz´ıc´ıho procesu
extern struct user u; z´asobn´ık j´adra
• proces se dostane do reˇzimu j´adra bud’ pˇr´ıchodem pˇreruˇsen´ı vyvolan´eho procesorem (v´ ypadek str´ anky, nezn´am´a instrukce,...), ˇcasovaˇcem (v pravideln´ ych intervalech je potˇreba aktivovat pl´anovaˇc proces˚ u), perifern´ım zaˇr´ızen´ım, nebo instrukc´ı synchronn´ıho pˇreruˇsen´ı (standardn´ı knihovna takto pˇred´av´a ˇr´ızen´ı j´ adru, aby obslouˇzilo syst´emov´e vol´ an´ı). • v pamˇeti je pouze jedna kopie k´odu a dat j´adra, sd´ılen´a vˇsemi procesy. K´od j´ adra je vˇzdy cel´ y rezidentn´ı v pamˇeti, nen´ı odkl´ad´an na disk. 119
• text j´ adra . . . k´ od j´ adra operaˇcn´ıho syst´emu, zaveden´ y pˇri startu syst´emu a rezidentn´ı v pamˇeti po celou dobu bˇehu syst´emu. Nˇekter´e implementace umoˇzn ˇuj´ı pˇrid´ avat funkˇcn´ı moduly do j´adra za bˇehu (napˇr. pˇri pˇrid´an´ı nov´eho zaˇr´ızen´ı se do j´ adra dynamicky pˇrid´a nov´ y ovladaˇc), nen´ı proto tˇreba kv˚ uli kaˇzd´e zmˇenˇe regenerovat j´ adro a restartovat syst´em. • data a bss j´ adra . . . datov´e struktury pouˇz´ıvan´e j´adrem, souˇc´ast´ı je i u-oblast pr´ avˇe bˇeˇz´ıc´ıho procesu. • z´ asobn´ık j´ adra . . . samostatn´ y pro kaˇzd´ y proces, je pr´azdn´ y, jestliˇze je proces v uˇzivatelsk´em reˇzimu (a tedy pouˇz´ıv´a uˇzivatelsk´ y z´asobn´ık).
Pamˇ et’ov´ e segmenty procesu a segs struct as
read only shared
s as s prev
text
s next
read/write private
data
read/write private
z´asobn´ık
read/write shared
sd´ılen´a pamˇet’
struct proc
• kaˇzd´ y proces m´ a tˇri z´ akladn´ı segmenty (pamˇet’ov´e segmenty, nemluv´ıme o hardwarov´ ych segmentech): – text – data – z´ asobn´ık • sekce pro inicializovan´e i neinicializovan´e promˇenn´e a hromada jsou br´any dohromady jako data • d´ ale lze do adresov´eho prostoru pˇripojit segmenty sd´ılen´e pamˇeti (shmat) nebo soubory (mmap). • text je sd´ılen vˇsemi procesy, kter´e prov´ad´ı stejn´ y k´od. Datov´ y segment a z´ asobn´ık jsou priv´ atn´ı pro kaˇzd´ y proces. 120
• z´ akladn´ım rysem t´eto architektury je tzv. memory object, coˇz je abstrakce mapov´ an´ı mezi kusem pamˇeti a m´ıstem, kde jsou data norm´alnˇe uloˇzena (tzv. backing store nebo data object). Takov´e m´ısto uloˇzen´ı m˚ uˇze b´ yt napˇr´ıklad swap nebo soubor. Adresov´ y prostor procesu je pak mnoˇzina mapov´an´ı na r˚ uzn´e datov´e objekty. Existuje i anonymn´ı objekt, kter´ y nem´a m´ısto trval´eho uloˇzen´ı (pouˇz´ıv´ a se napˇr´ıklad pro z´asobn´ık). Fyzick´a pamˇet’ pak slouˇz´ı jako cache pro data tˇechto namapovan´ ych datov´ ych objekt˚ u. • tato zde velmi hrubˇe popsan´a architektura se naz´ yv´a VM (od Virtual Memory), a objevila se v SunOS 4.0. Na t´eto architektuˇre je zaloˇzena architektura viru´ aln´ı pamˇeti v SVR4. V´ıce informac´ı viz [Vahalia], p˚ uvodn´ı ˇcl´anek z roku 1987 pˇredstavuj´ıc´ı tuto architekturu: Gingell, R. A., Moran J. P., Shannon, W. A. – Virtual Memory Architecture in SunOS nebo pˇredn´aˇska o operaˇcn´ıch syst´emech na MFF (NSWI004 - Operaˇcn´ı syst´emy). • z jak´ ych segment˚ u se skl´ ad´ a pamˇet’ov´ y prostor konkr´etn´ıho procesu lze zjistit pomoc´ı pˇr´ıkazu pmap(1) (na Solarisu, na NetBSD a v nˇekter´ ych Linuxov´ ych distribuc´ıch) nebo procmap(1) (v OpenBSD) nebo vmmap(1) v Mac OS X.
Virtu´ aln´ı pamˇ et’ pamˇet’ procesu 1 000 111
111 000 000 111 000 111 000 111 111 000 000 111 000 111
re´aln´a pamˇet’
pamˇet’ procesu 3
pamˇet’ procesu 2
111 000 000 111 000 111 000 111 000 111
111 000 000 111 000 111 000 111
1 0 00 11 0 1 00 11 0 1 00 11 0 1 00 11 0 1 00 11 0 1 00 011 1 00 11
11 00 00 11 00 11 00 11 00 11 00 11 00 11
pamˇet’ procesu 4
1111111 0000000 0000000 1111111 1111111 0000000 111 000 000 111 000 111 000 111
1 0 0 1 0 1 0 1 0 1 0 1 0 1
• kaˇzd´ y proces vid´ı sv˚ uj adresov´ y prostor jako souvisl´ y interval (virtu´aln´ıch) adres od nuly po nˇejakou maxim´aln´ı hodnotu. Pˇr´ıstupn´e jsou pouze ty adresy, na kter´ ych je namapov´ an nˇekter´ y segment procesu (to je pr´avˇe to mapov´an´ı, o kter´em se mluv´ı na pˇredchoz´ım slajdu). • j´ adro d´ ale rozdˇeluje pamˇet’ procesu na str´anky. Kaˇzd´a str´anka m´a sv´e um´ıstˇen´ı v r´ amci fyzick´e pamˇeti. Toto um´ıstˇen´ı je d´ano str´ankovac´ımi tabulkami
121
j´ adra a str´ anky mohou b´ yt v r´amc´ıch libovolnˇe prom´ıch´any v˚ uˇci jejich poˇrad´ı ve virtu´ aln´ı adresov´em prostoru. • pokud nen´ı str´ anka pr´ avˇe pouˇz´ıv´ana, m˚ uˇze b´ yt tak´e odloˇzena na disk. • pamˇet’ov´ y manager j´ adra zajiˇst’uje mapov´an´ı mezi virtu´aln´ımi adresami pouˇz´ıvan´ ymi k´ odem uˇzivatelsk´ ych proces˚ u i j´adra na fyzick´e adresy a naˇcten´ı odloˇzen´ ych str´ anek z disku pˇri v´ ypadku str´anky.
Implementace virtu´ aln´ı pamˇ eti • procesy v UNIXu pouˇz´ıvaj´ı k pˇr´ıstupu do pamˇeti virtu´aln´ı adresy, kter´e na fyzick´e adresy pˇrev´ad´ı hardware ve spolupr´aci s j´ adrem syst´emu. • pˇri nedostatku voln´e pamˇeti se odkl´adaj´ı nepouˇz´ıvan´e u ´seky pamˇeti do odkl´ adac´ı oblasti (swap) na disk. • pˇred verz´ı SVR2 se procesem swapper (nyn´ı sched) odkl´adaly cel´e procesy. • od verze SVR2 se pouˇz´ıv´a str´ankov´an´ı na ˇz´adost (demand paging) a copy-on-write. Str´anky se alokuj´ı aˇz pˇri prvn´ım pouˇzit´ı a priv´ atn´ı str´ anky se kop´ıruj´ı pˇri prvn´ı modifikaci. Uvolˇ nov´ an´ı a odkl´ ad´ an´ı jednotliv´ ych str´anek prov´ad´ı proces pageout, odkl´ ad´ an´ı cel´ ych proces˚ u nastupuje aˇz pˇri kritick´em nedostatku pamˇeti.
pˇ reklad adres: pˇr´ıstup na neplatnou adresu nebo pokus o z´apis do pamˇeti pouze pro ˇcten´ı vyvol´ a sign´ al SIGSEGV. swap: odkl´ adac´ı prostor se vytv´ aˇr´ı na samostatn´em odd´ılu disku, od SVR4 m˚ uˇze b´ yt i v souboru. swapper: proces swapper se snaˇz´ı odloˇzit na disk nˇejak´ y proces, kter´ y nen´ı zamˇcen v pamˇeti, a na uvolnˇen´e m´ısto zav´est dˇr´ıve odloˇzen´ y proces. demand paging: pˇri ˇz´ adosti procesu o pamˇet’ se pouze uprav´ı tabulka str´anek. Prvn´ı instrukce adresuj´ıc´ı obsah str´anky vyvol´a v´ yjimku. J´adro ji oˇsetˇr´ı t´ım, ˇze alokuje str´ anku. copy-on-write: v´ıce proces˚ u m˚ uˇze sd´ılet zapisovatelnou fyzickou str´anku, kter´a je ale logicky priv´ atn´ı pro kaˇzd´ y proces (tato situace nastane napˇr. po vytvoˇren´ı procesu vol´ an´ım fork). Dokud procesy z pamˇeti pouze ˇctou, pˇristupuj´ı ke sd´ılen´e str´ ance. Pokud se proces pokus´ı obsah str´anky zmˇenit, vyvol´a v´ yjimku. J´ adro zkop´ıruje str´ anku, pˇridˇel´ı procesu kopii, kter´a uˇz je priv´atn´ı a proces ji m˚ uˇze d´ ale libovolnˇe mˇenit. Ostatn´ı procesy pouˇz´ıvaj´ı st´ale nezmˇenˇenou p˚ uvodn´ı str´ anku. 122
str´ anky k odloˇzen´ı se hledaj´ı algoritmem NRU (not recently used): kaˇzd´a str´anka m´ a pˇr´ıznaky referenced a modified, na zaˇc´atku vynulovan´e. Pˇri prvn´ım pˇr´ıstupu se nastav´ı referenced, pˇri zmˇenˇe modified. Oba pˇr´ıznaky se periodicky nuluj´ı. Pˇrednostnˇe se uvolˇ nuj´ı str´anky, kter´e nejsou modifikovan´e ani pouˇzit´e. Str´ anky k´ odu programu a mapovan´ ych soubor˚ u se neukl´adaj´ı do odkl´ adac´ıho prostoru, ale obnovuj´ı se z pˇr´ısluˇsn´eho souboru.
Stavy procesu bˇeˇz´ı v uˇziv. reˇzimu
vznik
*
pˇreruˇsen´ı
n´avrat
z´anik
†
wait
exit kill
m´atoha (zombie)
preempce pˇripraven v pamˇeti odloˇzen´ı
bˇeˇz´ı v reˇzimu j´adra pˇridˇelen´ı procesoru
zaveden´ı
pˇripraven na disku
probuzen´ı
probuzen´ı
usp´an´ı sp´ı v pamˇeti odloˇzen´ı sp´ı na disku
• po ukonˇcen´ı procesu vol´ an´ım exit nebo v reakci na sign´al pˇrech´az´ı proces do stavu m´ atoha (zombie), protoˇze j´adro si mus´ı pamatovat k ˇc´ıslu procesu jeho n´ avratovou hodnotu. Cel´ a pamˇet’ procesu je uvolnˇena, zb´ yv´a pouze struktura proc. Proces lze definitivnˇe zruˇsit, aˇz kdyˇz se jeho rodiˇc zept´a na n´avratovou hodnotu vol´ an´ım typu wait. Kdyˇz nebude p˚ uvodn´ı rodiˇc k dispozici, provede vol´ an´ı wait proces init, kter´ y pˇrevzal rodiˇcovst´ı. • v dneˇsn´ıch UNIXech se obvykle do odkl´adac´ı oblasti na disku (swap area) neodkl´ adaj´ı cel´e procesy, ale jednotliv´e str´anky pamˇeti. • proces je usp´ an, kdyˇz o to s´ am poˇz´ad´a, napˇr. zaˇcne ˇcekat na dokonˇcen´ı perifern´ı operace. Preempce je naopak nedobrovoln´e odebr´an´ı procesoru pl´anovaˇcem.
123
Pl´ anov´ an´ı proces˚ u • preemptivn´ı pl´ anov´ an´ı – jestliˇze se proces nevzd´a procesoru (neusp´ı se ˇcek´ an´ım na nˇejakou ud´alost), je mu odebr´an procesor po uplynut´ı ˇcasov´eho kvanta. • procesy jsou zaˇrazeny do front podle priority, procesor je pˇridˇelen vˇzdy prvn´ımu pˇripraven´emu procesu z fronty, kter´a m´ a nejvyˇsˇs´ı prioritu. • v SVR4 byly zavedeny prioritn´ı tˇr´ıdy a podpora proces˚ u re´ aln´eho ˇcasu (real-time) s garantovanou maxim´aln´ı dobou odezvy. • na rozd´ıl od pˇredchoz´ıch verz´ı znamen´a v SVR4 vyˇsˇs´ı ˇc´ıslo vyˇsˇs´ı prioritu.
• z´ akladem preemptivn´ıho pl´ anov´ an´ı jsou pravideln´ a pˇ reruˇ sen´ı od ˇ casovaˇ ce, kter´ a odeberou procesor bˇeˇz´ıc´ımu procesu a pˇredaj´ı ˇr´ızen´ı j´adru (aktivuje se pl´ anovaˇc proces˚ u). • jin´ a varianta je nepreemptivn´ı (kooperativn´ı) pl´anov´an´ı, kdy proces bˇeˇz´ı, dokud se s´ am nevzd´ a procesoru, tj. dokud nezavol´a takovou syst´emovou funkci, kter´ a pˇrepne kontext na jin´ y proces. Nev´ yhodou kooperativn´ıho pl´anov´an´ı je, ˇze jeden proces m˚ uˇze st´ ale blokovat procesor a ostatn´ı procesy se nikdy nedostanou na ˇradu. • UNIX pouˇz´ıv´ a pro uˇzivatelsk´e procesy pouze preemptivn´ı pl´anov´an´ı. • tradiˇcn´ı UNIXov´e j´ adro funguje kooperativn´ım zp˚ usobem, tj. proces bˇeˇz´ıc´ı v reˇzimu j´ adra nen´ı pˇrepl´ anov´an, dokud se s´am nevzd´a procesoru. J´ adra modern´ıch UNIX˚ u jsou jiˇ z preemtivn´ı – je to hlavnˇe kv˚ uli real-time syst´em˚ um; tam je potˇreba m´ıt moˇznost bˇeˇz´ıc´ı proces zbavit procesoru okamˇzitˇe, neˇcekat na to, aˇz se vr´at´ı z reˇzimu j´adra nebo se s´am usp´ı. Pozor na at’ to nepletete – UNIX byl od sam´eho zaˇc´atku preemptivn´ı syst´em, ale jeho j´ adro bylo nepreemptivn´ı. • pˇri preemptivn´ım pl´ anov´ an´ı m˚ uˇze b´ yt proces kdykoliv pˇreruˇsen a ˇr´ızen´ı pˇred´ ano jin´emu procesu. Proces si proto nikdy nem˚ uˇze b´ yt jist´ y, ˇze urˇcitou operaci (v´ıce neˇz jednu intstrukci, kromˇe syst´emov´ ych vol´an´ı se zaruˇcenou atomiˇcnost´ı) provede atomicky, bez ovlivnˇen´ı ostatn´ımi procesy. Pokud je tˇreba zajistit atomiˇcnost nˇejak´e akce, mus´ı se procesy navz´ajem synchronizovat. Pˇri kooperativn´ım pl´ anov´an´ı probl´em synchronizace odpad´a (atomick´a posloupnost operac´ı se zajist´ı t´ım, ˇze se proces bˇehem n´ı nevzd´a procesoru). 124
Prioritn´ı tˇ r´ıdy • syst´ emov´ a – priorita 60 aˇz 99 – rezervov´ ana pro syst´emov´e procesy (pageout, sched, . . . ) – pevn´ a priorita • real-time – priorita 100 aˇz 159 – pevn´ a priorita – pro kaˇzdou hodnotu priority definov´ano ˇcasov´e kvantum • sd´ılen´ı ˇ casu (time-shared) – priorita 0 aˇz 59 – promˇenn´ a dvousloˇzkov´a priorita, pevn´a uˇzivatelsk´a a promˇenn´ a syst´emov´a ˇc´ast – pokud proces hodnˇe vyuˇz´ıv´a procesor, je mu sniˇzov´ana priorita (a zvˇetˇsov´ano ˇcasov´e kvantum)
• syst´emov´ a tˇr´ıda je pouˇz´ıv´ ana pouze j´adrem, uˇzivatelsk´ y proces bˇeˇz´ıc´ı v reˇzimu j´ adra si ponech´ av´ a svou pl´anovac´ı charakteristiku. • procesy ve tˇr´ıdˇe re´ aln´eho ˇcasu maj´ı nejvyˇsˇs´ı prioritu, proto mus´ı b´ yt spr´avnˇe nakonfigurov´ any, aby nezablokovaly zbytek syst´emu. • jestliˇze je proces ve tˇr´ıdˇe sd´ılen´ı ˇcasu usp´an a ˇcek´a na nˇejakou ud´alost, je mu doˇcasnˇe pˇriˇrazena syst´emov´ a priorita. Po probuzen´ı se takov´ y proces dostane na procesor dˇr´ıve, neˇz ostatn´ı procesy, kter´e nesp´ı. • pevn´ a ˇc´ ast priority procesu ve tˇr´ıdˇe sd´ılen´ı ˇcasu se d´a nastavit pomoc´ı int setpriority(int which, id t who, int prio ); nebo int nice(int incr ); Hodnota which ud´ av´ a co bude v argumentu who. Pokud je napˇr. which PRIO PGRP, bude v who ˇc´ıslo skupiny proces˚ u. Pozor na vol´an´ı nice kter´e vrac´ı novou hodnotu nice. Protoˇze je -1 validn´ı hodnota, je potˇreba pˇred vol´ an´ı vyˇcistit errno a pokud funkce vr´at´ı -1 tak ji zkontrolovat. • prioritn´ı tˇr´ıdu a hodnotu nice dan´eho procesu lze zobrazit pˇrep´ınaˇcem -l programu ps(1) nebo pomoc´ı specifikace hodnot kter´e se maj´ı vypsat. • Pˇr´ıklad: hodnoty priority maj´ı na r˚ uzn´ ych syst´emech r˚ uzn´e ˇsk´aly. Napˇr. na Mac OS X 10.9 m´ a spuˇstˇen´ y proces hodnotu priority 30, po zv´ yˇsen´ı hodnoty nice (proces je ”hodnˇejˇs´ı”na ostatn´ı procesy a dobrovolnˇe tedy sn´ıˇzil svoji prioritu) se mu hodnota prorita sn´ıˇz´ı na 21 coˇz na Mac OS X znamen´a i sn´ıˇzen´ı priority: 125
$ sleep 200 & [1] 36877 $ ps -O pri,nice -p $! PID PRI NI TT STAT 36877 31 0 s003 S $ renice 10 -p $! $ ps -O pri,nice -p $! PID PRI NI TT STAT 36877 21 10 s003 SN
TIME COMMAND 0:00.00 sleep 200
TIME COMMAND 0:00.00 sleep 200
Na Linuxu 3.10 to ale bude vypadat jinak - po zv´ yˇsen´ı hodnoty nice se zv´ yˇs´ı i hodnota priority, to ale v t´eto verzi znamen´a, ˇze proces bude bˇeˇzet s prioritou niˇzˇs´ı.
Skupiny proces˚ u, ˇ r´ızen´ı termin´ al˚ u • kaˇzd´ y proces patˇr´ı do skupiny proces˚ u, tzv. process group • kaˇzd´ a skupina m˚ uˇze m´ıt vedouc´ı proces, tzv. group leader • kaˇzd´ y proces m˚ uˇze m´ıt ˇr´ıd´ıc´ı termin´al (je to obvykle login termin´ al), tzv. controlling terminal • speci´ aln´ı soubor /dev/tty je asociov´an s ˇr´ıd´ıc´ım termin´alem kaˇzd´eho procesu • kaˇzd´ y termin´ al je asociov´an se skupinou proces˚ u, tato skupina se naz´ yv´ a ˇr´ıd´ıc´ı skupina (controlling group) • kontrola job˚ u (job control ) je mechanizmus, jak pozastavovat a probouzet skupiny proces˚ u a ˇr´ıdit jejich pˇr´ıstup k termin´al˚ um • session (relace) je kolekce skupin proces˚ u vytvoˇren´a pro u ´ˇcely ˇr´ızen´ı job˚ u
• kdyˇz se uˇzivatel pˇrihl´ as´ı do syst´emu, je vytvoˇren´a nov´a relace, kter´a se skl´ad´a z jedn´e skupiny proces˚ u, ve kter´e je jeden proces – ten kter´ y vykon´av´a uˇzivatel˚ uv shell. Tento proces je z´aroveˇ n vedouc´ı t´eto jedin´e skupiny proces˚ u a tak´e je vedouc´ı relace. V pˇr´ıpadˇe, ˇze job control je povolen, kaˇzd´ y pˇr´ıkaz nebo kolona pˇr´ıkaz˚ u vytvoˇr´ı novou skupinu proces˚ u, jeden z proces˚ u v kaˇzd´e skupinˇe se vˇzdy stane vedouc´ım procesem dan´e skupiny. Jedna ze skupin m˚ uˇze bˇeˇzet na popˇred´ı, ostatn´ı bˇeˇz´ı na pozad´ı. Sign´aly, kter´e jsou generovan´e z kl´ avesnice (tj. stiskem kombinace kl´aves, nemysl´ı se t´ım spuˇstˇen´ı pˇr´ıkazu kill!), jsou zasl´ any pouze skupinˇe, kter´a bˇeˇz´ı na popˇred´ı. • pokud job control nen´ı zapnut, znamen´a spustˇen´ı pˇr´ıkazu na pozad´ı pouze to, ˇze shell neˇcek´ a na jeho ukonˇcen´ı. Existuje pouze jedna skupina proces˚ u,
126
sign´ aly z kl´ avesnice se pos´ılaj´ı vˇsem proces˚ um beˇz´ıc´ım na popˇred´ı i na pozad´ı. Nelze pˇresouvat procesy z pozad´ı na popˇred´ı a naopak. • kdyˇz proces, kter´ y m´ a kontroln´ı termin´al, otevˇre soubor /dev/tty, tak se asociuje se sv´ ym kontroln´ım termin´alem. Tj. pokud dva r˚ uzn´e procesy z r˚ uzn´ ych relac´ı otevˇrou tento soubor, pˇristupuj´ı oba k r˚ uzn´ ym termin´al˚ um. • v bashi se skupina proces˚ u (job) pozastav´ı pomoc´ı Ctrl-Z, a rozbˇehne pˇres ,,fg %N” kde N je ˇc´ıslo jobu podle v´ ypisu pˇr´ıkazu jobs. V´ıce informac´ı viz sekce ,,JOB CONTROL” v manu´alov´e str´ance pro bash.
Identifikace procesu pid t getpid(void); • vrac´ı proces ID volaj´ıc´ıho procesu. pid t getpgrp(void); • vrac´ı ID skupiny proces˚ u, do kter´e patˇr´ı volaj´ıc´ı proces. pid t getppid(void); • vrac´ı proces ID rodiˇce. pid t getsid(pid t pid ); • vrac´ı group ID vedouc´ıho procesu session (sezen´ı, termin´alov´e relace) pro proces pid (0 znamen´a pro volaj´ıc´ı proces)
skupiny proces˚ u umoˇzn ˇuj´ı pos´ılat sign´aly najednou cel´e skupinˇe. session (relace, sezen´ı) je kolekce proces˚ u vytvoˇren´a pro u ´ˇcely ˇr´ızen´ı prac´ı (job control ). Procesy sezen´ı sd´ılej´ı jeden ˇr´ıd´ıc´ı termin´ al. Session zahrnuje jednu nebo v´ıce skupin proces˚ u. Max. jedna skupina v r´amci sezen´ı bˇeˇz´ı na popˇred´ı (foreground process group) a m´a pˇr´ıstup k ˇr´ıdic´ımu termin´alu pro vstup i v´ ystup, ostatn´ı bˇeˇz´ı na pozad´ı (background process groups) a maj´ı k ˇr´ıdic´ımu termin´ alu pˇr´ıstup volitelnˇe jen pro v´ ystup nebo v˚ ubec (nepovolen´a operace s termin´ alem pozastav´ı proces). rodiˇ covsk´ y proces: Kaˇzd´ y proces (kromˇe swapperu, pid == 0) m´a rodiˇce, tj. proces, kter´ y ho stvoˇril vol´ an´ım fork. Jestliˇze rodiˇc skonˇc´ı dˇr´ıve neˇz d´ıtˇe, adoptivn´ım rodiˇcem se st´ av´ a proces init s pid == 1, kter´ y se tak´e postar´a o uklizen´ı zombie po skonˇcen´ı procesu. • Pozor na to, ˇze zjiˇst’ovat ˇze rodiˇc skonˇcil pomoc´ı kontroly hodnoty vr´acen´e z getppid na hodnotu 1 (coˇz je bˇeˇznˇe proces init resp. jeho ekvivalent), 127
nen´ı portabiln´ı. V r˚ uzn´ ych virtualizovan´ ych prostˇred´ıch (PID namespaces v Linux, Zones v Solarisu) to nemus´ı platit. Viz pˇr´ıklad session/getppid.c .
Nastaven´ı skupiny/sezen´ı int setpgid(pid t pid, pid t pgid ); • nastav´ı process group ID procesu s pid na pgid. pid t setsid(void); • vytvoˇr´ı novou session, volaj´ıc´ı proces se stane vedouc´ım sezen´ı i skupiny proces˚ u.
• pro vol´ an´ı setpgid plat´ı: 1. pid == pgid : proces s pid se stane vedouc´ım skupiny proces˚ u 2. pid != pgid : proces s pid se stane ˇclenem skupiny proces˚ u • Proces, kter´ y jeˇstˇe nen´ı vedouc´ım skupiny proces˚ u, se m˚ uˇze st´at vedouc´ım sezen´ı a z´ aroveˇ n skupiny proces˚ u vol´an´ım setsid. Jestliˇze proces uˇz je vedouc´ım skupiny, setsid selˇze, pak je tˇreba prov´est fork a setsid zavolat v synovsk´em procesu. Takov´ y proces nem´a ˇr´ıdic´ı termin´al, m˚ uˇze ho z´ıskat otevˇren´ım termin´ alu, kter´ y jeˇstˇe nen´ı ˇr´ıdic´ım termin´alem sezen´ı, kdyˇz pˇri open neuvede pˇr´ıznak O NOCTTY, nebo jin´ ym implementaˇcnˇe z´avisl´ ym zp˚ usobem.
128
Vytvoˇ ren´ı procesu: fork() getpid() == 1234 switch(pid = case -1: case 0: default: } rodiˇc (pokraˇcuje)
fork()) { /* Chyba */ /* D´ ıtˇ e */ /* Rodiˇ c */
d´ıtˇe (nov´y proces)
getpid()==1234, pid==2345 switch(pid = fork()) { case -1: /* Chyba */ case 0: /* D´ ıtˇ e */ default: /* Rodiˇ c */ }
getpid()==2345, pid==0 switch(pid = case -1: case 0: default: }
fork()) { /* Chyba */ /* D´ ıtˇ e */ /* Rodiˇ c */
• d´ıtˇe je t´emˇeˇr pˇresnou kopi´ı rodiˇce. Z´akladn´ı atributy, kter´e se liˇs´ı, jsou: – PID procesu a parent PID – pokud mˇel rodiˇc v´ıce vl´aken, m´a syn pouze to, kter´e fork zavolalo; o tom v´ıce na stranˇe 229, aˇz se dostaneme k vl´akn˚ um. Tyto informace by kaˇzdop´ adnˇe mˇely b´ yt v manu´alov´e str´ance fork(2). – u ´ˇctovac´ı ˇcasy se nastav´ı na 0 – nedˇed´ı se nastaven´ı alarm a z´amky soubor˚ u • co je d˚ uleˇzit´e je, ˇze tabulky deskriptor˚ u jsou stejn´e v obou procesech, ˇc´ımˇz v´ıce proces˚ u m˚ uˇze sd´ılet a posunovat spoleˇcnou pozici v souboru, a tak´e ˇze masky sign´ al˚ u se nemˇen´ı, viz strana 164. • pro urychlen´ı a menˇs´ı spotˇrebu pamˇeti se adresov´ y prostor nekop´ıruje, ale pouˇz´ıv´ a se mechanizmus copy-on-write. • je logick´e, ˇze pˇri u ´spˇeˇsn´em proveden´ı vol´an´ı otec dostane jako n´avratovou hodnotu vol´ an´ı fork PID syna a syn ˇc´ıslo 0; syn si totiˇz m˚ uˇze velmi jednoduˇse sv˚ uj vlastn´ı PID zjistit vol´an´ım getpid. Otec ale neum´ı jednoduˇse zjistit PID syna, nav´ıc v situaci, pokud jiˇz dˇr´ıve vytvoˇril syny jin´e. • pˇr´ıklad: fork/fork.c • zaj´ımavost´ı m˚ uˇze b´ yt vol´ an´ı vfork, kter´ ym se kdysi obch´azel probl´em, kdy draze vytvoˇren´ y proces byl okamˇzitˇe pˇreps´an n´asledn´ ym vol´an´ım exec. Tento probl´em byl d´ avno vyˇreˇsen jiˇz zm´ınˇen´ ym pouˇzit´ım mechanizmu copy-onwrite, ale pˇr´ıklad fork/vfork.c ukazuje, jak toto vol´an´ı fungovalo. 129
Spuˇ stˇ en´ı programu: exec extern char **environ; int execl(const char *path, const char *arg0, ... ); • spust´ı program definovan´ y celou cestou path, dalˇs´ı argumenty se pak pˇredaj´ı programu v parametrech argc a argv funkce main. Seznam argument˚ u je ukonˇcen pomoc´ı (char *)0, tj. NULL. arg0 by mˇel obsahovat jm´eno programu (tj. ne celou cestu) • u ´spˇeˇsn´e vol´ an´ı execl se nikdy nevr´at´ı, protoˇze spuˇstˇen´ y program zcela nahrad´ı dosavadn´ı adresov´ y prostor procesu. • program dˇed´ı promˇenn´e prostˇred´ı, tj. obsah environ. • handlery sign´ al˚ u se nahrad´ı implicitn´ı obsluhou • zavˇrou se deskriptory soubor˚ u, kter´e maj´ı nastaven´ y pˇr´ıznak FD CLOEXEC (implicitnˇe nen´ı nastaven).
• o sign´ alech v´ıce na stranˇe 153 • v path mus´ı b´ yt cel´ a (absolutn´ı nebo relativn´ı) cesta ke spustiteln´emu souboru. Obsah promˇenn´e prostˇred´ı PATH se pouˇz´ıv´a jen pˇri vol´an´ıch execlp a execvp, kdyˇz argument path neobsahuje znak ’/’. • nˇekdy se pouˇz´ıv´ a argv[0] r˚ uzn´e od jm´ena spustiteln´eho souboru. Napˇr. login vloˇz´ı na zaˇc´ atek jm´ena spouˇstˇen´eho shellu znak ’-’. Shell podle toho pozn´ a, ˇze m´ a fungovat jako login-shell, tj. napˇr´ıklad naˇc´ıst /etc/profile. • exec nepˇred´ a kontrolu naˇcten´emu programu v pamˇeti pˇr´ımo, ale pˇres dynamick´ y linker (t´eˇz naz´ yvan´ y loader ), pot´e co ho (= toho loader a) namapuje do adresov´eho prostoru procesu. Loader n´aslednˇe namapuje potˇrebn´e dynamick´e objekty a teprve pot´e pˇred´a kontrolu aplikaci. Viz tak´e strana 36. Pro jednoduch´e ovˇeˇren´ı staˇc´ı vytvoˇrit program obsahuj´ıc´ı pouze tˇreba vol´an´ı open. Tento program pak spust’te pomoc´ı truss(1) takto: truss ./a.out. Uvid´ıte, kter´ a vol´ an´ı se pouˇzij´ı jeˇstˇe pˇredt´ım, neˇz se zavol´a open. • exec nezmˇen´ı hodnoty RUID a RGID. A pokud je to program s nastaven´ ym SUID bitem, tak se EUID a uschovan´e UID nastav´ı na UID majitele spustiteln´eho souboru. • dneˇsn´ı unixov´e syst´emy um´ı spouˇstˇet i skripty, kter´e zaˇc´ınaj´ı ˇr´adkem #!/interpreter path /interpreter name [args]
130
Varianty sluˇ zby exec int execv(const char *path, char *const argv []); • obdoba execl(), ale argumenty jsou v poli argv, jehoˇz posledn´ı prvek je NULL. int execle(const char *path, const char *arg0, ... , char *const envp []); • obdoba execl(), ale m´ısto environ se pouˇzije envp. int execve(const char *path, char *const argv [], char *const envp []); • obdoba execv(), ale m´ısto environ se pouˇzije envp. int execlp(const char *file, const char *arg0, ...); int execvp(const char *file, char *const argv []); • obdoby execl() a execv(), ale pro hled´an´ı spustiteln´eho souboru se pouˇzije promˇenn´a PATH.
• l = list (tj. line´ arn´ı seznam argument˚ u), v = vector (tj. pole ukazatel˚ u na ˇretˇezce), e = environment (tj. pˇred´avaj´ı se promˇenn´e prostˇred´ı), p = PATH. • kromˇe execlp a execvp je nutn´e jm´eno programu vˇzdy zad´avat celou cestu. • vˇsechny varianty kromˇe execle a execve pˇred´avaj´ı spouˇstˇen´emu programu tak´e sv´e aktu´ aln´ı prostˇred´ı, tj. obsah pole environ. • z mˇe nezn´ am´ ych historick´ ych d˚ uvod˚ u neexistuje vol´an´ı s p a e dohromady. • pˇr´ıklad: exec/exec-date.c • n´ asleduj´ıc´ı pouˇzit´ı vol´ an´ı execl je ˇspatnˇe, protoˇze mu chyb´ı povinn´ y parametr pro argv[0]: execl("/bin/ls", NULL); Na nˇekter´ ych syst´emech to m´a zaj´ımav´ y efekt. Jelikoˇz je NULL br´an jako oˇcek´ avan´ y argv[0], dalˇs´ı data na z´asobn´ıku jsou akceptov´ana jako ukazatele na ˇretˇezce, dokud se nenalezne dalˇs´ı NULL. V naˇsem pˇr´ıpadˇe tak pˇr´ıkaz ls zkouˇs´ı zobrazit informace o souborech, jako jejichˇz jm´ena jsou pouˇzity ˇretˇezce nastaven´ı promˇenn´ ych prostˇred´ı z pole environ, o nichˇz jiˇz v´ıme, ˇze se vol´ an´ım execl spouˇstˇen´emu programu pˇred´avaj´ı: $ ./a.out : BLOCKSIZE=K: No such file or directory : FTP_PASSIVE_MODE=YES: No such file or directory 131
: HISTCONTROL=ignoredups: No such file or directory : HISTSIZE=10000: No such file or directory ... ...
Form´ at spustiteln´ eho souboru • Common Object File Format (COFF) – starˇs´ı System V • Extensible Linking Format (ELF) – nov´ y v SVR4 • ˇcasto se mluv´ı o a.out form´atu, protoˇze tak se jmenuje (pokud nen´ı pouˇzit pˇrep´ınaˇc -o) v´ ystup linkeru. • Form´ at ELF:
hlaviˇcka souboru tabulka programov´ ych hlaviˇcek sekce 1 .. . sekce N tabulka hlaviˇcek sekc´ı
• Standard nestanovuje form´ at spustiteln´eho souboru. I kdyˇz vˇetˇsina unixov´ ych syst´em˚ u pˇreˇsla na ELF, st´ ale se najdou mainstreamov´e syst´emy pouˇz´ıvaj´ıc´ı jin´e form´ aty. Napˇr. Mac OS X (coˇz je certifikovan´ y UNIX) pouˇz´ıv´a tzv. MachO form´ at. Stejnˇe jako ELF je strukturovan´ y, odliˇsuje se napˇr. v tom, ˇze za hlaviˇckou n´ asleduj´ı tzv. load commands, kter´e se odkazuj´ı na sekce v dalˇs´ı ˇc´ asti. Za nimi n´ asleduj´ı samotn´e segmenty, kter´e se skl´adaj´ı ze sekc´ı. Kaˇzd´ y segment je u ´sek virtu´ aln´ı pamˇeti, kterou dynamick´ y linker mapuje do adresov´eho prostoru procesu. Kaˇzd´a sekce obsahuje k´od nebo data dan´eho typu. • Hlaviˇcka souboru (ELF header ) obsahuje z´akladn´ı informace o souboru. • Na Solarisu je k dispozici program elfdump umoˇzn ˇuj´ıc´ı zobrazen´ı prakticky vˇsech sekc´ı v ELF souboru a jejich obsahu v lidsky ˇciteln´e formˇe. V Linuxu lze pouˇz´ıt program readelf, jeho funkcionalita je ovˇsem proti elfdump omezena. • Tabulka programov´ ych hlaviˇcek (program header table) je pˇr´ıtomna pouze u soubor˚ u obsahuj´ıc´ıch spustiteln´e programy, zobraz´ı se pomoc´ı “elfdump -p”. Obsahuje informaci o rozvrˇzen´ı virtu´aln´ı pamˇeti procesu. • Sekce obsahuj´ı instrukce, data, tabulku symbol˚ u, relokaˇcn´ı data, apod. • Tabulka hlaviˇcek sekc´ı (section header table) obsahuje sluˇzebn´ı informace pro linker, “elfdump -c”. 132
• Nen´ı to tak d´ avno co nˇekter´e unixov´e syst´emy pˇreˇsly na ELF. Napˇr. OpenBSD pˇreˇslo z a.out form´ atu (coˇz je dalˇs´ı form´at spustiteln´ ych soubor˚ u, viz a.out(5) man page na OpenBSD) na ELF ve verzi 3.4 v roce 2003. Proti ELFu je a.out mnohem jednoduˇsˇs´ı a m´enˇe obecn´ y, hod´ı se tedy dobˇre na zkoum´an´ı fundament´ aln´ıch princip˚ u zav´ adˇen´ı spustiteln´ ych soubor˚ u do pamˇeti. Z´akladem hlaviˇcka (obsahuj´ıc´ı napˇr. informace o tom zda spustiteln´ y soubor vyˇzaduje sluˇzby run time linkeru nebo zda obsahuje Position Independent Code) a pak dalˇs´ıch nepovinn´ ych 6 sekc´ı (text, data, relokace v textov´em segmentu, relokace v datov´em segmentu, tabulka symbol˚ u, tabulka string˚ u koresponduj´ıc´ıch s jednotliv´ ymi symboly). • Dnes je naprosto bˇeˇzn´e ˇze virtu´aln´ı adresy spustiteln´eho souboru, namapovan´ ych knihoven, z´ asobn´ıku a heapu jsou n´ahodn´e, mˇen´ı se pˇri kaˇzd´em spuˇstˇen´ı procesu. Tato technika (Adress Space Layout Randomization) slouˇz´ı k znesnadnˇen´ı u ´tok˚ u, kter´e potˇrebuj´ı zn´at napˇr. adresy symbol˚ u v knihovnˇe libc nebo z´ asobn´ıku. Poprv´e s touto ideou pˇriˇsel Linux, OpenBSD byl prvn´ı syst´em kter´ y ji zaˇcal pouˇz´ıvat pro vˇsechny programy implicitnˇe. R˚ uzn´e syst´emy aplikuj´ı tuto techniku s r˚ uzn´ ymi parametry a na r˚ uzn´e ˇc´asti programu. Obecnˇeji vzato lze vn´ aˇset n´ ahodnost do dalˇs´ıch ˇc´ast´ı syst´emu (napˇr. ID proces˚ u, inici´ aln´ı sekvenˇcn´ı ˇc´ısla pro TCP apod.).
Ukonˇ cen´ı procesu void exit(int status ); • ukonˇc´ı proces s n´ avratov´ ym k´odem status. • nikdy se nevr´ at´ı na instrukci n´asleduj´ıc´ı za vol´an´ım. pid t wait(int *stat loc ); • poˇck´ a, aˇz skonˇc´ı nˇekter´ y synovsk´ y proces, vr´at´ı jeho PID a do stat_loc uloˇz´ı n´ avratov´ y k´od, kter´ y lze d´ale testovat: – WIFEXITED(stat val) . . . proces volal exit() – WEXITSTATUS(stat val) . . . argument exit() – WIFSIGNALED(stat val) . . . proces dostal sign´al – WTERMSIG(stat val) . . . ˇc´ıslo sign´alu – WIFSTOPPED(stat val) . . . proces pozastaven – WSTOPSIG(stat val) . . . ˇc´ıslo sign´alu pid t waitpid(pid t pid, int *stat loc, int opts ); • ˇcek´ an´ı na jeden proces.
• status loc roven NULL znaˇc´ı ignoraci statusu • funkce exit funguje jako exit s t´ım, ˇze se neprov´ad´ı flush stdio stream˚ ua nevolaj´ı se funkce nastaven´e pomoc´ı atexit
133
• ve standardu je jeˇstˇe WIFCONTINUED(stat val), coˇz znaˇc´ı opˇetn´e rozbˇehnut´ı procesu po jeho pˇredchoz´ım zastaven´ı, je to ale souˇc´ast jist´eho rozˇs´ıˇren´ı, kter´e nemus´ı vˇsechny syst´emy podporovat. • pozn´ amka – pozastavit proces lze pomoc´ı ,,kill -STOP ”, rozbˇehnout pak pˇres ,,kill -CONT ”. • opts ve waitpid jsou OR-kombinace n´asleduj´ıch pˇr´ıznak˚ u: – WNOHANG . . . neˇcek´ a, pokud nen´ı status okamˇzitˇe k dispozici – WUNTRACED . . . vr´ at´ı status i zastaven´ ych proces˚ u, kter´e jeˇstˇe nebyly testov´ any po jejich zastaven´ı. WSTOPPED m˚ uˇze na nˇekter´ ych syst´emech b´ yt synonymem pro WUNTRACED, nen´ı ale souˇc´ast´ı normy. – WCONTINUED . . . vr´ at´ı status i tˇech proces˚ u, kter´e jeˇstˇe nebyly testov´any od pokraˇcov´ an´ı procesu po zastaven´ı. Bez tohoto pˇr´ıznaku nejsou takov´e procesy reportov´ any. Souˇc´ast´ı stejn´eho rozˇs´ıˇren´ı jako WIFCONTINUED. – pro WUNTRACED a WCONTINUED plat´ı, ˇze v pˇrenositeln´ ych aplikac´ıch byste tyto pˇr´ıznaky mˇeli pouˇz´ıvat jen kdyˇz je v definov´ano makro POSIX JOB CONTROL. • pid ve waitpid: – == -1 . . . libovoln´e d´ıtˇe – > 0 . . . jedno d´ıtˇe – == 0 . . . d´ıtˇe ve stejn´e skupinˇe proces˚ u jako volaj´ıc´ı proces – < -1 . . . d´ıtˇe ve skupinˇe abs(pid) • rodiˇc by mˇel vˇzdy na sv´e dˇeti zavolat wait nebo waitpid, protoˇze jinak se v syst´emu hromad´ı zombie (ukonˇcen´e procesy, kter´e pouze ˇcekaj´ı, aˇz si rodiˇc pˇreˇcte jejich n´ avratovou hodnotu). Hromadˇen´ı tˇechto zombie proces˚ uv tabulce proces˚ u kernelu m˚ uˇze skonˇcit vyˇcerp´an´ım veˇsker´e voln´e pamˇeti, takˇze pozor na to. Pokud takov´ y rodiˇc s´am skonˇc´ı, vˇsechny jeho syny adoptuje process init, kter´ y se tak postar´a i o vˇsechny zombie procesy. • pˇr´ıklad: wait/wait.c
134
Pˇ r´ıklad: spuˇ stˇ en´ı programu a ˇ cek´ an´ı int status; pid = fork() rodiˇc if(pid > 0)
wait(&status);
d´ıtˇe else execl("/bin/ls", "ls", "/", NULL); /bin/ls int main(int argc, char *argv[]) { ... exit(result); }
• toto je klasick´ y zp˚ usob jak spustit nˇejak´ y program a po jeho skonˇcen´ı pokraˇcovat. Rodiˇc nemus´ı jen ˇcekat na ukonˇcen´ı potomka, ale m˚ uˇze vykon´avat d´ al sv˚ uj k´ od. • pozor na to, ˇze aˇckoli to na obr´azku tak vypad´a, n´avratov´a hodnota je pouze souˇc´ ast toho, co dostanete z vol´an´ı wait. Na jej´ı z´ısk´an´ı je potˇreba pouˇz´ıt makra z pˇredchoz´ıho slajdu.
135
Roura: pipe() int pipe(int fildes [2]); • vytvoˇr´ı rouru a dva deskriptory – fildes[0] . . . ˇcten´ı z roury – fildes[1] . . . z´ apis do roury • roura zajiˇst’uje synchronizaci ˇcten´ı a z´apisu: – zapisuj´ıc´ı proces se zablokuje, kdyˇz je roura pln´a, – ˇctouc´ı proces se zablokuje, kdyˇz je roura pr´azdn´a. • ˇctouc´ı proces pˇreˇcte konec souboru (tj. read() vr´at´ı 0), pokud jsou uzavˇreny vˇsechny kopie fildes[1]. • pojmenovan´ a roura (vytvoˇren´a vol´an´ım mkfifo()) funguje stejnˇe, ale m´ a pˇridˇelen´e jm´eno v syst´emu soubor˚ u a mohou ji tedy pouˇz´ıvat libovoln´e procesy.
• nepojmenovanou rouru vytv´aˇr´ı jeden proces a m˚ uˇze ji pˇredat pouze sv´ ym potomk˚ um (pomoc´ı deskriptor˚ u zdˇedˇen´ ych pˇri fork). Toto omezen´ı se d´a obej´ıt pomoc´ı pˇred´ an´ı otevˇren´eho deskriptoru pˇres unix-domain socket. • jestliˇze funkce write zap´ıˇse do roury nejv´ yˇse PIPE BUF (syst´emov´a konstanta) bajt˚ u, je zaruˇceno, ˇze z´ apis bude atomick´ y, tj. tato data nebudou proloˇzena daty zapisovan´ ymi souˇcasnˇe jin´ ymi procesy. • v normˇe SUSv3 nen´ı specifikov´ano, zda fildes[0] je tak´e otevˇren´ y pro z´apis a zda fildes[1] je t´eˇz otevˇren´ y pro ˇcten´ı. Pozor na to, ˇze napˇr´ıklad FreeBSD a Solaris maj´ı roury obousmˇern´e, ale Linux (kdyˇz jsem se naposledy d´ıval) ne. Je proto velmi vhodn´e poˇc´ıtat pouze s jednosmˇern´ ymi rourami. • d˚ uleˇ zit´ e: pro ˇcten´ı/z´ apis z/do roury plat´ı stejn´e podm´ınky jako pro pojmenovanou rouru, jak jsou uvedeny na stranˇe 104. To tak´e znamen´a, ˇze jedin´ y zp˚ usob, jak ˇcten´ aˇrovi roury “poslat” end-of-file je, ˇze vˇsichni zapisovatel´e mus´ı zavˇr´ıt pˇr´ısluˇsn´ y deskriptor pro z´apis, pˇr´ıpadnˇe vˇsechny takov´e deskriptory, maj´ı-li jich v´ıce. • pˇr´ıklady: pipe/broken-pipe.c , pipe/main.c
136
Pˇ r´ıklad: roura mezi dvˇ ema procesy shell: ls / | more
int pd[2]; pipe(pd); switch(fork()) {
producent (d´ıtˇe)
konzument (rodiˇc) default:
case 0: close(1);
close(0);
dup(pd[1]);
dup(pd[0]);
close(pd[0]);
close(pd[0]);
close(pd[1]);
close(pd[1]);
execl("/bin/ls", "ls",
execl("/bin/more", "more", NULL);
"/", NULL);
pd[1]
pd[0]
/bin/ls
/bin/more
• zavˇren´ı z´ apisov´eho deskriptoru pd[1] (ozn. .) v procesu konzumenta je nutn´e, protoˇze jinak se na rouˇre nikdy nedetekuje konec souboru. • ˇctec´ı deskriptor v procesu producenta pd[0] je tak´e vhodn´e zav´ırat (ozn. .), protoˇze kdyˇz konzument pˇredˇcasnˇe skonˇc´ı, dostane producent sign´al SIGPIPE. Kbyby deskriptor v producentovi nebyl zavˇren, producent se nedozv´ı, ˇze konzument skonˇcil, a po naplnˇen´ı bufferu roury v j´adru se zablokuje. • pokud nem´ ame jistotu, ˇze pˇred vol´an´ım pipe byl otevˇren deskriptor 0, mus´ıme v producentovi pouˇz´ıt dup2(pd[1], 1), protoˇze dup(pd[1]) by mohl vr´atit deskriptor 0 m´ısto poˇzadovan´eho 1. Tak´e je tˇreba testovat, zda nenastala situace pd[1] == 1, abychom si nechtˇenˇe nezavˇreli rouru. Podobnˇe je tˇreba otestovat pd[0] == 0 v konzumentovi. • je lepˇs´ı vytv´ aˇret rouru od syna k otci, protoˇze typicky nejdˇr´ıv skonˇc´ı proces zapisuj´ıc´ı do roury, ˇctouc´ı proces pˇreˇcte zbyl´a data, zpracuje je, nˇeco vyp´ıˇse a teprve pak skonˇc´ı. D˚ uvodem je to, ˇze shell, kter´ y pˇr´ısluˇsnou kolonu pˇr´ıkaz˚ u spouˇst´ı, ˇcek´a na ukonˇcen´ı otce a o jeho syny se nezaj´ım´a. Kdyby tedy smˇeˇrovala roura od otce k synovi, otec jako producent dat skonˇc´ı, shell vyp´ıˇse prompt, ale pak jeˇstˇe syn, kter´ y pln´ı funkci konzumenta, m˚ uˇze vypsat nˇejak´ y v´ ystup. Moˇzn´ ym ˇreˇsen´ım je ˇcek´ an´ı otce na skonˇcen´ı syna, jenˇze to se ned´a zajistit, pokud otec provede exec. • p˚ uvodn´ı Bourne shell stav´ı rouru tak, ˇze posledn´ı proces v rouˇre vytvoˇr´ı pˇredposledn´ı jako sv´eho syna, ten vytvoˇr´ı pˇredchoz´ı proces a tak se postupuje aˇz k zaˇc´ atku roury. 137
• v shellu bash jsou vˇsechny procesy v rouˇre pˇr´ımo potomky shellu (shell vol´a fork tolikr´ at, jak dlouh´ a je roura). Shell pˇred vyps´an´ım promptu ˇcek´a, aˇz vˇsechny procesy roury skonˇc´ı.
Sd´ılen´ a pamˇ et’ – u ´ vod • pajpy a soubory jako metody meziprocesov´e komunikace vyˇzaduj´ı syst´emov´ a vol´an´ı • v´ yhoda: procesy nemohou poˇskodit adresov´ y prostor jin´eho procesu • nev´ yhoda: velk´ a reˇzie pro syst´emov´a vol´an´ı, typicky read, write • sd´ılen´ a pamˇet’ je namapov´an´ı ˇc´asti pamˇeti do adresov´eho prostoru v´ıce proces˚ u • odstranˇen´ı nev´ yhody, ztr´ata dosavadn´ı v´ yhody • synchronizace pˇr´ıstupu do sd´ılen´e pamˇeti – System V semafory – POSIX semafory bez nutnosti syst´emov´eho vol´an´ı v bˇeˇzn´em pˇr´ıpadˇe
• mapov´ an´ı soubor˚ u do pamˇeti je jednou z implementac´ı sd´ılen´e pamˇeti. Pro popis u ´seku sd´ılen´e pamˇeti pouˇz´ıv´a soubor. • takto implementovan´ a sd´ılen´a pamˇet’ je tedy pravidelnˇe zapisov´ana na disk • pro sd´ılen´ı bez reˇzie z´ apisu zmˇenˇen´ ych dat na disk je moˇzn´e pouˇz´ıt memory based filesyst´em, napˇr´ıklad tmpfs (Solaris, NetBSD – zde byl tmpfs naps´an v roce 2005 jako souˇc´ ast Summer of Code sponzorovan´eho firmou Google, FreeBSD m´ a podobnou vlastnost pod n´azvem memory disk ). Jako tzv. backing store pro pamˇet’ov´e str´ anky patˇr´ıc´ı tˇemto filesyst´em˚ um je obecnˇe moˇzn´e pouˇz´ıt swap oblast na disku. • pro synchronizaci pˇr´ıstupu do sd´ılen´e pamˇeti se vˇetˇsinou pouˇz´ıvaj´ı semafory.
138
Mapov´ an´ı soubor˚ u do pamˇ eti (1) void *mmap(void *addr, size t len, int prot, int flags, int fildes, off t off ); • do pamˇet’ov´eho prostoru procesu od adresy addr (0 . . . adresu pˇridˇel´ı j´ adro) namapuje u ´sek d´elky len zaˇc´ınaj´ıc´ı na pozici off souboru reprezentovan´eho deskriptorem fildes. • vrac´ı adresu namapovan´eho u ´seku nebo MAP FAILED. • v prot je OR-kombinace PROT READ (lze ˇc´ıst), PROT WRITE (lze zapisovat), PROT EXEC (lze spouˇstˇet), nebo PROT NONE (nelze k dat˚ um pˇristupovat). • ve flags je OR-kombinace MAP PRIVATE (zmˇeny jsou priv´atn´ı pro proces, neukl´ adaj´ı se do souboru), MAP SHARED (zmˇeny se ukl´ adaj´ı do souboru), MAP FIXED (j´adro nezmˇen´ı addr).
• Pˇr´ıklady: mmap/reverse.c , mmap/map-nocore.c • v pˇr´ıznac´ıch mus´ı b´ yt pˇr´ıtomen pr´ avˇ e jeden z MAP PRIVATE a MAP SHARED • mapov´ an´ı soubor˚ u do pamˇeti je alternativou ke zpracov´an´ı soubor˚ u pomoc´ı read, write, lseek. Po namapov´an´ı lze se souborem pracovat jako s datovou strukturou v pamˇeti. Soubor se nekop´ıruje cel´ y do pamˇeti, alokuj´ı se pouze str´ anky na kter´e se pˇristupuje. Pokud je potˇreba str´anku uvolnit, obsah se ukl´ ad´ a zpˇet do souboru (kdyˇz je pouˇzit MAP SHARED – tento zp˚ usob namapov´ an´ı je tedy ekvivalentn´ı tomu, kdy program zap´ıˇse do stejn´eho souboru pomoc´ı write(2)) nebo do swapu – pouˇz´ıv´a se mechanizmus copy-on-write (pˇri MAP PRIVATE). • pro namapov´ an´ı souboru do pamˇeti tedy potˇrebuji soubor nejdˇr´ıve otevˇr´ıt pomoc´ı open. M´ od v prot nem˚ uˇze b´ yt “vyˇsˇs´ı” neˇz bylo specifikov´ano v m´odu uˇze b´ yt probl´em pro open. Pouˇzit´ı MAP FIXED se nedoporuˇcuje, protoˇze to m˚ pro pˇrenositelnost k´ odu. • varov´ an´ı: toto se t´ yk´ a pouze MAP SHARED – pokud je soubor jin´ ym procesem zkr´ acen tak, ˇze zkr´ acen´ı se t´ yk´a i pr´avˇe namapovan´e ˇc´asti, pˇri pˇr´ıstupu do ˇ sen´ım je takov´e pamˇeti je procesu zasl´an sign´al SIGBUS resp. SIGSEGV. Reˇ pouˇz´ıt mandatory locking, to ale nen´ı vˇsude implementov´ano. V pˇr´ıpadˇe, ˇze je namapovan´ a pamˇet’ pouˇzita jako parametr vol´an´ı write, sign´al se nepoˇsle, protoˇze write vr´ at´ı -1 a errno je nastaveno na EFAULT. • pˇri pouˇzit´ı MAP PRIVATE vid´ım na Solarisu vˇsechny zmˇeny proveden´e jin´ ymi procesy, kter´e namapovaly sd´ılenˇe, aˇz do t´e doby, kdy do str´anky zap´ıˇsu
139
– v tom okamˇziku se vytvoˇr´ı kopie str´anky a dalˇs´ı takov´e zmˇeny jiˇz nevid´ım. Na FreeBSD tyto zmˇeny nevid´ım ani pˇred z´apisem. Viz specifikace: ,,It is unspecified whether modifications to the underlying object done after the MAP PRIVATE mapping is established are visible through the MAP PRIVATE mapping.” • hodnota off+len m˚ uˇze pˇrekraˇcovat aktu´aln´ı velikost souboru, za konec souboru ale nelze zapisovat a soubor tak prodlouˇzit - proces by obdrˇzel sign´al SIGBUS resp. SIGSEGV, viz pˇr´ıklad mmap/lseek.c . Sign´al dostanu stejnˇe tak v situaci, kdy do read-only namapovan´eho segmentu zkus´ım zapsat (je to logick´e, pˇriˇrazen´ı nem´ a n´ avratovou hodnotu kterou byste mohli otestovat). • mapuje se vˇzdy po cel´ ych str´ank´ach, hodnoty off (a pˇri MAP FIXED i addr) mus´ı b´ yt spr´ avnˇe zarovnan´e. Posledn´ı str´anka je za koncem souboru doplnˇena nulami a tento u ´sek se nikdy nepˇrepisuje do souboru. • Anonymn´ı mapov´ an´ı lze sd´ılet mezi r˚ uzn´ ymi procesy pouze pomoc´ı fork(). Jedinou alternativou je pamˇet’ sd´ılen´a pomoc´ı syst´emov´eho vol´an´ı shmat. • pˇr´ıstup do namapovan´eho u ´seku, ale za posledn´ı existuj´ıc´ı str´anku namapovan´eho objektu, zp˚ usob´ı sign´al SIGBUS resp. SIGSEGV. Neplat´ı to vˇsude u ´plnˇe pˇresnˇe, viz pˇr´ıklad mmap/sigbus.c . • Pˇri pouˇzit´ı MAP FIXED namapov´an´ı souboru nahrad´ı pˇr´ıpadn´e pˇredchoz´ı mapov´ an´ı str´ anek v rozsahu. addr aˇz addr+len-1, viz pˇr´ıklad mmap/override.c . • existuj´ıc´ı rozˇs´ıˇren´ı pro flagy (nejsou souˇc´ast´ı SUSv3): – pˇr´ıznak MAP ANON ve FreeBSD a Solarisu – vytvoˇren´ı anonymn´ıho segmentu bez vazby na soubor, deskriptor mus´ı b´ yt -1. Mapuje se tak anonymn´ı objekt, kter´ y jak v´ıme m´a m´ısto fyzick´eho uloˇzen´ı na swapu (tedy nen´ı trval´e). Linux m´ a podobnou funkcionalitu pˇres MAP ANONYMOUS. Tento pˇr´ıznak pouˇz´ıvaj´ı pamˇet’ov´e alok´atory, kter´e pracuj´ı s vol´an´ım mmap, viz tak´e strana 124. – v IRIXu lze pomoc´ı MAP AUTOGROW automaticky zvˇetˇsit namapovan´ y objekt pˇri pˇr´ıstupu za jeho st´avaj´ıc´ı konec. • bˇeˇzn´ ym pˇr´ıkazem, kter´ y pouˇz´ıv´a mapov´an´ı soubor˚ u do pamˇeti, je cat(1). ˇ ıst z takov´e pamˇeti je prostˇe rychlejˇs´ı neˇz opakovanˇe volat read, kde je C´ nutn´e se pro kaˇzd´e takov´e vol´an´ı pˇrepnout z uˇzivatelsk´eho reˇzimu do reˇzimu j´ adra a zpˇet.
140
Mapov´ an´ı soubor˚ u do pamˇ eti (2) int msync(void *addr, size t len, int flags ); • zap´ıˇse zmˇenˇen´e str´ anky v u ´seku len bajt˚ u od adresy addr do souboru. Hodnota flags je OR-kombinace – MS ASYNC . . . asynchronn´ı z´apis – MS SYNC . . . synchronn´ı z´apis – MS INVALIDATE . . . zruˇsit namapovan´a data, kter´a se liˇs´ı od obsahu souboru int munmap(void *addr, size t len ); • zap´ıˇse zmˇeny, zruˇs´ı mapov´an´ı v d´elce len od adresy addr. int mprotect(void *addr, size t len, int prot ); • zmˇen´ı pˇr´ıstupov´ a pr´ ava k namapovan´emu u ´seku souboru. Hodnoty prot jsou stejn´e jako u mmap().
• uloˇzen´ı zmˇen do souboru na disk je zaruˇcen´e aˇz po proveden´ı msync nebo munmap, ale ostatn´ı procesy, kter´e maj´ı soubor namapov´an, vid´ı zmˇeny hned. • mapov´ an´ı pamˇeti a nastavov´an´ı pˇr´ıstupov´ ych pr´av pouˇz´ıv´a napˇr. knihovna Electric Fence, kter´ a slouˇz´ı pro ladˇen´ı chyb pˇri pr´aci s dynamickou pamˇet´ı.
141
Pˇ r´ıklad: mapov´ an´ı soubor˚ u do pamˇ eti int main(int argc, char *argv[]) { int fd, fsz; char *addr, *p1, *p2, c; fd = open(argv[1], O RDWR); fsz = lseek(fd, 0, SEEK END); p1 = addr = mmap(0, fsz, PROT READ|PROT WRITE, MAP SHARED, fd, 0); p2 = p1 + fsz - 1; while(p1
• Tento program otoˇc´ı poˇrad´ı znak˚ u v souboru (zap´ıˇse soubor od konce k zaˇc´ atku). • Jedna z hlavn´ıch v´ yhod sd´ılen´ ych segment˚ u je moˇznost pracovat s daty v souboru pomoc´ı pointerov´e aritmetiky. Obecnˇe je ale nutn´e d´at pozor na zarovn´ an´ı pˇri dereferenc´ıch; napˇr. na SPARCu dojde pˇri takov´em pˇr´ıstupu sign´ al SIGBUS, viz pˇr´ıklad mmap/aligned.c .
142
Dynamick´ y pˇ r´ıstup ke knihovn´ am void *dlopen(const char *file, int mode ); • zpˇr´ıstupn´ı knihovnu v souboru file, vr´at´ı handle nebo NULL. • v mode je OR-kombinace RTLD NOW (okamˇzit´e relokace), RTLD LAZY (odloˇzen´e relokace), RTLD GLOBAL (symboly budou glob´ alnˇe dostupn´e), RTLD LOCAL (nebudou glob´alnˇe dostupn´e). void *dlsym(void *handle, const char *name ); • vr´ at´ı adresu symbolu zadan´eho jm´ena z knihovny. int dlclose(void *handle ); • ukonˇc´ı pˇr´ıstup ke knihovnˇe. char *dlerror(void); • vr´ at´ı textov´ y popis chyby pˇri pr´aci s knihovnami.
• pomoc´ı tˇechto funkc´ı lze implementovat dynamicky nahr´avan´e plug-in moduly naˇc´ıtan´e aplikac´ı podle potˇreby (napˇr. podle obsahu jej´ıho konfiguraˇcn´ıho souboru). • dynamick´ ym naˇc´ıt´ an´ım knihoven se tak´e d´a vyˇreˇsit situace, kdy potˇrebujeme vyuˇz´ıt nˇekolik knihoven, kter´e definuj´ı symbol se stejn´ ym jm´enem. Jedna knihovna se pˇr´ımo pˇrilinkuje k programu, k ostatn´ım se pˇristupuje pomoc´ı dlopen. • soubor mus´ı b´ yt ve spr´ avn´em form´atu (sd´ılen´a knihovna .so ve form´atu ELF resp. form´ atu podporovan´em na dan´em syst´emu), napˇr´ıklad u gcc to znamen´ a pouˇz´ıt pˇrep´ınaˇc -shared, u cc na Solarisu (Sun Studio Compiler) je to pˇrep´ınaˇc -G. Na OS X (s form´atem Mach-O) je to -dynamiclib, knihovny maj´ı pˇr´ıponu .dynlib. • pokud cesta obsahuje znak /, bere se podle tvaru jako glob´aln´ı nebo relativn´ı. Pokud lom´ıtko neobsahuje, pouˇzije se pro hledan´ı objektu defaultn´ı nastaven´ı dynamick´eho linkeru, typicky /lib a /usr/lib, kter´e se d´a rozˇs´ıˇr´ıt pomoc´ı promˇenn´e LD LIBRARY PATH. Na jej´ı pouˇzit´ı ovˇsem pozor, viz pozn´amky na stranˇe 37. • konstanty pro parametr mode funkce dlopen: – RTLD NOW – vˇsechny relokace (vyˇreˇsen´ı vˇsech odkaz˚ u) pro symboly nalezen´e v pˇripojovan´em objektu jsou provedeny okamˇzitˇe po nataˇzen´ı knihovny, aplikace m´ a jistotu, ˇze jsou vˇsechny symboly pˇr´ıstupn´e
143
– RTLD LAZY – relokace mohou b´ yt odloˇzeny aˇz do chv´ıle pouˇzit´ı symbolu. Co to v praxi znamen´a? Pokud otevˇrete objekt, kter´ y z´avis´ı na dalˇs´ıch objektech, dynamick´ y linker tyto ostatn´ı z´avisl´e objekty mapuje do pamˇeti, aˇz kdyˇz jsou opravdu potˇreba. M˚ uˇze se tak st´at, ˇze z´ avislosti neexistuj´ı, ale vol´an´ı dlopen stejnˇe uspˇeje. U RTLD NOW se z´ avisl´e objekty mapuj´ı do pamˇeti hned, a teprve pak dlopen vr´at´ı pˇr´ısluˇsn´ y handle. Na Solarisu m˚ uˇzete defaultn´ı chov´an´ı pro dynamick´ y linker vynutit promˇenn´ ymi prostˇred´ı LD BIND NOW a LD BIND LAZY. Pˇri konfliktu nastaven´ı m´ a vˇzdy pˇrednost nastaven´ı NOW, at’ jiˇz je glob´aln´ı nebo jen v m´ odu vol´ an´ı dlopen pˇri mapov´an´ı jednoho konkr´etn´ıho objektu. Pˇri spouˇstˇen´ı aplikace jsou vˇsechny z´avisl´e objekty defaultnˇe mapovan´e hned, ale je moˇzn´e jednotliv´e knihovny linkovat pro ,,lazy binding” pomoc´ı -z lazyload, viz manu´alov´e str´anky pro ld a ld.so.1. Pˇr´ıklad: dyn-lib/ld-lazy.c . – RTLD GLOBAL . . . symboly z knihovny mohou b´ yt pouˇzity pˇri zpracov´an´ı relokac´ı v ostatn´ıch knihovn´ach a jsou dostupn´e pomoc´ı dlopen(0, RTLD GLOBAL). Toto je defaultn´ı nastaven´ı pro objekty mapovan´e pˇri spuˇstˇen´ı programu. Pro dlopen je defaultn´ı nastaven´ı RTLD LOCAL. To znamen´ a, ˇze je moˇzn´e namapovat stejnou knihovnu nˇekolikr´at a symboly se nebudou vz´ ajemnˇe pˇrekr´ yvat. Pozor ale na to, kdyˇz takov´e knihovny budou pouˇz´ıvat symboly jin´eho glob´aln´ı objektu - napˇr. errno z libc.so. Takov´ y symbol je d´ale spoleˇcn´ y pro vˇsechny namapovan´e objekty, vˇcetnˇe tˇech mapovan´ ych pomoc´ı RTLD LOCAL. • speci´ aln´ı handle RTLD NEXT hled´a symbol pouze v knihovn´ach nahran´ ych po knihovnˇe, ve kter´e je vol´ an´ı dlsym. Hod´ı se pro pˇredefinov´an´ı existuj´ıc´ıch funkc´ı, pokud v redefinovan´e funkci potˇrebujeme volat p˚ uvodn´ı. Knihovna s novou funkc´ı se nahr´ av´ a jako prvn´ı (napˇr. pomoc´ı promˇenn´e LD PRELOAD), adresu p˚ uvodn´ı funkce z´ısk´ a vol´an´ım dlsym(RTLD NEXT, fn name ). Pˇr´ıklad: dyn-lib/rtld next.c . • vˇsechny tyto funkce jsou souˇc´ast´ı dynamick´eho linkeru, kter´ y m´a kaˇzd´a dynamicky slinkovan´ a aplikace namapovan´ y ve sv´em adresov´em prostoru. Viz tak´e strany 36 a 137.
144
Pˇ r´ıklad: zpˇ r´ıstupnˇ en´ı knihovny int err; void *handle; double y, x = 1.3; double (*fun)(double); char *libname = "libm.so", *fn name = "sin"; if ((handle = dlopen(libname, RTLD NOW)) == NULL) { fprintf(stderr, "%s\n", dlerror()); exit(1); } fun = dlsym(handle, fn name); if ((err = dlerror()) != NULL) fprintf(stderr, "%s\n", err); exit(1); y = fun(x); dlclose(handle);
• zde se vol´ a funkce sin z knihovny matematick´ ych funkc´ı libm.so. • funkce dlsym vr´ at´ı adresu symbolu dan´eho jm´ena, ale vˇzdy jako ukazatel na void, neprob´ıh´ a ˇz´ adn´ a typov´a kontrola ani nen´ı k dispozici ˇz´adn´a informace o typu symbolu. Ten, kdo tuto adresu pouˇz´ıv´a, mus´ı zajistit jej´ı spr´avn´e pˇretypov´ an´ı. • pˇri pouˇzit´ı knihoven v C++ je tˇreba si uvˇedomit, ˇze C++ pouˇz´ıv´a name mangling, tj. do jm´ena funkce (metody) je zak´odov´ano pˇr´ıpadn´e jm´eno tˇr´ıdy nebo namespace a typy parametr˚ u. • pˇr´ıklad: dyn-lib/dlopen.c
145
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
Sign´ aly • informuj´ı proces o v´ yskytu urˇcit´e ud´alosti • na uˇzivatelsk´e u ´rovni zpˇr´ıstupˇ nuj´ı mechanizmy pˇreruˇsen´ı • kategorie sign´ al˚ u: – chybov´ e ud´ alosti generovan´e bˇeˇz´ıc´ım procesem, napˇr. pokus o pˇr´ıstup mimo pˇridˇelenou oblast pamˇeti (SIGSEGV) – asynchronn´ı ud´ alosti vznikaj´ıc´ı mimo proces, napˇr. sign´al od jin´eho procesu, vyprˇsen´ı nastaven´eho ˇcasu (SIGALRM), odpojen´ı termin´ alu (SIGHUP), stisk Ctrl-C (SIGINT) • nejjednoduˇsˇs´ı mechanizmus pro komunikaci mezi procesy – nesou pouze informaci o tom, ˇze nastala nˇejak´a ud´alost. • zpracov´ avaj´ı se asynchronnˇe – pˇr´ıchod sign´alu pˇreruˇs´ı bˇeh procesu a vyvol´ a se obsluˇzn´a funkce, tzv. handler sign´ alu
146
• se sign´ alem nen´ı sv´ az´ ana ˇz´ adn´a jin´a informace neˇz ˇc´ıslo sign´alu, pokud se nepouˇzije POSIX-1003.1b rozˇs´ıˇren´ı (real-time), viz strana 161. • po n´ avratu z handleru (pokud k nˇemu dojde) proces pokraˇcuje od m´ısta pˇreruˇsen´ı. • historicky sign´ aly vznikly jako mechanizmus pro ,,n´asiln´e” ukonˇcen´ı procesu. Z toho vyplynul i n´ azev funkce kill pro posl´an´ı sign´alu.
Posl´ an´ı sign´ alu int kill(pid t pid, int sig ); • poˇsle sign´ al s ˇc´ıslem sig procesu (nebo skupinˇe proces˚ u) podle hodnoty pid: – > 0 . . . procesu s ˇc´ıslem pid – == 0 . . . vˇsem proces˚ um ve stejn´e skupinˇe – == -1 . . . vˇsem proces˚ um, kromˇe syst´emov´ ych – < -1 . . . proces˚ um ve skupinˇe abs(pid) • sig == 0 znamen´ a, ˇze se pouze zkontroluje opr´avnˇen´ı poslat sign´ al, ale ˇz´ adn´ y sign´ al se nepoˇsle. • pr´ avo procesu poslat sign´al jin´emu procesu z´avis´ı na UID obou proces˚ u.
• proces s EUID == 0 m˚ uˇze poslat sign´al libovoln´emu procesu. • ostatn´ı procesy: – Linux, Solaris: RUID nebo EUID procesu, kter´ y poslal sign´al, se mus´ı shodovat s re´ aln´ ym UID nebo saved set-user-ID c´ılov´eho procesu. – FreeBSD: mus´ı se shodovat EUID obou proces˚ u. – IRIX: RUID nebo EUID procesu, kter´ y poslal sign´al, se mus´ı shodovat s re´ aln´ ym nebo efektivn´ım UID nebo saved set-user-ID c´ılov´eho procesu. • pˇr´ıklad (obsahuje i zachycen´ı sign´alu, viz dalˇs´ı slajdy): signals/killme.c
147
Oˇ setˇ ren´ı sign´ al˚ u • pokud proces neˇrekne jinak, provede se v z´avislosti na konkr´etn´ım sign´ alu implicitn´ı akce, tj. bud’: – ukonˇcen´ı procesu (exit) – ukonˇcen´ı procesu plus coredump (core) – ignorov´ an´ı sign´ alu (ignore) – pozastaven´ı procesu (stop) – pokraˇcov´ an´ı pozastaven´eho procesu (continue) • proces tak´e m˚ uˇze nastavit ignorov´an´ı sign´alu • nebo sign´ al oˇsetˇren´ı uˇzivatelsky definovanou funkc´ı (handler), po n´ avratu z handleru proces pokraˇcuje od m´ısta pˇreruˇsen´ı sign´ aly SIGKILL a SIGSTOP vˇzdy vyvolaj´ı implicitn´ı akci (zruˇsen´ı, resp. pozastaven´ı).
• vytvoˇren´ı core dumpu znamen´a uloˇzen´ı kompletn´ıho obsahu pamˇeti procesu do souboru, typicky se jm´enem core • vˇetˇsina sign´ al˚ u implicitnˇe ukonˇc´ı proces, nˇekter´e nav´ıc vytvoˇr´ı jiˇz zmiˇ novan´ y core dump, kter´ y je moˇzn´e n´aslednˇe pouˇz´ıt pro ladic´ı u ´ˇcely. • d˚ uvod toho, proˇc pˇri exec se vˇsechny nastaven´e handlery sign´al˚ u nahrad´ı implicitn´ı obsluhou (strana 137) je jasn´ y – k´od pˇr´ısluˇsn´ ych obluˇzn´ ych funkc´ı po vol´ an´ı exec pˇrestane existovat.
148
Pˇ rehled sign´ al˚ u (1) sign´ aly je moˇzn´e logicky rozdˇelit do nˇekolika skupin. . . detekovan´ e chyby: SIGBUS
pˇr´ıstup k nedef. ˇc´asti pamˇet’ov´eho objektu (core)
SIGFPE
chyba aritmetiky v pohybliv´e ˇc´arce (core)
SIGILL
nepovolen´ a instrukce (core)
SIGPIPE
z´ apis do roury, kterou nikdo neˇcte (exit)
SIGSEGV
pouˇzit´ı nepovolen´e adresy v pamˇeti (core)
SIGSYS
chybn´e syst´emov´e vol´an´ı (core)
SIGXCPU
pˇrekroˇcen´ı ˇcasov´eho limitu CPU (core)
SIGXFSZ
pˇrekroˇcen´ı limitu velikosti souboru (core)
• Generov´ an´ı tˇechto sign´ al˚ u vych´az´ı z chyb programu. • Pro sign´ aly SIGBUS, SIGFPE, SIGILL a SIGSEGV nen´ı normou pˇresnˇe definov´ ana pˇr´ıˇcina, ale obvykle jsou to chyby detekovan´e hardwarem. • Pro tyto ˇ ctyˇ ri sign´ aly tak´ e plat´ı tato speci´ aln´ı pravidla (podrobnosti viz kapitola 2.4 Signal Concepts v normˇe SUSv3): – Pokud byly nastaven´e jako ignorovan´e vol´an´ım sigaction, je chov´an´ı programu po t´e, co je mu takov´ y sign´al posl´an, normou nedefinov´ano. – N´ avratov´ a hodnova handleru je nedefinov´ana. – N´ asledek situace, kdy jeden z tˇechto sign´alu je maskov´an v okamˇziku jeho vygenerovan´ı je nedefinovan´ y. • Jin´ ymi slovy – pokud je hardwarem detekovan´a chyba re´aln´a (sign´al nen´ı posl´ an pˇres kill a podobn´ ymi funkcemi), v´aˇs program se pˇres tuto chybu nemus´ı v˚ ubec dostat. Nen´ı bezpeˇcn´e chybu ignorovat, pokraˇcovat v bˇehu po n´ avratu z handleru nebo odd´alit ˇreˇsen´ı pomoc´ı zamaskov´an´ı. Zachytit tyto sign´ aly lze, o tom, ˇze by to bylo jinak, norma nemluv´ı. Pokud tedy m´ate pro tyto sign´ aly handler, je potˇ reba poˇ reˇ sit danou situaci jak uzn´ ate za vhodn´ e a pak ukonˇ cit program. M˚ uˇzete to vyzkouˇset na pˇr´ıkladu signals/catch-SIGSEGV.c . Dalˇs´ı informace vˇcetnˇe jin´eho pˇr´ıkladu na vyzkouˇsen´ı je moˇzn´e nal´ezt na stranˇe 231. • Pozn´ amka: pokud je nˇeco normou nedefinov´ano (undefined ), obecnˇe to znamen´ a, ˇze se neoˇcek´ av´ a, ˇze by program´ator potˇreboval zn´at pˇresn´e chov´an´ı v
149
takov´e situaci. Pokud je to potˇreba, pravdˇepodobnˇe je ve vaˇsem programu nˇeco ˇspatnˇe. Jako vˇzdy, urˇcite by se naˇsly vyj´ımky potvrzuj´ıc´ı pravidlo.
Pˇ rehled sign´ al˚ u (2) generovan´ e uˇ zivatelem nebo aplikac´ı: SIGABRT
ukonˇcen´ı procesu (core)
SIGHUP
odpojen´ı termin´alu (exit)
SIGINT
stisk speci´ aln´ı kl´avesy Ctrl-C (exit)
SIGKILL
zruˇsen´ı procesu (exit, nelze oˇ setˇ rit ani ignorovat)
SIGQUIT
stisk speci´ aln´ı kl´avesy Ctrl-\ (core)
SIGTERM
zruˇsen´ı procesu (exit)
SIGUSR1
uˇzivatelsky definovan´ y sign´al 1 (exit)
SIGUSR2
uˇzivatelsky definovan´ y sign´al 2 (exit)
• sign´ al SIGHUP se ˇcasto pouˇz´ıv´a jako zp˚ usob, jak ozn´amit bˇeˇz´ıc´ımu d´emonu, ˇze se zmˇenil jeho konfiguraˇcn´ı soubor a m´a si ho proto znovu naˇc´ıst. • SIGINT a SIGQUIT jsou obvykle generov´any z termin´alu (Ctrl-C a Ctrl-\) a lze je pˇredefinovat pˇr´ıkazem stty nebo pomoc´ı funkce tcsetattr. Pro to aby se mohl vygenerovat core file je zapotˇreb´ı m´ıt to povolen´e v syst´emov´e konfiguraci a limitech, v shellu se toho dos´ahne pˇr´ıkazem ulimit. • vzhledem k tomu, ˇze SIGKILL nelze zachytit, jej pouˇz´ıvejte jen v nutn´ ych pˇr´ıpadech; typick´ ym pˇr´ıpadem je to, ˇze bˇeˇz´ıc´ı proces jiˇz nelze ukonˇcit jin´ ym sign´ alem. Mnoho aplikac´ı, hlavnˇe d´emon˚ u, spol´eh´a na to, ˇze vynucen´e ukonˇcen´ı sign´ alem je pˇres SIGTERM. Tento sign´al si zachyt´ı a provede ukonˇcovac´ı operace – napˇr´ıklad uloˇzen´ı aktu´aln´ı datab´aze na disk, smaz´an´ı doˇcasn´ ych soubor˚ u apod. Pouˇz´ıvat rovnou SIGKILL proto, ˇze proces to “vˇzdycky zabije”, je neznalost vˇeci, kter´ a se v´ am m˚ uˇze dost vymst´ıt. • uk´ azka na SIGQUIT na Solarisu: $ sleep 10 ^\Quit (core dumped) $ mdb core Loading modules: [ libc.so.1 ld.so.1 ] > $c libc.so.1‘__nanosleep+0x15(8047900, 8047908) 150
libc.so.1‘sleep+0x35(a) main+0xbc(2, 8047970, 804797c) _start+0x7a(2, 8047a74, 8047a7a, 0, 8047a7d, 8047b91) > • SIGTERM je defaultn´ı sign´ al pro pˇr´ıkaz kill(1) • SIGUSR1 a SIGUSR2 nejsou pouˇzity ˇz´adn´ ym syst´emov´ ym vol´an´ım a jsou plnˇe k dispozici uˇzivateli • zaj´ımavost: FreeBSD 5.3 obsahuje chybu, kter´a dovol´ı za jist´ ych okolnost´ı zachytit sign´ al SIGKILL. Pˇr´ıklad: signals/catch-SIGKILL.c .
Pˇ rehled sign´ al˚ u (3) job control: SIGCHLD
zmˇena stavu synovsk´eho procesu (ignore)
SIGCONT
pokraˇcov´ an´ı pozastaven´eho procesu (continue)
SIGSTOP
pozastaven´ı (stop, nelze oˇ setˇ rit ani ignorovat)
SIGTSTP
pozastaven´ı z termin´alu Ctrl-Z (stop)
SIGTTIN
ˇcten´ı z termin´ alu procesem na pozad´ı (stop)
SIGTTOU
z´ apis na termin´al procesem na pozad´ı (stop)
• souˇc´ ast´ı nepovinn´eho POSIX rozˇs´ıˇren´ı, existuj´ı pouze kdyˇz v je definov´ ano makro POSIX JOB CONTROL
• plat´ı, ˇze nikdy nen´ı povoleno v´ıce proces˚ um najednou ˇc´ıst z kontroln´ıho termin´ alu, ale v´ıce proces˚ u najednou m˚ uˇze na termin´al zapisovat. • pozastaven´ı skupiny proces˚ u spustˇen´e z termin´alu (ˇcasto pˇres Ctrl-Z) se prov´ ad´ı sign´ alem SIGTSTP, ne SIGSTOP; aplikace tedy tento sign´al m˚ uˇze zachytit.
151
Pˇ rehled sign´ al˚ u (4) ˇ casovaˇ ce: SIGALRM
pl´ anovan´e ˇcasov´e pˇreruˇsen´ı (exit)
SIGPROF
vyprˇsen´ı profiluj´ıc´ıho ˇcasovaˇce (exit)
SIGVTALRM
vyprˇsen´ı virtu´aln´ıho ˇcasovaˇce (exit)
r˚ uzn´ e: SIGPOLL
testovateln´ a ud´alost (exit)
SIGTRAP
ladic´ı pˇreruˇsen´ı (core)
SIGURG
urgentn´ı ud´ alost na soketu (ignore)
• SIGALRM a souvisej´ıc´ı funkce alarm se pouˇz´ıvaj´ı pro odmˇeˇrov´an´ı ˇcasov´ ych interval˚ u v uˇzivatelsk´em procesu (napˇr. pˇri implementaci timeout˚ u).
152
Nastaven´ı obsluhy sign´ al˚ u int sigaction(int sig, const struct sigaction *act, struct sigaction *oact ); • nastav´ı obsluhu sign´ alu sig podle act a vr´at´ı pˇredchoz´ı nastaven´ı v oact. • obsah struktury sigaction: – void (*sa handler )(int) . . . SIG DFL, SIG IGN, nebo adresa handleru – sigset t sa mask . . . sign´aly blokovan´e v handleru, nav´ıc je blokov´ an sign´ al sig – int sa flags . . . SA RESETHAND (pˇri vstupu do handleru nastavit SIG DFL), SA RESTART (restartovat pˇreruˇsen´a syst´emov´ a vol´ an´ı), SA NODEFER (neblokovat sign´al sig bˇehem obsluhy)
• kdyˇz je act == NULL, pouze se zjist´ı nastaven´ı obsluhy, nemˇen´ı se. Jestliˇze n´ as pˇredchoz´ı nastaven´ı nezaj´ım´a, lze pouˇz´ıt oact == NULL. • pokud nen´ı nastaveno SA RESTART, syst´emov´a vol´an´ı aktivn´ı v bodˇe pˇr´ıchodu sign´ alu skonˇc´ı s chybou EINTR. Restartov´an´ı nemus´ı fungovat pro vˇsechna syst´emov´ a vol´ an´ı, napˇr. na FreeBSD je select pˇreruˇsen sign´alem vˇzdy, i kdyˇz je nastaveno SA RESTART (pozn: nemus´ı b´ yt pravda u souˇcasn´ ych verz´ı, nezkouˇsel jsem to na nich). • pozor na probl´em vz´ ajemn´eho vylouˇcen´ı mezi procesem a handlerem, popˇr. mezi handlery pro r˚ uzn´e sign´aly. Jestliˇze je nastaveno SA NODEFER, mˇel by b´ yt handler reentrantn´ı. • v handleru sign´ alu by se mˇ ely pouˇ z´ıvat pouze funkce, kter´ e jsou pro takov´ e pouˇ zit´ı bezpeˇ cn´ e. Mus´ı bud’ b´ yt reentrantn´ı, nebo je nutn´e zajistit, aby nepˇriˇsel sign´ al v nevhodnou dobu (napˇr. uvnitˇr funkce pˇrijde sign´ al, v jehoˇz handleru se vol´a stejn´a funkce). Minim´aln´ı skupina funkc´ı, kter´e mus´ı b´ yt tzv. async-signal-safe, je vyjmenov´ana v SUSv3 v sekci System Interfaces: General Information ⇒ Signal Concepts ⇒ Signal Actions (2.4.3). Jednotliv´e syst´emy mohou samozˇrejmˇe definovat i dalˇs´ı takov´e funkce. Zda funkce je nebo nen´ı bezpeˇcnˇe pouˇziteln´a v handleru by mˇelo b´ yt jasn´e z manu´ alov´e str´ anky; na Solarisu je tato informace vˇzdy v sekci ATTRIBUTES. • proˇc m˚ uˇze nastat probl´em, kdyˇz pouˇzijete v handleru sign´alu jinou funkci neˇz async-signal-safe? Je to jednoduch´e – pˇredstavte si, ˇze kdyˇz program vykon´ av´ a funkci, kter´ a nen´ı async-signal-safe, pˇrijde sign´al a v handleru se 153
vyvol´ a funkce stejn´ a. Pokud funkce nen´ı pro takov´e pouˇzit´ı napsan´a, tak m˚ uˇze doj´ıt napˇr´ıklad k nekonzistenci statick´ ych dat ve funkci pouˇzit´ ych, pˇr´ıpadnˇe k uv´ aznut´ı (dead lock) apod. Pr´avˇe kv˚ uli tomu, ˇze v handlerech lze bezpeˇcnˇe pouˇz´ıt jen podmnoˇzinu existuj´ıc´ıch vol´an´ı, se v handleru ˇcasto pouze nastav´ı glob´ aln´ı promˇenn´a oznaˇcuj´ıc´ı pˇr´ıchod pˇr´ısluˇsn´eho sign´alu a ta se n´ aslednˇe testuje, napˇr´ıklad v cyklu serveru, kter´ y vyˇrizuje poˇzadavky. Zpomalen´ı obsluhy sign´ alu je minim´aln´ı, protoˇze funkce kter´a ˇcek´a na dalˇs´ı poˇzadavek je typicky pˇreruˇsiteln´a sign´alem a v takov´em pˇr´ıpadˇe ihned vrac´ı EINTR. N´ asleduje kontrola glob´aln´ı(ch) promˇenn´e(´ ych) na to, zda byl pˇrijmut nˇejak´ y sign´ al. • funkce sigaction je obecnˇejˇs´ı neˇz starˇs´ı funkce signal a sigset, kter´e zde ani nezmiˇ nuji. Doporuˇcuji pouˇz´ıvat pouze sigaction. Pouˇzit´ı signal nen´ı napˇr´ıklad spr´ avn´e s vl´ akny, viz specifikace: “Use of this function is unspecified in a multi-threaded process.” • Pozor na to, ˇze chov´ an´ı funkce signal() se m˚ uˇze liˇsit podle syst´emu. Napˇr´ıklad na FreeBSD z˚ ust´ av´ a handler st´ale nastaven, na Solarisu je z d˚ uvodu zachov´ an´ı zpˇetn´e kompatibility handler pˇred jeho vyvol´an´ım resetov´an na SIG DFL. Funkce pˇr´ıznaku SA RESETHAND je pr´avˇe to, aby bylo moˇzn´e simulovat p˚ uvodn´ı chov´ an´ı funkce signal(), kter´e b´ yv´a implementov´ano pomoc´ı syst´emov´eho vol´ an´ı sigaction(). Pˇr´ıklad na rozd´ıl mezi funkcemi signal() a sigaction(): signal/signal-vs-sigaction.c . • (nebudete nejsp´ıˇs potˇrebovat) pro v´ yskok z handleru sign´alu jinam neˇz na m´ısto vzniku sign´ alu se daj´ı pouˇz´ıt funkce sigsetjmp a siglongjmp. Pozor na to, ˇze v tomto pˇr´ıpadˇe si mus´ıme b´ yt jisti, ˇze v okamˇziku pˇr´ıchodu sign´alu nen´ı program uvnitˇr ne-reentrantn´ı funkce. V´ yskokem z handleru do hlavn´ıho programu nen´ı vykon´ av´ an´ı takov´e funkce ukonˇceno a mohou nastat stejn´e probl´emy jako pˇri vol´ an´ı ne-reentrantn´ı funkce pˇr´ımo z handleru. • pokud syst´em podporuje ˇc´ ast POSIX.1b zvanou Realtime Signals Extension (RTS), je moˇzn´e pˇr´ıznakem SA SIGINFO toto rozˇs´ıˇren´ı pouˇz´ıt. V tom pˇr´ıpadˇe se pouˇzije jin´ a poloˇzka struktury sigaction pro ukazatel na handler, a tou je sa sigaction. Tento handler m´a jiˇz 3 parametry a je moˇzn´e zjistit napˇr´ıklad to, kter´ y proces sign´al poslal, pod jak´ ym uˇzivatelem proces bˇeˇzel a mnoho dalˇs´ıch informac´ı. Z´ajemce odkazuji na manu´alovou str´anku signal.h(3HEAD) na Solarisu, specifikaci tohoto hlaviˇckov´eho souboru pˇr´ımo v SUSv3 nebo na knihu [POSIX.4], strana 5. Dalˇs´ı informace jsou tak´e na stran´ ach 16 a 165. Pˇr´ıklady: signals/siginfo.c , signals/sigqueue.c .
154
Pˇ r´ıklad: ˇ casovˇ e omezen´ y vstup void handler(int sig) { write(2," !!! TIMEOUT !!! \n", 17); } int main(void) { char buf[1024]; struct sigaction act; int sz; act.sa handler = handler; sigemptyset(&act.sa mask); act.sa flags = 0; sigaction(SIGALRM, &act, NULL); alarm(5); sz = read(0, buf, 1024); if (sz > 0) write(1, buf, sz); return (0); }
• lze pouˇz´ıvat i ˇcasovaˇce s jemnˇejˇs´ım rozliˇsen´ım neˇz 1 s. Nastavuj´ı a testuj´ı se funkcemi setitimer a getitimer. Pˇri vyprˇsen´ı pos´ılaj´ı sign´aly procesu, kter´ y ˇcasovaˇce nastavil: – ITIMER REAL . . . mˇeˇr´ı re´aln´ y ˇcas, pos´ıl´a SIGALRM – ITIMER VIRTUAL . . . mˇeˇr´ı virtu´aln´ı ˇcas (pouze ˇcas, kdy proces bˇeˇz´ı), pos´ıl´ a SIGVTALRM – ITIMER PROF . . . mˇeˇr´ı virtu´aln´ı ˇcas a ˇcas, kdy syst´em bˇeˇz´ı na konto procesu, pos´ıl´ a SIGPROF • pozn: vˇsimˇete si, ˇze aˇckoliv by to sv´adˇelo pouˇz´ıt pro tisk hl´aˇsky v pˇr´ıkladu funkci fprintf apod., nemusel by to b´ yt dobr´ y n´apad, protoˇze nemus´ı b´ yt bezpeˇcn´ a pro pouˇzit´ı v handleru sign´alu, viz strana 160. • pˇr´ıklad: signals/alarm.c
155
Blokov´ an´ı sign´ al˚ u • blokovan´e sign´ aly budou procesu doruˇceny a zpracov´any aˇz po odblokov´ an´ı. int sigprocmask(int how, const sigset t *set, sigset t *oset ); • nastav´ı masku blokovan´ ych sign´al˚ u a vr´at´ı starou masku. u co se maj´ı blokovat, pro • how – SIG BLOCK pro pˇrid´an´ı sign´al˚ odebr´ an´ı SIG UNBLOCK, pro kompletn´ı zmˇenu masky SIG SETMASK • pro manipulaci s maskou sign´al˚ u slouˇz´ı funkce: sigaddset(), sigdelset(), sigemptyset(), sigfillset(), sigismember() int sigpending(sigset t *set ); • vr´ at´ı ˇcekaj´ıc´ı zablokovan´e sign´aly.
• Je rozd´ıl mezi ignorov´ an´ım a blokov´an´ım sign´alu. Ignorovan´ y sign´al j´adro zahod´ı a proces ho nedostane, blokovan´ y sign´al proces dostane po jeho odblokov´ an´ı. • Z´ avis´ı na implementaci, zda pˇri v´ıcen´asobn´em doruˇcen´ı stejn´eho sign´alu procesu, kter´ y m´ a tento sign´ al zablokovan´ y, bude sign´al po odblokov´an´ı oˇsetˇren jednou nebo v´ıcekr´ at. • V pˇr´ıpadˇe rozˇs´ıˇren´ı sign´ al˚ u z POSIX.4 (strana 161), tj. pouˇzit´ı pˇr´ıznaku SA SIGINFO, jsou sign´ aly doruˇcovan´e pˇres frontu a tedy se ˇz´adn´ y n´asobn´ y v´ yskyt stejn´eho sign´ alu neztrat´ı. • Argument oset pro z´ısk´ an´ı p˚ uvodn´ı masky m˚ uˇze b´ yt NULL, stejnˇe jako m˚ uˇze b´ yt nastaven´ y na NULL i parametr druh´ y, tj. set. Ve speci´aln´ım pˇr´ıpadˇe, kdy jsou oba parametry nulov´e, funkce sigprocmask nedˇel´a nic.
156
Pˇ r´ıklad: blokov´ an´ı sign´ al˚ u sigset t sigs, osigs; structure sigaction sa; sigfillset(&sigs); sigprocmask(SIG BLOCK, &sigs, &osigs); switch(cpid = fork()) { case -1: /* Chyba */ sigprocmask(SIG SETMASK, &osigs, NULL); ... case 0: /* Synovsk´ y proces */ sa.sa handler = h cld; sigemptyset(&sa.sa mask); sa.sa flags = 0; sigaction(SIGINT, &sa, NULL); sigprocmask(SIG SETMASK, &osigs, NULL); ... default: /* Rodiˇ covsk´ y proces */ sigprocmask(SIG SETMASK, &osigs, NULL); ... }
• pˇr´ıklad ukazuje situaci, kdy proces vytv´aˇr´ı potomky pomoc´ı fork a je potˇreba, aby potomci mˇeli jin´ y handler sign´al˚ u neˇz rodiˇcovsk´ y proces. Funguje to proto, ˇze vol´ an´ı fork nemˇen´ı masky sign´al˚ u, viz strana 136. • pro jednoduchost v pˇr´ıkladu blokuji vˇsechny sign´aly, i kdyˇz na stran´ach 156 a 231 je vysvˇetleno, proˇc to nen´ı spr´avn´e pouˇzit´ı maskov´an´ı. • blokov´ an´ı je vhodn´e pouˇz´ıt tam, kde oˇsetˇren´ı pˇreruˇsen´ı uprostˇred posloupnosti operac´ı by bylo pˇr´ıliˇs sloˇzit´e, nebo kde korektn´ı oˇsetˇren´ı nen´ı jinak moˇzn´e. V uveden´em pˇr´ıkladˇe by bez blokov´an´ı sign´al˚ u mohl synovsk´ y proces dostat sign´ al dˇr´ıv, neˇz stihne zmˇenit handler. • dalˇs´ı pˇr´ıklad je proces, kter´ y pˇri vyprˇsen´ı timeoutu pˇreruˇs´ı prov´adˇenou posloupnost operac´ı vol´ an´ım siglongjmp zevnitˇr handleru sign´alu. Je potˇreba zablokovat sign´ al SIGALRM bˇehem prov´adˇen´ı atomick´ ych podposloupnost´ı (tj. takov´ ych, kter´e se mus´ı prov´est bud’ cel´e, nebo v˚ ubec ne).
157
ˇ an´ı na sign´ Cek´ al int pause(void); • pozastav´ı volaj´ıc´ı proces do pˇr´ıchodu (nemaskovan´eho) sign´alu int sigsuspend(const sigset t *sigmask ); • jako pause(), ale nav´ıc po dobu ˇcek´an´ı masku blokovan´ ych sign´ al˚ u zmˇen´ı na sigmask int sigwait(const sigset t *set, int *sig ); • ˇcek´ a na pˇr´ıchod sign´ alu z mnoˇziny set (tyto sign´aly mus´ı b´ yt pˇredt´ım zablokovan´e), ˇc´ıslo sign´alu vr´at´ı v sig. Vrac´ı 0 nebo ˇc´ıslo chyby. • nevol´ a se handler sign´ alu (to ale nen´ı v normˇe jednoznaˇcnˇe definov´ ano)
• Nemaskovan´ y sign´ al v pause a sigsuspend vyvol´a handler a po jeho skonˇcen´ı program opust´ı sign´ al zachycuj´ıc´ı funkci a pokraˇcuje d´ale. Pokud m´a ale sign´ al proces ukonˇcit (napˇr. nemaskovan´ y SIGTERM bez handleru), stane se tak. • Pomoc´ı tˇechto funkc´ı a blokov´an´ı sign´al˚ u se implementuje synchronn´ı obsluha sign´ al˚ u. Proces nejprve zablokuje sign´aly, kter´e ho zaj´ımaj´ı, a pak na nˇe ve vhodn´ ych chv´ıl´ıch bud’ ˇcek´ a, nebo jen testuje (pomoc´ı sigpending), zda sign´ al pˇriˇsel, a pokud ne, pokraˇcuje d´al. • Funkce sigwait byla pˇrid´ ana s POSIX-1003.1c rozˇs´ıˇren´ım (vl´akna) a je to “jedin´ y” spr´ avn´ y zp˚ usob, jak obsluhovat asynchronn´ı sign´aly v multivl´ aknov´e aplikaci. To ˇze byla pˇrid´ana s vl´akny je potvrzeno i t´ım, ˇze v pˇr´ıpadˇe probl´em˚ u vrac´ı pˇr´ımo ˇc´ıslo chyby. • Je nutn´e d´ at pozor na to, ˇze existuj´ı i pˇr´ıbuzn´e podobnˇe se jmenuj´ıc´ı funkce sigwaitinfo a sigtimedwait, definovan´e s rozˇs´ıˇren´ım POSIX-1003.1b (realtime). Funguj´ı na podobn´em principu, ale na rozd´ıl od sigwait pracuj´ı s errno a je z nich moˇzn´e z´ıskat v´ıce informac´ı d´ıky struktuˇre siginfo t, viz strana 161. Je tedy moˇzn´e je pouˇz´ıt m´ısto sigwait. • Pˇr´ıklad (sign´ al se pouˇzije pro synchronizaci dvou proces˚ u komunikuj´ıc´ıch pˇres sd´ılenou pamˇet’): signals/sigwait.c • Pozor na to, ˇze byste nemˇeli tento zp˚ usob obsluhy sign´al˚ u pouˇz´ıvat pro sign´ aly synchronn´ı jako jsou SIGSEGV, SIGILL, apod. V´ıce se doˇctete na stran´ach 156 a 231. 158
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
159
Probl´ em: konflikt pˇ ri sd´ılen´ı dat • m´ ame strukturu struct { int a, b; } shared ; • for( ; ; ) { /* neatomick´ a operace */ a = shared.a; b = shared.b; if (a != b) printf("NEKONZISTENTN´ I STAV"); /* neatomick´ a operace */ shared.a = val; shared.b = val; } • jestliˇze tento cyklus spust´ıme ve dvou r˚ uzn´ ych procesech (nebo vl´ aknech), kter´e obˇe sd´ılej´ı stejnou strukturu shared a maj´ı r˚ uzn´e hodnoty val, bude doch´azet ke konflikt˚ um. • pˇr´ıˇcina: operace na zv´ yraznˇen´ ych ˇr´adc´ıch nejsou atomick´e.
• ani operace, kterou lze v C zapsat jedn´ım pˇr´ıkazem, nemus´ı b´ yt atomick´a. Pˇr.: na RISCov´ ych procesorech se pˇr´ıkaz a++ typicky pˇreloˇz´ı jako sekvence: load reg,[a] inc reg store [a],reg a to z toho d˚ uvodu, ˇze na t´eto architektuˇre nelze inkrementovat ˇc´ıslo pˇr´ımo v pamˇeti. Pro tyto pˇr´ıpady m´ a napˇr´ıklad Solaris sadu funkc´ı atomic add(3c), jejichˇz pouˇzit´ı je mnohem rychlejˇs´ı neˇz klasick´e zamykac´ı mechanismy. V´ıce viz strana 242. • obecnˇe obdobn´ y probl´em nast´av´a, kdyˇz v´ıce proces˚ u sd´ıl´ı nˇejak´ y syst´emov´ y zdroj. • pˇr´ıklad: race/race.c
160
Sc´ en´ aˇ r konfliktu Procesy A(val==1) a B(val==2)
a
b
1.
poˇc´ ateˇcn´ı stav struktury
?
?
2.
proces A zap´ıˇse do poloˇzky a
1
?
3.
proces B zap´ıˇse do poloˇzky a
2
?
4.
proces B zap´ıˇse do poloˇzky b
2
2
5.
proces A zap´ıˇse do poloˇzky b
2
1
6.
struktura je v nekonzistentn´ım stavu a jeden z proces˚ u to zjist´ı.
• dalˇs´ı moˇznost: 1. struktura je v konzistentn´ım stavu, napˇr. (1, 1) 2. proces B zap´ıˇse 2 do poloˇzky a 3. proces A pˇreˇcte hodnotu struktury (2, 1) dˇr´ıve, neˇz proces B stihne zapsat poloˇzku b • pozor na to, ˇze synchronizaˇcn´ı probl´emy se ˇcasto projev´ı aˇz na v´ıceprocesorov´em stroji, nebo na v´ıce procesorech neˇz kolik jich m´ate pˇri v´ yvoji k dispozici apod. Je tˇreba na to myslet pˇri testov´an´ı.
161
ˇ sen´ı: vz´ Reˇ ajemn´ e vylouˇ cen´ı proces˚ u • je potˇreba zajistit atomicitu operac´ı nad strukturou, tzn. jeden proces prov´ ad´ı modifikaci a dokud neuvede strukturu do konzistentn´ıho stavu, druh´ y proces s n´ı nem˚ uˇze manipulovat. Procesy A(val==1) a B(val==2)
a
b
poˇc´ ateˇcn´ı stav struktury
?
?
proces A zap´ıˇse do poloˇzky a
1
?
proces B mus´ı ˇcekat
1
?
proces A zap´ıˇse do poloˇzky b
1
1
proces B zap´ıˇse do poloˇzky a
2
1
6.
proces B zap´ıˇse do poloˇzky b
2
2
7.
Struktura je v konzistentn´ım stavu.
1. 2. 3. 4. 5.
• je tˇreba zajistit vz´ ajemn´e vylouˇcen´ı i pˇri ˇcten´ı, aby ˇctouc´ı proces nepˇreˇcetl nekonzistentn´ı obsah struktury uprostˇred zmˇen. Pˇri z´apisu je nutn´e vylouˇcit vˇsechny ostatn´ı procesy, ale pˇri ˇcten´ı staˇc´ı vylouˇcit jen z´apis, souˇcasn´e ˇcten´ı v´ıce procesy nevad´ı. • kritick´ a sekce je kus k´ odu, kter´ y by mˇel prov´adˇet pouze jeden proces nebo vl´ akno, jinak m˚ uˇze doj´ıt k nekonzistenc´ım; napˇr´ıklad ˇspatnˇe pospojovan´ y v´ azan´ y seznam, neodpov´ıdaj´ıc´ı si indexy v datab´azi apod. Je moˇzn´e to definovat i tak, ˇze kritick´ a sekce je k´od, kter´ y pˇristupuje k nebo modifikuje bl´ıˇze neurˇcen´ y zdroj sd´ılen´ y v´ıce procesy nebo vl´akny a proto je nutn´e pˇr´ıstup k takov´emu k´ odu synchronizovat. Kritick´a sekce by mˇela b´ yt co nejkratˇs´ı, aby ostatn´ı procesy (vl´ akna) ˇz´ adaj´ıc´ı o vstup do t´eto sekce ˇcekaly co nejkratˇs´ı moˇznou dobu. Druh´ a definice je o nˇeco obecnˇejˇs´ı, protoˇze se do n´ı vejdou i situace, kdy pouze jeden proces nebo vl´akno m˚ uˇze stav mˇenit, ale pokud se tak nedˇeje, m˚ uˇze v´ıce proces˚ u ˇci vl´aken dan´ y stav ˇc´ıst, to znamen´a ˇze k´od kritick´e sekce za jist´ ych pˇresnˇe definovan´ ych podm´ınek m˚ uˇze vykon´avat v´ıce proces˚ u nebo vl´ aken najednou.
162
Probl´ em: konflikt zapisovatel˚ uaˇ cten´ aˇ r˚ u • nˇekolik bˇeˇz´ıc´ıch proces˚ u zapisuje protokol o sv´e ˇcinnosti do spoleˇcn´eho log-souboru. Nov´ y z´aznam je pˇripojen vˇzdy na konec souboru. • pokud z´ apis z´ aznamu nen´ı proveden atomickou operac´ı, m˚ uˇze doj´ıt k prom´ıch´ an´ı v´ıce souˇcasnˇe zapisovan´ ych z´aznam˚ u. • zapisovat sm´ı vˇzdy pouze jeden proces. • dalˇs´ı procesy ˇctou data z log-souboru. • pˇri pˇreˇcten´ı pr´ avˇe zapisovan´eho z´aznamu obdrˇz´ıme nespr´avn´a (ne´ upln´ a) data. • bˇehem operace z´ apisu ze souboru nelze ˇc´ıst. Kdyˇz nikdo nezapisuje, m˚ uˇze v´ıce proces˚ u ˇc´ıst souˇcasnˇe.
• povoleny jsou tedy 2 situace: jeden zapisovatel nebo v´ıce ˇcten´aˇr˚ u • na lok´ aln´ım disku lze pro synchronizaci zapisovatel˚ u pouˇz´ıt ˇreˇsen´ı pomoc´ı pˇr´ıznaku O APPEND, kter´e ale nebude fungovat napˇr. na vzd´alen´em disku pˇr´ıstupn´em pˇres NFS nebo v pˇr´ıpadˇe, ˇze z´apis jedn´e logovac´ı zpr´avy je proveden v´ıce neˇz jedn´ım vol´ an´ım write(). Nav´ıc to neˇreˇs´ı synchronizaci ˇcten´aˇr˚ u – lze ˇc´ıst i v pr˚ ubˇehu z´ apisu.
163
ˇ sen´ı: zamyk´ Reˇ an´ı soubor˚ u • zapisuj´ıc´ı proces zamkne soubor pro z´apis. Ostatn´ı procesy (zapisovatel´e i ˇcten´ aˇri) se souborem nemohou pracovat a mus´ı ˇcekat na odemˇcen´ı z´ amku. • ˇctouc´ı proces zamkne soubor pro ˇcten´ı. Zapisovatel´e mus´ı ˇcekat na odemˇcen´ı z´ amku, ale ostatn´ı ˇcten´aˇri mohou tak´e zamknout soubor pro ˇcten´ı a ˇc´ıst data. • v jednom okamˇziku m˚ uˇze b´ yt na souboru aktivn´ı nejv´ yˇse jeden z´ amek pro z´ apis nebo libovolnˇe mnoho z´amk˚ u pro ˇcten´ı, ale ne oba typy z´ amk˚ u souˇcasnˇe. • z d˚ uvodu efektivity by kaˇzd´ y proces mˇel drˇzet z´amek co nejkratˇs´ı dobu a pokud moˇzno nezamykat cel´ y soubor, ale jen u ´sek, se kter´ ym pracuje. Preferovan´e je pasivn´ı ˇcek´an´ı, aktivn´ı ˇcek´ an´ı je vhodn´e jen na velmi kr´atkou dobu.
• dva zp˚ usoby ˇcek´ an´ı: aktivn´ı (busy waiting) – proces v cyklu testuje podm´ınku, na kterou ˇcek´a, dokud nen´ı splnˇena pasivn´ı – proces se zaregistruje v j´adru jako ˇcekaj´ıc´ı na podm´ınku a pak se usp´ı, j´ adro ho probud´ı, kdyˇz dojde ke splnˇen´ı podm´ınky • aktivn´ı ˇcek´ an´ı je ospravedlniteln´e pouze ve speci´aln´ıch situac´ıch. Na takov´e situace asi v z´ apoˇctov´em programu nenaraz´ıte a urˇcitˇe ne v pˇr´ıkladu u zkouˇsky. Pouˇ zit´ı aktivn´ıho ˇ cek´ an´ı v takov´ em pˇ r´ıpadˇ e automaticky znamen´ a nesplnˇ en´ı zad´ an´ı p´ısemky.
164
Synchronizaˇ cn´ı mechanismy • teoretick´e ˇreˇsen´ı – algoritmy vz´ajemn´eho vylouˇcen´ı (Dekker 1965, Peterson 1981) • z´ akaz pˇreruˇsen´ı (na 1 CPU stroji), speci´aln´ı test-and-set instrukce • lock-soubory • n´ astroje poskytovan´e operaˇcn´ım syst´emem – semafory (souˇc´ ast System V IPC) – z´ amky pro soubory (fcntl(), flock()) – synchronizace vl´ aken: mutexy (ohraniˇcuj´ı kritick´e sekce, pouze jedno vl´ akno m˚ uˇze drˇzet mutex), podm´ınkov´ e promˇ enn´ e (zablokuj´ı vl´akno, dokud jin´e vl´akno nesignalizuje zmˇenu podm´ınky), read-write z´ amky (sd´ılen´e a exkluzivn´ı z´amky, podobnˇe jako pro soubory)
• Dekker i Peterson potˇrebuj´ı k dosaˇzen´ı poˇzadavan´eho v´ ysledku pouze sd´ılenou pamˇ et’, tj. nˇekolik promˇenn´ ych sd´ılen´ ych obˇema procesy. • Dekkerovo ˇreˇsen´ı se ud´ av´ a jako prvn´ı ˇreˇsen´ı probl´emu vz´ajemn´eho vylouˇcen´ı dvou proces˚ u, aniˇz by bylo nutn´e aplikovat naivn´ı algoritmus striktn´ıho stˇr´ıd´ an´ı, tj. pokud druh´ y proces nevykazoval z´ajem o vstup do kritick´e sekce, mohl tam prvn´ı (a naopak) proces vstoupit tolikr´at za sebou, kolikr´at chtˇel. Dekkerovo ˇreˇsen´ı nen´ı v˚ ubec trivi´aln´ı, porovnejte s o 16 let mladˇs´ım ˇreˇsen´ım Petersonov´ ym, napˇr´ıklad na en.wikipedia.org (hledejte “Dekker’s algorithm”, “Peterson’s algorithm”) • my se nebudeme zab´ yvat teoretick´ ymi algoritmy vz´ajemn´eho vylouˇcen´ı ani nebudeme popisovat hardwarov´e mechanismy pouˇz´ıvan´e j´adrem. Zamˇeˇr´ıme se pouze na pouˇzit´ı lock soubor˚ u (kter´e vyuˇz´ıvaj´ı atomiˇcnosti nˇekter´ ych souborov´ ych operac´ı) a speci´ aln´ıch synchronizaˇcn´ıch n´astroj˚ u poskytovan´ ych j´ adrem. • podm´ınkov´e promˇenn´e = conditional variables
165
Lock-soubory • pro kaˇzd´ y sd´ılen´ y zdroj existuje dohodnut´e jm´eno souboru. Zamˇcen´ı zdroje se provede vytvoˇren´ım souboru, odemˇcen´ı smaz´ an´ım souboru. Kaˇzd´ y proces mus´ı otestovat, zda soubor existuje, a pokud ano, tak poˇckat. void lock(char *lockfile) { while( (fd = open(lockfile, O RDWR|O CREAT|O EXCL, 0600)) == -1) ˇ ame ve smyˇcce na odemˇcen´ı */ sleep(1); /* Cek´ close(fd); } void unlock(char *lockfile) { unlink(lockfile); }
• kl´ıˇcem k u ´spˇechu je samozˇrejmˇe pouˇzit´ı flagu O EXCL ym • pˇr´ıklad: file-locking/lock-unlock.c , a pouˇzijte spoleˇcnˇe se shellov´ skriptem file-locking/run.sh . • pˇri hav´ arii procesu nedojde ke zruˇsen´ı pˇr´ıpadn´ ych z´amk˚ u a ostatn´ı procesy by ˇcekaly vˇeˇcnˇe. Proto je vhodn´e si do lock-souboru poznamenat PID procesu, kter´ y z´ amek vytvoˇril. Proces, kter´ y ˇcek´a na odemˇcen´ı, m˚ uˇze testovat, zda proces, kter´emu z´ amek patˇr´ı, st´ale bˇeˇz´ı. Kdyˇz ne, lze z´amek zruˇsit a znovu zkusit vytvoˇrit. User level pˇr´ıkaz kter´ y toto um´ı a dovoluje pouˇz´ıvat lock soubory z shellov´ ych skript˚ u je napˇr´ıklad shlock(1) (na FreeBSD v /usr/ports/sysutils/shlock), teoreticky by vˇsak mohl zp˚ usobit situaci z n´ asleduj´ıc´ıho odstavce. • pozor: jestliˇze v´ıce proces˚ u najednou zjist´ı, ˇze proces drˇz´ıc´ı z´amek uˇz neexistuje, m˚ uˇze doj´ıt k n´ asleduj´ıc´ı chybˇe. Prvn´ı proces smaˇze z´amek a znovu ho vytvoˇr´ı se sv´ ym PID. Dalˇs´ı proces udˇel´a tot´eˇz, protoˇze operace pˇreˇcten´ı obsahu souboru a jeho n´ asledn´eho smaz´an´ı nen´ı atomick´a. Ted’ si ale oba procesy mysl´ı, ˇze z´ıskaly z´ amek! • probl´ em: funkce lock() obsahuje aktivn´ı ˇcek´an´ı na uvolnˇen´ı z´amku. Lze ˇreˇsit napˇr. tak, ˇze proces, kter´ y z´ısk´a z´amek, otevˇre pro z´apis pojmenovanou ˇ rouru. Cekaj´ ıc´ı procesy se usp´ı t´ım, ˇze zkus´ı ˇc´ıst z roury. Souˇc´ast´ı unlock() bude i zavˇren´ı roury a t´ım uvolnˇen´ı ˇcekaj´ıc´ıch proces˚ u. Upozorˇ nuji na to, ˇ ze zkouˇ skov´ e pˇ r´ıklady pro zad´ an´ı obsahuj´ıc´ı jakoukoli synchronizaci nejsou akceptov´ any jako spr´ avn´ e, pokud je jakkoli pouˇ zito aktivn´ı ˇ cek´ an´ı, vˇ cetnˇ e uk´ azan´ eho ˇ reˇ sen´ı typu sleep(1) volan´ eho ve smyˇ cce. 166
• lock soubory se v praxi vˇetˇsinou pouˇz´ıvaj´ı pouze pro situace, kdy se napˇr´ıklad hl´ıd´ a n´ asobn´e spuˇstˇen´ı t´eˇze aplikace. Ze zkuˇ senosti radˇ eji opˇ et upozorˇ nuji, ˇ ze pokud je bude student na zkouˇ sce pouˇ z´ıvat napˇ r´ıklad pro synchronizaci vl´ aken, neuspˇ eje.
Zamyk´ an´ı soubor˚ u: fcntl() int fcntl(int fildes, int cmd, ...); • k nastaven´ı z´ amk˚ u pro soubor fildes se pouˇz´ıv´a cmd: – F GETLK . . . vezme popis z´amku z tˇret´ıho argumentu a nahrad´ı ho popisem existuj´ıc´ıho z´amku, kter´ y s n´ım koliduje – F SETLK . . . nastav´ı nebo zruˇs´ı z´amek popsan´ y tˇret´ım argumentem, pokud nelze z´amek nastavit, ihned vrac´ı −1 – F SETLKW . . . jako F SETLK, ale usp´ı proces, dokud nen´ı moˇzn´e nastavit z´ amek (W znamen´a “wait”) • tˇret´ı argument obsahuje popis z´amku a je typu ukazatel na struct flock
• zamyk´ an´ı soubor˚ u sd´ılen´ ych pˇres NFS zajiˇst’uje d´emon lockd. • z´ amky jsou obecnˇe dvou typ˚ u: advisory locks – pro spr´ avnou funkci mus´ı vˇsechny procesy pracuj´ıc´ı se z´ amˇcen´ ymi soubory kontrolovat z´amky pˇred ˇcten´ım nebo z´apisem souboru; jsou v´ıce pouˇz´ıvan´e mandatory locks – jestliˇze je na souboru z´amek, budou ˇctec´ı a z´apisov´e operace se souborem automaticky zablokov´any, tj. z´amek se uplatn´ı i v procesech, kter´e ho explicitnˇe nekontroluj´ı – nedoporuˇcuj´ı se, ne vˇzdy funguj´ı (napˇr. lockd implementuje pouze advisory locking) – pro urˇcit´ y soubor se zapne nastaven´ım bitu SGID a zruˇsen´ım pr´ava spuˇstˇen´ı pro skupinu (tj. nastaven´ı, kter´e jinak nem´a velk´ y smysl - m´ıt set GID executable bit na souboru, kter´ y nen´ı spustiteln´ y). Funguje to tak, ˇze jeden proces si vezme z´amek na dan´em souboru (napˇr. pomoc´ı fcntl). Dalˇs´ı procesy pak nemus´ı explicitnˇe zamykat, protoˇze kaˇzdou operaci open/read/write s dan´ ym souborem kontroluje kernel proti z´amk˚ um na souboru a vynut´ı ˇcek´an´ı aˇz do chv´ıle kdy je z´ amek explicitnˇe prvn´ı procesem uvolnˇen.
167
Syst´em, kter´ y mandatory locking implementuje, je napˇr´ıklad Solaris nebo Linux, FreeBSD tuto vlastnost naopak nepodporuje. Manu´ alov´ a str´ anka fcntl(2) na Linuxu (2013) nedporuˇcuje mandatory locking pouˇz´ıvat, protoˇze Linuxov´a implementace obsahuje chyby kter´e vedou k soubˇehu a mandatory zamyk´an´ı nedok´aˇze tedy zajistit konzistenci. • Je d˚ uleˇzit´e si uvˇedomit, ˇze po skonˇcen´ı procesu se vˇsechny z´amky, kter´e drˇzel uvoln´ı.
Zamyk´ an´ı soubor˚ u: struct flock • l type . . . typ z´ amku – F RDLCK . . . sd´ılen´ y z´amek (pro ˇcten´ı), v´ıce proces˚ u – F WRLCK . . . exkluzivn´ı z´amek (pro z´apis), jeden proces – F UNLCK . . . odemˇcen´ı • l whence . . . jako u lseek(), tj. SEEK SET, SEEK CUR, SEEK END atek zamykan´eho u ´seku vzhledem k l whence • l start . . . zaˇc´ • l len . . . d´elka u ´seku v bajtech, 0 znamen´a do konce souboru • l pid . . . PID procesu drˇz´ıc´ıho z´amek, pouˇz´ıv´a se jen pro F GETLK pˇri n´ avratu
• soubory se daj´ı zamykat po ˇc´astech a d´a se zjistit, kter´ y proces drˇz´ı z´amek. Pˇri ukonˇcen´ı procesu se automaticky uvoln´ı vˇsechny jeho z´amky. • pokud pˇri pouˇzit´ı F GETLK nen´ı pˇr´ısluˇsn´a ˇc´ast souboru zamˇcen´a, je struktura flock vr´ acena bez zmˇeny kromˇe prvn´ı poloˇzky, kter´a je nastavena na F UNLCK. • pˇr´ıklad: file-locking/fcntl-locking.c • pˇr´ıklad na fcntl (je to pomoc´ı fcntl ,,opraven´a” verze dˇr´ıvejˇs´ıho pˇr´ıkladu race/race.c ze strany 167): race/fcntl-fixed-race.c . • zamyk´ an´ı pˇres fcntl i lockf m´a jednu d˚ uleˇzitou vlastnost, kterou v´ ystiˇznˇe popisuje napˇr´ıklad manu´ alov´a str´anka pro fcntl v syst´emu FreeBSD: This interface follows the completely stupid semantics of System V and IEEE Std 1003.1-1988 (“POSIX.1”) that require that all locks associated with a file for a given process are removed when any file descriptor for that file is closed 168
by that process. This semantic means that applications must be aware of any files that a subroutine library may access. For example if an application for updating the password file locks the password file database while making the update, and then calls getpwnam(3) to retrieve a record, the lock will be lost because getpwnam(3) opens, reads, and closes the password database. • Funkce lockf (souˇc´ ast´ı SUSv3) je jednoduˇsˇs´ı variantou fcntl, specifikuje se pouze jak zamˇc´ıt a kolik bajt˚ u od souˇcasn´e pozice v souboru. Velmi ˇcasto je implementov´ ana jako wrapper kolem fcntl. • Pˇr´ıklad file-locking/lockf.c demonstruje jak funguje mandatory locking a ukazuje pouˇzit´ı funkce lockf.
Deadlock (aka uv´ aznut´ı) • m´ ame dva sd´ılen´e zdroje res1 a res2 chr´anˇen´e z´amky lck1 a lck2. Procesy p1 a p2 chtˇej´ı kaˇzd´ y v´ yluˇcn´ y pˇr´ıstup k obˇema zdroj˚ um. p1 lock(lck1); /* OK */
p2
ˇ a na p2 */ lock(lck2); /* Cek´ use(res1, res2); unlock(lck2); unlock(lck1);
lock(lck2); /* OK */
ˇ a na p1 */ lock(lck1); /* Cek´ Deadlock use(res1, res2); unlock(lck2); unlock(lck1);
• pozor na poˇ rad´ı zamyk´ an´ı!
• obecnˇe deadlock vznikne, jestliˇze proces ˇcek´a na ud´alost, kter´a nem˚ uˇze nastat. Zde napˇr. na sebe dva procesy ˇcekaj´ı navz´ajem, aˇz ten druh´ y uvoln´ı z´ amek, ale k tomu nikdy nedojde. Dalˇs´ı moˇznost´ı je deadlock jednoho procesu, kter´ y ˇcte z roury a pˇredt´ım zapomnˇel uzavˇr´ıt z´apisov´ y konec roury. Jestliˇze rouru nem´ a uˇz nikdo dalˇs´ı otevˇrenou, pokus o ˇcten´ı ze zablokuje, protoˇze nejsou zavˇreny vˇsechny kopie z´apisov´eho deskriptoru a tedy nenastane konec souboru na rouˇre, ale ˇctouc´ı proces sv˚ uj z´apisov´ y deskriptor nem˚ uˇze zavˇr´ıt, protoˇze ˇcek´ a ve vol´an´ı read: int main(void) { int c, fd; mkfifo("test", 0666); 169
fd = open("test", O_RDWR); read(fd, &c, sizeof(c)); /* never reached */ return (0); } $ ./a.out ^C • fcntl() prov´ ad´ı kontrolu a pˇri v´ yskytu deadlocku vr´at´ı chybu EDEADLK. • je vhodn´e se deadlocku snaˇzit vyvarovat spr´avn´ ym naprogramov´an´ım a nespol´ehat se na kontrolu syst´emu.
System V IPC • IPC je zkratka pro Inter-Process Communication • komunikace mezi procesy v r´ amci jednoho syst´ emu, tj. nezahrnuje s´ıt’ovou komunikaci • semafory . . . pouˇzit´ı pro synchronizaci proces˚ u • sd´ılen´ a pamˇ et’ . . . pˇred´av´an´ı dat mezi procesy, pˇrin´aˇs´ı podobn´e probl´emy jako sd´ılen´ı soubor˚ u, k ˇreˇsen´ı lze pouˇz´ıt semafory • fronty zpr´ av . . . spojuj´ı komunikaci (zpr´ava nese data) se synchronizac´ı (ˇcek´ an´ı procesu na pˇr´ıchod zpr´avy) • prostˇredky IPC maj´ı podobnˇe jako soubory definovan´a pˇ r´ıstupov´ a pr´ ava (pro ˇcten´ı a z´apis) pro vlastn´ıka, skupinu a ostatn´ı.
• uvˇedomte si, ˇze tyto prostˇredky se vztahuj´ı ke konkr´etn´ımu syst´emu, System V, kde se objevily jako prvn´ı. Dalˇs´ı syst´emy je pak pˇrevzaly. Ze tˇ r´ı zde uveden´ ych synchronizaˇ cn´ıch prostˇ redk˚ u Systemu V se budeme zab´ yvat pouze semafory. Pro sd´ılenou pamˇ et’ je moˇ zn´ e pouˇ z´ıt jiˇ z probran´ e vol´ an´ı mmap, m´ısto zas´ıl´ an´ı zpr´ av je moˇ zn´ e pouˇ z´ıt sockety (budou v nˇ ekter´ e z pˇ r´ıˇ st´ıch pˇ redn´ aˇ sek). • prostˇredky IPC existuj´ı i pot´e, kdy skonˇc´ı proces, kter´ y je vytvoˇril. O jejich zruˇsen´ı je nutno explicitnˇe poˇz´adat (ze shellu lze zjistit seznam IPC prostˇredk˚ u pˇr´ıkazem ipcs a smazat je pˇr´ıkazem ipcrm). Stav a obsah existuj´ıc´ıch prostˇredk˚ u IPC z˚ ust´av´a v platnosti, i kdyˇz s nimi pr´avˇe nepracuje ˇz´ adn´ y proces (napˇr. data ve sd´ılen´e pamˇeti z˚ ust´avaj´ı, i kdyˇz ji nem´a ˇz´adn´ y proces pˇripojenou). 170
Semafory • zavedl je E. Dijkstra • semafor s je datov´ a struktura obsahuj´ıc´ı – cel´e nez´ aporn´e ˇc´ıslo i (voln´a kapacita) – frontu proces˚ u q, kter´e ˇcekaj´ı na uvolnˇen´ı • operace nad semaforem: init(s, n) vypr´ azdnit s.q; s.i = n P(s) if(s.i > 0) s.i-- else uspat volaj´ ıc´ ı proces a zaˇ radit do s.q V(s) if(s.q pr´ azdn´ a) s.i++ else odstranit jeden proces z s.q a probudit ho
• P je z holandsk´eho ,,proberen te verlagen” – zkus dekrementovat, V pak ze slova ,,verhogen” – inkrementovat. • operace P(s) a V(s) lze zobecnit: hodnotu semaforu je moˇzn´e mˇenit o libovolnou hodnotu n . . . P(s, n), V(s, n). • Allen B. Downey: The Little Book of Semaphores, Second Edition, on-line na http://greenteapress.com/semaphores/ • bin´ arn´ı semafor m´ a pouze hodnotu 0 nebo 1
171
Vz´ ajemn´ e vylouˇ cen´ı pomoc´ı semafor˚ u • jeden proces inicializuje semafor sem s; init(s, 1); • kritick´ a sekce se dopln´ı o operace nad semaforem ... P(s); kritick´ a sekce; V(s); ...
• inicializace semaforu na hodnotu n dovol´ı vstoupit do kritick´e sekce n proces˚ um. Zde semafor funguje jako z´amek, vˇzdy ho odemyk´a (zvyˇsuje hodnotu) stejn´ y proces, kter´ y ho zamknul (sn´ıˇzil hodnotu). • obecnˇe ale m˚ uˇze semafor zvednout jin´ y proces, neˇz kter´ y ho sn´ıˇzil; jinak by to ani nemˇelo velk´ y smysl. Je zde rozd´ıl oproti z´amk˚ um, viz strana 233.
172
API pro semafory int semget(key t key, int nsems, int semflg ); • vr´ at´ı identifik´ ator pole obsahuj´ıc´ıho nsems semafor˚ u asociovan´ y s kl´ıˇcem key (kl´ıˇc IPC PRIVATE . . . priv´atn´ı semafory, pˇri kaˇzd´em pouˇzit´ı vr´ at´ı jin´ y identifik´ator). semflg je OR-kombinace pˇr´ıstupov´ ych pr´av a konstant IPC CREAT (vytvoˇrit, pokud neexistuje), IPC EXCL (chyba, pokud existuje). int semctl(int semid, int semnum, int cmd, ...); • ˇr´ıdic´ı funkce, voliteln´ y ˇctvrt´ y parametr arg je typu union semun. int semop(int semid, struct sembuf *sops, size t nsops ); • zobecnˇen´e operace P a V.
• jak z´ıskat kl´ıˇc pro semget je vysvˇetleno na strane 183. • nejvˇetˇs´ı zaj´ımavost na System V implementaci semafor˚ u je skuteˇcnost, ˇze dan´ y syscall neoperuje nad jedn´ım semaforem, ale nad polem semafor˚ u, a to atomicky. Vˇetˇsinou vˇsak budete potˇrebovat pouze jeden semafor, tj. pole o jednom prvku. Pro takov´e pouˇzit´ı jsou System V semafory zbyteˇcnˇe sloˇzit´e. • pˇr´ıstupov´ a pr´ ava jsou jen pro ˇcten´ı a z´apis; bit execute zde nem´a smysl. • podobn´e sch´ema API funkc´ı (funkce na vytvoˇren´ı, ˇr´ızen´ı a operace) dodrˇzuj´ı i ostatn´ı System V IPC mechanismy. • jakmile je jednou pole semafor˚ u jedn´ım procesem vytvoˇreno, mohou i ostatn´ı procesy pouˇz´ıt semctl() a semop(), aniˇz by pˇredt´ım volaly semget(). To plat´ı i pro semafory vytvoˇren´e s kl´ıˇcem IPC PRIVATE, pro kter´e nelze volat semget(), protoˇze by se t´ım vytvoˇrilo nov´e pole semafor˚ u. Je to tak proto, aby i priv´ atn´ı semafory mohly b´ yt dˇedˇen´e v r´amci fork.
173
API pro semafory: semctl() • semnum . . . ˇc´ıslo semaforu v poli • moˇzn´e hodnoty cmd: – GETVAL . . . vr´ at´ı hodnotu semaforu – SETVAL . . . nastav´ı semafor na hodnotu arg.val – GETPID . . . PID procesu, kter´ y provedl posledn´ı operaci – GETNCNT . . . poˇcet proces˚ u ˇcekaj´ıc´ıch na vˇetˇs´ı hodnotu – GETZCNT . . . poˇcet proces˚ u ˇcekaj´ıc´ıch na nulu – GETALL . . . uloˇz´ı hodnoty vˇsech semafor˚ u do pole arg.array – SETALL . . . nastav´ı vˇsechny semafory podle arg.array – IPC STAT . . . do arg.buf d´a poˇcet semafor˚ u, pˇr´ıstupov´a pr´ ava a ˇcasy posledn´ıch semctl() a semop() – IPC SET . . . nastav´ı pˇr´ıstupov´a pr´ava – IPC RMID . . . zruˇs´ı pole semafor˚ u
• vol´ an´ı semctl(semid, semnum, SETVAL, arg) odpov´ıd´a obecn´e semaforov´e inicializaˇcn´ı operaci init(s, n).
174
API pro semafory: semop() • operace se prov´ ad´ı atomicky (tj. bud’ se povede pro vˇsechny semafory, nebo pro ˇz´ adn´ y) na nsops semaforech podle pole sops struktur struct sembuf, kter´a obsahuje: – sem num . . . ˇc´ıslo semaforu – sem op . . . operace ∗ P(sem num, abs(sem op)) pro sem op < 0 ∗ V(sem num, sem op) pro sem op > 0 ∗ ˇcek´ an´ı na nulovou hodnotu semaforu pro sem op == 0 – sem flg . . . OR-kombinace ∗ IPC NOWAIT . . . kdyˇz nelze operaci hned prov´est, neˇcek´a a vr´ at´ı chybu ∗ SEM UNDO . . . pˇri ukonˇcen´ı procesu vr´atit operace se semaforem
• atomiˇcnost pˇres mnoˇzinu semafor˚ u zajist´ı, ˇze nedojde k deadlocku v n´asleduj´ıc´ı situaci: dva procesy A a B budou pouˇz´ıvat dva semafory k ˇr´ızen´ı pˇr´ıstupu (zamyk´ an´ı) ke dvˇema syst´emov´ ym zdroj˚ um. Proces A je bude zamykat v poˇrad´ı (0, 1) a proces B v poˇrad´ı (1, 0). Ve chv´ıli, kdy proces A zamkne semafor 0 a B zamkne 1, dojde k deadlocku, protoˇze ani jeden proces nem˚ uˇze pokraˇcovat (potˇreboval by zamknout druh´ y semafor). Pˇri pouˇzit´ı atomick´e operace zamˇcen´ı obou semafor˚ u najednou bude u ´spˇeˇsn´ y vˇzdy pr´avˇe jeden proces, kter´ y z´ısk´ a oba semafory, druh´ y bude ˇcekat. • SEM UNDO zajist´ı, ˇze pˇri ukonˇcen´ı procesu dojde k odemˇcen´ı semafor˚ u (pouˇzit´ ych jako z´ amky), kter´e tento proces mˇel zamˇcen´e.
175
Vytv´ aˇ ren´ı prostˇ redk˚ u IPC • jeden proces prostˇredek vytvoˇr´ı, ostatn´ı se k nˇemu pˇripoj´ı. • po skonˇcen´ı pouˇz´ıv´ an´ı je tˇreba prostˇredek IPC zruˇsit. • funkce semget(), shmget() a msgget() maj´ı jako prvn´ı parametr kl´ıˇc identifikuj´ıc´ı prostˇredek IPC. Skupina proces˚ u, kter´ a chce komunikovat, se mus´ı domluvit na spoleˇcn´em kl´ıˇci. R˚ uzn´e skupiny komunikuj´ıc´ıch proces˚ u by mˇely m´ıt r˚ uzn´e kl´ıˇce. key t ftok(const char *path, int id ); • vr´ at´ı kl´ıˇc odvozen´ y ze zadan´eho jm´ena souboru path a ˇc´ısla id. Pro stejn´e id a libovolnou cestu odkazuj´ıc´ı na stejn´ y soubor vr´ at´ı stejn´ y kl´ıˇc. Pro r˚ uzn´a id nebo r˚ uzn´e soubory na stejn´em svazku vr´ at´ı r˚ uzn´e kl´ıˇce.
pozn´ amky k ftok(): • z id se pouˇzije jen nejniˇzˇs´ıch 8 bit˚ u. • nen´ı specifikov´ ano, zda bude stejn´ y kl´ıˇc vr´acen i po smaz´an´ı a znovuvytvoˇren´ı souboru. Vˇetˇsinou ne, protoˇze v kl´ıˇci se ˇcasto odr´aˇz´ı ˇc´ıslo indexov´eho uzlu. • r˚ uzn´e kl´ıˇce pro r˚ uzn´e soubory nejsou vˇzdy zaruˇcen´e. Napˇr. na Linuxu se kl´ıˇc z´ısk´ a kombinac´ı 16 bit˚ u ˇc´ısla i-uzlu, 8 bit˚ u id a 8 bit˚ u vedlejˇs´ıho ˇc´ısla zaˇr´ızen´ı. Stejn´ y kl´ıˇc pro r˚ uzn´e soubory je vr´acen, pokud se ˇc´ısla i-uzl˚ u shoduj´ı na spodn´ıch 16 bitech. • pokud tedy nepˇr´ıbuzn´e procesy chtˇej´ı pouˇz´ıvat stejn´ y semafor, mus´ı b´ yt jm´ eho souboru pro kl´ıˇ c domluveno pˇ redem. • pˇr´ıklad na semafory (je to pomoc´ı semafor˚ u ,,opraven´a” verze dˇr´ıvejˇs´ıho pˇr´ıkladu race/race.c ze strany 167): race/sem-fixed-race.c .
176
Dalˇ s´ı prostˇ redky IPC • POSIX a SUSv3 definuj´ı jeˇstˇe dalˇs´ı prostˇredky komunikace mezi procesy: – sign´ aly . . . pro uˇzivatelsk´e u ´ˇcely lze vyuˇz´ıt sign´aly SIGUSR1 a SIGUSR2 – POSIXov´ a sd´ılen´ a pamˇ et’ pˇr´ıstupn´a pomoc´ı shm open() a mmap() – POSIXov´ e semafory . . . sem open(), sem post(), sem wait(), . . . – POSIXov´ e fronty zpr´ av . . . mq open(), mq send(), mq receive(), . . . • Z BSD poch´ az´ı sokety (sockets) umoˇzn ˇuj´ıc´ı komunikaci v dom´en´ ach AF UNIX (komunikace v r´amci jednoho poˇc´ıtaˇce) a AF INET (komunikace na jednom poˇc´ıtaˇci nebo po s´ıti).
• POSIXov´e IPC pouˇz´ıv´ a pro pojmenov´an´ı jednotliv´ ych IPC objekt˚ u ˇretˇezce m´ısto numerick´ ych identifik´ ator˚ u, proto do znaˇcn´e m´ıry odpadaj´ı probl´emy s identifikac´ı zn´ am´e ze System V IPC (kde se ˇreˇs´ı napˇr. funkc´ı ftok()). • POSIXov´ a rozhran´ı zde uveden´a jsou souˇc´ast´ı rozˇs´ıˇren´ı 1003.1b (aka POSIX.4), viz strana 6. • sokety se z BSD rozˇs´ıˇrily i do ostatn´ıch UNIXov´ ych syst´em˚ u a dostaly se i do normy SUSv2. • existuj´ı dalˇs´ı API specifick´e pro konkr´etn´ı syst´em, napˇr. Solaris doors.
177
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
S´ıt’ov´ a komunikace UUCP (UNIX-to-UNIX Copy Program) – prvn´ı aplikace pro komunikaci UNIXov´ ych syst´em˚ u propojen´ ych pˇr´ımo nebo pˇres modemy, souˇc´ ast Version 7 UNIX (1978) sokety (sockets) – zavedeny ve 4.1aBSD (1982); soket je jeden konec obousmˇern´eho komunikaˇcn´ıho kan´alu vytvoˇren´eho mezi dvˇema procesy bud’ lok´alnˇe na jednom poˇc´ıtaˇci, nebo s vyuˇzit´ım s´ıt’ov´eho spojen´ı TLI (Transport Layer Interface) – SVR3 (1987); knihovna zajiˇst’uj´ıc´ı s´ıt’ovou komunikaci na u ´rovni 4. vrstvy referenˇcn´ıho modelu ISO OSI RPC (Remote Procedure Call) – SunOS (1984); protokol pro pˇr´ıstup ke sluˇzb´ am na vzd´alen´em stroji, data pˇren´aˇsena ve tvaru XDR (External Data Representation)
178
• ISO (International Standards Organization) OSI (Open Systems Interconnect) – vrstvy (layers): 1. 2. 3. 4. 5. 6. 7.
fyzick´ a (physical) linkov´ a (data link) s´ıt’ov´ a (network) transportn´ı (transport) relaˇcn´ı (session) prezentaˇcn´ı (presentation) aplikaˇcn´ı (application)
• UUCP je tvoˇreno aplikaˇcn´ımi programy, nevyˇzaduje ˇz´adnou podporu v j´adru. Implementace soket˚ u a TLI jsou souˇc´ast´ı j´adra. TLI je ve verzi SVR4 implementov´ ano s vyuˇzit´ım mechanismu STREAMS. RPC existuje jako knihovna linkovan´ a k aplikac´ım, kter´ a vyuˇz´ıv´a sokety (funguje nad protokoly TCP a UDP). RPC bylo vyvinuto jako komunikaˇcn´ı protokol pro NFS (Networked File System). • existuje v´ıce (vz´ ajemnˇe nekompatibiln´ıch) implementac´ı RPC • komunikaˇcn´ı kan´ al je specifikov´an adresami dvou soket˚ u. • sokety pro komunikaci pouze v r´amci jednoho poˇc´ıtaˇce jsou v dom´enˇe AF UNIX a jejich jm´ena jsou jm´ena speci´aln´ıch soubor˚ u, kter´e reprezentuj´ı sokety v syst´emu soubor˚ u. • sokety AF UNIX jsou nˇeco jin´eho neˇz lok´aln´ı TCP/IP komunikace pˇres loopback rozhran´ı localhost (127.0.0.1). V´ıce o AF UNIX na stranˇe 192.
TCP/IP • protokoly – IP (Internet Protocol) – pˇr´ıstupn´ y jen pro uˇzivatele root – TCP (Transmission Control Protocol) – streamov´ y, spojovan´ y, spolehliv´ y – UDP (User Datagram Protocol) – datagramov´ y, nespojovan´ y, nespolehliv´ y • IP adresa – 4 bajty (IPv4) / 16 bajt˚ u (IPv6), definuje s´ıt’ov´e rozhran´ı, nikoliv poˇc´ıtaˇc • port – 2 bajty, rozliˇsen´ı v r´amci 1 IP adresy, porty s ˇc´ıslem menˇs´ım neˇz 1024 jsou rezervovan´e (jejich pouˇzit´ı vyˇzaduje pr´ ava uˇzivatele root) • DNS (Domain Name System) – pˇrevod mezi symbolick´ ymi jm´eny a numerick´ ymi IP adresami
179
• pokud nev´ıte, o ˇcem je ˇreˇc, doporuˇcuji Peterkovy pˇredn´aˇsky; jsou volnˇe ke staˇzen´ı na webu. Jsou to nejlepˇs´ı materi´aly, kter´e jsem k dan´e problematice vidˇel. • UNIX pouˇz´ıv´ a pro s´ıt’ovou komunikaci nejˇcastˇeji rodinu protokol˚ u TCP/IP. Pro u ´ˇcely programov´ an´ı aplikac´ı n´as budou zaj´ımat pˇredevˇs´ım protokoly TCP (spojovan´ a spolehliv´ a komunikace) a UDP (nespojovan´a nespolehliv´a komunikace). V obou protokolech je jeden konec komunikaˇcn´ıho kan´alu (odpov´ıd´ a soketu) identifikov´ an IP adresou s´ıt’ov´eho rozhran´ı a ˇc´ıslem portu (pomoc´ı port˚ u se rozliˇsuj´ı s´ıt’ov´e sluˇzby bˇeˇz´ıc´ı na jednom poˇc´ıtaˇci). TCP spojen´ı je pak jednoznaˇcnˇe definov´ ano jedn´ım p´arem soket˚ u. • IP – protokol 3. vrstvy, zajiˇst’uje pˇrenos paket˚ u (datagram˚ u) mezi rozhran´ımi identifikovan´ ymi IP adresou; je nespolehliv´ y (nezaruˇcuje doruˇcen´ı dat). Je definov´ an v RFC 791. Ned´ılnou souˇc´ast´ı IP je Internet Control Message Protocol (ICMP), RFC 792. • UDP – jednoduch´ a nadstavba nad IP, pˇrid´av´a ˇc´ısla port˚ u, z˚ ust´av´a nespolehliv´ y a datagramovˇe orientovan´ y. RFC 768. • TCP – vytv´ aˇr´ı obousmˇern´e spojen´ı mezi dvˇema body (IP+port), poskytuje tok dat (stream) bez rozdˇelen´ı na zpr´avy, zajiˇst’uje ˇr´ızen´ı toku dat a spolehliv´e doruˇcov´ an´ı. Pro vytvoˇren´ı spojen´ı je tˇreba prov´est tzv. handshake. RFC 793. • DNS – hierarchicky organizovan´a datab´aze, jej´ı struktura nemus´ı m´ıt nic spoleˇcn´eho se strukturou IP adres
Spojovan´ e sluˇ zby (TCP), sekvenˇ cn´ı obsluha server
s´ıt’
fd = socket()
klient
fd = socket()
bind(fd) listen(fd) fd2 = accept(fd)
connect(fd)
read(fd2); write(fd2)
write(fd);read(fd)
close(fd2)
close(fd)
180
• uvˇedomte si, ˇze se pouˇz´ıvaj´ı bˇeˇzn´a read a write vol´an´ı. • server vytvoˇr´ı jedno spojen´ı, teprve po jeho ukonˇcen´ı akceptuje dalˇs´ıho klienta. • syst´emov´ a vol´ an´ı: – socket – vytvoˇr´ı soket, vr´at´ı jeho deskriptor – bind – definuje adresu soketu (IP adresu a ˇc´ıslo portu), mus´ı to b´ yt bud’ adresa jednoho ze s´ıt’ov´ ych rozhran´ı poˇc´ıtaˇce, na kter´em je vytvoˇren soket; pak bude soket pˇrij´ımat ˇz´adosti klient˚ u pouze pˇres toto rozhran´ı, nebo je to speci´aln´ı hodnota ,,libovoln´a adresa”; pak soket pˇrij´ım´ a poˇzadavky prostˇrednictv´ım vˇsech s´ıt’ov´ ych rozhran´ı (tzv. wildcard socket). – listen – ozn´ am´ı j´ adru, ˇze soket bude pˇrij´ımat poˇzadavky klient˚ u – accept – usp´ı proces, dokud nebude k dispozici nˇejak´a ˇz´adost klienta o spojen´ı, vytvoˇr´ı spojen´ı a vr´at´ı nov´ y deskriptor, pˇres kter´ y bude prob´ıhat dalˇs´ı komunikace s klientem, p˚ uvodn´ı deskriptor lze pouˇz´ıt k nov´emu vol´ an´ı accept pro obslouˇzen´ı dalˇs´ıho klienta – close – ukonˇc´ı komunikaci – connect – ˇz´ adost klienta o nav´az´an´ı spojen´ı, IP adresa a ˇc´ıslo portu serveru se zad´ avaj´ı jako parametry, komunikace prob´ıh´a pˇres deskriptor fd (na rozd´ıl od accept nevytv´aˇr´ı nov´ y deskriptor) • klient nemus´ı volat bind, v takov´em pˇr´ıpadˇe mu j´adro pˇridˇel´ı nˇekter´ y voln´ y port. Existuj´ı sluˇzby (napˇr. rsh), kter´e vyˇzaduj´ı, aby se klient spojoval z privilegovan´eho portu (porty 0-1023). Takov´ y klient pak mus´ı prov´est bind (a nav´ıc bˇeˇzet s dostateˇcn´ ymi pr´avy alespoˇ n do okamˇziku proveden´ı bind; dostateˇcn´ a pr´ ava mohou znamenat uˇzivatele root nebo speci´aln´ı privilegium/capability).
181
Spojovan´ e sluˇ zby (TCP), paraleln´ı obsluha server
s´ıt’
fd = socket() bind(fd) listen(fd) fd2 = accept(fd) fork()
klient
fd = socket()
connect(fd)
close(fd2) syn while(waitpid( read(fd2) -1, stat, write(fd2) WNOHANG)>0) ; exit(0)
write(fd);read(fd) close(fd)
• server akceptuje spojen´ı od klienta a na vlastn´ı komunikaci vytvoˇr´ı nov´ y proces, kter´ y po uzavˇren´ı spojen´ı s klientem skonˇc´ı. Rodiˇcovsk´ y proces m˚ uˇze mezit´ım akceptovat dalˇs´ı klienty a spouˇstˇet pro nˇe obsluˇzn´e procesy. Souˇcasnˇe je tedy obsluhov´ ano v´ıce klient˚ u. • po proveden´ı fork, ale pˇred zah´ajen´ım obsluhy spojen´ı, m˚ uˇze synovsk´ y proces prov´est exec – takto funguje napˇr. inetd (strana 214). • vol´ an´ı waitpid v cyklu odstraˇ nuje ze syst´emu zombie. Jinou moˇznost´ı je vyuˇzit´ı sign´ alu SIGCHLD, jehoˇz explicitn´ı ignorov´an´ı zabr´an´ı vzniku ˇziv´ ych mrtv´ ych, popˇr. lze instalovat handler, v nˇemˇz se vol´a wait.
182
Spojovan´ e sluˇ zby, paraleln´ı accept() server
s´ıt’
fd = socket() bind(fd) listen(fd) fork()
...
klient
fd = socket()
fd2=accept(fd) read(fd2) write(fd2) close(fd2)
connect(fd) write(fd);read(fd) close(fd)
• po bind a listen se vytvoˇr´ı nˇekolik synovsk´ ych proces˚ u a kaˇzd´ y v cyklu vol´a accept a obsluhuje klienty. J´adro, pro uˇzivatele nedeterministicky, vybere pro kaˇzd´ y poˇzadavek jeden proces, v nˇemˇz accept nav´aˇze spojen´ı. Hlavn´ı proces ˇz´ adn´e poˇzadavky nevyˇrizuje, ale ˇcek´a v nˇekter´em z vol´an´ı typu wait a vytv´ aˇr´ı procesy, kdyˇz je potˇreba. • jednotliv´e procesy serveru mezi sebou mohou komunikovat, aby v pˇr´ıpadˇe, ˇze souˇcasn´ ych poˇzadavk˚ u je v´ıce neˇz serverov´ ych proces˚ u, se tato skuteˇcnost zjistila a hlavn´ı server mohl dynamicky vytvoˇrit dalˇs´ı serverov´ y proces. • takto funguje napˇr. webov´ y server Apache. Je moˇzn´e definovat, kolik poˇzadavk˚ u m´ a potomek zpracovat, neˇz s´am skonˇc´ı. T´ımto zp˚ usobem se v´ yraznˇe omez´ı n´ asledky pˇr´ıpadn´ ych probl´em˚ u, napˇr´ıklad neuvolˇ nov´an´ı pamˇeti, tzv. memory leaks. • vˇ sechny tˇ ri uveden´ e zp˚ usoby ˇ cinnosti serveru funguj´ı se stejn´ ym klientem – ˇcinnost klienta nez´avis´ı na variantˇe serveru.
183
Datagramov´ e sluˇ zby (UDP) server
s´ıt’
klient
fd = socket()
fd = socket() bind(fd) recvfrom(fd) sendto(fd)
sendto(fd) recvfrom(fd)
close(fd)
• z pohledu volan´ ych s´ıt’ov´ ych funkc´ı je funkce serveru a klienta shodn´a. Klient je zde ten, kdo poˇsle prvn´ı datagram. • stejnˇe jako v pˇr´ıpadˇe TCP, klient nemus´ı dˇelat bind, jestliˇze mu nez´aleˇz´ı na tom, jak´e ˇc´ıslo portu bude pouˇz´ıvat. Server zjist´ı port klienta z obsahu adresn´ı ˇc´ asti datagramu. • v´ yhodou nespojovan´e sluˇzby je menˇs´ı reˇzie a to, ˇze pˇres jeden soket lze komunikovat s v´ıce procesy (pˇri spojovan´e komunikaci je spojen´ı vˇzdy nav´az´ano s jedn´ım procesem). • pro UDP je moˇzn´e volat connect. T´ım se nenav´aˇze spojen´ı, ale soket se nastav´ı tak, ˇze m˚ uˇze nad´ ale komunikovat pouze s adresou a portem specifikovan´ ymi ve vol´ an´ı connect. M´ısto sendto a recvfrom se pak pouˇz´ıvaj´ı funkce send a recv.
184
Vytvoˇ ren´ı soketu: socket() int socket(int domain, int type, int protocol ); • vytvoˇr´ı soket a vr´ at´ı jeho deskriptor. • domain – ,,kde se bude komunikovat”: – AF UNIX . . . lok´ aln´ı komunikace, adresa je jm´eno souboru – AF INET, AF INET6 . . . s´ıt’ov´a komunikace, adresa je dvojice (IP adresa, port) • type: – SOCK STREAM . . . spojovan´a spolehliv´a sluˇzba, poskytuje obousmˇern´ y sekvenˇcn´ı proud dat – SOCK DGRAM . . . nespojovan´a nespolehliv´a sluˇzba, pˇrenos datagram˚ u • protocol: 0 (default pro dan´ y type) nebo platn´e ˇc´ıslo protokolu (napˇr. 6 = TCP, 17 = UDP)
• funkce je deklarov´ ana v <sys/socket.h>, stejnˇe jako funkce z dalˇs´ıch slajd˚ u – bind, listen, a accept, a pouˇzit´e konstanty. • sokety jsou pˇ r´ıstupn´ e pˇ res deskriptory soubor˚ u. Po nav´az´an´ı spojen´ı je (spojovan´ a) komunikace pˇres soket podobn´a komunikaci pomoc´ı roury s t´ım rozd´ılem, ˇze sokety jsou vˇzdy obousmˇ ern´ e, coˇz nemus´ı platit u rour, viz strana 143. • ˇcasto uvid´ıte konstanty zaˇc´ınaj´ıc´ı PF_ (jako protocol family, napˇr. PF INET, PF_UNIX, nebo PF INET6) a pouˇz´ıvan´e ve vol´an´ı socket. Konstanty AF_ (address family) jsou pak pouˇz´ıvan´e pouze pˇri zad´av´an´ı adres soket˚ u. I kdyˇz to snad d´ av´ a vˇetˇs´ı smysl, norma specifikuje pouze AF konstanty pro pouˇzit´ı pro vol´ an´ı socket i pro zad´ av´an´ı adres. Hodnoty odpov´ıdaj´ıc´ıch konstant PF jsou pak stejn´e, tj. definovan´e pomoc´ı AF konstant. Doporuˇcuji pouˇz´ıvat jen AF. • existuj´ı dalˇs´ı typy soket˚ u pro pln´ y pˇr´ıstup k dan´emu protokolu (SOCK RAW), nebo k informac´ım z routovac´ı tabulky. Pro pouˇzit´ı SOCK RAW typicky potˇrebujete dodateˇcn´ a privilegia – proto napˇr´ıklad pˇr´ıkaz ping, kter´ y vyplˇ nuje ICMP hlaviˇcky odes´ılan´ ych paket˚ u, b´ yv´a nastaven jako SUID: $ ls -l /usr/sbin/ping -r-sr-xr-x 1 root bin
55680 Nov 14 19:01 /usr/sbin/ping
185
Pojmenov´ an´ı soketu: bind() int bind(int socket, const struct sockaddr *address, socklen t address len ); • pˇriˇrad´ı soketu zadan´emu deskriptorem socket adresu • obecn´ a struct sockaddr, nepouˇz´ıv´a se pro vlastn´ı zad´av´an´ı adres: – sa family t sa family . . . dom´ena – char sa data [] . . . adresa • pro AF INET se pouˇz´ıv´ a struct sockaddr in: – sa family t sin family . . . dom´ena (AF INET) – in port t sin port . . . ˇc´ıslo portu (16 bit˚ u) u) – struct in addr sin addr . . . IP adresa (32 bit˚ – unsigned char sin zero [8] . . . v´ yplˇ n (padding)
• vol´ an´ı bind pˇriˇrazuje soketu lok´ aln´ı adresu, tj. zdrojovou adresu odes´ılan´ ych dat a c´ılovou adresu pˇrij´ıman´ ych dat. Vzd´alen´a adresa, tj. adresa druh´eho konce komunikaˇcn´ıho kan´ alu, se nastavuje pomoc´ı connect. • struktura sockaddr je obecn´ y typ, pouˇz´ıvan´ y kernelem. Pro konkr´etn´ı nastaven´ı adres pak podle zvolen´e dom´eny a typu lze pouˇz´ıt ,,konkr´etnˇejˇs´ı” struktury k tomu definovan´e (viz dalˇs´ı slajd), kter´e je pak nutn´e pˇri pouˇzit´ı v bind pˇretypovat na struct sockaddr. V naprost´e vˇetˇsinˇe pˇr´ıpad˚ u je ale pouˇzit´ı tˇechto struktur zbyteˇcn´e a nedoporuˇcen´e - program pak bude fungovat pouze pro jednu address family. Pˇri pouˇzit´ı obecn´ ych funkc´ı pro pˇrevod jmen na adresy se lze pouˇzit´ı tˇechto struktur u ´plnˇe vyhnout - viz funkce getaddrinfo na stranˇe 205. • Budete potˇrebovat i dalˇs´ı hlaviˇckov´e soubory. Pˇr´ıklad je na stranˇe 194. • pro dom´eny AF INET a AF INET6 lze zadat ˇc´ıslo portu a speci´aln´ı hodnotu IP adresy, kter´ a znamen´ a libovolnou adresu na dan´em stroji. Na takov´ y soket (wildcard soket) pak bude moˇzn´e pˇr´ıj´ımat poˇzadavky na kteroukoli IP adresu nastavenou na kter´ekoli s´ıt’ov´e kartˇe. Je moˇzn´e zadat i jednu konkr´etn´ı adresu, k tomu se dostaneme. – pro AF INET je to hodnota INADDR ANY (4 nulov´e bajty odpov´ıdaj´ıc´ı adrese 0.0.0.0) – u AF INET6 je situace komplikovanˇejˇs´ı, bud’to lze pouˇz´ıt konstantn´ı promˇennou in6addr any nebo konstantu IN6ADDR ANY INIT, kterou ale lze
186
pouˇz´ıt pouze pro inicializaci promˇenn´e (typu struct in6 addr), nikoliv pro pˇriˇrazen´ı. Obˇe tyto hodnoty odpov´ıdaj´ı adrese :: (16 nulov´ ych bajt˚ u). • nelze spojit v´ıce soket˚ u s jednou dvojic´ı (adresa, port). • vol´ an´ı bind lze vynechat, j´ adro pak soketu (v pˇr´ıpadˇe TCP, UDP) pˇriˇrad´ı adresu a nˇekter´ y voln´ y port. Obvykle bind vol´a pouze server, protoˇze klienti oˇcek´ avaj´ı, ˇze bude poslouchat na pevn´em portu. Naopak klient pevn´ y port nepotˇrebuje, server se port klienta dozv´ı pˇri nav´az´an´ı spojen´ı nebo z prvn´ıho pˇrijat´eho datagramu. • adresa i port mus´ı b´ yt do struktury uloˇ zeny vˇ zdy v s´ıt’ov´ em poˇ rad´ı bajt˚ u. Poˇrad´ı bajt˚ u bylo vysvˇetleno na stranˇe 19, dalˇs´ı informace pak jsou na stranˇe 204.
Struktura pro IPv4 adresy • kaˇzd´ a adresn´ı rodina m´a svoji strukturu a sv˚ uj hlaviˇckov´ y soubor • pouˇzitou strukturu pak ve vol´an´ı socket pˇretypujete na struct sockaddr #include struct sockaddr_in in; /* IPv4 */ bzero(&in, sizeof (in)); in.sin_family = AF_INET; in.sin_port = htons(2222); in.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (struct sockaddr *)&in, sizeof (in)) == -1) ...
• Tento pˇr´ıklad slouˇz´ı ˇcistˇe pro demonstraci toho jak funguj´ı struktury uvnitˇr. Pokud to nen´ı nezbytnˇe nutn´e, nemˇel by k´od kter´ y nap´ıˇsete tyto konstrukce obsahovat - pˇri pouˇzit´ı obecn´ ych funkc´ı pro pˇrevod jm´ena na adresy vyplˇ nov´ an´ı struktur specifick´ ych pro danou address family odpad´a. U vˇetˇsiny s´ıt’ov´ ych program˚ u se dnes pˇredpokl´ad´a, ˇze budou fungovat jak na IPv4, tak na IPv6. • u IPv4 je poloˇzka sin addr sama strukturou, typu in addr. Tato struktura mus´ı m´ıt alespoˇ n jednu poloˇzku, s addr, jej´ıˇz typ mus´ı b´ yt ekvivaletn´ı 4bajtov´emu integeru – pro ˇctyˇri bajty IPv4 adresy. To ˇr´ık´a pˇr´ımo UNIX norma pro soubor netinet/in.h.
187
• sockaddr in6 je o nˇeco sloˇzitˇejˇs´ı. Je pˇr´ıstupn´ y z netinet/in.h (bud’to je v nˇem tato struktura pˇr´ımo definov´ana nebo soubor inkluduje netinet6/in6.h s jej´ı definic´ı). yt port i adresa v s´ıt’ov´em poˇrad´ı bajt˚ u, viz • u AF INET a AF INET6 mus´ı b´ strana 204. a jako 0, takˇze ˇcasto uvid´ıte jej´ı pouˇzit´ı bez htonl. • INADDR ANY je definovan´ Nen´ı to dobr´ y zvyk. Aˇz m´ısto tohoto makra vloˇz´ıte IP adresu definovanou integerem a zapomenete htonl pˇridat, je hned probl´em. Kdyˇz budete od zaˇc´ atku programovat jako sluˇsnˇe vychovan´ı lid´e, toto se v´am nem˚ uˇze st´at. A kdyˇz budete ps´ at s´ıt’ov´e programy tak aby uˇz od zaˇc´atku byly agnostick´e k address family, tak se tomuto probl´emu vyhnete u ´plnˇe. • v dom´enˇe AF UNIX se pouˇz´ıv´a adresov´a struktura struct sockaddr un, definovan´ a v <sys/un.h>: – sa family t sun family . . . dom´ena – char sun path [] . . . jm´eno soketu – d´elka jm´ena nen´ı ve standardu definov´ana, z´avis´ı na implementaci. Obvykl´e hodnoty jsou mezi 92 a 108.
ˇ an´ı na spojen´ı: listen() Cek´ int listen(int socket, int backlog ); • oznaˇc´ı soket zadan´ y desktriptorem socket jako akceptuj´ıc´ı spojen´ı a syst´em na soketu zaˇcne poslouchat. • maxim´ alnˇe backlog ˇz´ adost´ı o spojen´ı m˚ uˇze najednou ˇcekat ve frontˇe na obslouˇzen´ı (implementace m˚ uˇze hodnotu backlog ˇ adosti, kter´e se zmˇenit, pokud nen´ı v podporovan´em rozsahu). Z´ nevejdou do fronty, jsou odm´ıtnuty (tj. vol´an´ı connect skonˇc´ı s chybou). • soket ˇcek´ a na spojen´ı na adrese, kter´a mu byla dˇr´ıve pˇriˇrazena vol´ an´ım bind.
• Wildcard sokety se pouˇz´ıv´ aj´ı pro server nejˇcastˇeji. Konkr´etn´ı IP adresa serveru se zad´ av´ a tehdy, jestliˇze je potˇreba rozliˇsit, pˇres kter´e s´ıt’ov´e rozhran´ı pˇriˇsel poˇzadavek na spojen´ı (pro kaˇzd´e rozhran´ı m´ame jeden soket). Tuto moˇznost vyuˇz´ıvaly web servery, kter´e podle IP adresy rozliˇsovaly virtu´aln´ı
188
servery. Obvykle se na takov´em serveru jednomu fyzick´emu rozhran´ı pˇriˇradilo nˇekolik IP adres (IP aliasing). To je ale jiˇz d´avno minulost´ı – rozliˇsen´ı virtu´ aln´ıch server˚ u podle HTTP hlaviˇcky ,,Host:” uˇz nepotˇrebuje IP aliasy. Podobnˇe TLS protokol obsahuje rozˇs´ıˇren´ı ServerName. • To ˇze syst´em zaˇcne na portu poslouchat znamen´a, ˇze po pˇripojen´ı probˇehne TCP handshake a syst´em zaˇcne pˇrij´ımat data. Data se ukl´adaj´ı do bufferu s omezenou velikost´ı, a po dosaˇzen´ı jeho limitu je spojen´ı sice st´ale aktivn´ı, ale TCP window je nastaveno na 0 – syst´em dalˇs´ı data pˇrestal pˇrij´ımat. Velikost bufferu b´ yv´ a v ˇr´ adu des´ıtek kilobajt˚ u. Pˇr´ıklad: tcp/up-to-listen-only.c . • V pˇr´ıkladu je pouˇzito makro SOMAXCONN, vyˇzadovan´e normou v souboru sys/socket.h. Specifikuje maxim´aln´ı povolenou d´elku fronty pro listen(). Co se d´ıv´ am na r˚ uzn´e verze syst´em˚ u co m´am k dispozici, tak Linux, FreeBSD, Mac OS X i Solaris pouˇz´ıvaj´ı pro toto makro hodnotu 128.
Akceptov´ an´ı spojen´ı: accept() int accept(int socket, struct sockaddr *address, socklen t *address len ); • vytvoˇr´ı spojen´ı mezi lok´aln´ım soketem socket (kter´ y dˇr´ıve zavolal listen) a vzd´ alen´ ym soketem, ˇz´adaj´ıc´ım o spojen´ı pomoc´ı connect. Vr´ at´ı deskriptor (nov´ y soket), kter´ y lze pouˇz´ıvat pro komunikaci se vzd´alen´ ym procesem. P˚ uvodn´ı soket m˚ uˇze hned pˇrij´ımat dalˇs´ı spojen´ı pomoc´ı accept. • v address vr´ at´ı adresu vzd´alen´eho soketu. • address len je velikost struktury pro uloˇzen´ı adresy v bajtech, po n´ avratu obsahuje skuteˇcnou d´elku adresy.
• Vytvoˇren´ı druh´eho deskriptoru pro komunikaci umoˇzn ˇuje na tom p˚ uvodn´ım ihned znovu volat accept. • Novˇe vytvoˇren´ y soket m´ a stejn´e vlastnosti, jako m´a soket socket, tj. pokud je napˇr´ıklad neblokuj´ıc´ı, je i nov´ y soket neblokuj´ıc´ı. • Jestliˇze se v´ıce klient˚ u ze stejn´eho poˇc´ıtaˇce najednou pˇripoj´ı k jednomu serveru (tj. na jednu serverovou IP adresu a jeden port), jsou jednotliv´a spojen´ı rozliˇsena jen ˇc´ıslem portu na klientsk´e stranˇe. Nezapomeˇ nte, ˇze TCP spojen´ı je jednoznaˇcnˇe definov´ ano dvˇema sokety, tj. ((addr1, port1), (addr2, port2)).
189
• address m˚ uˇze b´ yt zad´ ana jako NULL, ˇc´ımˇz oznamujeme, ˇze n´as konkr´etn´ı adresa naˇseho nov´eho soketu nezaj´ım´a. V tomto pˇr´ıpadˇe by i address len mˇelo b´ yt 0. • Pokud je program naps´ an spr´avnˇe a je tedy nez´avisl´ y na address family, mˇel by v druh´em argumentu pˇred´avat adresu struktury struct sockaddr storage (do kter´e se vejde jak´ akoliv struktura specifick´a pro danou address family, tedy struct sockaddr in nebo struct sockaddr in6) a v tˇret´ım argumentu jej´ı d´elku. • Pˇr´ıklad: tcp/tcp-sink-server.c
Pr´ ace s IPv4 a IPv6 adresami • bin´ arn´ı reprezentace adresy se n´am ˇspatnˇe ˇcte • a reprezentaci adresy ˇretˇezcem nelze pouˇz´ıt pˇri pr´aci se sockaddr strukturami int inet pton(int af, const char *src, void *dst ); • pˇrevede ˇretˇezec na bin´ arn´ı adresu, tj. to co je moˇzn´e pouˇz´ıt v in addr nebo in6 addr poloˇzk´ach sockaddr struktur • vrac´ı 1 (OK), 0 (chybnˇe zadan´a adresa) nebo -1 (a nastav´ı errno) cont char *inet ntop(int af, const void *src, char *dst, size t size ); • opak k inet pton; vrac´ı dst nebo NULL (a nastav´ı errno) • pro obˇe vol´ an´ı plat´ı, ˇze af je AF INET nebo AF INET6
• Funkce jsou deklarovan´e v arpa/inet.h. • inet pton vrac´ı 1 pokud konverze probˇehla, 0 pokud dan´ y ˇretˇezec nen´ı adresa dan´e adresn´ı rodiny, a -1 pokud af nen´ı podporov´ano (EAFNOSUPPORT). ym errno pokud ne. inet ntop vrac´ı dst pokud je vˇse OK a NULL s nastaven´ • Adresy i porty v bin´ arn´ı podobˇe jsou v s´ıt’ov´em poˇrad´ı bajt˚ u, jak lze oˇcek´avat. • dst mus´ı b´ yt dostateˇcnˇe velk´e, protoˇze zde nen´ı parametr specifikuj´ıc´ı velikost. To ale nen´ı probl´em, podle nastaven´ı af pouˇzijete konkr´etn´ı adresn´ı strukturu nebo ˇretˇezec. Pro maxim´aln´ı postaˇcuj´ıc´ı velikost ˇretˇezc˚ u pro adresy norma definuje dvˇe makra, INET ADDRSTRLEN (16) a INET6 ADDRSTRLEN (48). Je to vˇcetnˇe m´ısta pro ukonˇcuj´ıc´ı \0. • size je velikost ˇretˇezce dst. Pokud nen´ı dostateˇcn´a, vol´an´ı selˇze s chybou ENOSPC. 190
• n je network, p je presentable • Pro IPv4 adresy se pouˇz´ıvala vol´an´ı inet aton a inet ntoa (a jako ascii). D´ıky nov´ ym vol´ an´ım jiˇz tyto funkce nen´ı potˇreba pouˇz´ıvat. Vˇsechna tato vol´ an´ı b´ yvaj´ı zdokumentov´ ana v manu´alov´e str´ance inet. • Uvˇedomte si, ˇze pomoc´ı tˇechto funkc´ı m˚ uˇzete pˇrev´est najednou jen jeden typ, to je bud’ IPv4, anebo IPv6 adresu. Kdyˇz nev´ıte, co ˇcekat, zkus´ıte jednu a pokud to nevyjde, zkus´ıte druhou. Pˇr´ıklad: resolving/addresses.c .
Nav´ az´ an´ı spojen´ı: connect() int connect(int sock, struct sockaddr *address, socklen t address len); • nav´ aˇze spojen´ı lok´ aln´ıho soketu sock se vzd´alen´ ym procesem, kter´ y pomoc´ı listen a accept ˇcek´a na spojen´ı na adrese address (o d´elce address len). • jestliˇze pro soket sock nebyla definov´ana adresa vol´an´ım bind, je mu pˇriˇrazena nˇejak´ a nepouˇzit´a adresa dle zvolen´e rodiny protokol˚ u. • pokud se spojen´ı nepovede, je soket v nedefinovan´em stavu. Pˇred nov´ ym pokusem o spojen´ı by aplikace mˇela zavˇr´ıt deskriptor sock a vytvoˇrit nov´ y soket.
• Po u ´spˇeˇsn´em nav´ az´ an´ı spojen´ı mohou server i klient pro komunikaci pouˇz´ıvat bˇeˇzn´ a souborov´ a vol´ an´ı write a read, nebo funkce send, recv, sendmsg, recvmsg. Chov´ an´ı funkc´ı pro z´apis a ˇcten´ı dat je podobn´e jako write a read pro roury. • Pˇr´ıklad: tcp/connect.c • I pro nespojovan´e sluˇzby (UDP) se d´a volat connect, t´ım se nenav´aˇze spojen´ı, ale pouze se omez´ı adresa protistrany, se kterou je moˇzn´e pˇres soket komunikovat. Pro pos´ıl´ an´ı datagram˚ u se pak pouˇz´ıvaj´ı funkce send a recv, kter´e uˇz nemaj´ı parametr pro adresu protistrany. Pro nespojovan´e sluˇzby m˚ uˇzeme tak´e volat connect() v´ıcekr´at, kaˇzd´e vol´an´ı novˇe nastav´ı adresu komunikuj´ıc´ı strany. Pokud pouˇzijeme m´ısto adresy NULL, nastaven´ı protistrany je zresetov´ ano. • Pokud je socket nastaven jako neblokuj´ıc´ı, viz strana 111, connect se nezablokuje ˇcek´ an´ım na spojen´ı. M´ısto toho vr´at´ı -1 s errno nastaven´e na 191
EINPROGRESS (= “nebylo moˇzn´e vytvoˇrit spojen´ı okamˇzitˇe”), a ˇz´adost o spojen´ı se uloˇz´ı do syst´emov´e fronty pro n´asledn´e vyˇr´ızen´ı. Do t´e doby, neˇz je spojen´ı pˇripraveno, vol´ an´ı connect vrac´ı -1, nyn´ı ale s chybou EALREADY. Nen´ı ale takto vhodn´e testovat pˇripravenost spojen´ı, protoˇze pokud connect skonˇc´ı s chybou, dalˇs´ı vol´ an´ı connect provede nov´ y pokus o spojen´ı a jsme opˇet tam, kde jsme byli. . . Je moˇzn´e ale pouˇz´ıt select nebo poll pro ˇcek´an´ı na z´ apis (ne ˇcten´ı) do socketu, viz strany 210, 213 kter´e obsahuj´ı kompletn´ı pˇr´ıklad na neblokuj´ıc´ı connect, viz strana 211.
Pˇ rijet´ı zpr´ avy: recvfrom() ssize t recvfrom(int sock, void *buf, size t len, int flg, struct sockaddr *address, socklen t *address len ); • pˇrijme zpr´ avu ze soketu sock, uloˇz´ı ji do bufferu buf o velikosti len, do address d´ a adresu odes´ılatele zpr´avy, do address len d´elku adresy. Vr´ at´ı d´elku zpr´avy. Kdyˇz je zpr´ava delˇs´ı neˇz len, nadbyteˇcn´ a data se zahod´ı (SOCK STREAM nedˇel´ı data na zpr´ avy, data se nezahazuj´ı). • ve flg mohou b´ yt pˇr´ıznaky: ava se bere jako nepˇreˇcten´a, dalˇs´ı – MSG PEEK . . . zpr´ recvfrom ji vr´ at´ı znovu – MSG OOB . . . pˇreˇcte urgentn´ı (out-of-band) data y objem dat, – MSG WAITALL . . . ˇcek´a, dokud nen´ı naˇcten pln´ tj. len bajt˚ u
• pouˇz´ıv´ a se hlavnˇe pro sokety typu SOCK DGRAM. V takov´e situaci opˇet ˇcek´a na celou zpr´ avu, tj. nevr´ at´ı v´am p˚ ulku datagramu. Opˇet je moˇzn´e nastavit socket jako neblokuj´ıc´ı. • address len mus´ı b´ yt inicializovan´a velikost´ı bufferu, pokud nen´ı adresa NULL, ˇc´ımˇz ˇr´ık´ ate, ˇze v´ as adresa protistrany nezaj´ım´a – tomu tak ale u datagram˚ u vetˇsinou nen´ı. • m´ısto recvfrom se d´ a pouˇz´ıt obecnˇejˇs´ı funkce recvmsg. • pro sokety, na kter´e bylo vol´ano connect, se m´ısto funkce recvfrom vol´a recv. • po u ´speˇsn´em n´ avratu z recvfrom je moˇzn´e address a address len beze zmˇeny pouˇz´ıt pro n´ asledn´e vol´an´ı sendto. • stejnˇe jako sendto, je i recvfrom moˇzn´e pouˇz´ıt pro spojovanou sluˇzbu. Z´ıskat adresu protistrany je ale asi jednoduˇsˇs´ı pˇres vol´an´ı getpeername, viz strana 203. 192
• pˇr´ıklad: udp/udp-server.c
Posl´ an´ı zpr´ avy: sendto() ssize t sendto(int socket, void *msg, size t len, int flags, struct sockaddr *addr, socklen t addr len ); • prostˇrednictv´ım soketu socket poˇsle zpr´avu msg o d´elce len na adresu addr (o d´elce addr len). • parametr flags m˚ uˇze obsahovat pˇr´ıznaky: – MSG EOB . . . ukonˇcen´ı z´aznamu (pokud je podporov´ano protokolem) an´ı urgentn´ıch (out-of-band) dat, jejichˇz – MSG OOB . . . posl´ v´ yznam je z´ avisl´ y na protokolu
• Pouˇz´ıv´ a se hlavnˇe pro sokety typu SOCK DGRAM, protoˇze v t´eto situaci m´ame pouze socket reprezentuj´ıc´ı naˇsi stranu spojen´ı; viz pozn´amka u slajdu k accept. Mus´ıme proto specifikovat adresu protistrany, coˇz vol´an´ım write nedok´ aˇzeme. Pro datagramovou sluˇzbu se nav´ıc data berou jako nedˇeliteln´a, tj. bud’ se akceptuj´ı cel´ a, nebo se vol´an´ı zablokuje – neexistuje ˇc´asteˇcn´ y z´apis. Stejnˇe jako je tomu u souborov´ ych deskriptor˚ u, je i zde moˇzn´e socket nastavit jako neblokuj´ıc´ı, viz strana 111. • M´ısto sendto se d´ a pouˇz´ıt obecnˇejˇs´ı funkce sendmsg. • Pro sokety, na kter´e bylo vol´ ano connect, se m´ısto sendto m˚ uˇze pouˇz´ıt send. ´ eˇsn´ • Uspˇ y n´ avrat z libovoln´e funkce zapisuj´ıc´ı data do soketu neznamen´ a u ´ spˇ eˇ sn´ e doruˇ cen´ı zpr´ avy protistranˇ e, ale pouze uloˇzen´ı dat do lok´aln´ıho bufferu odes´ılan´ ych dat. • Pokud pouˇzijete sendto na streamovanou sluˇzbu, je to moˇzn´e, ale adresa se ignoruje. Jedin´ y d˚ uvod proˇc nepouˇz´ıt pˇr´ımo write by tak byla moˇznost pouˇz´ıt flagy. V tom pˇr´ıpadˇe je ale jednoduˇsˇs´ı pouˇz´ıt send. • Pˇr´ıklad: udp/udp-client.c .
193
Uzavˇ ren´ı soketu: close() int close(int fildes); • zruˇs´ı deskriptor, pˇri zruˇsen´ı posledn´ıho deskriptoru soketu zavˇre soket. • pro SOCK STREAM soket z´aleˇz´ı na nastaven´ı pˇr´ıznaku SO LINGER (default je l onoff == 0, mˇen´ı se funkc´ı setsockopt). – l onoff == 0 . . . vol´an´ı close se vr´at´ı, ale j´adro se snaˇz´ı d´ al pˇren´est zbyl´ a data – l onoff == 1 && l linger != 0 . . . j´adro se snaˇz´ı pˇren´est zbyl´ a data do vyprˇsen´ı timeoutu l linger v sekund´ach, kdyˇz se to nepovede, close vr´at´ı chybu, jinak vr´at´ı OK po pˇrenesen´ı dat. – l onoff == 1 && l linger == 0 . . . provede se reset spojen´ı
• po uzavˇren´ı m˚ uˇze TCP soket z˚ ustat po nˇejakou dobu v pˇrechodn´em stavu, kter´ y je definov´ an protokolem TCP pˇri ukonˇcov´an´ı spojen´ı. Neˇz je soket zcela zruˇsen, nelze pouˇz´ıt jin´ y soket na stejn´em portu, pokud toto nebylo pˇredt´ım povoleno nastaven´ım pˇr´ıznaku SO REUSEADDR pomoc´ı funkce setsockopt, viz strana 203. • reset spojen´ı je abnorm´ aln´ı ukonˇcen´ı spojen´ı. V TCP se pouˇzije paket s nastaven´ ym pˇr´ıznakem RST. Na druh´e stranˇe se norm´aln´ı ukonˇcen´ı spojen´ı projev´ı jako konec souboru (pˇri ˇcten´ı), reset zp˚ usob´ı chybu ECONNRESET.
194
Uzavˇ ren´ı soketu: shutdown() int shutdown(int socket, int how ); • Uzavˇre soket (ale neruˇs´ı deskriptor) podle hodnoty how: – SHUT RD . . . pro ˇcten´ı – SHUT WR . . . pro z´ apis – SHUT RDWR . . . pro ˇcten´ı i z´apis
• Po uzavˇren´ı soketu pomoc´ı shutdown je nutn´e zavˇr´ıt i deskriptor pomoc´ı close. • Pˇri norm´ aln´ım ukonˇcen´ı spojen´ı na u ´rovni protokolu TCP kaˇzd´a strana signalizuje, ˇze uˇz nebude nic zapisovat. To plat´ı i v pˇr´ıpadˇe pouˇzit´ı close nebo shutdown(fd, SHUT RDWR). Pˇri pouˇzit´ı shutdown(fd, SHUT WR) lze ze soketu d´ al ˇc´ıst. Druh´ a strana dostane EOF pˇri ˇcten´ı, ale m˚ uˇze d´al zapisovat.
195
Dalˇ s´ı funkce pro sokety int setsockopt(int socket, int level, int opt name, const void *opt value, socklen t option len ); • nastaven´ı parametr˚ u soketu int getsockopt(int socket, int level, int opt name, void *opt value, socklen t *option len ); • pˇreˇcten´ı parametr˚ u soketu int getsockname(int socket, struct sockaddr *address, socklen t *address len ); • zjiˇstˇen´ı (lok´ aln´ı) adresy soketu int getpeername(int socket, struct sockaddr *address, socklen t *address len ); • zjiˇstˇen´ı adresy vzd´ alen´eho soketu (druh´eho konce spojen´ı)
• hodnota level v getsockopt a setsockopt je obvykle SOL_SOCKET. U getsockopt, option len mus´ı b´ yt inicializov´ana na velikost opt value. • funkce getsockname se pouˇz´ıv´a, kdyˇz nevol´ame bind a potˇrebujeme zjistit, jak´ a (lok´ aln´ı!) adresa byla j´ adrem soketu pˇridˇelena. • vol´ an´ı getsockopt(sock, SOL_SOCKET, SO_ERROR, &val, &len) vr´at´ı (a vymaˇze) pˇr´ıznak chyby na soketu. Asi nejuˇziteˇcnˇejˇs´ı je pˇri zjiˇst’ov´an´ı, jak dopadl neblokuj´ıc´ı connect, viz strana 198. • pˇri pouˇzit´ı SO_REUSEADDR se d´a po uzavˇren´ı poslouchaj´ıc´ıho serverov´eho soketu znovu spustit server – volat socket, bind, listen a accept na stejn´e adrese a portu – i kdyˇz jeˇstˇe dob´ıhaj´ı spojen´ı vytvoˇren´a pˇredchoz´ı instanc´ı serveru: int opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); • Pouˇzit´ı setsockopt s SO_REUSEADDR je vidˇet napˇr. v pˇr´ıkladu tcp/reuseaddr.c . Pozor na to, ˇze mus´ıte udˇelat alespoˇ n jedno spojen´ı, jinak syst´em nem´a na co ˇcekat a opakovan´ y bind za sebou se podaˇr´ı i tak.
196
Poˇ rad´ı bajt˚ u • s´ıt’ov´e sluˇzby pouˇz´ıvaj´ı poˇrad´ı bajt˚ u, kter´e se m˚ uˇze liˇsit od poˇrad´ı pouˇz´ıvan´eho na lok´aln´ım syst´emu. Pro pˇrevod lze pouˇz´ıt funkce (makra): – uint32 t htonl(uint32 t hostlong ); host → s´ıt’, 32 bit˚ u – uint16 t htons(uint16 t hostshort ); host → s´ıt’, 16 bit˚ u – uint32 t ntohl(uint32 t netlong ); s´ıt’ → host, 32 bit˚ u – uint16 t ntohs(uint16 t netshort ); s´ıt’ → host, 16 bit˚ u • s´ıt’ov´e poˇrad´ı bajt˚ u je big-endian, tj. nejprve vyˇsˇs´ı bajt. Pouˇz´ıv´a se hlavnˇe ve funkc´ıch pracuj´ıc´ıch s adresami a ˇc´ısly port˚ u.
• pokud lok´ aln´ı syst´em pouˇz´ıv´a stejn´e poˇrad´ı bajt˚ u jako s´ıt’, nedˇelaj´ı pˇrevodn´ı funkce nic. • jednoduch´ y a pˇritom vˇetˇsinou postaˇcuj´ıc´ı test na to, zda spr´avnˇe pˇrev´ad´ıte poˇrad´ı bajt˚ u, je spustit vaˇs program proti sobˇe (pokud to tak lze) na architektur´ ach s rozd´ıln´ ym uspoˇra´d´an´ım bajt˚ u.
197
ˇ ısla protokol˚ C´ u a port˚ u struct protoent *getprotobyname(const char *name ); • v poloˇzce p proto vr´ at´ı ˇc´ıslo protokolu se jm´enem name (napˇr. pro "tcp" vr´ at´ı 6). • ˇc´ısla protokol˚ u jsou uloˇzena v souboru /etc/protocols. struct servent *getservbyname(const char *name, const char *proto ); • pro zadan´e jm´eno sluˇzby name a jm´eno protokolu proto vr´at´ı v poloˇzce s port ˇc´ıslo portu. • ˇc´ısla port˚ u jsou uloˇzena v souboru /etc/services. funkce vrac´ı NULL, kdyˇz v datab´azi nen´ı odpov´ıdaj´ıc´ı poloˇzka.
• v´ ysledek getprotobyname se hod´ı pro vol´an´ı socket, v´ ysledek getservbyname pro vol´ an´ı bind. • kromˇe getservbyname existuje jeˇstˇe getservbyport umoˇzn ˇuj´ıc´ı hledat sluˇzbu pomoc´ı ˇc´ısla portu (pozor, v network byte order !) a funkce getservent a spol. pro ruˇcn´ı proch´ azen´ı z´ aznam˚ u. • vˇsechny tyto funkce prohled´avaj´ı pouze ”ofici´aln´ı”seznamy sluˇzeb a protokol˚ u, kter´e se nach´ azej´ı vˇetˇsinou v souborech zm´ınˇen´ ych na slajdu. • v uveden´ ych souborech je definov´ano mapov´an´ı mezi jm´eny a ˇc´ısly pro standardn´ı protokoly a sluˇzby. • pozor na to, ˇze protokolem zde nemysl´ıme HTTP, SSH, telnet nebo FTP – to jsou zde sluˇzby, reprezentovan´e ˇc´ısly port˚ u. Protokol je TCP, UDP, OSPF, GRE apod., tedy to, co je pˇren´aˇseno v IP paketu v poloˇzce Protocol, viz strany 11 a 14 v RFC 791. • pˇr´ıklad: resolving/getbyname.c
198
Pˇ revod hostname na adresy: getaddrinfo() • ze zadan´ ych parametr˚ u pˇr´ımo generuje sockaddr struktury • pracuje s adresami i s porty • jej´ı chov´ an´ı je ovlivniteln´e pomoc´ı tzv. hint˚ u int getaddrinfo(const char *nodename, const char *servicename, const struct addrinfo *hint, struct addrinfo **res ); • poloˇzky struktury addrinfo: ai flags (pro hinty), ai family (address family), ai socktype, ai protocol, ai addrlen, ysledn´e adresy), char struct sockaddr *ai addr (v´ *ai canonname, struct addrinfo *ai next (dalˇs´ı prvek v seznamu)
• Pˇri vyhodnocov´ an´ı dotaz˚ u na adresy a jm´ena se pouˇz´ıvaj´ı jmenn´e sluˇzby podle konfigurace v souboru /etc/nsswitch.conf. • struktura addrinfo je definov´ana v netdb.h • getaddrinfo um´ı do struktur sockaddr pˇrev´adˇet jak ˇretˇezec kter´ y obsahuje hostname, tak ˇretˇezec obsahuj´ıc´ı IP adresu. Stejnˇe je tomu i pro porty. • funkce getaddrinfo vrac´ı parametrem res seznam struktur sockaddr, ve kter´ ych jsou uloˇzeny adresy koresponduj´ıc´ı k dan´emu vstupu. • je rozd´ıl zda budou adresn´ı struktury d´ale pouˇzity pro server nebo klient; pro server napˇr. staˇc´ı jako nodename d´at NULL (wildcard socket). Podobnˇe pro pro poloˇzku ai flags struktury addrinfo pouˇzit´e pro parametr hint a jej´ı hodnotu AI PASSIVE. • Po skonˇcen´ı pr´ ace s v´ ysledky nezapomeˇ nte zavolat funkci freeaddrinfo, kter´ a uvoln´ı naalokovanou pamˇet. • pˇr´ıklad: resolving/getaddrinfo.c • Dˇr´ıve se hodnˇe pouˇz´ıvaly funkce gethostbyname a gethostbyaddr. Tyto funkce pracuj´ı pouze s IPv4 adresami a jm´eny, jsou tak povaˇ zov´ any za zastaral´ e a jejich pouˇzit´ı se nedoporuˇcuje. M´ısto nich se doporuˇcuje pouˇz´ıvat obecnˇejˇs´ı vol´ an´ı getipnodebyname a getipnodebyaddr (getipnode* funkce se nach´ az´ı v r˚ uzn´ ych syst´emech, nicm´enˇe byly ned´avno vyjmuty z GNU libc, takˇze se na jejich pˇr´ıtomnost nelze univerz´alnˇe spolehnout. Nav´ıc nejsou souˇc´ ast´ı UNIX standardu.) resp. getaddrinfo, getnameinfo. Z tˇechto funkc´ı pouze getaddrinfo a getnameinfo splˇ nuj´ı standard (POSIX.1-2001). 199
• Pochopitelnˇe, je nutn´e dobˇre uv´aˇzit, zda existuj´ıc´ı k´od pouˇz´ıvaj´ıc´ı legacy/obsolete funkce je potˇreba pˇrepisovat. Pokud takov´emu programu bude IPv4 staˇcit i nad´ ale, mˇenit existuj´ıc´ı a funkˇcn´ı k´od nemus´ı b´ yt vˇzdy rozumn´e. Nov´ y k´ od ale vˇ zdy piˇ ste pomoc´ı nov´ ych funkc´ı, kter´e podporuj´ı IPv4 i IPv6, i pokud byste si mysleli, ˇze v´ aˇs program nebude IPv6 nikdy pouˇz´ıvat. Za pouˇzit´ı gethostbyname, gethostbyaddr nebo struktur specifick´ ych pro IPv4 resp. IPv6 (bez v´ aˇzn´eho d˚ uvodu) p˚ ujde u zkouˇsky hodnocen´ı dol˚ u.
Pˇ revod adresy na hostname: getnameinfo() • protˇejˇsek k funkci getaddrinfo • jako vstup pracuje s cel´ ymi sockaddr strukturami, tedy funguje s IPv4 i IPv6 adresami, narozd´ıl od funkce gethostbyaddr. int getnameinfo(const struct sockaddr *sa, socklen t *sa len, char *nodename, socketlen t *nodelen, char *servicename, socketlen t *servicelen, unsigned flags );
• getnameinfo prov´ ad´ı v jednom vol´an´ı funkce konverzi adresy a ˇc´ısla portu uloˇzen´ ych ve struktuˇre sockaddr na stringy v z´avislosti na namingov´ ych sluˇzb´ ach (tedy standardn´ı cesta pˇres naming backendy v /etc/nsswitch.conf) a hodnotˇe flags. Tedy to, co by bylo postaru nutn´e prov´adˇet pomoc´ı vol´an´ı gethostbyaddr a getservbyport. • Tato funkce je narozd´ıl od v´ yˇse zm´ınˇen´ ych legacy funkc´ı reentrantn´ı a hod´ı se tedy pro pouˇzit´ı v prostˇred´ı s thready (alespoˇ n na vˇetˇsinˇe syst´em˚ u). • manu´ alov´ a str´ anka obsahuje seznam pouˇziteln´ ych NI flag˚ u pro flags a jejich v´ yznam • pˇr´ıklad: resolving/getnameinfo.c
200
Pˇ r´ıklad: TCP server struct sockaddr storage ca; int nclients = 10, fd, nfd; struct addrinfo *r, *rorig, hi; memset(&hi, 0, sizeof (hi)); hi.ai family = AF UNSPEC; hi.ai socktype = SOCK STREAM; hi.ai flags = AI PASSIVE; getaddrinfo(NULL, portstr, &hi, &rorig); for (r = rorig; r != NULL; r = r->ai next) { fd = socket(r->ai family, r->ai socktype, r->ai protocol); if (!bind(fd, r->ai addr, r->ai addrlen)) break; } freeaddrinfo(rorig); listen(fd, nclients); for (;;) { sz = sizeof(ca); nfd = accept(fd, (struct sockaddr *)&ca, &sz); /* Komunikace s klientem */ close(newsock); }
• Toto je obecn´ a kostra serveru. Argument portstr je jedin´ y vstupn´ı parametr. Prvn´ı u ´spˇeˇsn´ y bind ukonˇc´ı proch´azen´ı seznamu adres. • Takto napsan´ y server bude na syst´emu, kter´ y podporuje IPv6 i IPv4 pˇrij´ımat spojen´ı v obou adresn´ıch rodin´ach na tomt´eˇz socketu. Spojen´ı z IPv4 klient˚ u budou m´ıt jako zdrojovou adresu tzv. IPv4-mapped IPv6 adresu, kter´a v sobˇe obsahuje IPv4 adresu (napˇr. ::FFFF:78.128.192.1). • Vˇsimnˇete si, ˇze nen´ı nutn´e t´emˇeˇr nic pˇretypov´avat na pointer na strukturu sockaddr. Jedinou v´ yjimkou je vol´an´ı accept. Pro vol´an´ı accept se pouˇz´ıv´ a struktura typu sockaddr storage coˇz je obecn´ y ”kontejner”schopn´ y pojmout prvky struktury sockaddr in i sockaddr in6. • socket a bind se volaj´ı pro vˇsechny vr´acen´e sockaddr struktury dokud pro jednu z nich bind neuspˇeje nebo se seznam nevyˇcerp´a. • Probl´em: nekontroluj´ı se nˇekter´e n´avratov´e hodnoty a neobsahuje vol´an´ı uvolˇ nuj´ıc´ı alokovan´e entity, napˇr. uzavˇren´ı socketu v pˇr´ıpadˇe chyby vol´an´ı bind. Stejnˇe tak se neˇreˇs´ı situace kdy se vyˇcerp´a seznam bez toho aby bind uspˇel (to lze detekovat pomoc´ı kontroly nenulovosti pointeru r za koncem prvn´ıho cyklu).
201
Pˇ r´ıklad: TCP klient int fd; struct addrinfo *r, *rorig, hi; memset(&hi, 0, sizeof (hi)); hi.ai family = AF UNSPEC; hi.ai socktype = SOCK STREAM; getaddrinfo(hoststr, portstr, &hi, &r); for (rorig = r; r != NULL; r = r->ai next) { fd = socket(r->ai family, r->ai socktype, r->ai protocol); if (connect(fd, (struct sockaddr *)r->ai addr, r->ai addrlen) == 0) break; } freeaddrinfo(resorig); /* Komunikace se serverem */ close(fd);
• Toto je obecn´ a kostra TCP klienta. Argumenty hoststr a portstr jsou jedin´e vstupn´ı parametry. Prvn´ı u ´spˇeˇsn´ y connect ukonˇc´ı proch´azen´ı adres pro dan´ y host. • V pˇr´ıkladu vyuˇz´ıv´ ame automatick´eho pˇridˇelen´ı voln´eho portu syst´emem pˇri vol´ an´ı connect (nepˇredch´ azelo mu vol´an´ı bind). • postupnˇe se vol´ a connect na vˇsechny vr´acen´e sockaddr struktury (v addrinfo struktur´ ach) pro dan´ y host do t´e doby, neˇz se podaˇr´ı spojit. • Probl´em: neobsahuje kontrolu n´avratov´ ych hodnot vol´an´ı. Stejnˇe tak se neˇreˇs´ı situace kdy se vyˇcerp´ a seznam bez toho aby connect uspˇel (to lze detekovat pomoc´ı kontroly nenulovosti pointeru r za koncem prvn´ıho cyklu). Nebo se neuzav´ır´ a socket pˇri ne´ uspˇeˇsn´em vol´an´ı connect, tj. je to file descriptor leak.
202
ˇ an´ı na data: select() Cek´ int select(int nfds, fd set *readfds, fd set *writefds, fd set *errorfds, struct timeval *timeout ); • zjist´ı, kter´e ze zadan´ ych deskriptor˚ u jsou pˇripraveny pro ˇcten´ı, z´ apis, nebo na kter´ ych doˇslo k v´ yjimeˇcn´emu stavu. Pokud ˇz´ adn´ y takov´ y deskriptor nen´ı, ˇcek´a do vyprˇsen´ı ˇcasu timeout (NULL . . . ˇcek´ a se libovolnˇe dlouho). Parametr nfds ud´av´a rozsah testovan´ ych deskriptor˚ u (0, ..., nfds-1). • pro nastaven´ı a test masek deskriptor˚ u slouˇz´ı funkce: – void FD ZERO(fd set *fdset ) . . . inicializace – void FD SET(int fd, fd set *fdset ) . . . nastaven´ı – void FD CLR(int fd, fd set *fdset ) . . . zruˇsen´ı – int FD ISSET(int fd, fd set *fdset ) . . . test
• Motivace: jestliˇze chceme ˇc´ıst data z v´ıce deskriptor˚ u, je moˇzn´e, pokud jde o soubor, rovnou jej otevˇr´ıt s pˇr´ıznakem O NONBLOCK, anebo tento pˇr´ıznak kdykoli nastavit na deskriptoru pomoc´ı vol´an´ı fncnl s druh´ ym parametrem O SETFL (ne O SETFD, viz strana 111). Neblokuj´ıc´ım read pak stˇr´ıdavˇe testujeme jednotliv´e deskriptory, a mezi kaˇzd´ ym kolem test˚ u tˇreba pouˇz´ıt sleep(1). Nev´ yhody jsou aktivn´ı ˇcek´an´ı, reˇzie pˇrep´ın´an´ı mezi m´odem uˇzivatelsk´ ym a j´ adra, moˇzn´ a prodleva (aˇz do d´elky vaˇseho ˇcek´an´ı mezi jednotliv´ ymi koly), a tak´e to, ˇze nesloˇz´ıte zkouˇsku (viz strana 173). Spr´avn´e ˇreˇsen´ı t´eto situace je pouˇz´ıt napˇr´ıklad select a n´aslednˇe zavolat read na ty deskriptory, kter´e vol´ an´ı select ohl´ as´ı jako pˇripraven´e. • Pˇr´ıklad na busy waiting: select/busy-wait.c . Vˇsimnˇete si, ˇze novˇe vytvoˇren´ y soket v pˇr´ıkladu je bez flagu O NONBLOCK, viz strana 196, takˇze je nutn´e flag nastavit. • Pˇripraven´y (ready) znamen´ a, ˇze read nebo write s vynulovan´ ym pˇr´ıznakem O NONBLOCK by se nezablokovalo, tedy ne nutnˇe ˇze nˇejak´a data jsou pˇripravena (read napˇr. m˚ uˇze vr´ atit 0 pro end-of-file nebo -1 pro chybu) • Mnoˇzina errorfds je pro v´ yj´ımky v z´avislosti na typu deskriptoru; pro socket to je napˇr´ıklad pˇr´ıchod urgentn´ıch dat (flag URG v TCP hlaviˇcce). Neznamen´a to, ˇze na dan´em deskriptoru doˇslo k chybˇe ! Chyba s nastaven´ ym errno se zjist´ı z ostatn´ıch mnoˇzin po n´avratov´em k´odu -1 proveden´eho vol´an´ı, tj. napˇr´ıklad read. • Pˇri vol´ an´ı jsou v mnoˇzin´ ach deskriptory, kter´e chceme testovat, po n´avratu z˚ ustanou nastaven´e jen ty deskriptory, na kter´ ych nastala testovan´a ud´alost. 203
Je nutn´ e je tedy pˇ red dalˇ s´ım vol´ an´ım select znovu nastavit. Typicky to jsou bitov´e masky, ale nemus´ı tomu b´ yt tak; z pozice program´atora je to samozˇrejmˇe jedno. Proch´ azen´ı pˇres vr´acen´e mnoˇziny je nutn´e dˇelat po jednom deskriptoru, pˇres FD ISSET. • Funkce select je pouˇziteln´ a i pro ˇcek´an´ı na moˇznost z´apisu do roury nebo soketu – ˇcek´ a se, aˇz druh´ a strana nˇeco pˇreˇcte a uvoln´ı se m´ısto v bufferu pro dalˇs´ı data. • M´ısto mnoˇziny pro deskriptory je moˇzn´e uv´est NULL, speci´aln´ı pˇr´ıpad pˇri nastaven´ı vˇsech mnoˇzin na NULL je vol´an´ı, kter´e se pouze zablokuje do pˇr´ıchodu sign´ alu nebo do vyprˇsen´ı time-outu. • Po n´ avratu je nutn´e otestovat kaˇzd´ y deskriptor zvl´aˇst’, nen´ı k dispozici vol´an´ı, kter´e by v´ am vytvoˇrilo mnoˇzinu pˇripraven´ ych deskriptor˚ u. • Pokud obsluhuje s´ıt’ov´ y server v´ıce port˚ u, m˚ uˇze volat select na pˇr´ısluˇsn´e deskriptory soket˚ u a n´ aslednˇe accept na deskriptory, pro kter´e select ohl´asil pˇr´ıchod ˇz´ adosti klienta (pˇripravenost ke ˇcten´ı). • Vol´ an´ı connect na neblokuj´ıc´ım soketu se hned vr´at´ı, nav´az´an´ı spojen´ı ohl´as´ı n´ asledn´ y select jako pˇripravenost k z´apisu. V´ıce viz strana 198. • Dalˇs´ı moˇznost pouˇzit´ı select je s´ıt’ov´ y server, kter´ y v jednom procesu obsluhuje paralelnˇe nˇekolik klient˚ u. Pomoc´ı select se testuje stav deskriptor˚ u odpov´ıdaj´ıc´ıch spojen´ı s jednotliv´ ymi klienty a pˇres deskriptory pˇripraven´e pro ˇcten´ı/z´ apis se komunikuje. Aby se mohli pˇripojovat nov´ı klienti, testuje se i deskriptor soketu, kter´ y se pouˇz´ıv´a pro accept. Vyuˇz´ıv´a se toho, ˇze select ohl´ as´ı deskriptor s ˇcekaj´ıc´ı ˇz´ adost´ı klienta o spojen´ı jako pˇripraven´ y pro ˇcten´ı. Na takov´ y deskriptor je moˇzn´e volat accept. • Pozor na to, ˇze select m˚ uˇ ze zmˇenit strukturu timeval, existuje nov´e vol´an´ı pselect, kter´e kromˇe dalˇs´ıch zmˇen strukturu pro timeout nezmˇen´ı. • Pro nfds je moˇzn´e pouˇz´ıt FD SETSIZE, coˇz je syst´emov´a konstanta pro maxim´ aln´ı poˇcet deskriptor˚ u. Nen´ı to ale nejlepˇs´ı ˇreˇsen´ı, protoˇze tato konstanta je sice vˇetˇsinou jen 1024 na 32-bitov´ ych syst´emech, na Solarisu to vˇsak pro 64-bitov´e architektury je uˇz 65536. Pˇredpokl´ad´am podobn´e chov´an´ı i pro ostatn´ı syst´emy. • Pokud se ˇcas nastav´ı na 0, tedy ted’ nemluv´ıme o nastaven´ı ukazatele na NULL, select se d´ a pouˇz´ıt pro tzv. polling – zjist´ı souˇcasn´ y stav a hned se vr´ at´ı. • Pˇr´ıklad: select/select.c • select lze pouˇz´ıt na zjiˇst’ov´ an´ı stavu po vol´an´ı connect na neblokuj´ıc´ı socket. Zda spojen´ı probˇehlo u ´spˇeˇsnˇe se dozv´ıte z funkce getsockopt s opt name nastaven´ ym na SO ERROR, viz strana 203. Pˇr´ıklad: tcp/non-blocking-connect.c .
204
Pˇ r´ıklad: pouˇ zit´ı select() • Deskriptor fd odkazuje na soket, pˇrepisuje s´ıt’ovou komunikaci na termin´ al a naopak. int sz; fd set rfdset, efdset; char buf[BUFSZ]; for(;;) { FD ZERO(&rfdset); FD SET(0, &rfdset); FD SET(fd, &rfdset); efdset = rfdset; select(fd+1, &rfdset, NULL, &efdset, NULL); if(FD ISSET(0, &efdset)) /* V´ yjimka na stdin */ ; if(FD ISSET(fd, &efdset)) /* V´ yjimka na fd */ ; if(FD ISSET(0, &rfdset)) sz = read(0, buf, BUFSZ); write(fd, buf, sz); if(FD ISSET(fd, &rfdset)) sz = read(fd, buf, BUFSZ); write(1,buf,sz); }
• Zde je typick´e pouˇzit´ı select, kdy je tˇreba ˇc´ıst data souˇcasnˇe ze dvou zdroj˚ u. Tento pˇr´ıklad pˇredpokl´ ad´ a, ˇze deskriptory 0 a fd jsou nastaveny jako neblokuj´ıc´ı. • Pˇred kaˇzd´ ym vol´ an´ım select se mus´ı znovu nastavit mnoˇziny deskriptor˚ u. • Lepˇs´ı ˇreˇsen´ı je pouˇz´ıt select i na z´apis. Logika ˇr´ızen´ı je pak takov´a, ˇze pro kaˇzd´ y smˇer datov´e komunikace m´ame samostatn´ y buffer. Pˇr´ısluˇsn´ y ˇctec´ı deskriptor bude v mnoˇzinˇe pro ˇcten´ı v select, pr´avˇe kdyˇz je buffer pr´azdn´ y. Naopak z´ apisov´ y deskriptor bude v mnoˇzinˇe pro z´apis, pr´avˇe kdyˇz je buffer nepr´ azdn´ y. • select usp´ı proces i pˇri kontrole pˇripravenosti k z´apisu, pokud data nejsou z druh´e strany ˇctena. To jde jednoduˇse nasimulovat pomoc´ı programu, kter´ y se jen pˇripoj´ı (provede TCP handshake), ale nic neˇcte. Viz pˇr´ıklad a koment´aˇr v select/write-select.c .
205
ˇ an´ı na data: poll() Cek´ int poll(struct pollfd fds [], nfds t nfds, int timeout ); • ˇcek´ a na ud´ alost na nˇekter´em z deskriptor˚ u v poli fds o nfds prvc´ıch po dobu timeout ms (0 . . . vr´at´ı se hned, -1 . . . ˇcek´a se libovolnˇe dlouho). • prvky struktury pollfd: – fd . . . ˇc´ıslo deskriptoru – events . . . oˇcek´ avan´e ud´alosti, OR-kombinace POLLIN (lze ˇc´ıst), POLLOUT (lze ps´at), atd. – revents . . . ud´ alosti, kter´e nastaly, pˇr´ıznaky jako v events, nav´ıc napˇr. POLLERR (nastala chyba)
• Tato funkce je obdoba vol´ an´ı select. • argument timeout je v milisekund´ach • Existuj´ıc´ıch pˇr´ıznak˚ u pro pouˇzit´ı je mnohem v´ıce, viz manu´alov´a str´anka. • Na Solarisu je poll syst´emov´e vol´an´ı, select pak knihovn´ı funkce implementovan´ a pomoc´ı poll, a poll je tam preferov´ano. poll je nutn´e pouˇz´ıt v pˇr´ıpadˇe, ˇze chcete testovat deskriptor vˇetˇs´ı nebo rovn´ y FD SETSIZE. To je hlavn´ı rozd´ıl mezi vol´ an´ımi select a poll. Dalˇs´ı rozd´ıl je ten, ˇze nen´ı tˇreba nastavovat deskriptory po kaˇzd´em vol´an´ı poll, ani nulovat revents. ˇ nastaven´ • Cas y na -1 je to sam´e jako NULL u select. • Pokud nastav´ıte poˇcet deskriptor˚ u na 0 (a mˇeli byste pak pro fds pouˇz´ıt NULL), m˚ uˇzete poll jednoduˇse vyuˇz´ıt pro ˇcek´an´ı s menˇs´ı granularitou neˇz po sekund´ ach nab´ızen´e vol´ an´ım sleep. Pˇr´ıklad: sleep/poll-sleep.c – Mimochodem, jin´ y zp˚ usob, jak dos´ahnout menˇs´ı granularity neˇz je jedna sekunda je vol´ an´ı nanosleep, kter´e je ale definovan´e rozˇs´ıˇren´ım POSIX.4 a tedy nemus´ı b´ yt vˇzdy k dispozici. Pˇr´ıklad: sleep/nanosleep.c .
206
Spr´ ava s´ıt’ov´ ych sluˇ zeb: inetd • servery s´ıt’ov´ ych sluˇzeb se spouˇst´ı bud’ pˇri startu syst´emu, nebo je startuje d´emon inetd pˇri pˇripojen´ı klienta. • d´emon inetd ˇcek´ a na portech definovan´ ych v /etc/inetd.conf a kdyˇz detekuje pˇripojen´ı klienta, nav´aˇze spojen´ı, spust´ı pˇr´ısluˇsn´ y server a pˇresmˇeruje mu deskriptory 0, 1 a 2 do soketu, pˇres kter´ y lze komunikovat s klientem. • pˇr´ıklad obsahu /etc/inetd.conf: ftp stream tcp nowait root /usr/etc/ftpd ftpd -l shell stream tcp nowait root /usr/etc/rshd rshd -L login stream tcp nowait root /usr/etc/rlogind rlogind exec stream tcp nowait root /usr/etc/rexecd rexecd finger stream tcp nowait guest /usr/etc/fingerd fingerd ntalk dgram udp wait root /usr/etc/talkd talkd tcpmux stream tcp nowait root internal echo stream tcp nowait root internal
• inetd je v podstatˇe velk´ y poll cyklus obhospodaˇruj´ıc´ı sadu socket˚ u podle konfigurace. • Start pˇres inetd ˇsetˇr´ı prostˇredky, protoˇze pˇr´ısluˇsn´ y server bˇeˇz´ı pouze po ˇcas, kdy jsou jeho sluˇzby opravdu potˇreba. Nehod´ı se tedy pro pro spoustˇen´ı vyt´ıˇzen´ ych sluˇzeb (HTTP) nebo sluˇzeb kde m˚ uˇze b´ yt velk´ y overhead pˇri inicializaci (napˇr. SSH). • Typicky se pomoc´ı inetd spouˇst´ı servery, kter´e se pouˇz´ıvaj´ı m´alo nebo jejichˇz inicializace je relativnˇe nen´ aroˇcn´a (telnetd, ftpd). Silnˇe vyt´ıˇzen´e a dlouho startuj´ıc´ı servery (httpd) se obvykle startuj´ı ze syst´emov´ ych inicializaˇcn´ıch skript˚ u a bˇeˇz´ı st´ ale. ˇ • Casto m´ a cenu m´ıt inetd vypnut´ yu ´plnˇe. Pokud na vaˇsem stroji bˇeˇz´ı napˇr. pouze SSH, tak pro to se inetd ve vˇetˇsinˇe pˇr´ıpad˚ u nepouˇz´ıv´a, inetd by byl jen dalˇs´ım serverem bˇeˇz´ıc´ım na stroji a zdroj potenci´aln´ıho nebezpeˇc´ı, pokud by se v nˇem nebo v jednom z nˇej spouˇstˇen´ ych program˚ u objevila bezpeˇcnostn´ı chyba. To by ostatnˇe mˇelo platit pro vˇsechny instalovan´e programy poskytuj´ıc´ı s´ıt’ov´e sluˇzby - bud’to by mˇely implicitnˇe poslouchat pouze na localhostu (resp. pouˇz´ıvat unixov´e sockety) nebo by implicitnˇe nemˇely bˇeˇzet a mˇely by b´ yt spuˇstˇeny (ve smyslu permanentnˇe zapnuty) aˇz tehdy kdyˇz jsou skuteˇcnˇe tˇreba (tento pˇr´ıstup se oznaˇcuje jako secure by default).
207
Form´ at souboru /etc/inetd.conf sluˇ zba soket proto ˇ cek´ an´ ı uˇ ziv server argumenty • sluˇ zba . . . jm´eno s´ıt’ov´e sluˇzby podle /etc/services • soket . . . stream nebo dgram • proto . . . komunikaˇcn´ı protokol (tcp, udp) • c ˇek´ an´ ı . . . wait (inetd ˇcek´a na ukonˇcen´ı serveru pˇred akceptov´ an´ım dalˇs´ıho klienta), nowait (inetd akceptuje dalˇs´ıho klienta hned) • uˇ zivatel . . . server pobˇeˇz´ı s identitou tohoto uˇzivatele • server . . . u ´pln´ a cesta k programu serveru nebo internal ’ (sluˇzbu zajiˇst uje inetd) • argumenty . . . pˇr´ıkazov´ y ˇr´adek pro server, vˇcetnˇe argv[0]
• Soket typu stream: – wait . . . server dostane soket, na kter´ y mus´ı aspoˇ n jednou zavolat accept. Teprve t´ım z´ısk´a nov´ y soket, pˇres kter´ y m˚ uˇze komunikovat s klientem. Po skonˇcen´ı serveru pˇreb´ır´a ˇr´ızen´ı soketu zpˇet inetd. – nowait . . . inetd zavol´a accept a z´ıskan´ y soket pˇred´a serveru, server tedy m˚ uˇze rovnou komunikovat (m˚ uˇze pouˇz´ıvat standardn´ı vstup a v´ ystup) a nemus´ı vˇedˇet, ˇze komunikuje po s´ıti. Mezit´ım inetd ˇcek´a na dalˇs´ı klienty a podle potˇreby spouˇst´ı dalˇs´ı instance serveru. • Jak pro wait tak pro nowait provedl inetd na soketu vol´an´ı bind aby mu pˇriˇradil ˇc´ıslo portu podle pole sluˇ zba. • V pˇr´ıpadˇe wait dostane server soket od inetd v podobˇe deskriptoru 0 (standarn´ı vstup). inetd pˇred vol´an´ım fork provedl dup2(sock, 0), kde sock je nabindovan´ y socket. Viz pˇr´ıklad inetd/accept-rw.c . • Pro soket typu dgram m´ a smysl pouze wait. Server mus´ı pˇreˇc´ıst ze soketu aspoˇ n jeden datagram. • Jestliˇze inetd restartuje server (kromˇe stream nowait) pˇr´ıliˇs ˇcasto (cca jednou za sekundu), usoud´ı, ˇze nastala chyba a po urˇcitou dobu (asi 10 minut) sluˇzbu zablokuje (nespouˇst´ı server a odm´ıt´a spojen´ı). Ostatn´ı servery spouˇst´ı norm´ alnˇe d´ al. • Pˇr´ıklad: inetd/echo-server.sh . Pod´ıvejte se do dan´eho skriptu pro podrobn´e instrukce, jak ho pouˇz´ıt. 208
• Pro zaj´ımavost, ne vˇsechny syst´emy mus´ı nutnˇe inetd.conf pouˇz´ıvat stejn´ ym zp˚ usobem. Napˇr´ıklad od Solarisu 10 se tento soubor pouˇzije pouze pro poˇc´ ateˇcn´ı konverzi do intern´ı datab´aze pomoc´ı inetconv(1M) a pro zap´ın´an´ı a vyp´ın´ an´ı sluˇzeb se pak pouˇz´ıv´a pˇr´ıkaz inetadm(1M).
Obsah • u ´vod, v´ yvoj UNIXu a C, program´atorsk´e n´astroje • z´ akladn´ı pojmy a konvence UNIXu a jeho API • pˇr´ıstupov´ a pr´ ava, perifern´ı zaˇr´ızen´ı, syst´em soubor˚ u • manipulace s procesy, spouˇstˇen´ı program˚ u • sign´ aly • synchronizace a komunikace proces˚ u • s´ıt’ov´ a komunikace • vl´ akna, synchronizace vl´ aken • ??? - bude definov´ ano pozdˇeji, podle toho kolik zbyde ˇcasu
209
Vl´ akna • vl´ akno (thread ) = linie v´ ypoˇctu (thread of execution) • vl´ akna umoˇzn ˇuj´ı m´ıt v´ıce lini´ı v´ ypoˇctu v r´amci jednoho procesu • klasick´ y unixov´ y model: jednovl´aknov´e procesy • vl´ akna nejsou vhodn´ a pro vˇsechny aplikace • v´ yhody vl´ aken: – zrychlen´ı aplikace, typicky na v´ıceprocesorech (vl´akna jednoho procesu mohou bˇeˇzet souˇcasnˇe na r˚ uzn´ ych procesorech) – modul´ arn´ı programov´an´ı • nev´ yhody vl´ aken: – nen´ı jednoduch´e korektnˇe napsat sloˇzitˇejˇs´ı k´od s vl´akny – obt´ıˇznˇejˇs´ı debugging
• Pro aplikace, kde kaˇzd´ y krok z´avis´ı na kroku pˇredch´azej´ıc´ım, nemaj´ı vl´akna pˇr´ıliˇs velk´ y smysl. • Debuggery typicky maj´ı podporu vl´aken, ale debugging zmˇen´ı timing, takˇze to co v re´ alu dˇel´ a probl´em se pˇri debuggingu v˚ ubec nemus´ı projevit. Toto vˇetˇsinou nen´ı probl´emem u klasick´ ych 1-vl´aknov´ ych proces˚ u. • Jak bylo uvedeno na slajdech s doporuˇcenou literaturou, k vl´akn˚ um existuje v´ yborn´ a kniha [Butenhof]. On-line pak je tˇreba dostupn´a obs´ahl´a kniha Multithreaded Programming Guide na http://docs.oracle.com. • Dalˇs´ı situac´ı, kdy je potˇreba z˚ ustat u pouˇzit´ı proces˚ u, je pokud je nutn´e vytvoˇren´ ym proces˚ um mˇenit re´ aln´e a efektivn´ı UID. To je tˇreba pˇr´ıpad OpenSSH, kde se pro kaˇzd´e spojen´ı vytvoˇr´ı dva server procesy. Jeden proces bˇeˇz´ı s maxim´ aln´ımi privilegii, typicky jako root, a poskytuje sluˇzby neprivilegovan´emu procesu bˇeˇz´ıc´ım pod pˇrihl´ aˇsen´ ym uˇzivatelem. Takovou sluˇzbou je tˇreba alokace presudo termin´ alu, coˇz pod bˇeˇzn´ ym uˇzivatelem nelze prov´est. Idea je takov´ a, ˇze vˇetˇsina k´ odu privilegia roota nepotˇrebuje, ˇc´ımˇz se v´ yraznˇe zmenˇs´ı mnoˇzstv´ı k´ odu, kde by chyba vedla k z´ısk´an´ı privilegi´ı roota. Tento zp˚ usob se naz´ yv´ a privilege separation a nen´ı moˇzn´e ho dos´ahnout pomoc´ı vl´aken tak, ˇze by r˚ uzn´ a vl´ akna bˇeˇzela pod r˚ uzn´ ymi uˇzivateli, protoˇze vˇsechna vl´akna sd´ılej´ı stejn´ y adresov´ y prostor, a kaˇzd´e ho tak m˚ uˇze mˇenit.
210
Implementace vl´ aken library-thread model • vl´ akna jsou implementov´ana v knihovn´ach, j´adro je nevid´ı. • run-time knihovna pl´anuje vl´akna na procesy a j´adro pl´ anuje procesy na procesory. ⊕ menˇs´ı reˇzie pˇrep´ın´ an´ı kontextu nem˚ uˇze bˇeˇzet v´ıce vl´aken stejn´eho procesu najednou. kernel-thread model • vl´ akna jsou implementov´ana pˇr´ımo j´adrem. ⊕ v´ıce vl´ aken jednoho procesu m˚ uˇze bˇeˇzet najednou na r˚ uzn´ ych procesorech. pl´ anov´ an´ı thread˚ u pouˇz´ıv´a syst´emov´a vol´an´ı m´ısto knihovn´ıch funkc´ı, t´ım v´ıce zatˇeˇzuje syst´em. hybridn´ı modely • vl´ akna se multiplexuj´ı na nˇekolik j´adrem pl´anovan´ ych entit.
• p˚ uvodnˇe UNIX s vl´ akny nepracoval a prvn´ı implementace byly ˇcistˇe knihovn´ı, bez u ´ˇcasti j´ adra. Dnes se pouˇz´ıv´a sp´ıˇse implementace vl´aken v j´adru nebo sm´ıˇsen´ y model. • zat´ımco pˇ ri pr´ aci s v´ıce procesy je nutn´ e vyvinout jist´ eu ´ sil´ı proto, aby dan´ e procesy mohly data sd´ılet, u vl´ aken je naopak nutn´ eˇ reˇ sit situaci, jak pˇ rirozen´ e sd´ılen´ı dat uhl´ıdat. • vl´ akna implementov´ ana pouze v knihovnˇe mohou b´ yt preemptivn´ı i nepreemptivn´ı. Pro preemptivnost je moˇzn´e pouˇz´ıt ˇcasovaˇce a sign´aly. Pokud multithreading (= pouˇzit´ı vl´ aken v aplikaci) nen´ı pouˇzit pro zv´ yˇsen´ı v´ ykonu aplikace, typicky nen´ı probl´em s pouˇzit´ım nepreemptivn´ıch vl´aken. Stˇr´ıd´an´ı vl´ aken se automaticky dos´ ahne pouˇz´ıv´an´ım blokuj´ıc´ıch syst´emov´ ych vol´an´ı. • pokud se vol´ an´ı v library-thread modelu zablokuje, zablokuje se cel´ y proces, tj. ˇz´ adn´e vl´ akno nem˚ uˇze bˇeˇzet. To vypl´ yv´a z toho, ˇze j´adro v tomto modelu o pojmu vl´ akno nic nev´ı. Knihovn´ı funkce jsou proto pˇreps´any tak, ˇze m´ısto blokuj´ıc´ıch vol´ an´ı se pouˇzij´ı neblokuj´ıc´ı, aktu´aln´ı kontext se uloˇz´ı a n´ aslednˇe pˇrepne na jin´e vl´ akno pomoc´ı vol´an´ı setjmp a longjmp. Pˇr´ıklad: pthreads/setjmp.c .
211
POSIX vl´ akna (pthreads) • definovan´e rozˇs´ıˇren´ım POSIX.1c • vol´ an´ı t´ ykaj´ıc´ı se vl´ aken zaˇc´ınaj´ı prefixem pthread • tyto funkce vrac´ı 0 (OK) nebo pˇr´ımo ˇc´ıslo chyby – . . . a nenastavuj´ı errno! – nelze s nimi proto pouˇz´ıt funkce perror nebo err • POSIX.1c definuje i dalˇs´ı funkce, napˇr´ıklad nov´e verze k tˇem, kter´e nebylo moˇzn´e upravit pro pouˇzit´ı ve vl´aknech bez zmˇeny API, napˇr readdir r, strtok r, atd. – r znamen´ a reentrant, tedy ˇze funkci m˚ uˇze volat v´ıce vl´aken najednou bez vedlejˇs´ıch efekt˚ u
• Obecn´e informace o POSIXu jsou na stranˇe 16. • Existuj´ı i dalˇs´ı implementace vl´aken podporuj´ıc´ı rozd´ıln´a API, napˇr. syst´emov´e vol´ an´ı sproc() v IRIXu, Cthreads, Solaris threads, . . . • API pro POSIX vl´ akna jsou na r˚ uzn´ ych syst´emech k dispozici v r˚ uzn´ ych knihovn´ ach. Napˇr. na Linuxu je nutn´e programy pouˇz´ıvaj´ıc´ı POSIX threads API linkovat s -lpthread, na Solarisu jsou funkce souˇc´ast´ı libc. • Implementace POSIX threads je vˇetˇsinou postavena nad nativn´ı implementac´ı thread˚ u na dan´em syst´emu, napˇr. na Solarisu nad funkcemi s prefixem thr . • O reentrantn´ıch funkc´ıch ve spojen´ı s vl´akny v´ıce na stranˇe 247 • Co se t´ yk´ a oˇsetˇren´ı n´ avratov´ ych hodnot pthread funkc´ı, tak vzhledem k tomu, ˇze nenastavuj´ı errno, nen´ı n´asleduj´ıc´ı k´od naps´an korektnˇe: if (pthread_create(&thr, NULL, thrfn, NULL) != 0) err(1, "pthread_create"); protoˇze program v´ am pˇri chybˇe vyp´ıˇse nˇeco jako: – “a.out: pthread create: Error 0” na Solarisu – “a.out: pthread create: Success” na Linuxov´ ych distribuc´ıch – “a.out: pthread create: Unknown error: 0” na FreeBSD
212
– nebo nˇeco jin´eho, podle syst´emu, kter´ y zrovna pouˇz´ıv´ate Trochu matouc´ı mi pˇrijde pˇr´ıstup Linuxu, protoˇze na prvn´ı pohled nen´ı jasn´e, co se vlastnˇe stalo a ˇze errno bylo nulov´e. Nicm´enˇe, spr´avnˇe napsan´ y k´od je napˇr´ıklad tento: int e; if ((e = pthread_create(&thr, NULL, thrfn, NULL)) != 0) errx(1, "pthread_create: %s", strerror(e)); Vˇsimnˇete si nutnosti pouˇz´ıt errx funkci, ne err, protoˇze ta internˇe pracuje s errno. Pozor na to, ˇze promˇenn´a errno by v˚ ubec nemusela b´ yt nulov´a, pokud by ji nastavila jin´ a funkce volan´a pˇred pthread create, coˇz by mohlo uˇzivatele nebo program´ atora zm´ast jeˇstˇe v´ıce. • Ostatn´ı funkce, kter´e errno nastavuj´ı, funguj´ı stejnˇe i s vl´akny, tj. kaˇzd´e vl´ akno m´ a svoj´ı errno. Pozor je ale tˇreba d´at na to, ˇze r˚ uzn´e syst´emy se chovaj´ı r˚ uznˇe. Na Linuxu je errno automaticky thread-safe protoˇze tam mus´ıte specifikovat vl´ aknovou knihovnu, na Solarisu ale mus´ıte u Sun Studia pouˇz´ıt pˇrep´ınaˇc -mt nebo -D REENTRANT, viz manu´alov´a str´anka threads(5). Pokud tak neudˇel´ ate, nebude errno nastaveno korektnˇe. Pod´ıvejte se na definici errno v /usr/include/errno.h a pochop´ıte, jak to funguje. Dalˇs´ı rozd´ıl je ten, ˇze Solaris m´ a vl´akna pˇr´ımo v libc, u ostatn´ıch syst´em˚ u mus´ıte vˇetˇsinout specifikovat vl´ aknovou knihovnu. U gcc staˇc´ı na jak´emkoli syst´emu pouˇz´ıt pˇrep´ınaˇc -pthreads, potˇrebnou knihovnu si pˇrekladaˇc uˇz najde s´am, a z´ aroveˇ n nastav´ı errno jako thread-safe. Stejnˇe jako u cc je ale na Solarisu moˇzn´e pouˇz´ıt jen -D REENTRANT.
Vytvoˇ ren´ı vl´ akna int pthread create(pthread t *thread, const pthread attr t *attr, void *(*start fun )(void*), void *arg ); • vytvoˇr´ı nov´e vl´ akno, do thread uloˇz´ı jeho ID. • podle attr nastav´ı jeho atributy, napˇr. velikost z´asobn´ıku ˇci pl´ anovac´ı politiku. NULL znamen´a pouˇz´ıt implicitn´ı atributy. • ve vytvoˇren´em vl´ aknu spust´ı funkci start fun() s argumentem arg. Po n´ avratu z t´eto funkce se zruˇs´ı vl´akno. • s objekty pthread attr t lze manipulovat funkcemi pthread attr init(), pthread attr destroy(), pthread attr setstackaddr(), atd. . .
213
• Pozor na konstrukce typu: for (i = 0; i < N; i++) pthread create(&tid, attr, start routine, &i); Na prvn´ı pohled takto pˇred´ ame kaˇzd´emu vl´aknu jeho index. Jenˇze pl´anovaˇc m˚ uˇze zp˚ usobit to, ˇze neˇz si novˇe spuˇstˇen´e vl´akno staˇc´ı pˇreˇc´ıst hodnotu i, pˇr´ıkaz for provede dalˇs´ı iteraci a hodnota se zmˇen´ı. Obecnˇe vl´akno m˚ uˇze dostat m´ısto spr´ avn´e hodnoty i libovolnou vˇetˇs´ı. • Pˇr´ıklady: pthreads/wrong-use-of-arg.c , pthreads/correct-use-of-arg.c . • Co je moˇzn´e pouˇz´ıt, pokud potˇrebujeme pˇredat pouze jednu hodnotu (podle C standardu je to ale implementaˇ cnˇ e z´ avisl´ e a tedy nepˇ renositeln´ e): assert(sizeof (void *) >= sizeof (int)); for (i = 0; i < N; i++) pthread create(&tid, attr, start routine, (void *)(intptr t)i); . . . a ve funkci void *start routine(void *arg) pak pˇretypovat ukazatel zp´ atky na integer a m´ ame potˇrebn´ y identifik´ator vl´akna: printf("thread %d started\n", (int)arg); Pˇr´ıklad: pthreads/int-as-arg.c • Pokud potˇrebujeme pˇredat v´ıce bajt˚ u neˇz je velikost ukazatele, tak uˇz opravdu mus´ıme pˇredat ukazatel na pamˇet’ s pˇr´ısluˇsn´ ymi pˇred´avan´ ymi daty nebo pouˇz´ıt glob´ aln´ı promˇenn´e; pˇr´ıstup k nim je pak ale samozˇrejmˇe nutn´e synchronizovat. uhledn´ y”typ, do kter´eho program nevid´ı a ani by nemˇel, • pthread t je ”pr˚ jeho implementace se m˚ uˇze liˇsit syst´em od syst´emu; nicm´enˇe vˇetˇsinou jde o celoˇc´ıseln´ y typ, kter´ y se pouˇz´ıv´a k mapov´an´ı na nativn´ı thready na dan´em syst´emu. Pˇri vytv´ aˇren´ı nˇekolika thread˚ u v cyklu je tedy nutn´e pˇredat funkci pthread create pokaˇzd´e adresu jin´e promˇenn´e pthread t, jinak uˇz nep˚ ujde s tˇemito vl´ akny nad´ ale manipulovat z hlavn´ıho vl´akna, napˇr. ˇcekat na jejich dokonˇcen´ı (aˇckoliv bˇeˇzet budou norm´alnˇe), jejich identifikace se ztrat´ı. Toto je ˇreˇsiteln´e napˇr. pˇres pole pthread t nebo dynamickou alokac´ı.
214
Soukrom´ e atributy vl´ aken • ˇc´ıtaˇc instrukc´ı • z´ asobn´ık (automatick´e promˇenn´e) • thread ID, dostupn´e funkc´ı pthread t pthread self(void); • pl´ anovac´ı priorita a politika • hodnota errno • kl´ıˇcovan´e hodnoty – dvojice (pthread key t key, void *ptr ) – kl´ıˇc vytvoˇren´ y vol´ an´ım pthread key create() je viditeln´ y ve vˇsech vl´ aknech procesu. – v kaˇzd´em vl´ aknu m˚ uˇze b´ yt s kl´ıˇcem asociov´ana jin´a hodnota vol´ an´ım pthread setspecific().
• Kaˇzd´e vl´ akno m´ a z´ asobn´ık pevn´e velikosti, kter´ y se automaticky nezvˇ etˇ suje. B´ yv´ a to kolem 64-512 KB, takˇze pokud si ve funkci alokujete pole o velikosti 256KB a pouˇzijete ho, je dost moˇzn´e, ˇze skonˇc´ıte s core dumpem. Pokud chcete z´ asobn´ık vˇetˇs´ı neˇz je syst´emov´ y default, mus´ıte pouˇz´ıt argument attr pˇri vytvoˇren´ı vl´ akna. Pˇr´ıklad: pthreads/pthread-stack-overflow.c • O soukrom´ ych kl´ıˇcovan´ ych atributech vl´akna v´ıce na stranˇe 227. • Kaˇzd´e vl´ akno m´ a jeˇstˇe vlastn´ı sign´alovou masku, k tomu se tak´e dostaneme, viz strana 230.
215
Ukonˇ cen´ı vl´ akna void pthread exit(void *val ptr ); • ukonˇc´ı volaj´ıc´ı vl´ akno, je to obdoba exit pro proces int pthread join(pthread t thr, void **val ptr ); • poˇck´ a na ukonˇcen´ı vl´ akna thr a ve val ptr vr´at´ı hodnotu ukazatele z pthread exit nebo n´avratovou hodnotu vl´aknov´e funkce. Pokud vl´ akno skonˇcilo dˇr´ıve, funkce hned vrac´ı pˇr´ısluˇsnˇe nastaven´e val ptr. • obdoba ˇcek´ an´ı na synovsk´ y proces pomoc´ı wait int pthread detach(pthread t thr ); • nastav´ı okamˇzit´e uvolnˇen´ı pamˇeti po ukonˇcen´ı vl´akna, na vl´ akno nelze pouˇz´ıt pthread join.
• Pokud se nepouˇzije pthread exit, vyvol´a se tato funkce pˇri skonˇcen´ı vl´akna implicitnˇe, s hodnotou pouˇzitou pro return • Norma specifikuje, ˇze stav z´ asobn´ıku ukonˇcovan´eho vl´akna nen´ı definovan´ y, a proto by se v pthread exit nemˇely pouˇz´ıvat odkazy na lok´aln´ı promˇenn´e ukonˇcovan´eho vl´ akna pro parametr val ptr • Pokud nem´ ame v u ´myslu po skonˇcen´ı vl´akna volat pthread join, je tˇreba zavolat pthread detach. Jinak po ukonˇcen´em vl´aknu z˚ ustanou v pamˇeti data nutn´ a pro zjiˇstˇen´ı jeho v´ ysledku pomoc´ı pthread join. To je podobn´a situace, jako kdyˇz rodiˇcovsk´ y proces nepouˇz´ıv´a wait a v syst´emu se hromad´ı zombie. Ve funkci pro takov´e vl´akno je moˇzn´e jednoduˇse pouˇz´ıt toto: pthread detach(pthread self()); • Jin´ a moˇznost jak nastavit, ˇze se na vl´akna nemus´ı ˇcekat, je zavolat funkci pthread attr setdetachstate s hodnotou PTHREAD CREATE DETACHED nad strukturou atribut˚ u a tu pak pouˇz´ıt ve vol´an´ıch pthread create. Pˇr´ıklad: pthreads/set-detachstate.c • Nastaven´ım NULL do argumentu val ptr syst´emu sdˇelujeme, ˇze n´as n´avratov´ a hodnota ukonˇcen´eho vl´akna nezaj´ım´a. ˇ • Cekat na ukonˇcen´ı vl´ akna m˚ uˇze libovoln´e jin´e vl´akno, nejen to, kter´e ho spustilo.
216
• Doporuˇcuji vˇzdy kontrolovat n´avratovou hodnotu pthread join, t´ım si budete jisti, ˇze ˇcek´ ate spr´ avnˇe, pˇri pouˇzit´ı ˇspatn´eho ID vl´akna funkce hned vr´at´ı, coˇz v´ aˇs program nutnˇe nemus´ı negativnˇe ovlivnit co se t´ yk´a funkˇcnosti, ale m˚ uˇzete pak narazit na limit poˇctu vl´aken nebo vypl´ ytvat pamˇet’. • Na rozd´ıl od proces˚ u nelze ˇ cekat na ukonˇ cen´ı kter´ ehokoli vl´ akna. Je to z toho d˚ uvodu, ˇze vl´ akna nemaj´ı vztah rodiˇc–potomek, a proto to nebylo povaˇzov´ ano za potˇrebn´e. Pro zaj´ımavost, Solaris vl´akna toto umoˇzn´ı (jako ID vl´ akna ve funkci thr join se pouˇzije 0). Pokud byste tuto funkˇcnost potˇrebovali s POSIXov´ ymi vl´akny, je jednoduch´e nastavit vl´akna jako detached, pouˇz´ıt podm´ınkov´e promˇenn´e, a pˇred´avat potˇrebn´e informace pˇres glob´ aln´ı promˇennou pod ochranou z´amku, kter´ y je s podm´ınkovou promˇennou sv´ az´ an. V´ıce viz strana 236. • Pˇr´ıklady: pthreads/pthread-join.c , pthreads/pthread-detach-join.c
Inicializace int pthread once(pthread once t *once control, void (*init routine )(void)); • V parametru once control se pˇred´av´a ukazatel na staticky inicializovanou promˇennou pthread once t once control = PTHREAD ONCE INIT; • Prvn´ı vl´ akno, kter´e zavol´a pthread once(), provede inicializaˇcn´ı funkci init routine(). Ostatn´ı vl´akna uˇz tuto funkci neprov´ adˇej´ı, nav´ıc, pokud inicializaˇcn´ı funkce jeˇstˇe neskonˇcila, zablokuj´ı se a ˇcekaj´ı na jej´ı dokonˇcen´ı. • Lze pouˇz´ıt napˇr. na dynamickou inicializaci glob´aln´ıch dat v knihovn´ ach, jejichˇz k´od m˚ uˇze zavolat v´ıce vl´aken souˇcasnˇe, ale je tˇreba zajistit, ˇze inicializace probˇehne jen jednou.
• V programu samotn´em tuto funkci asi potˇrebovat nebudete. M´ısto pouˇzit´ı pthread once staˇc´ı danou inicializaˇcn´ı funkci zavolat pˇred t´ım, neˇz vytvoˇr´ıte vl´ akna... • Nen´ı definov´ ano, co se m´ a st´ at, pokud je once control automatick´a promˇenn´ a nebo nem´ a poˇzadovanou hodnotu.
217
Zruˇ sen´ı vl´ akna int pthread cancel(pthread t thread ); • poˇz´ ad´ a o zruˇsen´ı vl´ akna thread. Z´avis´ı na nastaven´ı int pthread setcancelstate(int state, int *old ); • nastav´ı nov´ y stav a vr´ at´ı star´ y: – PTHREAD CANCEL ENABLE . . . povoleno zruˇsit – PTHREAD CANCEL DISABLE . . . nelze zruˇsit, ˇz´adost bude ˇcekat na povolen´ı int pthread setcanceltype(int type, int *old ); • PTHREAD CANCEL ASYNCHRONOUS . . . okamˇzit´e zruˇsen´ı • PTHREAD CANCEL DEFERRED . . . ˇz´adost ˇcek´a na vstup do urˇcit´ ych funkc´ı (napˇr. open(), read(), wait()), nebo na vol´an´ı void pthread testcancel(void);
• Vl´ akna je moˇzn´e zvenku ,,n´asilnˇe” ruˇsit (obdoba ukonˇcen´ı procesu pomoc´ı sign´ alu) bud’ okamˇzitˇe, nebo jen v urˇcit´ ych vol´an´ıch (tzv. cancellation points). To znamen´ a, ˇze v takov´em pˇr´ıpadˇe je moˇzn´e vl´akno zruˇsit v ˇcase, kdy je vl´ akno vykon´ av´ a danou funkci. Pokud vl´akno zrovna takovou funkci nevykon´ av´ a, informace o zruˇsen´ı se “doruˇc´ı” bˇehem vykon´an´ı prvn´ı takov´e funkce od t´e doby. • Funkce pthread_cancel se podob´a zruˇsen´ı procesu sign´alem poslan´ ym vol´ an´ım kill. • Pˇri zruˇsen´ı vl´ akna se zavolaj´ı u ´klidov´e handlery, viz strana 228. Pokud se rozhodnete ruˇsen´ı vl´ aken pouˇz´ıvat, bud’te velmi opatrn´ı. Napˇr´ıklad, pokud budete ruˇsit vl´ akno, kter´e m´a zamknut´ y mutex, mus´ıte mutex odemknout v u ´klidov´ ych handlerech. • Funkce pthread_setcancelstate a pthread_setcanceltype jsou obdobou zak´ az´ an´ı a povolen´ı zruˇsen´ı procesu sign´alem pomoc´ı manipulace s maskou blokovan´ ych sign´ al˚ u (sigprocmask). • Pˇr´ıklad: pthreads/pthread-cancel.c • Pˇri ruˇsen´ı vl´ akna m˚ uˇze nastat mnoho moˇznost´ı. Solaris m´a tˇreba samostatnou manu´ alnovou str´ anku cancellation(5), kter´a se tomu vˇenuje. FreeBSD definuje cancellation points v manu´alov´e str´ance pthread setcanceltype(3).
218
Pˇ r´ıklad: vl´ akna pthread_t id_a, id_b; void *res_a, *res_b; pthread_create(&id_a, NULL, do_a, "a"); pthread_create(&id_b, NULL, do_b, "b");
void *do_a(void *arg) { ... return arg; }
void *do_b(void *arg) { ... return arg; }
pthread_join(id_a, &res_a); pthread_join(id_b, &res_b);
• Toto je trivi´ aln´ı pˇr´ıklad, kdy proces (hlavn´ı vl´akno) vytvoˇr´ı dvˇe dalˇs´ı vl´akna a poˇck´ a na jejich ukonˇcen´ı.
219
Glob´ aln´ı promˇ enn´ e pro vl´ akno int pthread key create(pthread key t *key, void (*destructor )(void *)); • vytvoˇr´ı kl´ıˇc, kter´ y lze asociovat s hodnotou typu (void *). Funkce destructor() se volaj´ı opakovanˇe pro vˇsechny kl´ıˇce, jejichˇz hodnota nen´ı NULL, pˇri ukonˇcen´ı vl´akna. int pthread key delete(pthread key t key ); • zruˇs´ı kl´ıˇc, nemˇen´ı asociovan´a data. int pthread setspecific(pthread key t key, const void *value ); • pˇriˇrad´ı ukazatel value ke kl´ıˇci key. void *pthread getspecific(pthread key t key ); • vr´ at´ı hodnotu ukazatele pˇr´ısluˇsn´eho ke kl´ıˇci key.
• Bˇeˇzn´e glob´ aln´ı promˇenn´e (a tak´e dynamicky alokovan´a data) jsou spoleˇcn´e pro vˇsechna vl´ akna. Kl´ıˇcovan´e hodnoty pˇredstavuj´ı zp˚ usob, jak vytvoˇrit glob´ aln´ı promˇenn´e v r´ amci vl´ aken. Uvˇedomte si rozd´ıl proti lok´aln´ı promˇenn´e definovan´e ve vl´ aknov´e funkci - takov´a promˇenn´a nen´ı vidˇet v dalˇs´ıch volan´ ych funkc´ıch ve stejn´em vl´aknˇe. Vyuˇzitelnost soukrom´ ych atribut˚ u vl´aken se m˚ uˇze zd´ at mal´ a, ale obˇcas se to velmi dobˇre hod´ı. J´a to jednou pouˇzil v existuj´ıc´ım k´ odu, kde jsem potˇreboval nahradit pr´aci s glob´aln´ım spojov´ ym seznamem na lok´ aln´ı seznamy specifick´e pro kaˇzd´e vl´akno. Nejjednoduˇsˇs´ı, tj. nejm´enˇe zmˇen v existuj´ıc´ım k´odu bylo pˇrev´est pr´aci s glob´aln´ı promˇennou na pr´ aci se soukrom´ ym atributem vl´akna. • Pˇri vytvoˇren´ı kl´ıˇce je s n´ım asociov´ana hodnota NULL. Ukazatel na destruktor funkci nen´ı povinn´ y, pokud nen´ı potˇreba, pouˇzijte NULL. • Pˇri ukonˇcen´ı nebo zruˇsen´ı vl´akna se volaj´ı destruktory (v nespecifikovan´em poˇrad´ı) pro vˇsechny kl´ıˇce s hodnotou r˚ uznou od NULL. Aktu´aln´ı hodnota je destruktoru pˇred´ ana v parametru. Jestliˇze po skonˇcen´ı vˇsech destruktor˚ u zb´ yvaj´ı kl´ıˇce s hodnotou r˚ uznou od NULL, znovu se volaj´ı destruktory. Implementace m˚ uˇze (ale nemus´ı) zastavit vol´an´ı destruktor˚ u po PTHREAD DESTRUCTOR ITERATIONS iterac´ıch. Destruktor by tedy mˇel nastavit hodnotu na NULL, jinak hroz´ı nekoneˇcn´ y cyklus. • Destruktor si mus´ı s´ am zjistit kl´ıˇc poloˇzky, ke kter´e patˇr´ı, a zruˇsit hodnotu vol´ an´ım pthread setspecific(key, NULL). • SUSv3 tento nesmysln´ y poˇzadavek odstraˇ nuje, protoˇze definuje, ˇze pˇred vstupem do destruktoru je hodnota automaticky nastaven´a na NULL; destruktor se n´ aslednˇe vyvol´ a s pˇredchoz´ı hodnotou kl´ıˇce. 220
´ Uklid pˇ ri ukonˇ cen´ı/zruˇ sen´ı vl´ akna • vl´ akno m´ a z´ asobn´ık u ´klidov´ ych handler˚ u, kter´e se volaj´ı pˇri ukonˇcen´ı nebo zruˇsen´ı vl´akna funkcemi pthread exit a pthread cancel (ale ne pˇri return). Handlery se spouˇst´ı v opaˇcn´em poˇrad´ı neˇz byly vkl´ad´any do z´asobn´ıku. • po proveden´ı handler˚ u se volaj´ı destruktory priv´atn´ıch kl´ıˇcovan´ ych dat vl´ akna (poˇrad´ı nen´ı specifikovan´e) void pthread cleanup push(void (*routine )(void *), void *arg ); • vloˇz´ı handler do z´ asobn´ıku. void pthread cleanup pop(int execute ); • vyjme naposledy vloˇzen´ y handler ze z´asobn´ıku. Provede ho, pokud je execute nenulov´e.
• Handlery se volaj´ı jako routine(arg). • Tyto handlery se daj´ı pouˇz´ıvat napˇr. na u ´klid lok´aln´ıch dat funkc´ı (obdoba vol´ an´ı destruktor˚ u pro automatick´e promˇenn´e v C++).
221
fork() a vl´ akna (POSIX) • je nutn´e definovat s´emantiku vol´an´ı fork v aplikac´ıch pouˇz´ıvaj´ıc´ıch vl´ akna. Norma definuje, ˇze: – nov´ y proces obsahuje pˇresnou kopii volaj´ıc´ıho vl´akna, vˇcetnˇe pˇr´ıpadn´ ych stav˚ u mutex˚ u a jin´ ych zdroj˚ u – ostatn´ı vl´ akna v synovsk´em procesu neexistuj´ı – pokud takov´ a vl´ akna mˇela naalokovanou pamˇet’, z˚ ustane ’ tato pamˇet naalokovan´a (= ztracen´a) – obdobnˇe to plat´ı pro zamˇcen´ y mutex jiˇz neexistuj´ıc´ıho vl´ akna • vytvoˇren´ı nov´eho procesu z multivl´aknov´e aplikace m´a smysl pro n´ asledn´e vol´ an´ı exec (tj. vˇcetnˇe vol´an´ı popen, system apod.)
• Pokud mˇelo jiˇz neexistuj´ıc´ı vl´akno zamˇcen´ y mutex, tak je pˇr´ıstup k pˇr´ısluˇsn´ ym sd´ılen´ ym dat˚ um ztracen, protoˇze zamˇcen´ y mutex m˚ uˇze odemknout pouze to vl´ akno, kter´e ho zamknulo. Zde ale trochu pˇredb´ıh´am, mutexy jsou pˇredstaven´e aˇz na stranˇe 233. • Ostatn´ı vl´ akna v nov´em procesu pˇrestanou existovat, nevolaj´ı se ˇz´adn´e rutiny ych jako pˇri vol´ an´ı pthread exit, pthread cancel nebo destruktory kl´ıˇcovan´ dat. • Pozor na to, ˇze chov´ an´ı fork tak´e z´avis´ı na pouˇzit´e knihovnˇe a verzi syst´emu, napˇr´ıklad na Solarisu pˇred verz´ı 10 znamenalo fork v knihovnˇe libthread (jin´ a knihovna neˇz libpthread) to sam´e co forkall. • Pˇr´ıklady: pthreads/fork.c , pthreads/fork-not-in-main.c , pthreads/forkall.c • Pomoc´ı funkce pthread atfork je moˇzn´e nastavit handlery kter´e se automaticky vykonaj´ı pˇred pouˇzit´ım fork v rodiˇci a po n´avratu z fork v rodiˇci a synovsk´em procesu. Toto vol´an´ı se velmi hod´ı pro vˇsechny multithreadov´e programy kter´e volaj´ı fork a nepouˇz´ıvaj´ı ho pouze jako jednoduch´ y wrapper pro exec. Po fork se totiˇz v synovsk´em procesu nach´azej´ı vˇsechny promˇenn´e ve stejn´em stavu jako byly v rodiˇci ve chv´ıli kdy se zavolal fork a tedy pokud mˇelo nˇejak´e jin´e vl´ akno (neˇz kter´e zavolalo fork) napˇr. zamˇcen´ y mutex (zamyk´ an´ı pomoc´ı mutex˚ u viz strana 233) tak bude tento mutex zamˇcen´ yiv synovsk´em procesu. Pokud se pak vl´akno ze synovsk´eho procesu tento mutex pokus´ı zamknout, dojde k deadlocku. Pokud se v pre-fork handleru provede zamˇcen´ı vˇsech mutex˚ u a v obou (rodiˇc i syn) post-fork handler˚ u odemˇcen´ı vˇsech mutex˚ u, tato situace nenastane. Tento mechanismus funguje d´ıky tomu, 222
ˇze pre-fork handler se zavol´ a ve vl´aknu, kter´e zavolalo fork, jeˇstˇe pˇredt´ım neˇz se provede samotn´ y fork syscall; ostatn´ı vl´akna mezit´ım d´al bˇeˇz´ı a mohou tedy uvolnit mutexy (jednoduˇse tak ˇze kaˇzd´e vl´akno v rozumnˇe napsan´em programu ˇcasem opust´ı kritickou sekci), na kter´e se ˇcek´a v handleru. Toto samozˇrejmˇe pˇredpokl´ ad´ a, ˇze zamyk´an´ı a odemyk´an´ı v handlerech dodrˇzuje zamykac´ı pravidla (”protokol”) stanoven´ y pro cel´ y program a nedojde tedy k deadlocku. pthreads/atfork.c V´ıce napˇr. v [Butenhof].
Sign´ aly a vl´ akna • sign´ aly mohou b´ yt generov´any pro proces (vol´an´ım kill), nebo pro vl´ akno (chybov´e ud´alosti, vol´an´ı pthread kill). • nastaven´ı obsluhy sign´ al˚ u je stejn´e pro vˇsechna vl´akna procesu, ale masku blokovan´ ych sign´al˚ u m´a kaˇzd´e vl´akno vlastn´ı, nastavuje se funkc´ı int pthread sigmask(int how, const sigset t *set, sigset t *oset ); • sign´ al urˇcen´ y pro proces oˇsetˇr´ı vˇzdy pr´avˇe jedno vl´akno, kter´e nem´ a tento sign´ al zablokovan´ y. • lze vyhradit jedno vl´ akno pro synchronn´ı pˇr´ıjem sign´al˚ u pomoc´ı vol´ an´ı sigwait. Ve vˇsech vl´aknech se sign´aly zablokuj´ı.
• Jestliˇze je pro sign´ al nastaveno ukonˇcen´ı procesu, skonˇc´ı cel´ y proces, nejen jedno vl´ akno. • Vytvoˇren´e vl´ akno dˇed´ı nastaven´ı sign´alov´e masky od vl´akna, kter´e ho vytvoˇrilo • Analogicky k pouˇzit´ı sigwait s procesy (strana 165) – zablokujte pˇr´ısluˇsn´e sign´ aly ve vˇsech vl´ aknech, vˇcetnˇe vl´akna, ve kter´em chcete zpracov´av´avat sign´ aly pomoc´ı sigwait. Tento zp˚ usob zpracov´ an´ı sign´ al˚ u b´ yv´ aˇ casto jedin´ y opravdu doporuˇ covan´ y pro vl´ akna, a nav´ıc je i nejsn´aze implementovateln´ y. Jak vypl´ yv´ a z pˇredchoz´ı pozn´amky, staˇc´ı zamaskovat sign´aly pouze jednou, a to v hlavn´ım vl´aknˇe, protoˇze maska se pak podˇed´ı pˇri kaˇzd´em vol´ an´ı pthread create. • V prostˇred´ı vl´ aken nepouˇz´ıvejte sigprocmask (strana 163), protoˇze chov´an´ı tohoto vol´ an´ı nen´ı v takov´em prostˇred´ı normou specifikov´ano. M˚ uˇze to fungovat, a tak´e nemus´ı. • Pˇr´ıklad: pthreads/sigwait.c . 223
• Pozor na to, ˇze byste nemˇeli tento zp˚ usob obsluhy sign´al˚ u pouˇz´ıvat pro sign´ aly synchronn´ı jako jsou SIGSEGV, SIGILL, apod. Tyto sign´aly jsou generovan´e pˇr´ımo pro vl´ akno, takˇze pokud je zablokujete, vl´akno urˇcen´e pro synchronn´ı pˇr´ıjem sign´ al˚ u je nemus´ı “vidˇet”, jedinˇe pokud by ten sign´al zp˚ usobilo samo, samozˇrejmˇe. Dalˇs´ı vˇec ale je, ˇze specifikac´ı nen´ı definov´ano, zda blokov´ an´ı takov´ ych sign´ al˚ u tyto sign´aly skuteˇcnˇe zablokuje, jak jiˇz bylo zm´ınˇeno na stranˇe 156. Nˇekter´e syst´emy tyto sign´aly norm´alnˇe doruˇc´ı, ˇc´ımˇz proces ukonˇc´ı, viz pˇresn´e znˇen´ı: If any of the SIGFPE, SIGILL, SIGSEGV, or SIGBUS signals are generated while they are blocked, the result is undefined, unless the signal was generated by the kill() function, the sigqueue() function, or the raise() function. Pˇr´ıklad: pthreads/sigwait-with-sync-signals.c . Tento pˇr´ıklad ukazuje, ˇze na Solarisu 10, Solarisu 11, FreeBSD 7.2 a Linux distribuci, kter´a se hl´as´ı jako “Gentoo Base System release 1.12.13”, je sign´al SIGSEGV doruˇcen a proces zabit bez ohledu na to, zda je maskov´an. Naˇsel jsem ale i syst´em, kter´ y po zamaskov´ an´ı sign´ al nedoruˇc´ı – FreeBSD 6.0. Synchronn´ı sign´aly by ale mˇelo b´ yt vˇzdy moˇzn´e zachytit (pˇred zavol´an´ım exit), viz strana 156, kde je i pˇr´ıklad.
Synchronizace vl´ aken obecnˇ e • vˇetˇsina program˚ u pouˇz´ıvaj´ıc´ıch vl´akna mezi nimi sd´ıl´ı data • nebo vyˇzaduje, aby r˚ uzn´e akce byly prov´adˇeny v jist´em poˇrad´ı • . . . toto vˇse potˇrebuje synchronizovat aktivitu bˇeˇz´ıc´ıch vl´aken • jak bylo zm´ınˇeno u implementace vl´aken, u proces˚ u je pro sd´ılen´ı dat nutn´e vynaloˇzit jist´e u ´sil´ı, u vl´aken naopak mus´ıme investovat do toho, abychom pˇrirozen´e sd´ılen´ı dat uhl´ıdali • my se vzhledem k vl´ akn˚ um budeme zab´ yvat: – mutexy – podm´ınkov´ ymi promˇenn´ ymi – read-write z´ amky
• pro pˇripomenut´ı, synchronizac´ı proces˚ u jsme se jiˇz zab´ yvali na stran´ach 167 aˇz 184. • pomoc´ı mutex˚ u a podm´ınkov´ ych promˇenn´ ych je moˇzn´e postavit jak´ ykoli jin´ y synchronizaˇcn´ı model. 224
• na tom jak´ ym zp˚ usobem budou fungovat synchronizaˇcn´ı primitiva se velkou ˇc´ ast´ı pod´ıl´ı scheduler, kter´ y napˇr. rozhoduje kter´e z vl´aken ˇcekaj´ıc´ıch na odemˇcen´ı bude po odemˇcen´ı probuzeno. S t´ım souvis´ı klasick´e probl´emy, napˇr. thundering horde (na odemˇcen´ı ˇcek´a velk´e mnoˇzstv´ı vl´aken) nebo priority inversion (vl´ akno, kter´e drˇz´ı z´amek, m´a menˇs´ı prioritu neˇz vl´akno, kter´e ˇcek´ a na tento z´ amek).
Synchronizace vl´ aken: mutexy (1) • nejjednoduˇsˇs´ı zp˚ usob zajiˇstˇen´ı synchronizovan´eho pˇr´ıstupu ke sd´ılen´ ym dat˚ um mezi vl´akny je pouˇzit´ım mutexu • inicializace staticky definovan´eho mutexu: pthread mutex t mutex = PTHREAD MUTEX INITIALIZER; • inicializace dynamicky alokovan´eho mutexu mx s atributy attr (nastavuj´ı se pomoc´ı pthread mutexattr ...; a je-li m´ısto attr pouˇzit NULL, vezmou se defaultn´ı atributy) int pthread mutex init(pthread mutex t *mx, const pthread mutexattr t *attr ); • po skonˇcen´ı pouˇz´ıv´ an´ı mutexu je moˇzn´e ho zruˇsit: int pthread mutex destroy(pthread mutex t *mx );
• Mutex = mutual exclusion (vz´ajemn´e vylouˇcen´ı) • Je to speci´ aln´ı forma Dijkstrov´ ych semafor˚ u – rozd´ıl mezi mutexy a bin´arn´ımi semafory je ten, ˇze mutex m´ a majitele a zamˇ cen´ y mutex mus´ı odemknout pouze to vl´ akno, kter´ e ho zamknulo. To u semafor˚ u neplat´ı. Pozor na to, ˇze abyste zjistili, ˇze napˇr´ıklad zkouˇs´ıte odemknout mutex, kter´ y dan´e vl´ akno nezamknulo, je nutn´e jednak testovat n´avratovou hodnotu vol´an´ı pthread mutex lock, a tak´e je nutn´e ovˇeˇrit, ˇze m´ate nastavenou kontrolu zamyk´ an´ı; viz d´ ale. • Mutexy jsou urˇcen´e pouze ke kr´atkodob´emu drˇzen´ı a mˇely by fungovat rychle. Slouˇz´ı pro implementaci kritick´ ych sekc´ı (definice je na stranˇe 169), podobnˇe jako lock-soubory nebo semafory (pouˇzit´e jako z´amky). • Kontrola zamyk´ an´ı u mutex˚ u – defaultnˇe je typ mutexu PTHREAD MUTEX DEFAULT. V tomto nastaven´ı nejsou normou definov´any v´ ysledky zamknut´ı jiˇz zamknut´eho mutexu, odemknut´ı mutexu zamknut´eho jin´ ym vl´aknem ani odemknut´ı odemknut´eho mutexu. Implementace si namapuj´ı toto makro na normou definovan´e PTHREAD MUTEX NORMAL nebo PTHREAD MUTEX ERRORCHECK. M˚ uˇze se v´ am proto v z´ avislosti na konkr´etn´ı implementaci st´at, ˇze pokud 225
v defaultn´ı konfiguraci zamknete jiˇz jednou zamknut´ y mutex, nastane deadlock (normal), a nebo tak´e ne (errorcheck). V tom druh´em pˇr´ıpadˇe dostanete n´ avratovou hodnotu o chybˇe a pokud ji netestujete, budete se mylnˇe domn´ıvat, ˇze jste mutex zamknuli. U normal nen´ı v´ ysledek zbyl´ ych dvou situac´ı definov´ an, u errorcheck se v´am vr´at´ı chyba. “Nedefinov´ano” u normal znamen´ a, ˇze vl´ akno odemykaj´ıc´ı mutex, kter´ y nezamknulo, ho klidnˇe m˚ uˇze odemknout, nebo tak´e ne – vˇse z´avis´ı na konkr´etn´ı implementaci. “Nedefinov´ ano” ale tak´e znamen´a, ˇze skuteˇcn´ y v´ ysledek takov´ ychto situac´ı by v´ as nemˇel zaj´ımat, protoˇze byste se jim vˇzdy mˇeli vyhnout. V´ıce informac´ı viz norma nebo manu´alov´a str´anka k pthread mutexattr settype. Mnˇe osobnˇe pˇrijde testov´ an´ı mutexov´ ych funkc´ı jako nadbyteˇcn´e a trochu znepˇrehledˇ nuj´ıc´ı k´ od, ale m˚ uˇze b´ yt dobr´ y n´apad je pouˇz´ıvat pˇri v´ yvoji k´odu, a pˇred odevzd´ an´ım fin´ aln´ı verze pak testy odstranit. Solaris a Linux defaultnˇe pouˇz´ıvaj´ı nastaven´ı normal, FreeBSD pouˇz´ıv´a errorcheck. Pˇr´ıklad: mutexes/not-my-lock.c . • dalˇs´ı typ je PTHREAD MUTEX RECURSIVE kter´e drˇz´ı poˇcet zamˇcen´ı dan´ ym threadem. Ostatn´ı thready dostanou pˇr´ıstup jedinˇe tehdy kdyˇz poˇcet dos´ahne 0. Tento typ mutexu nelze sd´ılet mezi procesy. • K ˇcemu jsou dobr´e rekurzivn´ı mutexy ? Napˇr. m´ame-li knihovn´ı funkci A:foo(), kter´ a z´ısk´ a mutex a zavol´ a B:bar() kter´a ale m˚ uˇze zavolat A:bar(), kter´a se pokus´ı z´ıskat ten sam´ y mutex. Bez rekurzivn´ıch z´amk˚ u by doˇslo k deadlocku. S rekurzivn´ımi mutexy je to ok pokud tyto vol´an´ı prov´ad´ı ten sam´ y thread (jin´ y thread by se zablokoval), pokud si tedy A:foo() a A:bar() jsou vˇedomy, ˇze ten sam´ y thread uˇz m˚ uˇze b´ yt v kritick´e sekci. • typ mutexu se nastavuje pomoc´ı funkce pthread mutex setattrtype. • Chov´ an´ı v jednotliv´ ych pˇr´ıpadech podle typu mutexu shrnuje tabulka: detekuje deadlock v´ıcen´ asobn´e zamyk´ an´ı odemˇcen´ı jin´ ym vl´ aknem odemyk´ an´ı odemˇcen´eho lze sd´ılet mezi procesy
NORMAL N deadlock unedfined undefined Y
ERRORCHECK Y error error error Y
RECURSIVE N/A success error error N
• Inicializace statick´eho mutexu pomoc´ı zm´ınˇen´eho makra nastav´ı pro mutex jeho defaultn´ı atributy. Je moˇzn´e pouˇz´ıt inicializaˇcn´ı funkci i pro staticky alokovan´e mutexy. Pokud je mutex alokov´an dynamicky, je vˇzdy nutn´e pouˇz´ıt pthread mutex init funkci, at’ jiˇz budeme pouˇz´ıvat defaultn´ı atributy nebo ne. • Dynamick´e mutexy m˚ uˇzeme potˇrebovat napˇr´ıklad v situaci, kdy dynamicky alokujeme datovou strukturu, jej´ıˇz souˇc´ast´ı je i mutex, kter´ y sd´ılen´a data struktury chr´ an´ı. I zde, pˇred zavol´an´ım funkce free na datovou strukturu, je potˇreba pouˇz´ıt vol´ an´ı pro zruˇsen´ı mutexu, protoˇze mutex s´am m˚ uˇze m´ıt napˇr´ıklad alokovanou nˇejakou pamˇet’. V´ ysledek zruˇsen´ı zamknut´eho mutexu nen´ı normou definov´ an. • Kop´ırovat mutexy nen´ı normou definov´ano – v´ ysledek takov´e operace z´avis´ı na implementaci. Je moˇzn´e samozˇrejmˇe zkop´ırovat ukazatel na mutex a s t´ımto ukazatelem pak d´ ale pracovat. • Zruˇsen´ı mutexu znamen´ a jeho deinicializaci. 226
Mutexy (2) • pro zamˇcen´ı a odemˇcen´ı mutexu pouˇzijeme vol´an´ı: int pthread mutex lock(pthread mutex t *mx ); a int pthread mutex unlock(pthread mutex t *mx ); • pokud je mutex jiˇz zamˇcen´ y, pokus o zamknut´ı vy´ ust´ı v zablokov´ an´ı vl´ akna. Je moˇzn´e pouˇz´ıt i vol´an´ı: int pthread mutex trylock(pthread mutex t *mx ); . . . kter´e se pokus´ı zamknout mutex, a pokud to nelze prov´est, skonˇc´ı s chybou
• zamknout mutex, pokud ho dan´e vl´akno jiˇz zamˇcen´e m´a, nen´ı korektn´ı. Nˇekdy m˚ uˇze doj´ıt i k self dead-locku, viz pˇredchoz´ı strana. Pokud potˇrebujete odemknout mutex, kter´ y zamknulo jin´e vl´akno, pouˇzijte m´ısto toho bin´arn´ı semafor; opˇet viz pˇredchoz´ı strana. • pˇri vytv´ aˇren´ı aplikace, kde je efektivita kl´ıˇcov´ ym faktorem, je nutn´e rozmyslet, jak, kde a kolik mutex˚ u pouˇz´ıvat. Z knihovny, kter´a nebyla napsan´a pro pouˇzit´ı v aplikac´ıch pouˇz´ıvaj´ıc´ı vl´akna, je moˇzn´e udˇelat thread-safe (viz tak´e strana 247) knihovnu tak, ˇze pˇred zavol´an´ım jak´ekoli funkce knihovny zamknu jeden, tzv. “giant” mutex a po skonˇcen´ı funkce ho odemknu. M´am tedy pouze jeden mutex, ale vl´akna pouˇz´ıvaj´ıc´ı tuto knihovnu ˇcasto sp´ı pˇri ˇcek´ an´ı na pˇr´ıstup do knihovny. Na druhou stranu, pokud zamyk´am pˇr´ıstup ke konkr´etn´ım, mal´ ym sekc´ım, mohu potˇrebovat mnoho mutex˚ u a znaˇcn´e mnoˇzstv´ı ˇcasu tak mohu str´ avit ve funkc´ıch, kter´e mutexy implementuj´ı. Je proto vhodn´e podle situace zvolit rozumn´ y kompromis. Tak´e nezapomeˇ nte, ˇze je moˇzn´e explicitnˇe nastavit, zda chcete error checking u mutex˚ u nebo ne. M˚ uˇze b´ yt dobr´e ˇreˇsen´ı kontrolu zamyk´an´ı/odemyk´an´ı vypnout tehdy, kdyˇz uˇz m´ ate vˇse otestovan´e a vˇeˇr´ıte, ˇze error checking k´od d´ale nepotˇrebujete. • pˇr´ıklady: mutexes/race.c a mutexes/race-fixed.c • mutexy je moˇzn´e nastavit i pro fungovan´ı mezi vl´akny r˚ uzn´ ych proces˚ u. Funguje to tak, ˇze funkce implementuj´ıc´ı mutexy vyuˇzij´ı sd´ılenou pamˇet’, jej´ıˇz odkaz se nastav´ı jako jeden z atribut˚ u mutexu. V´ıce viz manu´alov´a str´anka pro pthread mutexattr setpshared.
227
Podm´ınkov´ e promˇ enn´ e (1) • mutexy slouˇz´ı pro synchronizaci pˇr´ıstupu ke sd´ılen´ ym dat˚ um • podm´ınkov´e promˇenn´e pak k pˇred´an´ı informac´ı o tˇechto sd´ılen´ ych datech – napˇr´ıklad ˇze se hodnota dat zmˇenila • . . . a umoˇzn´ı tak vl´ akna podle potˇreby usp´avat a probouzet • z toho plyne, ˇze kaˇ zd´ a podm´ınkov´ a promˇ enn´ a je vˇ zdy asociov´ ana s pr´ avˇ e jedn´ım mutexem • jeden mutex ale m˚ uˇze b´ yt asociov´an s v´ıce podm´ınkov´ ymi promˇenn´ ymi • spoleˇcnˇe pomoc´ı mutex˚ u a podm´ınkov´ ych promˇenn´ ych je moˇzn´e vytv´ aˇret dalˇs´ı synchronizaˇcn´ı primitiva – semafory, bari´ery, . . .
• jin´ ymi slovy – podm´ınkov´e promˇenn´e se pouˇz´ıvaj´ı v situaci, kdy vl´akno potˇrebuje otestovat stav sd´ılen´ ych dat (napˇr. poˇcet zpr´av ve frontˇe), a dobrovolnˇe se uspat, kdyˇz hledan´eho stavu nebylo dosaˇzeno. Sp´ıc´ı vl´akno je pak probuzeno jin´ ym vl´ aknem, kter´e zmˇenilo stav dat tak, ˇze nastala situace, na kterou prvn´ı vl´ akno ˇcek´ a (tˇreba vloˇzen´ım prvku do fronty). Druh´e vl´akno vzbud´ı prvn´ı vl´ akno zavol´ an´ım k tomu urˇcen´e funkce. Pokud ˇz´adn´e vl´akno v dan´e chv´ıli nesp´ı, probouzec´ı funkce nem´a ˇz´adn´ y efekt – nic se nikde neuloˇz´ı, prostˇe jako by se to nikdy nestalo. • nen´ı to tak, ˇze pˇri deklaraci podm´ınkov´e promˇenn´e, coˇz je pro program´atora zcela transparentn´ı typ, s n´ı asociujete podm´ınku napˇr. “n je vˇetˇs´ı neˇz 7 ”. Podm´ınkovou promˇennou totiˇz m˚ uˇzete pˇrirovnat k praporu nˇejak´e barvy, a pokud jej zvednete, znamen´ a to, ˇze ta vl´akna, kter´a ˇcekaj´ı aˇz touto vlajkou nˇekdo zam´ av´ a nad hlavou, jsou o t´eto situaci informov´ana (= vzbuzena) a mohou se podle toho zaˇr´ıdit. Nˇekter´a vl´akna tak mohou ˇcekat na to, aˇz n bude vˇetˇs´ı neˇz 7, jin´ a mohou ˇcekat pouze na to, aˇz se n jakkoli zmˇen´ı. Je pouze na program´ atorovi, zda pro kaˇzdou konkr´etn´ı situaci pouˇzije jednu podm´ınkovou promˇennou, nebo jednu pro vˇsechny situace dohromady. Pro druhou situaci plat´ı, ˇze vl´ akna ˇcekaj´ıc´ı na n == 7 pak mus´ı vˇzdy n otestovat, protoˇze v´ı, ˇze je vzbuzeno pˇri kazd´e zmˇenˇe ˇc´ıtaˇce n. Pokud nen´ı ˇc´ıtaˇc roven sedmi, znovu se dobrovolnˇe usp´ı. Jak je vˇsak uvedeno d´ale, test je nutn´ e po probuzen´ı prov´ est vˇ zdy, i kdyˇz pro nˇej pouˇz´ıv´ate samostatnou podm´ınkovou promˇennou – m˚ uˇze se st´ at, ˇze syst´em z r˚ uzn´ ych implementaˇcn´ıch d˚ uvod˚ u vzbud´ı vl´ akno uspan´e nad podm´ınkovou promˇennou, aniˇz by to zp˚ usobilo jin´e vl´ akno a tedy aniˇz by stav na kter´ y vl´akno ˇcek´a opravdu nastal.
228
Podm´ınkov´ e promˇ enn´ e (2) int pthread cond init(pthread cond t *cond, const pthread condattr t *attr ); • Inicializuje podm´ınkovou promˇennou cond s atributy attr (nastavuj´ı je funkce pthread condattr ...()), NULL = default. int pthread cond destroy(pthread cond t *cond ); • zruˇs´ı podm´ınkovou promˇennou. int pthread cond wait(pthread cond t *cond, pthread mutex t *mutex ); • ˇcek´ a na podm´ınkov´e promˇenn´e dokud jin´e vl´akno nezavol´a pthread cond signal() nebo pthread cond broadcast().
• je nutn´e, aby po t´e, co vl´ akno zamkne mutex a dˇr´ıve, neˇz vl´akno zavol´a pthread cond wait, otestovat podm´ınku. Pokud tuhle operaci vl´akno neprovede, mohlo by se uspat na neurˇcitou dobu, protoˇze hl´aˇska o splnˇen´ı podm´ınky od jin´eho vl´ akna by proˇsla “bez povˇsimnut´ı”. Jinak ˇreˇceno, nesm´ım se uspat pˇri ˇcek´ an´ı na situaci, kter´a uˇz mezit´ım nastala. Nefunguje to jako sign´ aly, kter´e pro v´ as syst´em drˇz´ı, pokud je m´ate napˇr´ıklad zablokovan´e. Co je d˚ uleˇzit´e je to, ˇze ten test prov´ad´ıte pod ochranou mutexu, tedy si opravdu m˚ uˇzete b´ yt jist´ı dan´ ym stavem vˇec´ı pˇri zavol´an´ı pthread cond wait. • to, ˇze podm´ınkov´e promˇenn´e opravdu funguj´ı je zp˚ usobeno t´ım, ˇze pˇri vstupu do kritick´e sekce vl´ akno zamkne mutex a funkce pthread cond wait pˇ red usp´ an´ım vl´ akna mutex odemkne. Pˇred n´avratem z t´eto funkce se pak mutex opˇet zamkne. M˚ uˇze se tedy st´at, ˇze vl´akno je probuzeno z ˇcek´an´ı nad podm´ınkovou promˇennou, ale nˇejak´ y ˇcas se pak jeˇstˇe usp´ı pˇri pokusu o zamknut´ı mutexu. Nehledejte v tom nic sloˇzit´eho, jde pouze o klasick´e vz´ ajemn´e vylouˇcen´ı proces˚ u nad kritickou sekc´ı.
229
Podm´ınkov´ e promˇ enn´ e (3) int pthread cond signal(pthread cond t *cond ); • probud´ı jeden proces ˇcekaj´ıc´ı na podm´ınkov´e promˇenn´e cond. int pthread cond broadcast(pthread cond t *cond ); • probud´ı vˇsechny procesy ˇcekaj´ıc´ı na podm´ınkov´e promˇenn´e cond. int pthread cond timedwait(pthread cond t *cond, pthread mutex t *mutex, const struct timespec *atime ); • ˇcek´ a na pthread cond signal() nebo pthread cond broadcast(), ale maxim´alnˇe do doby neˇz syst´emov´ y ˇcas dos´ ahne hodnoty dan´e atime.
• jak jiˇz bylo ˇreˇceno, jedna podm´ınkov´a promˇenn´a m˚ uˇze b´ yt pouˇzita pro hl´aˇsen´ı nˇekolika rozd´ıln´ ych situac´ı najednou – napˇr´ıklad pˇri vloˇzen´ı prvku do fronty i pˇri jeho vyjmut´ı. Z toho d˚ uvodu je nutn´e po probuzen´ı otestovat podm´ınku, na kterou se ˇcek´a. Dalˇs´ı vˇec, kter´a z toho vych´az´ı je ta, ˇze v takov´em pˇr´ıpadˇe mus´ıte pouˇz´ıt broadcast. Staˇc´ı si pˇredstavit n´asleduj´ıc´ı situaci – ˇcek´ ate na podm´ınku “zmˇenil se stav fronty”, na kter´e ˇcekaj´ı ˇcten´aˇri i zapisovatel´e (pˇredpokl´ adejme, ˇze jednu frontu pouˇz´ıv´a v´ıce ˇcten´aˇr˚ u i v´ıce zapisovatel˚ u). Pokud po vloˇzen´ı zpr´avy pouze vypust´ıte jednu signalizaci, tak se m˚ uˇze st´ at, ˇze tato signalizace probud´ı jin´eho zapisovatele, kter´ y ale samozˇrejmˇe ˇcek´ a na situaci, kdy ˇcten´aˇr z fronty zpr´avu odebere. T´ım se stane, ˇze ve frontˇe z˚ ustane zpr´ ava, kter´a m˚ uˇze b´ yt vytlaˇcena aˇz dalˇs´ı signalizac´ı. • vl´ akno m˚ uˇze b´ yt probuzeno jin´ ym vl´aknem i v pˇr´ıpadˇe, ˇze je podm´ınkov´a promˇenn´ a sv´ az´ ana pouze s jednou konkr´etn´ı ud´alost´ı, kter´a vˇsak po probuzen´ı vl´ akna jiˇz neplat´ı. Pˇredstavte si, ˇze tˇesnˇe po t´e, kdy jin´e vl´akno zahl´as´ı splnˇen´ı podm´ınky, m˚ uˇze dalˇs´ı vl´akno zamknout mutex a nˇejakou akc´ı, napˇr. vyjmut´ım prvku z fronty, zruˇsit platnost podm´ınky “ve frontˇe je zpr´ava”. To vl´ akno, kter´e je probuzeno, tak najde frontu pr´azdnou. Dalˇs´ı d˚ uvod pro to, ˇze podm´ınkov´e promˇenn´e je nutn´e vˇ zdy testovat v cyklu. • v ˇr´ıdk´ ych pˇr´ıpadech je moˇzn´e, ˇze se vl´akno probud´ı a podm´ınka nen´ı platn´a i d´ıky konkr´etn´ı implementaci. Z toho opˇet vypl´ yv´a nutnost pouˇzit´ı cyklu. • v parametru abstime funkce pthread cond timedwait se pˇred´av´a absolutn´ı ˇcas, tj. timeout vyprˇs´ı, kdyˇz syst´emov´ y ˇcas dos´ahne hodnoty vˇetˇs´ı nebo rovn´e abstime. Pro absolutn´ı ˇcas bylo rozhodnuto proto, aby program´ator nemusel
230
pˇri pˇr´ıpadn´ ych probuzen´ıch, kdy n´aslednˇe zjist´ı, ˇze dan´a podm´ınka neplat´ı, pˇrepoˇc´ıt´ avat ˇcasov´ y rozd´ıl.
Pouˇ zit´ı podm´ınkov´ ych promˇ enn´ ych pthread cond t cond; pthread mutex t mutex; ... pthread mutex lock(&mutex); while (!podminka(data)) pthread cond wait(&cond, &mutex); process data(data, ...); pthread mutex unlock(&mutex); ... pthread mutex lock(&mutex); produce data(data, ...); pthread cond signal(&cond); pthread mutex unlock(&mutex);
• Prvn´ı kus k´ odu ˇcek´ a na zmˇenu podm´ınky. Pokud k n´ı dojde, data zmˇenila a tedy mohou b´ yt zpracov´ ana. Druh´ y kus k´odu (spuˇstˇen´ y v jin´em vl´aknˇe) data pˇripravuje ke zpracov´ an´ı. Jakmile jsou pˇripravena, zasignalizuje konzumentovi. • pro zopakov´ an´ı – ke kaˇzd´e podm´ınkov´e promˇenn´e je potˇreba m´ıt jeˇstˇe mutex. • funkce pthread cond wait atomicky odemkne mutex a usp´ı vl´akno. Kdyˇz je vl´ akno probuzeno, nejprve se znovu zamkne mutex (tj. toto zamknut´ı se provede v r´ amci pˇr´ısluˇsn´e implementace podm´ınkov´ ych promˇenn´ ych!) a teprve pak se vol´ an´ı pthread cond wait vr´at´ı. • kdyˇz signalizujeme, ˇze se nˇeco zmˇenilo, neznamen´a to jeˇstˇe, ˇze po zmˇenˇe bude platit podm´ınka. Nav´ıc, jak bylo nˇekolikr´at zd˚ uraznˇeno, pthread cond wait m˚ uˇze vr´ atit, i kdyˇz ˇz´ adn´e jin´e vl´akno nezavolalo pthread cond signal ani pthread cond broadcast. Dalˇs´ı d˚ uvod proˇc je potˇreba znovu otestovat podm´ınku a pˇr´ıpadnˇe obnovit ˇcek´an´ı. • vˇsimnˇete si, ˇze odemknut´ı z´amku n´asleduje aˇz po signalizaci podm´ınky. Je velmi jednoduch´e naj´ıt situaci, kdy byste se dostali do probl´em˚ u, pokud byste signalizovali mimo kritickou sekci. Pˇredstavte si, ˇze dvˇe vl´akna pˇridaj´ı prvek do fronty. Prvn´ı zasignalizuje a t´ım probud´ı ˇcten´aˇre, kter´ y vyjme jeden prvek a zpracov´ av´ a ho. Bˇehem toho dojde k druh´e signalizaci, kter´a protoˇze je mimo kritickou sekci, nastane v dobˇe, kdy ˇcten´aˇr jeˇstˇe zpracov´av´a prvn´ı prvek a
231
tedy se vlastnˇe “ztrat´ı”. N´ aslednˇe se ˇcten´aˇr usp´ı pthread cond wait, a druh´ y prvek tak ve frontˇe z˚ ustane, dokud jin´e vl´akno nevloˇz´ı prvek jin´ y (a ten bude ve frontˇe viset podobn´ ym zp˚ usobem). • pˇr´ıklad: cond-variables/queue-simulation.c
Read-write z´ amky (1) int pthread rwlock init(pthread rwlock t *l, const pthread rwlockattr t *attr ); • vytvoˇr´ı z´ amek s atributy podle attr (nastavuj´ı se funkcemi pthread rwlockattr ...(), NULL = default) int pthread rwlock destroy(pthread rwlock t *l ); • zruˇs´ı z´ amek int pthread rwlock rdlock(pthread rwlock t *l ); int pthread rwlock tryrdlock(pthread rwlock t *rwlock ); • zamkne z´ amek pro ˇcten´ı (v´ıce vl´aken m˚ uˇze drˇzet z´amek pro ˇcten´ı), pokud m´ a nˇekdo z´amek pro z´apis, usp´ı volaj´ıc´ı vl´akno (rdlock()) resp. vr´ at´ı chybu (tryrdlock()).
• nen´ı souˇc´ ast´ı POSIXov´ ych vl´aken z POSIX.1c, ale POSIX.1j rozˇs´ıˇren´ı, nazvan´e “advanced realtime extensions”. • najednou m˚ uˇze m´ıt z´ amek bud’ nˇekolik vl´aken zamˇceno pro ˇcten´ı nebo maxim´ alnˇe jedno vl´ akno zamˇceno pro z´apis (a nikdo pro ˇcten´ı). • read-write z´ amky jsou semanticky podobn´e zamyk´an´ı soubor˚ u pomoc´ı funkce fcntl.
232
Read-write z´ amky (2) int pthread rwlock wrlock(pthread rwlock t *rwlock ); • zamkne z´ amek pro z´ apis, pokud m´a nˇekdo z´amek pro ˇcten´ı nebo z´ apis, ˇcek´ a. int pthread rwlock trywrlock(pthread rwlock t *rwlock ); • jako pthread rwlock wrlock(), ale kdyˇz nem˚ uˇze zamknout, vr´ at´ı chybu. int pthread rwlock unlock(pthread rwlock t *rwlock ); • odemkne z´ amek
• zvl´ aˇstnost: pokud vl´ akno ˇcekaj´ıc´ı na z´amek dostane sign´al, po n´avratu z handleru se vˇzdy pokraˇcuje v ˇcek´an´ı, tj. nenastane chyba EINTR. Takto se chovaj´ı i mutexy a podm´ınkov´e promˇenn´e.
233
Atomick´ e aritmetick´ e operace • pro architektury, kde operace sˇc´ıt´an´ı nen´ı atomick´a • atomic add(3c) od Solarisu 10, atomic(3) na OS X • v´ yraznˇe rychlejˇs´ı neˇz jin´e mechanismy pro z´ısk´an´ı exkluzivn´ıho pˇr´ıstupu • sada vol´ an´ı pro r˚ uzn´e celoˇc´ıseln´e typy, napˇr´ıklad na Solarisu: void atomic add int(volatile uint t *target, int delta); void atomic add 8(volatile uint8 t *target, int8 t delta); . . . a dalˇs´ı
• Pˇr´ıklad race/atomic-add.c demonstruje probl´em se soubˇehem pˇri sˇc´ıt´an´ı a jeho moˇzn´ a ˇreˇsen´ı. Program spust´ı dvˇe vl´akna, kaˇzd´e vl´akno pracuje se stejnou glob´ aln´ı promˇennou x, do kter´e v cyklu postupnˇe pˇriˇcte ˇc´ısla od jedn´e do velikosti parametru arg, kter´ y program dostane na pˇr´ıkazov´e ˇr´adce. Vl´ akna bˇeˇz´ı paralelnˇe, kaˇzd´e z nich prov´ad´ı toto: for (i = 1; i < arg; ++i) x = x + i; Pot´e se sˇc´ıt´ an´ı pro kontrolu provede v hlavn´ım vl´aknu, a dvojn´asobek (mˇeli jsme dva thready) se porovn´ a s hodnotou glob´aln´ı promˇenn´e x. Pokud nejsou v´ ysledky stejn´e, doˇslo k soubˇehu (pˇreteˇcen´ı promˇenn´e m˚ uˇzeme v t´eto situaci zcela ignorovat). V´ ysledky a ˇcasy bˇehu se markantnˇe liˇs´ı pro situace, kdy program pouˇzil obyˇcejn´e sˇc´ıt´ an´ı, funkci atomic add int() a zamyk´an´ı pomoc´ı mutex˚ u. Je vidˇet nˇekolikan´ asobn´ y rozd´ıl v dobˇe bˇehu mezi pouˇzit´ım atomic add a mutex˚ u, zejm´ena na procesorech s hardwarov´ ymi thready (jako napˇr. UltraSPARC T1). • Atomick´e aritmetick´e rutiny lze d´ıky tomu, ˇze druh´ y argument je cel´e ˇc´ıslo, pouˇz´ıt i k atomick´emu odeˇc´ıt´an´ı. Podobnˇe existuj´ı na dalˇs´ı atomick´e rutiny pro bitov´e operace AND a OR, pro pˇriˇrazen´ı hodnoty atd.
234
Bari´ era • bari´era (barrier ) je zp˚ usob, jak udrˇzet ˇcleny skupiny pohromadˇe • vˇsechna vl´ akna ˇcekaj´ı na bari´eˇre, dokud ji nedos´ahne posledn´ı vl´ akno; pak mohou pokraˇcovat • typick´e pouˇzit´ı je paraleln´ı zpracov´an´ı dat int pthread barrier init(pthread barrier t *barrier, attr, unsigned count ); • inicializuje bari´eru pro count vstup˚ u do n´ı int pthread barrier wait(pthread barrier t *barrier ); • zablokuje se dokud nen´ı zavol´ana count-kr´at int pthread barrier destroy(pthread barrier t *barrier ); • zruˇs´ı bari´eru
• API pro bari´ery je definovan´e od SUSv3, pod´ıvejte se napˇr´ıklad na pthread barrier init, je moˇzn´e je ale jednoduˇse vytvoˇrit pomoc´ı mutex˚ u a podm´ınkov´ ych promˇenn´ ych. • Pozor na to, ˇze bari´ery jsou nepovinnou ˇc´ast´ı POSIXu (patˇr´ı do Advanced realtime threads) a tedy i syst´em certifikovan´ y na SUS je nemus´ı implementovat, coˇz je pˇr´ıpad Mac OS X (10.10). • bari´eru m˚ uˇzete vyuˇz´ıt napˇr. v situaci, kdy mezi jednotliv´ ymi f´azemi zpracov´ an´ı je potˇreba prov´est jistou inicializaci, vl´akna pˇred n´ı tedy na sebe vˇzdy mus´ı poˇckat, protoˇze inicializace dalˇs´ı f´aze m˚ uˇze zaˇc´ıt aˇz tehdy, kdy skonˇc´ı f´ aze pˇredchoz´ı. Pˇr´ıklad pthreads/pthread-barrier.c ukazuje pouˇzit´ı bari´ery pro nˇekolik f´ az´ı zpracov´an´ı dat. • podm´ınka pro bari´eru je napˇr´ıklad hodnota ˇc´ıtaˇce rovnaj´ıc´ı se nule. Kaˇzd´e vl´ akno, kter´e dos´ ahne bari´ery, sn´ıˇz´ı ˇc´ıtaˇc, kter´ y je na zaˇc´atku inicializov´an na poˇcet vl´ aken. Pokud vl´ akno po dekrementov´an´ı ˇc´ıtaˇce zjist´ı, ˇze jeˇstˇe nen´ı roven nule, usp´ı se na podm´ınkov´e promˇenn´e. Pokud dan´e vl´akno je t´ım vl´ aknem, kter´e sn´ıˇz´ı ˇc´ıtaˇc na nulu, m´ısto zavol´an´ı pthread cond wait poˇsle broadcast, kter´ y n´ aslednˇe probud´ı vˇsechna vl´akna sp´ıc´ı na bari´eˇre (pthread cond signal zde nestaˇc´ı, chcete probudit vˇsechna vl´akna, ne jen jedno!). Pˇred spuˇstˇen´ım dalˇs´ı f´aze zpracov´an´ı se ˇc´ıtaˇc reinicializuje na p˚ uvodn´ı hodnotu. I zde je nutn´e ˇreˇsit r˚ uzn´e probl´emy, napˇr´ıklad nen´ı moˇzn´e jen tak reinicializovat ˇc´ıtaˇc pot´e, co bari´ery dos´ahne posledn´ı vl´akno, protoˇze jak jiˇz v´ıme, vl´ akna po probuzen´ı z pthread cond wait mus´ı vˇzdy otestovat, zda ˇc´ıtaˇc je opravdu nulov´ y a pokud nen´ı, opˇet se usp´ı. Takˇze by se v´am mohlo
235
st´ at, ˇze by se probudila jen nˇekter´a vl´akna, nebo taky ˇz´adn´a. Je nutn´e zresetovat ˇc´ıtaˇc aˇz po probuzen´ı posledn´ıho vl´akna. Jak byste to ˇreˇsili?
Semafory • semafory poch´ az´ı z POSIX-1003.1b (real-time extensions) • jm´ena funkc´ı nezaˇc´ınaj´ı pthread , ale sem (sem init, sem post, sem wait, . . . ) • je moˇzn´e je pouˇz´ıt s vl´ akny
• funkce pro semafory se drˇz´ı klasick´e UNIXov´e s´emantiky – pˇri chybˇe vracej´ı -1 a nastav´ı errno
236
Typick´ e pouˇ zit´ı vl´ aken • pipeline – kaˇzd´e z vl´ aken prov´ad´ı svoji operaci nad daty, kter´a se postupnˇe pˇred´ avaj´ı mezi vl´akny – kaˇzd´e vl´ akno typicky prov´ad´ı jinou operaci . . . zpracov´ an´ı obr´ azku, kde kaˇzd´e vl´akno provede jin´ y filtr • work crew – vl´ akna prov´ adˇej´ı stejnou operaci, ale nad jin´ ymi daty . . . zpracov´ an´ı obr´ azku dekompozic´ı – kaˇzd´e vl´akno zpracov´av´a jinou ˇc´ ast obr´ azku, v´ ysledkem je spojen´ı zpracovan´ ych dat ze vˇsech vl´ aken; zde se hod´ı ˇreˇsen´ı s bari´erou • client – server
• dan´e rozdˇelen´ı je jen orientaˇcn´ı, pouˇzit´ı vl´aken je samozˇrejmˇe neomezen´e, toto jsou asi ty tˇri nejˇcastˇejˇs´ı pouˇzit´ı • V pˇr´ıpadˇe modelu klient – server zpracov´av´a kaˇzd´e vl´akno jeden poˇzadavek od jednoho klienta.
237
Thread-safe versus reentrantn´ı • thead-safe znamen´ a, ˇze k´od m˚ uˇze b´ yt vol´an z v´ıce vl´aken najednou bez destruktivn´ıch n´asledk˚ u – do funkce, kter´ a nebyla navrˇzena jako thread-safe, je moˇzn´e pˇridat jeden z´ amek – na zaˇc´atku funkce se zamkne, na konci odemkne – tento zp˚ usob ale samozˇrejmˇe nen´ı pˇr´ıliˇs efektivn´ı • slovem reentrantn´ı se typicky mysl´ı, ˇze dan´a funkce byla navrˇzena s pˇrihl´ednut´ım na existenci vl´aken – . . . tedy ˇze funkce pracuje efektivnˇe i ve v´ıcevl´aknov´em prostˇred´ı – takov´ a funkce by se mˇela vyvarovat pouˇzit´ı statick´ ych dat a pokud moˇzno i prostˇredk˚ u pro synchronizaci vl´aken, protoˇze bˇeh aplikace zpomaluj´ı
• z v´ yˇse uveden´eho vypl´ yv´ a, ˇze thread-safe je slabˇs´ı vlastnost neˇz reentrantn´ı. Napsat thread-safe funkci lze s pouˇzit´ım synchronizaˇcn´ıch primitiv; pˇreps´an´ı existuj´ıc´ı funkci tak, aby byla reentrantn´ı vyˇzaduje mnohem v´ıce invence. • reentrantn´ı funkce jsou tak´e jedin´e funkce bezpeˇcnˇe pouˇziteln´e v signal handlerech. • v dneˇsn´ı dobˇe thread-safe vˇetˇsinou znamen´a reentrantn´ı, tj. funkce jsou pˇreps´ any tak, aby pracovaly efektivnˇe i s vl´akny, je ale dobr´e vˇedˇet, ˇze nˇekdy to m˚ uˇze vyjadˇrovat rozd´ıl. • o zamyk´ an´ı knihoven viz tak´e strana 235. • existuje mnoˇzstv´ı funkc´ı, kter´e mohou b´ yt thread-safe, ale nikoliv reentrant, napˇr. gethostbyname. Bˇeˇznˇe tato funkce pouˇz´ıv´a statickou promˇennou, kter´a se znovu pouˇzije pˇri kaˇzd´em vol´an´ı, takˇze je pro pouˇzit´ı v multithreadov´em prostˇred´ı nevhodn´e - nen´ı thread-safe. Nicm´enˇe, na FreeBSD 6.0 je tato funkce implementovan´ a tak, ˇze pouˇz´ıv´a implicitnˇe thread-local storage pro uloˇzen´ı v´ ystupn´ıch dat a t´ım p´adem je thread safe. To ji jeˇstˇe ale neˇcin´ı u ´plnˇe bezpeˇcnou k pouˇzit´ı (nemluvˇe o tom, ˇze program , kter´ y se spol´eh´a na takov´e chov´ an´ı nen´ı portabiln´ı), viz pˇr´ıklad reentrant/gethostbyname.c . O nˇeco lepˇs´ı je pouˇz´ıt reentrantn´ı verzi t´eto funkce gethostbyname r (pokud je na dan´em syst´emu k dispozici), u kter´e lze specifikovat adresu, kam m´ a ukl´ adat sv˚ uj v´ ystup, ˇc´ımˇz se st´av´a reentrantn´ı. Daleko nejlepˇs´ı ˇreˇsen´ı je pouˇz´ıt standardn´ı funkci getaddrinfo (viz strana 206), kter´a je sama o sobˇe reentrantn´ı.
238
• pˇr´ıklad: reentrant/inet ntoa.c - tady je vidˇet, ˇze ani takto napsan´a funkce v´ am nepom˚ uˇze pokud je volan´a dvakr´at v r´amci jednoho vol´an´ı printf. Pokaˇzd´e vrac´ı pointer se stejnou adresou (v jednom threadu), kterou si printf pouze poznamen´ a a pˇri fin´ aln´ım tisku tedy vyp´ıˇse reprezentaci posledn´ı adresy, se kterou byla inet ntoa vol´ana. Na Solarisu je to vidˇet pomoc´ı: truss -t\!all -u libnsl::inet_ntoa ./a.out • Na Solarisu obsahuj´ı manu´ alov´e str´anky knihovn´ıch funkc´ı poloˇzku MT-level v sekci ATTRIBUTES, kter´ a ud´av´a zda je moˇzn´e funkci pouˇz´ıt v multithreadov´em prostˇred´ı a pˇr´ıpadnˇe s jak´ ymi omezen´ımi. Tyto u ´rovnˇe jsou pops´any v manu´ alov´e str´ ance attributes(5).
Nepˇ renositeln´ a vol´ an´ı • nepˇrenositeln´ a vol´ an´ı konˇc´ı ˇretˇezcem np (non-portable) a jednotliv´e syst´emy si takto definuj´ı vlastn´ı vol´an´ı • FreeBSD – pthread set name np(pthread t tid, const char *name) → umoˇzn ˇuje pojmenovat vl´akno • Solaris – pthread cond reltimedwait np(...) → jako timedwait, ale ˇcasov´ y timeout je relativn´ı • OpenBSD – int pthread main np(void) → umoˇzn ˇuje zjistit, zda volaj´ıc´ı vl´akno je hlavn´ı (= main())
• Tyto informace jsou pro zaj´ımavost, abyste vˇedˇeli, ˇze se s podobn´ ymi vˇecmi m˚ uˇzete setkat. Nepˇrenositeln´a vol´an´ı by se mˇela pouˇz´ıvat sp´ıˇse pro lad´ıc´ı u ´ˇcely. Nikdy nev´ıte, kdo bude potˇrebovat v´aˇs k´od spustit na jin´em syst´emu, coˇz se stane typicky a neˇcekanˇe po t´e, co zrovna opust´ıte vaˇsi spoleˇcnost a nem´ ate jiˇz ˇcas to opravit. • Zjistit, jak´ a nepˇrenositeln´ a vol´an´ı v´aˇs syst´em poskytuje je jednoduch´e, tˇreba pomoc´ı apropos np, nebo o nˇeco hrubˇeji (aplikujte na sv˚ uj syst´em podle lokace manu´ alov´ ych str´ anek): $ cd /usr/share/man $ find . -name ’*_np\.*’ ./man3c/mq_reltimedreceive_np.3c 239
./man3c/mq_reltimedsend_np.3c ./man3c/posix_spawnattr_getsigignore_np.3c ./man3c/posix_spawnattr_setsigignore_np.3c ./man3c/pthread_cond_reltimedwait_np.3c ./man3c/pthread_key_create_once_np.3c ./man3c/pthread_mutex_reltimedlock_np.3c ./man3c/pthread_rwlock_reltimedrdlock_np.3c ./man3c/pthread_rwlock_reltimedwrlock_np.3c ./man3c/sem_reltimedwait_np.3c
240
ChangeLog 2014-12-17 pozn´ amky k rekurzivn´ım mutex˚ um, p233 2014-12-16 pˇrid´ an pˇr´ıklad na velikost stacku u pthread˚ u pthreads/pthread-stack-overflow.c , p222 2014-12-16 u ´prava k´ odu TCP klienta a rozˇs´ıˇreny pozn´ amky o hyb´ ach pro TCP server a klient, p208, p209 2014-12-15 zobecnˇ en slajd na stranˇ e 242 o atomick´ ych aritmetick´ ych operac´ıch, pˇr´ıklad race/atomic-add.c pˇreps´ an aby fungoval jak na Solarisu, tak OS X. 2014-12-15 pozn´ amka k IPv4-mapped IPv6 adres´ ach u pˇr´ıkladu TCP serveru, p208. 2014-12-15 vyjmut slajd o legacy/obsolete funkc´ıch gethostbyname a gethostbyaddr. 2014-12-12 r˚ uzn´ e pozn´ amky k socket API 2014-02-13 pˇrid´ an pˇr´ıklad pthreads/pthread-barrier.c a vysvˇ etlen´ı API k POSIX bari´ er´ am, p243. 2013-11-28 pˇrid´ an pˇr´ıklad file-locking/lockf.c a pozn´ amky k mandatory lockingu, p174. 2013-11-18 v´ıce pozn´ amek k OS X, pˇr´ıklad session/getppid.c 2013-11-12 v´ıce k mmap(), pˇr´ıklad mmap/aligned.c 2013-11-11 podrobnˇ ejˇs´ı vysvˇ etlen´ı k priorit´ am proces˚ u 2013-10-21 pozn´ amka k rekurzivn´ımu maz´ an´ı adres´ aˇre u knihovn´ıho vol´ an´ı rmdir 2013-05-31 pˇrid´ an pˇr´ıklad inetd/accept-rw.c a v´ıce informac´ı k inetd, p214. 2013-01-01 v´ıce detail˚ u k fungov´ an´ı k pthread atfork, pthreads/atfork.c , p229. 2012-12-18 pozn´ amka k vytv´ aˇren´ı thread˚ u v cyklu, p221 2012-12-05 pˇrid´ an pˇr´ıklad resolving/getbyname.c , p205 a koment´ aˇr k funkc´ım prohled´ avaj´ıc´ım datab´ aze sluˇ zeb a protokol˚ u 2012-11-28 pˇrid´ an pˇr´ıklad race/fcntl-fixed-race.c , p175 2012-11-21 pˇrid´ an pˇr´ıklad signals/sigqueue.c 2012-11-14 pˇrid´ an pˇr´ıklad mmap/lseek.c k mmap 2012-01-30 priklady jsou nyni odkazy na web 2012-01-22 pˇrid´ any pozn´ amky k tread-safe/reentrant funkc´ım a odkazy na pˇr´ıklady reentrant/gethostbyname.c , reentrant/inet ntoa.c , p247. 2012-01-16 slajd o bari´ eˇre a semaforech rozdˇ elen na dva, p243 2012-01-16 pˇrid´ ana tabulka shrnuj´ıc´ı chov´ an´ı jednotliv´ ych typ˚ u mutex˚ u, p234. 2012-01-13 pozn´ amka o zp˚ usobu implementace POSIX threads a knihovn´ ach, kter´ e toto API nab´ızej´ı, p219 2012-01-13 pozn´ amka o d˚ uleˇ zitosti funkce scheduleru pˇri implementaci synchronizaˇ cn´ıch primitiv 2012-01-11 pˇrid´ an odkaz na pˇr´ıklad pthreads/set-detachstate.c 2012-01-11 rozveden popis pouˇ zit´ı funkce pthread atfork, p229. 2012-01-10 pˇreformulov´ an koment´ aˇr k slajdu s pˇr´ıkladem pro atomic add, p242. 2012-01-10 pˇr´ıklad na neblokuj´ıc´ı connect pˇresunut za vol´ an´ı getsockopt a select. 2012-01-05 pˇreps´ any slajdy s pˇr´ıklady pro TCP klient a server tak aby nebyly specifick´ e pro ˇ z´ adnou address family 2012-01-04 pˇrid´ an pˇr´ıklad resolving/getnameinfo.c , doplnˇ eny slajdy pro funkce getnameinfo a getaddrinfo na str´ ank´ ach 208 a 206. 2012-01-03 pˇr´ıklady pro vyˇsˇs´ı granularitu sleep byly pˇresunuty do samostatn´ eho adres´ aˇre resolving 2011-12-22 upˇresnˇ en´ı k wildcard soket adres´ am u vol´ an´ı bind an´ı setsockopt 2011-12-21 pˇrid´ an pˇr´ıklad tcp/reuseaddr.c pro demonstraci parametru SO REUSEADDR vol´
241
2011-11-24 nov´ y slajd o setpgid a setsid 2011-11-23 pozn´ amky k form´ atu spustiteln´ ych soubor˚ u a.out 2011-11-09 pozn´ amky k pˇresmˇ erov´ an´ı a tabulce deskriptor˚ u 2011-11-07 Pˇrid´ any PDF bookmarks druh´ eho ˇr´ adu k definic´ım funkc´ı a d˚ uleˇ zit´ ym slajd˚ um. 2011-11-07 Ot´ azka k fstat 2011-11-02 Doplnˇ eny informace o /etc/passwd a /etc/shadow. Pˇrid´ an slajd p 72 s funkcemi pro z´ısk´ av´ an´ı u ´ daj˚ u o uˇ zivatel´ıch/skupin´ ach (getpwent apod.) 2011-10-26 Zm´ınˇ en chroot, kosmetick´ e opravy v sekci o API pro soubory. enn´ e prostˇred´ı LD LIBRARY PATH. 2011-10-19 Pˇrid´ an pˇr´ıklad exit/exit.c a informace o promˇ 2011-10-18 Aktualizace informac´ı o souˇ casn´ ych unixov´ ych syst´ emech, pˇrid´ ano p´ ar detail˚ u k historii. ´ 2011-10-17 Upravy stylu (linky, pˇr´ıklady, PDF bookmarks). 2011-01-08 Nov´ y pˇr´ıklad mutexes/not-my-lock.c , p234. 2010-10-23 Pozn´ amka o funkci znaku ‘-’ ve jm´ enˇ e shellu v argv[0], p51. 2010-10-21 Nov´ e pˇr´ıklady read/lseek.c , p106, read/big-file.c , p106, read/redirect.c , p109, stat/filetype.c a stat/stat.c , p113. 2010-10-20 Nov´ y pˇr´ıklad read/cat.c , p103. 2010-10-15 Nov´ y pˇr´ıklad err/err.c , p67. 2010-03-07 Upˇresnˇ en´ı informac´ı ohlednˇ e zachyt´ av´ an´ı synchronn´ıch sign´ al˚ u, p156 a p231, a nov´ y pˇr´ıklad signals/catch-SIGSEGV.c . 2010-03-06 Pˇrid´ any informace o sigwait vol´ an´ı vzhledem k synchronn´ım sign´ al˚ um, p231. Nov´ y pˇr´ıklad pthreads/sigwait-with-sync-signals.c . 2010-02-28 Drobn´ eu ´pravy. 2010-01-11 V´ıce o errno pˇri pouˇ zit´ı s vl´ akny, p220. 2009-12-13 Nov´ e pˇr´ıklady select/busy-waiting.c , p210, select/write-select.c , p212 a tcp/addresses.c , p198. 2009-12-06 Nov´ y pˇr´ıklad signal/signal-vs-sigaction.c , p161, zm´ınˇ eno makro SOMAXCONN. 2009-10-11 Nov´ e pˇr´ıklady odkazovan´ ezˇ c´ asti pro prvn´ı pˇredn´ aˇsku. 2009-10-06 Drobn´ e fixy, pˇrid´ an´ı Vl´ adi Kotala jako vyuˇ cuj´ıc´ıho tohoto pˇredmˇ etu pro letoˇsn´ı rok. 2009-02-13 Zm´ınˇ eno vol´ an´ı remove(3), p120. 2009-02-06 Nov´ y pˇr´ıklad tcp/non-blocking-connect.c , p211. 2009-01-14 Nov´ e pˇr´ıklady z adres´ aˇre main, p52, lib-abi/abi-main.c , p39, exec/exec-date.c , p138, getaddrinfo/gethostbyname.c , getopt/getopts.sh a getopt/getopt.c , p58. 2009-01-13 Nov´ e slajdy getaddrinfo, p206, getnameinfo, p208, nov´ e pˇr´ıklady cond-variables/queue-simulation.c , p240, getaddrinfo/getaddrinfo.c , p206. 2009-01-10 Nov´ y slajd, kter´ y uv´ ad´ı celou sekci o vl´ aknech, p219, a obsahuje nˇ ekter´ e obecn´ e informace, kter´ e by jinak nebyly zd˚ uraznˇ eny. Nov´ e pˇr´ıklady mutexes/race.c a mutexes/race-fixed.c , p235. 2009-01-06 Pˇrid´ ana zm´ınka o privilege separation, p217, pˇrid´ any pˇr´ıklady pthreads/setjmp.c , p218, pthreads/wrong-use-of-arg.c a pthreads/correct-use-of-arg.c , p221, pthreads/int-as-arg.c , p221, pthreads/pthread-join.c a pthread-detach-join.c , p224, pthreads/pthread-cancel.c , p225, pthreads/fork.c , pthreads/fork-not-in-main.c , a pthreads/forkall.c , p229, a pthreads/sigwait.c , p231.
242
2008-12-16 Nov´ e slajdy o adresn´ıch struktur´ ach pro s´ıt’ovou komunikaci, p194, a o pˇrevodech adres z bin´ arn´ı reprezentace na ˇretˇ ezce a naopak, p197. Pˇrid´ any pˇr´ıklady na TCP, tcp/up-to-listen-only.c , p196, tcp/tcp-sink-server.c , p197, tcp/connect.c , p198, a UDP, udp/udpclient.c , aci s v´ıce deskriptory, select/select.c , p201, udp/udp-server.c , p200. Pˇr´ıklad pro pr´ p211, a alternativn´ı pouˇ zit´ı vol´ an´ı poll v select/poll-sleep.c , p213. 2008-12-09 Pˇr´ıklady file-locking/lock-unlock.c , p173, file-locking/fcntl-locking.c , p175, semaphores/sem-fixed-race.c , p183. 2008-12-01 Pˇr´ıklad dyn-lib/rtld next.c , p151 2008-11-30 Pˇr´ıklady signals/killme.c , p154, signals/catch-SIGKILL.c , p158, signals/ctrl-z.c , p158, signals/sa sigaction.c , p161, race/race.c , p167. 2008-11-28 Pozn´ amka k return (-1), p52. Doplnˇ en´ı pozn´ amek k dynamick´ emu linkov´ an´ı. Nov´ e pˇr´ıklady: dyn-lib/ld-lazy.c a dyn-lib/dlopen.c , p150, signals/alarm.c , p162, signals/sigwait.c , p165. a poloˇ zka struktury dirent, p119. Nov´ y slajd s uk´ azkou konkr´ etn´ıho 2008-11-22 d type, nepˇrenositeln´ adresov´ eho prostoru procesu, p124, a k tomu pˇr´ıklad pmap/proc-addr-space.c . 2008-11-19 Nov´ y pˇr´ıklad wait/wait.c , p141, a doplnˇ en´ı pozn´ amek ke stejn´ emu slajdu. Nov´ e pˇr´ıklady fork/fork.c a fork/vfork.c , p136. 2008-11-11 Zaˇ cal jsem postupnˇ e pˇrid´ avat odkazy na pˇr´ıklady k jednotliv´ ym slajd˚ um. Pokud je tedy v pozn´ amk´ ach uvedeno ,,pˇr´ıklad: readdir/readdir.c ”, znamen´ a to, ˇ ze pˇr´ıklad najdete zde: http://mff.devnull.cz/pvu/src/readdir/readdir.c 2008-11-06 Pˇrid´ any r˚ uzn´ e pozn´ amky k API pro pr´ aci se soubory. Nov´ y slajd pˇredstavuj´ıc´ı funkce err(3), p67. 2008-10-21 Pˇrid´ an pˇr´ıklad bin´ arn´ı nekompatibility OpenSSL knihoven r˚ uzn´ ych verz´ı v dneˇsn´ıch syst´ emech, p41. 2008-10-04 Pˇrid´ any r˚ uzn´ e pozn´ amky a doplnˇ en´ı pˇri kontrole pˇred prvn´ı pˇredn´ aˇskou nov´ eho semestru, napˇr´ıklad uk´ azka na nespr´ avn´ e pouˇ zit´ı execl vol´ an´ı, p138, nebo upozornˇ en´ı na to, ˇ ze jak´ ekoli ˇreˇsen´ı pro synchronizaci pomoc´ı aktivn´ıho ˇ cek´ an´ı nen´ı pˇri zkouˇsce akceptov´ ano, p173, 210. 2008-09-05 Zm´ınˇ ena vol´ an´ı setjmp a longjmp, p218 2008-03-30 Pˇrid´ any informace k m´ od˚ um RTLD LAZY a RTLD NOW pro vol´ an´ı dlopen, p150. 2008-03-23 Pˇrid´ ana pozn´ amka k maz´ an´ı soubor˚ u a sticky bitu, p73, v´ıce informac´ı k SIGKILL, p157, k async-signal-safe funkc´ım, p160, k aktivn´ımu ˇ cek´ an´ı pˇri synchronizaci, p171, pˇr´ıklad na deadlock, p177. 2008-03-01 Drobn´ a doplnˇ en´ı k sign´ al˚ um. Pˇrid´ ana pozn´ amka o funkc´ıch sigwaitinfo(3) a sigtimedwait(3), p165. 2008-02-29 Pˇrid´ an samostatn´ y slajd o POSIXu, p16. 2008-02-17 Mnoho oprav r˚ uzn´ ych chyb a jin´ ych nepˇresnost´ı. Pˇrid´ ana informace o POSIX.4, p161. Dˇ ekuju Martinovi Horˇ ciˇ ckovi (martin at horcicka dot eu) za feedback. 2008-01-25 Mal´ e opravy v sazbˇ e textu, pˇrid´ an´ı kˇr´ıˇ zov´ ych odkaz˚ u, drobn´ a doplnˇ en´ı pozn´ amek na r˚ uzn´ ych m´ıstech apod. 2007-12-29 Doplnˇ en´ı definice kritick´ e sekce (p169), pˇrid´ an u ´vodn´ı slajd k synchronizaci vl´ aken (p232), doplnˇ eny informace o r˚ uzn´ ych typech mutex˚ u (p233), doplnˇ eny pozn´ amky k podm´ınkov´ ym promˇ enn´ ym. 2007-12-12 Pˇrid´ ano mnoho nov´ ych pozn´ amek ke slajd˚ um o s´ıt’ov´ e komunikaci. 2007-12-09 Pˇrid´ an slajd pro atomic add(3c) v sekci synchronizace vl´ aken, p242. 2007-11-21 R˚ uzn´ a doplnˇ en´ı k sign´ al˚ um na slajdech i v pozn´ amk´ ach. 2007-11-18 Opraveny dalˇs´ı v´ yskyty znaku ‘0’ m´ısto ˇretˇ ezce ‘len’, tentokr´ at na slajdech k mmap(2) vol´ an´ı. 2007-11-07 Pˇrid´ an konkr´ etn´ı pˇr´ıklad k pozn´ amk´ am o soft updates.
243
2007-10-23 Nov´ y slajd ,,API vers ABI”, p38, a doplnˇ en´ı pozn´ amek k z´ apisu do pojmenovan´ e roury, p104. 2007-10-15 Doplnˇ eny pozn´ amky ke slajd˚ um o promˇ enn´ ych prostˇred´ı a ˇ cten´ı/z´ apisu z/do roury, kde byly i faktick´ e chyby. ˇ asteˇ 2007-10-07 C´ cnˇ e pˇreps´ any pozn´ amky ke slajd˚ um o historii unixu, standardech a souˇ casn´ ych syst´ emech; doplnˇ eny pozn´ amky k dynamick´ emu linkeru, debugger˚ um a C. 2007-09-16 Vyˇrazena kapitola o administraci, bude nahrazeno nˇ eˇ c´ım jin´ ym, pravdˇ epodobnˇ e aplikac´ı autoconf. 2007-09-16 Opravy pˇreklep˚ u, gramatick´ ych chyb a pod. D´ıky Honzovi Friedlovi (frido at devnull dot cz) 2007-07-18 Do tˇ echto materi´ al˚ u byl pˇrid´ an ChangeLog pro snadnˇ ejˇs´ı sledov´ an´ı zmˇ en. Generov´ an´ı tohoto textu bylo z´ aroveˇ n pˇresunuto z FreeBSD na Solaris a v souvislosti s t´ım jsem pˇreˇsel z CsLaTeX na Babel. M˚ uˇ ze to ovlivnit nˇ ekter´ e fonty a t´ım i sazbu textu.
244
The End.
245