Principi rada u operacijskim sustavima sličnim UNIX-u na primjeru Linuxa. Stvaranje vlastitog laganog procesa

Jedan od najneugodnijih trenutaka pri prelasku iz okruženja u Temeljen na sustavu Windows za upotrebu naredbeni redak– gubitak lakog obavljanja više zadataka istovremeno. Čak i na Linuxu, ako koristite sustav X Window, možete koristiti miš da jednostavno kliknete novi program i otvori ga. U naredbenom retku, međutim, prilično ste zapeli s monotaskingom. U ovom članku ćemo vam pokazati kako raditi više zadataka u Linuxu pomoću naredbenog retka.

Upravljanje pozadinskim i prioritetnim procesima

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

Nezaštićena

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

Drugo, trebate prekinuti tijek rada da biste pokrenuli novu naredbu. Morate izaći iz onoga što trenutno radite i upisati više naredbi u ljusku. Radi, ali je nezgodno.

Treći, trebali biste nadzirati izlaze iz pozadinskih procesa. Svaki njihov izlaz pojavit će se u naredbenom retku i ometat će ono što trenutno radite. Dakle, pozadinski zadaci moraju ili preusmjeriti svoj izlaz na zasebna datoteka, ili ih je potrebno potpuno onemogućiti.

Zbog ovih nedostataka, postoje veliki problemi u upravljanju procesom u pozadini i prvom planu. Najbolja odluka– koristite pomoćni program naredbenog retka “screen” kao što je prikazano u nastavku.

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”

Korisnost zaslon omogućuje stvaranje više radnih tijekova otvorenih u isto vrijeme - najbliži analog "prozorima". Prema zadanim postavkama dostupan je u običnim spremištima Linuxa. Instalirajte ga na CentOS/RHEL pomoću sljedeće naredbe:

Sudo yum instalacijski zaslon

Otvaranje novog ekrana

Sada započnite svoju sesiju upisivanjem "screen".

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

Naš zaslon ovdje označen je brojem "0" kao što je prikazano. Na ovoj snimci zaslona koristimo lažnu naredbu "čitanje" kako bismo blokirali terminal i natjerali ga da čeka na unos. Sada recimo da želimo učiniti nešto drugo dok čekamo.

Otvoriti novi ekran i radimo nešto drugo, ispisujemo:

Ctrl+a c

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

  • ctrl+a c – C aktivira novi ekran
  • ctrl+a [broj]– skok na određeni broj zaslona
  • ctrl+a k – K isključuje trenutni ekran
  • ctrl+a n – Idi na ekran n
  • ctrl+a “- prikazuje sve aktivne zaslone u sesiji

Ako pritisnemo “ctrl+a c” dobit ćemo novi ekran s novim brojem.

Možete koristiti kursorske tipke za kretanje kroz popis i odlazak na željeni zaslon.
Zasloni su najbliži "prozorima", poput sustava u naredbi Linux niz. Naravno, to nije tako jednostavno kao klik mišem, ali grafički podsustav je vrlo zahtjevan. Sa zaslonima 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 sustav manipulira slikom procesa, koja predstavlja programski kod, kao i odjeljcima podataka procesa koji definiraju okruženje izvršenja.

Tijekom izvođenja ili dok čekaju "na krilima", procesi su sadržani u virtualnoj memoriji s organizacijom stranica. Dio te virtualne memorije mapiran je u fizičku memoriju. Dio fizičke memorije rezerviran je za jezgru operacijskog sustava. Korisnici mogu pristupiti samo preostaloj memoriji za procese. Ako je potrebno, stranice procesne memorije se prebacuju iz fizičke memorije na disk, u swap područje. Prilikom pristupa stranici u virtualnoj memoriji, ako nije u fizičkoj memoriji, ona se prebacuje s diska.

Virtualnu memoriju implementira i automatski održava UNIX kernel.

Vrste procesa

U UNIX operativnim sustavima postoje tri vrste procesa: sistemski, daemon procesi I procesi primjene.

Procesi sustava dio su jezgre i uvijek se nalaze u RAM memorija. Procesi sustava nemaju odgovarajuće programe u obliku izvršnih datoteka i pokreću se na poseban način prilikom inicijalizacije jezgre sustava. Upute za izvršavanje i podaci ovih procesa nalaze se u jezgri sustava, tako da mogu pozivati ​​funkcije i pristupati podacima kojima drugi procesi ne mogu pristupiti.

