Principi rada u operativnim sistemima sličnim UNIX-u na primjeru Linuxa. Multitasking Linux programiranje
Original: Laki procesi: seciranje Linux niti
Autori: Vishal Kanaujia, Chetan Giridhar
Datum objave: 1. avgust 2011
Prijevod: A.Panin
Datum objave prevoda: 22. oktobar 2012
Namijenjen Linux programerima i studentima informatike, ovaj članak pokriva osnove rada niti i kako se implementiraju pomoću laganih Linux procesa, te daje primjere izvornog koda za bolje razumijevanje.
Programske niti su osnovni element multitasking softversko okruženje. Programska nit se može opisati kao okruženje izvršenja procesa; stoga, svaki proces ima najmanje jednu programsku nit. Multithreading uključuje postojanje nekoliko istovremenih (na višeprocesorskim sistemima) i obično sinhronizovanih okruženja za izvršavanje procesa.
Programske niti imaju svoje identifikatore (ID niti) i mogu se izvršavati nezavisno jedna od druge. Oni među sobom dijele isti procesni adresni prostor i koriste ovu mogućnost kao prednost, omogućavajući da se IPC kanali (sistemi međuprocesne komunikacije - zajednička memorija, kanali i drugi sistemi) ne koriste za razmjenu podataka. Programske niti procesa mogu komunicirati - na primjer, nezavisne niti mogu dobiti/promijeniti vrijednost globalne varijable. Ovaj model interakcije eliminiše troškove IPC poziva na nivou kernela. Budući da niti rade u istom adresnom prostoru, preklopnici konteksta za nit su brzi i ne zahtijevaju puno resursa.
Svaku programsku nit može pojedinačno obraditi planer zadataka, tako da su aplikacije sa više niti pogodne za paralelno izvršavanje na višeprocesorskim sistemima. Također, kreiranje i uništavanje niti je brzo. Za razliku od fork() poziva, u slučaju niti se ne kreira kopija nadređenog adresnog prostora, umjesto toga niti dijele adresni prostor zajedno s drugim resursima, uključujući deskriptore datoteka i rukovaoce signalima.
Aplikacija sa više niti koristi resurse optimalno i što efikasnije. U takvoj aplikaciji, programske niti obavljaju različite zadatke kako bi optimizirali korištenje sistema. Jedna nit može čitati datoteku s diska, druga može pisati podatke u socket. Obje niti će raditi u tandemu, neovisno jedna od druge. Ovaj model optimizira korištenje sistema, čime se poboljšavaju performanse.
Nekoliko zanimljivih karakteristika
Najpoznatija karakteristika rada sa nitima je njihova sinhronizacija, posebno kada postoji zajednički resurs označen kao kritični segment. Ovo je segment koda u kojem se pristupa zajedničkom resursu i ne bi trebalo da mu pristupa više od jedne niti u bilo kojem trenutku. S obzirom da se svaka nit može samostalno izvršavati, pristup zajedničkom resursu nije kontroliran, što dovodi do potrebe za korištenjem primitiva za sinhronizaciju, uključujući mutekse (međusobno isključivanje – međusobno isključivanje), semafore, brave čitanja/pisanja i druge.
Ovi primitivi omogućavaju programerima da kontrolišu pristup zajedničkom resursu. Pored navedenog, niti su, poput procesa, podložne ulasku u stanje blokiranja ili čekanja ako model sinhronizacije nije ispravno dizajniran. Otklanjanje grešaka i analiza višenitnih aplikacija također može biti prilično opterećujuće.
Kako su niti implementirane u Linuxu?
Linux vam omogućava da razvijate i koristite višenitne aplikacije. Na nivou korisnika, implementacija niti u Linuxu je u skladu s POSIX (Portable Operating System Interface for uniX) otvorenim standardom. Unix sistemi), označen kao IEEE 1003. Biblioteka na nivou korisnika (glibc.so na Ubuntu) pruža implementaciju POSIX API-ja za niti.
U Linuxu, niti postoje u dva odvojena prostora - korisničkom prostoru i prostoru kernela. U korisničkom prostoru, niti se kreiraju pomoću API-ja biblioteke pthread kompatibilnog s POSIX-om. Ove niti korisničkog prostora su neraskidivo povezane sa nitima prostora kernela. U Linuxu, kernel-space niti se tretiraju kao "laki procesi". Lagani proces je jedinica osnovnog okruženja izvršenja. Za razliku od različitih tipova UNIX-a, uključujući sisteme kao što su HP-UX i SunOS, Linux nema poseban sistem uvođenja niti. Proces ili nit u Linuxu se tretira kao "zadatak" i koristi iste interne strukture (skup struct task_structs).
Za brojne procesne niti kreirane u korisničkom prostoru, postoji niz lakih procesa povezanih s njima u kernelu. Primjer ilustruje ovu izjavu: #include
Koristeći ps uslužni program, možete dobiti informacije o procesima, kao io lakim procesima / nitima ovih procesa: [email protected]:~/Desktop$ ps -fL UID PID PPID LWP C NLWP VRIJEME TTY VRIJEME CMD kanaujia 17281 5191 17281 0 1 jun 11 pts/2 00:00:02 bash kanaujia 22838 22832 172380 7 20 20 20 0 : 00 ps -fL kanaujia 17647 14111 17647 0 2 00:06 pts/0 00:00:00 vi clone.s
Šta su laki procesi?
Lagani proces je proces koji održava nit korisničkog prostora. Svaka nit korisničkog prostora je neraskidivo povezana sa laganim procesom. Procedura za kreiranje laganog procesa razlikuje se od procedure za kreiranje redovnog procesa; korisnički proces "P" može imati više pridruženih lakih procesa sa istim ID-om grupe. Grupisanje omogućava kernelu da dijeli resurse (resursi uključuju adresni prostor, stranice fizičke memorije (VM), rukovaoce signalima i deskriptore datoteka). Također omogućava kernelu da izbjegne promjene konteksta kada se bavi ovim procesima. Iscrpno dijeljenje resursa je razlog zašto se ovi procesi nazivaju laganim.
Kako Linux stvara lagane procese?
Na Linuxu, laki procesi se kreiraju korištenjem nestandardnog clone() sistemskog poziva. Slično je pozivanju fork() , ali sa više funkcija. Općenito, fork() poziv se implementira sa pozivom clone() s dodatnim parametrima koji ukazuju na resurse koji se dijele između procesa. Poziv clone() kreira proces, pri čemu podređeni proces dijeli okruženje za izvršavanje s roditeljem, uključujući memoriju, deskriptore datoteka i rukovaoce signalima. Biblioteka pthread također koristi clone() poziv za implementaciju niti. Pogledajte datoteku izvornog koda ./nptl/sysdeps/pthread/createthread.c u izvornom direktoriju glibc verzije 2.11.2.
Kreiranje vlastitog laganog procesa
Hajde da demonstriramo primer korišćenja poziva clone(). Pogledaj u izvor iz demo.c fajla ispod:
#include
Program demo.c vam omogućava da kreirate niti na isti način kao i pthread biblioteka. Međutim, direktna upotreba poziva clone() je nepoželjna, jer ako se koristi nepravilno, aplikacija koja se razvija može pasti. Sintaksa Linux clone() je sljedeća: #include
Prvi argument je funkcija niti; poziva se tokom pokretanja niti. Nakon što se poziv clone() uspješno završi, funkcija fn počinje da se izvršava u isto vrijeme kada i proces poziva.
Sljedeći argument je pokazivač na memorijsku lokaciju za stog podređenog procesa. U koraku prije poziva fork() i clone(), od programera se traži da rezerviše memoriju i prosledi pokazivač koji će se koristiti kao stek deteta, pošto roditelj i dete dele stranice memorije - uključujući i stek. Podređeni proces može pozvati drugu funkciju od roditeljskog procesa, zbog čega je potreban poseban stek. U našem programu, ovaj dio memorije dodjeljujemo na hrpu pomoću funkcije malloc(). Veličina steka je postavljena na 64 KB. Budući da stek na arhitekturi x86 raste prema dolje, potrebno je simulirati slično ponašanje korištenjem dodijeljene memorije s kraja sekcije. Iz tog razloga, sljedeću adresu prosljeđujemo funkciji clone(): (char*) stack + STACK
Sljedeći argument zastavice je posebno važan. Omogućava vam da odredite koje resurse treba dijeliti sa kreiranim procesom. Izabrali smo bitmasku SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM opisano u nastavku:
- SIGCHLD : Nit šalje SIGCHLD signal roditeljskom procesu po završetku. Postavljanje ove opcije omogućava roditeljskom procesu da koristi funkciju čekanja() da sačeka da se sve niti završe.
- CLONE_FS : Dijelite informacije o sistemu datoteka između nadređenog procesa i niti. Informacije uključuju root sistem podataka, radni direktorij i umask vrijednost.
- CLONE_FILES : Dijelite tablicu deskriptora datoteke između nadređenog procesa i niti. Promjene u tablici se odražavaju u nadređenom procesu i svim nitima.
- CLOSE_SIGHAND : Dijelite tabelu rukovanja signalom između roditelja i djeteta. Opet, ako roditeljski proces ili jedna od niti promijeni rukovaoca signala, promjena će se odraziti u tabelama drugih procesa.
- CLONE_VM : Roditeljski proces i niti se pokreću u istom memorijskom prostoru. Sva upisivanja u memoriju ili mapiranja napravljena od strane jednog od njih dostupni su drugim procesima.
Posljednji parametar je argument proslijeđen funkciji ( threadFunction ), u našem slučaju to je deskriptor datoteke.
Molimo pogledajte primjer demo.c laganog procesa koji smo ranije objavili.
Nit zatvara datoteku (/dev/null) koju je otvorio roditeljski proces. Budući da roditeljski proces i nit dijele tablicu deskriptora datoteke, operacija zatvaranja datoteke utječe i na roditeljski proces, uzrokujući neuspjeh naknadnih poziva write(). Roditeljski proces čeka da se nit završi (do trenutka kada se primi signal SIGCHLD). Zatim oslobađa rezerviranu memoriju i vraća se.
Sastavite i pokrenite program na uobičajen način; izlaz bi trebao biti sličan sljedećem: $gcc demo.c $./a.out Kreiranje podređene niti podređene niti koja ulazi u podređenu nit napušta Roditelj: dijete je zatvorilo naš deskriptor datoteke $
Linux pruža podršku za efikasnu, jednostavnu i skalabilnu niti infrastrukturu. Ovo podstiče programere da eksperimentišu i razvijaju biblioteke za rad sa nitima koristeći clone() kao glavnu funkciju.
Upišite sljedeću naredbu u svoju ljusku:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Na ekranu će se prikazati lista svih procesa koji se pokreću u sistemu. Ako želite da izbrojite broj procesa, upišite nešto ovako:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
$ ps -e --bez zaglavlja | nl | rep -n 1
74 4650 poena/0 00:00:00 rep
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Prvi broj je broj procesa koji se pokreću na sistemu. Korisnici KDE-a mogu koristiti kpm program, a Gnome korisnici mogu koristiti program gnome-system-monitor da dobiju informacije o procesu. Zato je i Linux, da se omogući korisniku da radi istu stvar na različite načine.
Postavlja se pitanje: "Šta je proces?". Procesi u Linuxu, poput datoteka, su aksiomatski koncepti. Ponekad se proces identificira sa pokrenutim programom, međutim, to nije uvijek slučaj. Smatraćemo da je proces radna jedinica sistema koja nešto izvodi. Multitasking je mogućnost da više procesa koegzistiraju na istom sistemu u isto vrijeme.
Linux je operativni sistem koji obavlja više zadataka. To znači da procesi u njemu rade istovremeno. Naravno, ovo je uslovna izjava. Linux kernel konstantno mijenja procese, to jest, s vremena na vrijeme svakom od njih daje malo procesorskog vremena. Prebacivanje se dešava prilično brzo, tako da nam se čini da procesi rade u isto vreme.
Neki procesi mogu pokrenuti druge procese, formirajući strukturu stabla. Roditeljski procesi se nazivaju roditelji ili nadređeni procesi, a pokrenuti procesi nazivaju se podređeni ili podređeni procesi. Na vrhu ovog "stabla" je init proces, koji jezgro automatski pokreće tokom pokretanja sistema.
Svaki proces u sistemu je povezan s parom nenegativnih cijelih brojeva: ID procesa PID (IDentifikator procesa) i ID nadređenog procesa PPID (Parent Process IDentifier). Za svaki proces, PID je jedinstven (u određenom trenutku), a PPID je jednak ID-u roditeljskog procesa. Ako unesete naredbu ps -ef u shell, tada će na ekranu biti prikazana lista procesa sa njihovim PID i PPID vrijednostima (drugi i treći stupac). Primjer takve naredbe:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
UID PID PPID C VRIJEME TTY VRIJEME CMD
root 1 0 0 17:08? 00:00:00 /sbin/init
root 2 0 0 17:08? 00:00:00
root 3 2 0 17:08? 00:00:00
root 4 2 0 17:08? 00:00:00
root 5 2 0 17:08? 00:00:00
root 6 2 0 17:08? 00:00:00
root 7 2 0 17:08? 00:00:00
root 8 2 0 17:08? 00:00:00
root 9 2 0 17:08? 00:00:00
root 10 2 0 17:08? 00:00:00
root 11 2 0 17:08? 00:00:00
root 12 2 0 17:08? 00:00:00
root 13 2 0 17:08? 00:00:00
root 14 2 0 17:08? 00:00:00
root 15 2 0 17:08? 00:00:00
root 16 2 0 17:08? 00:00:00
root 17 2 0 17:08? 00:00:00
root 18 2 0 17:08? 00:00:00
root 19 2 0 17:08? 00:00:00
df00 16389 16387 0 20:10 pts/1 00:00:00 /bin/bash
df00 17446 2538 0 20:26? 00:00:00
df00 18544 2932 0 20:41 pts/2 00:00:00 /bin/bash -l
df00 19010 18544 0 20:48 pts/2 00:00:00 ps -ef
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Treba napomenuti da init proces uvijek ima ID 1 i PPID 0, iako u stvarnosti ne postoji proces sa ID-om 0. Stablo procesa se također može vizualizirati korištenjem --forest opcije ps programa:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
2? 00:00:00 kthreadd
3 ?00:00:00 \_migracija/0
4 ?00:00:00 \_ksoftirqd/0
5 ?00:00:00 \_watchdog/0
6 ?00:00:00 \_migracija/1
7 ?00:00:00 \_ksoftirqd/1
8 ?00:00:00 \_watchdog/1
9 ?00:00:00 \_ događaji/0
10 00:00:00 \_ događaji/1
11 ?00:00:00 \_cpuset
12 00:00:00 \_ khelper
13 ?00:00:00 \_ netns
14 ?00:00:00 \_ async/mgr
15 ?00:00:00 \_kintegrityd/0
16 ?00:00:00 \_kintegrityd/1
18544 pts/2 00:00:00 \_bash
16388 ?00:00:00 \_gnome-pty-helpe
16389 pts/1 00:00:00 \_bash
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Ako pozovete ps program bez argumenata, tada će se prikazati lista procesa koji pripadaju trenutnoj grupi, odnosno koji rade pod trenutnim terminalom.
Korištenje getpid() i getppid()
Proces može saznati svoj ID (PID) kao i roditeljski ID (PPID) koristeći sistemske pozive getpid() i getppid().
Sistemski pozivi getpid() i getppid() imaju sljedeće prototipove:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
pid_t getpid(void);
pid_t getppid(void);
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Da biste koristili getpid() i getppid(), datoteke zaglavlja unistd.h i sys/types.h (za tip pid_t) moraju biti uključene u program sa #include direktivom. Poziv getpid() vraća ID trenutnog procesa (PID), dok getppid() vraća ID nadređenog procesa (PPID). pid_t je cjelobrojni tip, čija dimenzija ovisi o specifičnom sistemu. Vrijednostima ovog tipa može se manipulirati kao redovnim cijelim brojevima tipa int.
Razmislite sada jednostavan program, koji prikazuje PID i PPID, a zatim se "zamrzava" dok korisnik ne pritisne
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
#include
#include
#include
pid_t pid, ppid;
pid = getpid();
ppid = getppid();
printf("PID: %d\n", pid);
printf("PPID: %d\n", ppid);
fprintf(stderr, "Pritisnite
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Hajde da sada proverimo kako ovaj program radi. Da biste to učinili, kompajlirajte i pokrenite ga:
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
$ gcc -o getpid getpid.c
Pritisnite
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Sada bez pritiskanja
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
$ ps -ef | grep getpid
df00 19724 19702 0 20:58 pts/3 00:00:00 ./main
df00 19856 18544 0 21:00 pts/2 00:00:00 grep --colour=auto main
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
LAB #3
MULTITASK PROGRAMIRANJE INLinux
1. Cilj rada: Upoznajte se sa gcc kompajlerom, metodama za otklanjanje grešaka u programima, funkcijama za rad sa procesima.
2. Kratke teorijske informacije.
Minimalni skup opcija gcc kompajlera je - Wall (štampanje svih grešaka i upozorenja) i - o (izlazni fajl):
gcc - Zid - o print_pid print_pid. c
Tim će kreirati izvršnu datoteku print_pid.
Standardna biblioteka C (libc, implementirana na Linux u glibc) koristi prednosti multitasking mogućnosti Unix System V (u daljem tekstu SysV). U libc-u, tip pid_t je definiran kao cijeli broj koji može sadržavati pid. Funkcija koja prijavljuje pid trenutnog procesa ima prototip pid_t getpid(void) i definirana je zajedno sa pid_t u unistd. h i sys/tipovi. h).
Funkcija fork se koristi za kreiranje novog procesa:
pid_t fork(void)
Umetanjem kašnjenja nasumične dužine koristeći funkcije spavanja i rand, možete jasnije vidjeti učinak multitaskinga:
ovo će učiniti da program "uspava" nasumičnim brojem sekundi: od 0 do 3.
Da biste pozvali funkciju kao podređeni proces, samo je pozovite nakon grananja:
// ako je podređeni proces pokrenut, onda pozovite funkciju
pid=proces(arg);
// izlaz iz procesa
Često je potrebno pokrenuti drugi program kao podređeni proces. Da biste to učinili, koristite funkcije exec porodice:
// ako je podređeni proces pokrenut, onda programski poziv
if (execl("./file","file",arg, NULL)<0) {
printf("GREŠKA prilikom pokretanja procesa\n");
else printf("proces je pokrenut (pid=%d)\n", pid);
// izlaz iz procesa
Često, roditeljski proces treba da komunicira sa, ili da se barem sinhronizuje sa, podređenim procesima kako bi izvršio operacije u pravo vreme. Jedan od načina za sinhronizaciju procesa je funkcija čekanja i čekanja:
#include
#include
pid_t wait(int *status) - Obustavlja izvršavanje trenutnog procesa dok se bilo koji od njegovih podređenih procesa ne završi.
pid_t waitpid (pid_t pid, int *status, int opcije) - Obustavlja izvršavanje trenutnog procesa dok se navedeni proces ne završi, ili provjerava da li je određeni proces prekinut.
Ako trebate znati stanje podređenog procesa na njegovom završetku i vrijednost koju je vratio, tada koristite makro WEXITSTATUS, proslijeđujući mu status podređenog procesa kao parametar.
status=waitpid(pid,&status, WNOHANG);
if (pid == status) (
printf("PID: %d, Rezultat = %d\n", pid, WEXITSTATUS(status)); )
Setpriority i funkcije se koriste za promjenu prioriteta podređenih procesa. Prioriteti su postavljeni u rasponu od -20 (najviši) do 20 (najniži), normalna vrijednost je 0. Imajte na umu da samo superkorisnik može povećati prioritet iznad normalnog!
#include
#include
int proces(int i) (
setpriority(PRIO_PROCESS, getpid(),i);
printf("Proces %d ID niti: %d radi sa prioritetom %d\n",i, getpid(),getpriority(PRIO_PROCESS, getpid()));
return(getpriority(PRIO_PROCESS, getpid()));
Kill funkcija se koristi za ukidanje procesa:
#include
#include
int kill(pid_t pid, int sig);
Ako je pid > 0, onda on specificira PID procesa kojem se šalje signal. Ako je pid = 0, tada se signal šalje svim procesima u grupi kojoj trenutni proces pripada.
sig - tip signala. Neke vrste signala u Linuxu:
SIGKILL Ovaj signal uzrokuje da se proces odmah završi. Ovaj signal proces ne može zanemariti.
SIGTERM Ovaj signal je zahtjev za prekid procesa.
SIGCHLD Sistem šalje ovaj signal procesu kada se jedan od njegovih podređenih procesa završi. primjer:
if (pid[i] == status) (
printf("ThreadID: %d završeno sa statusom %d\n", pid[i], WEXITSTATUS(status));
else kill(pid[i],SIGKILL);
3. Metodička uputstva.
3.1. Da biste se upoznali sa opcijama gcc kompajlera, opisom funkcija jezika C, koristite man i info uputstva.
3.2. Za otklanjanje grešaka u programima, zgodno je koristiti ugrađeni uređivač upravitelja datoteka Midnight Commander (MC), koji ističe različite jezičke konstrukcije i ukazuje na poziciju kursora u datoteci (red, kolona) u gornjem redu ekran.
3.3. Upravljač datotekama Midnight Commander ima tampon komande koje se pozivaju prečicom na tastaturi
3.4. Imajte na umu da trenutni direktorij nije sadržan u putanji, dakle from komandna linija morate pokrenuti program kao "./print_pid". U MC-u samo zadržite pokazivač iznad datoteke i pritisnite
3.5. Da vidite rezultat izvršavanja programa, koristite prečicu na tastaturi
3.6. Da biste evidentirali rezultate izvršavanja programa, preporučljivo je koristiti preusmjeravanje izlaza s konzole na datoteku: ./test > rezultat. poruka
3.7. Za pristup fajlovima kreiranim na Linux server, koristite ftp protokol, čiji je klijentski program dostupan u Windows 2000 i ugrađen u file manager FAR. Gde Račun i lozinka su isti kao kod povezivanja preko ssh protokola.
4.1. Upoznajte se sa opcijama gcc kompajlera, metodologijom za otklanjanje grešaka u programima.
4.2. Za opcije zadataka od laboratorijski rad#1 napisati i otkloniti greške u programu koji implementira generirani proces.
4.3. Za varijante zadataka iz laboratorijskog rada br. 1 napisati i otkloniti grešku u programu koji implementira roditeljski proces koji poziva i prati stanje podređenih procesa – programa (čekajući njihov završetak ili ih uništava, ovisno o verziji).
4.4. Za varijante zadataka iz laboratorijskog rada br. 1 napišite i debagirajte program koji implementira roditeljski proces koji poziva i prati stanje podređenih procesa - funkcija (čekajući njihov završetak ili ih uništava, ovisno o verziji).
5. Opcije zadatka. Vidi opcije za zadatke iz laboratorijskog rada br.1
6. Prijavite sadržaj.
6.1. Cilj rada.
6.2. Opcija zadatka.
6.3. Liste programa.
6.4. Protokoli za izvršavanje programa.
7. Kontrolna pitanja.
7.1. Karakteristike kompajliranja i pokretanja C-programa u Linuxu.
7.2. Šta je pid, kako ga definisati operativni sistem i program?
7.3. Funkcija fork - svrha, aplikacija, povratna vrijednost.
7.4. Kako pokrenuti funkciju u podređenom procesu? program?
7.5. Načini sinhronizacije nadređenih i podređenih procesa.
7.6. Kako mogu saznati stanje pokrenutog procesa kada izađe i vrijednost koju vraća?
7.7. Kako upravljati prioritetima procesa?
7.8. Kako ubiti proces u operativnom sistemu i programu?
Multithreading u programiranju je važan mehanizam u naše vrijeme. Stoga sam odlučio posvetiti nekoliko članaka ovoj temi.
U Windows OS porodicama, svaki program pokreće jedan proces izvršavanja, u kojem postoji najmanje jedna nit (nit). U procesu može biti mnogo niti, između kojih je podijeljeno vrijeme procesora. Jedan proces ne može direktno pristupiti memoriji drugog procesa, a niti dijele isti adresni prostor jednog procesa. To jest, u Windows-u je proces kolekcija niti.
Na Linuxu je malo drugačije. Suština procesa je ista kao u Windows-u - to je izvršni program sa vlastitim podacima. Ali nit u Linuxu je zaseban proces (naziv možete naići kao "lagani proces", LWP). Razlika je ista - proces je poseban program sa svojom memorijom, ne može direktno pristupiti memoriji drugog procesa, ali nit, iako je poseban proces, ima pristup memoriji roditeljskog procesa. LWP procesi se kreiraju pomoću clone() sistemskog poziva sa određenim oznakama.
Ali postoji i nešto što se zove "POSIX Threads" - POSIX standardna biblioteka koja organizuje niti (aka niti) unutar procesa. Odnosno, ovdje se već odvija paralelizacija u okviru jednog procesa.
I tu se postavlja pitanje razlike između pojmova "nit", "proces", "nit" itd. Problem je u tome što su u engleskoj literaturi ovi pojmovi nedvosmisleno definisani, dok imamo kontradiktornosti sa našim velikim i moćnim, što može dovesti do divljeg neslaganja.
Ali ovo je sve generalno, za tačnije informacije treba pogledati relevantnu literaturu, ili službenu dokumentaciju, možete pročitati čovjekovu. Na kraju članka ću dati neke korisne linkove na resurse gdje je detaljnije opisano kako sve funkcionira, ali za sada hajde da vježbamo.
Razmotrit ću dvije opcije za "paraleliziranje" programa - kreiranje niti / niti pomoću funkcija iz pthread.h (POSIX Threads) ili kreiranje zasebnog procesa pomoću funkcije fork ().
Danas ćemo razmotriti teme iz pthread biblioteke.
Predložak koda za rad s nitima izgleda ovako:
#include
#include //stream funkcija //prekini nit pthread_exit(0) ; int main()( //neki podaci za stream (na primjer) void * thread_data = NULL; //kreiraj ID niti pthread_t thread ; //kreiramo nit pomoću identifikatora niti i funkcije niti threadFunc //i proslijediti pokazivač na thread_data u nit pthread_create (& thread, NULL, threadFunc, thread_data) ; //čekaj da se nit završi pthread_join (nit, NULL) ; return 0 ; |
Kao što možete vidjeti iz koda, suština niti je utjelovljena u funkciji, u ovom slučaju threadFunc. Ime takve funkcije može biti proizvoljno, ali tip povratka i tip ulaznog argumenta moraju biti striktno void*. Ova funkcija će se izvršiti u posebnoj niti izvršenja, tako da morate biti vrlo oprezni kada implementirate ovu funkciju zbog pristupa istoj memoriji roditeljskog procesa od strane više niti. Završetak se postiže na nekoliko načina: nit je dostigla svoju krajnju tačku (povratak, pthread_exit(0)), ili je nit eksterno prekinuta.
Nit se kreira pomoću funkcije pthread_create(pthread_t *tid, const pthread_attr_t *attr, void*(*function)(void*), void* arg), gdje je: tid identifikator niti, attr su parametri niti (NULL su zadani atributi, detalji u man), funkcija je pokazivač na funkciju niti, u našem slučaju threadFunc i arg je pokazivač na podatke proslijeđene niti.
Funkcija pthread_join čeka da se nit završi. Drugi parametar ove funkcije je rezultat koji vraća stream.
Hajde da pokušamo da napišemo program zasnovan na ovom obrascu koji radi nešto korisno. Na primjer, pokušajmo dodati dvije matrice i pohraniti rezultat u treću rezultirajuću matricu. Da bi se riješio ovaj problem, već je potrebno razmišljati o pravilnoj distribuciji podataka između niti. Implementirao sam jednostavan algoritam - koliko redova u matrici, toliko niti. Svaka nit dodaje elemente reda prve matrice sa elementima reda druge matrice i pohranjuje rezultat u red treće matrice. Ispostavilo se da svaka nit radi tačno sa svojim podacima, pa je pristup jedne niti podacima druge niti isključen. Primjer programa koji koristi pthread niti izgleda ovako:
#include
#include #include #include #include //veličine matrice #defini #5 #definiraj M 5 //posebna struktura za podatke niti typedef struktura ( int rowN ; //broj obrađene linije int rowSize ; //veličina niza //pokazivači na matrice int * * niz1 ; int * * array2 ; int * * resArr ; ) pthrData ; void * threadFunc(void * thread_data) ( //dobijemo strukturu podataka pthrData * data = (pthrData * ) thread_data ; //dodajte elemente redova matrice i spremite rezultat za (int i = 0; i< data ->rowSize ; i++) podaci -> resArr [ podaci -> redN ] [ i ] = podaci -> niz1 [ podaci -> redN ] [ i ] + podaci -> niz2 [ podaci -> red N ] [ i ] ; return NULL ; int main()( //dodijeliti memoriju za dvodimenzionalne nizove int * * matrix1 = (int * *) malloc (N * sizeof (int *)) ; int * * matrix2 = (int * *) malloc (N * sizeof (int *)) ; int * * resultMatrix = (int * * ) malloc (N * sizeof (int * ) ) ; //dodijeliti memoriju za matrične elemente za (int i = 0; i< M ; i ++ ) { matrica1 [ i ] = (int * ) malloc (M * sizeof (int) ) ; matrix2 [ i ] = (int * ) malloc (M * sizeof (int) ) ; resultMatrix [ i ] = (int * ) malloc (M * sizeof (int ) ) ; //inicijalizirati početnim vrijednostima za (int i = 0; i< N ; i ++ ) { za (int j = 0; j< M ; j ++ ) { matrica1 [ i ] [ j ] = i ; matrica2[i][j] = j; resultMatrix [ i ] [ j ] = 0 ; //dodijeliti memoriju za niz ID-ova niti pthread_t * threads = (pthread_t *) malloc (N * sizeof (pthread_t)) ; //koliko niti - toliko struktura sa podacima toka pthrData * threadData = (pthrData * ) malloc (N * sizeof (pthrData) ) ; //inicijaliziramo strukture niti za (int i = 0; i< N ; i ++ ) { threadData[i]. red N = i ; |