Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

Введення в операційні системи

Привіт, Хабре! Хочу представити вашій увазі серію статей-перекладів однієї цікавої на мою думку літератури — OSTEP. У цьому матеріалі розглядається досить глибоко робота unix-подібних операційних систем, а саме робота з процесами, різними планувальниками, пам'яттю та іншими подібними компонентами, які складають сучасну ОС. Оригінал всіх матеріалів ви можете подивитись ось тут. Прошу врахувати, що переклад виконаний непрофесійно (досить вільно), але сподіваюся, загальний зміст я зберіг.

Лабораторні роботи з даного предмета можна знайти ось тут:
- оригінал: pages.cs.wisc.edu/~remzi/OSTEP/Homework/homework.html
- оригінал: github.com/remzi-arpacidusseau/ostep-code
- Моя особиста адаптація: github.com/bykvaadm/OS/tree/master/ostep

А ще можете заглядати до мене на канал у телеграма =)

Робота програми

Що ж відбувається, коли працює якась програма? Запущена програма виконує одну просту річ — вона виконує інструкції. Кожну секунду мільйони і навіть, можливо, мільярди інструкцій витягуються процесором з оперативної пам'яті, у свою чергу він декодує їх (наприклад, розпізнає до якого типу, належать ці інструкції) і виконує. Це може бути додавання двох чисел, доступ до пам'яті, перевірка умови, перехід до функції тощо. Після закінчення виконання однієї інструкції процесор переходить до виконання іншою. І так інструкція за інструкцією, вони виконуються доти, доки програма не завершиться.
Даний приклад природно розглянутий спрощено — насправді для прискорення роботи процесора сучасне залізо дозволяє виконувати інструкції позачергово, прораховувати можливі результати, виконувати інструкції одночасно подібні хитрощі.

Фон-Нейманівська модель обчислення

Описана нами спрощена форма роботи схожа на Фон-Нейманівську модель обчислень. Фон-Нейман це один із піонерів комп'ютерних систем, також він один із авторів теорії ігор. Під час роботи програми відбувається ще купа інших подій, працює безліч інших процесів та сторонньої логіки, основна мета яких – спрощення запуску, роботи та обслуговування системи.
Існує набір програмного забезпечення, який відповідальний за простоту запуску програм (або навіть дозволяє запускати кілька програм одночасно), він дозволяє програмам розділяти ту саму пам'ять, а так само взаємодіяти з різними пристроями. Такий набір ПЗ (програмного забезпечення) по суті і називають операційною системою і в його завдання входять відстеження того, щоб система працювала коректно та ефективно, а також забезпечення простоти управління цією системою.

Операційна система

