Darbības principi UNIX līdzīgās operētājsistēmās, kā piemēru izmantojot Linux. Izveidojiet savu vieglo procesu

Viens no kaitinošākiem brīžiem, pārejot no vides uz Uz Windows bāzes lietošanai komandrinda– vieglas daudzuzdevumu veikšanas zaudēšana. Pat operētājsistēmā Linux, ja izmantojat X Window sistēmu, varat izmantot peli, lai vienkārši noklikšķinātu uz jauna programma un atveriet to. Tomēr komandrindā jūs esat diezgan iestrēdzis ar monotasking. Šajā rakstā mēs jums parādīsim kā veikt vairākus uzdevumus operētājsistēmā Linux, izmantojot komandrindu.

Fona un prioritāro procesu vadība

Tomēr Linux joprojām ir vairāki veidi, kā veikt vairākus uzdevumus, un daži no tiem ir visaptverošāki nekā citi. Viena iebūvēta metode, kas neprasa papildu programmatūra, vienkārši pārvieto procesus fonā un priekšplānā. Mēs par to runājam. Tomēr tam ir daži trūkumi.

Neaizsargāts

Pirmkārt Lai nosūtītu procesu fonā, vispirms tas ir jāaptur. Nav iespējams nosūtīt jau strādājošu programmu fonā un vienlaikus to uzturēt.

Otrkārt, jums ir jāpārtrauc darbplūsma, lai sāktu jaunu komandu. Jums ir jāiziet no tā, ko pašlaik darāt, un čaulā jāievada vairāk komandu. Tas darbojas, bet tas ir neērti.

Trešais, jums jāuzrauga fona procesu izvadi. Jebkāda izvade no tām parādīsies komandrindā un traucēs tam, ko jūs pašlaik darāt. Tātad fona uzdevumiem ir jānovirza izvade uz atsevišķu failu, vai arī tie ir pilnībā jāatspējo.

Šo trūkumu dēļ fona un priekšplāna procesa pārvaldībā ir milzīgas problēmas. Labākais lēmums– izmantojiet komandrindas utilītu “screen”, kā parādīts tālāk.

Bet vispirms - jūs atvērsiet jaunu SSH sesiju

Neaizmirstiet, ka jūs tikko atverat jaunu SSH sesiju.

Var būt neērti visu laiku atvērt jaunas sesijas. Un tas ir tad, kad jums ir nepieciešams "ekrāns"

Lietderība ekrānsļauj izveidot vairākas vienlaikus atvērtas darbplūsmas - vistuvāko analogu “logiem”. Pēc noklusējuma tas ir pieejams parastajās Linux krātuvēs. Instalējiet to vietnē CentOS / RHEL, izmantojot šādu komandu:

Sudo yum instalēšanas ekrāns

Tiek atvērts jauns ekrāns

Tagad sāciet sesiju, ierakstot “screen”.

Tas radīs tukšs logs esošajā SSH sesijā un piešķiriet tai numuru, kas tiek parādīts galvenē šādi:

Mūsu ekrāns šeit ir numurēts “0”, kā parādīts attēlā. Šajā ekrānuzņēmumā mēs izmantojam fiktīvu komandu “lasīt”, lai bloķētu termināli un liktu tam gaidīt ievadi. Tagad pieņemsim, ka vēlamies darīt kaut ko citu, kamēr gaidām.

Atvērt jauns ekrāns un darām kaut ko citu, mēs drukājam:

Ctrl+a c

“Ctrl+a” ir noklusējuma taustiņu kombinācija ekrānu vadīšanai ekrāna programmā. Tas, ko jūs ierakstāt pēc tā, nosaka darbību. Piemēram:

  • ctrl+a c – C aktivizē jaunu ekrānu
  • ctrl+a [numurs]– pāriet uz noteiktu ekrāna numuru
  • ctrl+a k – K izslēdz pašreizējo ekrānu
  • ctrl+a n — pāriet uz ekrānu n
  • ctrl+a “- parāda visus sesijas aktīvos ekrānus

Nospiežot taustiņu kombināciju ctrl+a c, tiks parādīts jauns ekrāns ar jaunu numuru.

Varat izmantot kursora taustiņus, lai pārvietotos sarakstā un pārietu uz vajadzīgo ekrānu.
Ekrāni ir vistuvāk “logiem”, piemēram, sistēma komandā Linux virkne. Protams, tas nav tik vienkārši, kā noklikšķināt ar peli, taču grafikas apakšsistēma ir ļoti resursietilpīga. Izmantojot ekrānus, varat iegūt gandrīz tādu pašu funkcionalitāti un iespējot pilnu vairākuzdevumu veikšanu!

Procesi UNIX

UNIX sistēmā galvenais organizācijas līdzeklis un daudzuzdevumu vienība ir process. Operētājsistēma manipulē ar procesa attēlu, kas attēlo programmas kodu, kā arī procesa datu sadaļas, kas nosaka izpildes vidi.

Izpildes laikā vai gaidot “spārnos” procesi tiek ietverti virtuālajā atmiņā ar lapas organizāciju. Daļa no šīs virtuālās atmiņas ir piesaistīta fiziskajai atmiņai. Daļa fiziskās atmiņas ir rezervēta operētājsistēmas kodolam. Lietotāji var piekļūt tikai procesiem atlikušajai atmiņai. Ja nepieciešams, procesa atmiņas lapas tiek nomainītas no fiziskās atmiņas uz disku, uz mijmaiņas apgabalu. Piekļūstot lapai virtuālajā atmiņā, ja tā nav fiziskajā atmiņā, tā tiek nomainīta no diska.

Virtuālo atmiņu ievieš un automātiski uztur UNIX kodols.

Procesu veidi

UNIX operētājsistēmās ir trīs veidu procesi: sistēmisks, dēmonu procesi Un pieteikšanās procesi.

Sistēmas procesi ir daļa no kodola un vienmēr atrodas brīvpiekļuves atmiņa. Sistēmas procesiem nav atbilstošu programmu izpildāmo failu veidā, un tie tiek palaisti īpašā veidā, kad tiek inicializēts sistēmas kodols. Šo procesu izpildes instrukcijas un dati atrodas sistēmas kodolā, lai tie varētu izsaukt funkcijas un piekļūt datiem, kuriem citi procesi nevar piekļūt.

Sistēmas procesos ietilpst sākotnējās inicializācijas process, tajā, kas ir visu pārējo procesu priekštecis. Lai gan tajā nav kodola daļa, un tā izpilde notiek no izpildāmā faila, tā darbība ir ļoti svarīga visas sistēmas darbībai kopumā.

Dēmoni- tie ir neinteraktīvi procesi, kas tiek palaisti parastajā veidā - ielādējot tām atbilstošās programmas atmiņā, un tiek izpildīti fonā. Parasti dēmoni tiek palaisti sistēmas inicializācijas laikā, bet pēc kodola inicializācijas un nodrošina dažādu UNIX apakšsistēmu darbību: termināla piekļuves sistēmas, drukas sistēmas, tīkla pakalpojumi utt. Dēmoni nav saistīti ne ar vienu lietotāju. Lielāko daļu laika dēmoni gaida, līdz tiks pieprasīts viens vai otrs process konkrēts pakalpojums.



UZ pieteikšanās procesi ietver visus citus procesus, kas darbojas sistēmā. Parasti tie ir procesi, kas radušies lietotāja sesijas laikā. Vissvarīgākais lietotāja process ir sākotnējo komandu tulks, kas nodrošina lietotāja komandu izpildi UNIX sistēmā.

Lietotāju procesi var darboties gan interaktīvajā (preemptīvajā), gan fona režīmi. Interaktīvajiem procesiem ir ekskluzīvas termināļa īpašumtiesības, un, kamēr šāds process nav pabeidzis tā izpildi, lietotājam nav piekļuves komandrindai.

Procesa atribūti

UNIX procesam ir vairāki atribūti, kas to ļauj operētājsistēma vadīt savu darbu. Galvenie atribūti:

· Procesa ID (PID), ļaujot sistēmas kodolam atšķirt procesus. Kad izveidots jauns process, kodols piešķir tam nākamo brīvo (t.i., ne ar vienu procesu nesaistītu) identifikatoru. Identifikatora piešķiršana parasti notiek augošā secībā, t.i. Jaunā procesa ID ir lielāks nekā pirms tā izveidotā procesa ID. Ja ID sasniedz maksimālo vērtību (parasti 65737), nākamais process saņems minimālo bezmaksas PID un cikls atkārtojas. Kad process iziet, kodols atbrīvo izmantoto identifikatoru.

· Vecāku procesa ID (PPID)– procesa identifikators, kas izraisīja šo procesu. Visi sistēmā notiekošie procesi, izņemot sistēmas procesi un process tajā, kas ir atlikušo procesu priekštecis, ģenerē kāds no esošajiem vai iepriekš esošajiem procesiem.

· Prioritātes korekcija (NI)– procesa relatīvā prioritāte, ko plānotājs ņem vērā, nosakot palaišanas secību. Faktisko procesora resursu sadalījumu nosaka izpildes prioritāte (atribūts PRI), atkarībā no vairākiem faktoriem, jo ​​īpaši no konkrētās relatīvās prioritātes. Relatīvo prioritāti sistēma nemaina visā procesa laikā, lai gan lietotājs vai administrators to var mainīt, uzsākot procesu, izmantojot komandu jauki. Prioritātes pieauguma vērtību diapazons lielākajā daļā sistēmu ir no -20 līdz 20. Ja pieaugums nav norādīts, tiek izmantota noklusējuma vērtība 10. Pozitīvs pieaugums nozīmē pašreizējās prioritātes samazināšanos. Parastie lietotāji var iestatīt tikai pozitīvu pieaugumu un tādējādi tikai samazināt prioritāti. Lietotājs sakne var iestatīt negatīvu pieaugumu, kas palielina procesa prioritāti un tādējādi veicina tās augstāku ātrs darbs. Atšķirībā no relatīvās prioritātes, plānotājs dinamiski maina procesa izpildes prioritāti.

· Termināļa līnija (TTY)– ar procesu saistīts terminālis vai pseidoterminālis. Šis terminālis ir saistīts ar standartu straumes: ievade, brīvdiena Un ziņojumu plūsma par kļūdām. Straumes ( programmu kanāliem) ir standarta līdzekļi starpprocesu komunikācija UNIX OS. Dēmonu procesi nav saistīti ar termināli.

· Reālie (UID) un efektīvie (EUID) lietotāju identifikatori. Noteiktā procesa reālais lietotāja ID ir tā lietotāja ID, kurš sāka procesu. Efektīvo identifikatoru izmanto, lai noteiktu procesa piekļuves tiesības sistēmas resursiem (galvenokārt resursiem failu sistēma). Parasti īstie un efektīvie identifikatori ir vienādi, t.i. procesam sistēmā ir tādas pašas tiesības kā lietotājam, kurš to palaida. Tomēr, iestatot, procesam ir iespējams piešķirt vairāk tiesību nekā lietotājam SUID bits kad efektīvais identifikators ir iestatīts uz izpildāmā faila īpašnieka identifikatoru (piemēram, lietotāja sakne).

· Reālie (GID) un efektīvie (EGID) grupu identifikatori. Reālais grupas ID ir vienāds ar tā lietotāja primāro vai pašreizējo grupas ID, kurš sāka procesu. Efektīvais identifikators tiek izmantots, lai noteiktu piekļuves tiesības sistēmas resursiem grupas vārdā. Parasti efektīvais grupas ID ir tāds pats kā īstais. Bet, ja izpildāmais fails ir iestatīts uz SGID bits, šāds fails tiek izpildīts ar faktisko īpašnieku grupas ID.

LABORATORIJAS DARBS Nr.3

DAUDZUZDEVUMU PROGRAMMĒŠANALINUX

