Načela delovanja v UNIX podobnih operacijskih sistemih na primeru Linuxa. Ustvarjanje lastnega lahkega postopka

Eden najzobejših trenutkov pri premiku iz okolja v Temelji na sistemu Windows za uporabo ukazna vrstica– izguba enostavne večopravilnosti. Tudi v Linuxu, če uporabljate sistem X Window, lahko z miško preprosto kliknete nov program in ga odprite. V ukazni vrstici pa ste precej obtičali z monotaskingom. V tem članku vam bomo pokazali kako večopravilnost v Linuxu z uporabo ukazne vrstice.

Upravljanje ozadja in prioritetnih procesov

Vendar še vedno obstajajo načini za večopravilnost v Linuxu in nekateri od njih so obsežnejši od drugih. Ena vgrajena metoda, ki ne zahteva dodatnih programsko opremo, je preprosto premikanje procesov v ozadju in v ospredju. Govorimo o tem. Vendar pa ima nekaj slabosti.

Nezaščiten

PrvičČe želite proces poslati v ozadje, ga morate najprej začasno ustaviti. Že delujočega programa ni mogoče poslati v ozadje in ga hkrati vzdrževati.

Drugič, morate prekiniti potek dela, da začnete nov ukaz. Zapustiti morate tisto, kar trenutno počnete, in v lupino vnesti več ukazov. Deluje, vendar je neprijetno.

Tretjič, morate spremljati rezultate procesov v ozadju. Vsak njihov izhod se bo pojavil v ukazni vrstici in bo motil vaše trenutno početje. Torej morajo opravila v ozadju preusmeriti svoj izhod na ločena datoteka ali pa jih je treba popolnoma onemogočiti.

Zaradi teh pomanjkljivosti so velike težave pri upravljanju procesa v ozadju in v ospredju. Najboljša odločitev– uporabite pripomoček ukazne vrstice “screen”, kot je prikazano spodaj.

Toda najprej - odprli boste novo sejo SSH

Ne pozabite, da pravkar odpirate novo sejo SSH.

Morda bo neprijetno ves čas odpirati nove seje. In takrat potrebujete "zaslon"

Pripomoček zaslon omogoča ustvarjanje več delovnih tokov, ki so odprti hkrati - najbližji analog "oknom". Privzeto je na voljo v običajnih repozitorijih Linuxa. Namestite ga na CentOS/RHEL z naslednjim ukazom:

Zaslon za namestitev Sudo yum

Odpiranje novega zaslona

Zdaj začnite sejo s tipkanjem »screen«.

To bo ustvarilo prazno okno znotraj obstoječe seje SSH in ji dajte številko, ki je prikazana v glavi, kot je ta:

Naš zaslon tukaj je oštevilčen z "0", kot je prikazano. Na tem posnetku zaslona uporabljamo navidezni ukaz »branje«, da blokiramo terminal in poskrbimo, da čaka na vnos. Zdaj pa recimo, da želimo narediti nekaj drugega, medtem ko čakamo.

Odpreti nov zaslon in naredimo nekaj drugega, natisnemo:

Ctrl+a c

“ctrl+a” je privzeta kombinacija tipk za nadzor zaslonov v programu zaslona. To, kar vnesete po njem, določa dejanje. Na primer:

  • ctrl+a c – C aktivira nov zaslon
  • ctrl+a [številka]– skoči na določeno številko zaslona
  • ctrl+a k – K izklopi trenutni zaslon
  • ctrl+a n – Pojdi na zaslon n
  • ctrl+a “- prikaže vse aktivne zaslone v seji

Če pritisnemo »ctrl+a c«, bomo dobili nov zaslon z novo številko.

Za krmarjenje po seznamu in pomik na želeni zaslon lahko uporabite smerne tipke.
Zasloni so najbližje "oknom", kot sistem v ukazu Linuxov niz. Seveda ni tako preprosto kot klikanje z miško, vendar je grafični podsistem zelo zahteven. Z zasloni lahko dobite skoraj enako funkcionalnost in omogočite popolno večopravilnost!

Procesi v sistemu UNIX

V UNIX-u je glavno sredstvo organizacije in enota večopravilnosti proces. Operacijski sistem manipulira s sliko procesa, ki predstavlja programsko kodo, kot tudi z odseki podatkov procesa, ki definirajo okolje izvajanja.

Med izvajanjem ali med čakanjem »na koncu« so procesi shranjeni v navideznem pomnilniku z organizacijo strani. Del tega navideznega pomnilnika je preslikanega v fizični pomnilnik. Del fizičnega pomnilnika je rezerviran za jedro operacijskega sistema. Uporabniki lahko dostopajo le do preostalega pomnilnika za procese. Po potrebi se strani pomnilnika procesa zamenjajo iz fizičnega pomnilnika na disk, v območje izmenjave. Pri dostopu do strani v navideznem pomnilniku, če ni v fizičnem pomnilniku, se zamenja z diska.

Navidezni pomnilnik implementira in samodejno vzdržuje jedro UNIX.

Vrste procesov

V operacijskih sistemih UNIX obstajajo tri vrste procesov: sistemski, demonski procesi in aplikacijskih procesov.

Sistemski procesi so del jedra in se vedno nahajajo v pomnilnik z naključnim dostopom. Sistemski procesi nimajo ustreznih programov v obliki izvedljivih datotek in se zaženejo na poseben način, ko je sistemsko jedro inicializirano. Navodila za izvajanje in podatki teh procesov so v jedru sistema, tako da lahko kličejo funkcije in dostopajo do podatkov, do katerih drugi procesi ne morejo dostopati.

