Princípy fungovania v operačných systémoch podobných UNIX s použitím Linuxu ako príkladu. Vytvorenie vlastného ľahkého procesu

Jeden z najotravnejších momentov pri prechode z prostredia do Na báze Windows na použitie príkazový riadok– strata jednoduchého multitaskingu. Dokonca aj v systéme Linux, ak používate systém X Window, môžete jednoducho kliknúť myšou nový program a otvorte ho. Na príkazovom riadku ste však dosť uviazli pri monotaskingu. V tomto článku vám to ukážeme ako multitasking v Linuxe pomocou príkazového riadku.

Zázemie a prioritné riadenie procesov

V Linuxe však stále existujú spôsoby multitaskingu a niektoré z nich sú komplexnejšie ako iné. Jedna vstavaná metóda, ktorá nevyžaduje žiadne ďalšie softvér, jednoducho presúva procesy na pozadí a v popredí. Hovoríme o tom. Má to však určité nevýhody.

Nechránené

Po prvé Ak chcete odoslať proces na pozadí, musíte ho najskôr pozastaviť. Už spustený program nie je možné poslať do pozadia a zároveň ho udržiavať.

Po druhé, musíte prerušiť pracovný postup, aby ste mohli spustiť nový príkaz. Musíte sa dostať von z toho, čo práve robíte, a zadať do shellu viac príkazov. Funguje to, ale je to nepohodlné.

Po tretie, mali by ste monitorovať výstupy z procesov na pozadí. Akýkoľvek výstup z nich sa objaví na príkazovom riadku a bude zasahovať do toho, čo práve robíte. Takže úlohy na pozadí musia buď presmerovať svoj výstup na samostatný súbor alebo ich treba úplne deaktivovať.

Kvôli týmto nedostatkom existujú obrovské problémy pri riadení procesu na pozadí a v popredí. Najlepšie rozhodnutie– použite obslužný program príkazového riadka „screen“, ako je znázornené nižšie.

Najprv však otvoríte novú reláciu SSH

Nezabudnite, že práve otvárate novú reláciu SSH.

Môže byť nepohodlné neustále otvárať nové relácie. A práve vtedy potrebujete „obrazovku“

Utility obrazovke umožňuje vytvoriť viacero pracovných postupov otvorených súčasne - najbližší analóg k „oknám“. Štandardne je k dispozícii v bežných repozitároch Linuxu. Nainštalujte ho na CentOS/RHEL pomocou nasledujúceho príkazu:

Inštalačná obrazovka Sudo yum

Otvorenie novej obrazovky

Teraz začnite reláciu zadaním „screen“.

Toto vytvorí prázdne okno v rámci existujúcej relácie SSH a priraďte jej číslo, ktoré je zobrazené v hlavičke takto:

Naša obrazovka tu má číslo „0“, ako je znázornené. Na tejto snímke obrazovky používame fiktívny príkaz „read“ na zablokovanie terminálu a jeho čakanie na vstup. Teraz povedzme, že na počkanie chceme urobiť niečo iné.

Na otvorenie nová obrazovka a urobte niečo iné, vytlačíme:

Ctrl+a c

„ctrl+a“ je predvolená kombinácia kláves na ovládanie obrazoviek v programe obrazovky. Čo napíšete po ňom, určuje akciu. Napríklad:

  • ctrl+a c – C aktivuje novú obrazovku
  • ctrl+a [číslo]– skok na konkrétne číslo obrazovky
  • ctrl+a k – K vypne aktuálnu obrazovku
  • ctrl+a n – Prejsť na obrazovku n
  • ctrl+a “- zobrazí všetky aktívne obrazovky v relácii

Ak stlačíme „ctrl+a c“ dostaneme novú obrazovku s novým číslom.

Na navigáciu v zozname a prechod na požadovanú obrazovku môžete použiť kurzorové klávesy.
Obrazovky sú najbližšie k „oknám“, ako systém v príkaze Linuxový reťazec. Samozrejme, nie je to také jednoduché ako klikanie myšou, ale grafický subsystém je veľmi náročný na zdroje. S obrazovkami môžete získať takmer rovnakú funkčnosť a povoliť plný multitasking!

Procesy v systéme UNIX

V UNIXe je hlavným prostriedkom organizácie a jednotkou multitaskingu proces. Operačný systém manipuluje s obrazom procesu, ktorý predstavuje programový kód, ako aj so sekciami údajov procesu, ktoré definujú prostredie vykonávania.

Počas vykonávania alebo počas čakania „v krídlach“ sú procesy obsiahnuté vo virtuálnej pamäti s organizáciou stránky. Časť tejto virtuálnej pamäte je namapovaná na fyzickú pamäť. Časť fyzickej pamäte je vyhradená pre jadro operačného systému. Používatelia majú prístup len k pamäti zostávajúcej pre procesy. V prípade potreby sa stránky procesnej pamäte vymenia z fyzickej pamäte na disk, do oblasti swap. Pri prístupe na stránku vo virtuálnej pamäti, ak nie je vo fyzickej pamäti, dôjde k jej výmene z disku.

Virtuálna pamäť je implementovaná a automaticky udržiavaná jadrom UNIX.

Typy procesov

V operačných systémoch UNIX existujú tri typy procesov: systémový, procesy démonov A aplikačných procesov.

Systémové procesy sú súčasťou jadra a vždy sa nachádzajú v Náhodný vstup do pamäťe. Systémové procesy nemajú zodpovedajúce programy vo forme spustiteľných súborov a spúšťajú sa špeciálnym spôsobom pri inicializácii jadra systému. Vykonávacie inštrukcie a údaje týchto procesov sa nachádzajú v jadre systému, takže môžu volať funkcie a pristupovať k údajom, ku ktorým iné procesy nemajú prístup.

