Principi rada u operativnim sistemima sličnim UNIX-u koristeći Linux kao primjer. Kreiranje vlastitog laganog procesa

Jedan od najneugodnijih trenutaka pri prelasku iz okruženja u Windows baziran za upotrebu komandna linija– gubitak lakog multitaskinga. Čak i na Linuxu, ako koristite X Window sistem, možete koristiti miš da jednostavno kliknete novi program i otvori ga. Na komandnoj liniji, međutim, prilično ste zaglavili sa monotaskingom. U ovom članku ćemo vam pokazati kako raditi više zadataka u Linuxu koristeći komandnu liniju.

Pozadina i upravljanje prioritetnim procesima

Međutim, još uvijek postoje načini za obavljanje više zadataka u Linuxu, a neki od njih su sveobuhvatniji od drugih. Jedna ugrađena metoda koja ne zahtijeva nikakve dodatne softver, je jednostavno kretanje procesa u pozadini iu prvom planu. Pričamo o ovome. Međutim, on ima neke nedostatke.

Nezaštićena

Prvo Da biste poslali proces u pozadini, prvo ga morate obustaviti. Nije moguće poslati program koji je već pokrenut u pozadini i istovremeno ga održavati.

Drugo, morate prekinuti tok posla da započnete novu naredbu. Morate izaći iz onoga što trenutno radite i ukucati više komandi u shell. Radi, ali je nezgodno.

Treće, trebali biste pratiti izlaze iz pozadinskih procesa. Svaki njihov izlaz će se pojaviti na komandnoj liniji i ometat će ono što trenutno radite. Dakle, pozadinski zadaci moraju ili preusmjeriti svoj izlaz na zaseban fajl, ili ih je potrebno potpuno onemogućiti.

Zbog ovih nedostataka, postoje veliki problemi u upravljanju pozadinskim i prvim planom procesa. Najbolja odluka– koristite uslužni program komandne linije “screen” kao što je prikazano ispod.

Ali prvo - otvorit ćete novu SSH sesiju

Ne zaboravite da upravo otvarate novu SSH sesiju.

Može biti nezgodno stalno otvarati nove sesije. I tada vam treba "ekran"

Utility ekran omogućava vam da kreirate više radnih tokova otvorenih u isto vrijeme - najbliži analog "prozorima". Podrazumevano je dostupan u redovnim Linux repozitorijumima. Instalirajte ga na CentOS/RHEL koristeći sljedeću naredbu:

Sudo yum instalacijski ekran

Otvaranje novog ekrana

Sada započnite svoju sesiju upisivanjem “screen”.

Ovo će stvoriti prazan prozor unutar postojeće SSH sesije i dajte joj broj koji je prikazan u zaglavlju ovako:

Naš ekran ovdje je označen brojem “0” kao što je prikazano. Na ovom snimku ekrana koristimo lažnu naredbu „čitaj“ da blokiramo terminal i natjeramo ga da čeka na unos. Sada recimo da želimo da radimo nešto drugo dok čekamo.

Otvoriti novi ekran i uradimo nešto drugo, štampamo:

Ctrl+a c

“ctrl+a” je zadana kombinacija tipki za upravljanje ekranima u programu za ekran. Ono što upišete nakon toga određuje radnju. Na primjer:

  • ctrl+a c – C aktivira novi ekran
  • ctrl+a [broj]– skočite na određeni broj ekrana
  • ctrl+a k – K isključuje trenutni ekran
  • ctrl+a n – Idite na ekran n
  • ctrl+a “- prikazuje sve aktivne ekrane u sesiji

Ako pritisnemo “ctrl+a c” dobićemo novi ekran sa novim brojem.

Možete koristiti tastere sa strelicama da se krećete kroz listu i idete na ekran koji želite.
Ekrani su najbliži što ćete dobiti „prozorima“, poput sistema u komandi Linux string. Naravno, to nije tako jednostavno kao klik mišem, ali grafički podsistem je veoma intenzivan. Sa ekranima, možete dobiti gotovo istu funkcionalnost i omogućiti potpuni multitasking!

Procesi u UNIX-u

U UNIX-u, glavno sredstvo organizacije i jedinica multitaskinga je proces. Operativni sistem manipuliše slikom procesa, koja predstavlja programski kod, kao i delovima podataka procesa koji definišu okruženje izvršavanja.

Tokom izvršavanja ili dok čekaju „u krilima“, procesi su sadržani u virtuelnoj memoriji sa organizacijom stranica. Dio ove virtualne memorije je mapiran u fizičku memoriju. Dio fizičke memorije rezerviran je za kernel operativnog sistema. Korisnici mogu pristupiti samo preostaloj memoriji za procese. Ako je potrebno, stranice procesne memorije se zamjenjuju iz fizičke memorije na disk, u područje zamjene. Prilikom pristupa stranici u virtuelnoj memoriji, ako nije u fizičkoj memoriji, ona se zamjenjuje s diska.