Procesi sustava uključuju proces početne inicijalizacije, u tome, koji je praotac svih ostalih procesa. Iako u tome nije dio kernela, a njegovo se izvršavanje odvija iz izvršne datoteke, njegov rad je vitalan za funkcioniranje cijelog sustava 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. Tipično, demoni se pokreću tijekom inicijalizacije sustava, ali nakon inicijalizacije kernela, i osiguravaju rad različitih UNIX podsustava: terminalski pristupni sustavi, ispisni sustavi, mrežne usluge itd. Demoni nisu povezani ni s jednim korisnikom. Većinu vremena demoni čekaju da jedan ili drugi proces zatraži konkretnu uslugu.



DO procesi primjene uključuje sve ostale procese koji se izvode na sustavu. Obično su to procesi pokrenuti unutar korisničke sesije. Najvažniji korisnički proces je interpreter početnih naredbi, koji omogućuje izvršavanje korisničkih naredbi na UNIX sustavu.

Korisnički procesi mogu se izvoditi i interaktivno (preemptivno) i pozadinski načini rada. Interaktivni procesi imaju isključivo vlasništvo nad terminalom i dok takav proces ne završi svoje izvršenje, korisnik nema pristup naredbenom retku.

Atributi procesa

UNIX proces ima niz atributa koji mu omogućuju operacijski sustav upravljati njegovim radom. Glavni atributi:

· ID procesa (PID), omogućujući jezgri sustava da razlikuje procese. Kada se stvara novi proces, kernel mu dodjeljuje sljedeći slobodni (tj. koji nije povezan ni s jednim procesom) identifikator. Dodjeljivanje identifikatora obično se odvija uzlaznim redoslijedom, tj. ID novog procesa veći je od ID-a procesa kreiranog prije njega. Ako ID dosegne 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 nadređenog procesa (PPID)– identifikator procesa koji je iznjedrio ovaj proces. Svi procesi u sustavu osim procesi sustava i proces u tome, koji je praotac preostalih procesa, generirani su jednim od postojećih ili prethodno postojećih procesa.

· Korekcija prioriteta (NI)– relativni prioritet procesa, koji planer uzima u obzir prilikom određivanja redoslijeda pokretanja. Stvarna raspodjela resursa procesora određena je prioritetom izvršavanja (atribut PRI), ovisno o nekoliko čimbenika, posebice o danom relativnom prioritetu. Relativni prioritet sustav ne mijenja tijekom 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 sustava je od -20 do 20. Ako nije navedeno povećanje, koristi se zadana vrijednost od 10. Pozitivno povećanje znači smanjenje trenutnog prioriteta. Obični korisnici mogu postaviti samo pozitivno povećanje i time samo smanjiti prioritet. Korisnik korijen može postaviti negativni inkrement, čime se povećava prioritet procesa i time pridonosi njegovom većem brz rad. Za razliku od relativnog prioriteta, planer dinamički mijenja prioritet izvršenja procesa.

· Terminalna linija (TTY)– terminal ili pseudo-terminal povezan s procesom. Ovaj terminal je povezan sa standardnim potoci: ulazni, slobodan dan I protok poruka 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 određenog procesa je ID korisnika koji je pokrenuo proces. Efektivni identifikator koristi se za određivanje prava pristupa procesa resursima sustava (prvenstveno resursima sustav datoteka). Obično su stvarni i učinkoviti identifikatori isti, tj. proces ima ista prava u sustavu kao i korisnik koji ga je pokrenuo. Međutim, moguće je dati procesu više prava nego korisniku postavljanjem SUID bit kada je efektivni identifikator postavljen na identifikator vlasnika izvršne datoteke (na primjer, korisnik korijen).

· Pravi (GID) i efektivni (EGID) identifikatori grupe. Pravi ID grupe jednak je ID-u primarne ili trenutne grupe korisnika koji je započeo proces. Efektivni identifikator koristi se za određivanje prava pristupa resursima sustava u ime grupe. Obično je efektivni ID grupe isti kao pravi. Ali ako je izvršna datoteka postavljena na SGID bit, takva datoteka se izvršava s efektivnim ID-om grupe vlasnika.

LABORATORIJSKI RAD br.3

PROGRAMIRANJE ZA VIŠE ZADATAKA ULINUX

1. Cilj rada: Upoznajte se s gcc prevoditeljem, tehnikama otklanjanja pogrešaka programa i funkcijama za rad s procesima.

2. Kratke teorijske informacije.

Minimalni skup prekidača gcc prevoditelja je - Wall (prikaz svih grešaka i upozorenja) i - o (izlazna datoteka):

gcc - Zid - o print_pid print_pid. c

Tim će stvarati izvršna datoteka ispis_pid.

C standardna biblioteka (libc, implementirana u Linuxu u glibc) iskorištava mogućnosti multitaskinga Unix System V (u daljnjem tekstu SysV). U libc-u je tip pid_t definiran kao cijeli broj koji može sadržavati pid. Funkcija koja javlja pid trenutnog procesa ima prototip pid_t getpid(void) i definirana je zajedno s pid_t u unistd. h i sys/vrste. h).

