Лекції "Системне програмування"

Site: Державний університет "Житомирська політехніка" - Освітній портал
Course: Системне програмування
Book: Лекції "Системне програмування"
Printed by: Guest user
Date: Wednesday, 20 January 2021, 1:15 PM

1. Лекція 1 Створення ОС Windows. Структура ОС Windows

Створення ОС Windows. Структура ОС Windows

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

Мал. 1.1. Шари програмного забезпечення комп'ютерної системи

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

Альтернативний погляд на ОС дає уявлення про ОС як про менеджера ресурсів, який здійснює впорядкований і контрольований розподіл процесорів, пам'яті та інших ресурсів між різними програмами.

Коротка історія створення ОС Windows

Перша версія ряду операційних систем - ОС Windows NT з'явилася в 1993 р Короткий історичний екскурс дозволяє пояснити ряд її особливостей і відмінностей.

Найбільш важливі моменти еволюції операційних систем

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

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

Архітектурні особливості операційних систем.

В даний час переважна більшість операційних систем має так званий монолітний дизайн.

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

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

 

Сучасна тенденція в розробці операційних систем полягає в перенесенні значної частини системного коду на рівень користувача і одночасної мінімізації ядра. Йдеться про підхід до побудови ядра, яку називають мікроядерною архітектурою (microkernel architecture) операційної системи, коли більшість її складових є самостійними програмами. У цьому випадку взаємодія між ними забезпечує спеціальний модуль ядра, званий мікроядром. Мікроядро працює в привілейованому режимі і забезпечує взаємодію між програмами, планування використання процесора, первинну обробку переривань, операції введення-виведення і базове управління пам'яттю. Інші компоненти взаємодіють шляхом обміну повідомленнями в рамках архітектури клієнт-сервер (Мал. 1.2).


Мал. 1.2. Реалізація моделі клієнт-сервер в рамках мікроядерної архітектури

Створення ОС Windows

Операційні системи корпорації Microsoft можна умовно розділити на три групи:

• MS-DOS і MS-DOS + Windows 3.1,

• так звані споживчі (consumer) версії Windows (Windows 95/98 / Me)

• і лінія ОС, які ведуть свій початок від Windows NT (Windows NT / 2000 / XP / Vista/...).

Однозадачна 16-розрядна ОС MS-DOS була випущена на початку 80-х років і потім широко застосовувалася на комп'ютерах з процесором x86. Спочатку MS-DOS була досить примітивна (деградація ОС), її оболонка займалася, головним чином, обробкою командного рядка, але в наступні версії було внесено багато покращень, запозичених, головним чином, з ОС Unix.

Потім під впливом успіхів дружнього графічного інтерфейсу корпорації Apple для комп'ютерів Macintosh була розроблена система Windows. Особливо широкого поширення набули версії Windows 3.0, 3.1 і 3.11.

Спочатку це була не самостійна ОС, а скоріше багатозадачна (з витісняючою багатозадачностюі) графічна оболонка MS-DOS, яка контролювала комп'ютер і файлову систему.

У 1995 р була випущена 32-розрядна ОС Windows 95, де була реалізована витісняюча багатозадачність. ОС Windows95 включала великий обсяг 16-розрядного коду, головним чином для забезпечення наступності з додатками MS-DOS. 16-розрядний код був присутній і в наступних версіях цієї серії Windows 98 і Windows Me. Іншою проблемою даної версії Windows, багато в чому обумовленої тією ж причиною, була нереентерабельність значної частини коду ядра. Так, якщо один з потоків був зайнятий модифікацією даних в ядрі, інший потік, щоб не отримати ці дані в суперечливому стані, змушений був чекати, тобто не міг скористатися системними сервісами. Це, найчастіше, зводило нанівець переваги багатозадачності.

ОС Windows NT (New Technology) - нова 32-розрядна ОС, сумісна з попередніми версіями Windows по інтерфейсу. Помітна спадкоємність в системі управління великим адресним простором і саморозміщувані безліччю процесу, в системі пріоритетів звичайних процесів і процесів реального часу, в засобах синхронізації і т.д. Разом з тим Windows NT - це абсолютно новий амбітний проект розробки системи з урахуванням новітніх досягнень в галузі архітектури мікроядра. Перша версія, названа Windows NT 3.1 для відповідності популярної Windows 3.1, була випущена в 1993р Комерційного успіху добилася версія Windows NT 4.0, яка мала графічний інтерфейс Windows 95. На початку 1999р була випущена Windows NT 5.0, перейменована в Windows 2000. Наступна версія цієї ОС даної серії - Windows XP з'явилася в 2001 році, а Windows Server 2003 - в 2003р, далі випущена Windows Vista, раніше відома під кодовим ім'ям Longhorn, - нова версія Windows, яка продовжує лінійку Windows NT.

Мал. 1.3. Порівняння архітектур ОС Windows та VAX/VMS

Можливості системи

Перед розробниками системи було поставлено завдання створити операційну систему персонального комп'ютера, призначену для вирішення серйозних завдань, а також для домашнього використання. Перелік можливостей системи досить широкий, ось лише деякі з них. Операційна система Windows:

  • є істинно 32(64)-розрядною, підтримує багатозадачність;
  • працює на різних апаратних архітектурах і має здатність до порівняно легкого переносу на нові апаратні архітектури;
  • підтримує роботу з віртуальною пам'яттю;
  • є повністю реєнтерабельною;
  • добре масштабується в системах із симетричною мультипроцесорної обробкою;
  • є розподіленої обчислювальної платформою, здатною виступати в ролі як клієнта мережі, так і сервера;
  • захищена як від внутрішніх збоїв, так і від зовнішніх деструктивних дій. У додатків немає можливості порушити роботу операційної системи або інших додатків;
  • сумісна, тобто, її призначений для користувача інтерфейс і API сумісні з попередніми версіями Windows і MS-DOS. Вона також вміє взаємодіяти з іншими системами на кшталт UNIX, OS/2 і NetWare;
  • має високу продуктивність незалежно від апаратної платформи;
  • підтримує багатопоточність і об'єктну модель.

Структура ОС Windows

Загальний опис структури

Архітектура ОС, зазнала ряд змін в процесі еволюції. Перші версії системи мали мікроядерний дизайн, заснований на мікроядрі Mach, яке було розроблено в університеті Карнегі-Меллона. Архітектура пізніших версій системи мікроядерною вже не є.

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

Сьогодні микроядро ОС Windows занадто велике (більше 1 Мб), щоб носити приставку "мікро". Основні компоненти ядра Windows NT розташовуються в пам'яті що витісняється і взаємодіють один з одним шляхом передачі повідомлень, як і належить в мікроядерним операційним системам. У той же час всі компоненти ядра працюють в одному адресному просторі і активно використовують загальні структури даних, що властиво операційним системам з монолітним ядром.

Висока модульність і гнучкість перших версій Windows NT дозволила успішно перенести систему на такі відмінні від Intel платформи, як Alpha (корпорація DEC), Power PC (IBM) і MIPS (Silicon Graphic). Пізніші версії обмежуються підтримкою архітектури Intel x86.

Спрощена схема архітектури, орієнтована на виконання Win32/64-додатків, показана на мал. 1.4.