Virtuelna memorija je implementirana i automatski održavana od strane UNIX kernela.

Vrste procesa

Postoje tri tipa procesa u UNIX operativnim sistemima: sistemski, demonski procesi I procesi aplikacije.

Sistemski procesi dio su jezgra i uvijek se nalaze u ram memorija. Sistemski procesi nemaju odgovarajuće programe u obliku izvršnih datoteka i pokreću se na poseban način kada se inicijalizira jezgro sistema. Izvršne instrukcije i podaci ovih procesa nalaze se u jezgru sistema, tako da mogu pozvati funkcije i pristupiti podacima kojima drugi procesi ne mogu pristupiti.

Sistemski procesi uključuju proces početne inicijalizacije, u tome, koji je rodonačelnik svih ostalih procesa. Iako u tome nije dio kernela, a njegovo izvršavanje se odvija iz izvršne datoteke, njegov rad je od vitalnog značaja za funkcioniranje cijelog sistema u cjelini.

Demoni- to su neinteraktivni procesi koji se pokreću na uobičajen način - učitavanjem odgovarajućih programa u memoriju, a izvršavaju se u pozadini. Obično se demoni pokreću tokom inicijalizacije sistema, ali nakon što je kernel inicijaliziran, i osiguravaju rad različitih UNIX podsistema: terminalski pristupni sistemi, sistemi za štampanje, mrežne usluge itd. Demoni nisu povezani ni sa jednim korisnikom. Većinu vremena, demoni čekaju da jedan ili drugi proces zatraži specifičnu uslugu.



TO procesi aplikacije uključuje sve ostale procese koji se pokreću na sistemu. Obično su to procesi pokrenuti unutar korisničke sesije. Najvažniji korisnički proces je inicijalni interpreter komandi, koji omogućava izvršavanje korisničkih naredbi na UNIX sistemu.

Korisnički procesi mogu se izvoditi i interaktivno (preventivno) i pozadinski režimi. Interaktivni procesi imaju ekskluzivno vlasništvo nad terminalom, a dok takav proces ne završi svoje izvršenje, korisnik nema pristup komandnoj liniji.

Procesni atributi

UNIX proces ima niz atributa koji mu to dozvoljavaju operativni sistem upravljati njegovim radom. Glavni atributi:

· ID procesa (PID), omogućavajući kernelu sistema da razlikuje procese. Kada se kreira novi proces, kernel mu dodjeljuje sljedeći besplatni (tj. nije povezan ni sa jednim procesom) identifikator. Dodjela identifikatora se obično odvija uzlaznim redoslijedom, tj. ID novog procesa je veći od ID-a procesa kreiranog prije njega. Ako ID dostigne maksimalnu vrijednost (obično 65737), sljedeći proces će primiti minimalni slobodni PID i ciklus se ponavlja. Kada proces izađe, kernel oslobađa identifikator koji je koristio.

· ID roditeljskog procesa (PPID)– identifikator procesa koji je pokrenuo ovaj proces. Svi procesi u sistemu osim sistemski procesi i proces u tome, koji je progenitor preostalih procesa, generisani su jednim od postojećih ili prethodno postojećih procesa.

· Korekcija prioriteta (NI)– relativni prioritet procesa, uzet u obzir od strane planera prilikom određivanja redosleda pokretanja. Stvarna distribucija resursa procesora određena je prioritetom izvršenja (atribut PRI), u zavisnosti od nekoliko faktora, posebno od datog relativnog prioriteta. Relativni prioritet sistem ne mijenja tokom cijelog trajanja procesa, iako ga korisnik ili administrator može promijeniti prilikom pokretanja procesa pomoću naredbe lijepo. Raspon vrijednosti povećanja prioriteta na većini sistema je od -20 do 20. Ako nije specificirano povećanje, koristi se zadana vrijednost od 10. Pozitivan prirast znači smanjenje trenutnog prioriteta. Obični korisnici mogu postaviti samo pozitivan prirast i time samo smanjiti prioritet. Korisnik root može postaviti negativan prirast, što povećava prioritet procesa i samim tim doprinosi njegovom većem brz rad. Za razliku od relativnog prioriteta, prioritet izvršenja procesa se dinamički mijenja od strane planera.

· Terminalna linija (TTY)– terminal ili pseudo-terminal povezan s procesom. Ovaj terminal je povezan sa standardom potoci: unos, slobodan dan I protok poruke o greškama. streamovi ( programskih kanala) su standardnim sredstvima međuprocesna komunikacija u UNIX OS-u. Daemon procesi nisu povezani s terminalom.