Systémové procesy zahŕňajú proces počiatočnej inicializácie, init, ktorý je predchodcom všetkých ostatných procesov. Hoci init nie je súčasťou jadra a jeho spustenie prebieha zo spustiteľného súboru, jeho činnosť je životne dôležitá pre fungovanie celého systému ako celku.

Démoni- sú to neinteraktívne procesy, ktoré sa spúšťajú obvyklým spôsobom - načítaním príslušných programov do pamäte a sú vykonávané na pozadí. Démony sa zvyčajne spúšťajú počas inicializácie systému, ale po inicializácii jadra a zabezpečujú prevádzku rôznych podsystémov UNIX: systémy terminálového prístupu, tlačové systémy, sieťové služby atď. Démoni nie sú spojení so žiadnym používateľom. Démoni väčšinou čakajú na žiadosť jedného alebo druhého procesu konkrétnu službu.



TO aplikačných procesov zahŕňa všetky ostatné procesy bežiace v systéme. Zvyčajne ide o procesy vytvorené v rámci relácie používateľa. Najdôležitejším užívateľským procesom je počiatočný tlmočník príkazov, ktorý poskytuje vykonávanie užívateľských príkazov v systéme UNIX.

Užívateľské procesy môžu bežať ako interaktívne (preemptívne), tak aj režimy na pozadí. Interaktívne procesy majú výhradné vlastníctvo terminálu a kým takýto proces nedokončí svoju realizáciu, používateľ nemá prístup k príkazovému riadku.

Procesné atribúty

Proces UNIX má množstvo atribútov, ktoré mu to umožňujú operačný systém riadiť jeho prácu. Hlavné atribúty:

· ID procesu (PID), čo umožňuje jadru systému rozlišovať medzi procesmi. Pri vytvorení nový proces, jadro mu pridelí ďalší voľný (t. j. nepriradený k žiadnemu procesu) identifikátor. Prideľovanie identifikátora sa zvyčajne vyskytuje vo vzostupnom poradí, t.j. ID nového procesu je väčšie ako ID procesu vytvoreného pred ním. Ak ID dosiahne maximálnu hodnotu (zvyčajne 65737), ďalší proces získa minimálny voľný PID a cyklus sa zopakuje. Keď proces skončí, jadro uvoľní identifikátor, ktorý používalo.

· ID rodičovského procesu (PPID)– identifikátor procesu, ktorý splodil tento proces. Všetky procesy v systéme okrem systémové procesy a proces init, ktorý je predchodcom zostávajúcich procesov, sú generované jedným z existujúcich alebo predtým existujúcich procesov.

· Prioritná korekcia (NI)– relatívnu prioritu procesu, ktorú plánovač berie do úvahy pri určovaní poradia spustenia. Skutočná distribúcia zdrojov procesora je určená prioritou vykonávania (atribút PRI), v závislosti od viacerých faktorov, najmä od danej relatívnej priority. Relatívnu prioritu systém nemení počas životnosti procesu, hoci ju môže zmeniť používateľ alebo administrátor pri spustení procesu pomocou príkazu pekný. Rozsah hodnôt prírastku priority na väčšine systémov je -20 až 20. Ak nie je zadaný žiadny prírastok, použije sa predvolená hodnota 10. Kladný prírastok znamená zníženie aktuálnej priority. Bežní používatelia môžu nastaviť iba kladný prírastok, a teda iba znížiť prioritu. Používateľ koreň môže nastaviť záporný prírastok, ktorý zvyšuje prioritu procesu a tým prispieva k jej vyššej rýchla práca. Na rozdiel od relatívnej priority, prioritu vykonávania procesu dynamicky mení plánovač.

· Koncová linka (TTY)– terminál alebo pseudoterminál spojený s procesom. Tento terminál je spojený so štandardom tokov: vstup, deň voľna A tok správ o chybách. Streamy ( programové kanály) sú štandardné prostriedky medziprocesová komunikácia v OS UNIX. Procesy démonov nie sú spojené s terminálom.

· Skutočné (UID) a efektívne (EUID) identifikátory používateľa. Skutočné ID používateľa daného procesu je ID používateľa, ktorý proces spustil. Efektívny identifikátor sa používa na určenie prístupových práv procesu k systémovým prostriedkom (predovšetkým zdrojom systém súborov). Zvyčajne sú skutočné a efektívne identifikátory rovnaké, t.j. proces má v systéme rovnaké práva ako používateľ, ktorý ho spustil. Nastavením je však možné dať procesu viac práv ako má používateľ SUID bit keď je účinný identifikátor nastavený na identifikátor vlastníka spustiteľného súboru (napr. koreň).

· Skutočné (GID) a efektívne (EGID) skupinové identifikátory. Skutočné ID skupiny sa rovná primárnemu alebo aktuálnemu ID skupiny používateľa, ktorý spustil proces. Efektívny identifikátor sa používa na určenie prístupových práv k systémovým prostriedkom v mene skupiny. Zvyčajne je efektívne ID skupiny rovnaké ako skutočné. Ale ak je spustiteľný súbor nastavený na Bit SGID, takýto súbor sa spustí s ID skupiny skutočných vlastníkov.

LABORATÓRNE PRÁCE č.3

MULTI-TASKING PROGRAMOVANIE VLINUX

1. Cieľ práce: Oboznámte sa s kompilátorom gcc, technikami ladenia programu a funkciami na prácu s procesmi.