1. Darba mērķis: Iepazīstieties ar gcc kompilatoru, programmu atkļūdošanas metodēm un funkcijām darbam ar procesiem.

2. Īsa teorētiskā informācija.

Minimālais gcc kompilatora slēdžu komplekts ir - Wall (parāda visas kļūdas un brīdinājumus) un - o (izejas fails):

gcc — siena — vai print_pid print_pid. c

Komanda radīs izpildāmais fails print_pid.

C standarta bibliotēka (libc, ieviesta Linux sistēmā glibc) izmanto Unix System V (turpmāk SysV) daudzuzdevumu iespējas. Programmā libc tips pid_t ir definēts kā vesels skaitlis, kas var saturēt pid. Funkcijai, kas ziņo par pašreizējā procesa pid, ir prototips pid_t getpid(void), un tā ir definēta kopā ar pid_t programmā unistd. h un sys/types. h).

Lai izveidotu jaunu procesu, izmantojiet funkciju Fork:

pid_t fork(tukšums)

Ievietojot nejauša garuma aizkavi, izmantojot miega un rand funkcijas, varat skaidrāk redzēt vairākuzdevumu izpildes efektu:

tas liks programmai "aizmigt" nejauši izvēlētu sekunžu skaitu: no 0 līdz 3.

Lai izsauktu funkciju kā bērnprocesu, vienkārši izsauciet to pēc atzarošanas:

// ja darbojas pakārtots process, izsauciet funkciju

pid=process(arg);

// iziet no procesa

Bieži vien ir nepieciešams palaist citu programmu kā bērnu procesu. Lai to izdarītu, izmantojiet exec saimes funkcijas:

// ja darbojas bērnu process, izsauciet programmu


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

printf("KĻŪDA, uzsākot procesu\n");

else printf("process sākts (pid=%d)\n", pid);

// iziet no procesa

Bieži vien vecāku procesam ir jāapmainās ar informāciju ar saviem bērniem vai vismaz jāsinhronizējas ar viņiem, lai veiktu darbības īstajā laikā. Viens no veidiem, kā sinhronizēt procesus, ir gaidīšanas un gaidīšanas funkcijas:

#iekļauts

#iekļauts

pid_t wait(int *statuss) - aptur pašreizējā procesa izpildi, līdz tiek pārtraukts kāds no tā pakārtotajiem procesiem.

pid_t waitpid (pid_t pid, int *statuss, int opcijas) - aptur pašreizējā procesa izpildi, līdz norādītais process ir pabeigts vai pārbauda norādītā procesa pabeigšanu.

Ja nepieciešams noskaidrot pakārtotā procesa stāvokli, kad tas beidzas, un vērtību, ko tas atgriež, izmantojiet makro WEXITSTATUS, nododot tam kā parametru pakārtotā procesa statusu.

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

if (pid == statuss) (

printf("PID: %d, rezultāts = %d\n", pid, WEXITSTATUS(statuss)); )

Lai mainītu radīto procesu prioritātes, tiek izmantota iestatītā prioritāte un funkcijas. Prioritātes ir iestatītas diapazonā no -20 (augstākā) līdz 20 (zemākā), normālā vērtība ir 0. Ņemiet vērā, ka tikai superlietotājs var paaugstināt prioritāti virs normas!

#iekļauts

#iekļauts