· Pravi (UID) i efektivni (EUID) identifikatori korisnika. Pravi korisnički ID datog procesa je ID korisnika koji je pokrenuo proces. Efektivni identifikator se koristi za određivanje prava pristupa procesa sistemskim resursima (prvenstveno resursima sistem podataka). Obično su pravi i efektivni identifikatori isti, tj. proces ima ista prava u sistemu kao i korisnik koji ga je pokrenuo. Međutim, postavkama je moguće dati procesu više prava od korisnika SUID bit kada je efektivni identifikator postavljen na identifikator vlasnika izvršne datoteke (na primjer, korisnika root).

· Pravi (GID) i efektivni (EGID) grupni identifikatori. Pravi ID grupe jednak je primarnom ili trenutnom ID-u grupe korisnika koji je pokrenuo proces. Efektivni identifikator se koristi za određivanje prava pristupa sistemskim resursima u ime grupe. Obično je efektivni ID grupe isti kao i stvarni. Ali ako je izvršna datoteka postavljena na SGID bit, takva datoteka se izvršava sa efektivnim ID-om grupe vlasnika.

LABORATORIJSKI RAD br.3

PROGRAMIRANJE ZA VIŠE ZADATAKA INLINUX

1. Cilj rada: Upoznajte se sa gcc kompajlerom, tehnikama za otklanjanje grešaka u programu i funkcijama za rad sa procesima.

2. Kratke teorijske informacije.

Minimalni skup prekidača gcc kompajlera je - Wall (prikaz 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 u Linux u glibc) koristi prednosti višezadać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).

Da kreirate novi proces, koristite funkciju vilice:

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 uzrokovati 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, 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 porodice exec:

// ako je podređeni proces pokrenut, onda pozovite program


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

Roditeljski proces često treba da razmjenjuje informacije sa svojom djecom, ili se barem sinhronizira s njima, kako bi izvršio operacije u pravo vrijeme. 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 provjeri završetak navedenog procesa.

Ako trebate saznati stanje podređenog procesa kada se završi i vrijednost koju vraća, 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)); )

Za promjenu prioriteta pokrenutih procesa koriste se setpriority i funkcije. 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()));

Da biste ubili proces, koristite funkciju kill:

#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 grupe kojoj trenutni proces pripada.

sig - tip signala. Neke vrste signala u Linuxu:

SIGKILL Ovaj signal uzrokuje da se proces odmah završi. Proces ne može zanemariti ovaj signal.

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 i opisima funkcija jezika C, koristite uputstva za man i info.

3.2. Za otklanjanje grešaka u programima zgodno je koristiti ugrađeni editor upravitelja datoteka Ponoćni komandant(MC), koji u boji ističe različite jezičke konstrukcije i ukazuje na poziciju kursora u datoteci (red, kolona) u gornjem redu ekrana.

3.3. Upravitelj datoteka Midnight Commander ima bafer komandi koji se može pozvati prečicom na tastaturi - H, koji se može pomicati pomoću strelica kursora (gore i dolje). Da biste umetnuli naredbu iz bafera u komandnu liniju, koristite tipku , za uređivanje komande iz bafera - tipke<- и ->, I .


3.4. Zapamtite da trenutni direktorij nije sadržan u putanji, tako da morate pokrenuti program kao "./print_pid" iz komandne linije. U MC-u samo zadržite pokazivač iznad datoteke i kliknite .

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 iz konzole u datoteku: ./test > result. poruka

3.7. Za pristup datotekama kreiranim na Linux server, koristite ftp protokol, za koji je klijentski program dostupan u Windows 2000 i ugrađen je u file manager FAR. Gde Račun a lozinka je ista kao kod povezivanja preko ssh-a.

4.1. Upoznajte se sa opcijama i metodama gcc kompajlera za otklanjanje grešaka u programima.

4.2. Za varijante zadataka iz laboratorijskog rada br. 1 napišite i otklonite greške u programu koji implementira generirani proces.

4.3. Za opcije zadataka od laboratorijski rad Br. 1 napisati i otkloniti greške 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 opciji).

4.4. Za varijante zadataka iz laboratorijskog rada br. 1 napisati i debagovati program koji implementira roditeljski proces koji poziva i prati stanje podređenih procesa - funkcija (čekaju se na njihov završetak ili ih uništavaju, u zavisnosti od varijante).

5. Opcije za zadatke. Vidi opcije za zadatke iz laboratorijskog rada br.1

6. Sadržaj izvještaja.

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 na Linuxu.

7.2. Šta je pid, kako ga odrediti u operativnom sistemu i programu?

7.3. Funkcija viljuške - svrha, aplikacija, povratna vrijednost.

7.4. Kako pokrenuti funkciju u pokrenutom procesu? Program?

7.5. Načini sinhronizacije nadređenih i podređenih procesa.

7.6. Kako mogu saznati stanje pokrenutog procesa kada se završi i vrijednost koju vraća?

7.7. Kako upravljati prioritetima procesa?

7.8. Kako ubiti proces u operativnom sistemu i programu?