Za stvaranje novog procesa upotrijebite funkciju fork:

pid_t fork(void)

Umetanjem odgode nasumične duljine pomoću funkcija sleep i rand, možete jasnije vidjeti učinak multitaskinga:

ovo će uzrokovati "spavanje" programa na slučajan broj sekundi: od 0 do 3.

Da biste pozvali funkciju kao proces dijete, samo je pozovite nakon grananja:

// ako je proces dijete pokrenut, pozovite funkciju

pid=proces(arg);

// izađi iz procesa

Često je potrebno pokrenuti drugi program kao podređeni proces. Da biste to učinili, koristite funkcije obitelji exec:

// ako je proces dijete pokrenut, pozovite program


if (execl("./file","file",arg, NULL)<0) {

printf("GREŠKA prilikom pokretanja procesa\n");

else printf("proces pokrenut (pid=%d)\n", pid);

// izađi iz procesa

Često roditeljski proces treba razmjenjivati ​​informacije sa svojom djecom, ili se barem sinkronizirati s njima, kako bi izvršio operacije u pravo vrijeme. Jedan od načina za sinkronizaciju procesa su funkcije čekanja i čekanja:

#uključi

#uključi

pid_t wait(int *status) - obustavlja izvršenje 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šenje trenutnog procesa dok navedeni proces ne završi ili ne provjeri da li je navedeni proces završen.

Ako trebate saznati stanje procesa djeteta kada se prekine i vrijednost koju vraća, tada koristite makronaredbu WEXITSTATUS, prosljeđujući mu status procesa djeteta kao parametar.

status=waitpid(pid,&status, WNOHANG);

if (pid == status) (

printf("PID: %d, Rezultat = %d\n", pid, WEXITSTATUS(status)); )

Za promjenu prioriteta stvorenih procesa koriste se funkcije setpriority i . 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!

#uključi

#uključi

int proces(int i) (

setpriority(PRIO_PROCESS, getpid(),i);

printf("Proces %d ID niti: %d radi s prioritetom %d\n",i, getpid(),getpriority(PRIO_PROCESS, getpid()));

return(getpriority(PRIO_PROCESS, getpid()));

Za ubijanje procesa upotrijebite funkciju kill:

#uključi

#uključi

int kill(pid_t pid, int sig);

Ako je pid > 0, tada specificira PID procesa kojem je signal poslan. Ako je pid = 0, tada se signal šalje svim procesima grupe kojoj trenutni proces pripada.

sig - vrsta signala. Neke vrste signala u Linuxu:

SIGKILL Ovaj signal uzrokuje trenutni prekid procesa. Proces ne može zanemariti ovaj signal.

SIGTERM Ovaj signal je zahtjev za prekid procesa.

SIGCHLD Sustav šalje ovaj signal procesu kada jedan od njegovih podređenih procesa završi. Primjer:

if (pid[i] == status) (

printf("ThreadID: %d završen sa statusom %d\n", pid[i], WEXITSTATUS(status));

inače ubij(pid[i],SIGKILL);

3. Metodičke upute.

3.1. Kako biste se upoznali s opcijama gcc prevoditelja i opisima funkcija jezika C, upotrijebite man i info upute.

3.2. Za otklanjanje pogrešaka programa prikladno je koristiti ugrađeni uređivač upravitelja datotekama Ponoćni zapovjednik(MC), koji bojom ističe različite jezične konstrukcije i označava položaj pokazivača u datoteci (redak, stupac) u gornjem retku ekrana.

3.3. Upravitelj datoteka Midnight Commander ima međuspremnik naredbi koji se može pozvati prečicom na tipkovnici - H, koji se može pomicati pomoću strelica kursora (gore i dolje). Za umetanje naredbe iz međuspremnika u naredbeni redak koristite tipku , za uređivanje naredbe iz međuspremnika - tipke<- и ->, I .


3.4. Imajte na umu da trenutni direktorij nije sadržan u stazi, tako da morate pokrenuti program kao "./print_pid" iz retka za naredbe. U MC-u samo prijeđite pokazivačem iznad datoteke i kliknite .

3.5. Za pregled rezultata izvršavanja programa koristite tipkovni prečac - O. Također rade u načinu rada za uređivanje datoteka.

3.6. Za bilježenje rezultata izvođenja programa, preporučljivo je koristiti preusmjeravanje izlaza iz konzole u datoteku: ./test > rezultat. txt

3.7. Za pristup datotekama stvorenim na Linux poslužitelj, koristite ftp protokol, čiji je klijentski program dostupan u sustavu Windows 2000 i ugrađen je u upravitelj datoteka DALEKO. pri čemu Račun a lozinka je ista kao kod spajanja preko ssh-a.

4.1. Upoznajte se s opcijama gcc prevoditelja i metodama za otklanjanje pogrešaka programa.

4.2. Za varijante zadataka iz laboratorijskog rada br. 1 napisati i debugirati program koji implementira generirani proces.

4.3. Za opcije zadataka iz laboratorijski rad br. 1 napisati i debugirati program koji implementira roditeljski proces koji poziva i nadzire stanje dječjih procesa - programa (čekajući da završe ili ih uništava, ovisno o opciji).

4.4. Za varijante zadataka iz laboratorijskog rada br. 1 napisati i debugirati program koji implementira nadređeni proces koji poziva i prati stanje podređenih procesa – funkcija (čekajući njihov završetak ili ih uništava, ovisno o varijanti).

5. Mogućnosti za zadatke. Vidi opcije zadataka iz laboratorijskog rada br.1

6. Sadržaj izvješća.

6.1. Cilj rada.

6.2. Opcija zadatka.

6.3. Popisi programa.

6.4. Protokoli za izvođenje programa.

7. Kontrolna pitanja.

7.1. Značajke prevođenja i pokretanja C programa na Linuxu.

7.2. Što je pid, kako ga odrediti u operativnom sustavu i programu?

7.3. Fork funkcija - svrha, primjena, povratna vrijednost.

7.4. Kako pokrenuti funkciju u pokrenutom procesu? Program?

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

7.6. Kako saznati stanje pokrenutog procesa kada prekine i vrijednost koju vraća?

7.7. Kako upravljati prioritetima procesa?

7.8. Kako ubiti proces u operativnom sustavu i programu?

Nastavljamo s temom višenitnosti u Linux kernelu. Prošli put sam pričao o prekidima, njihovoj obradi i taskletima, a budući da je prvotna namjera bila da ovo bude jedan članak, u svojoj priči o workqueueu osvrnut ću se na tasklete, pod pretpostavkom da su čitatelj već upoznati s njima.
Kao i prošli put, pokušat ću svoju priču učiniti š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 opisati sve zamršenosti implementacije, ali se nadam da ću više ili manje detaljno analizirati najvažnije stvari.
Workqueues, poput taskleta, služe za odgođenu obradu prekida (iako se mogu koristiti i u druge svrhe), ali se, za razliku od taskleta, izvršavaju u kontekstu kernel procesa, stoga ne moraju biti atomski i mogu koristiti stanje mirovanja. (), razne alate za sinkronizaciju itd.

Prvo shvatimo kako je općenito organiziran proces obrade radnog reda. Slika to prikazuje vrlo približno i pojednostavljeno, kako se sve zapravo događa detaljno je opisano u nastavku.

Nekoliko je entiteta uključeno u ovu tamnu tvar.
Prvo, radni predmet(ukratko samo rad) je struktura koja opisuje funkciju (na primjer, rukovatelj prekidom) koju želimo rasporediti. Može se smatrati analogom strukture taskleta. Prilikom raspoređivanja, Taskleti su dodani u redove čekanja skrivene od korisnika, ali sada moramo koristiti poseban red čekanja - radni red.
Zadaci se skupljaju pomoću funkcije planera, a red čekanja obrađuju posebne niti koje se nazivaju radnici.
Radnik pružaju asinkrono izvršavanje poslova iz radnog reda. Iako oni rad nazivaju redoslijedom rotacije, u općem slučaju nema govora o strogom, sekvencijalnom izvršenju: na kraju krajeva, ovdje se odvija preemption, spavanje, čekanje itd.

Općenito, radnici su niti kernela, to jest, njima upravlja glavni planer kernela Linuxa. Ali radnici djelomično interveniraju u planiranju dodatne organizacije paralelnog izvođenja radova. O tome će biti više riječi u nastavku.

Kako bih opisao glavne mogućnosti mehanizma radnog reda, predlažem da istražite API.

O redu čekanja i njegovom stvaranju

alloc_workqueue(fmt, zastavice, max_active, args...)
Parametri fmt i args su printf format za ime i njegove argumente. Parametar max_activate odgovoran je za najveći broj radova koji se iz ovog reda čekanja mogu paralelno izvršiti na jednom CPU-u.
Red se može stvoriti sa sljedećim zastavicama:
  • WQ_VISOKI PRI
  • WQ_NEVEZAN
  • WQ_CPU_INTENZIVNO
  • WQ_ZAMRZIV
  • WQ_MEM_POVRAT
Posebnu pozornost treba obratiti na zastavu WQ_NEVEZAN. Na temelju prisutnosti ove oznake, redovi se dijele na vezane i nevezane.
U povezanim redovima Kada se dodaju, poslovi su vezani za trenutni CPU, to jest, u takvim redovima čekanja, radovi se izvršavaju na jezgri koja ih raspoređuje. U tom smislu, vezani redovi nalikuju taskletima.
U nevezanim redovima rad se može izvršiti na bilo kojoj jezgri.

Važna značajka implementacije radnog reda u Linux kernelu je dodatna organizacija paralelnog izvođenja koja je prisutna u vezanim redovima. Detaljnije je napisano u nastavku, ali sada ću reći da se provodi na način da se koristi što manje memorije i da procesor ne miruje. Ovo je sve implementirano uz pretpostavku da jedno djelo ne koristi previše ciklusa procesora.
To nije slučaj za nepripojene redove. U biti, takvi redovi jednostavno pružaju kontekst radnicima i pokreću ih što je ranije moguće.
Stoga bi se trebali koristiti nepripojeni redovi čekanja ako se očekuje CPU intenzivno radno opterećenje, budući da će se u tom slučaju planer pobrinuti za paralelno izvođenje na više jezgri.

Po analogiji sa taskletima, radovima se može dodijeliti prioritet izvršenja, normalan ili visok. Prioritet je zajednički za cijeli red. Prema zadanim postavkama, red ima normalan prioritet, a ako postavite zastavicu WQ_VISOKI PRI, onda, prema tome, visok.

Zastava WQ_CPU_INTENZIVNO ima smisla samo za vezane redove. Ova zastavica je odbijanje sudjelovanja u dodatnoj organizaciji paralelnog izvršenja. Ovu oznaku treba koristiti kada se očekuje da će rad oduzimati puno CPU vremena, u kojem slučaju je bolje prebaciti odgovornost na planera. Ovo je detaljnije opisano u nastavku.

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

Ponekad ima smisla ne stvarati 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 za izvršenje
  • system_unbound_wq - nevezani red

O radu i njihovom planiranju

Sada se pozabavimo radovima. Prvo, pogledajmo makronaredbe inicijalizacije, deklaracije i pripreme:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* u vrijeme prevođenja */ INIT(_DELAYED)_WORK(_work, _func); /* tijekom izvođenja */ PREPARE(_DELAYED)_WORK(_work, _func); /* za promjenu funkcije koja se izvršava */
Radovi se dodaju u red čekanja 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, unsigned long delay); /* posao će biti dodan u red čekanja tek nakon što odgoda istekne */
Na ovom se vrijedi detaljnije zadržati. Iako navodimo red čekanja kao parametar, zapravo se radovi ne nalaze u samom radnom redu čekanja, kao što bi se moglo činiti, već u potpuno drugom entitetu - u popisu čekanja čekanja strukture worker_pool. Struktura skup_radnika, zapravo, najvažniji entitet u organiziranju mehanizma workqueue, iako za korisnika ostaje iza scene. S njima rade radnici i u njima su sadržane sve osnovne informacije.

Sada da vidimo koji su bazeni u sustavu.
Za početak, bazeni za vezane redove (na slici). Za svaki CPU statički su dodijeljena dva skupa radnika: jedan za rad s visokim prioritetom, drugi za rad s normalnim prioritetom. Odnosno, ako imamo četiri jezgre, tada će postojati samo osam vezanih bazena, unatoč činjenici da može biti onoliko radnih redova koliko god želite.
Kada kreiramo radni red, on ima uslugu dodijeljenu za svaki CPU bazen_radni red(pwq). Svaki takav pool_workqueue pridružen je radnom skupu koji je dodijeljen istom CPU-u i po prioritetu odgovara tipu reda čekanja. Preko njih, radni red komunicira sa skupom radnika.
Radnici izvršavaju rad iz skupa radnika bez razlike, bez razlikovanja kojem su radnom redu izvorno pripadali.

Za nepripojene redove, skupovi radnika se dodjeljuju dinamički. Svi se redovi čekanja 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, odnosno, skup radnika.
Zapravo, za nevezane redove sve je malo kompliciranije: ako su za vezane redove pwq i redovi stvoreni za svaki CPU, ovdje se stvaraju za svaki NUMA čvor, ali ovo je dodatna optimizacija koju nećemo detaljno razmatrati.

Svakakve sitnice

Dat ću i nekoliko funkcija iz API-ja da upotpunim sliku, ali neću govoriti o njima u detalje:
/* Prisilno dovršenje */ bool flush_work(struct work_struct *work); bool flush_odgođeni_rad(struktura odgođeni_rad *dwork); /* Otkaži izvršenje posla */ bool cancel_work_sync(struct work_struct *work); bool odustani_odgođen_rad(struct odgođen_rad *dwork); bool cancel_delayed_work_sync(struct delayed_work *dwork); /* Brisanje reda čekanja */ void destroy_workqueue(struct workqueue_struct *wq);

Kako radnici rade svoj posao

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

Radnici uvjetno mogu biti u tri logična stanja: mogu mirovati, trčati ili upravljati.
Radnik može stajati besposlen i ništa ne učiniti. To je, na primjer, kada se sav posao već izvodi. Kada radnik uđe u ovo stanje, on ide spavati i, prema tome, neće izvršiti dok se ne probudi;
Ako upravljanje bazenom nije potrebno i popis planiranih radova nije prazan, tada ih radnik počinje izvršavati. Takve ćemo konvencionalno nazvati radnicima trčanje.
Ako je potrebno, radnik preuzima ulogu menadžer bazen. Skupina može imati ili samo jednog upravljačkog radnika ili niti jednog radnika. Njegov zadatak je održavati optimalan broj radnika po bazenu. Kako on to radi? Prvo se brišu radnici koji su dugo bili u mirovanju. Drugo, novi radnici se stvaraju ako su ispunjena tri uvjeta odjednom:

  • ima još zadataka za izvršiti (radi u bazenu)
  • nema besposlenih radnika
  • nema radnika koji rade (odnosno, aktivni i ne spavaju)
Međutim, posljednji uvjet ima svoje nijanse. Ako su skupovi redovi čekanja nepripojeni, tada se aktivni radnici ne uzimaju u obzir; za njih je ovaj uvjet uvijek istinit. Isto vrijedi u slučaju radnika koji izvršava zadatak iz povezanog, ali s oznakom WQ_CPU_INTENZIVNO, redovi čekanja. Štoviše, u slučaju vezanih redova čekanja, budući da radnici rade s radovima iz zajedničkog bazena (koji je jedan od dva za svaku jezgru na gornjoj slici), ispada da se neki od njih računaju kao radni, a neki ne. Također proizlazi da obavljanje poslova iz WQ_CPU_INTENZIVNO redovi možda neće započeti odmah, ali oni sami ne ometaju izvrš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 dugo trajati.

Računovodstvo za radne radnike provodi se izravno iz glavnog planera jezgre Linuxa. Ovaj kontrolni mehanizam osigurava optimalnu razinu istovremenosti, sprječavajući da radni red stvori previše radnika, ali također ne uzrokuje nepotrebno predugo čekanje posla.

Koga zanima može pogledati radnu funkciju u kernelu, zove se worker_thread().

Sve opisane funkcije i strukture mogu se detaljnije pronaći u datotekama uključi/linux/workqueue.h, kernel/workqueue.c I kernel/workqueue_internal.h. Postoji i dokumentacija o radnom redu u Dokumentacija/workqueue.txt.

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

Stoga smo pogledali mehanizme za obradu odgođenog prekida u Linux kernelu - tasklet i workqueue, koji su poseban oblik multitaskinga. Možete čitati o prekidima, taskletima i radnim redovima u knjizi “Linux Device Drivers” Jonathana Corbeta, Grega Kroah-Hartmana, Alessandra Rubinija, iako su informacije tamo ponekad zastarjele.

Nastavljamo s temom višenitnosti u Linux kernelu. Prošli put sam pričao o prekidima, njihovoj obradi i taskletima, a budući da je prvotna namjera bila da ovo bude jedan članak, u svojoj priči o workqueueu osvrnut ću se na tasklete, pod pretpostavkom da su čitatelj već upoznati s njima.
Kao i prošli put, pokušat ću svoju priču učiniti š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 opisati sve zamršenosti implementacije, ali se nadam da ću više ili manje detaljno analizirati najvažnije stvari.
Workqueues, poput taskleta, služe za odgođenu obradu prekida (iako se mogu koristiti i u druge svrhe), ali se, za razliku od taskleta, izvršavaju u kontekstu kernel procesa, stoga ne moraju biti atomski i mogu koristiti stanje mirovanja. (), razne alate za sinkronizaciju itd.

Prvo shvatimo kako je općenito organiziran proces obrade radnog reda. Slika to prikazuje vrlo približno i pojednostavljeno, kako se sve zapravo događa detaljno je opisano u nastavku.

Nekoliko je entiteta uključeno u ovu tamnu tvar.
Prvo, radni predmet(ukratko samo rad) je struktura koja opisuje funkciju (na primjer, rukovatelj prekidom) koju želimo rasporediti. Može se smatrati analogom strukture taskleta. Prilikom raspoređivanja, Taskleti su dodani u redove čekanja skrivene od korisnika, ali sada moramo koristiti poseban red čekanja - radni red.
Zadaci se skupljaju pomoću funkcije planera, a red čekanja obrađuju posebne niti koje se nazivaju radnici.
Radnik pružaju asinkrono izvršavanje poslova iz radnog reda. Iako oni rad nazivaju redoslijedom rotacije, u općem slučaju nema govora o strogom, sekvencijalnom izvršenju: na kraju krajeva, ovdje se odvija preemption, spavanje, čekanje itd.

Općenito, radnici su niti kernela, to jest, njima upravlja glavni planer kernela Linuxa. Ali radnici djelomično interveniraju u planiranju dodatne organizacije paralelnog izvođenja radova. O tome će biti više riječi u nastavku.

Kako bih opisao glavne mogućnosti mehanizma radnog reda, predlažem da istražite API.

O redu čekanja i njegovom stvaranju

alloc_workqueue(fmt, zastavice, max_active, args...)
Parametri fmt i args su printf format za ime i njegove argumente. Parametar max_activate odgovoran je za najveći broj radova koji se iz ovog reda čekanja mogu paralelno izvršiti na jednom CPU-u.
Red se može stvoriti sa sljedećim zastavicama:
  • WQ_VISOKI PRI
  • WQ_NEVEZAN
  • WQ_CPU_INTENZIVNO
  • WQ_ZAMRZIV
  • WQ_MEM_POVRAT
Posebnu pozornost treba obratiti na zastavu WQ_NEVEZAN. Na temelju prisutnosti ove oznake, redovi se dijele na vezane i nevezane.
U povezanim redovima Kada se dodaju, poslovi su vezani za trenutni CPU, to jest, u takvim redovima čekanja, radovi se izvršavaju na jezgri koja ih raspoređuje. U tom smislu, vezani redovi nalikuju taskletima.
U nevezanim redovima rad se može izvršiti na bilo kojoj jezgri.

Važna značajka implementacije radnog reda u Linux kernelu je dodatna organizacija paralelnog izvođenja koja je prisutna u vezanim redovima. Detaljnije je napisano u nastavku, ali sada ću reći da se provodi na način da se koristi što manje memorije i da procesor ne miruje. Ovo je sve implementirano uz pretpostavku da jedno djelo ne koristi previše ciklusa procesora.
To nije slučaj za nepripojene redove. U biti, takvi redovi jednostavno pružaju kontekst radnicima i pokreću ih što je ranije moguće.
Stoga bi se trebali koristiti nepripojeni redovi čekanja ako se očekuje CPU intenzivno radno opterećenje, budući da će se u tom slučaju planer pobrinuti za paralelno izvođenje na više jezgri.

Po analogiji sa taskletima, radovima se može dodijeliti prioritet izvršenja, normalan ili visok. Prioritet je zajednički za cijeli red. Prema zadanim postavkama, red ima normalan prioritet, a ako postavite zastavicu WQ_VISOKI PRI, onda, prema tome, visok.

Zastava WQ_CPU_INTENZIVNO ima smisla samo za vezane redove. Ova zastavica je odbijanje sudjelovanja u dodatnoj organizaciji paralelnog izvršenja. Ovu oznaku treba koristiti kada se očekuje da će rad oduzimati puno CPU vremena, u kojem slučaju je bolje prebaciti odgovornost na planera. Ovo je detaljnije opisano u nastavku.

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

Ponekad ima smisla ne stvarati 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 za izvršenje
  • system_unbound_wq - nevezani red

O radu i njihovom planiranju

Sada se pozabavimo radovima. Prvo, pogledajmo makronaredbe inicijalizacije, deklaracije i pripreme:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* u vrijeme prevođenja */ INIT(_DELAYED)_WORK(_work, _func); /* tijekom izvođenja */ PREPARE(_DELAYED)_WORK(_work, _func); /* za promjenu funkcije koja se izvršava */
Radovi se dodaju u red čekanja 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, unsigned long delay); /* posao će biti dodan u red čekanja tek nakon što odgoda istekne */
Na ovom se vrijedi detaljnije zadržati. Iako navodimo red čekanja kao parametar, zapravo se radovi ne nalaze u samom radnom redu čekanja, kao što bi se moglo činiti, već u potpuno drugom entitetu - u popisu čekanja čekanja strukture worker_pool. Struktura skup_radnika, zapravo, najvažniji entitet u organiziranju mehanizma workqueue, iako za korisnika ostaje iza scene. S njima rade radnici i u njima su sadržane sve osnovne informacije.

Sada da vidimo koji su bazeni u sustavu.
Za početak, bazeni za vezane redove (na slici). Za svaki CPU statički su dodijeljena dva skupa radnika: jedan za rad s visokim prioritetom, drugi za rad s normalnim prioritetom. Odnosno, ako imamo četiri jezgre, tada će postojati samo osam vezanih bazena, unatoč činjenici da može biti onoliko radnih redova koliko god želite.
Kada kreiramo radni red, on ima uslugu dodijeljenu za svaki CPU bazen_radni red(pwq). Svaki takav pool_workqueue pridružen je radnom skupu koji je dodijeljen istom CPU-u i po prioritetu odgovara tipu reda čekanja. Preko njih, radni red komunicira sa skupom radnika.
Radnici izvršavaju rad iz skupa radnika bez razlike, bez razlikovanja kojem su radnom redu izvorno pripadali.

Za nepripojene redove, skupovi radnika se dodjeljuju dinamički. Svi se redovi čekanja 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, odnosno, skup radnika.
Zapravo, za nevezane redove sve je malo kompliciranije: ako su za vezane redove pwq i redovi stvoreni za svaki CPU, ovdje se stvaraju za svaki NUMA čvor, ali ovo je dodatna optimizacija koju nećemo detaljno razmatrati.

Svakakve sitnice

Dat ću i nekoliko funkcija iz API-ja da upotpunim sliku, ali neću govoriti o njima u detalje:
/* Prisilno dovršenje */ bool flush_work(struct work_struct *work); bool flush_odgođeni_rad(struktura odgođeni_rad *dwork); /* Otkaži izvršenje posla */ bool cancel_work_sync(struct work_struct *work); bool odustani_odgođen_rad(struct odgođen_rad *dwork); bool cancel_delayed_work_sync(struct delayed_work *dwork); /* Brisanje reda čekanja */ void destroy_workqueue(struct workqueue_struct *wq);

Kako radnici rade svoj posao

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

Radnici uvjetno mogu biti u tri logična stanja: mogu mirovati, trčati ili upravljati.
Radnik može stajati besposlen i ništa ne učiniti. To je, na primjer, kada se sav posao već izvodi. Kada radnik uđe u ovo stanje, on ide spavati i, prema tome, neće izvršiti dok se ne probudi;
Ako upravljanje bazenom nije potrebno i popis planiranih radova nije prazan, tada ih radnik počinje izvršavati. Takve ćemo konvencionalno nazvati radnicima trčanje.
Ako je potrebno, radnik preuzima ulogu menadžer bazen. Skupina može imati ili samo jednog upravljačkog radnika ili niti jednog radnika. Njegov zadatak je održavati optimalan broj radnika po bazenu. Kako on to radi? Prvo se brišu radnici koji su dugo bili u mirovanju. Drugo, novi radnici se stvaraju ako su ispunjena tri uvjeta odjednom:

  • ima još zadataka za izvršiti (radi u bazenu)
  • nema besposlenih radnika
  • nema radnika koji rade (odnosno, aktivni i ne spavaju)
Međutim, posljednji uvjet ima svoje nijanse. Ako su skupovi redovi čekanja nepripojeni, tada se aktivni radnici ne uzimaju u obzir; za njih je ovaj uvjet uvijek istinit. Isto vrijedi u slučaju radnika koji izvršava zadatak iz povezanog, ali s oznakom WQ_CPU_INTENZIVNO, redovi čekanja. Štoviše, u slučaju vezanih redova čekanja, budući da radnici rade s radovima iz zajedničkog bazena (koji je jedan od dva za svaku jezgru na gornjoj slici), ispada da se neki od njih računaju kao radni, a neki ne. Također proizlazi da obavljanje poslova iz WQ_CPU_INTENZIVNO redovi možda neće započeti odmah, ali oni sami ne ometaju izvrš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 dugo trajati.

Računovodstvo za radne radnike provodi se izravno iz glavnog planera jezgre Linuxa. Ovaj kontrolni mehanizam osigurava optimalnu razinu istovremenosti, sprječavajući da radni red stvori previše radnika, ali također ne uzrokuje nepotrebno predugo čekanje posla.

Koga zanima može pogledati radnu funkciju u kernelu, zove se worker_thread().

Sve opisane funkcije i strukture mogu se detaljnije pronaći u datotekama uključi/linux/workqueue.h, kernel/workqueue.c I kernel/workqueue_internal.h. Postoji i dokumentacija o radnom redu u Dokumentacija/workqueue.txt.

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

Stoga smo pogledali mehanizme za obradu odgođenog prekida u Linux kernelu - tasklet i workqueue, koji su poseban oblik multitaskinga. Možete čitati o prekidima, taskletima i radnim redovima u knjizi “Linux Device Drivers” Jonathana Corbeta, Grega Kroah-Hartmana, Alessandra Rubinija, iako su informacije tamo ponekad zastarjele.