Sistemski procesi vključujejo proces začetne inicializacije, v, ki je prednik vseh drugih procesov. čeprav v ni del jedra in se izvaja iz izvršljive datoteke, zato je njegovo delovanje ključnega pomena za delovanje celotnega sistema kot celote.

Demoni- gre za neinteraktivne procese, ki se zaženejo na običajen način - z nalaganjem ustreznih programov v pomnilnik in se izvajajo v ozadju. Običajno se demoni zaženejo med inicializacijo sistema, vendar po inicializaciji jedra, in zagotavljajo delovanje različnih podsistemov UNIX: sistemi za dostop do terminalov, sistemi za tiskanje, omrežne storitve itd. Demoni niso povezani z nobenim uporabnikom. Večino časa demoni čakajo na zahtevo enega ali drugega procesa določeno storitev.



TO aplikacijskih procesov vključuje vse druge procese, ki se izvajajo v sistemu. Običajno so to procesi, ki nastanejo znotraj uporabniške seje. Najpomembnejši uporabniški proces je začetni tolmač ukazov, ki zagotavlja izvajanje uporabniških ukazov v sistemu UNIX.

Uporabniški procesi se lahko izvajajo tako interaktivno (preventivno) kot načini v ozadju. Interaktivni procesi imajo izključno lastništvo nad terminalom in dokler tak proces ne zaključi svoje izvedbe, uporabnik nima dostopa do ukazne vrstice.

Atributi procesa

Proces UNIX ima številne atribute, ki mu omogočajo operacijski sistem upravljati svoje delo. Glavni atributi:

· ID procesa (PID), kar jedru sistema omogoča razlikovanje med procesi. Ko je ustvarjen nov proces, mu jedro dodeli naslednji prosti (tj. nepovezan z nobenim procesom) identifikator. Dodelitev identifikatorja običajno poteka v naraščajočem vrstnem redu, tj. ID novega procesa je večji od ID-ja procesa, ustvarjenega pred njim. Če ID doseže največjo vrednost (običajno 65737), bo naslednji proces prejel najmanjši prosti PID in cikel se ponovi. Ko se proces zaključi, jedro sprosti identifikator, ki ga je uporabljalo.

· ID nadrejenega procesa (PPID)– identifikator procesa, ki je sprožil ta proces. Vsi procesi v sistemu razen sistemskih procesov in proces v, ki je prednik preostalih procesov, generira eden od obstoječih ali predhodno obstoječih procesov.

· Prednostni popravek (NI)– relativno prioriteto procesa, ki jo razporejevalnik upošteva pri določanju vrstnega reda zagona. Dejansko porazdelitev procesorskih virov določa prioriteta izvajanja (atribut PRI), odvisno od več dejavnikov, zlasti od dane relativne prednosti. Relativne prioritete sistem ne spremeni skozi celotno življenjsko dobo procesa, čeprav jo lahko spremeni uporabnik ali skrbnik, ko zažene proces z ukazom lepo. Razpon vrednosti prirastka prioritete v večini sistemov je od -20 do 20. Če prirastek ni določen, se uporabi privzeta vrednost 10. Pozitiven prirastek pomeni zmanjšanje trenutne prioritete. Navadni uporabniki lahko nastavijo samo pozitiven inkrement in s tem samo znižajo prioriteto. Uporabnik korenina lahko nastavi negativni prirastek, kar poveča prioriteto procesa in s tem prispeva k njegovi višji hitro delo. Za razliko od relativne prioritete prednost izvajanja procesa dinamično spreminja razporejevalnik.

· Terminalna linija (TTY)– terminal ali psevdoterminal, povezan s procesom. Ta terminal je povezan s standardom tokovi: vnos, prosti dan in pretok sporočil o napakah. Tokovi ( programskih kanalov) so standardna sredstva medprocesna komunikacija v OS UNIX. Daemon procesi niso povezani s terminalom.

· Realni (UID) in efektivni (EUID) uporabniški identifikatorji. Pravi uporabniški ID danega procesa je ID uporabnika, ki je začel proces. Učinkovit identifikator se uporablja za določanje pravic dostopa procesa do sistemskih virov (predvsem virov datotečni sistem). Običajno sta dejanska in učinkovita identifikatorja enaka, tj. proces ima enake pravice v sistemu kot uporabnik, ki ga je zagnal. Vendar pa je mogoče z nastavitvijo dati procesu več pravic kot uporabniku bit SUID ko je efektivni identifikator nastavljen na identifikator lastnika izvršljive datoteke (na primer uporabnika korenina).

· Realni (GID) in efektivni (EGID) identifikatorji skupin. Pravi ID skupine je enak ID-ju primarne ali trenutne skupine uporabnika, ki je začel postopek. Učinkovit identifikator se uporablja za določanje pravic dostopa do sistemskih virov v imenu skupine. Običajno je efektivni ID skupine enak pravemu. Če pa je izvršljiva datoteka nastavljena na bit SGID, se taka datoteka izvede z ID-jem efektivne skupine lastnikov.

LABORATORIJSKO DELO št. 3

VEČOPRAVILNO PROGRAMIRANJE INLINUX

1. Cilj dela: Seznanite se s prevajalnikom gcc, tehnikami odpravljanja napak v programih in funkcijami za delo s procesi.

2. Kratke teoretične informacije.

Najmanjši nabor stikal prevajalnika gcc je - Wall (prikaži vse napake in opozorila) in - o (izhodna datoteka):