Nastavljamo temu višenitnog rada u Linux kernelu. Prošli put sam govorio o prekidima, njihovoj obradi i tasklet-ima, a pošto je prvobitno bilo zamišljeno da ovo bude jedan članak, u svojoj priči o radnom redu osvrću se na tasklete, pod pretpostavkom da je čitalac već upoznat sa njima.
Kao i prošli put, pokušaću da svoju priču učinim što detaljnijom i detaljnijom.

Članci u seriji:

  1. Multitasking u Linux kernelu: radni red

Radni red

Radni red- ovo su složeniji i teži entiteti od zadataka. Ovdje neću ni pokušavati da opisujem sve zamršenosti implementacije, ali nadam se da ću manje-više detaljno analizirati najvažnije stvari.
Radni redovi, poput zadataka, služe za odloženu obradu prekida (iako se mogu koristiti u druge svrhe), ali se, za razliku od taskleta, izvršavaju u kontekstu procesa kernela; shodno tome, ne moraju biti atomski i mogu koristiti spavanje () funkcija, razni alati za sinhronizaciju, itd.

Hajde da prvo shvatimo kako je uopšte organizovan proces obrade radnog reda. Na slici je to vrlo približno i pojednostavljeno, kako se sve zapravo dešava detaljno je opisano u nastavku.

Nekoliko entiteta je uključeno u ovu tamnu materiju.
prvo, radni predmet(samo rad ukratko) je struktura koja opisuje funkciju (na primjer, rukovalac prekida) koju želimo zakazati.Može se smatrati analogom strukture taskleta. Prilikom zakazivanja, Tasklets su dodani u redove koji su skriveni od korisnika, ali sada moramo koristiti poseban red - radni red.
Tasklets se prikupljaju pomoću funkcije rasporeda, a radni red se obrađuje posebnim nitima koje se nazivaju radnici.
Radnik' omogućava asinhrono izvršavanje posla iz radnog reda. Iako rad nazivaju redosledom rotacije, u opštem slučaju nema govora o strogom, sekvencijalnom izvršavanju: na kraju krajeva, ovde se dešavaju prevencija, spavanje, čekanje itd.

Općenito, radnici su niti kernela, to jest, njima upravlja glavni Linux kernel planer. Ali radnici djelimično intervenišu u planiranju dodatne organizacije paralelnog izvođenja poslova. O tome će se detaljnije govoriti u nastavku.

Da bih ocrtao glavne mogućnosti mehanizma redova rada, predlažem da istražite API.

O redu čekanja i njegovom kreiranju

alloc_workqueue(fmt, flags, max_active, args...)
Parametri fmt i args su printf format za ime i argumente za njega. Parametar max_activate je odgovoran za maksimalan broj radova koji se iz ovog reda mogu izvršiti paralelno na jednom CPU-u.
Red se može kreirati sa sljedećim oznakama:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREEZABLE
  • WQ_MEM_RECLAIM
Posebnu pažnju treba obratiti na zastavu WQ_UNBOUND. Na osnovu prisustva ove zastavice, redovi se dijele na vezane i nevezane.
U povezanim redovima Kada se dodaju, radnja je vezana za trenutni CPU, odnosno u takvim redovima se radnja izvršava na jezgri koja ga zakazuje. U tom smislu, vezani redovi liče na tasklete.
U nevezanim redovima rad se može izvršiti na bilo kojoj jezgri.

Važna karakteristika implementacije radnog reda u Linux kernelu je dodatna organizacija paralelnog izvršavanja koja je prisutna u vezanim redovima. U nastavku je detaljnije napisano, ali sada ću reći da se izvodi na način da se koristi što manje memorije, a da procesor ne miruje. Ovo je sve implementirano uz pretpostavku da jedan rad ne koristi previše ciklusa procesora.
Ovo nije slučaj za nevezane redove. U suštini, takvi redovi jednostavno pružaju kontekst radnicima i pokreću ih što je ranije moguće.
Stoga, nevezane redove treba koristiti ako se očekuje CPU intenzivno radno opterećenje, jer će se u ovom slučaju planer pobrinuti za paralelno izvršavanje na više jezgara.

Po analogiji sa taskletima, radovima se može dodijeliti prioritet izvršenja, normalan ili visok. Prioritet je zajednički za cijeli red čekanja. Po defaultu, red ima normalan prioritet i ako postavite zastavicu WQ_HIGHPRI, zatim, shodno tome, visoka.

Zastava WQ_CPU_INTENSIVE ima smisla samo za vezane redove. Ova zastavica je odbijanje učešća u dodatnoj organizaciji paralelnog izvršenja. Ovu zastavicu treba koristiti kada se očekuje da će posao potrošiti puno CPU vremena, u kom slučaju je bolje prebaciti odgovornost na planer. Ovo je detaljnije opisano u nastavku.

Zastave WQ_FREEZABLE I WQ_MEM_RECLAIM specifične su i izvan okvira teme, pa se na njima nećemo detaljnije zadržavati.

