Innledning til operativsystemer
Hei, Habr! Jeg ønsker å presentere for deg en serie med oversatte artikler fra en litteratur som jeg synes er interessant — OSTEP. I dette materialet blir arbeidet til UNIX-lignende operativsystemer behandlet temmelig grundig, nemlig arbeidet med prosesser, forskjellige planleggere, minne og andre lignende komponenter som utgjør et moderne OS. Den originale teksten til alt materialet kan du se her . Vennligst merk at oversettelsen er gjort uprofesjonelt (temmelig fritt), men jeg håper jeg har bevart den generelle meningen.
Laboratoriearbeid knyttet til emnet kan finnes her:
Andre deler:
Og du kan også sjekke kanalen min på =)
Alarm! det finnes et laboratorium for denne forelesningen! se
Prosess-API
La oss se på et eksempel på opprettelse av en prosess i et UNIX-system. Dette skjer gjennom to systemanrop fork() og exec().
Anropet fork()

La oss se på et program som utfører et fork()-anrop. Resultatet av kjøringen vil være som følger.

Først går vi inn i funksjonen main() og utfører utskrift av en streng på skjermen. Strengen inneholder prosessidentifikatoren som i originalen kalles PID eller prosessidentifikator. Denne identifikatoren brukes i UNIX for å referere til prosessen. Den følgende kommandoen vil utføre et kall til fork(). På dette tidspunktet blir en nesten identisk kopi av prosessen opprettet. For operativsystemet ser det ut som om det er to kopier av det samme programmet som kjører, som igjen vil forlate funksjonen fork(). Den nylig opprettede barneprosessen (i forhold til den opprinnelige forelderen) vil ikke bli utført fra funksjonen main() videre. Det er viktig å huske at barneprosessen ikke er en nøyaktig kopi av forelderen, spesielt fordi den har sitt eget adresseområde, egne registre, sin egen peker til kjørbare instruksjoner osv. Dermed vil verdien som returneres til den som kaller fork()-funksjonen være forskjellig. Spesielt vil forelderen motta PID-verdien til barneprosessen, mens barnet vil motta verdien lik 0. På bakgrunn av disse returkodene kan prosessene i fremtiden skille fra hverandre og tvinges til å utføre sitt eget arbeid. Utførelsen av dette programmet er imidlertid ikke strengt definert. Etter deling i to prosesser begynner OS å overvåke dem, og planlegge arbeidet deres. I tilfellet med kjøring på en enkeltkjernet prosessor, vil en av prosessene, i dette tilfellet - forelderen, fortsette arbeidet, og deretter vil kontrollen bli overført til barneprosessen. Ved neste oppstart kan situasjonen utvikle seg annerledes.
Kallet wait()

La oss se på følgende program. I dette programmet vil kallet wait() forelderen alltid vente på at barneprosessen skal avslutte. I dette tilfellet vil vi få en strengt definert tekstutskrift på skjermen.

Kallet exec()

La oss se på kallet exec(). Denne systemkallet er nyttig når vi ønsker å starte et helt annet program. Her skal vi kalle execvp() for å kjøre programmet wc, som er et program for telling av ord. Hva skjer egentlig når exec() kalles? Denne funksjonen får som argumenter navnet på den kjørbare filen og noen parametere. Deretter blir koden og statiske data lastet fra den kjørbare filen, og den nåværende kodesegmentet blir overskrevet. Andre minnedeler, som stakk og heap, blir reinitialisert. Etter dette utfører OS ganske enkelt programmet, og gir det et sett med argumenter. Dermed har vi ikke opprettet en ny prosess, men bare transformert det nåværende kjørende programmet til et annet kjørende program. Etter å ha kalt exec() får barneprosessen inntrykk av at den opprinnelige programvaren aldri ble startet.
Denne kompleksiteten ved oppstart er helt normal for Unix-shell, og lar denne skjellkjernen kjøre kode etter kallet fork(), men før kallet exec(). Et eksempel på slik kode kan være å tilpasse skjellmiljøet til behovene til det kjørende programmet før det faktisk starter.
Shell er bare et brukerprogram. Det viser deg en kommandolinje og venter på at du skal skrive noe der. I de fleste tilfeller, hvis du skriver navnet på programmet der, vil shell finne dets plassering, kalle fork()-metoden, og deretter, for å opprette en ny prosess, vil det kalle en av typene exec() og vente på at den fullføres ved hjelp av wait(). Når barneprosessen avsluttes, vil shell komme tilbake fra wait()-kallet, vise deg kommandolinjen igjen og vente på at neste kommando blir skrevet inn.
Skille mellom fork() & exec() lar shell gjøre følgende ting, for eksempel:
wc file > new_file.
I dette eksemplet er utdataene fra programmet wc omdirigert til en fil. Måten shell oppnår dette på er ganske enkel — når barneprosessen opprettes før kallet exec(), stenger shell standard utdata-strømmen og åpner filen new_file, slik at all utdata fra det senere kjørte programmet wc vil bli omdirigert til filen i stedet for skjermen.
Unix-rør er implementert på en lignende måte, med den forskjellen at de bruker pipe()-kallet. I dette tilfellet vil utdata-strømmen til prosessen være koblet til en pipe-kø som ligger i kjernen, som også vil være koblet til inndata-strømmen til en annen prosess.
Kilde: habr.com