Мал. 1.4. Спрощена архітектурна схема ОС Windows

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

Завдання рівня абстрагування від устаткування (hardware abstraction layer, HAL) - приховати апаратні відмінності апаратних архітектур для потенційного перенесення системи з однієї платформи на іншу. HAL надає вище лежачим рівнями апаратні пристрої в абстрактному вигляді, вільному від індивідуальних особливостей. Це дозволяє ізолювати ядро, драйвери і виконавчу систему ОС Windows від специфіки обладнання (наприклад, від відмінностей між материнськими платами).

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

Ядро і HAL є апаратно-залежними і написані на мовах Сі та асемблера. Верхні рівні написані на мові Сі та є машинно-незалежними.

Виконавча система (executive) забезпечує управління пам'яттю, процесами і потоками, захист, введення-виведення і взаємодія між процесами. Драйвери пристроїв містять апаратно-залежний код і забезпечують трансляцію користувальницьких викликів в запити, специфічні для конкретних пристроїв. Підсистема підтримки вікон і графіки реалізує функції графічного інтерфейсу користувача (GUI), більш відомі як Win-32-функції модулів USER і GDI

У просторі користувача працюють різноманітні сервіси (аналоги демонів в Unix), керовані диспетчером сервісів і вирішальні системні завдання. Деякі системні процеси (наприклад, обробка входу до системи) диспетчером сервісів не справляються і називаються фіксованими процесами підтримки системи. Призначені для користувача програми (user applications) бувають п'яти типів: Win32, Windows 3.1, MS-DOS, POSIX і OS/2.

 Середовище для виконання призначених для користувача процесів надають три підсистеми оточення: Win32, POSIX і OS/2. Таким чином, призначені для користувача програми не можуть викликати системні виклики ОС Windows безпосередньо, а змушені звертатися до DLL.

Основні компоненти ОС Windows реалізовані в наступних системних файлах, що знаходяться в каталозі system32:

  • ntoskrnl.exe - виконавча система і ядро;
  • ntdll.dll - внутрішні функції підтримки і інтерфейси диспетчера системних сервісів з функціями виконавчої системи;
  • hal.dll - рівень абстрагування від устаткування;
  • win32k.sys - частина підсистеми Win32, що працює в режимі ядра;
  • kernel32.dll, advapi32.dll, user32.dll, gdi32.dll - основні dll підсистеми Win32.

Підсистема Win32

Взаємодія між додатком і операційною системою здійснюється за допомогою системних викликів (системних сервісів в термінології Microsoft). Однак програма не може викликати системний виклик безпосередньо (більш того, системні виклики не задокументовані). Замість цього додаток повинен скористатися програмним інтерфейсом ОС - Win32 API.

Win32 API (Application Programming Interface) - основний інтерфейс програмування в сімействі операційних систем Microsoft Windows. Функції Win32 API, наприклад, CreateProcess() або CreateFile(), - документовані, що викликаються підпрограми, реалізовані Win32 підсистемою.

До складу Win32 підсистеми (див. Мал. 1.4) входять:

  • cерверний процес підсистеми оточення csrss.exe,
  • драйвер режиму ядра Win32k.sys,
  • dll - модулі підсистем (kernel32.dll, advapi32.dll, user32.dll і gdi32.dll), експортують Win32-функції і драйвери графічних пристроїв.

В процесі еволюції структура підсистеми зазнала змін. Наприклад, функції вікон і малювання з метою підвищення продуктивності були перенесені з серверного процесу, що працює в режимі користувача, в драйвер режиму ядра Win32k.sys. Однак це і подібні зміни ніяк не позначилися на працездатності додатків, оскільки існуючі виклики Win32 API не змінюються з новими випусками системи Windows, хоча їх склад постійно поповнюється.

Додаток, орієнтоване на використання Win32 API, може працювати практично на всіх версіях Windows, незважаючи на те, що самі системні виклики в різних системах різні (див. Мал. 1.5). Таким шляхом корпорація Microsoft забезпечує спадкоємність своїх операційних систем.

Мал. 1.5. Підтримка єдиного програмного інтерфейсу для різних версій Windows

 

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

 

Мал. 1.6. Різні маршрути виконання викликів Win32 API.

При виклику додатком однієї з Win32-функцій dll-підсистем може виникнути одна з трьох ситуацій (див. Мал. 1.6).

  • Функція повністю виконується всередині даної dll (крок 1).
  • Для виконання функції залучається сервер csrss, для чого йому надсилається повідомлення (крок 2a, за яким зазвичай йдуть кроки 2b і 2c).
  • Даний виклик транслюється в системний сервіс (системний виклик), який зазвичай обробляється в модулі ntdll.dll (кроки 3a і 3b). Наприклад, Win32-функція ReadFile виконується за допомогою недокументовані сервісу NtReadFile.

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

У перших версіях ОС Windows практично всі виклики Win32 API виконувалися, слідуючи маршрутом 2 (2a, 2b, 2c). Після того, як значна частина коду системи для збільшення продуктивності була перенесена в ядро (починаючи з Windows NT 4.0), виклики Win32 API, як правило, йдуть безпосередньо за 3-му (3a, 3b) шляху, минаючи підсистему оточення Win32. В даний час лише невелика кількість викликів виконується по довгому 2-му маршруту.

Крім перерахованих, найбільш важливих dll-бібліотек, в системному каталозі system32 є велика кількість інших dll-файлів. В даний час кількість викликів API становить кілька десятків тисяч.

Перелік функцій, що експортуються кожною конкретною dll, можна подивитися за допомогою утиліти depends, що входить в пакет Platform SDK. Так, на мал. 1.7 наведена інформація про структуру бібліотеки kernel32.dll ОС Windows XP, що експортує 949 функцій.

Мал. 1.7. Вікно утиліти depends.exe

Процеси і потоки

Під процесом розуміється контейнер ресурсів, використовуваних потоками.

Процес включає:

  •  закритий адресний простір, в якому розташовуються: код, дані та стеки потоків;
  • список відкритих описувачів (handles) ресурсів;
  • контекст захисту;
  • ідентифікатор процесу.

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

 

2. Лекція 3 Базові поняття ОС Windows

Базові поняття ОС Windows

 Переривання, виключення, системні виклики

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

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

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

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

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

Застосовуючи структурну обробку виключень, можна спробувати "виправити" непоправну виняткову ситуацію, повернувши управління програмою, яка згенерувала цю ситуацію.

Приклад програми реалізує структурну обробку виключень

#include <stdio.h>

void main(){ 

int i = 1, j = 0, k = 0;

__try {

   k = i / j;

   puts("in try");

   printf("k=%d\n",k);

}

__except (1) {

   puts("in except");

   printf("k=%d\n",k);

}

}

Реалізація переривань, системних викликів і винятків
 в ОС Windows

Розглянемо реалізацію основних механізмів операційної системи в ОС Windows. Слід зазначити, що термінологія корпорації Microsoft дещо відрізняється від загальноприйнятої. Наприклад,

системні виклики називаються системними сервісами,

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

Пастки (trap)

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

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

У типовому випадку зберігаються і згодом відновлюються:

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

Ця інформація специфікована в структурі CONTEXT (файл winnt.h), і може бути отримана користувачем за допомогою функції GetThreadContext.