gcc - Zid - o print_pid print_pid. c

Ekipa bo ustvarjala izvršljiva datoteka print_pid.

Standardna knjižnica C (libc, implementirana v Linux v glibc) izkorišča prednosti večopravilnosti Unix System V (v nadaljevanju SysV). V libc je tip pid_t definiran kot celo število, ki lahko vsebuje pid. Funkcija, ki poroča pid trenutnega procesa, ima prototip pid_t getpid(void) in je definirana skupaj s pid_t v unistd. h in sys/vrste. h).

Če želite ustvariti nov proces, uporabite funkcijo fork:

pid_t fork(void)

Če vstavite zakasnitev naključne dolžine s funkcijama spanja in rand, lahko jasneje vidite učinek večopravilnosti:

to bo povzročilo, da program "spi" za naključno število sekund: od 0 do 3.

Če želite poklicati funkcijo kot podrejeni proces, jo pokličite po razvejanju:

// če se izvaja podrejeni proces, pokliči funkcijo

pid=proces(arg);

// izhod iz procesa

Pogosto je treba zagnati drug program kot podrejeni proces. Če želite to narediti, uporabite družinske funkcije exec:

// če se izvaja podrejeni proces, pokliči program


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

printf("NAPAKA med zagonom procesa\n");

else printf("proces se je začel (pid=%d)\n", pid);

// izhod iz procesa

Pogosto mora nadrejeni proces izmenjati informacije s svojimi otroki ali se vsaj sinhronizirati z njimi, da lahko izvede operacije ob pravem času. Eden od načinov za sinhronizacijo procesov je funkcija čakanja in čakanja:

#vključi

#vključi

pid_t wait(int *status) - začasno ustavi izvajanje trenutnega procesa, dokler se kateri od njegovih podrejenih procesov ne konča.

pid_t waitpid (pid_t pid, int *status, int options) - začasno ustavi izvajanje trenutnega procesa, dokler se podani proces ne zaključi ali preveri, ali je določen postopek dokončan.

Če morate izvedeti stanje podrejenega procesa, ko se zaključi, in vrednost, ki jo vrne, potem uporabite makro WEXITSTATUS in mu posredujte status podrejenega procesa kot parameter.

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

if (pid == stanje) (

printf("PID: %d, rezultat = %d\n", pid, WEXITSTATUS(stanje)); )

Za spreminjanje prioritet sproženih procesov se uporabljajo funkcije setpriority in . Prioritete so nastavljene v območju od -20 (najvišja) do 20 (najnižja), normalna vrednost je 0. Upoštevajte, da lahko samo superuporabnik poveča prioriteto nad normalno!

#vključi

#vključi