2. Stručné teoretické informácie.

Minimálna sada prepínačov kompilátora gcc je - Wall (zobrazuje všetky chyby a varovania) a - o (výstupný súbor):

gcc - Wall - o print_pid print_pid. c

Tým vytvorí spustiteľný súbor print_pid.

Štandardná knižnica C (libc, implementovaná v Linuxe v glibc) využíva možnosti multitaskingu Unix System V (ďalej len SysV). V libc je typ pid_t definovaný ako celé číslo schopné obsahovať pid. Funkcia, ktorá hlási pid aktuálneho procesu, má prototyp pid_t getpid(void) a je definovaná spolu s pid_t v unistd. h a sys/typy. h).

Ak chcete vytvoriť nový proces, použite funkciu vidlice:

pid_t fork(void)

Vložením oneskorenia náhodnej dĺžky pomocou funkcií spánku a rand môžete jasnejšie vidieť efekt multitaskingu:

to spôsobí, že program „spí“ na náhodný počet sekúnd: od 0 do 3.

Ak chcete zavolať funkciu ako podriadený proces, stačí ju zavolať po rozvetvení:

// ak je spustený podradený proces, zavolajte funkciu

pid=proces(arg);

// ukončite proces

Často je potrebné spustiť iný program ako podriadený proces. Ak to chcete urobiť, použite funkcie rodiny exec:

// ak je spustený podradený proces, potom zavolajte program


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

printf("CHYBA pri spustení procesu\n");

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

// ukončite proces

Rodičovský proces si často potrebuje vymieňať informácie so svojimi deťmi alebo sa s nimi aspoň synchronizovať, aby mohol vykonávať operácie v správnom čase. Jedným zo spôsobov synchronizácie procesov sú funkcie wait a waitpid:

#include

#include

pid_t wait(int *status) - pozastaví vykonávanie aktuálneho procesu, kým sa neskončí niektorý z jeho podradených procesov.

pid_t waitpid (pid_t pid, int *stav, možnosti int) - pozastaví vykonávanie aktuálneho procesu, kým sa zadaný proces nedokončí alebo neskontroluje dokončenie zadaného procesu.

Ak potrebujete zistiť stav podriadeného procesu, keď sa ukončí, a hodnotu, ktorú vráti, použite makro WEXITSTATUS a ako parameter mu odovzdajte stav podriadeného procesu.

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

if (pid == stav) (

printf("PID: %d, vysledok = %d\n", pid, WEXITSTATUS(stav)); )

Na zmenu priorít vytvorených procesov sa používa setpriorita a funkcie. Priority sú nastavené v rozsahu od -20 (najvyššia) do 20 (najnižšia), normálna hodnota je 0. Upozorňujeme, že iba superužívateľ môže zvýšiť prioritu nad normálnu!

#include

#include