Ponekad ima smisla ne kreirati vlastite redove, već koristiti uobičajene. Glavni:

  • system_wq - vezani red za brz rad
  • system_long_wq - vezani red za radove za koje se očekuje da će trebati dugo vremena da se izvrše
  • system_unbound_wq - nevezani red

O radu i njihovom planiranju

A sada da se pozabavimo radovima. Prvo, pogledajmo makroe za inicijalizaciju, deklaraciju i pripremu:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* u vrijeme kompajliranja */ INIT(_DELAYED)_WORK(_work, _func); /* tokom izvršavanja */ PREPARE(_DELAYED)_WORK(_work, _func); /* za promjenu funkcije koja se izvršava */
Radovi se dodaju u red pomoću funkcija:
bool queue_work(struct workqueue_struct *wq, struct work_struct *work); bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, nepotpisano dugo kašnjenje); /* rad će biti dodat u red tek nakon isteka kašnjenja */
O tome se vrijedi detaljnije zadržati. Iako navedemo red kao parametar, u stvari, radnja se ne postavljaju u sam radni red, kao što bi moglo izgledati, već u potpuno drugačiji entitet - u listu reda čekanja strukture worker_pool. Struktura worker_pool, u stvari, je najvažniji entitet u organizaciji mehanizma redova rada, iako za korisnika ostaje iza scene. S njima rade radnici iu njima su sadržane sve osnovne informacije.

Sada da vidimo koji su bazeni u sistemu.
Za početak, skupovi za vezane redove (na slici). Za svaki CPU, dva skupa radnika su statički dodijeljena: jedan za rad visokog prioriteta, drugi za rad s normalnim prioritetom. Odnosno, ako imamo četiri jezgre, tada će biti samo osam vezanih pulova, uprkos činjenici da može biti onoliko radnih redova koliko želite.
Kada kreiramo radni red, on ima uslugu dodijeljenu za svaki CPU pool_workqueue(pwq). Svaki takav pool_workqueue je pridružen skupu radnika, koji je dodijeljen na istom CPU-u i po prioritetu odgovara tipu reda. Preko njih radni red stupa u interakciju sa skupom radnika.
Radnici neselektivno izvode posao iz grupe radnika, bez razlikovanja kojem su radnom redu prvobitno pripadali.

Za nevezane redove, skupovi radnika se dodeljuju dinamički. Svi redovi se mogu podijeliti u klase ekvivalencije prema njihovim parametrima, a za svaku takvu klasu kreira se vlastiti skup radnika. Pristupa im se pomoću posebne hash tablice, gdje je ključ skup parametara, a vrijednost je skup radnika.
Zapravo, za nevezane redove sve je malo složenije: ako su za vezane redove pwq i redovi kreirani za svaki CPU, ovdje se kreiraju za svaki NUMA čvor, ali ovo je dodatna optimizacija koju nećemo detaljno razmatrati.

Sve vrste sitnica

Također ću dati nekoliko funkcija iz API-ja da upotpunim sliku, ali neću govoriti o njima detaljno:
/* Prisilno dovršavanje */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Otkazivanje izvršenja posla */ bool cancel_work_sync(struct work_struct *work); bool cancel_delayed_work(struct delayed_work *dwork); bool cancel_delayed_work_sync(struct delayed_work *dwork); /* Brisanje reda */ void destroy_workqueue(struct workqueue_struct *wq);

Kako radnici rade svoj posao

Sada kada smo upoznati sa API-jem, pokušajmo detaljnije razumjeti kako sve funkcionira i kako se njime upravlja.
Svaki bazen ima skup radnika koji se bave zadacima. Štaviše, broj radnika se dinamično mijenja, prilagođavajući se trenutnoj situaciji.
Kao što smo već saznali, radnici su niti koje obavljaju rad u kontekstu kernela. Radnik ih dohvaća redom, jedan za drugim, iz skupa radnika koji je s njim povezan, a radnici, kao što već znamo, mogu pripadati različitim izvornim redovima.

Radnici mogu uslovno biti u tri logička stanja: mogu biti u stanju mirovanja, u radu ili u upravljanju.
Radnik može stajati u praznom hodu i ne radi ništa. To je, na primjer, kada se sav posao već izvodi. Kada radnik uđe u ovo stanje, on odlazi u san i, shodno tome, neće izvršiti dok se ne probudi;
Ako upravljanje pulom nije potrebno i lista planiranih radova nije prazna, tada radnik počinje da ih izvršava. Takve radnike ćemo konvencionalno zvati trčanje.
Ako je potrebno, tu ulogu preuzima radnik menadžer bazen. Pul može imati samo jednog rukovodećeg radnika ili nijednog radnika. Njegov zadatak je održavanje optimalnog broja radnika po bazenu. Kako to radi? Prvo se brišu radnici koji su dugo bili neaktivni. Drugo, novi radnici se stvaraju ako se istovremeno ispune tri uslova:

  • ima još zadataka za završetak (radovi u bazenu)
  • nema nezaposlenih radnika
  • nema radnika koji rade (odnosno aktivni i ne spavaju)