int process(int i) (

setpriority(PRIO_PROCESS, getpid(),i);

printf("Apstrādāt %d ThreadID: %d strādā ar prioritāti %d\n",i, getpid(),getpriority(PRIO_PROCESS, getpid()));

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

Lai apturētu procesu, izmantojiet nogalināšanas funkciju:

#iekļauts

#iekļauts

int kill(pid_t pid, int sig);

Ja pid > 0, tad tas norāda procesa PID, kuram tiek nosūtīts signāls. Ja pid = 0, tad signāls tiek nosūtīts uz visiem tās grupas procesiem, kurai pieder pašreizējais process.

sig - signāla veids. Daži signālu veidi operētājsistēmā Linux:

SIGKILL Šis signāls liek procesam nekavējoties pārtraukt. Process nevar ignorēt šo signālu.

SIGTERM Šis signāls ir pieprasījums pārtraukt procesu.

SIGCHLD Sistēma nosūta šo signālu procesam, kad tiek pārtraukts kāds no tā pakārtotajiem procesiem. Piemērs:

if (pid[i] == statuss) (

printf("Pavediena ID: %d pabeigts ar statusu %d\n", pid[i], WEXITSTATUS(statuss));

else kill(pid[i],SIGKILL);

3. Metodiskie norādījumi.

3.1. Lai iepazītos ar gcc kompilatora opcijām un C valodas funkciju aprakstiem, izmantojiet man un info norādījumus.

3.2. Programmu atkļūdošanai ir ērti izmantot iebūvēto failu pārvaldnieka redaktoru Pusnakts komandieris(MC), kas krāsaini izceļ dažādas valodas konstrukcijas un ekrāna augšējā rindā norāda kursora pozīciju failā (rindā, kolonnā).

3.3. Midnight Commander failu pārvaldniekam ir komandu buferis, ko var izsaukt, izmantojot īsinājumtaustiņus - H, kuru var pārvietot, izmantojot kursora bultiņas (augšup un lejup). Lai komandrindā ievietotu komandu no bufera, izmantojiet taustiņu , lai rediģētu komandu no bufera - taustiņiem<- и ->, Un .


3.4. Atcerieties, ka pašreizējais direktorijs nav ietverts ceļā, tāpēc jums ir jāpalaiž programma kā "./print_pid" no komandrindas. Programmā MC vienkārši virziet kursoru virs faila un noklikšķiniet uz .

3.5. Lai skatītu programmas izpildes rezultātu, izmantojiet īsinājumtaustiņus - O. Tie darbojas arī failu rediģēšanas režīmā.

3.6. Lai reģistrētu programmas izpildes rezultātus, ieteicams izmantot izvades pāradresāciju no konsoles uz failu: ./test > rezultāts. txt

3.7. Lai piekļūtu failiem, kas izveidoti Linux serveris, izmantojiet ftp protokolu, kura klienta programma ir pieejama operētājsistēmā Windows 2000 un ir iebūvēta failu menedžeris TĀLU. Kurā Konts un parole ir tāda pati kā pieslēdzoties caur ssh.

4.1. Iepazīstieties ar gcc kompilatora opcijām un programmu atkļūdošanas metodēm.

4.2. Uzdevumu variantiem no laboratorijas darba Nr.1 ​​uzrakstiet un atkļūdojiet programmu, kas realizē ģenerēto procesu.

4.3. Uzdevumu opcijām no laboratorijas darbi Nr.1 rakstīt un atkļūdot programmu, kas īsteno vecāku procesu, kas izsauc un uzrauga bērnu procesu stāvokli - programmas (gaida, kamēr tās pabeigs vai iznīcina, atkarībā no opcijas).

4.4. Uzdevumu variantiem no laboratorijas darba Nr.1 ​​uzrakstiet un atkļūdojiet programmu, kas realizē vecāku procesu, kas izsauc un uzrauga bērnprocesu - funkciju stāvokli (gaida to izpildi vai iznīcina, atkarībā no varianta).

5. Uzdevumu iespējas. Skatiet uzdevumu variantus no laboratorijas darba Nr.1

6. Ziņojuma saturs.

6.1. Darba mērķis.

6.2. Uzdevuma opcija.

6.3. Programmu saraksti.

6.4. Programmas izpildes protokoli.

7. Kontroles jautājumi.

7.1. C programmu kompilēšanas un palaišanas iespējas operētājsistēmā Linux.

7.2. Kas ir pid, kā to noteikt operētājsistēmā un programmā?

7.3. Dakšas funkcija - mērķis, pielietojums, atgriešanas vērtība.

7.4. Kā palaist funkciju radītā procesā? Programma?

7.5. Vecāku un bērnu procesu sinhronizācijas veidi.

7.6. Kā uzzināt izveidotā procesa stāvokli, kad tas beidzas, un vērtību, ko tas atgriež?

7.7. Kā pārvaldīt procesa prioritātes?

7.8. Kā nogalināt procesu operētājsistēmā un programmā?

Mēs turpinām daudzpavedienu tēmu Linux kodolā. Iepriekšējā reizē es runāju par pārtraukumiem, to apstrādi un uzdevumiem, un, tā kā sākotnēji bija paredzēts, ka šis būs viens raksts, tad savā stāstā par darbrindu atsaukšos uz uzdevumletēm, pieņemot, ka lasītājs ar tām jau ir pazīstams.
Tāpat kā pagājušajā reizē, es centīšos savu stāstu veidot pēc iespējas detalizētāku un detalizētāku.

Raksti sērijā:

  1. Daudzuzdevumu veikšana Linux kodolā: darbrinda

Darba rinda

Darba rinda- tās ir sarežģītākas un smagākas vienības nekā uzdevumi. Es pat nemēģināšu šeit aprakstīt visas ieviešanas smalkumus, bet es ceru, ka svarīgākās lietas analizēšu vairāk vai mazāk detalizēti.
Darbrindas, tāpat kā uzdevumi, kalpo atliktai pārtraukumu apstrādei (lai gan tās var izmantot citiem mērķiem), taču atšķirībā no uzdevumletēm tās tiek izpildītas kodola procesa kontekstā, attiecīgi tām nav jābūt atomārām un var izmantot miega režīmu. () funkcija, dažādi sinhronizācijas rīki utt.

Vispirms sapratīsim, kā kopumā tiek organizēts darbrindu apstrādes process. Attēlā ļoti aptuveni un vienkāršoti parādīts, kā viss patiesībā notiek, sīkāk aprakstīts zemāk.

Šajā tumšajā matērijā ir iesaistītas vairākas vienības.
Pirmkārt, darba priekšmets(īsumā tikai darbs) ir struktūra, kas apraksta funkciju (piemēram, pārtraukumu apstrādātāju), kuru vēlamies ieplānot. To var uzskatīt par uzdevuma struktūras analogu. Plānojot uzdevumu komplekti tika pievienoti rindām, kas paslēptas no lietotāja, bet tagad mums ir jāizmanto īpaša rinda - darba rinda.
Uzdevumu komplektus sakārto plānotāja funkcija, un darbrindu apstrādā īpaši pavedieni, ko sauc par darbiniekiem.
Strādnieks's nodrošina asinhronu darbu izpildi no darba rindas. Lai gan viņi sauc darbu rotācijas kārtībā, vispārīgā gadījumā par stingru, secīgu izpildi nevar runāt: galu galā šeit notiek pirmpirkšana, gulēšana, gaidīšana utt.

Kopumā darbinieki ir kodola pavedieni, tas ir, tos kontrolē galvenais Linux kodola plānotājs. Taču strādnieki daļēji iejaucas paralēlas darba izpildes papildu organizēšanas plānošanā. Tas tiks apspriests sīkāk tālāk.

Lai ieskicētu darbrindas mehānisma galvenās iespējas, iesaku izpētīt API.

Par rindu un tās izveidi

alloc_workqueue(fmt, karodziņi, max_active, args...)
Parametri fmt un args ir nosaukuma un tā argumentu printf formāts. Parametrs max_activate ir atbildīgs par maksimālo darbu skaitu, ko no šīs rindas var izpildīt paralēli vienā CPU.
Rindu var izveidot ar šādiem karodziņiem:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREZABLE
  • WQ_MEM_RECLAIM
Īpaša uzmanība jāpievērš karogam WQ_UNBOUND. Pamatojoties uz šī karoga klātbūtni, rindas tiek sadalītas saistītās un nepiesietās.
Saistītās rindās Pievienojot, darbi ir saistīti ar pašreizējo CPU, tas ir, šādās rindās darbi tiek izpildīti kodolā, kas to ieplāno. Šajā sakarā saistītās rindas atgādina uzdevumlets.
Nesaistītās rindās darbu var izpildīt uz jebkura kodola.

Svarīga darbrindas ieviešanas iezīme Linux kodolā ir papildu paralēlās izpildes organizācija, kas atrodas saistītajās rindās. Tālāk par to ir rakstīts sīkāk, bet tagad teikšu, ka tas tiek veikts tā, lai tiktu izmantots pēc iespējas mazāk atmiņas un lai procesors nestāvētu dīkstāvē. Tas viss tiek īstenots ar pieņēmumu, ka viens darbs neizmanto pārāk daudz procesora ciklu.
Tas neattiecas uz nesaistītām rindām. Būtībā šādas rindas vienkārši nodrošina darbiniekiem kontekstu un sāk tās pēc iespējas agrāk.
Tādējādi, ja ir paredzama CPU intensīva darba slodze, jāizmanto nesaistītas rindas, jo šajā gadījumā plānotājs parūpēsies par paralēlu izpildi vairākos kodolos.

Pēc analoģijas ar uzdevumletēm darbiem var piešķirt izpildes prioritāti, normālu vai augstu. Prioritāte ir kopīga visai rindai. Pēc noklusējuma rindai ir normāla prioritāte, un, ja iestatāt karogu WQ_HIGHPRI, tad attiecīgi augsts.

Karogs WQ_CPU_INTENSIVE jēga ir tikai saistītajām rindām. Šis karogs ir atteikums piedalīties paralēlas izpildes papildu organizēšanā. Šis karodziņš ir jāizmanto, ja paredzams, ka darbs patērēs daudz CPU laika, un tādā gadījumā labāk ir pārcelt atbildību uz plānotāju. Tas ir sīkāk aprakstīts tālāk.

Karogi WQ_FREZABLE Un WQ_MEM_RECLAIM ir specifiski un neietilpst tēmas ietvaros, tāpēc mēs pie tiem sīkāk nekavēsimies.

Dažreiz ir jēga neveidot savas rindas, bet izmantot parastās. Galvenie:

  • system_wq — saistīta rinda ātram darbam
  • system_long_wq — saistīta rinda darbiem, kuru izpildei ir nepieciešams ilgs laiks
  • system_unbound_wq — nesaistīta rinda

Par darbu un to plānošanu

Tagad nodarbosimies ar darbiem. Vispirms apskatīsim inicializācijas, deklarēšanas un sagatavošanas makro:
DECLARE(_DELAYED)_WORK(nosaukums, void (*funkcija)(struct work_struct *work)); /* kompilēšanas laikā */ INIT(_DELAYED)_WORK(_work, _func); /* izpildes laikā */ PREPARE(_DELAYED)_WORK(_work, _func); /* lai mainītu izpildāmo funkciju */
Darbi tiek pievienoti rindai, izmantojot funkcijas:
bool queue_work(struct workqueue_struct *wq, struct work_struct *darbs); bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, neparakstīta ilga aizkave); /* darbs tiks pievienots rindai tikai pēc tam, kad būs beidzies kavēšanās */
Par to ir vērts pakavēties sīkāk. Lai gan kā parametru norādām rindu, patiesībā darbi netiek ievietoti pašā darba rindā, kā varētu šķist, bet gan pavisam citā entītijā - worker_pool struktūras rindu sarakstā. Struktūra strādnieku_pulks, patiesībā, ir vissvarīgākā vienība darba rindas mehānisma organizēšanā, lai gan lietotājam tas paliek aizkulisēs. Tieši pie viņiem strādā strādnieki, un tieši viņos ir ietverta visa pamatinformācija.

Tagad redzēsim, kādi baseini ir sistēmā.
Sākumā baseini saistītajām rindām (attēlā). Katram CPU ir statiski piešķirti divi darbinieku pūli: viens augstas prioritātes darbam, otrs darbam ar parasto prioritāti. Tas ir, ja mums ir četri kodoli, tad būs tikai astoņi pievienotie baseini, neskatoties uz to, ka darba rinda var būt tik daudz, cik vēlaties.
Kad mēs veidojam darbrindu, tai katram CPU ir piešķirts pakalpojums baseins_darba rinda(pwq). Katrs šāds pool_workqueue ir saistīts ar darbinieku pūlu, kas ir piešķirts tajā pašā CPU un atbilst rindas veidam. Caur tiem darbrinda mijiedarbojas ar darbinieku kopu.
Darbinieki veic darbu no darbinieku kopas bez izšķirības, nenošķirot, kurai darba rindai viņi sākotnēji piederēja.

Nesaistītām rindām darbinieku pūli tiek piešķirti dinamiski. Visas rindas var iedalīt ekvivalences klasēs pēc to parametriem, un katrai šādai klasei tiek izveidots savs darbinieku kopums. Tiem var piekļūt, izmantojot īpašu hash tabulu, kur atslēga ir parametru kopa, un vērtība attiecīgi ir darbinieku kopa.
Faktiski nesaistītajām rindām viss ir nedaudz sarežģītāk: ja saistītajām rindām pwq un rindas tika izveidotas katram CPU, šeit tās tiek izveidotas katram NUMA mezglam, taču šī ir papildu optimizācija, kuru mēs sīkāk neapskatīsim.

Visādi sīkumi

Es arī sniegšu dažas API funkcijas, lai pabeigtu attēlu, taču es par tām nerunāšu sīkāk:
/* Piespiedu pabeigšana */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Atcelt darba izpildi */ 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); /* Dzēst rindu */ void delete_workqueue(struct workqueue_struct *wq);

