Створюємо нову OS. Дійсно нову, реально операційну, і правда – систему


Про створення нової операційної системи останнім часом говорять чимало, особливо в Росії. В сумі розмір усіх публікацій по даній темі напевно перевищує розміри вихідного коду будь-якої операційної системи. Так що залишається тільки одна проблема – від цих розмов ніяких нових OS не з'являється. Все, що пред'являється публіці (і на що витрачаються бюджетні гроші), на перевірку виявляється кастомізованими збірками OS сімейства Linux, а значить, не містить нічого принципово нового. Але, якщо про щось не говорять, це не означає, що його не існує.
У цій статті – проект принципово нової OS, створений в неробочий час одним з провідних співробітників (Principal Engineer) російського підрозділу Intel.

Загальні характеристики системи
Властивості пропонованої ОС збігаються з цілями її створення. А саме:
  • мінімально можливе споживання системних ресурсів
  • масштабованість по відношенню до потужності процесора, пам'яті, місця на диску, кількості одночасно виконуваних завдань, тобто, застосування як в потужних серверах, так і на мобільних пристроях і навіть таких специфічних системах, як мережі бездротових сенсорів
  • уніфіковане рішення для гетерогенних комп'ютерних систем, включаючи мережеві, тобто, підтримка спільної роботи на системах CPU і GPU різної архітектури, з оптимальним розподілом роботи між ними
  • підвищення надійності і безпеки (у порівнянні з існуючими системами)
  • легкість розробки, розповсюдження та оновлення програмного забезпечення при максимальному збереженні існуючих нині мов та інструментів розробки софта.


Основні принципи, що лежать в основі роботи ОС
Спочатку – про те, що передбачається залишити без (істотних) змін у порівнянні з існуючими системами або просто винести за рамки дизайну ОС, залишивши це на розсуд розробників конкретних імплементацій.
  • Користувальницький інтерфейс. Він може бути будь – яким- від найбільш складних варіантів до простої текстової консолі, наприклад, у випадку системи бездротових сенсорів.
  • Модель роботи з пам'яттю. Віртуальна фізична пам'ять, сторінки… або лінійний адресний простір. Наявність або відсутність свопу, захисту областей пам'яті..
  • Розмежування прав доступу користувача та привілейовані..
  • Файлова система. Вона також може бути будь-який з нині існуючих, або реалізувати сучасні ідеї відмови від файлів і подання у вигляді об'єктів, або взагалі, як Гудвін, великий та жахливий, виглядати по-різному для різних додатків....
А тепер про те, що буде новим.
Насамперед, назва ;). У пропонованій ОС поки немає усталеного назви. Хоча обговорюються різні варіанти (можете пропонувати свої в коментарях), робоча назва – ROS OS.

▍Знайомтеся, Resource-Owner-Service (ROS) модель
Основна ідея нової ОС, названа авторомResource-Owner-Service (Ресурс-Власник-Послуга) і покликана забезпечити вищенаведені характеристики системи, красива, проста і, на перший погляд, знайома. Але тільки на перший погляд. Отже:
  • Всі «компоненти» пристрою, на якому працює ОС, як залізні (ЦПУ, місце для зберігання даних тощо), так і програмні (наприклад, ділянки коду, що реалізують певний алгоритм), – розглядаються як ресурси і розділяються між власниками – програмами/завданнями.
  • Всі взаємодії між завданнями здійснюються виключно за допомогою запитів і наданням послуг (сервісів).
Нагадує звичайну модель клієнт-сервер? Так, але є ще третій принцип, в якому і полягає основна відмінність з даною моделлю —
  • «Уречевлення» сервісів. А саме, сервіс в ROS моделі – це не просто функціональність або інтерфейс для обміну даними, що подаються об'єктом, а нова комунікаційна та синхронизационная сутність – об'єкт.


