Hyrja në sistemet operative
Përshëndetje, Habr! Dua të paraqes para jush një seri artikujsh me përkthime nga një letërsi interesante sipas mendimit tim - OSTEP. Ky material shqyrton mjaft thellë funksionimin e sistemeve operativë të ngjashme me UNIX, domethënë - punën me proceset, planifikuesit e ndryshëm, memorien dhe komponentë të tjerë të ngjashëm që përbëjnë një sistem operativ modern. Origjinali i të gjithë materialeve mund ta shihni këtu . Ju lutem, merrni parasysh se përkthimi është realizuar në mënyrë jo profesionale (mjaft lirshëm), por shpresoj se kuptimi i përgjithshëm e kam ruajtur.
Për laboratorët në këtë lëndë mund të gjeni këtu:
Pjesët e tjera:
Dhe gjithashtu mund të shikoni kanalin tim në =)
Alarm! ka një laboratorium për këtë ligjëratë! shiko
API-i i Procesit
Le të shqyrtojmë një shembull të krijimit të një procesi në sistemin UNIX. Ai ndodh përmes dy thirrjeve sistemike fork() и exec().
Thirrja fork()

Le të shqyrtojmë programin që kryen thirrjen fork(). Rezultati i ekzekutimit të tij do të jetë si vijon.

Në radhë të parë ne hyjmë në funksionin main() dhe kryejmë daljen e një vargu në ekran. Vargu përmban identifikuesin e procesit që në origjinal quhet PID ose identifikuesi i procesit. Ky identifikues përdoret në UNIX për t'u referuar një procesi. Komanda e ardhshme do të ekzekutojë thirrjen fork(). Në këtë moment krijohet një kopje pothuajse e saktë e procesit. Për sistemin operativ, duket se janë nisur si të ishin 2 kopje të të njëjtës programi, të cilat përfundimisht do të dalin nga funksioni fork(). Procesi i ri i krijuar (në lidhje me procesin prind) nuk do të ekzekutojë më, duke filluar nga funksioni main(). Duhet mbajtur mend se procesi i ri nuk është një kopje e saktë e procesit prind, veçanërisht pasi ai ka hapësirën e tij të adresave, regjistrat e tij, treguesin për instruktionet e ekzekutueshme dhe gjëra të ngjashme. Kështu, vlera e kthyer nga thirrësja e funksionit fork() do të jetë e ndryshme. Saktësisht, procesi prind do të marrë si kthim vlerën PID të procesit fëmijë, ndërsa fëmija do të marrë një vlerë të barabartë me 0. Me këto kode të kthimit më pas është e mundur të ndahen proceset dhe të bëjnë që secili prej tyre të kryejë punën e vet. Në këtë rast, ekzekutimi i këtij programi nuk është i përcaktuar me saktësi. Pas ndarjes në 2 procese, sistemi operativ fillon t'i ndjekë ato, ashtu si dhe t'i planifikojë aktivitetet e tyre. Në rastin e ekzekutimit në një procesor me një bërthamë, puna do të vazhdojë me një nga proceset, në këtë rast - prindin, dhe më pas kontrolli do të kalojë te procesi fëmijë. Në një përsëritje të mëtejshme, situata mund të zhvillohet ndryshe.
Thirrja wait()

Le të shqyrtojmë programin e mëposhtëm. Në këtë program, falë thirrjes wait() procesi prind gjithmonë do të presë për përfundimin e punës së procesit fëmijë. Në këtë rast, ne do të marrim një dalje të përcaktuar me saktësi në ekran

Thirrja exec()

Le të shqyrtojmë thirrjen exec(). Ky thirrje sistemike është e dobishme kur duam të nisemi një program krejtësisht tjetër. Këtu do të thërrasim execvp() për të nisur programin wc, i cili është një program për numërimin e fjalëve. Çfarë ndodh kur thirret exec()? Kësaj thirrjeje i jepet si argument emri i skedarit ekzekutiv dhe disa parametra. Pas kësaj ngarkohet kodi dhe të dhënat statike nga ky skedar ekzekutiv dhe mbi-shkruhet seksioni i vet me kod. Pjesët e tjera të memories, si steka dhe kupa, inicializohen përsëri. Pas kësaj, OS thjesht ekzekuton programin, duke i dhënë një grup argumentesh. Kështu, ne nuk kemi krijuar një proces të ri, thjesht kemi transformuar programin aktual po funksionon në një program tjetër që po funksionon. Pas thirrjes exec() në fëmijë krijohet përshtypja se programi fillestar nuk është nisur fare.
Ky komplimin i nisi është krejtësisht normal për shell-in Unix dhe i lejon atij të ekzekutojë kod pas thirrjes fork(), por para thirrjes exec(). Një shembull i tillë kodi mund të jetë përshtatja e mjedisit të shell-it për nevojat e programit që do të nisë, para nisjes së tij të drejtpërdrejtë.
Shell — thjesht është një program përdoruesi. Ai tregon rreshtin e promovimit dhe pret derisa të shkruani diçka atje. Në shumicën e rasteve, nëse shkruani emrin e programit atje, shell do ta gjejë vendndodhjen e tij, do të thërrasë metodën fork(), dhe pastaj për të krijuar një proces të ri do të thërrasë ndonjë nga llojet e exec() dhe do të presë për ekzekutimin e tij me thirrjen wait(). Kur procesi fëmijë përfundon, shell do të kthehet nga thirrja wait() dhe do të tregojë përsëri rreshtin e promovimit dhe do të presë për futjen e komandës tjetër.
Ndarja e fork() & exec() i lejon shell-it të bëjë gjëra si:
wc file > new_file.
Në këtë shembull, dalja e programit wc është ridrejtuar në skedarin. Mënyra se si shell e arrin këtë është mjaft e thjeshtë — kur krijon procesin e fëmijës para thirrjes exec(), shell mbyll rrjedhën standarde të daljes dhe hap skedarin new_file, kështu që të gjitha daljet nga programi që do të ekzekutohet më vonë wc do të ridrejtohen në skedar në vend të ekranit.
Unix pipe janë zbatuar në mënyrë të ngjashme, me ndryshimin se ato përdorin thirrjen pipe(). Në këtë rast, rrjedha e daljes së procesit do të lidhet me një radhë pipe, e cila ndodhet në bërthamë, me rrjedhën e hyrjes së një procesi tjetër.
Burimi: habr.com