int proces(int i) (

setpriority(PRIO_PROCESS, getpid(),i);

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

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

Če želite uničiti proces, uporabite funkcijo kill:

#vključi

#vključi

int kill(pid_t pid, int sig);

Če je pid > 0, potem določa PID procesa, kateremu je poslan signal. Če je pid = 0, se signal pošlje vsem procesom skupine, ki ji trenutni proces pripada.

sig - vrsta signala. Nekatere vrste signalov v Linuxu:

SIGKILL Ta signal povzroči, da se proces takoj zaključi. Proces tega signala ne more prezreti.

SIGTERM Ta signal je zahteva za prekinitev procesa.

SIGCHLD Sistem pošlje ta signal procesu, ko se eden od njegovih podrejenih procesov zaključi. primer:

if (pid[i] == stanje) (

printf("ThreadID: %d končan s statusom %d\n", pid[i], WEXITSTATUS(stanje));

else kill(pid[i],SIGKILL);

3. Metodična navodila.

3.1. Če se želite seznaniti z možnostmi prevajalnika gcc in opisi funkcij jezika C, uporabite navodila man in info.

3.2. Za odpravljanje napak v programih je priročno uporabiti vgrajeni urejevalnik upravitelja datotek Polnočni poveljnik(MC), ki barvno osvetljuje različne jezikovne konstrukte in označuje položaj kazalca v datoteki (vrstica, stolpec) v zgornji vrstici zaslona.

3.3. Upravitelj datotek Midnight Commander ima ukazni medpomnilnik, ki ga lahko prikličete z bližnjico na tipkovnici - H, ki ga lahko premikate s smernimi puščicami (gor in dol). Za vstavljanje ukaza iz medpomnilnika v ukazno vrstico uporabite tipko , za urejanje ukaza iz medpomnilnika - tipke<- и ->, in .


3.4. Ne pozabite, da trenutni imenik ni v poti, zato morate program zagnati kot "./print_pid" iz ukazne vrstice. V MC samo premaknite miškin kazalec nad datoteko in kliknite .

3.5. Če si želite ogledati rezultat izvajanja programa, uporabite bližnjico na tipkovnici - O. Delujejo tudi v načinu urejanja datotek.

3.6. Za beleženje rezultatov izvajanja programa je priporočljivo uporabiti preusmeritev izhoda iz konzole v datoteko: ./test > rezultat. txt

3.7. Za dostop do datotek, ustvarjenih na Linux strežnik, uporabite protokol ftp, odjemalski program za katerega je na voljo v sistemu Windows 2000 in je vgrajen v upravitelj datotek DALEČ. pri čemer račun in geslo je enako kot pri povezovanju preko ssh.

4.1. Seznanite se z možnostmi prevajalnika gcc in metodami za odpravljanje napak v programih.

4.2. Za variante nalog iz laboratorijskega dela št. 1 napišite in razhroščite program, ki implementira generirani proces.

4.3. Za možnosti nalog od laboratorijsko delošt. 1 napišite in razhroščite program, ki implementira nadrejeni proces, ki kliče in spremlja stanje podrejenih procesov - programov (čaka, da dokončajo ali jih uniči, odvisno od možnosti).

4.4. Za variante nalog iz laboratorijskega dela št. 1 napišite in razhroščite program, ki implementira nadrejeni proces, ki kliče in spremlja stanje podrejenih procesov – funkcij (čaka na njihovo dokončanje ali jih uniči, odvisno od variante).

5. Možnosti za naloge. Glej možnosti za naloge iz laboratorijskega dela št. 1

6. Vsebina poročila.

6.1. Cilj dela.

6.2. Možnost naloge.

6.3. Seznami programov.

6.4. Protokoli izvajanja programa.

7. Kontrolna vprašanja.

7.1. Značilnosti prevajanja in izvajanja programov C v Linuxu.

7.2. Kaj je pid, kako ga določiti v operacijskem sistemu in programu?

7.3. Funkcija fork – namen, uporaba, povratna vrednost.

7.4. Kako zagnati funkcijo v sproženem procesu? Program?

7.5. Načini za sinhronizacijo nadrejenih in podrejenih procesov.

7.6. Kako ugotoviti stanje ustvarjenega procesa, ko se zaključi, in vrednost, ki jo vrne?

7.7. Kako upravljati s prioritetami procesov?

7.8. Kako ubiti proces v operacijskem sistemu in programu?

Nadaljujemo s temo večnitnosti v jedru Linuxa. Zadnjič sem govoril o prekinitvah, njihovi obdelavi in ​​taskletih, in ker je bilo prvotno mišljeno, da bo to en članek, se bom v zgodbi o workqueue nanašal na tasklete, ob predpostavki, da jih bralec že pozna.
Tako kot zadnjič bom poskušal svojo zgodbo narediti čim bolj podrobno in podrobno.

Članki v seriji:

  1. Večopravilnost v jedru Linuxa: delovna čakalna vrsta

Delovna vrsta

Delovna vrsta- to so bolj zapletene in težke entitete kot tasklets. Tu niti ne bom poskušal opisati vseh zapletenosti implementacije, upam pa, da bom bolj ali manj podrobno analiziral najpomembnejše stvari.
Delovne čakalne vrste, tako kot tasklets, služijo za odloženo obdelavo prekinitev (čeprav jih je mogoče uporabiti tudi za druge namene), vendar se za razliko od tasklets izvajajo v kontekstu jedrnega procesa, zato jim ni treba biti atomski in lahko uporabljajo spanje. (), različna orodja za sinhronizacijo itd.

Najprej razumemo, kako je na splošno organiziran proces obdelave delovne vrste. Slika prikazuje zelo približno in poenostavljeno, kako se vse dejansko zgodi, je podrobno opisano spodaj.

V to temno snov je vpletenih več entitet.
Prvič, delovni predmet(na kratko samo delo) je struktura, ki opisuje funkcijo (na primer upravljalnik prekinitev), ki jo želimo načrtovati. Lahko si jo predstavljamo kot analogno strukturi tasklet. Pri razporejanju so bili Taskleti dodani v čakalne vrste, skrite pred uporabnikom, zdaj pa moramo uporabiti posebno čakalno vrsto - delovna čakalna vrsta.
Opravila se zbirajo s funkcijo razporejevalnika, delovno čakalno vrsto pa obdelujejo posebne niti, imenovane delavci.
Delavec zagotavljajo asinhrono izvajanje del iz delovne čakalne vrste. Čeprav imenujejo delo po vrstnem redu rotacije, v splošnem primeru ne gre za strogo, zaporedno izvajanje: navsezadnje se tukaj odvijajo prednost, spanje, čakanje itd.

Na splošno so delavci niti jedra, kar pomeni, da jih nadzoruje glavni razporejevalnik jedra Linuxa. Delavci pa delno posegajo v načrtovanje dodatne organizacije vzporednega izvajanja dela. O tem bomo podrobneje razpravljali v nadaljevanju.

Za oris glavnih zmožnosti mehanizma delovne čakalne vrste predlagam, da raziščete API.

O čakalni vrsti in njenem ustvarjanju

alloc_workqueue(fmt, zastavice, max_active, args...)
Parametra fmt in args sta format printf za ime in argumente zanj. Parameter max_activate je odgovoren za največje število del, ki se lahko iz te čakalne vrste izvajajo vzporedno na eni CPU.
Čakalno vrsto je mogoče ustvariti z naslednjimi zastavicami:
  • WQ_HIGHPRI
  • WQ_NEVEZAN
  • WQ_CPE_INTENSIVE
  • WQ_FREZABLE
  • WQ_MEM_RECLAIM
Posebno pozornost je treba nameniti zastavi WQ_NEVEZAN. Na podlagi prisotnosti te zastavice so čakalne vrste razdeljene na vezane in nevezane.
V povezanih čakalnih vrstah Ko so dodana, so dela vezana na trenutno CPE, kar pomeni, da se v takšnih čakalnih vrstah dela izvajajo v jedru, ki ga načrtuje. V zvezi s tem so vezane čakalne vrste podobne opravilnim programom.
V nevezanih čakalnih vrstah dela se lahko izvajajo na katerem koli jedru.

Pomembna lastnost implementacije delovne čakalne vrste v jedru Linuxa je dodatna organizacija vzporednega izvajanja, ki je prisotno v vezanih čakalnih vrstah. Spodaj je podrobneje napisano, zdaj pa bom rekel, da je izvedeno tako, da se porabi čim manj pomnilnika in da procesor ne miruje. Vse to je izvedeno ob predpostavki, da eno delo ne uporablja preveč procesorskih ciklov.
To ne velja za nevezane čakalne vrste. V bistvu takšne čakalne vrste preprosto zagotovijo kontekst delavcem in jih začnejo čim prej.
Zato je treba uporabiti nepritrjene čakalne vrste, če se pričakuje intenzivna obremenitev procesorja, saj bo v tem primeru razporejevalnik poskrbel za vzporedno izvajanje na več jedrih.

Po analogiji z opravilnimi programi se lahko delom dodeli prioriteta izvajanja, normalna ali visoka. Prioriteta je skupna za celotno čakalno vrsto. Privzeto ima čakalna vrsta običajno prioriteto in če nastavite zastavico WQ_HIGHPRI, potem, v skladu s tem, visoko.

Zastava WQ_CPE_INTENSIVE je smiselno samo za vezane čakalne vrste. Ta zastavica je zavrnitev sodelovanja v dodatni organizaciji vzporedne izvedbe. To zastavico je treba uporabiti, ko se pričakuje, da bo delo porabilo veliko časa procesorja; v tem primeru je bolje preložiti odgovornost na razporejevalnik. To je podrobneje opisano spodaj.

Zastave WQ_FREZABLE in WQ_MEM_RECLAIM so specifični in presegajo okvir teme, zato se na njih ne bomo podrobneje spuščali.

Včasih je smiselno, da ne ustvarite lastnih čakalnih vrst, ampak uporabite skupne. Glavni:

  • system_wq - vezana čakalna vrsta za hitro delo
  • system_long_wq - vezana čakalna vrsta za dela, za katera se pričakuje, da bodo trajala dolgo časa
  • system_unbound_wq - nevezana čakalna vrsta

O delu in njegovem načrtovanju

Zdaj pa se lotimo del. Najprej si poglejmo makre za inicializacijo, deklaracijo in pripravo:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* v času prevajanja */ INIT(_DELAYED)_WORK(_work, _func); /* med izvajanjem */ PREPARE(_DELAYED)_WORK(_work, _func); /* za spremembo funkcije, ki se izvaja */
Dela se v čakalno vrsto dodajajo s funkcijami:
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); /* delo bo dodano v čakalno vrsto šele po izteku zakasnitve */
O tem se je vredno podrobneje pogovoriti. Čeprav določimo čakalno vrsto kot parameter, dela dejansko niso postavljena v samo delovno čakalno vrsto, kot se morda zdi, ampak v popolnoma drugo entiteto - na seznam čakalnih vrst strukture worker_pool. Struktura worker_pool, je pravzaprav najpomembnejša entiteta pri organiziranju mehanizma delovne vrste, čeprav za uporabnika ostaja v zakulisju. Z njimi delajo delavci in v njih so shranjene vse osnovne informacije.

Zdaj pa poglejmo, kateri bazeni so v sistemu.
Za začetek bazeni za vezane čakalne vrste (na sliki). Za vsako CPU sta statično dodeljeni dve skupini delavcev: eno za delo z visoko prioriteto, drugo za delo z običajno prioriteto. Se pravi, če imamo štiri jedra, potem bo vezanih bazenov samo osem, kljub temu, da je lahko delovnih vrst poljubno.
Ko ustvarimo delovno čakalno vrsto, ima za vsak CPE dodeljeno storitev pool_workqueue(pwq). Vsaka taka pool_workqueue je povezana z delovno skupino, ki je dodeljena istemu CPU in ustreza prednostni vrsti čakalne vrste. Prek njih delovna čakalna vrsta sodeluje z naborom delavcev.
Delavci izvajajo delo iz skupine delavcev brez razlike, ne da bi razlikovali, kateri delovni vrsti so prvotno pripadali.

Za nepovezane čakalne vrste so skupine delavcev dodeljene dinamično. Vse čakalne vrste je mogoče razdeliti v ekvivalenčne razrede glede na njihove parametre, za vsak tak razred pa se ustvari lasten nabor delavcev. Do njih se dostopa s posebno razpršilno tabelo, kjer je ključ nabor parametrov, vrednost pa delovni bazen.
Pravzaprav je za nevezane čakalne vrste vse nekoliko bolj zapleteno: če so bile za vezane čakalne vrste pwq in čakalne vrste ustvarjene za vsak CPU, so tukaj ustvarjene za vsako vozlišče NUMA, vendar je to dodatna optimizacija, ki je ne bomo podrobno obravnavali.

Raznorazne malenkosti

Navedel bom tudi nekaj funkcij iz API-ja, da dopolnim sliko, vendar o njih ne bom podrobno govoril:
/* Vsili dokončanje */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Prekliči izvajanje dela */ 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); /* Izbriši čakalno vrsto */ void destroy_workqueue(struct workqueue_struct *wq);