Kā strādnieki veic savu darbu

Tagad, kad esam iepazinušies ar API, mēģināsim sīkāk izprast, kā tas viss darbojas un tiek pārvaldīts.
Katrā baseinā ir darbinieku kopums, kas veic uzdevumus. Turklāt strādājošo skaits dinamiski mainās, pielāgojoties esošajai situācijai.
Kā mēs jau noskaidrojām, darbinieki ir pavedieni, kas veic darbu kodola kontekstā. Darbinieks tos izgūst secībā vienu pēc otra no ar to saistītā darbinieku kopas, un darbinieki, kā mēs jau zinām, var piederēt dažādām avota rindām.

Darbinieki nosacīti var atrasties trīs loģiskos stāvokļos: tie var būt dīkstāvē, darboties vai vadīt.
Strādnieks var stāvēt dīkstāvē un neko nedarīt. Tas ir, piemēram, kad viss darbs jau tiek izpildīts. Kad strādnieks nonāk šajā stāvoklī, tas iet gulēt un attiecīgi neizpildīs, kamēr nav pamodināts;
Ja pūla pārvaldība nav nepieciešama un plānoto darbu saraksts nav tukšs, tad darbinieks sāk tos izpildīt. Mēs nosacīti sauksim šādus strādniekus skrienot.
Ja nepieciešams, darbinieks uzņemas lomu vadītājs baseins. Kopā var būt tikai viens vadošais darbinieks vai vispār nav darbinieku. Tās uzdevums ir uzturēt optimālu darbinieku skaitu vienā baseinā. Kā viņš to dara? Pirmkārt, tiek dzēsti darbinieki, kuri ilgu laiku ir bijuši dīkstāvē. Otrkārt, jauni darbinieki tiek radīti, ja vienlaikus tiek izpildīti trīs nosacījumi:

  • vēl ir jāizpilda uzdevumi (darbi baseinā)
  • nav dīkā strādājošo
  • nav strādājošu darbinieku (tas ir, aktīvi un neguļ)