Операційна система, скорочено ОС - комплекс взаємопов'язаних програм, призначених для управління ресурсами комп'ютера та організації взаємодії користувача з комп'ютером.
ОС досягає своєї ефективності в першу чергу, через найголовнішу техніку — техніку віртуалізації. ОС взаємодіє з фізичним ресурсом (процесором, пам'яттю, диском тощо) і трансформує їх у більш загальну, з більшими можливостями і просту для використання форму себе. Тому для загального розуміння можна дуже грубо порівняти операційну систему з віртуальною машиною.
Для того щоб дозволяти користувачам давати команди операційній системі і таким чином використовувати можливості віртуальної машини (такі як: запуск програми, виділення пам'яті, доступ до файлу тощо), операційна система надає деякий інтерфейс, що називається API (application programming interface) і якого можна робити виклики (call). Типова операційна система дозволяє зробити сотні системних викликів.
І нарешті, оскільки віртуалізація дозволяє безлічі програм працювати (таким чином, спільно використовувати CPU), і одночасно отримувати доступ до їх інструкцій і даних (тим самим розділяючи пам'ять), а також отримувати доступ до дисків (таким чином, спільно використовувати пристрої введення-виводу ), операційну систему ще називають менеджером ресурсів. Кожен процесор, диск і пам'ять це ресурс системи і таким чином однією з ролей операційної системи стає завдання управління цими ресурсами, роблячи це ефективно, чесно або, навпаки, залежно від задачі, для якої ця операційна система розроблена.

Віртуалізація CPU

Розглянемо таку програму:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

Вона не виконує якихось особливих дій, по суті, все що вона робить — викликає функцію прясти(), завданням якої є циклічна перевірка часу та повернення, після того як пройшла одна секунда. Таким чином, вона повторює нескінченно рядок, який користувач передав як аргумент.
Запустимо цю програму і передамо їй аргументом символ “А”. Результат виходить не дуже цікавий - система просто виконує програму, яка періодично виводить на екран символ "А".
Тепер спробуємо варіант, коли запущено безліч екземплярів однієї програми, але виводять різні літери, щоб було зрозуміліше. У цьому випадку, результат вийде дещо інший. Незважаючи на те, що у нас один процесор, програма виконується одночасно. Як же так виходить? А виходить, що операційна система, не без допомоги можливостей обладнання, створює ілюзію. Ілюзію того, що в системі є кілька віртуальних процесорів, перетворюючи один фізичний процесор на теоретично нескінченну кількість і тим самим дозволяючи програмам на вигляд виконуватися одночасно. Таку ілюзію і називають Віртуалізацією CPU.
Подібна картина породжує багато питань, наприклад, якщо кілька програм бажають запуститися одночасно, то яка саме буде запущена? За це питання відповідають "політики" ОЗ. Політики використовуються в багатьох місцях ОС і відповідають на подібні питання, а також є базовими механізмами, які ОС втілює. Звідси роль ОС як ресурсного менеджера.

Віртуалізація пам'яті

Тепер розглянемо пам'ять. Фізична модель пам'яті у сучасних системах представляється як масив байт. Для читання з пам'яті необхідно вказати адреса осередку, щоб отримати доступ до неї. Щоб записати або оновити дані, потрібно також вказати дані та адресу осередку, куди їх записати.
Звернення до пам'яті відбувається постійно у процесі роботи програми. Програма зберігає у пам'яті всю її структуру даних, і звертається до неї, виконуючи різні інструкції. Інструкції також зберігаються в пам'яті, тому звернення до неї відбувається також на кожен запит до наступної інструкції.

Виклик malloc()

Розглянемо наступну програму, яка виділяє область пам'яті, використовуючи виклик malloc () (https://youtu.be/jnlKRnoT1m0):

Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

Програма робить кілька речей. По-перше, виділяє деякий обсяг пам'яті (рядок 7), потім виводить адресу виділеного осередку (рядок 9), записує нуль у перший слот виділеної пам'яті. Далі програма входить у цикл, у якому інкрементує значення, записане у пам'яті за адресою змінної “p”. Також вона виводить ідентифікатор процесу себе. Ідентифікатор процесу є унікальним для кожного запущеного процесу. Запустивши кілька копій, ми натрапимо на цікавий результат: У першому випадку, якщо не зробити нічого і просто запустити кілька копій, то адреси будуть різними. Але ж це не потрапляє під нашу теорію! Правильно, оскільки у сучасних дистрибутивах включена за умовчанням функція рандомізації пам'яті. Якщо її відключити, отримаємо очікуваний результат - адреси пам'яті у двох програм, що одночасно працюють, будуть збігатися.

Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

У результаті виходить, що дві незалежні програми працюють зі своїми власними приватними адресними просторами, які, у свою чергу, відображаються операційною системою у фізичній пам'яті.. Тому використання адрес пам'яті в межах однієї програми ніяк не зачіпатиме інших і кожній програмі здається, що в неї власний шматок фізичної пам'яті, повністю відданий у її розпорядження. Реальність, однак, така, що фізична пам'ять — ресурс, що розділяється, управління яким здійснює операційна система.

Узгодженість

Ще одна з важливих тем у рамках операційних систем узгодженість. Цей термін використовується, коли йдеться про проблеми в системі, які можуть виникати під час роботи з багатьма речами одночасно в межах однієї програми. Проблеми узгодженості виникають навіть у самій операційній системі. У попередніх прикладах з віртуалізацією пам'яті та процесора ми зрозуміли, що ОС керує багатьма речами одночасно — запускає перший процес, потім другий і так далі. Як виявилось, така поведінка може призвести до деяких проблем. Так, наприклад, сучасні багатопотокові програми зазнають таких труднощів.

Розглянемо таку програму:

Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

Програма у головній функції створює два потоки, використовуючи виклик Pthread_create(). У цьому прикладі про потік можна думати як про функцію, запущену в одному просторі пам'яті поруч з іншими функціями, причому кількість запущених одночасно функцій явно більше однієї. У цьому прикладі кожен потік стартує і виконує функцію worker() яка в свою чергу просто інкрементує змінну,.

Запустимо цю програму з аргументом 1000. Як ви вже могли здогадатися, результатом має стати 2000, оскільки кожен потік інкрементував змінну 1000 разів. Але все не так просто. Спробуємо запустити програму з кількістю повторень значно більше.

Operating Systems: Three Easy Pieces. Part 1: Intro (переклад)

Подаючи на вхід число, наприклад, 100000, ми очікуємо побачити на виході число 200000. Однак, запустивши число 100000 кілька разів, ми не тільки не побачимо правильну відповідь, але й отримаємо різні неправильні відповіді. Розгадка полягає в тому, що для збільшення числа потрібно три операції - вилучення числа з пам'яті, інкрементація, а потім запис числа назад. Оскільки ці інструкції не здійснюються атомарно (всі одночасно), такі дивні речі можуть відбуватися. Ця проблема називається у програмуванні race condition - стан гонки. Коли невідомі сили в невідомий момент можуть вплинути на виконання будь-яких операцій.

Джерело: habr.com

Додати коментар або відгук