Kako delavci opravljajo svoje delo

Zdaj, ko smo seznanjeni z API-jem, poskusimo podrobneje razumeti, kako vse deluje in se upravlja.
Vsak bazen ima nabor delavcev, ki opravljajo naloge. Poleg tega se število delavcev dinamično spreminja in prilagaja trenutnim razmeram.
Kot smo že ugotovili, so delavci niti, ki opravljajo delo v kontekstu jedra. Delavec jih po vrstnem redu pridobi enega za drugim iz zbirke delavcev, ki je z njim povezana, delavci pa lahko, kot že vemo, pripadajo različnim izvornim čakalnim vrstam.

Delavci so lahko pogojno v treh logičnih stanjih: lahko mirujejo, tečejo ali upravljajo.
Delavec lahko stati brez dela in storiti ničesar. To je na primer, ko se vse delo že izvaja. Ko delavec preide v to stanje, zaspi in v skladu s tem ne bo izvršil, dokler se ne zbudi;
Če upravljanje bazena ni potrebno in seznam načrtovanih del ni prazen, jih delavec začne izvajati. Takšne delavce bomo običajno imenovali teče.
Če je potrebno, vlogo prevzame delavec vodja bazen. Skupina ima lahko samo enega upravljavca ali pa nobenega delavca. Njegova naloga je vzdrževati optimalno število delavcev na bazen. Kako mu to uspe? Najprej se izbrišejo delavci, ki so dalj časa mirovali. Drugič, novi delavci nastanejo, če so hkrati izpolnjeni trije pogoji:

  • še vedno je treba dokončati naloge (dela v bazenu)
  • brez prostih delavcev
  • ni delovnih delavcev (torej aktivnih in nespečih)
