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 #include #include int main() ( pthread_t tid = pthread_self(); int sid = syscall(SYS_gettid); printf("LWP id je %dn", sid); printf("ID POSIX niti je %dn", tid); return 0; )

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 #include #include #include #include #include #include //veličina stog je 64kB #define STACK 1024*64 // Podređena nit će izvršiti ovu funkciju int threadFunction(void* argument) ( printf("child thread entering\n"); close((int*)argument); printf( "podređena nit izlazi\n"); return 0; ) int main() ( void* stack; pid_t pid; int fd; fd = open("/dev/null", O_RDWR); if (fd< 0) { perror("/dev/null"); exit(1); } // Резервирование памяти для стека stack = malloc(STACK); if (stack == 0) { perror("malloc: could not allocate stack"); exit(1); } printf("Creating child thread\n"); // Вызов clone() для создания дочернего потока pid = clone(&threadFunction, (char*) stack + STACK, SIGCHLD | CLONE_FS | CLONE_FILES |\ CLONE_SIGHAND | CLONE_VM, (void*)fd); if (pid == -1) { perror("clone"); exit(2); } // Ожидание завершения дочернего потока pid = waitpid(pid, 0, 0); if (pid == -1) { perror("waitpid"); exit(3); } // Попытка записи в файл закончится неудачей, так как поток // закрыл файл if (write(fd, "c", 1) < 0) { printf("Parent:\t child closed our file descriptor\n"); } // Освободить память, используемую для стека free(stack); return 0; }

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 int klon (int (*fn) (void *), void *child_stack, int zastavice, void *arg);

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 za izlaz...");

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Hajde da sada proverimo kako ovaj program radi. Da biste to učinili, kompajlirajte i pokrenite ga:

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

$ gcc -o getpid getpid.c

Pritisnite da izađem...

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Sada bez pritiskanja , otvorite drugi prozor terminala i provjerite da li getpid() i getppid() sistemski pozivi rade ispravno:

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

$ 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 - H, koji se pomera strelicama kursora (gore i dole). Da biste zalijepili naredbu iz bafera u komandnu liniju, koristite ključ , za uređivanje komande iz bafera - tipke<- и ->, I .


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 - O. Oni također rade u modu za uređivanje datoteka.

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 //funkcija niti void* threadFunc(void* thread_data)( //završi nit pthread_exit(0); ) int main()( //neki podaci za nit (na primjer) void* thread_data = NULL; //kreiraj nit identifikator pthread_t thread; //kreirajte nit pomoću ID-a niti i funkcije niti threadFunc //i prosljeđujete pokazivač na podatke thread_data pthread_create(&thread, NULL, threadFunc, thread_data); //pričekajte da nit završi pthread_join(thread, NULL); return); 0; )

#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 //veličine matrica #define N 5 #define M 5 //posebna struktura za stream podataka typedef struct( int rowN; //broj reda koji se obrađuje int rowSize; //veličina reda //pokazivači na matrice int** array1; int** array2; int** resArr; ) pthrData; void* threadFunc(void* thread_data)( //dobijemo strukturu podataka pthrData *data = (pthrData*) thread_data; //dodamo elemente reda matrice i pohranimo rezultat za(int i = 0; i< data->rowSize; i++) data->resArr[i] = data->array1[i] + data->array2[i]; return NULL; ) int main()( //dodijeli 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*)); //dodijeli memoriju za elemente matrice for(int i = 0; i< M; i++){ matrix1[i] = (int*) malloc(M * sizeof(int)); matrix2[i] = (int*) malloc(M * sizeof(int)); resultMatrix[i] = (int*) malloc(M * sizeof(int)); } //инициализируем начальными значениями for(int i = 0; i < N; i++){ for(int j = 0; j < M; j++){ matrix1[i][j] = i; matrix2[i][j] = j; resultMatrix[i][j] = 0; } } //выделяем память под массив идентификаторов потоков pthread_t* threads = (pthread_t*) malloc(N * sizeof(pthread_t)); //сколько потоков - столько и структур с потоковых данных pthrData* threadData = (pthrData*) malloc(N * sizeof(pthrData)); //инициализируем структуры потоков for(int i = 0; i < N; i++){ threadData[i].rowN = i; threadData[i].rowSize = M; threadData[i].array1 = matrix1; threadData[i].array2 = matrix2; threadData[i].resArr = resultMatrix; //запускаем поток pthread_create(&(threads[i]), NULL, threadFunc, &threadData[i]); } //ожидаем выполнение всех потоков for(int i = 0; i < N; i++) pthread_join(threads[i], NULL); //освобождаем память free(threads); free(threadData); for(int i = 0; i < N; i++){ free(matrix1[i]); free(matrix2[i]); free(resultMatrix[i]); } free(matrix1); free(matrix2); free(resultMatrix); return 0; }

#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 ;

mob_info