Međutim, posljednji uvjet ima svoje nijanse. Ako su redovi bazena nevezani, tada se aktivni radnici ne uzimaju u obzir; za njih je ovaj uvjet uvijek istinit. Isto je i u slučaju da radnik izvršava zadatak iz povezanog, ali sa zastavicom WQ_CPU_INTENSIVE, redovi. Štaviše, u slučaju vezanih redova, pošto radnici rade sa radovima iz zajedničkog pula (koji je jedan od dva za svako jezgro na gornjoj slici), ispada da se neki od njih računaju kao radni, a neki ne. Iz toga proizilazi i da obavljanje poslova iz WQ_CPU_INTENSIVE redovi možda neće početi odmah, ali sami ne ometaju izvođenje drugog posla. Sada bi trebalo biti jasno zašto se ova zastavica tako zove i zašto se koristi kada očekujemo da će posao trajati dugo.

Obračun radnih radnika vrši se direktno iz glavnog rasporeda jezgra Linuxa. Ovaj kontrolni mehanizam osigurava optimalan nivo konkurentnosti, sprečavajući da radni red stvori previše radnika, ali i ne uzrokuje nepotrebno čekanje posla predugo.

Oni koji su zainteresovani mogu pogledati radnu funkciju u kernelu, zove se worker_thread().

Sve opisane funkcije i strukture mogu se naći detaljnije u datotekama include/linux/workqueue.h, kernel/workqueue.c I kernel/workqueue_internal.h. Postoji i dokumentacija o radnom redu u Documentation/workqueue.txt.

Također je vrijedno napomenuti da se mehanizam reda rada koristi u kernelu ne samo za odgođenu obradu prekida (iako je ovo prilično čest scenario).

Tako smo pogledali mehanizme za odgođeno rukovanje prekidima u Linux kernelu – tasklet i workqueue, koji su poseban oblik multitaskinga. O prekidima, programskim programima i radnim redovima možete pročitati u knjizi “Linux Device Drivers” Jonathana Corbeta, Grega Kroah-Hartmana, Alessandra Rubinija, iako su informacije tamo ponekad zastarjele.

Nastavljamo temu višenitnog rada u Linux kernelu. Prošli put sam govorio o prekidima, njihovoj obradi i tasklet-ima, a pošto je prvobitno bilo zamišljeno da ovo bude jedan članak, u svojoj priči o radnom redu osvrću se na tasklete, pod pretpostavkom da je čitalac već upoznat sa njima.
Kao i prošli put, pokušaću da svoju priču učinim što detaljnijom i detaljnijom.

Članci u seriji:

  1. Multitasking u Linux kernelu: radni red

Radni red

Radni red- ovo su složeniji i teži entiteti od zadataka. Ovdje neću ni pokušavati da opisujem sve zamršenosti implementacije, ali nadam se da ću manje-više detaljno analizirati najvažnije stvari.
Radni redovi, poput zadataka, služe za odloženu obradu prekida (iako se mogu koristiti u druge svrhe), ali se, za razliku od taskleta, izvršavaju u kontekstu procesa kernela; shodno tome, ne moraju biti atomski i mogu koristiti spavanje () funkcija, razni alati za sinhronizaciju, itd.

Hajde da prvo shvatimo kako je uopšte organizovan proces obrade radnog reda. Na slici je to vrlo približno i pojednostavljeno, kako se sve zapravo dešava detaljno je opisano u nastavku.

Nekoliko entiteta je uključeno u ovu tamnu materiju.
prvo, radni predmet(samo rad ukratko) je struktura koja opisuje funkciju (na primjer, rukovalac prekida) koju želimo zakazati.Može se smatrati analogom strukture taskleta. Prilikom zakazivanja, Tasklets su dodani u redove koji su skriveni od korisnika, ali sada moramo koristiti poseban red - radni red.
Tasklets se prikupljaju pomoću funkcije rasporeda, a radni red se obrađuje posebnim nitima koje se nazivaju radnici.
Radnik' omogućava asinhrono izvršavanje posla iz radnog reda. Iako rad nazivaju redosledom rotacije, u opštem slučaju nema govora o strogom, sekvencijalnom izvršavanju: na kraju krajeva, ovde se dešavaju prevencija, spavanje, čekanje itd.

Općenito, radnici su niti kernela, to jest, njima upravlja glavni Linux kernel planer. Ali radnici djelimično intervenišu u planiranju dodatne organizacije paralelnog izvođenja poslova. O tome će se detaljnije govoriti u nastavku.

Da bih ocrtao glavne mogućnosti mehanizma redova rada, predlažem da istražite API.