Tomēr pēdējam nosacījumam ir savas nianses. Ja pūla rindas nav pievienotas, strādājošie darbinieki netiek ņemti vērā; viņiem šis nosacījums vienmēr ir spēkā. Tas pats attiecas uz darbinieku, kurš izpilda uzdevumu no saistītā uzdevuma, bet ar karogu WQ_CPU_INTENSIVE, rindas. Turklāt sasaistīto rindu gadījumā, tā kā strādnieki strādā ar darbiem no kopējā pūla (kas ir viens no diviem katram kodolam augšējā attēlā), izrādās, ka daži no tiem tiek uzskatīti par strādājošiem, bet daži ne. No tā arī izriet, ka veicot darbu no WQ_CPU_INTENSIVE rindas var nesākties uzreiz, bet tās pašas netraucē citu darbu izpildi. Tagad vajadzētu būt skaidram, kāpēc šo karogu tā sauc un kāpēc tas tiek izmantots, ja mēs paredzam, ka darbs prasīs ilgu laiku.

Strādājošo darbinieku uzskaite tiek veikta tieši no galvenā Linux kodola plānotāja. Šis kontroles mehānisms nodrošina optimālu vienlaicīguma līmeni, neļaujot darba rindā izveidot pārāk daudz darbinieku, bet arī neliekot darbam nevajadzīgi gaidīt pārāk ilgu laiku.

Tie, kas ir ieinteresēti, var apskatīt worker funkciju kodolā, to sauc worker_thread().

Visas aprakstītās funkcijas un struktūras sīkāk atrodamas failos include/linux/workqueue.h, kernel/workqueue.c Un kernel/workqueue_internal.h. Ir arī dokumentācija par darba rindu Dokumentācija/workqueue.txt.

Ir arī vērts atzīmēt, ka darbrindas mehānisms kodolā tiek izmantots ne tikai atlikto pārtraukumu apstrādei (lai gan tas ir diezgan izplatīts scenārijs).

Tādējādi mēs apskatījām mehānismus atlikto pārtraukumu apstrādei Linux kodolā - uzdevumlets un darbrinda, kas ir īpašs daudzuzdevumu veikšanas veids. Par pārtraukumiem, uzdevumletēm un darbrindām var lasīt Džonatana Korbeta, Grega Kroa-Hārtmena, Alesandro Rubini grāmatā “Linux ierīču draiveri”, lai gan informācija tur dažkārt ir novecojusi.

Mēs turpinām daudzpavedienu tēmu Linux kodolā. Iepriekšējā reizē es runāju par pārtraukumiem, to apstrādi un uzdevumiem, un, tā kā sākotnēji bija paredzēts, ka šis būs viens raksts, tad savā stāstā par darbrindu atsaukšos uz uzdevumletēm, pieņemot, ka lasītājs ar tām jau ir pazīstams.
Tāpat kā pagājušajā reizē, es centīšos savu stāstu veidot pēc iespējas detalizētāku un detalizētāku.

Raksti sērijā:

  1. Daudzuzdevumu veikšana Linux kodolā: darbrinda

Darba rinda

Darba rinda- tās ir sarežģītākas un smagākas vienības nekā uzdevumi. Es pat nemēģināšu šeit aprakstīt visas ieviešanas smalkumus, bet es ceru, ka svarīgākās lietas analizēšu vairāk vai mazāk detalizēti.
Darbrindas, tāpat kā uzdevumi, kalpo atliktai pārtraukumu apstrādei (lai gan tās var izmantot citiem mērķiem), taču atšķirībā no uzdevumletēm tās tiek izpildītas kodola procesa kontekstā, attiecīgi tām nav jābūt atomārām un var izmantot miega režīmu. () funkcija, dažādi sinhronizācijas rīki utt.

Vispirms sapratīsim, kā kopumā tiek organizēts darbrindu apstrādes process. Attēlā ļoti aptuveni un vienkāršoti parādīts, kā viss patiesībā notiek, sīkāk aprakstīts zemāk.