Адреса частини ядра ОС, відповідальної за обробку даного конкретного події визначається з вектору переривань, який номеру події ставить у відповідність адресу процедури його первинної обробки

Для асинхронних подій їх номер визначається контролером переривань, а для синхронних - ядром.

Процедура перегляду вектору переривань, який в термінології корпорації Microsoft називається таблицею диспетчеризації переривань (interrupt dispatch table, IDT), за допомогою відладчика kd.

Наприклад, для x86 процесора переривання від клавіатури відповідає номер 0x52, системним сервісів - 0x2e, а виняткової ситуації, пов'язаної з сторінкової помилкою, - 0xE (див. Мал. 3.1).

Мал. 3.1 Вектор переривання (IDT)

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

Те ж саме відбувається в разі виникнення виключень і переривань.

Прості виключення можуть бути оброблені диспетчером пасток, а більш складні обробляються диспетчером винятків, який може в разі виникнення виключення повернути управління додатку який викликав цей виняток. Це робиться за допомогою згаданого вище апарату структурної обробки виключень.

Вторинна обробка переривання забезпечується драйверами відповідних пристроїв.

Як приклад розглянемо процедуру обробки створення файлу.

Виклик Win32 функції CreateFile() генерує передачу управління функції NtCreateFile виконавчої системи, асемблерний код якої містить наступні операції:

mov ЕАХ, Ox17 номер системного сервісу для NtCreateFile

mov ebx, esp

int Ox2E обробка системного сервісу

ret Ox2C повернення управління

 

Мал. 3.2 ілюструє подальшу обробку даного сервісу.

 

Пріоритети. IRQL

У більшості операційних систем апаратні переривання мають пріоритети, які визначаються контролерами переривань.

Однак ОС Windows має свою апаратно-незалежну шкалу пріоритетів, які називаються рівні запитів переривань (interrupt request levels, IRQL), і охоплює не тільки переривання, а всі події, що вимагають системної обробки. У таблиці 3.1 наведені значення IRQL рівнів для x86 систем.

Таблиця 3.1. Рівні запитів переривань (IRQL) в x86 системах

Рівень

Значення

Номер

High

Найвищий рівень

31

Power fail

Відмова електроживлення

30

Inter-process interrupt

між процесорний сигнал

29

Clock

Системний годинник

28

Profile

Контроль продуктивності ядра

27

Device n

Переривання від пристрою

26

Переривання від пристроїв

 

Device 1

Переривання від пристрою

3

DPC / dispatch

Відкладені операції і планування

2

APC

Асинхронні виклики процедур

1

Passive

Нормальне виконання потоків

0

 

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

Значення IRQL для апаратних переривань розставляються диспетчером Plug and Play за допомогою рівня абстрагування від устаткування HAL, а для інших подій - ядром. Таким чином, рівень IRQL визначається джерелом події, що має інший зміст, ніж пріоритети в стратегії планування потоків. Розбиття на IRQL рівні є основним механізмом упорядкування за пріоритетами дій операційної системи.

Можна сказати, що в ОС Windows діє дворівнева схема планування. Пріоритети вищого рівня (в даному випадку IRQLs) визначаються апаратними або програмними перериваннями, а пріоритети нижчого рівня (в своєму діапазоні від 0 до 31) встановлюються для призначених для користувача потоків, які виконуються на нульовому рівні IRQL, і контролюються планувальником.

На нульовому (PASSIVE LEVEL) рівні IRQL працюють призначені для користувача процеси і частина коду операційної системи. Програма, що працює на цьому рівні, може бути витіснена майже будь-якою подією, що трапилася в системі.

Більшість процедур режиму ядра намагається утримувати IRQL процесора якомога нижчим.

IRQL рівні 1 (APC LEVEL) і 2 (DISPATCH LEVEL) призначені для так званих програмних (в термінології Microsoft) переривань відповідно:

 асинхронний виклик процедури - APC (asynchronous procedure call)

і до дзвінка процедури - DPC (deferred procedure call).

Якщо ядро приймає рішення виконати деяку системну процедуру, але немає необхідності робити це негайно, воно ставить її в чергу DPC і генерує DPC переривання.

Коли IRQL процесора стане досить низьким, ця процедура виконується. Характерний приклад - відкладена операція планування. З цього випливає, що код, що виконується на IRQL рівні, вище або дорівнює 2, не схильний до операції планування.

Асинхронний виклик процедур - механізм, аналогічний механізму DPC, але більш загального призначення, зокрема, доступний для процесів користувача.

IRQL рівні 3-26 відносяться до звичайних переривань від пристроїв.

3. Лекція 4 Об'єкти. Менеджер об'єктів. Реєстр

Об'єкти. Менеджер об'єктів. Реєстр

Вступ

Для роботи з важливими системними ресурсами ОС Windows створює об'єкти, управління якими здійснює менеджер об'єктів. Коли додаток відкриває файл, створює потік або семафор, він отримує дискриптор, описувач (handle) відповідного об'єкта (див. Мал. 4.1). Наприклад, після виконання програмного оператора

hSemaphore = CreateSemaphore (NULL, 0, MaxCount, "MySemaphore");

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

Мал. 4.1. Створення додатком об'єкта "семафор"

 

Об'єкти - абстрактна концепція, яка активно використовується в ОС Windows для регулювання доступу до системних ресурсів.

Наявність об'єктів є безперечною необхідністю системи.

  • Це єдиний інтерфейс до всіх системних ресурсів і структур даних, таких, як процеси, потоки, семафори і т.д.
  • Іменування об'єктів і доступ до них здійснюються по одній схемі.
  • Використання об'єктів дає Microsoft можливість оновлювати функціональність системи, не зачіпаючи програмного інтерфейсу додатків.
  • Це дуже важливо з точки зору системної безпеки. Кожен об'єкт має список прав доступу, який перевіряється щоразу, коли додаток створює свій описувач об'єкта. Відповідно, всі перевірки, пов'язані із захистом, можуть бути виконані в одному модулі - диспетчері об'єктів (відповідно до вимог безпеки), з гарантією, що жоден процес не може їх обійти.
  • Легко організувати спільний доступ до об'єктів, нескладно відстежити об'єкти, які більше не використовуються, і т.д.

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

Об'єкти ядра (kernel objects)

Підтримка об'єктів ядра здійснюється власне ядром і виконавчою системою. Крім об'єктів ядра є також об'єкти, призначені для керування вікнами (User), і об'єкти, призначені для керування графікою (GDI).

Сукупність об'єктів утворює багатошарову структуру. Ядро підтримує базові об'єкти двох видів: об'єкти диспетчера (події, м'ютекси, семафори, потоки ядра, таймери і ін.) і керуючі (DPC, APC, переривання, процеси, профілі та ін.)

Над об'єктами ядра знаходяться об'єкти виконавчої системи, кожен з яких інкапсулює один або більше об'єктів ядра. Об'єкти виконавчої системи призначені для управління пам'яттю, процесами і міжпроцесорним обміном. Вони експортуються в розпорядження користувача додатків через Win32 функції. До них відносяться такі об'єкти, як: процес, потік, відкритий файл, семафор, м'ютекс, маркер доступу і ряд інших. Повний список можна побачити в MSDN. Ці об'єкти і називаються об'єктами ядра в посібниках з програмування.

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

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

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