O redu čekanja i njegovom kreiranju

alloc_workqueue(fmt, flags, max_active, args...)
Parametri fmt i args su printf format za ime i argumente za njega. Parametar max_activate je odgovoran za maksimalan broj radova koji se iz ovog reda mogu izvršiti paralelno na jednom CPU-u.
Red se može kreirati sa sljedećim oznakama:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREEZABLE
  • WQ_MEM_RECLAIM
Posebnu pažnju treba obratiti na zastavu WQ_UNBOUND. Na osnovu prisustva ove zastavice, redovi se dijele na vezane i nevezane.
U povezanim redovima Kada se dodaju, radnja je vezana za trenutni CPU, odnosno u takvim redovima se radnja izvršava na jezgri koja ga zakazuje. U tom smislu, vezani redovi liče na tasklete.
U nevezanim redovima rad se može izvršiti na bilo kojoj jezgri.

Važna karakteristika implementacije radnog reda u Linux kernelu je dodatna organizacija paralelnog izvršavanja koja je prisutna u vezanim redovima. U nastavku je detaljnije napisano, ali sada ću reći da se izvodi na način da se koristi što manje memorije, a da procesor ne miruje. Ovo je sve implementirano uz pretpostavku da jedan rad ne koristi previše ciklusa procesora.
Ovo nije slučaj za nevezane redove. U suštini, takvi redovi jednostavno pružaju kontekst radnicima i pokreću ih što je ranije moguće.
Stoga, nevezane redove treba koristiti ako se očekuje CPU intenzivno radno opterećenje, jer će se u ovom slučaju planer pobrinuti za paralelno izvršavanje na više jezgara.

Po analogiji sa taskletima, radovima se može dodijeliti prioritet izvršenja, normalan ili visok. Prioritet je zajednički za cijeli red čekanja. Po defaultu, red ima normalan prioritet i ako postavite zastavicu WQ_HIGHPRI, zatim, shodno tome, visoka.

Zastava WQ_CPU_INTENSIVE ima smisla samo za vezane redove. Ova zastavica je odbijanje učešća u dodatnoj organizaciji paralelnog izvršenja. Ovu zastavicu treba koristiti kada se očekuje da će posao potrošiti puno CPU vremena, u kom slučaju je bolje prebaciti odgovornost na planer. Ovo je detaljnije opisano u nastavku.

Zastave WQ_FREEZABLE I WQ_MEM_RECLAIM specifične su i izvan okvira teme, pa se na njima nećemo detaljnije zadržavati.

Ponekad ima smisla ne kreirati vlastite redove, već koristiti uobičajene. Glavni:

  • system_wq - vezani red za brz rad
  • system_long_wq - vezani red za radove za koje se očekuje da će trebati dugo vremena da se izvrše
  • system_unbound_wq - nevezani red

O radu i njihovom planiranju

A sada da se pozabavimo radovima. Prvo, pogledajmo makroe za inicijalizaciju, deklaraciju i pripremu:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* u vrijeme kompajliranja */ INIT(_DELAYED)_WORK(_work, _func); /* tokom izvršavanja */ PREPARE(_DELAYED)_WORK(_work, _func); /* za promjenu funkcije koja se izvršava */
Radovi se dodaju u red pomoću funkcija:
bool queue_work(struct workqueue_struct *wq, struct work_struct *work); bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, nepotpisano dugo kašnjenje); /* rad će biti dodat u red tek nakon isteka kašnjenja */
O tome se vrijedi detaljnije zadržati. Iako navedemo red kao parametar, u stvari, radnja se ne postavljaju u sam radni red, kao što bi moglo izgledati, već u potpuno drugačiji entitet - u listu reda čekanja strukture worker_pool. Struktura worker_pool, u stvari, je najvažniji entitet u organizaciji mehanizma redova rada, iako za korisnika ostaje iza scene. S njima rade radnici iu njima su sadržane sve osnovne informacije.

Sada da vidimo koji su bazeni u sistemu.
Za početak, skupovi za vezane redove (na slici). Za svaki CPU, dva skupa radnika su statički dodijeljena: jedan za rad visokog prioriteta, drugi za rad s normalnim prioritetom. Odnosno, ako imamo četiri jezgre, tada će biti samo osam vezanih pulova, uprkos činjenici da može biti onoliko radnih redova koliko želite.
Kada kreiramo radni red, on ima uslugu dodijeljenu za svaki CPU pool_workqueue(pwq). Svaki takav pool_workqueue je pridružen skupu radnika, koji je dodijeljen na istom CPU-u i po prioritetu odgovara tipu reda. Preko njih radni red stupa u interakciju sa skupom radnika.
Radnici neselektivno izvode posao iz grupe radnika, bez razlikovanja kojem su radnom redu prvobitno pripadali.

Za nevezane redove, skupovi radnika se dodeljuju dinamički. Svi redovi se mogu podijeliti u klase ekvivalencije prema njihovim parametrima, a za svaku takvu klasu kreira se vlastiti skup radnika. Pristupa im se pomoću posebne hash tablice, gdje je ključ skup parametara, a vrijednost je skup radnika.
Zapravo, za nevezane redove sve je malo složenije: ako su za vezane redove pwq i redovi kreirani za svaki CPU, ovdje se kreiraju za svaki NUMA čvor, ali ovo je dodatna optimizacija koju nećemo detaljno razmatrati.

Sve vrste sitnica

Također ću dati nekoliko funkcija iz API-ja da upotpunim sliku, ali neću govoriti o njima detaljno:
/* Prisilno dovršavanje */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Otkazivanje izvršenja posla */ bool cancel_work_sync(struct work_struct *work); bool cancel_delayed_work(struct delayed_work *dwork); bool cancel_delayed_work_sync(struct delayed_work *dwork); /* Brisanje reda */ void destroy_workqueue(struct workqueue_struct *wq);

Kako radnici rade svoj posao

Sada kada smo upoznati sa API-jem, pokušajmo detaljnije razumjeti kako sve funkcionira i kako se njime upravlja.
Svaki bazen ima skup radnika koji se bave zadacima. Štaviše, broj radnika se dinamično mijenja, prilagođavajući se trenutnoj situaciji.
Kao što smo već saznali, radnici su niti koje obavljaju rad u kontekstu kernela. Radnik ih dohvaća redom, jedan za drugim, iz skupa radnika koji je s njim povezan, a radnici, kao što već znamo, mogu pripadati različitim izvornim redovima.

Radnici mogu uslovno biti u tri logička stanja: mogu biti u stanju mirovanja, u radu ili u upravljanju.
Radnik može stajati u praznom hodu i ne radi ništa. To je, na primjer, kada se sav posao već izvodi. Kada radnik uđe u ovo stanje, on odlazi u san i, shodno tome, neće izvršiti dok se ne probudi;
Ako upravljanje pulom nije potrebno i lista planiranih radova nije prazna, tada radnik počinje da ih izvršava. Takve radnike ćemo konvencionalno zvati trčanje.
Ako je potrebno, tu ulogu preuzima radnik menadžer bazen. Pul može imati samo jednog rukovodećeg radnika ili nijednog radnika. Njegov zadatak je održavanje optimalnog broja radnika po bazenu. Kako to radi? Prvo se brišu radnici koji su dugo bili neaktivni. Drugo, novi radnici se stvaraju ako se istovremeno ispune tri uslova:

  • ima još zadataka za završetak (radovi u bazenu)
  • nema nezaposlenih radnika
  • nema radnika koji rade (odnosno aktivni i ne spavaju)
Međutim, posljednji uvjet ima svoje nijanse. Ako su redovi bazena nevezani, tada se aktivni radnici ne uzimaju u obzir; za njih je ovaj uvjet uvijek istinit. Isto je i u slučaju da radnik izvršava zadatak iz povezanog, ali sa zastavicom WQ_CPU_INTENSIVE, redovi. Štaviše, u slučaju vezanih redova, pošto radnici rade sa radovima iz zajedničkog pula (koji je jedan od dva za svako jezgro na gornjoj slici), ispada da se neki od njih računaju kao radni, a neki ne. Iz toga proizilazi i da obavljanje poslova iz WQ_CPU_INTENSIVE redovi možda neće početi odmah, ali sami ne ometaju izvođenje drugog posla. Sada bi trebalo biti jasno zašto se ova zastavica tako zove i zašto se koristi kada očekujemo da će posao trajati dugo.

Obračun radnih radnika vrši se direktno iz glavnog rasporeda jezgra Linuxa. Ovaj kontrolni mehanizam osigurava optimalan nivo konkurentnosti, sprečavajući da radni red stvori previše radnika, ali i ne uzrokuje nepotrebno čekanje posla predugo.

Oni koji su zainteresovani mogu pogledati radnu funkciju u kernelu, zove se worker_thread().

Sve opisane funkcije i strukture mogu se naći detaljnije u datotekama include/linux/workqueue.h, kernel/workqueue.c I kernel/workqueue_internal.h. Postoji i dokumentacija o radnom redu u Documentation/workqueue.txt.

Također je vrijedno napomenuti da se mehanizam reda rada koristi u kernelu ne samo za odgođenu obradu prekida (iako je ovo prilično čest scenario).

Tako smo pogledali mehanizme za odgođeno rukovanje prekidima u Linux kernelu – tasklet i workqueue, koji su poseban oblik multitaskinga. O prekidima, programskim programima i radnim redovima možete pročitati u knjizi “Linux Device Drivers” Jonathana Corbeta, Grega Kroah-Hartmana, Alessandra Rubinija, iako su informacije tamo ponekad zastarjele.