Šajā tumšajā matērijā ir iesaistītas vairākas vienības.
Pirmkārt, darba priekšmets(īsumā tikai darbs) ir struktūra, kas apraksta funkciju (piemēram, pārtraukumu apstrādātāju), kuru vēlamies ieplānot. To var uzskatīt par uzdevuma struktūras analogu. Plānojot uzdevumu komplekti tika pievienoti rindām, kas paslēptas no lietotāja, bet tagad mums ir jāizmanto īpaša rinda - darba rinda.
Uzdevumu komplektus sakārto plānotāja funkcija, un darbrindu apstrādā īpaši pavedieni, ko sauc par darbiniekiem.
Strādnieks's nodrošina asinhronu darbu izpildi no darba rindas. Lai gan viņi sauc darbu rotācijas kārtībā, vispārīgā gadījumā par stingru, secīgu izpildi nevar runāt: galu galā šeit notiek pirmpirkšana, gulēšana, gaidīšana utt.

Kopumā darbinieki ir kodola pavedieni, tas ir, tos kontrolē galvenais Linux kodola plānotājs. Taču strādnieki daļēji iejaucas paralēlas darba izpildes papildu organizēšanas plānošanā. Tas tiks apspriests sīkāk tālāk.

Lai ieskicētu darbrindas mehānisma galvenās iespējas, iesaku izpētīt API.

Par rindu un tās izveidi

alloc_workqueue(fmt, karodziņi, max_active, args...)
Parametri fmt un args ir nosaukuma un tā argumentu printf formāts. Parametrs max_activate ir atbildīgs par maksimālo darbu skaitu, ko no šīs rindas var izpildīt paralēli vienā CPU.
Rindu var izveidot ar šādiem karodziņiem:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_INTENSIVE
  • WQ_FREZABLE
  • WQ_MEM_RECLAIM
Īpaša uzmanība jāpievērš karogam WQ_UNBOUND. Pamatojoties uz šī karoga klātbūtni, rindas tiek sadalītas saistītās un nepiesietās.
Saistītās rindās Pievienojot, darbi ir saistīti ar pašreizējo CPU, tas ir, šādās rindās darbi tiek izpildīti kodolā, kas to ieplāno. Šajā sakarā saistītās rindas atgādina uzdevumlets.
Nesaistītās rindās darbu var izpildīt uz jebkura kodola.

Svarīga darbrindas ieviešanas iezīme Linux kodolā ir papildu paralēlās izpildes organizācija, kas atrodas saistītajās rindās. Tālāk par to ir rakstīts sīkāk, bet tagad teikšu, ka tas tiek veikts tā, lai tiktu izmantots pēc iespējas mazāk atmiņas un lai procesors nestāvētu dīkstāvē. Tas viss tiek īstenots ar pieņēmumu, ka viens darbs neizmanto pārāk daudz procesora ciklu.
Tas neattiecas uz nesaistītām rindām. Būtībā šādas rindas vienkārši nodrošina darbiniekiem kontekstu un sāk tās pēc iespējas agrāk.
Tādējādi, ja ir paredzama CPU intensīva darba slodze, jāizmanto nesaistītas rindas, jo šajā gadījumā plānotājs parūpēsies par paralēlu izpildi vairākos kodolos.

Pēc analoģijas ar uzdevumletēm darbiem var piešķirt izpildes prioritāti, normālu vai augstu. Prioritāte ir kopīga visai rindai. Pēc noklusējuma rindai ir normāla prioritāte, un, ja iestatāt karogu WQ_HIGHPRI, tad attiecīgi augsts.

Karogs WQ_CPU_INTENSIVE jēga ir tikai saistītajām rindām. Šis karogs ir atteikums piedalīties paralēlas izpildes papildu organizēšanā. Šis karodziņš ir jāizmanto, ja paredzams, ka darbs patērēs daudz CPU laika, un tādā gadījumā labāk ir pārcelt atbildību uz plānotāju. Tas ir sīkāk aprakstīts tālāk.

Karogi WQ_FREZABLE Un WQ_MEM_RECLAIM ir specifiski un neietilpst tēmas ietvaros, tāpēc mēs pie tiem sīkāk nekavēsimies.

Dažreiz ir jēga neveidot savas rindas, bet izmantot parastās. Galvenie:

  • system_wq — saistīta rinda ātram darbam
  • system_long_wq — saistīta rinda darbiem, kuru izpildei ir nepieciešams ilgs laiks
  • system_unbound_wq — nesaistīta rinda

Par darbu un to plānošanu

Tagad nodarbosimies ar darbiem. Vispirms apskatīsim inicializācijas, deklarēšanas un sagatavošanas makro:
DECLARE(_DELAYED)_WORK(nosaukums, void (*funkcija)(struct work_struct *work)); /* kompilēšanas laikā */ INIT(_DELAYED)_WORK(_work, _func); /* izpildes laikā */ PREPARE(_DELAYED)_WORK(_work, _func); /* lai mainītu izpildāmo funkciju */
Darbi tiek pievienoti rindai, izmantojot funkcijas:
bool queue_work(struct workqueue_struct *wq, struct work_struct *darbs); bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, neparakstīta ilga aizkave); /* darbs tiks pievienots rindai tikai pēc tam, kad būs beidzies kavēšanās */
Par to ir vērts pakavēties sīkāk. Lai gan kā parametru norādām rindu, patiesībā darbi netiek ievietoti pašā darba rindā, kā varētu šķist, bet gan pavisam citā entītijā - worker_pool struktūras rindu sarakstā. Struktūra strādnieku_pulks, patiesībā, ir vissvarīgākā vienība darba rindas mehānisma organizēšanā, lai gan lietotājam tas paliek aizkulisēs. Tieši pie viņiem strādā strādnieki, un tieši viņos ir ietverta visa pamatinformācija.

Tagad redzēsim, kādi baseini ir sistēmā.
Sākumā baseini saistītajām rindām (attēlā). Katram CPU ir statiski piešķirti divi darbinieku pūli: viens augstas prioritātes darbam, otrs darbam ar parasto prioritāti. Tas ir, ja mums ir četri kodoli, tad būs tikai astoņi pievienotie baseini, neskatoties uz to, ka darba rinda var būt tik daudz, cik vēlaties.
Kad mēs veidojam darbrindu, tai katram CPU ir piešķirts pakalpojums baseins_darba rinda(pwq). Katrs šāds pool_workqueue ir saistīts ar darbinieku pūlu, kas ir piešķirts tajā pašā CPU un atbilst rindas veidam. Caur tiem darbrinda mijiedarbojas ar darbinieku kopu.
Darbinieki veic darbu no darbinieku kopas bez izšķirības, nenošķirot, kurai darba rindai viņi sākotnēji piederēja.