Структура об'єкта. методи об'єкта

Мал. 4.2. структура об'єкта

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

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

Квота встановлює обмеження на обсяги ресурсів. Незважаючи на те, що в ОС Windows реалізований код для відстеження квот, в даний час квоти не застосовуються і існують досить м'які обмеження. Наприклад, за замовчуванням ліміт на відкриті об'єкти для процесу - 230. Безліч об'єктів поділяється на типи, а у кожного з об'єктів є атрибути, незмінні для об'єктів даного типу. Посилання на тип об'єкту також входить до складу заголовка. Поля ім'я об'єкта і каталог будуть описані в розділі "іменування об'єктів".

Методи об'єкта

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

Описувачі об'єктів

Створення нових об'єктів, або відкриття за ім'ям вже існуючих, додаток може здійснити за допомогою Win32-функцій, таких, як CreateFile, CreateSemaphore, OpenSemaphore і т.д. Це бібліотечні процедури, за якими стоять сервіси Windows і методи об'єктів. У разі успішного виконання створюється 64-бітний описувач в таблиці описувачів процесу в пам'яті ядра. На цю таблицю є посилання з блоку управління процесом EPROCESS.

З 64-х розрядів описувача 29 розрядів використовуються для посилання на блок пам'яті об'єкта ядра, 3 - для прапорів, а що залишилися 32 - в якості маски прав доступу.

Маска прав доступу формується на етапі створення чи відкриття об'єкта, коли виконується перевірка дозволів. Таким чином, описувач об'єкта - приналежність процесу, який створив цей об'єкт. За замовчуванням він не може бути переданий іншому процесу. Проте, система надає можливість дублювання описувач і передачі його іншому процесу спеціальним чином.

Мал. 4.3. Об'єкти і їх описувачі

Win32-функції, що створюють об'єкт, повертають додатку не сам описувач, а індекс в таблиці описувачів, тобто мале число: типу 1, 2 а не 64-розрядне (див. Рис. 4.3). Надалі це значення передається функціям, які приймають описувач об'єкту у якості аргументу. Однією з таких функцій є функція CloseHandle(), завдання якої - закрити об'єкт. Щоб уникнути витоку пам'яті завжди рекомендується закривати об'єкт, якщо в ньому відпала потреба. Втім, після закінчення роботи процесу система закриває всі його об'єкти. Таким чином, структури об'єктів ядра доступні тільки ядру, додаток не може самостійно знайти ці структури в пам'яті та безпосередньо модифікувати їх вміст.

Іменування об'єктів. колективні ресурси

Багато об'єктів в системі мають імена. Іменування об'єктів зручно для обліку об'єктів і пошуку потрібного об'єкту. Крім того, знання імені об'єкта може бути використано процесом для отримання до нього доступу (спільне використання ресурсів).

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

"Побачити" простір імен можна тільки за допомогою спеціальних інструментальних засобів, наприклад, за допомогою утиліти winobj, що входить до складу MS Platform SDK.

Мал. 4.4. Вікно утиліти winobj

Приклад програми, що створює об'єкт

Прикладом такої програми може бути програма відображення файлу в пам'ять.

#include <windows.h>

#include <stdio.h>

void main (void){

HANDLE hMapFile;

HANDLE hFile;

 

hFile = CreateFile ( "MyFile.txt",

GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,

NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

 

hMapFile = CreateFileMapping (hFile, NULL,

PAGE_READWRITE, 0, 0, "MyFileObject");

getchar();

}

В результаті роботи цієї програми файл "myfile.txt" відображається в пам'ять, при цьому в каталозі об'єктів BaseNamedObjects створюється об'єкт "секція" з ім'ям "MyFileObject".

Імена усіх базових об'єктів, таких, як м'ютекси, проекції файлу в пам'ять, семафори зберігаються в одному каталозі BaseNamedObjects. Тому вони не повинні збігатися навіть у об'єктів різних типів. Обрання одного і того ж імені для різних об'єктів призведе до помилки. Це властивість можна використовувати для запобігання запуску декількох екземплярів додатку, в якому створюється іменований об'єкт.

Іменування допускають не всі об'єкти ядра. Зазвичай функція, що створює іменований об'єкт, має в якості параметра ім'я об'єкта. Наприклад, функція CreateMutex створює іменований об'єкт.

HANDLE CreateMutex (PSECURITY_ATTRIBUTES psa,

  BOOL bInitialOwner, PCTSTR pszName);

Останній параметр pszName і є ім'я (можна створити безіменний об'єкт, якщо вказати тут NULL). Між іншим, об'єкт "відкритий файл" є неіменований, і, отже, функція яка створює цей об'єкт CreateFile цього параметра не має. Власного імені файлу цілком достатньо для його ідентифікації та поділу, а простір імен файлів є продовженням простору імен об'єктів (див. Мал. 4.5)

Мал. 4.5. Зв'язок простору імен об'єктів і простору імен файлової системи

Спільне використання об'єктів

Іноді у додатків виникає необхідність у спальному використанні ресурсів.

Найбільш поширений варіант - коли двом або більше процесів відоме ім'я спільного об'єкту. У цьому випадку один з процесів створює об'єкт (наприклад, за допомогою функції CreateSemaphore), а інші відкривають його для себе (наприклад, за допомогою функції OpenSemaphore).

Мал. 4.6. Спільне використання об'єктів

Іншим способом отримання доступу до вже існуючого об'єкта є успадкування дочірнім процесом об'єктів батьківського процесу. Можна здійснити дублювання описувача відкритого об'єкта за допомогою функції DuplicateHandle.

Система керує об'єктами "файл" не так як іншими об'єктами ядра. Об'єкт "файл" містить покажчик поточної позиції у файлі. Тому кожен виклик функції CreateFile для одного і того ж файлу призводить до створення нового об'єкта "файл" і відповідно - нового описувача об'єкта. Лише процедури дублювання описувачів і успадкування дозволяють отримати новий описувач до одного і того ж об'єкту.

Реєстр

Операційна система керує великим об'ємом інформації, необхідної для її завантаження і конфігурації. У ранніх версіях Windows ця інформація містилася в різних текстових файлах з розширенням .ini (Win.ini, System.ini і т.д.).

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

Мал. 4.7. Загальний вигляд редактора реєстру

Дані реєстру зберігаються у вигляді ієрархічної деревоподібної структури. Кожен вузол або каталог називається розділом або ключем (keys), а назви каталогів верхнього рівня починаються з рядка HKEY. Кожен розділ може містити підрозділ (subkey). Записи нижній частині структури називаються параметрами (values), дані яких строго типізовані, см. MSDN.

Реєстр містить два кореневих розділи:

  • HKEY__USERS
  • HKEY_LOCAL_MACHINE

Ще є посилання на підвузли:

  • HKEY_CURRENT_USER
  • HKEY_CLASSES_ROOT
  • HKEY_PERFORMANCE_DATA
  • HKEY_CURRENT_CONFIG

Найбільш важливим є розділ HKEY_LOCAL_MACHINE. У ньому міститься вся інформація по локальній системі.

Простір імен реєстру інтегровано із загальним простором імен ядра. Воно є третім простором імен в системі поряд з просторами імен об'єктів і файлів. Для інтеграції система підтримує об'єкт "розділ реєстру" (key є серед типів об'єктів).

Реєстр зберігається на диску у вигляді набору файлів, які називаються "кущами" або "вуликами" (hives). Більшість з них знаходиться в каталозі \ Systemroot \ System32\Config. Велике значення приділяється підвищенню надійності зберігання. Зокрема, система веде протоколи модифікації кущів (за допомогою так званих реєстраційних кущів, log hives), які забезпечують гарантовану можливість відновлення постійних кущів реєстру. Для ще більшого захисту цілісності на диску підтримуються дзеркальні копії критично важливих кущів.

Дані реєстру повністю доступні через Win32 API. Існують виклики для перегляду, створення і видалення розділів і параметрів. Щоб отримати доступ до даних, потрібно (при наявності необхідних привілеїв) відкрити відповідний розділ за допомогою функції RegOpenKeyEx. Для запису або видалення можна використовувати функції RegSetValue, RegDeleteValue.

4. Лекція 5.1 Багатозадачність. Процеси і потоки

Багатозадачність

Процеси і потоки

Процес - завантажена в пам'ять і готова до виконання програма.

Кожен процес має свій власний віртуальний адресний простір (4Gb). Процес складається з коду, даних і інших системних ресурсів, таких як відкриті файли, канали (pipes), що синхронізують об'єкти.

Потік (thread) - базовий об'єкт, якому операційна система розподіляє час центрального процесора.

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

Процесорний час розподіляється по черзі між потоками, а не між процесами. Тривалість кванта виділення часу складає близько 20 мс.


 Розподіл часу між потоками

Процесорний час виділяється потокам відповідно до їхнього рівня пріоритету. Потоку з більш низьким пріоритетом не виділяється час, якщо на нього претендує потік з більш високим рівнем пріоритету. Більш того, процес з більш низьким пріоритетом переривається до витікання кванта часу, якщо на процесор претендує потік з більш високим рівнем пріоритету. Необхідно пам'ятати, що в середовищі Windows основна “робота” потоку складається в чеканні події і реагуванні на нього. Це дає шанс на виконання потокам з низьким рівнем пріоритету.

Рівні пріоритетів варіюються в діапазоні від 0 (нижчий) до 31 (вищий).

Рівень пріоритету кожного потоку складається з трьох складових

  • клас пріоритету процесу (простоючий, нормальний, високий, реального часу)
  • рівень пріоритету потоку усередині класу пріоритету процесу (нижній, нижче нормального, нормальний, вище нормального, вищий)
  • динамічно встановлений рівень пріоритету.
    1. Клас пріоритету процесу

Клас

Прапор у функції CreateProcess

Числовий рівень

Idle (простоючий)

IDLE_PRIORITY_CLASS

4

Normal (нормальний)

NORMAL_PRIORITY_CLASS

7-9

High (високий)

HIGH_PRIORITY_CLASS

13

Real time(реального часу)

REALTIME_PRIORITY_CLASS

24

2. При запуску на виконання процесу  призначається один з чотирьох класів пріоритету.

Рівень Idle призначається процесу, що нічого не повинний робити у випадку активності інших процесів (наприклад, хоронитель екрана).

Процесам, що запускається користувачем, привласнюється нормальний рівень. Користувач може запустити кілька процесів. Тому процесу, з яким користувач безпосередньо працює (а це може бути тільки один процес) рівень пріоритету піднімається на двох одиниць (7+2 = 9).

Це робить спілкування з прикладною програмою більш “комфортабельним”. Високий клас пріоритету призначається деяким системним процесам, що простоюють до виникнення визначених подій і, тому, не заважають іншим процесам. Тільки в особливих випадках процес може відноситися до класу Real time.

      1. Рівень пріоритету потоку усередині класу пріоритетів

Ідентифікатор

Рівень пріоритету потоку

THREAD_PRIORITY_LOWEST

На 2 нижче рівні класу

THREAD_PRIORITY_BELOW_NORMAL

На 1 нижче рівня класу

THREAD_PRIORITY_NORMAL

Дорівнює рівню класу

THREAD_PRIORITY_ABOVE_NORMAL

На 1 вище рівня класу

THREAD_PRIORITY_HIGHEST

На 2 вище рівні класу

THREAD_PRIORITY_IDLE

Дорівнює 1 для процесів класу IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, і HIGH_PRIORITY_CLASS і дорівнює 16 для REALTIME_PRIORITY_CLASS

THREAD_PRIORITY_TIME_CRITICAL

Дорівнює 15 для процесів класу IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, і HIGH_PRIORITY_CLASS, і дорівнює 31 для REALTIME_PRIORITY_CLASS

      1. Динамічна зміна рівня пріоритету потоку

Клас пріоритету процесу і рівень пріоритету потоку усередині класу визначають базовий рівень пріоритету потоку. Цей рівень може динамічно змінюватися системою, а саме, підвищуватися на 2 одиниці у відповідь на надходження повідомлень у чергу потоку з наступним зниженням до базового рівня після закінчення визначеного проміжку часу. Це правило діє тільки для потоків до 15 рівня.

Робота з процесами і потоками в Win32® API.

Список функцій

У Win32® API визначені наступні функції роботи з процесами і потоками

Назва функції

Виконувана дія

AttachThreadInput

Переключення механізмів уведення з однієї нитки на іншу

CommandLineToArgv

Робить розбір командного рядка в Unicode

CreateProcess

Створює процес

CreateRemoteThread

Створює потік в адресному просторі іншого процесу

CreateThread

Створює потік

ExitProcess

Завершує процес і весь його потоки

ExitThread

Завершує потік

FreeEnvironmentStrings

Звільняє пам'ять області перемінні середовища

GetCommandLine

Повертає покажчик на командний рядок

GetCurrentProcess

Повертає описувач (handle) поточного процесу

GetCurrentProcessId

Повертає ідентифікатор поточного процесу

GetCurrentThread

Повертає описувач (handle) поточного потоку

GetCurrentThreadId

Повертає ідентифікатор поточного потоку

GetEnvironmentStrings

Повертає рядок перемінні середовища

GetEnvironmentVariable

Повертає значення зазначеного перемінного середовища

GetExitCodeProcess

Повертає код завершення процесу

GetExitCodeThread

Повертає код завершення потоку

GetPriorityClass

Повертає клас пріоритету процесу

GetProcessAffinityMask

Повідомляє, на яких процесорах дозволене виконання процесу

GetProcessShutdownParameters

Повідомляє параметри поводження процесу при завершенні роботи системи

GetProcessTimes

Повертає тимчасові характеристики зазначеного процесу

GetProcessVersion

Повідомляє версію Windows, для якої призначений процес

GetProcessWorkingSetSize

Повертає характеристики доступного процесу адресного простору

GetStartupInfo

Повертає параметри процесу, отримані їм при створенні

GetThreadPriority

Повідомляє пріоритет зазначеного потоку

GetThreadTimes

Повертає тимчасові характеристики зазначеного потоку

OpenProcess

Повертає описувач (handle) зазначеного процесу

ResumeThread

Зменшує лічильник затримок потоку (чи запускає його)

SetEnvironmentVariable

Установлює значення зазначеного перемінного середовища

SetPriorityClass

Установлює клас пріоритету процесу

SetProcessShutdownParameters

Установлює параметри поводження процесу при завершенні роботи системи

SetThreadAffinityMask

Установлює, на яких процесорах дозволене виконання потоку

SetThreadPriority

Установлює пріоритет зазначеного потоку

Sleep

Затримує виконання потоку на зазначену кількість мілісекунд

SleepEx

Затримує виконання до настання події введення/ чивисновку на час

SetProcessWorkingSetSize

Установлює характеристики доступного процесу адресного простору

SuspendThread

Припиняє виконання зазначеного потоку

TerminateProcess

Завершує зазначений процес

TerminateThread

Завершує зазначений потік

TlsAlloc

Розподіляє індекс локальної пам'яті потоку (thread local storage TLS)

TlsFree

Звільняє індекс TLS

TlsGetValue

Повертає дані, розміщені в TLS із зазначеним індексом

TlsSetValue

Поміщає дані в TLS із зазначеним індексом

WaitForInputIdle

Чекає, поки не почнеться введення для зазначеного процесу

WinExec

Виконує зазначений додаток

Докладний опис функцій приведений у Win32® Programmer’s Reference. Тут будуть докладно розглянуті тільки деякі функції.

·  Функція CreateProcess()

Створює новий процес і його первинний потік. Новий процес виконує зазначений файл, що виконується.

Формат функції:

BOOL CreateProcess(

LPCTSTR lpApplicationName, // ім'я файлу, що виконується

LPTSTR lpCommandLine, // командний рядок

LPSECURITY_ATTRIBUTES lpProcessAttributes, // атрибути захисту процесу

LPSECURITY_ATTRIBUTES lpThreadAttributes, // атрибути захисту

// потоку

BOOL bInheritHandles, // прапор спадкування описувачей

DWORD dwCreationFlags, // прапори створення

LPVOID lpEnvironment, // покажчик блоку перемінні середовища

LPCTSTR lpCurrentDirectory, // поточний каталог

LPSTARTUPINFO lpStartupInfo, // блок початкових параметрів

LPPROCESS_INFORMATION lpProcessInformation // покажчик

// структури, що описує породжений // процес

);

Функція повертає TRUE у випадку успіху і FALSE - у випадку невдачі.

Параметри:

lpApplicationName - покажчик на рядок, що містить ім'я програми, що виконується. Ім'я може бути повне. Якщо воно не повне, то пошук файлу виробляється в поточному каталозі. Параметру може бути привласнене значення NULL. У цьому випадку як ім'я файлу виступає перша виділена пробілами лексема з рядка lpCommandLine;

lpCommandLine - покажчик командного рядка. Якщо параметр lpApplicationName має значення NULL, то ім'я файлу, що виконується, виділяється з lpCommandLine, а пошук файлу, що виконується, виробляється відповідно до правил, що діють у системі;

lpProcessAttributes - покажчик на структуру, що описує параметри захисту процесу. Якщо параметру привласнене значення NULL, то встановлюються атрибути “за замовчуванням”;

lpThreadAttributes- покажчик на структуру, що описує параметри захисту первинного потоку. Якщо параметру привласнене значення NULL, то встановлюються атрибути “за замовчуванням”;

bInheritHandles - визначає, чи буде породжений процес успадковувати описувачі (handles) об'єктів батьківського процесу. Наприклад, якщо батьківський процес A уже до цього породжував процес B, те він одержав описувач процесу B і може їм маніпулювати. Якщо тепер він породжує процес C з параметром bInheritHandles рівним TRUE, то і процес C зможе працювати з описувачем процесу B;

dwCreationFlags - визначає деякі додаткові умови створення процесу і його клас пріоритету;

lpEnvironment- покажчик на блок перемінні середовища породженого процесу. Якщо цей параметр дорівнює NULL, то породжений процес успадковує середовище батька. Інакше він повинний указувати на блок рядків, що завершується нулем, кожна з який завершується нулем (аналогічно DOS);

lpCurrentDirectory - покажчик на рядок, що містить повне ім'я поточного каталогу породженого процесу. Якщо цей параметр дорівнює NULL, то породжений процес успадковує каталог батька;

lpStartupInfo - покажчик на структуру STARTUPINFO, що визначає параметри головного вікна породженого процесу;

lpProcessInformation - покажчик на структуру, що буде заповнена інформацією про породжений процес після повернення з функції.

Приклад: програма, що запускає Microsoft® Word

#include <windows.h>

#include <conio.h>

#include <stdio.h>

main()

{ PROCESS_INFORMATION pi ;

STARTUPINFO si ;

ZeroMemory( &si, sizeof(si)) ;

si.cb = sizeof( si ) ;

printf( "Press any key to start WinWord -- " );

getch() ;

CreateProcess( NULL, "WinWord", NULL, NULL, FALSE, 0,

NULL, NULL, &si, &pi ) ;

return 0 ;

}

·  Функція CreateThread()

Створює новий потік в адресному просторі процесу.

Формат функції:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes, // атрибути захисту

// потоку

DWORD dwStackSize, // розмір стека в байтах

LPTHREAD_START_ROUTINE lpStartAddress, //покажчик на функцію

// потоку

LPVOID lpParameter, // аргумент, переданий у функцію

// потоку

DWORD dwCreationFlags, // прапори керування створенням потоку

LPDWORD lpThreadId // область пам'яті для повернення

// ідентифікатора потоку

);