int proces(int i) (

setpriority(PRIO_PROCESS, getpid(),i);

printf("Proces %d ID vlákna: %d pracujúci s prioritou %d\n",i, getpid(),getpriority(PRIO_PROCESS, getpid()));

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

Ak chcete zabiť proces, použite funkciu zabitia:

#include

#include

int kill(pid_t pid, int sig);

Ak pid > 0, potom špecifikuje PID procesu, do ktorého sa signál posiela. Ak pid = 0, signál sa odošle všetkým procesom skupiny, do ktorej aktuálny proces patrí.

sig - typ signálu. Niektoré typy signálov v systéme Linux:

SIGKILL Tento signál spôsobí okamžité ukončenie procesu. Proces nemôže tento signál ignorovať.

SIGTERM Tento signál je žiadosťou o ukončenie procesu.

SIGCHLD Systém odošle tento signál procesu, keď jeden z jeho podriadených procesov skončí. Príklad:

if (pid[i] == stav) (

printf("ID vlákna: %d ukončené so stavom %d\n", pid[i], WEXITSTATUS(stav));

else kill(pid[i],SIGKILL);

3. Metodické pokyny.

3.1. Aby ste sa zoznámili s možnosťami kompilátora gcc a popisom funkcií jazyka C, použite pokyny man and info.

3.2. Na ladenie programov je vhodné použiť vstavaný editor správcu súborov Polnočný veliteľ(MC), ktorý farebne zvýrazňuje rôzne jazykové konštrukcie a označuje polohu kurzora v súbore (riadok, stĺpec) v hornom riadku obrazovky.

3.3. Správca súborov Midnight Commander má vyrovnávaciu pamäť príkazov, ktorú je možné vyvolať klávesovou skratkou - H, ktoré je možné posúvať pomocou kurzorových šípok (hore a dole). Ak chcete vložiť príkaz z vyrovnávacej pamäte do príkazového riadka, použite kláves , na úpravu príkazu z vyrovnávacej pamäte - klávesy<- и ->, A .


3.4. Pamätajte, že aktuálny adresár nie je obsiahnutý v ceste, takže musíte spustiť program ako "./print_pid" z príkazového riadku. V MC stačí umiestniť kurzor myši na súbor a kliknúť .

3.5. Ak chcete zobraziť výsledok spustenia programu, použite klávesovú skratku - O. Pracujú aj v režime úpravy súborov.

3.6. Na protokolovanie výsledkov vykonávania programu je vhodné použiť presmerovanie výstupu z konzoly do súboru: ./test > result. TXT

3.7. Na prístup k súborom vytvoreným na Linuxový server, použite protokol ftp, pre ktorý je klientsky program dostupný v systéme Windows 2000 a je v ňom zabudovaný Správca súborov FAR. V čom účtu a heslo je rovnaké ako pri pripojení cez ssh.

4.1. Oboznámte sa s možnosťami a metódami kompilátora gcc na ladenie programov.

4.2. Pre varianty úloh z laboratórnej práce č.1 napíšte a odlaďte program, ktorý implementuje vygenerovaný proces.

4.3. Pre možnosti úloh od laboratórne práce 1 napíšte a odlaďte program, ktorý implementuje nadradený proces, ktorý volá a monitoruje stav podriadených procesov – programov (čaká na ich dokončenie alebo ich zničí, podľa možnosti).

4.4. Pre varianty úloh z laboratórnej práce č.1 napíšte a odlaďte program, ktorý implementuje nadradený proces, ktorý volá a monitoruje stav podriadených procesov - funkcií (čaká na ich dokončenie alebo ich zničí, podľa variantu).

5. Možnosti úloh. Pozrite si možnosti úloh z laboratórnej práce č.1

6. Obsah správy.

6.1. Cieľ práce.

6.2. Možnosť úlohy.

6.3. Zoznamy programov.

6.4. Protokoly vykonávania programu.

7. Kontrolné otázky.

7.1. Funkcie kompilácie a spúšťania programov C v systéme Linux.

7.2. Čo je pid, ako ho určiť v operačnom systéme a programe?

7.3. Funkcia vidlice - účel, aplikácia, návratová hodnota.

7.4. Ako spustiť funkciu v splodenom procese? Program?

7.5. Spôsoby synchronizácie rodičovských a podradených procesov.

7.6. Ako zistiť stav spawnovaného procesu pri jeho ukončení a hodnotu, ktorú vráti?

7.7. Ako riadiť priority procesov?

7.8. Ako zabiť proces v operačnom systéme a programe?

Pokračujeme v téme multithreadingu v jadre Linuxu. Minule som hovoril o prerušeniach, ich spracovaní a taskletoch, a keďže to bolo pôvodne zamýšľané ako jeden článok, v mojom príbehu o workqueue budem odkazovať na tasklety, za predpokladu, že ich čitateľ už pozná.
Tak ako minule, aj teraz sa pokúsim urobiť môj príbeh čo najpodrobnejší a najpodrobnejší.

Články v seriáli:

  1. Multitasking v jadre Linuxu: pracovný front

Pracovná fronta

Pracovná fronta- sú to zložitejšie a ťažšie entity ako tasklety. Nebudem sa tu ani snažiť opísať všetky zložitosti implementácie, ale dúfam, že to najdôležitejšie rozoberiem viac či menej podrobne.
Pracovné fronty, podobne ako tasklety, slúžia na spracovanie odloženého prerušenia (hoci sa dajú použiť aj na iné účely), ale na rozdiel od taskletov sa vykonávajú v kontexte procesu jadra, teda nemusia byť atomické a môžu využívať spánok. () funkcia, rôzne synchronizačné nástroje atď.

Poďme najprv pochopiť, ako je vo všeobecnosti organizovaný proces spracovania pracovného frontu. Obrázok to ukazuje veľmi približne a zjednodušene, ako sa vlastne všetko deje je podrobne popísané nižšie.

Do tejto temnej hmoty je zapojených niekoľko subjektov.
po prvé, pracovná položka(len v skratke) je štruktúra, ktorá popisuje funkciu (napríklad obsluhu prerušení), ktorú chceme naplánovať. Možno si ju predstaviť ako analógiu štruktúry taskletu. Pri plánovaní boli Tasklety pridané do frontov skrytých pred používateľom, ale teraz musíme použiť špeciálny front - pracovný front.
Úlohy sú rakované funkciou plánovača a pracovný front je spracovávaný špeciálnymi vláknami nazývanými pracovníci.
Pracovník's poskytujú asynchrónne vykonávanie prác z pracovného frontu. Hoci prácu nazývajú v poradí rotácie, vo všeobecnom prípade nemôže byť reč o striktnom postupnom vykonávaní: veď sa tu odohráva preempcia, spánok, čakanie atď.

Vo všeobecnosti sú pracovníci vláknami jadra, to znamená, že ich riadi hlavný plánovač jadra Linuxu. Pracovníci však čiastočne zasahujú do plánovania dodatočnej organizácie paralelného vykonávania práce. Toto bude podrobnejšie diskutované nižšie.

Ak chcete načrtnúť hlavné možnosti mechanizmu pracovného frontu, navrhujem preskúmať rozhranie API.

O fronte a jej tvorbe

alloc_workqueue(fmt, príznaky, max_active, argumenty...)
Parametre fmt a args sú formát printf pre názov a jeho argumenty. Parameter max_activate je zodpovedný za maximálny počet prác, ktoré je možné z tohto frontu vykonať paralelne na jednom CPU.
Front je možné vytvoriť s nasledujúcimi príznakmi:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREEZABLE
  • WQ_MEM_RECLAIM
Osobitná pozornosť by sa mala venovať vlajke WQ_UNBOUND. Na základe prítomnosti tohto príznaku sa rady delia na viazané a nepriviazané.
V prepojených radoch Po pridaní sú práce viazané na aktuálny CPU, to znamená, že v takýchto frontoch sú práce vykonávané na jadre, ktoré ju plánuje. V tomto ohľade sa viazané fronty podobajú taskletom.
V nepripojených frontoch práca môže byť vykonaná na akomkoľvek jadre.

Dôležitou črtou implementácie pracovného frontu v jadre Linuxu je dodatočná organizácia paralelného vykonávania, ktorá je prítomná vo viazaných frontoch. Nižšie je to napísané podrobnejšie, ale teraz poviem, že sa to robí tak, aby sa spotrebovalo čo najmenej pamäte a aby procesor nezostával nečinný. To všetko je implementované s predpokladom, že jedna práca nepoužíva príliš veľa procesorových cyklov.
Toto neplatí pre nepripojené fronty. Takéto fronty v podstate jednoducho poskytujú pracovníkom kontext a spúšťajú ich čo najskôr.
Nepripojené fronty by sa teda mali používať, ak sa očakáva náročné zaťaženie CPU, pretože v tomto prípade sa plánovač postará o paralelné vykonávanie na viacerých jadrách.

Analogicky s taskletmi možno prácam priradiť prioritu vykonávania, normálnu alebo vysokú. Priorita je spoločná pre celý rad. Predvolene má front normálnu prioritu a ak nastavíte príznak WQ_HIGHPRI, potom podľa toho vysoká.

Vlajka WQ_CPU_INTENSIVE má zmysel len pre viazané fronty. Tento príznak predstavuje odmietnutie zúčastniť sa na dodatočnej organizácii paralelného vykonávania. Tento príznak by sa mal použiť, keď sa očakáva, že práca zaberie veľa času CPU, v takom prípade je lepšie presunúť zodpovednosť na plánovač. Toto je podrobnejšie popísané nižšie.

Vlajky WQ_FREEZABLE A WQ_MEM_RECLAIM sú špecifické a presahujú rámec témy, preto sa im nebudeme podrobne venovať.

Niekedy má zmysel nevytvárať si vlastné fronty, ale používať bežné. Tie hlavné:

  • system_wq - viazaný front pre rýchlu prácu
  • system_long_wq - viazaný front pre práce, pri ktorých sa očakáva, že budú trvať dlho
  • system_unbound_wq - neviazaný front

O práci a ich plánovaní

Teraz sa poďme zaoberať dielami. Najprv sa pozrime na inicializačné, deklaračné a prípravné makrá:
DECLARE(_DELAYED)_WORK(meno, void (*funkcia)(struct work_struct *work)); /* v čase kompilácie */ INIT(_DELAYED)_WORK(_work, _func); /* počas vykonávania */ PRIPRAVIŤ(_ODLOŽENÉ)_PRÁCA(_work, _func); /* na zmenu vykonávanej funkcie */
Diela sa pridávajú do frontu pomocou funkcií:
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); /* práca bude zaradená do poradia až po uplynutí oneskorenia */
Stojí za to sa tomu podrobnejšie venovať. Hoci ako parameter zadávame front, v skutočnosti sa práce neumiestňujú do samotného pracovného frontu, ako by sa mohlo zdať, ale do úplne inej entity – do zoznamu frontov štruktúry worker_pool. Štruktúra worker_pool, je v skutočnosti najdôležitejšou entitou pri organizovaní mechanizmu pracovného frontu, hoci pre používateľa zostáva v zákulisí. Práve s nimi pracujú pracovníci a práve v nich sú obsiahnuté všetky základné informácie.

Teraz sa pozrime, aké bazény sú v systéme.
Na začiatok, fondy pre viazané fronty (na obrázku). Pre každý CPU sú staticky alokované dve oblasti pracovníkov: jedna pre prácu s vysokou prioritou, druhá pre prácu s normálnou prioritou. To znamená, že ak máme štyri jadrá, potom bude pripojených iba osem bazénov, napriek tomu, že pracovný front môže byť ľubovoľný.
Keď vytvoríme pracovný front, má službu pridelenú každému CPU pool_workqueue(pwq). Každý takýto pool_workqueue je priradený k pracovnej oblasti, ktorá je alokovaná na rovnakom CPU a svojou prioritou zodpovedá typu frontu. Prostredníctvom nich interaguje pracovný front s fondom pracovníkov.
Pracovníci vykonávajú prácu z fondu pracovníkov bez rozdielu, bez rozlíšenia, do ktorého pracovného frontu pôvodne patrili.

Pre nepripojené fronty sa oblasti pracovníkov prideľujú dynamicky. Všetky fronty je možné rozdeliť do tried ekvivalencie podľa ich parametrov a pre každú takúto triedu je vytvorený vlastný fond pracovníkov. Pristupuje sa k nim pomocou špeciálnej hašovacej tabuľky, kde kľúčom je množina parametrov a hodnota je skupina pracovníkov.
V skutočnosti je pre neviazané fronty všetko trochu komplikovanejšie: ak pre viazané fronty boli vytvorené pwq a fronty pre každý CPU, tu sú vytvorené pre každý uzol NUMA, ale toto je ďalšia optimalizácia, ktorú nebudeme podrobne zvažovať.

Všelijaké maličkosti

Uvediem tiež niekoľko funkcií z API na dokončenie obrazu, ale nebudem o nich hovoriť podrobne:
/* Vynútiť dokončenie */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Zruš vykonanie práce */ 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); /* Vymazanie frontu */ void znič_pracovná fronta(struct workqueue_struct *wq);

