Introducere în sistemele de operare
Salut, Habr! Vreau să vă prezint o serie de articole-traduceri dintr-o literatură care mi se pare interesantă - OSTEP. Acest material examinează în mod profund funcționarea sistemelor de operare de tip unix, și anume - gestionarea proceselor, diferitele planificatoare, memorie și alte componente asemănătoare care formează un sistem de operare modern. Puteți vizualiza originalul tuturor materialelor aici . Vă rog să țineți cont că traducerea a fost efectuată neprofesional (destul de liber), dar sper că am păstrat sensul general.
Lucrările de laborator pentru această materie le puteți găsi aici:
Alte părți:
De asemenea, mă puteți urmări pe canalul meu de =)
Alarm! la această lecție există un laborator! vezi
API-ul procesului
Vom examina un exemplu de creare a unui proces în sistemul UNIX. Acesta se realizează prin două apeluri de sistem fork() și exec().
Apelul fork()

Să analizăm un program care efectuează apelul fork(). Rezultatul execuției sale va fi următorul.

În primul rând, intrăm în funcția main() și efectuam o ieșire a unui șir pe ecran. Șirul conține identificatorul procesului care se numește în original PID sau identificator de proces. Acest identificator este utilizat în UNIX pentru a accesa procesul. Următoarea comandă va efectua apelul fork(). În acest moment se creează o copie practic identică a procesului. Pentru sistemul de operare, pare că în system sunt pornite 2 copii ale aceleași programe, care la rândul lor vor ieși din execuția funcției fork(). Procesul fiu creat (în raport cu procesul părinte care l-a creat) nu va fi executat, începând cu funcția main(). Trebuie să reținem că procesul fiu nu este o copie exactă a procesului părinte, întrucât acesta are propriul său spațiu de adresare, propriile sale registre, propriul său pointer la instrucțiunile executabile și așa mai departe. Astfel, valoarea returnată apelantului funcției fork() va fi diferită. În special, procesul părinte va primi ca returnare valoarea PID a procesului copil, iar copilul va primi o valoare egală cu 0. Pe baza acestor coduri de returnare, putem separa ulterior procesele și să le facem pe fiecare să își îndeplinească sarcina. Totuși, executarea acestui program nu este strict definită. După separarea în 2 procese, sistemul de operare începe să le monitorizeze și să le planifice activitatea. În cazul executării pe un procesor cu un singur nucleu, activitatea va continua cu unul dintre procese, în acest caz — procesul părinte, iar apoi va prelua controlul procesul fiu. La o reluare a execuției, situația poate fi diferită.
Apelul wait()

Să examinăm următorul program. În acest program, datorită prezenței apelului wait() procesul părinte va aștepta întotdeauna finalizarea execuției procesului fiu. În acest caz, vom obține o ieșire strict definită a textului pe ecran

Apelul exec()

Să examinăm apelul exec(). Acest apel de sistem este util atunci când dorim să pornim o programă complet diferită. Aici vom apela execvp() pentru a rula programul wc, care este un program de numărare a cuvintelor. Ce se întâmplă atunci când este apelată exec()? La acest apel îi sunt transmise ca argumente numele fișierului executabil și câțiva parametri. Apoi, codul și datele statice din acest fișier executabil sunt încărcate și segmentul său cu cod este rescris. Alte zone de memorie, cum ar fi stiva și heap-ul sunt reinițializate. După aceea, sistemul de operare pur și simplu execută programul, oferindu-i un set de argumente. Astfel, nu am creat un nou proces, ci am transformat programul executat curent într-un alt program executat. După execuția apelului exec() în copil, apare impresia că programul inițial nu a fost, în principiu, deloc lansat.
Aceasta complexitate a lansării este complet normală pentru shell-ul Unix și permite acestei shell să execute cod după apelare fork(), dar înainte de apelul exec(). Un exemplu de astfel de cod poate fi ajustarea mediului shell-ului conform nevoilor programului executat, înainte de a-l lansa efectiv.
Shell — este doar un program pentru utilizatori. Îți arată un prompt și așteaptă până când introduci ceva. În majoritatea cazurilor, dacă introduci numele unui program, shell-ul îi va găsi locația, va apela metoda fork() și apoi, pentru a crea un nou proces, va apela unul dintre tipurile de exec() și va aștepta executarea acestuia cu ajutorul apelului wait(). Când procesul copil se finalizează, shell-ul revine din apelul wait() și va afișa din nou promptul, așteptând să introducă următoarea comandă.
Separarea fork() & exec() permite shell-ului să facă următoarele lucruri, de exemplu:
wc file > new_file.
În acest exemplu, ieșirea programului wc este redirecționată în fișier. Modul în care shell-ul realizează acest lucru este destul de simplu — atunci când creează procesul copil înainte de apelarea exec(), shell-ul închide fluxul standard de ieșire și deschide fișierul new_file, astfel, toată ieșirea din programul care va fi lansat mai departe wc va fi redirecționată în fișier în loc de pe ecran.
Unix pipe sunt realizate într-un mod similar, cu diferența că utilizează apelul pipe(). În acest caz, fluxul de ieșire al procesului va fi conectat la o coadă pipe situată în nucleu, care va fi de asemenea conectată la fluxul de intrare al altui proces.
Sursa: habr.com