Функція повертає описувач породженого потоку.

Параметри:

lpThreadAttributes - покажчик на структуру, що описує параметри захисту потоку. Якщо параметру привласнене значення NULL, то встановлюються атрибути “за замовчуванням”;

dwStackSize - установлює розмір стека, що приділяється потоку. Якщо параметр дорівнює нулю, то встановлюється стек, рівний стеку первинного потоку;

lpStartAddress - адреса функції, которую буде виконувати потік. Функція має один 32-бітний аргумент і повертає 32 бітне значення;

lpParameter - параметр, переданий у функцію, которую буде виконувати потік;

dwCreationFlags - додатковий прапор, що керує створенням потоку. Якщо цей параметр дорівнює CREATE_SUSPENDED, то потік після породження не запускається на виконання до виклику функціїResumeThread;

lpThreadId - покажчик на 32-бітну перемінну, котрої буде привласнене значення унікального ідентифікатора потоку.

Приклад: програма, що породжує потік

#include <stdio.h>

#include <conio.h>

#include <windows.h>

DWORD WINAPI Output( LPVOID Param )

{ while( TRUE ) {

printf( "A" ) ;

Sleep(100) ;

}

return( 0 ) ;

}

main()

{ HANDLE hThread ;

DWORD ThreadId ;

hThread = CreateThread( NULL, 0, Output, NULL, 0, &ThreadId ) ;

getch() ;

TerminateThread( hThread, 0 ) ;

return(0) ;

}

5. Лекція 5.2. Реалізація процесів і потоків

Реалізація процесів і потоків

Поняття процесу і потоку

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

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

