No tedy jĂĄ tenhle blog vedu. Teda spĂĹĄ ve ĹĄkole je to docela nĂĄroÄnĂŠ. Nebo spĂĹĄ si to ve ĹĄkole trochu nĂĄroÄnĂŠ dÄlĂĄm. V uplynulĂŠm tĂ˝dnu mne plnÄ vytÄĹžovala semestrĂĄlka na pĹedmÄt 36OSY – operaÄnĂ systĂŠmy. Tato semestrĂĄlka byla zamÄĹenĂĄ na pouĹžitĂ systĂŠmovĂ˝ch volĂĄnĂ unixu (vlastnÄ to jsou funkce jako kaĹždĂŠ jinĂŠ, jen v man strĂĄnkĂĄch je najdete v sekci 2 a jen nejsou vÄtĹĄinou tak hezky user friendly, jako jejich knihovnĂ nadstavby). KonkrĂŠtnÄ jsem jako zadĂĄnĂ dostal (nebo spĂĹĄ jsem si vybral z tÄch co jeĹĄtÄ zbĂ˝valy):
NapiĹĄte program talk, kterĂ˝ umoĹžnĂ vzĂĄjemnou komunikaci vĂce uĹživatelĹŻ v rĂĄmci jednoho poÄĂtaÄe. Ke komunikaci mezi procesy pouĹžijte System V IPC sdĂlenou pamÄĹĽ.
Povoleno bylo jak komunikovat po znacĂch tak po ĹĂĄdcĂch (jako tĹeba u IRC). PĹi tom proÄĂtĂĄnĂ zadĂĄnĂ jsem si vzpomnÄl, Ĺže komunikacĂ mezi procesy se zabĂ˝vĂĄ pomÄrnÄ podrobnÄ seriĂĄl o programovanĂ v Äasopise Linux+, kterĂŠho jsem ĹĄĹĽastnĂ˝m odbÄratelem. No a doma jsem skuteÄnÄ naĹĄel docela hezkĂŠ povĂdĂĄnĂ o semaforech, sdĂlenĂŠ pamÄti a zasĂlĂĄnĂ zprĂĄv v Linux+ 2/2004. Napsal to dokonce nÄjakĂ˝ student felu, dnes v ĹĄestĂŠm roÄnĂku. Asi si vzpomnÄl na tĹeĹĽĂĄk ;o) SouÄĂĄstĂ ÄlĂĄnku byl i listing kĂłdu producent/konzument, coĹž je typickĂ˝ pĹĂklad uvĂĄdÄnĂ˝ v souvislosti se sdĂlenou pamÄtĂ a semafory. JĂĄ jsem si ho u sĂŠgry na poÄĂtaÄi naskenoval a OCR programem pĹevedl do textu (pravdou je, Ĺže bych to asi mÄl rychleji pĹepsanĂŠ).
V tom pĹĂkladÄ jde o to, Ĺže jeden proces do sdĂlenĂŠ pamÄti zapisuje a druhĂ˝ z nĂ Äte – Äili v prvnĂm pĹiblĂĹženĂ pĹesnÄ to, co jsem potĹeboval. Mimochodem Ăşloha producent/konzument byla takĂŠ jednĂm ze zadĂĄnĂ. ZĂĄkladnĂ myĹĄlenka ĹeĹĄenĂ je v tom, Ĺže producent naalokuje sdĂlenou pamÄĹĽ pomocĂ funkce shmget nÄjak takhle:
shm_id=shmget(key,sizeof(struct shmem),IPC_CREAT|0666)
kde prvnĂ parametr je klĂÄ pod kterĂ˝m ten kus pamÄti bude hledat i konzument (tedy nejspĂĹĄ nÄjakĂĄ konstanta pĹi pĹekladu), nĂĄsleduje velikost pamÄti kterĂĄ nĂĄs zajĂmĂĄ a poslednĂ je bitovĂŠ logickĂŠ nebo pĹesnÄjĹĄĂho urÄenĂ chovĂĄnĂ (IPC_CREAT znamenĂĄ pokud jeĹĄtÄ nic takovĂŠho naalokovanĂŠho nenĂ tak vytvoĹ) a pĹĂstupovĂ˝ch prĂĄv k takovĂŠ pamÄti. VrĂĄcenou hodnotu shm_id hned pouĹžijeme v funkci, kterou si pĹipojĂme sdĂlenou pamÄĹĽ do svĂŠho adresovĂŠho prostoru:
pshmem=(struct shmem *)shmat(shm_id,NULL,0))
A abych nezapomnÄl – potĹebujete mĂt pro prĂĄci se sdĂlenou pamÄtĂ v hlaviÄce
#include <sys/ipc.h>
#include <sys/shm.h>
navĂc budeme potĹebovat pracovat se semafory (hned vysvÄtlĂm) a tak jeĹĄtÄ pĹidĂĄme:
#include <sys/sem.h>
Ono totiĹž se sdĂlenou pamÄtĂ je docela potĂĹž v tom, Ĺže nikdy nevĂme kdy kterĂ˝ proces se dostane k jejĂmu zĂĄpisu a k jejĂmu ÄtenĂ. V dĹŻsledku toho by pak mohly vznikat nekonzistence (u pĹĂkladu konzument/producent se tĹeba mĹŻĹže stĂĄt, Ĺže producent, jeĹĄtÄ do pamÄti nezapsal vĹĄechno, ale konzument byl rychlejĹĄĂ a pĹeÄetl z pamÄti co tam zatĂm bylo). DalĹĄĂ moĹžnĂ˝ pĹĂklad chyb vzniklĂ˝ch pokud nebudeme nÄjak synchronizovat ÄtenĂ a zĂĄpis vznikne tĹeba tehdy, kdyĹž producent jeĹĄtÄ nic novĂŠho do pamÄti nezapsal, ale producent v tom svĂŠm ÄtecĂm cyklu uĹž znovu doĹĄel k mĂstu s pĹeÄtenĂm sdĂlenĂŠ pamÄti. Existuje vĂce zpĹŻsobĹŻ jak chrĂĄnit tzv „kritickou sekci“, neboli mĂsto v kĂłdu kde se nÄjak manipuluje se sdĂlenou pamÄtĂ. U IPC to jsou semafory, pomocĂ kterĂ˝ch se dĂĄ ĹeĹĄit vÄtĹĄina problĂŠmĹŻ. PĹedstava je prostÄ takovĂĄ, Ĺže pĹi vstupu do kritickĂŠ sekce proces koukne na semafor a pokud tam je zelenĂĄ, tak rozsvĂtĂ Äervenou a vleze do vnitĹ. Pokud svĂtĂ ÄervenĂĄ tak ÄekĂĄ na zelenou a aĹž pak rozsvĂtĂ zase Äervenou a vejde dovnitĹ. Zelenou pak nesmĂ zapomenout rozsvĂtit, kdyĹž kritickou sekci opouĹĄtĂ.
ProblĂŠm by ale mohl vzniknout i tady a to tehdy, kdyĹž by se k lizu dostal druhĂ˝ proces mezi tĂm, kdy se ten nĂĄĹĄ podĂval na zelenou a tĂm kdy se ji pokusil nastavit. Proto jsou tyhle 2 akce operaÄnĂm systĂŠmem neoddÄlitelnĂŠ (tzv atomickĂŠ).
No tak abych se vrĂĄtil k tomu popisu producenta – ten si tedy zaalokuje potĹebnĂ˝ poÄet semaforĹŻ (tady budeme potĹebovat 2) coĹž se dÄlĂĄ – svÄte div se – pomocĂ funkce semget, kterou volĂĄme nÄjak takhle:
sem_id=semget(key,2,IPC_CREAT|0666))
TentokrĂĄt je snad uĹž vĹĄechno jasnĂŠ, ne? NĂĄsleduje inicializace hodnot semaforĹŻ – to se dÄlĂĄ pomocĂ funkce semctl, kterĂĄ jako jeden argument dostane
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} sem_arg;
kde nĂĄs vlastnÄ zajĂmĂĄ hlavnÄ to val, kam nahrajeme na co chceme pĹĂsluĹĄnĂ semafor inicializovat:
sem_arg.val=0;
semctl(sem_id,0,SETVAL,sem_arg)
sem_arg.val=1;
semctl(sem_id,1,SETVAL,sem_arg)
TĂm mĂĄme pĹĂpravnĂŠ prĂĄce za sebou a pĹed nĂĄmi je vĂ˝konnĂ˝ cykl. Ten zaÄĂnĂĄ podĂvĂĄnĂm se na semafor, jestli na nÄm svĂtĂ zelenĂĄ a pokud ano, tak rozsvĂcenĂ ÄervenĂŠ a pokraÄovĂĄnĂm. To se realizuje tak, Ĺže do struct sembuf sops; nĂĄsledujĂcĂ:
sops.sem_num=1; /* kterĂ˝ semafor */
sops.sem_op=-1; /* typ operace */
sops.sem_flg=0; /* dalĹĄĂ parametry operace */
semop(sem_id,&sops,1)
Jestli se dĂvĂĄme na zelenou, nebo naopak za sebou zelenou rozsvÄcĂme se ĹĂdĂ prĂĄvÄ parametrem sem_op a to takhle:
if (sem_op < 0) { /* podle toho jestli svĂtĂ zelenĂĄ nebo ÄervenĂĄ se zachovej */
if (semval < 0) /* svĂtĂ zelenĂĄ */
semval–; /* tak rozsviĹĽ Äervenou a pokraÄuj */
if (semval >=0) /* svĂtĂ ÄervenĂĄ */
Äekej dokud nebude (semval < 0);
}
if (sem_op > 0) { /* po odchodu z kritickĂŠ sekce */
semval++; /* za sebou rozsviĹĽ zelenou */
}
if (sem_op == 0) { /* pouĹžĂvĂĄ se jako bariĂŠra – poÄkat aĹž sem dobÄhnou vĹĄechny procesy */
Äekej dokud nebude (semval == 0);
}
SamozĹejmÄ existujĂ nÄjakĂŠ dalĹĄĂ vÄtve tohohle rozvÄtvenĂŠho stromu (napĹĂklad pĹĂchod signĂĄlu proces probudĂ a mĹŻĹže se vykonat jeho obsluha i kdyĹž fakticky stojĂ na semaforu), ale tohle jsou ty nejzĂĄkladnÄjĹĄĂ pĹĂpady. VĂce viz man semop.
V tom cyklu producent vĹždy nejdĹĂv koukne na semafor 1 (na zaÄĂĄtku je nastavenĂ˝ na 1 tady pokraÄuje), pak zapĂĹĄe do sdĂlenĂŠ pamÄti a nakonec nastavĂ zelenou (tedy sem_op=1) na semaforu 0, coĹž odblokuje konzumenta, kterĂ˝ to pĹeÄte a aĹž to pĹeÄte, tak nastavĂ zelenou na semaforu 1, na kterĂŠm do tĂŠ doby ÄekĂĄ producent.
Docela jednoduchĂŠ, co ĹĂkĂĄte ;o) No ale poĹĂĄd je to daleko lepĹĄĂ neĹž se patlat s nÄjakĂ˝m aktivnĂm ÄekĂĄnĂm, kterĂŠ by ale stejnÄ nÄkdy nĂĄhodnÄ nefungovalo. Tohle je zkuteÄnÄ bezpeÄnĂĄ cesta, jak programovÄ oĹĄetĹit tu nekonzistenci dat ve sdĂlenĂŠ pamÄti.
V tom Äasopise byl jeĹĄtÄ navĂc zdrojĂĄÄek pro uvolnÄnĂ sdĂlenĂŠ pamÄti. Na to nezapomĂnejte, protoĹže pak vĂĄm jednak nemusĂ sedÄt hodnoty semaforĹŻ a jednak velikost moĹžnĂŠ sdĂlenĂŠ pamÄti je pĹeci jen omezenĂĄ a mÄ se skuteÄnÄ na tÄch sunech ve ĹĄkole stalo, Ĺže ta pamÄĹĽ (ne tedy mojĂ vinou) doĹĄla. Jinak k vypsĂĄnĂ sdĂlenĂ˝ch prostĹedkĹŻ slouŞà pĹĂkaz ipcs a pro uvolnÄnĂ pak ipcrm {shm | msg | sem} id.
No a teÄ k tĂŠ mojĂ semestrĂĄlce. Opravdu mne nenapadlo, jak to udÄlat jinak abych zĂĄroveĹ mohl Äekat na vloĹženĂ nÄÄeho tĹeba funkcĂ gets() a pĹitom moci vypsat zprĂĄvu, kterĂĄ pĹiĹĄla od jinĂŠho uĹživatele, neĹž tak, Ĺže jsem svĹŻj proces po inicializacĂch rozdÄlil na 2, z nichĹž pĹedek pĹedstavoval producenta a potomek pak konzumenta. V unixu se rozdÄlenĂ procesĹŻ realizuje pomocĂ fork, konkrĂŠtnÄji tĂŠto konstrukce:
x = fork(); /* tady se proces rozdvojĂ */
if (x == 0) {
/* tady bude tÄlo potomka */
} else { /* protoĹže pĹedek v x mĂĄ teÄ PID potomka */
/* tak tady bude tÄlo pĹedka */
}
Pak ale nastane problĂŠm s tĂm, aby jednak na jeden zĂĄpis producenta pĹipadlo probÄhnutĂ vĹĄech spuĹĄtÄnĂ˝ch konzumentĹŻ a pĹitom kaĹždĂ˝ to ale musĂ pĹeÄĂst jen jednou. Nejprve tedy musĂm vÄdÄt kolikrĂĄt je mĹŻj program spuĹĄtÄn – mĂt counter v sdĂlenĂŠ pamÄti se pĹĂmo nabĂzĂ. NavĂc se mi ten counter hodĂ na to, abych pĹi ukonÄovĂĄnĂ vÄdÄl, jestli nejsem nĂĄhodou poslednĂ a tedy mohu bez obav vrĂĄtit sdĂlenou pamÄĹĽ a semafory. No a pak prostÄ poÄet instancĂ nastavĂm truct do pĹĂsluĹĄnĂŠho semaforu. To mi ale neĹeĹĄĂ problĂŠm s tĂm, Ĺže jeden konzument bude tak rychlĂ˝, Ĺže vypĂĹĄe tu zprĂĄvu ve sdĂlenĂŠ pamÄti nÄkolikrĂĄt a tĂm pĂĄdem se na nÄkterĂŠ nedostane. K tomu jsem prĂĄvÄ vyuĹžil tu bariĂŠru. VĹĄechny procesy, kterĂŠ uĹž vypsali aktuĂĄlnĂ zprĂĄvu ÄekajĂ na konci cyklu do tĂŠ doby, neĹž tam dorazĂ vĹĄichni producenti. KonkrĂŠtnÄ nastavĂm v producentovi do semaforu 3 poÄet instancĂ a pak pĹi kaĹždĂŠm prĹŻchodu ÄtenĂm v producentovi odeÄtu jedniÄku (ale poĹĂĄd semval zĹŻstĂĄvĂĄ kladnĂĄ takĹže pokraÄuje) a hned za tĂm nĂĄsleduje volĂĄnĂ semop s parametrem 0, tedy Äekej dokud nebude 0.
Na tohle neĹž jsem pĹiĹĄel tak jsem si zpĹŻsobil poĹĂĄdnĂŠ bolenĂ hlavy. VĂ˝slednĂ˝ program, kterĂ˝ jsem nazval stalk navĂc jeĹĄtÄ obsahuje jednoduchouÄkĂŠ curses uĹživatelskĂŠ rozhranĂ, kterĂŠ mne ovĹĄem nakonec nejspĂĹĄ stĂĄlo bod. VychĂĄzel jsem z toho, Ĺže nevĂm o jinĂŠm zpĹŻsobu jak oĹĄetĹit situaci kdy do rozepsanĂŠ zprĂĄvy pĹijde zprĂĄva nÄkoho jinĂŠho. Logicky z toho vyplĂ˝vĂĄ, Ĺže potĹebuju jednu ÄĂĄst terminĂĄlu aby slouĹžila jako vklĂĄdacĂ pro producenta a druhou ÄĂĄst jako vypisovacĂ pro konzumenta.
Velice dobrĂ˝ tutoriĂĄl (teda on popisuje ncurses, ale to je nĂĄslednĂk curses a pĹi svĂ˝ch zaÄĂĄteÄnickĂ˝ch pokusech jsem vyjma makra box() nenarazil na nic co by neumÄlo i samostatnĂŠ curses) je NCURSES Programming HOWTO. ProblĂŠm s curses (btw anglicky to znamenĂĄ „kletby“ a musĂm ĹĂct Ĺže uĹž vĂm proÄ) byl hlavnÄ v tom, Ĺže nenĂ stavÄnĂŠ na to, aby ho zĂĄroveĹ pouĹžĂvaly 2 procesy. UrÄitĂ˝m ĹeĹĄenĂm by byly thready (na venek se aplikace chovĂĄ jako jeden proces a vĹĄechny podprocesy majĂ sdĂlenou celou svojĂ pamÄĹĽ) ale ty jsem pouĹžĂt nemohl protoĹže jsem mezi volĂĄnĂm jĂĄdra nenaĹĄel jak je jednoduĹĄe vytvoĹit. JedinĂŠ jednoduchĂŠ ĹeĹĄenĂ pomocĂ syscallĹŻ mne napadlo pouĹžitĂ signĂĄlĹŻ.
To je mechanizmus asynchronnĂ kontroly procesĹŻ. VlastnÄ nÄco jako INT v hardwaru, nebo softwarovĂ˝ INT v assembleru. TĹeba pokud pouĹžĂvĂĄte nÄjakou aplikaci a stisknete CTRL+C tak aplikace dostane SIGINT a vÄtĹĄinou obsluha tohoto signĂĄlu ukonÄĂ bÄĹžĂcĂ aplikaci. Ale obsluha vÄtĹĄiny signĂĄlĹŻ se dĂĄ pĹedefinovat a dokonce ve standardu jsou 2 signĂĄly SIGUSR1 a SIGUSR2 kterĂŠ jsou pĹĂmo urÄeny k uĹživatelskĂŠmu pĹedefinovĂĄnĂ jejich obsluh.
Na vÄci okolo signĂĄlĹŻ jsem se ptal na cviÄenĂ z pĹedmÄtu unix (pĹednĂĄĹĄku kde se braly signĂĄly najdete tady) a ten cviÄĂcĂ mi doporuÄil aĹĽ si stĂĄhnu knihu Advanced Linux Programming. Mimochodem vyĹĄla i v ÄeĹĄtinÄ, ale ta se uĹž pokud vĂm stĂĄhnout z webu nedĂĄ 🙁
Aby se pĹi pĹijetĂ signĂĄlu pustila vaĹĄe obsluha zajistĂte takhle:
signal(SIGUSR1,printkonz);
kde ten druhý argument je jmÊno funkce s takovýmhle prototypem:
void printkonz(int cislo_signalu)
To ÄĂslo signĂĄlu se sakra hodĂ v pĹĂpadÄ, Ĺže pouĹžĂvĂĄte stejnou obsluhu pro vĂce pĹeruĹĄenĂ (nicmĂŠnÄ i kdyĹž nepouĹžĂvĂĄte, tak tam bĂ˝t musĂ aby funkce signal poznala Ĺže je to ta obsluha). Ono totiĹž po prvnĂm pĹĂchodu signĂĄlu se ta vaĹĄe obsluha zase odhlĂĄsĂ a musĂte jĂ znovu pĹihlĂĄsit pokud chcete aby se pĹi pĹĂĹĄtĂm pĹijetĂ signĂĄlu znovu pustila tahle obsluha.
No a to je vlastnÄ vĹĄechno co jsem si chtÄl zaznamenat. Snad se v tom vyznĂĄm, aĹž to nÄkdy budu zase potĹebovat a snad se to bude hodit nÄkdy i nÄkomu jinĂŠmu.
Napsat komentář
Pro přidávání komentářů se musíte nejdříve přihlásit.