Въведение в операционните системи
Здравейте, Хабр! Искам да ви представя серия от статии-преводи на една интересна, според мен, литература — OSTEP. В този материал се разглеждат дълбоко работата на Unix-подобни операционни системи, а именно — работата с процеси, различни планировчици, памет и прочие компоненти, които съставят съвременната операционна система. Оригиналът на всичките материали можете да видите тук . Моля, имайте предвид, че преводът е направен непрофесионално (достатъчно свободно), но се надявам, че общият смисъл е запазен.
Лабораторните работи по този предмет можете да намерите тук:
— оригинал:
— оригинал:
— моя лична адаптация:
И можете да посещавате канала ми в =)
Работа на програмата
Какво се случва, когато работи някаква програма? Стартираната програма изпълнява едно просто нещо — тя изпълнява инструкции. Всяка секунда милиони и дори възможно милиарди инструкции се извличат от оперативната памет от процесора, който от своя страна ги декодира (например, разпознава към какъв тип принадлежат тези инструкции) и ги изпълнява. Това могат да бъдат събиране на две числа, достъп до памет, проверка на условие, преход към функция и така нататък. След приключване на изпълнението на една инструкция, процесорът преминава към изпълнение на друга. И така инструкция след инструкция, те се изпълняват, докато програмата не завърши.
Този пример е естествено разгледан опростено — всъщност съвременният хардуер позволява на процесора да изпълнява инструкции извън реда, да изчислява възможни резултати, да изпълнява инструкции едновременно и подобни хитрости.
Модел на Фон Нойман
Описаната от нас опростена форма на работа прилича на модела на Фон Нойман за изчисления. Фон Нойман е един от пионерите на компютърните системи, също така е един от авторите на теорията на игрите. По време на работата на програмата се случват още много други събития, работят множество други процеси и странична логика, чиято основна цел е опростяване на стартирането, работата и поддръжката на системата.
Съществува набор от софтуер, който отговаря за лесното стартиране на програми (или дори позволява стартирането на няколко програми едновременно), той позволява на програмите да споделят същата памет и да взаимодействат с различни устройства. Такъв набор от софтуер (програмно осигуряване) по същество наричат операционна система и в неговите задължения влиза проследяване на това, дали системата работи правилно и ефективно, а също така осигуряване на лесно управление на тази система.
Операционна система
Операционната система, съкратено ОС — комплекс от взаимосвързани програми, предназначени за управление на ресурсите на компютъра и организиране на взаимодействието на потребителя с компютъра..
ОС постига своята ефективност предимно чрез най-важната техника — техниката на виртуализация.. ОС взаимодейства с физическия ресурс (процесора, паметта, диска и подобни) и го трансформира в по-обща, с по-големи възможности и по-лесна за използване форма на самия себе си. Затова за общо разбиране, може много грубо да сравним операционната система с виртуалната машина.
За да позволи на потребителите да дават команди на операционната система и по този начин да използват възможностите на виртуалната машина (като: стартиране на програма, разпределение на памет, достъп до файл и така нататък), операционната система предоставя някакъв интерфейс, наречен API (интерфейс за програмиране на приложения), към който може да се правят повиквания (call). Типичната операционна система позволява извършването на стотици системни повиквания.
Накрая, тъй като виртуализацията позволява на множество програми да работят (по този начин, споделяйки CPU), и едновременно да получават достъп до техните инструкции и данни (така споделяйки памет), а също така да получават достъп до дисковете (така споделяйки устройства за вход-изход), операционната система е наричана мениджър на ресурсите. Всеки процесор, диск и памет са ресурси на системата и така една от ролите на операционната система става управлението на тези ресурси, правейки го ефективно, честно или, обратното, в зависимост от задачата, за която е разработена тази операционна система.
Виртуализация на CPU
Нека разгледаме следната програма:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Тя не изпълнява особени действия, по същество всичко, което прави — извиква функцията spin(), чието задание е циклично проверяване на времето и връщане назад, след като е минала една секунда. По този начин, тя безкрайно повтаря низът, който потребителят е предал като аргумент.
Да стартираме тази програма и да и предадем символ “А” като аргумент. Резултатът не е особено интересен — системата просто изпълнява програмата, която периодично извежда на екрана символ “А”.
Сега да опитаме вариант, когато са стартирани множество екземпляри на същата програма, но извеждащи различни букви, за да бъде по-ясно. В този случай резултатът ще бъде малко различен. Въпреки че имаме един процесор, програмата се изпълнява едновременно. Как става това? Става така че операционната система, не без помощта на възможностите на оборудването, създава илюзия. Илюзията, че в системата има няколко виртуални процесора, превръщайки един физически процесор в теоретично безкрайно количество и по този начин позволява на програмите да изглеждат, че се изпълняват едновременно. Тази илюзия се нарича Виртуализация на CPU.
Подобна картина поражда много въпроси, например, ако няколко програми искат да стартират едновременно, то коя точно ще бъде стартирана? За този въпрос отговарят “политиките” на ОС. Политиките се използват на много места в ОС и отговарят на подобни въпроси, а също така са основните механизми, които ОС реализира. Оттук и ролята на ОС като ресурсен мениджър.
Виртуализация на паметта
Сега нека разгледаме паметта. Физическата модел на паметта в съвременните системи се представя като масив от байтове. За да се чете от паметта, е необходимо да се посочи адрес на клетка, за да се получи достъп до нея. За да запишете или обновите данни, е нужно също да посочите данните и адреса на клетката, където да бъдат записани.
Обращенията към паметта стават постоянно по време на работа на програмата. Програмата съхранява в паметта цялата си структура от данни и я използва, изпълнявайки различни инструкции. Инструкциите междувременно също се съхраняват в паметта, така че достъпът до нея става и при всяко следващо запитване за инструкция.
Извикване на malloc()
Нека разгледаме следната програма, която заделя памет, използвайки извикване malloc() (https://youtu.be/jnlKRnoT1m0):

Програмата извършва няколко неща. Първо, заделя определено количество памет (ред 7), след това извежда адреса на заделената клетка (ред 9), записва нула в първия слот на заделената памет. След това програмата входи в цикъл, в който увеличава стойността, записана в паметта по адреса в променливата “p”. Също така извежда идентификатора на процеса, в който се намира. Идентификаторът на процеса е уникален за всеки стартиран процес. Ако стартираме няколко копия, ще попаднем на интересен резултат: В първия случай, ако не направим нищо и просто стартираме няколко копия, адресите ще бъдат различни. Но това не е в съответствие с нашата теория! Правилно, тъй като в съвременните дистрибуции по подразбиране е включена функция за рандомизация на паметта. Ако я изключим, ще получим очаквания резултат — адресите на паметта ще съвпадат между две одновременно работещи програми.

В крайна сметка излиза, че две независими програми работят със собствените си частни адресни пространства, които от своя страна се отразяват от операционната система в физическата памет. Затова използването на адреси на паметта в рамките на една програма няма да влияе на другите и всяка програма смята, че разполага със собствена усилия физическа памет, напълно предоставена ѝ на разположение. Реалността обаче е, че физическата памет е споделен ресурс, управлението на който се извършва от операционната система.
Последователност
Още една важна тема в рамките на операционните системи — последователност. Този термин се използва, когато става дума за проблеми в системата, които могат да възникнат при работа с много неща едновременно в рамките на една програма. Проблемите с последователността възникват дори в самата операционна система. В предишните примери с виртуализация на паметта и процесора разбрахме, че ОС управлява много неща едновременно — стартира първия процес, след това втория и така нататък. Оказа се, че такова поведение може да доведе до някои проблеми. Например съвременните многопоточни програми изпитват такива трудности.
Нека разгледаме следната програма:

Програмата в главната функция създава два потока, използвайки извикване Pthread_create(). В този пример на потока можем да мислим като за функция, стартирана в едно пространство на паметта до други функции, като броят на стартираните едновременно функции е ясен по-голям от една. В този пример всеки поток стартира и изпълнява функцията worker(), която просто инкрементира променливата,.
. Ще стартираме тази програма с аргумент 1000. Както вече можете да се досетите, резултатът трябва да бъде 2000, тъй като всеки поток инкрементира променливата 1000 пъти. Въпреки това, всичко не е толкова просто. Нека опитаме да стартираме програмата с число на повтаряния, което е порядък по-високо.

Подайки на вход число, например 100000, очакваме да видим на изхода число 200000. Но когато стартираме числото 100000 няколко пъти, не само че не виждаме правилния отговор, но и получаваме различни неправилни отговори. Разгадката е в това, че за увеличаване на числото са необходими три операции — извличане на числото от паметта, инкрементиране и след това записване на числото обратно. Тъй като всички тези инструкции не се извършват атомарно (всички наведнъж), такива странни неща могат да се случват. Тази проблема в програмирането се нарича състояние на гонка. Когато неизвестни сили в неизвестен момент могат да повлияят на изпълнението на вашите операции.
Източник: habr.com