Для опису такого складного динамічного об'єкта ОС підтримує набір структур, головну з яких прийнято називати блоком управління процесом (PCB, Process control block). До складу PCB зазвичай включають:

  • стан, в якому знаходиться процес;
  • програмний лічильник процесу або, іншими словами, адреса команди, яка повинна бути виконана наступною;
  • вміст регістрів процесора;
  • дані, необхідні для планування використання процесора і управління пам'яттю (пріоритет процесу, розмір і розташування адресного простору і т. д.);
  • облікові дані (ідентифікаційний номер процесу, який користувач ініціював його роботу, загальний час використання процесора даним процесом і т. д.);
  • інформацію про пристрої введення-виведення, пов'язаних з процесом (наприклад, які пристрої закріплені за процесом; таблиця відкритих файлів).

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

Потоки

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

Мал. 5.1. Процес з декількома потоками

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

Реалізація процесів

Внутрішній устрій процесів в ОС Windows

У 32-розрядної версії системи у кожного процесу є 4-гігабайтний адресний простір, в якому користувальницький код займає нижні 2 гігабайти (в серверах 3 Гб). У своєму адресному просторі, яке представляє собою набір регіонів і описується спеціальними структурами даних, процес містить потоки, облікову інформацію і посилання на ресурси.

Блок управління процесом (PCB) реалізований у вигляді набору пов'язаних структур, головна з яких називається блоком процесу EPROCESS. Відповідно, кожен потік також представлений набором структур на чолі з блоком потоку ETHREAD. Ці набори даних, за винятком блоків змінних оточення процесу і потоку (PEB і TEB), існують в системному адресному просторі. Спрощена схема структур даних процесу показана на мал. 5.2.

Мал. 5.2. Керуючі структури даних процесу

Блок KPROCESS (на рис. Праворуч), блок змінних оточення процесу (PEB) і структура даних, підтримувана підсистемою Win32 (блок процесу Win32), містять додаткові відомості про об'єкт "процес".

Ідентифікатор процесу кратний чотирьом і використовується в ролі байтового індексу в таблицях ядра нарівні з іншими об'єктами.

Створення процесу

Зазвичай процес створюється іншим процесом викликом Win32-функції CreateProcess (а також CreateProcessAsUser і CreateProcessWithLogonW). Створення процесу здійснюється в кілька етапів.

На першому етапі, що виконується бібліотекою kernel32.dll в режимі користувача, на диску відшукується потрібний файл-образ, після чого створюється об'єкт "розділ" пам'яті для його проектування на адресний простір нового процесу.

На другому етапі виконується звернення до системного сервісу NtCreateProcess для створення об'єкта "процес". Формуються блоки EPROCESS, KPROCESS і блок змінних оточення PEB. Менеджер процесів ініціалізує в блоці процесу маркер доступу (копіюючи аналогічний маркер батьківського процесу), ідентифікатор і інші поля.

На третьому етапі в вже повністю проініціалізуваному об'єкті "процес" необхідно створити первинний потік. Це, за допомогою системного сервісу NtCreateThread, робить бібліотека kernel32.dll.

Потім kernel32.dll посилає підсистемі Win32 повідомлення, яке містить інформацію, необхідну для виконання нового процесу. Дані про процес і потоці поміщаються, відповідно, в список процесів і список потоків даного процесу, потім встановлюється пріоритет процесу, створюється структура, яка використовується тією частиною підсистеми Win32, яка працює в режимі ядра, і т.д.

Нарешті, запускається первинний потік, для чого формуються його початковий контекст і стек, і виконується запуск стартової процедури потоку режиму ядра KiThreadStartup. Після цього стартовий код з бібліотеки C / C ++ передає управління функції main() запускається програма.

функція CreateProcess

Якщо додаток має намір створити новий процес, один з його потоків повинен звернутися до Win32-функції CreateProcess.

BOOL CreateProcess (

  PCTSTR pszApplicationName,

  PTSTR pszCommandLine,

  PSECURITY_ATTRIBUTES psaProcess,

  PSECURITY_ATTRIBUTES psaThread,

  BOOL bInheritHandles,

  DWORD fdwCreate,

  PVOID pvEnvironment,

  PCTSTR pszCurDir,

  PSTARTUPINFO psiStartInfo,

  PPROCESS_INFORMATION ppiProcInfo);

Опис параметрів функції можна подивитися в MSDN.

Формально ОС Windows не підтримує будь-якої ієрархії процесів, наприклад, відносин "батьківський-дочірній". Однак, негласна ієрархія, яка полягає в тому, хто чиїм дескриптором (описувачем) володіє, все ж існує. Наприклад, володіння дескриптором процесу дозволяє впливати на його адресний простір і функціонування. В даному випадку описувач дочірнього процесу повертається створює процесу в складі параметра ppiProcInfo. Хоча він не може бути безпосередньо переданий іншому процесу, проте, є можливість передати іншому процесу його дублікат. Таким шляхом при необхідності в групі процесів може бути сформована потрібна ієрархія.

Приклад програми створення процесу

#include <windows.h>

#include <stdio.h>

void main (VOID)

{

    STARTUPINFO StartupInfo;

    PROCESS_INFORMATION ProcInfo;

    TCHAR CommandLine [] = TEXT ( "sleep");

 

    ZeroMemory (& StartupInfo, sizeof (StartupInfo));

    StartupInfo.cb = sizeof (StartupInfo);

    ZeroMemory (& ProcInfo, sizeof (ProcInfo));

 

    if (! CreateProcess (NULL, // Не використовується ім'я модуля

        CommandLine, // Командний рядок

        NULL, // Дескриптор процесу не успадковується.

        NULL, // Дескриптор потоку не успадковується.

        FALSE, // Установка описателей успадкування

        0, // Немає прапорів створення процесу

        NULL, // Блок змінних оточення батьківського процесу

        NULL, // Використовувати поточний каталог батьківського процесу

        & StartupInfo, // Покажчик на структуру STARTUPINFO.

        & ProcInfo) // Покажчик на структуру інформації про процес.

      )

    

printf ( "CreateProcess failed.");

  

    // Чекати закінчення дочірнього процесу

    WaitForSingleObject (ProcInfo.hProcess, INFINITE);

 

    // Закрити описатели процесу і потоку

    CloseHandle (ProcInfo.hProcess);

    CloseHandle (ProcInfo.hThread);

}

У наведеній програмі ім'я програми передається через другий параметр функції CreateProcess. У прикладі в якості дочірньої програми використовується найпростіша команда sleep, завдання якої - витримати паузу тривалістю 10 секунд.

#include <windows.h>

#include <stdio.h>

void main (VOID)

{

printf ( "Дана програма буде спати протягом 10000 мс \ n ");

Sleep (10000);

}

Виконання обох програм можна проконтролювати за допомогою диспетчера задач.

Завершення процесу може бути здійснено різними способами, наприклад, за допомогою функцій ExitProcess, TerminateProcess. Однак, єдиним способом, що гарантує коректну очистку всіх ресурсів, є повернення управління вхідний функції первинного потоку. Крім перерахованих, в системі є багато корисних функцій, що реалізують API для керування технологічними процесами. Їх повний перелік міститься в MSDN.

При завершенні процесу зіставлений з ним об'єкт ядра "процес" не звільняється до тих пір, поки не будуть закриті всі зовнішні посилання на цей об'єкт.