Ako si pracovníci robia svoju prácu

Teraz, keď sme sa oboznámili s API, skúsme podrobnejšie pochopiť, ako to celé funguje a ako sa riadi.
Každý bazén má skupinu pracovníkov, ktorí vykonávajú úlohy. Počet pracovníkov sa navyše dynamicky mení a prispôsobuje sa aktuálnej situácii.
Ako sme už zistili, pracovníci sú vlákna, ktoré vykonávajú prácu v kontexte jadra. Pracovník ich získava v poradí, jeden po druhom, z fondu pracovníkov, ktorý je s ním spojený, a pracovníci, ako už vieme, môžu patriť do rôznych zdrojových frontov.

Pracovníci môžu byť podmienečne v troch logických stavoch: môžu byť nečinní, bežiaci alebo riadiaci.
Pracovník môže nečinne stáť a nič nerobiť. To je napríklad vtedy, keď sa už vykonávajú všetky práce. Keď pracovník vstúpi do tohto stavu, prejde do režimu spánku, a preto sa nevykoná, kým sa nezobudí;
Ak sa nevyžaduje správa fondu a zoznam naplánovaných prác nie je prázdny, pracovník ich začne vykonávať. Takýchto pracovníkov budeme konvenčne nazývať beh.
V prípade potreby prevezme úlohu pracovník manažér bazén. Fond môže mať buď iba jedného riadiaceho pracovníka alebo žiadneho pracovníka. Jeho úlohou je udržiavať optimálny počet pracovníkov na bazén. Ako to robí? Po prvé, pracovníci, ktorí boli dlho nečinní, sú vymazaní. Po druhé, noví pracovníci sa vytvárajú, ak sú súčasne splnené tri podmienky:

  • stále je potrebné dokončiť úlohy (funguje v skupine)
  • žiadni nečinní pracovníci
  • nie sú žiadni pracujúci pracovníci (t. j. aktívni a nespiaci)
Posledná podmienka má však svoje vlastné nuansy. Ak sú fronty fondu nepripojené, bežiaci pracovníci sa neberú do úvahy; pre nich platí táto podmienka vždy. To isté platí v prípade, že pracovník vykonáva úlohu z prepojenej úlohy, ale s príznakom WQ_CPU_INTENSIVE, rady. Navyše v prípade viazaných radov, keďže pracovníci pracujú s dielami zo spoločného fondu (ktorý je jedným z dvoch pre každé jadro na obrázku vyššie), sa ukazuje, že niektoré z nich sa počítajú ako pracovné a niektoré nie. Z toho tiež vyplýva, že vykonávanie práce od WQ_CPU_INTENSIVE fronty sa nemusia spustiť okamžite, ale samy nezasahujú do vykonávania inej práce. Teraz by malo byť jasné, prečo sa tento príznak tak nazýva a prečo sa používa, keď očakávame, že dokončenie práce bude trvať dlho.

Účtovanie pracujúcich pracovníkov sa vykonáva priamo z hlavného plánovača jadra Linuxu. Tento kontrolný mechanizmus zaisťuje optimálnu úroveň súbežnosti, zabraňuje tomu, aby pracovný front vytváral príliš veľa pracovníkov, ale tiež nespôsoboval, že práca zbytočne dlho čaká.

Kto má záujem, môže sa pozrieť na funkciu worker v jadre, volá sa worker_thread().

Všetky popísané funkcie a štruktúry nájdete podrobnejšie v súboroch include/linux/workqueue.h, kernel/workqueue.c A kernel/workqueue_internal.h. K dispozícii je aj dokumentácia o pracovnom fronte Documentation/workqueue.txt.

Za zmienku tiež stojí, že mechanizmus workqueue sa v jadre používa nielen na spracovanie odloženého prerušenia (aj keď ide o pomerne bežný scenár).

Pozreli sme sa teda na mechanizmy na obsluhu odložených prerušení v jadre Linuxu – tasklet a workqueue, ktoré sú špeciálnou formou multitaskingu. O prerušeniach, úlohách a pracovných frontoch si môžete prečítať v knihe „Ovladače zariadení Linux“ od Jonathana Corbeta, Grega Kroaha-Hartmana, Alessandra Rubiniho, hoci informácie sú tam niekedy zastarané.

Pokračujeme v téme multithreadingu v jadre Linuxu. Minule som hovoril o prerušeniach, ich spracovaní a taskletoch, a keďže to bolo pôvodne zamýšľané ako jeden článok, v mojom príbehu o workqueue budem odkazovať na tasklety, za predpokladu, že ich čitateľ už pozná.
Tak ako minule, aj teraz sa pokúsim urobiť môj príbeh čo najpodrobnejší a najpodrobnejší.

Články v seriáli:

  1. Multitasking v jadre Linuxu: pracovný front

Pracovná fronta

Pracovná fronta- sú to zložitejšie a ťažšie entity ako tasklety. Nebudem sa tu ani snažiť opísať všetky zložitosti implementácie, ale dúfam, že to najdôležitejšie rozoberiem viac či menej podrobne.
Pracovné fronty, podobne ako tasklety, slúžia na spracovanie odloženého prerušenia (hoci sa dajú použiť aj na iné účely), ale na rozdiel od taskletov sa vykonávajú v kontexte procesu jadra, teda nemusia byť atomické a môžu využívať spánok. () funkcia, rôzne synchronizačné nástroje atď.

Poďme najprv pochopiť, ako je vo všeobecnosti organizovaný proces spracovania pracovného frontu. Obrázok to ukazuje veľmi približne a zjednodušene, ako sa vlastne všetko deje je podrobne popísané nižšie.

Do tejto temnej hmoty je zapojených niekoľko subjektov.
po prvé, pracovná položka(len v skratke) je štruktúra, ktorá popisuje funkciu (napríklad obsluhu prerušení), ktorú chceme naplánovať. Možno si ju predstaviť ako analógiu štruktúry taskletu. Pri plánovaní boli Tasklety pridané do frontov skrytých pred používateľom, ale teraz musíme použiť špeciálny front - pracovný front.
Úlohy sú rakované funkciou plánovača a pracovný front je spracovávaný špeciálnymi vláknami nazývanými pracovníci.
Pracovník's poskytujú asynchrónne vykonávanie prác z pracovného frontu. Hoci prácu nazývajú v poradí rotácie, vo všeobecnom prípade nemôže byť reč o striktnom postupnom vykonávaní: veď sa tu odohráva preempcia, spánok, čakanie atď.