Vendar ima zadnji pogoj svoje nianse. Če čakalne vrste bazena niso povezane, se delavci, ki se izvajajo, ne upoštevajo; zanje ta pogoj vedno velja. Enako velja v primeru delavca, ki izvaja nalogo iz povezanega, vendar z zastavico WQ_CPE_INTENSIVE, čakalne vrste. Poleg tega se v primeru vezanih čakalnih vrst, ker delavci delajo z deli iz skupnega bazena (ki je eden od dveh za vsako jedro na zgornji sliki), izkaže, da se nekateri štejejo kot delujoči, nekateri pa ne. Iz tega tudi izhaja, da opravljanje dela iz WQ_CPE_INTENSIVEčakalne vrste se morda ne bodo začele takoj, vendar same ne ovirajo izvajanja drugega dela. Zdaj bi moralo biti jasno, zakaj se ta zastavica tako imenuje in zakaj se uporablja, ko pričakujemo, da bo delo trajalo dolgo.

Obračunavanje delovnih delavcev se izvaja neposredno iz glavnega razporejevalnika jedra Linuxa. Ta nadzorni mehanizem zagotavlja optimalno raven sočasnosti in preprečuje, da bi čakalna vrsta dela ustvarila preveč delavcev, hkrati pa tudi ne povzroča predolgega čakanja dela po nepotrebnem.

Kogar zanima, si lahko ogleda delovno funkcijo v jedru, imenuje se worker_thread().

Vse opisane funkcije in strukture si lahko podrobneje ogledate v datotekah include/linux/workqueue.h, jedro/workqueue.c in jedro/workqueue_internal.h. Obstaja tudi dokumentacija o čakalni vrsti Dokumentacija/workqueue.txt.

Omeniti velja tudi, da se mehanizem delovne čakalne vrste v jedru uporablja ne samo za odloženo obdelavo prekinitev (čeprav je to precej pogost scenarij).

Tako smo pogledali mehanizme za odloženo obravnavo prekinitev v jedru Linuxa - tasklet in workqueue, ki sta posebna oblika večopravilnosti. O prekinitvah, opravilnih programih in delovnih čakalnih vrstah lahko preberete v knjigi »Gonilniki naprav Linux« avtorjev Jonathana Corbeta, Grega Kroah-Hartmana in Alessandra Rubinija, čeprav so tam informacije včasih zastarele.

Nadaljujemo s temo večnitnosti v jedru Linuxa. Zadnjič sem govoril o prekinitvah, njihovi obdelavi in ​​taskletih, in ker je bilo prvotno mišljeno, da bo to en članek, se bom v zgodbi o workqueue nanašal na tasklete, ob predpostavki, da jih bralec že pozna.
Tako kot zadnjič bom poskušal svojo zgodbo narediti čim bolj podrobno in podrobno.

Članki v seriji:

  1. Večopravilnost v jedru Linuxa: delovna čakalna vrsta

Delovna vrsta

Delovna vrsta- to so bolj zapletene in težke entitete kot tasklets. Tu niti ne bom poskušal opisati vseh zapletenosti implementacije, upam pa, da bom bolj ali manj podrobno analiziral najpomembnejše stvari.
Delovne čakalne vrste, tako kot tasklets, služijo za odloženo obdelavo prekinitev (čeprav jih je mogoče uporabiti tudi za druge namene), vendar se za razliko od tasklets izvajajo v kontekstu jedrnega procesa, zato jim ni treba biti atomski in lahko uporabljajo spanje. (), različna orodja za sinhronizacijo itd.

Najprej razumemo, kako je na splošno organiziran proces obdelave delovne vrste. Slika prikazuje zelo približno in poenostavljeno, kako se vse dejansko zgodi, je podrobno opisano spodaj.

V to temno snov je vpletenih več entitet.
Prvič, delovni predmet(na kratko samo delo) je struktura, ki opisuje funkcijo (na primer upravljalnik prekinitev), ki jo želimo načrtovati. Lahko si jo predstavljamo kot analogno strukturi tasklet. Pri razporejanju so bili Taskleti dodani v čakalne vrste, skrite pred uporabnikom, zdaj pa moramo uporabiti posebno čakalno vrsto - delovna čakalna vrsta.
Opravila se zbirajo s funkcijo razporejevalnika, delovno čakalno vrsto pa obdelujejo posebne niti, imenovane delavci.
Delavec zagotavljajo asinhrono izvajanje del iz delovne čakalne vrste. Čeprav imenujejo delo po vrstnem redu rotacije, v splošnem primeru ne gre za strogo, zaporedno izvajanje: navsezadnje se tukaj odvijajo prednost, spanje, čakanje itd.