Назвемо примірники цих об'єктів «каналами». Канал – це спільний об'єкт, що містить код, так і дані і призначений для передачі даних і управління між завданнями. Зауважте, що тут навмисно відсутнє розмежування на провайдера і споживача послуг (тобто, сервер і клієнт), так як кожна задача може і надавати, і споживати певний набір сервісів. З винятковості каналів як засоби взаємодії завдань слід ще одна властивість ROS-моделі: комунікаційна синхронізація. Будь-яка передача управління між завданнями по визначенню може здійснюватися тільки у зв'язці з отриманням\наданням послуги, тобто, канали стають синхронизационными об'єктами, однозначно визначають місце та призначення синхронізації завдань.

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

Дизайн ОС включає в себе два види каналів: дуальні і мульти. Як видно з назви, у перше число приєднаних завдань не може перевищувати 2, а в останніх обмеження за кількістю підключених завдань відсутні (визначаються виключно обсягом доступної пам'яті та особливостями конкретної реалізації ОС). Кожен канал має два протилежних кінця. Завдання, приєднані до них, називаються експортерами та імпортерами каналу, відповідно. Різниця між цими двома типами завдань несуттєва – обидва повинні створити екземпляр каналу, тобто, виділити пам'ять і таким чином помістити канал у свій адресний простір. Єдина мета поділу на експортерів та імпортерів, підтримка завдань з традиційною семантикою клієнт-сервер, тобто виробник і споживач послуг.

На малюнку нижче представлена принципова схема ROS OS:
image

Все вкрай просто. Сіро-блакитним кольором позначені фізичні компоненти системи – процесори (CPU), пам'ять (memory). Dev (Device) – це фізичний пристрій зберігання даних.
Ядро ОС володіє ресурсами процесорів і пам'яті. Іншими ресурсами системи можуть володіти привілейовані завдання (P-task), що надають в свою чергу сервіси завданням користувацького рівня (U-task) за допомогою комунікаційних каналів (channel).
ROS OS реалізує: Менеджер Пам'яті (ME-M), Менеджер Каналів (CH-M), Менеджер Системних Каналів (SC-M), Менеджер Завдань (TA-M), і Диспетчер Задач (TA-D). Ядро ідентифікує завдання та запити на сервіси допомогою системних каналів (syschan).

Управління завданнями
▍Скоординоване витіснення (cooperative preemption)
На відміну від Windows і Linux, які зберігають контекст завдань при їх перемиканні, споживаючи при цьому значна кількість пам'яті на розміщення стека, а також втрачаючи продуктивність на інструкціях збереження регістрів, пропонована OS дає можливість кожній задачі повідомити ядру, що зберігати її контекст не потрібно.

Замість цього, завдання надають оточення виконання, що складається з чотирьох покажчиків у формі {func(chan, sys, loc)}, так що ядро може очистити стек завдання, а потім, безпосередньо перед активацією, виділити задачі новий (або надати існуючий порожній) стек, після чого покликати функцію func з даними параметрами. При цьому, значення інших, не використовуваних у передачі цього оточення регістрів залишаються невизначеними або обнуляються, що швидше збереження\відновлення коректного значення.
Безпосередньо перед активацією завдання система очищає оточення з 4-ьох покажчиків, щоб виключити помилкове передчасне витіснення завдання.
Зауважимо, що скоординоване витіснення (за умови, що всі завдання в системі йому слідують) означає, що при ідеальних умовах виконання для кожного ядра CPU буде існувати тільки один стек. Додаткові стеки будуть виділятися, тільки якщо завдання буде витіснена передчасно, перед тим, як вона ініціалізує контекст з 4-ох покажчиків.

▍Модель передачі управління для потоків (Yield-To)
Дизайн ОС не передбачає явних функцій очікування (wait) або взаємного виключення для потоків. Замість цього межпотоковая синхронізація досягається явною управління передачею іншим завданням, идентифицируемым за допомогою покажчика на канал і номери задачі в каналі. Тобто, управління передається не конкретної задачі, а будь-якому агенту, який надає відповідний сервіс та проіндексовано у своєму сервісному каналі. При цьому, за конкретну ефективну внутрішню реалізацію протоколу для синхронізації відповідають самі комунікаційні агенти.
Операційна система може підтримувати множинні стратегії передачі керування завданнями, такі як «поступитися будь-який», «поступитися заданої» і «дозволити виконання заданої». Остання стратегія передбачає, що поступається завдання не має наміру віддавати контроль на «своєму» CPU, так що нова задача повинна бути активована в паралель з поточною на іншому CPU.

▍Детерміноване планування завдань
Кожна задача активується системним таймером (як і в існуючих ОС), але, з урахуванням застосування ROS OS до систем реального часу, кожна завдання має поставити вимоги до планувальником завдань у вигляді двох параметрів: частоти і тривалості активації. Крім свого основного призначення, ці параметри використовуються для оцінки передбачуваної завантаження системи, кількості очікуваних конфліктів завдань та виділення відповідно з цим необхідних ресурсів (тих же стеків)
В ідеалі, сумарна тривалість активацій всіх завдань повинна бути менше або дорівнює мінімальному заданому періоду активації, а періоди повинні бути кратні мінімального. Виконання цих умов буде гарантувати відсутність конфліктів планування.
Але в нашому недосконалому світі деякі завдання можуть перевищити свою заявлену тривалість, в такому випадку вони можуть бути витіснені іншими, готовими до виконання завдань та поміщені в список «на майбутнє виконання» в найближчі вільні слоти.

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

▍Спрощення структури коду
Оскільки канали являють собою динамічні структури даних, що розділяються між адресними просторами завдань і схильні до можливих переміщень у пам'яті ядром ОС, код каналів, так і завдань в загальному випадку повинен бути написаний так, щоб не залежати від статичного положення даних у пам'яті (або розташовувати їх відносно вхідного коду) і позиції самого коду. Відсутність статичних структур, незалежність від позиції і контекст з 4-ох покажчиків дозволяють обійтися без складних заголовків програм і визначає таку найпростішу структуру програми:
Кожна програма представляється у пам'яті у вигляді двох частин: коду і даних (з опціональним розмежуванням між ними для забезпечення захисту доступу). Початок шматка коду – це стартова функція задачі, що приймає на вхід три покажчика, як описано вище.

▍Створення і структура завдань
Завдання може бути створена з будь-якого фрагмента коду. Адреса початку коду буде адресою стартовою функції завдання – тієї самої, з прототипом чотирьох покажчиків – func(chan, sys, loc), де chan і loc – опціональні (так, loc – може вказувати на локальні дані), а sys – вказує на створений для завдання системний канал. Зауважимо, що код задачі копіюється на нове місце в пам'яті для спрощення подальших операцій з каналами – наприклад, якщо частина коду буде експортована в якості каналу.
Внутрішня структура завдань виглядає так:


Кожна задача містить дескриптор, що описує її тип (type) – інтерфейс або привілейований, а також покажчики: таблиці сторінок адресного простору (address space), локальну пам'ять (heap), стек (stack) і обробник винятків (exception handler).
Крім цього, дескриптор задачі включає список відображених каналів – (task2chan), тобто список пар: «індекс-дескриптор каналу, покажчик на код даного каналу».
У випадку, коли завдання поділяють спільний адресний простір, покажчики каналів можуть пропускатися з метою оптимізації використання пам'яті.
Перший елемент списку відображених каналів – це завжди системний канал завдання. Він зберігає контекст виконання та інші властивості, як описано далі.

▍Диспетчеризація задач
Дизайн ОС передбачає два типи активації завдань: (а) за системного таймера згідно запитуваній розкладом і (б) за явною передачі керування з іншого завдання.
Розклад активації може бути запитано у вигляді періоду і тривалості активації. Ці два параметри визначають наступну діаграму стану завдання


Граф зміни стану завдань.

Спочатку, при активації завдання, вона входить в захищене (active-protected) стан, тобто, вона не підлягає витіснення. Після того, як завдання перевищує заявлену тривалість, захист від витіснення знімається. У разі, коли в листі готових до виконання завдань (ready list) є інше завдання, система може витіснити першу задачу, помістити її дескриптор в ready list і активувати іншу задачу. Знову активована завдання відновлює виконання або в активному стані (якщо вона була витіснена раніше), або в захищеному стані (у разі запланованого виконання або взагалі для всіх завдань, в залежності від конкретної реалізації)

Важливий момент: завдання розкладу виконання на основі періоду дозволяє ядру системи гарантувати завдань реального часу певну частоту активації, а не граничний термін її початку. Таким чином, система може зрушувати завдання у часі, якщо це не порушує частоту активації. Завдання тривалості дозволяє уникнути проблем, пов'язаних з пріоритетом завдань, за допомогою визначення для них попередньо запланованого (запланованого) відносини активності/очікування. Конкретні реалізації ОС можуть обмежити тривалість максимальною довжиною кванта часу, щоб «жадібні» завдання не займали процесор нескінченно. Все вищесказане спрощує схему планувальника і забезпечує його швидке функціонування.
Планування на основі заданих періоду і тривалості активації дозволяє планувальником визначати неминучі конфлікти (див. малюнок нижче) і або відхилити запит на активацію, або пом'якшувати вимоги планування реального часу.


Крім витісняючої багатозадачності, система підтримує і кооперативну. Явна передача управління може здійснюватися за допомогою спеціального системного методу yield(). Виклик цього методу призведе до передачі управління задачі, ідентифікованої за допомогою покажчика на канал і номери задачі в ньому, і виконанню даної задачі на заданому процесорі. Також, як було сказано вище, можливо передати управління не конкретної задачі, а будь-якому агенту, який надає відповідний сервіс та проіндексовано у своєму сервісному каналі.

Кожен конфлікт планування (включаючи динамічний, коли завдання витісняють з перевищення тривалості виконання) у загальному випадку призводить до виділення ядром ОС додаткових ресурсів – нового стека для активованої завдання. Тому для оптимального використання системних ресурсів кожна задача може повідомити ядру ОС, що вона не потребує збереження регістрів даних і стека. У цьому випадку ОС при виклику yield() може звільнити стек завдання і скинути вміст регістрів. При активації такої задачі система викличе прототип {func(chan, sys, loc)} на порожньому стеку і невизначеному зміст регістрів.
Для забезпечення запланованої чи витісняє активації завдання диспетчер завдань підтримує список готових до виконання задач. Це – кільцевої буфер, що містить індекси-дескриптори (або покажчики) завдань і запланований час активації, як показано на малюнку нижче. Для витіснених завдань цей час буде, звичайно, нульовим.


Час може виражатися в абсолютних або відносних одиницях (по відношенню до максимального періоду), і оновлюватися відповідно до запитаними періодами активації. Коли завдання витісняється або поступається контроль, її дескриптор поміщається в перший вільний слот кільцевого буфера. Відзначимо, що так як список упорядкований за часом, завдання може бути вставлена в список перед іншими, якщо термін її активації настає раніше.
Готові до виконання завдання можуть вибиратися з аркуша, починаючи з кінця (хвоста) або з покажчиків на аркуші завдань (task list pointers), що містяться в Блоках управління процесорами, які описані нижче.

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

▍Управління каналами
Кожному каналу в системі присвоюється ідентифікатор, який зберігає інформацію про топологію каналу (type), його ідентифікатор (guid), а також покажчики: на тіло каналу (body pointer) в загальному адресному просторі, де розміщені всі канали; і на дескриптори завдань (chan2task), які приєднані до даного каналу. Тобто, система зберігає таблиці дескрипторів каналів, показану на малюнку нижче:


Зворотні посилання каналів на завдання необхідні для ідентифікації завдань за допомогою покажчика на канал і внутрішньоканального індексу (для передачі управління між завданнями). У цій схемі внутрішньоканальний індекс – це позиція дескриптора завдання (task descidx) у відповідному листі chan2task.

▍Системні виклики та управління контекстом
Системні канали – це комплекс засобів для ідентифікації завдань, збереження їх контексту, одержання системної інформації, генерації системних викликів і передачі керування іншим завданням.
На малюнку нижче показана структура системного каналу.


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

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

Системний канал повинен обов'язково підтримувати такі функції, які, фактично, будуть системним API:
//(1) Створення нової задачі з заданої функції func зазначеного розміру, типу і аргументів; повертає статус {ok, err};
int exec(func, size, type, arg0, arg1); 

//(2) Передача керування задачі, приєднаної до зазначеного каналу під заданим індексом subidx; або будь готової задачі. Варіант без аргументів призначений для завдання конкретного процесора для виконання завдання (викликом передує заповнення відповідних полів системного каналу); повертає статус {ok, err}; 
int yield(chan, subidx, strategy); 
int yield(); 

//(3) Знищення викликає дану функцію завдання;
void exit(); 

//(4) Виділення буфера пам'яті заданого розміру та типу. Повертає вказівник на виділений буфер
void* malloc(size, type); 

//(5) Звільнення буфера пам'яті, заданого показником-аргументом; повертає статус {ok, err};
int free(pointer); 

//(6)Експорт каналу заданого ID (guid) і топології (type) за заданою адресою. Повертає нову адресу каналу.
void* export(pointer, guid, type); 

//(7) Імпорт каналу заданого ID (guid) і топології (type) за заданою адресою. Повертає нову адресу каналу.
void* import(pointer, guid, type); 

//(8) Відключення завдання від даного каналу; повертає статус {ok, err, channel_destroyed}; 
int disconnect(pointer); 

//(9) Отримання індексу завдання в заданому каналі; шуканий індекс – обчислене значення функції.
int self(pointer); 

Тобто,весь системний API складається менше ніж з 10 функцій!!

▍Управління процесорами
Кожному процесору в системі присвоюється Блок Керування Процесором, який містить деякі властивості поточної виконуваної задачі, як показано на малюнку нижче.


Також у відповідність процесору ставляться деякі інші керуючі таблиці переривань (включаючи межпроцессорные) і таблиця системних дескрипторів.

Рівні складності системи
Складність реалізації певної системи може залежати від доступного обсягу пам'яті, процесорних можливостей і продуктивності конкретної платформи. Нижче описані можливості регулювання складності ядра ОС.

▍Реалізація управління каналами
Канали, будучи основою дизайну ОС, допускають значну варіативність. Наприклад, розробники ОС можуть вибрати підтримку виключно дуальних каналів, виключаючи таким чином необхідність динамічного виділення списків членів каналів. Крім того, система може не підтримувати канали з кодом (або, принаймні, змішані канали для коду та даних), внаслідок чого спрощується використання традиційних компіляторів та середовищ розробки, оскільки зникає необхідність агрегування коду і даних.

Канали можуть ідентифікуватися унікальними 128-бітними ідентифікаторами або певними індексами, валідність яких може контролюватися локальної (масштабу системи або мережі) або глобальної службою.
Ще одна можливість – привласнення каналах імен, утворених у вигляді ієрархічних шляхів, подібних системним файловим шляхах існуючих ОС.

▍Реалізація управління завданнями
Так само, як і в сучасних ОС, пропонований дизайн операційної системи передбачає можливість реалізації завдань як у формі процесів, так і у формі потоків, тобто, завдання можуть бути як ізольованими (логічно та фізично, якщо це дозволяють можливості процесорної системи), так і розділяти одне адресний простір, а значить, і передавати дані через колективні змінні.
Ще одна можлива, але дуже важлива особливість реалізації – це рівень привілеїв завдань, тобто, доступність завдань визначених системних і процесорних ресурсів (наприклад, межпроцессорных переривань, таблиць дескрипторів процесорів, вхідних-вихідних портів).
Можливе рішення – це підтримка виключно привілейованих завдань, що призведе до економії зусиль по реалізації ізоляції завдань, захист пам'яті і процесорних ресурсів.

Якщо ж підтримуються як привілейовані, так і непривілейовані завдання, дизайн ОС передбачає дві можливості для реалізації привілейованих операцій.

У першому випадку, всі привілейовані операції можуть виконуватися тільки ядром, тобто, всі привілейовані програми стають частиною ядра ОС і виконуються в його контексті (або контексті запитуючої сторони). Назвемо це «загальною схемою». Переваги цього підходу – обслуговування привілейованих операцій може не вимагати перемикання контексту завдань (операції виконуються в контексті запитуючої сторони) у разі, коли адресний простір ядра ОС відображається на адресний простір кожної задачі. Крім того, так як системне ядро завжди распараллелено за кількістю процесорів системи, паралельні завдання, які привілейовані операції, не будуть простоювати навіть при відсутності можливості виконання їх контексту. Недолік – запитує завдання не зможе продовжити виконання до тих пір, поки запит не буде оброблений.

У другому випадку всі привілейовані завдання можуть бути відокремлені від ядра («роздільна схема»).
Це вирішує проблему паралельного виконання запитувачів і виконують запити завдань, але вносить додаткове перемикання контексту по кожному запиту та отримання переривання (переривання, що належать привілейованим завданням, що можуть статися під час виконання будь-якої іншої задачі).

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

Для реалізації комбінованого підходу може знадобитися додаткове розширення до вищеописаної функціональності ядра – системний виклик, що з'єднує вектор переривань та його обробник – сервісну функцію, викликану ядром системи при передачі управління привілейованого каналу, власник якого – частина ядра, а не окреме завдання.
//(10) Зв'язок вектора переривань з його обробником., функція повертає статус {ok, err}
int connect(int vector, void* handler); 

При виклику цієї функції система перевіряє привілеї ініціатора запиту і поточний індекс завдання. Ядро ОС гарантує при отриманні кожного переривання з зазначеного вектора перемикання контексту на привілейовану завдання – ініціатора запиту. Нульовий параметр handler видаляє попередньо встановлений обробник переривань.
Дескриптор каналу в цьому випадку буде містити функцію — обробник переривань.


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

Дуальні канали є ефективним засобом передачі даних у разі, коли приєднані до них агенти копіюють свої дані в поділюване внутріканальне сховище і сповіщають одна одну про завершення операції копіювання\обробки даних (так що очікують агенти не гають обчислювальні ресурси).

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

Тут і на подальших рисунках U-Task означає пользовтельскую завдання.

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


Нижче обговорюються більш складні випадки мульти-канальних конфігурацій.

▍Пули завдань
Призначення пулів завдань – підтримка синхронизационных моделей, традиційно використовуваних в симетричної багатопроцесорності (SMP). Канали даних, до яких приєднані кілька завдань, можуть бути як контейнером для поділюваних (оброблюваних паралельно) даних, так і служити засобом синхронізації, тобто включати в себе лічильник числа завдань-користувачів каналу і, опціонально, їх індексів в каналі.
Пули завдань можуть використовувати можливості планування завдань ОС і забезпечувати ефективну синхронізації паралельних завдань – в протилежність найбільш примітивними схемами опитування каналів для синхронізації.

Експортер каналу може взяти на себе обов'язки розподілу роботи і, так як кількість процесорів і розподіляються завдань відомо, експортер може передати управління певним завданням на певних ЦПУ. Коли завдання-виконавці завершать обробку даних, вони можуть повернути управління розподіляє роботу задачі.


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


▍Маркери доступу
Маркери доступу (Access tokens) дозволяють реалізувати модель поділу даних (і\або отримання однакового обслуговування ) між декількома завданнями без встановлення каналів передачі даних. Замість цього, єдиний провайдер сервісів управляє всіма ресурсами або забезпечує сервіси всім активним агентам в системі. Кожен агент може запитати у нього маркер доступу, який і буде ключем до подільних даними. Потім агенти можуть «поділитися» маркером з іншими довіреними агентами (за допомогою спеціального каналу обміну), в результаті чого довірені агенти зможуть отримати той же самий тип обслуговування або доступу до даних.


▍Паралельний доступ до пристроїв
Перш ніж говорити про роботу з фізичними пристроями, зауважимо, що драйвера в ROS OS – це не особливі виділені сутності, а звичайні привілейовані завдання, які надають свої сервіси за допомогою каналів.
У багатьох випадках необхідно забезпечити кількох завдань одночасний доступ до потоків даних, вироблених чи споживаються фізичним пристроєм. На малюнку нижче показано 2 типових прикладу паралельної комунікації з пристроєм.


Перший буде ефективним, якщо ядро ОС розрізняє процеси і потоки. У цьому випадку спеціальна задача може обробляти всі операції з пристроєм і буферизувати вхідні\вихідні потоки даних, в той час як фактичне обслуговування завдань-споживачів даних буде проводитися потоками, мають повний доступ до внутрішнім буферам і, відповідно, можливості перевірити наявність даних і уникнути непотрібних затримок. На малюнку дана схема показана зліва.

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

▍Віддалене використання каналів
У відповідності з дизайном ОС – незалежністю роботи з компонентами від їх розташування, кожен агент в локальній системі може з'єднатися з каналом, що експортуються віддалено. Для підтримки цього механізму в систему вводяться так звані розповсюджувачів каналів, які крім підтримки звичайної функціональності експорту\імпорту каналів опитують свої локальні системи на предмет наявних каналів, перевіряють наявність мережевих запитів на ці канали, і, за їх наявності, підтримують пари (набори) віддалено синхронізованих каналів максимально прозорим чином для віддалених клієнтів цих каналів. Ілюстрація описаного механізму – нижче.

Тут і на подальших рисунках P-Task означає пользовтельскую завдання.

Зазначимо, що для ефективної підтримки віддалених реплікаторів каналів необхідний додатковий системний виклик
//(11)запит наявних каналів; Функція повертає довжину масиву каналів або помилку.
int query(guid[], size[], type[]); 

Тут guid[] – масив індексів каналів, size[]надає розміри каналів, а type[] містить типи\топологію запиту каналу (імпортовані, експортовані або мульти).

▍Підтримка з боку мов програмування
Існуючі мови програмування зажадають невеликого розширення для підтримки ROS моделі і полегшення розробки для пропонованої ОС. У випадку C++, розширення, зачіпає семантику мови, це модифікатор типу – «channel», показує тип вмісту, який має бути об'єднано у форму, придатну для межпроцессной комунікації. Залишилися проблеми, пов'язані з канальною комунікацією, вирішуються з використанням функцій ROS OS API, як показано нижче.
// оголошення
channel class A // новий модифікатор типу
{ 
int x; 
virtual void f(); 
void g(); 
} *a *b *c *d; 

//системний канал для виклику системних функцій
syschan_t* sys; 
bool multi = false; // true або для мультиканалов

// виділення пам'яті
a = new A; // об'єднує x, f, g і vft 
b = new A; // виділення простору для імпорту

// службові функції
c = export(a, sys, multi); // експортувати канал типу A 
if(c != a){ delete a; a = c; } // система перемістила канал 
d = import(b, sys, multi); // імпортувати канал типу A 
if(d != b){ delete b; b = d; } // система перемістила канал
int i = self(a, sys); // отримати свій індекс в каналі
disconnect(a, sys); // від'єднати експортований канал
disconnect(b, sys); // від'єднати імпортований канал
//OS може звільняти дескриптори каналів


Тобто, спочатку потрібно оголосити тип вашого каналу (використовуючи модифікатор типу channel), а також виділити стандартним способом області пам'яті для експортера та імпортера каналу. Після чого канал, очевидно, повинен бути приєднаний за допомогою експорту з одного боку та імпорту з іншого. Для цього використовуються спеціальні функції export() та import(), які передаються покажчики на ваш канал, системний канал і вказується вид каналу (мульти або дуальний).
Перевірки покажчиків, наведені після викликів цих функцій, наведені тут для середовищ з поділяється адресним простором, де існує ймовірність, що ядро ОС пересуне тіло каналу в пам'яті з виділеного раніше місця.
Кожні агент, приєднаний до каналу, має свій внутрішньоканальний індекс, одержуваний функцією self(), який залишається незмінним де тих пір, поки агент не відключиться від каналу (викликом disconnect()) і не приєднається до нього знову (за допомогою import() і export()). Решта пов'язані з каналами функції у відповідності з дизайном ОС рекомендується визначити так, щоб вони брали вказівник на ваш канал, поточний системний канал, а також вказівник на локальне сховище даних або опціональний параметр.
Нижче наведено прототип функції chanfunc, яка може бути як функцією запуску завдання з семантикою (arg0, sys, arg1), так і функцією ініціалізації каналу (яка виділить локальне сховище даних і поверне його вказівник loc), або взагалі будь-службової внутриканальной функцією
/// sys – Вказівник на системний канал
/// chan – вказівник на сам канал
/// loc – вказівник на локальні дані або опціональний параметр 
void* chanfunc(chan, sys, loc);


▍Генерація коду
При написанні програм для ROS ОС з використанням існуючих компіляторів, призначених для інших ОС, треба дотримувати обережності. Так, розробник повинен подбати про невикористання статичних змінних і працювати тільки з автоматично (стек) і динамічно виділяються даними та/або користуватися можливостями деяких компіляторів з генерації коду, не залежить від адреси завантаження. В ідеалі спеціальний компілятор для даної ОС повинен бути здатний виявити посилання на статичні дані та згенерувати обчислення відповідного адреси в пам'яті щодо адресою використовує ці дані функції.
Ще одна проблема, яка не може бути вирішена традиційними компіляторами (крім асемблера) – це підтримання комбінованих каналів коду і даних. Спеціалізований компілятор даної ОС повинен вміти об'єднувати тіла канальних функцій і дані в єдиний монолітний об'єкт, який може бути відображений, пересунуть і оброблений ОС.
Для процесорних архітектур, що підтримують захист виконання даних, агрегація коду і даних повинна проходити на базисі окремих сторінок, щоб не компрометувати безпеку системи.

Висновки
Основа пропонованої моделі ОС – сервісно-орієнтований підхід до паралельного виконання програм реалізує нові принципи передачі управління, планування завдань і управління ресурсами, а також нові логічні топології програмної комунікації і синхронізації, які задовольняють потреби сучасних паралельних та розподілених комп'ютерних систем, включаючи як потужні сервера, так і мережі бездротових сенсорів.
При цьому, система продумана з точки зору зв'язування разом залізних і програмних компонентів комп'ютерної системи, і максимально проста в реалізації.

Багато з описаних принципів дизайну ОС були спочатку втілені її автором ще в минулому столітті (1999 року) як частина легкої ОС, яка була передбачуваною і повністю контрольованим середовищем тестування для виконуваних файлів Windows NT. І, звичайно ж, з тих пір багато чого було додано і поліпшено.
Як бачите, створити свою ОС на базі описаних принципів можливо. Було б бажання. Якщо воно у вас є, то автор ідеї, безсумнівно зацікавлений у її реалізації, із задоволенням вам допоможе – проконсультує і підтримає.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.