Vo všeobecnosti sú pracovníci vláknami jadra, to znamená, že ich riadi hlavný plánovač jadra Linuxu. Pracovníci však čiastočne zasahujú do plánovania dodatočnej organizácie paralelného vykonávania práce. Toto bude podrobnejšie diskutované nižšie.

Ak chcete načrtnúť hlavné možnosti mechanizmu pracovného frontu, navrhujem preskúmať rozhranie API.

O fronte a jej tvorbe

alloc_workqueue(fmt, príznaky, max_active, argumenty...)
Parametre fmt a args sú formát printf pre názov a jeho argumenty. Parameter max_activate je zodpovedný za maximálny počet prác, ktoré je možné z tohto frontu vykonať paralelne na jednom CPU.
Front je možné vytvoriť s nasledujúcimi príznakmi:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREEZABLE
  • WQ_MEM_RECLAIM
Osobitná pozornosť by sa mala venovať vlajke WQ_UNBOUND. Na základe prítomnosti tohto príznaku sa rady delia na viazané a nepriviazané.
V prepojených radoch Po pridaní sú práce viazané na aktuálny CPU, to znamená, že v takýchto frontoch sú práce vykonávané na jadre, ktoré ju plánuje. V tomto ohľade sa viazané fronty podobajú taskletom.
V nepripojených frontoch práca môže byť vykonaná na akomkoľvek jadre.

Dôležitou črtou implementácie pracovného frontu v jadre Linuxu je dodatočná organizácia paralelného vykonávania, ktorá je prítomná vo viazaných frontoch. Nižšie je to napísané podrobnejšie, ale teraz poviem, že sa to robí tak, aby sa spotrebovalo čo najmenej pamäte a aby procesor nezostával nečinný. To všetko je implementované s predpokladom, že jedna práca nepoužíva príliš veľa procesorových cyklov.
Toto neplatí pre nepripojené fronty. Takéto fronty v podstate jednoducho poskytujú pracovníkom kontext a spúšťajú ich čo najskôr.
Nepripojené fronty by sa teda mali používať, ak sa očakáva náročné zaťaženie CPU, pretože v tomto prípade sa plánovač postará o paralelné vykonávanie na viacerých jadrách.

Analogicky s taskletmi možno prácam priradiť prioritu vykonávania, normálnu alebo vysokú. Priorita je spoločná pre celý rad. Predvolene má front normálnu prioritu a ak nastavíte príznak WQ_HIGHPRI, potom podľa toho vysoká.

Vlajka WQ_CPU_INTENSIVE má zmysel len pre viazané fronty. Tento príznak predstavuje odmietnutie zúčastniť sa na dodatočnej organizácii paralelného vykonávania. Tento príznak by sa mal použiť, keď sa očakáva, že práca zaberie veľa času CPU, v takom prípade je lepšie presunúť zodpovednosť na plánovač. Toto je podrobnejšie popísané nižšie.

Vlajky WQ_FREEZABLE A WQ_MEM_RECLAIM sú špecifické a presahujú rámec témy, preto sa im nebudeme podrobne venovať.

Niekedy má zmysel nevytvárať si vlastné fronty, ale používať bežné. Tie hlavné:

  • system_wq - viazaný front pre rýchlu prácu
  • system_long_wq - viazaný front pre práce, pri ktorých sa očakáva, že budú trvať dlho
  • system_unbound_wq - neviazaný front

O práci a ich plánovaní

Teraz sa poďme zaoberať dielami. Najprv sa pozrime na inicializačné, deklaračné a prípravné makrá:
DECLARE(_DELAYED)_WORK(meno, void (*funkcia)(struct work_struct *work)); /* v čase kompilácie */ INIT(_DELAYED)_WORK(_work, _func); /* počas vykonávania */ PRIPRAVIŤ(_ODLOŽENÉ)_PRÁCA(_work, _func); /* na zmenu vykonávanej funkcie */
Diela sa pridávajú do frontu pomocou funkcií:
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); /* práca bude zaradená do poradia až po uplynutí oneskorenia */
Stojí za to sa tomu podrobnejšie venovať. Hoci ako parameter zadávame front, v skutočnosti sa práce neumiestňujú do samotného pracovného frontu, ako by sa mohlo zdať, ale do úplne inej entity – do zoznamu frontov štruktúry worker_pool. Štruktúra worker_pool, je v skutočnosti najdôležitejšou entitou pri organizovaní mechanizmu pracovného frontu, hoci pre používateľa zostáva v zákulisí. Práve s nimi pracujú pracovníci a práve v nich sú obsiahnuté všetky základné informácie.

Teraz sa pozrime, aké bazény sú v systéme.
Na začiatok, fondy pre viazané fronty (na obrázku). Pre každý CPU sú staticky alokované dve oblasti pracovníkov: jedna pre prácu s vysokou prioritou, druhá pre prácu s normálnou prioritou. To znamená, že ak máme štyri jadrá, potom bude pripojených iba osem bazénov, napriek tomu, že pracovný front môže byť ľubovoľný.
Keď vytvoríme pracovný front, má službu pridelenú každému CPU pool_workqueue(pwq). Každý takýto pool_workqueue je priradený k pracovnej oblasti, ktorá je alokovaná na rovnakom CPU a svojou prioritou zodpovedá typu frontu. Prostredníctvom nich interaguje pracovný front s fondom pracovníkov.
Pracovníci vykonávajú prácu z fondu pracovníkov bez rozdielu, bez rozlíšenia, do ktorého pracovného frontu pôvodne patrili.