Na splošno so delavci niti jedra, kar pomeni, da jih nadzoruje glavni razporejevalnik jedra Linuxa. Delavci pa delno posegajo v načrtovanje dodatne organizacije vzporednega izvajanja dela. O tem bomo podrobneje razpravljali v nadaljevanju.

Za oris glavnih zmožnosti mehanizma delovne čakalne vrste predlagam, da raziščete API.

O čakalni vrsti in njenem ustvarjanju

alloc_workqueue(fmt, zastavice, max_active, args...)
Parametra fmt in args sta format printf za ime in argumente zanj. Parameter max_activate je odgovoren za največje število del, ki se lahko iz te čakalne vrste izvajajo vzporedno na eni CPU.
Čakalno vrsto je mogoče ustvariti z naslednjimi zastavicami:
  • WQ_HIGHPRI
  • WQ_NEVEZAN
  • WQ_CPE_INTENSIVE
  • WQ_FREZABLE
  • WQ_MEM_RECLAIM
Posebno pozornost je treba nameniti zastavi WQ_NEVEZAN. Na podlagi prisotnosti te zastavice so čakalne vrste razdeljene na vezane in nevezane.
V povezanih čakalnih vrstah Ko so dodana, so dela vezana na trenutno CPE, kar pomeni, da se v takšnih čakalnih vrstah dela izvajajo v jedru, ki ga načrtuje. V zvezi s tem so vezane čakalne vrste podobne opravilnim programom.
V nevezanih čakalnih vrstah dela se lahko izvajajo na katerem koli jedru.

Pomembna lastnost implementacije delovne čakalne vrste v jedru Linuxa je dodatna organizacija vzporednega izvajanja, ki je prisotno v vezanih čakalnih vrstah. Spodaj je podrobneje napisano, zdaj pa bom rekel, da je izvedeno tako, da se porabi čim manj pomnilnika in da procesor ne miruje. Vse to je izvedeno ob predpostavki, da eno delo ne uporablja preveč procesorskih ciklov.
To ne velja za nevezane čakalne vrste. V bistvu takšne čakalne vrste preprosto zagotovijo kontekst delavcem in jih začnejo čim prej.
Zato je treba uporabiti nepritrjene čakalne vrste, če se pričakuje intenzivna obremenitev procesorja, saj bo v tem primeru razporejevalnik poskrbel za vzporedno izvajanje na več jedrih.

Po analogiji z opravilnimi programi se lahko delom dodeli prioriteta izvajanja, normalna ali visoka. Prioriteta je skupna za celotno čakalno vrsto. Privzeto ima čakalna vrsta običajno prioriteto in če nastavite zastavico WQ_HIGHPRI, potem, v skladu s tem, visoko.

Zastava WQ_CPE_INTENSIVE je smiselno samo za vezane čakalne vrste. Ta zastavica je zavrnitev sodelovanja v dodatni organizaciji vzporedne izvedbe. To zastavico je treba uporabiti, ko se pričakuje, da bo delo porabilo veliko časa procesorja; v tem primeru je bolje preložiti odgovornost na razporejevalnik. To je podrobneje opisano spodaj.

Zastave WQ_FREZABLE in WQ_MEM_RECLAIM so specifični in presegajo okvir teme, zato se na njih ne bomo podrobneje spuščali.

Včasih je smiselno, da ne ustvarite lastnih čakalnih vrst, ampak uporabite skupne. Glavni:

  • system_wq - vezana čakalna vrsta za hitro delo
  • system_long_wq - vezana čakalna vrsta za dela, za katera se pričakuje, da bodo trajala dolgo časa
  • system_unbound_wq - nevezana čakalna vrsta

O delu in njegovem načrtovanju

Zdaj pa se lotimo del. Najprej si poglejmo makre za inicializacijo, deklaracijo in pripravo:
DECLARE(_DELAYED)_WORK(ime, void (*funkcija)(struct work_struct *work)); /* v času prevajanja */ INIT(_DELAYED)_WORK(_work, _func); /* med izvajanjem */ PREPARE(_DELAYED)_WORK(_work, _func); /* za spremembo funkcije, ki se izvaja */
Dela se v čakalno vrsto dodajajo s funkcijami:
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); /* delo bo dodano v čakalno vrsto šele po izteku zakasnitve */
O tem se je vredno podrobneje pogovoriti. Čeprav določimo čakalno vrsto kot parameter, dela dejansko niso postavljena v samo delovno čakalno vrsto, kot se morda zdi, ampak v popolnoma drugo entiteto - na seznam čakalnih vrst strukture worker_pool. Struktura worker_pool, je pravzaprav najpomembnejša entiteta pri organiziranju mehanizma delovne vrste, čeprav za uporabnika ostaja v zakulisju. Z njimi delajo delavci in v njih so shranjene vse osnovne informacije.

Zdaj pa poglejmo, kateri bazeni so v sistemu.
Za začetek bazeni za vezane čakalne vrste (na sliki). Za vsako CPU sta statično dodeljeni dve skupini delavcev: eno za delo z visoko prioriteto, drugo za delo z običajno prioriteto. Se pravi, če imamo štiri jedra, potem bo vezanih bazenov samo osem, kljub temu, da je lahko delovnih vrst poljubno.
Ko ustvarimo delovno čakalno vrsto, ima za vsak CPE dodeljeno storitev pool_workqueue(pwq). Vsaka taka pool_workqueue je povezana z delovno skupino, ki je dodeljena istemu CPU in ustreza prednostni vrsti čakalne vrste. Prek njih delovna čakalna vrsta sodeluje z naborom delavcev.
Delavci izvajajo delo iz skupine delavcev brez razlike, ne da bi razlikovali, kateri delovni vrsti so prvotno pripadali.