Реалізація потоків

Стан потоків

Кожен новий процес містить, принаймні, один потік, інші потоки створюються динамічно. Потоки складають основу планування і можуть: виконуватися на одному з процесорів, очікувати події або перебувати в якомусь іншому стані (див. Рис. 5.3 і "Планування потоків").

Мал. 5.3. Стану потоків в ОС Windows (версії Server)

Зазвичай в стані "Готовності" є черга готових до виконання (running) потоків. В даному випадку це стан розпадається на три складових. Це, власне, стан "Готовності (Ready)"; стан "Готовий. Відкладений (Deferred Ready)", що означає, що потік обраний для виконання на конкретному процесорі, але поки не запланований до виконання; і, нарешті, стан "Простоює (Standby)", в якому може перебувати тільки один обраний до виконання потік для кожного процесора в системі.

У стані "Очікування (Waiting)" потік блокований і чекає якої-небудь події, наприклад, завершення операції введення-виведення. При настанні цієї події потік переходить в стан "Готовності". Цей шлях може проходити через проміжне "Перехідний (Transition)" стан в тому випадку, якщо стек ядра потоку вивантажено з пам'яті.

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

Приклад програми, що ілюструє стану потоків

Системний монітор (вкладка "Продуктивність") являє собою зручний засіб спостереження за станами потоків. Пропонується здійснити прогін програми, яка містить тривалі цикли розрахунків і очікування. Наприклад, програма, що обчислює 5 * 107 значень функцій sin (x):

#include <windows.h>

#include <stdio.h>

#include <math.h>

 

VOID main (VOID) {

  int i, N = 50000000;

  double a, b;

  getchar ();

  printf ( "Before circle \ n");

  for (i = 0; i <N; i ++) {

b = (double) i / (double) N;

a = sin (b);

}

printf ( "After circle \ n");

getchar ();

}

Графічне представлення передбачає присвоєння цифрових значень різним станам потоку (наприклад, готовність - 1, виконання - 2, очікування - 5 і т.п.). Результат роботи монітора на однопроцесорній системі для даної програми представлений на рис. 5.4.

Мал. 5.4. Ілюстрація переходу потоку з одного стану в інший

Горизонтальні ділянки зі значенням 5 відповідають очікуванню натискання клавіші введення.

Окремі характеристики потоків

Ідентифікатори потоків, так само як і ідентифікатори процесів, кратні чотирьом, вибираються з того ж простору, що і ідентифікатори процесів, і з ними не перетинаються.

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

Крім стану, ідентифікатора і двох стеків, у кожного потоку є контекст, маркер доступу, а також невелика власна пам'ять для зберігання локальних змінних, наприклад, для запам'ятовування коду помилки. Оскільки процес є контейнером ресурсів всіх вхідних в нього потоків, будь-який потік може отримати доступ до всіх об'єктів свого процесу, незалежно від того, яким потоком даного процесу цей об'єкт створений.

Волокна і завдання

Перемикання між потоками займає досить багато часу, тому для полегшеного псевдопаралелізма в системі підтримуються волокна (fibers). Наявність волокон дозволяє реалізувати власний механізм планування, не використовуючи вбудований механізм планування потоків на основі пріоритетів.

ОС не знає про зміну волокон, для управління волокнами немає системних викликів, однак є виклики WinAPI ConvertThreadToFiber, CreateFiber, SwitchToFiber і т.д.

В системі є також завдання (job object), які забезпечують управління одним або декількома процесами як групою.

Внутрішній устрій потоків

Перейдемо до формального опису потоків. Матеріал цього розділу в рівній мірі відноситься як до звичайних потокам призначеного для користувача режиму, так і до системних потокам режиму ядра.

Подібно процесам, кожен потік має свій блок управління, реалізований у вигляді набору структур, головна з яких - ETHREAD - показана на рис. 5.5.

Мал. 5.5. Керуючі структури даних потоку

Зображені на рис. 5.5 структури, за винятком блоків змінних оточення потоку (TEB), існують в системному адресному просторі. Крім цього, паралельна структура для кожного потоку, створеного в Win32-процесі, підтримується процесом Csrss підсистеми Win32. У свою чергу, частина підсистеми Win32, що працює в режимі ядра (Win32k.sys), підтримує для кожного потоку структуру W32THREAD.

Блок потоку ядра KTHREAD містить інформацію, необхідну ядру для планування потоків і їх синхронізації з іншими потоками. Перегляд структур даних потоку може бути здійснений дебагером.

Створення потоків

Створення потоку ініціюється Win32-функцією CreateThread, яка знаходиться в бібліотеці Kernel32.dll. При цьому створюється об'єкт ядра "потік", який зберігає статистичну інформацію про створений потік. В адресному просторі процесу виділяється пам'ять під користувальницький стек потоку. Потім ініціалізується апаратний контекст потоку (нижче є опис відповідної структури CONTEXT).

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

Функція CreateThread

Первинний потік процесу створюється при виконанні функції CreateProcess, для створення додаткових потоків потрібно викликати функцію CreateThread:

HANDLE CreateThread (

PSECURITY_ATTRIBUTES psa,

DWORD cbStack,

PTHREAD_START_ROUTINE pfnStartAddr,

PVOID pvParam,

DWORD fdwCreate,

PDWORD pdwThreadID);

 

Приклад програми створення потоку

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

#include <windows.h>

#include <stdio.h>

 

DWORD WINAPI MyThread (LPVOID lpParam)

{

 printf ( "Parameter =% d \ n", * (DWORD *) lpParam);

 return 0;

}

 

VOID main (VOID)

{

  DWORD ThreadId, ThreadParameter = 10;

  HANDLE hThread;

  

  hThread = CreateThread (

    NULL, // атрибути безпеки за замовчуванням

    0, // розмір стека за замовчуванням

    MyThread, // покажчик на процедуру створюваного потоку

    & ThreadParameter, // аргумент, який передається функції потоку

    0, // прапори створення за замовчуванням

    & ThreadId); // повертається ідентифікатор потоку

 

if (hThread == NULL) printf ( "CreateThread failed.");

  

    getchar ();

    CloseHandle (hThread);

   }

Завершення потоку можна організувати різними способами, наприклад, за допомогою функцій ExitThread або TerminateThread.

Подібно процесам при завершенні потоку зіставлений з ним об'єкт ядра "потік" не звільняється допоки не будуть закриті всі зовнішні посилання на цей об'єкт.

Контекст потоку, перемикання контекстів

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

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

Ця інформація зберігається в поточному стеку ядра потоку.

Контекст зберігає стан регістрів процесора на момент останнього виконання потоку і зберігається в структурі CONTEXT, визначеної в заголовки WinNT.h. Елементи цієї структури відповідають регістрів процесора, наприклад, для процесорів x86 процесорів в її склад входять Eax, Ebx, Ecx, Edx і т д .. Win32-функція GetThreadContext дозволяє отримати поточний стан контексту, а функція SetThreadContext - задати новий вміст контексту. Перед цією операцією потік рекомендується призупинити.

Крім перерахованих в системі є багато корисних функцій, що реалізують API для керування потоками. Їх повний перелік міститься в MSDN.

Висновок

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