Pre nepripojené fronty sa oblasti pracovníkov prideľujú dynamicky. Všetky fronty je možné rozdeliť do tried ekvivalencie podľa ich parametrov a pre každú takúto triedu je vytvorený vlastný fond pracovníkov. Pristupuje sa k nim pomocou špeciálnej hašovacej tabuľky, kde kľúčom je množina parametrov a hodnota je skupina pracovníkov.
V skutočnosti je pre neviazané fronty všetko trochu komplikovanejšie: ak pre viazané fronty boli vytvorené pwq a fronty pre každý CPU, tu sú vytvorené pre každý uzol NUMA, ale toto je ďalšia optimalizácia, ktorú nebudeme podrobne zvažovať.

Všelijaké maličkosti

Uvediem tiež niekoľko funkcií z API na dokončenie obrazu, ale nebudem o nich hovoriť podrobne:
/* Vynútiť dokončenie */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Zruš vykonanie práce */ 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); /* Vymazanie frontu */ void znič_pracovná fronta(struct workqueue_struct *wq);

Ako si pracovníci robia svoju prácu

Teraz, keď sme sa oboznámili s API, skúsme podrobnejšie pochopiť, ako to celé funguje a ako sa riadi.
Každý bazén má skupinu pracovníkov, ktorí vykonávajú úlohy. Počet pracovníkov sa navyše dynamicky mení a prispôsobuje sa aktuálnej situácii.
Ako sme už zistili, pracovníci sú vlákna, ktoré vykonávajú prácu v kontexte jadra. Pracovník ich získava v poradí, jeden po druhom, z fondu pracovníkov, ktorý je s ním spojený, a pracovníci, ako už vieme, môžu patriť do rôznych zdrojových frontov.

Pracovníci môžu byť podmienečne v troch logických stavoch: môžu byť nečinní, bežiaci alebo riadiaci.
Pracovník môže nečinne stáť a nič nerobiť. To je napríklad vtedy, keď sa už vykonávajú všetky práce. Keď pracovník vstúpi do tohto stavu, prejde do režimu spánku, a preto sa nevykoná, kým sa nezobudí;
Ak sa nevyžaduje správa fondu a zoznam naplánovaných prác nie je prázdny, pracovník ich začne vykonávať. Takýchto pracovníkov budeme konvenčne nazývať beh.
V prípade potreby prevezme úlohu pracovník manažér bazén. Fond môže mať buď iba jedného riadiaceho pracovníka alebo žiadneho pracovníka. Jeho úlohou je udržiavať optimálny počet pracovníkov na bazén. Ako to robí? Po prvé, pracovníci, ktorí boli dlho nečinní, sú vymazaní. Po druhé, noví pracovníci sa vytvárajú, ak sú súčasne splnené tri podmienky:

  • stále je potrebné dokončiť úlohy (funguje v skupine)
  • žiadni nečinní pracovníci
  • nie sú žiadni pracujúci pracovníci (t. j. aktívni a nespiaci)
Posledná podmienka má však svoje vlastné nuansy. Ak sú fronty fondu nepripojené, bežiaci pracovníci sa neberú do úvahy; pre nich platí táto podmienka vždy. To isté platí v prípade, že pracovník vykonáva úlohu z prepojenej úlohy, ale s príznakom WQ_CPU_INTENSIVE, rady. Navyše v prípade viazaných radov, keďže pracovníci pracujú s dielami zo spoločného fondu (ktorý je jedným z dvoch pre každé jadro na obrázku vyššie), sa ukazuje, že niektoré z nich sa počítajú ako pracovné a niektoré nie. Z toho tiež vyplýva, že vykonávanie práce od WQ_CPU_INTENSIVE fronty sa nemusia spustiť okamžite, ale samy nezasahujú do vykonávania inej práce. Teraz by malo byť jasné, prečo sa tento príznak tak nazýva a prečo sa používa, keď očakávame, že dokončenie práce bude trvať dlho.

Účtovanie pracujúcich pracovníkov sa vykonáva priamo z hlavného plánovača jadra Linuxu. Tento kontrolný mechanizmus zaisťuje optimálnu úroveň súbežnosti, zabraňuje tomu, aby pracovný front vytváral príliš veľa pracovníkov, ale tiež nespôsoboval, že práca zbytočne dlho čaká.

Kto má záujem, môže sa pozrieť na funkciu worker v jadre, volá sa worker_thread().

Všetky popísané funkcie a štruktúry nájdete podrobnejšie v súboroch include/linux/workqueue.h, kernel/workqueue.c A kernel/workqueue_internal.h. K dispozícii je aj dokumentácia o pracovnom fronte Documentation/workqueue.txt.

Za zmienku tiež stojí, že mechanizmus workqueue sa v jadre používa nielen na spracovanie odloženého prerušenia (aj keď ide o pomerne bežný scenár).

Pozreli sme sa teda na mechanizmy na obsluhu odložených prerušení v jadre Linuxu – tasklet a workqueue, ktoré sú špeciálnou formou multitaskingu. O prerušeniach, úlohách a pracovných frontoch si môžete prečítať v knihe „Ovladače zariadení Linux“ od Jonathana Corbeta, Grega Kroaha-Hartmana, Alessandra Rubiniho, hoci informácie sú tam niekedy zastarané.