Za nepovezane čakalne vrste so skupine delavcev dodeljene dinamično. Vse čakalne vrste je mogoče razdeliti v ekvivalenčne razrede glede na njihove parametre, za vsak tak razred pa se ustvari lasten nabor delavcev. Do njih se dostopa s posebno razpršilno tabelo, kjer je ključ nabor parametrov, vrednost pa delovni bazen.
Pravzaprav je za nevezane čakalne vrste vse nekoliko bolj zapleteno: če so bile za vezane čakalne vrste pwq in čakalne vrste ustvarjene za vsak CPU, so tukaj ustvarjene za vsako vozlišče NUMA, vendar je to dodatna optimizacija, ki je ne bomo podrobno obravnavali.

Raznorazne malenkosti

Navedel bom tudi nekaj funkcij iz API-ja, da dopolnim sliko, vendar o njih ne bom podrobno govoril:
/* Vsili dokončanje */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Prekliči izvajanje dela */ 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); /* Izbriši čakalno vrsto */ void destroy_workqueue(struct workqueue_struct *wq);

Kako delavci opravljajo svoje delo

Zdaj, ko smo seznanjeni z API-jem, poskusimo podrobneje razumeti, kako vse deluje in se upravlja.
Vsak bazen ima nabor delavcev, ki opravljajo naloge. Poleg tega se število delavcev dinamično spreminja in prilagaja trenutnim razmeram.
Kot smo že ugotovili, so delavci niti, ki opravljajo delo v kontekstu jedra. Delavec jih po vrstnem redu pridobi enega za drugim iz zbirke delavcev, ki je z njim povezana, delavci pa lahko, kot že vemo, pripadajo različnim izvornim čakalnim vrstam.

Delavci so lahko pogojno v treh logičnih stanjih: lahko mirujejo, tečejo ali upravljajo.
Delavec lahko stati brez dela in storiti ničesar. To je na primer, ko se vse delo že izvaja. Ko delavec preide v to stanje, zaspi in v skladu s tem ne bo izvršil, dokler se ne zbudi;
Če upravljanje bazena ni potrebno in seznam načrtovanih del ni prazen, jih delavec začne izvajati. Takšne delavce bomo običajno imenovali teče.
Če je potrebno, vlogo prevzame delavec vodja bazen. Skupina ima lahko samo enega upravljavca ali pa nobenega delavca. Njegova naloga je vzdrževati optimalno število delavcev na bazen. Kako mu to uspe? Najprej se izbrišejo delavci, ki so dalj časa mirovali. Drugič, novi delavci nastanejo, če so hkrati izpolnjeni trije pogoji:

  • še vedno je treba dokončati naloge (dela v bazenu)
  • brez prostih delavcev
  • ni delovnih delavcev (torej aktivnih in nespečih)
Vendar ima zadnji pogoj svoje nianse. Če čakalne vrste bazena niso povezane, se delavci, ki se izvajajo, ne upoštevajo; zanje ta pogoj vedno velja. Enako velja v primeru delavca, ki izvaja nalogo iz povezanega, vendar z zastavico WQ_CPE_INTENSIVE, čakalne vrste. Poleg tega se v primeru vezanih čakalnih vrst, ker delavci delajo z deli iz skupnega bazena (ki je eden od dveh za vsako jedro na zgornji sliki), izkaže, da se nekateri štejejo kot delujoči, nekateri pa ne. Iz tega tudi izhaja, da opravljanje dela iz WQ_CPE_INTENSIVEčakalne vrste se morda ne bodo začele takoj, vendar same ne ovirajo izvajanja drugega dela. Zdaj bi moralo biti jasno, zakaj se ta zastavica tako imenuje in zakaj se uporablja, ko pričakujemo, da bo delo trajalo dolgo.

Obračunavanje delovnih delavcev se izvaja neposredno iz glavnega razporejevalnika jedra Linuxa. Ta nadzorni mehanizem zagotavlja optimalno raven sočasnosti in preprečuje, da bi čakalna vrsta dela ustvarila preveč delavcev, hkrati pa tudi ne povzroča predolgega čakanja dela po nepotrebnem.

Kogar zanima, si lahko ogleda delovno funkcijo v jedru, imenuje se worker_thread().

Vse opisane funkcije in strukture si lahko podrobneje ogledate v datotekah include/linux/workqueue.h, jedro/workqueue.c in jedro/workqueue_internal.h. Obstaja tudi dokumentacija o čakalni vrsti Dokumentacija/workqueue.txt.

Omeniti velja tudi, da se mehanizem delovne čakalne vrste v jedru uporablja ne samo za odloženo obdelavo prekinitev (čeprav je to precej pogost scenarij).

Tako smo pogledali mehanizme za odloženo obravnavo prekinitev v jedru Linuxa - tasklet in workqueue, ki sta posebna oblika večopravilnosti. O prekinitvah, opravilnih programih in delovnih čakalnih vrstah lahko preberete v knjigi »Gonilniki naprav Linux« avtorjev Jonathana Corbeta, Grega Kroah-Hartmana in Alessandra Rubinija, čeprav so tam informacije včasih zastarele.