Nesaistītām rindām darbinieku pūli tiek piešķirti dinamiski. Visas rindas var iedalīt ekvivalences klasēs pēc to parametriem, un katrai šādai klasei tiek izveidots savs darbinieku kopums. Tiem var piekļūt, izmantojot īpašu hash tabulu, kur atslēga ir parametru kopa, un vērtība attiecīgi ir darbinieku kopa.
Faktiski nesaistītajām rindām viss ir nedaudz sarežģītāk: ja saistītajām rindām pwq un rindas tika izveidotas katram CPU, šeit tās tiek izveidotas katram NUMA mezglam, taču šī ir papildu optimizācija, kuru mēs sīkāk neapskatīsim.

Visādi sīkumi

Es arī sniegšu dažas API funkcijas, lai pabeigtu attēlu, taču es par tām nerunāšu sīkāk:
/* Piespiedu pabeigšana */ bool flush_work(struct work_struct *work); bool flush_delayed_work(struct delayed_work *dwork); /* Atcelt darba izpildi */ 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); /* Dzēst rindu */ void delete_workqueue(struct workqueue_struct *wq);

Kā strādnieki veic savu darbu

Tagad, kad esam iepazinušies ar API, mēģināsim sīkāk izprast, kā tas viss darbojas un tiek pārvaldīts.
Katrā baseinā ir darbinieku kopums, kas veic uzdevumus. Turklāt strādājošo skaits dinamiski mainās, pielāgojoties esošajai situācijai.
Kā mēs jau noskaidrojām, darbinieki ir pavedieni, kas veic darbu kodola kontekstā. Darbinieks tos izgūst secībā vienu pēc otra no ar to saistītā darbinieku kopas, un darbinieki, kā mēs jau zinām, var piederēt dažādām avota rindām.

Darbinieki nosacīti var atrasties trīs loģiskos stāvokļos: tie var būt dīkstāvē, darboties vai vadīt.
Strādnieks var stāvēt dīkstāvē un neko nedarīt. Tas ir, piemēram, kad viss darbs jau tiek izpildīts. Kad strādnieks nonāk šajā stāvoklī, tas iet gulēt un attiecīgi neizpildīs, kamēr nav pamodināts;
Ja pūla pārvaldība nav nepieciešama un plānoto darbu saraksts nav tukšs, tad darbinieks sāk tos izpildīt. Mēs nosacīti sauksim šādus strādniekus skrienot.
Ja nepieciešams, darbinieks uzņemas lomu vadītājs baseins. Kopā var būt tikai viens vadošais darbinieks vai vispār nav darbinieku. Tās uzdevums ir uzturēt optimālu darbinieku skaitu vienā baseinā. Kā viņš to dara? Pirmkārt, tiek dzēsti darbinieki, kuri ilgu laiku ir bijuši dīkstāvē. Otrkārt, jauni darbinieki tiek radīti, ja vienlaikus tiek izpildīti trīs nosacījumi:

  • vēl ir jāizpilda uzdevumi (darbi baseinā)
  • nav dīkā strādājošo
  • nav strādājošu darbinieku (tas ir, aktīvi un neguļ)
Tomēr pēdējam nosacījumam ir savas nianses. Ja pūla rindas nav pievienotas, strādājošie darbinieki netiek ņemti vērā; viņiem šis nosacījums vienmēr ir spēkā. Tas pats attiecas uz darbinieku, kurš izpilda uzdevumu no saistītā uzdevuma, bet ar karogu WQ_CPU_INTENSIVE, rindas. Turklāt sasaistīto rindu gadījumā, tā kā strādnieki strādā ar darbiem no kopējā pūla (kas ir viens no diviem katram kodolam augšējā attēlā), izrādās, ka daži no tiem tiek uzskatīti par strādājošiem, bet daži ne. No tā arī izriet, ka veicot darbu no WQ_CPU_INTENSIVE rindas var nesākties uzreiz, bet tās pašas netraucē citu darbu izpildi. Tagad vajadzētu būt skaidram, kāpēc šo karogu tā sauc un kāpēc tas tiek izmantots, ja mēs paredzam, ka darbs prasīs ilgu laiku.

Strādājošo darbinieku uzskaite tiek veikta tieši no galvenā Linux kodola plānotāja. Šis kontroles mehānisms nodrošina optimālu vienlaicīguma līmeni, neļaujot darba rindā izveidot pārāk daudz darbinieku, bet arī neliekot darbam nevajadzīgi gaidīt pārāk ilgu laiku.

Tie, kas ir ieinteresēti, var apskatīt worker funkciju kodolā, to sauc worker_thread().

Visas aprakstītās funkcijas un struktūras sīkāk atrodamas failos include/linux/workqueue.h, kernel/workqueue.c Un kernel/workqueue_internal.h. Ir arī dokumentācija par darba rindu Dokumentācija/workqueue.txt.

Ir arī vērts atzīmēt, ka darbrindas mehānisms kodolā tiek izmantots ne tikai atlikto pārtraukumu apstrādei (lai gan tas ir diezgan izplatīts scenārijs).

Tādējādi mēs apskatījām mehānismus atlikto pārtraukumu apstrādei Linux kodolā - uzdevumlets un darbrinda, kas ir īpašs daudzuzdevumu veikšanas veids. Par pārtraukumiem, uzdevumletēm un darbrindām var lasīt Džonatana Korbeta, Grega Kroa-Hārtmena, Alesandro Rubini grāmatā “Linux ierīču draiveri”, lai gan informācija tur dažkārt ir novecojusi.