Операционни системи: Три лесни части. Част 3: Процес API (превод)

Въведение в операционните системи

Здравейте, Хабр! Искам да ви представя серия от статии-преводи на интересна, според мен, литература — OSTEP. В този материал се разглежда дълбоко работата на Unix-подобни операционни системи, а именно — работа с процеси, различни планиратели, памет и други подобни компоненти, които съставят съвременната ОС. Оригиналът на всички материали можете да видите тук тук. Моля, имайте предвид, че преводът е направен непрофесионално (достатъчно свободно), но се надявам, че общият смисъл е запазен.

Лабораторните работи по този предмет можете да намерите тук:

Други части:

И можете да посещавате канала ми в Телеграм =)

Сигнал! има лабораторна работа за тази лекция! виж тук гитхаб

Process API

Нека разгледаме пример за създаване на процес в UNIX система. То става чрез два системни повика. fork() и exec().

Повик fork()

Операционни системи: Три лесни части. Част 3: Процес API (превод)

Нека разгледаме програма, която извършва повик fork(). Резултатът от нейното изпълнение ще бъде следният.

Операционни системи: Три лесни части. Част 3: Процес API (превод)

На първо място влизаме в функция main() и извеждаме низ на екрана. Низът съдържа идентификатор на процеса, който в оригинал е наречен PID или идентификатор на процес. Този идентификатор се използва в UNIX, за да се адресира процес. Следващата команда ще извърши повик fork(). В този момент се създава практически точна копия на процеса. За ОС това изглежда така, че в системата се изпълняват като че ли 2 копия на една и съща програма, които след това ще излязат от изпълнението на функцията fork(). Създаденият процес-потомък (по отношение на родителския си процес) вече няма да се изпълнява, започвайки от функция main(). Трябва да се помни, че процес-потомъкът не е точна копия на родителския процес, по-специално той има собствено адресно пространство, собствени регистри, свой указател на изпълнявани инструкции и т.н. Така, стойността, връщана на повикващия функцията fork() ще бъде различна. По-специално, родителският процес ще получи като връщане стойността на PID на детския процес, а детето ще получи стойност равна на 0. С помощта на тези кодове за връщане по-късно вече може да разделим процесите и да накараме всеки от тях да изпълнява своята работа. При това изпълнението на тази програма не е строго определено. След разделянето на 2 процеса ОС започва да следи за тях, а също така и планира тяхната работа. В случай на изпълнение на едноядрен процесор, работата ще продължи един от процесите, в този случай — родителския, а след това управлението ще получи процес-потомък. При повторно стартиране ситуацията може да се развие по различен начин.

Повик wait()

Операционни системи: Три лесни части. Част 3: Процес API (превод)

Нека разгледаме следната програма. В тази програма поради наличието на повик wait() процес-потомъкът винаги ще изчака завършването на работата на родителския процес. В този случай ще получим строго определен изходен текст на екрана.

Операционни системи: Три лесни части. Част 3: Процес API (превод)

Повик exec()

Операционни системи: Три лесни части. Част 3: Процес API (превод)

Нека разгледаме повик exec(). Този системен повик е полезен, когато искаме да стартираме напълно друга програма. Тук ще извикаме execvp() за стартиране на програмата wc, която е програма за броене на думи. Какво се случва при извикването на exec()? На това извикване се предават като аргументи името на изпълнимия файл и някои параметри. След това се зареждат кодът и статичните данни от този изпълним файл и се презаписва собственият сегмент с код. Останалите области на паметта, като стек и купа, се инициализират отново. След което операционната система просто изпълнява програмата, предавайки ѝ набор от аргументи. Така ние не създаваме нов процес, а просто трансформираме текущо изпълняваната програма в друга изпълнявана програма. След извикването на exec() в потомството възниква впечатление, че първоначалната програма изобщо не е била стартирана.

Такава сложност при стартиране е напълно нормална за Unix shell и позволява на тази обвивка да изпълнява код след извикването fork(), но преди извикването exec(). Пример за такъв код може да бъде настройката на околната среда на обвивката според нуждите на стартираната програма, преди нейното непосредствено стартиране.

Шел — просто потребителска програма. Тя ви показва строката на поканата и изчаква, докато напишете нещо в нея. В повечето случаи, ако напишете там името на програмата, shell намира местонахождението ѝ, извиква метода fork(), а след това, за да създаде нов процес, извиква някой от типовете exec() и изчаква изпълнението му с помощта на извикването wait(). Когато процес-потомък завърши, shell се връща от извикването на wait() и отново показва строката на поканата, изчаквайки въвеждането на следващата команда.

Разделянето на fork() & exec() позволява на shell да прави следните неща, например:
wc file > new_file.

В този пример изходът на програмата wc е пренасочен в файл. Начинът, по който shell постига това, е достатъчно прост — при създаването на процес-потомък преди извикването на exec(), shell затваря стандартния изходящ поток и отваря файла new_file, така че целият изход от по-късно стартираната програма wc ще бъде пренасочен във файла вместо на екрана.

Unix pipe са реализирани по подобен начин, с разликата, че използват извикването на pipe(). В този случай изходният поток на процеса ще бъде свързан с опашката на pipe, разположена в ядрото, към която ще бъде присъединен входният поток на друг процес.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster