Студопедия  
Главная страница | Контакты | Случайная страница

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатика
ИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханика
ОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторика
СоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансы
ХимияЧерчениеЭкологияЭкономикаЭлектроника

Керування адресним простором під час створення процесів

Читайте также:
  1. III. Розвиток пізнавальних процесів
  2. Адаптація законодавства України до законодавства ЄС - один із важливих інструментів створення в Україні нової правової системи та громадянського суспільства
  3. Активізація державотворчих процесів на західноукраїнських землях та утворення ЗУНР. Об’єднання УНР та ЗУНР.
  4. Апарати для проведення гідромеханічних та механічних процесів.
  5. Апарати для проведення теплових процесів.
  6. БЕЗПЕКА ТЕХНОЛОГІЧНИХ ПРОЦЕСІВ ТА ОБЛАДНАННЯ
  7. БЕЗПЕЧНІСТЬ ТЕХНОЛОГІЧНИХ ПРОЦЕСІВ
  8. Визначити зв’язок хімічного складу і структурної організації м’язів з їх функцією і пояснити послідовність біохімічних процесів у м’язах при їх скороченні і розслабленні.
  9. Головні новоутворення у сфері пізнавальних процесів у підлітковому віці.
  10. Договір на створення і передачу науково-технічної продукції

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

 

Системні виклики fork() і ехес()

У першому підході адресний простір нащадка створюють як точну копію адресного простору предка. Така операція реалізована системним викликом, який у POSIX- системах називають fork().

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

Коли створення нового процесу відбувається шляхом дублювання адресного простору, предка, виникає потреба у спеціальних засобах для завантаження про­грамного коду в адресний простір процесу. Такі засоби реалізує системний ви­клик, який у POSIX-системах називають ехес(). Як параметр для виклику ехес() треба вказувати весь шлях до виконуваного файла програми, який буде заванта­жено у пам’ять.

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

 

Запуск застосування одним системним викликом

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

Можна виділити два етапи виконання такого системного виклику:

¨ виділення пам'яті під адресний простір нового процесу (жодна інформація при цьому з адресного простору предка не копіюється);

¨ завантаження виконуваного коду із зазначеного файла у виділений адресний простір.

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

 

Технологія копіювання під час запису

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

Для розв'язання цієї проблеми в сучасних ОС використовують технологію ко­піювання під час запису (copy-on-write). При цьому під час виклику fork () дані

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

 

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

 

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

 

3.6.4. Особливості завершення процесів

Розглянемо три варіанти завершення процесів.

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

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

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

у POSIX-системах називають ki 11 ().

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

 

3.6.5. Синхронне й асинхронне виконання процесів

Коли у системі з’являється новий процес, для старого процесу є два основних ва­ріанти дій:

¨ продовжити виконання паралельно з новим процесом — такий режим роботи називають асинхронним виконанням;

¨ призупинити виконання доти, поки новий процес не буде завершений, - та­кий режим роботи називають синхронним виконанням. (У цьому разі викори­стовують спеціальний системний виклик, який у POSIX-системах назива­ють waitO.)

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

 

3.6.6. Створення і завершення потоків

Особливості створення потоків

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

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

Можна створювати потоки з коду користувача за допомогою відповідного системного виклику.

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

Під час створення потоку система повинна виконати такі дії.

1. Створити структури даних, які відображають потік в ОС.

2. Виділити пам’ять під стек потоку.

3. Встановити лічильник команд процесора на початок коду, який буде викону­ватися в потоці; цей код називають процедурою або функцією потоку, покаж­чик на таку процедуру передають у системний виклик створення потоку.

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

Локальні змінні функції потоку розташовані у стеку потоку і доступні лише його коду, глобальні змінні доступні всім потокам.

 

Особливості завершення потоків

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

Як і процеси, потоки можуть виконуватися синхронно й асинхронно. Потік, створивши інший потік, може призупинити своє виконання до його завершення. Таке очікування називають приєднанням потоків (thread joining, очікує той, хто приєднує). Після завершення приєднаного потоку потік, який очікував його за­вершення, може дістати статус виконання. Під час створення потоку можна ви­значити, чи підлягає він приєднанню (якщо потік не можна приєднати, його на­зивають від'єднаним — detached). Якщо потік не є від'єднаним (nondetached або joinable, такий потік називатимемо приєднуваним), після завершення його обов'язко­во потрібно приєднувати, щоб уникнути витікання пам'яті.

Дамо деякі рекомендації щодо розробки багатопотокових програм.

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

¨ Для потоків не підтримується така ієрархія, як для процесів. Потік, що ство­рив інший потік, має рівні з ним права. Розраховувати на те, що такий потік сам буде продовжувати своє виконання після завершення потоків-нащадків, немає сенсу.

¨ Стек потоку очищається після виходу із функції потоку. Щоб цьому запобігти, не слід, наприклад, передавати нікуди покажчики на локальні змінні такої функції. Якщо необхідні змінні, значення яких мають зберігатися між викли­ками функції потоку, але при цьому вони не будуть доступні іншим потокам, треба скористатися локальною пам’яттю потоку (Thread Local Storage, TLS) - певним чином організованими даними, доступ до яких здійснюється за допо­могою спеціальних функцій.

 

3.7. Керування процесами в UNIX і Linux

 

3.7.1. Образ процесу

В UNIX-системах образ процесу містить такі компоненти:

¨ керуючий блок процесу;

¨ код програми, яку виконує процес;

¨ стек процесу, де зберігаються тимчасові дані (параметри процедур, повернені значення, локальні змінні тощо);

¨ глобальні дані, спільні для всього процесу.

Для кожного компонента образу процесу виділяють окрему ділянку пам’яті. У розділі 9 буде докладно розглянуто структуру різних ділянок пам’яті процесу та особливості їхнього використання.

 

3.7.2. Ідентифікаційна інформація та атрибути безпеки процесу

Із кожним процесом у системі пов’язана ідентифікаційна інформація.

Ідентифікатор процесу (pid) є унікальним у межах усієї системи, і його вико­ристовують для доступу до цього процесу. Ідентифікатор процесу і пі t завжди до­рівнює одиниці.

Ідентифікатор процесу-предка (ppid) задають під час його створення. Будь- який процес має мати доступ до цього ідентифікатора. Так в UNIX-системах обов’язково підтримується зв’язок «предок-нащадок». Якщо предок процесу Р завершує виконання, предком цього процесу автоматично стає і nit, тому ppid для Р дорівнюватиме одиниці.

Із процесом також пов’язаний набір атрибутів безпеки.

¨ Реальні ідентифікатори користувача і групи процесу (uid, gid) відповідають користувачеві, що запустив програму, внаслідок чого в системі з’явився відпо­відний процес.

¨ Ефективні ідентифікатори користувача і групи процесу (euid, egid) викори­стовують у спеціальному режимі виконання процесу — виконанні з правами власника. Цей режим буде розглянуто у розділі 16.

 

3.7.3. Керуючий блок процесу

Розглянемо структуру керуючого блоку процесу в Linux і відмінності його реалі­зації у традиційних UNIX-системах [58, 95].

Керуючий блок процесу в Linux відображається структурою даних task__struct. До найважливіших полів цієї структури належать поля, що містять таку інформацію:

¨ ідентифікаційні дані (зокрема pid — ідентифікатор процесу);

¨ стан процесу (виконання, очікування тощо);

¨ покажчики на структури предка і нащадків;

¨ час створення процесу та загальний час виконання (так звані таймери процесу);

¨ стан процесора (вміст регістрів і лічильник інструкцій);

¨ атрибути безпеки процесу (uid, gid, euid, egid).

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

Крім перелічених вище, task_struct має кілька полів спеціалізованого призна­чення, необхідних для різних підсистем Linux:

¨ відомості для обробки сигналів;

¨ інформація для планування процесів;

¨ інформація про файли і каталоги, пов'язані із процесом;

¨ структури даних для підсистеми керування пам’яттю.

Дані полів task_struct можуть спільно використовувати декілька процесів спе­ціалізованого призначення, у цьому випадку такі процеси фактично є потоками. Докладніше дане питання буде розглянуте під час вивчення реалізації багатопото­ковості в Linux.

Керуючі блоки процесу зберігаються в ядрі у спеціальній структурі даних. До появи ядра версії 2.4 таку структуру називали таблицею процесів системи; це був масив, максимальна довжина якого не могла перевищувати 4 Кбайт. У ядрі версії 2.4 таблиця процесів була замінена двома динамічними структурами даних, що не мають такого обмеження на довжину:

¨ хеш-таблицею (де в ролі хеша виступає pid процесу), ця структура дає змогу швидко знаходити процес за його ідентифікатором;

¨ кільцевим двозв’язним списком, ця структура забезпечує виконання дій у цик­лі для всіх процесів системи.

Тепер обмеження на максимальну кількість процесів перевіряється тільки все­редині реалізації функції fork () і залежить від обсягу доступної пам’яті (наприк­лад, є відомості [58], що у системі з 512 Мбайт пам’яті можна створити близько 32 000 процесів)

Реалізація керуючого блоку в Linux відрізняється від його традиційної реалі­зації в UNIX-системах. У більшості версій UNIX (System V, BSD) керуючий блок процесу складається з двох структур даних — структури процесу (ргос) і структу­ри користувача (и).

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

 

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

Реалізація fork() в Linux

В UNIX-сумісних системах процеси створює вже відомий нам системний виклик fork(). Розглянемо його реалізацію в Linux.

1. Виділяють пам’ять для нового керуючого блоку процесу (task struct). Якщо пам'яті недостатньо, повертається помилка.

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

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

3. Для процесу генерується ідентифікатор (pid), при цьому використовують спе­ціальний алгоритм, що гарантує унікальність.

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

5. Формують адресний простір процесу.

6. Структуру даних процесу поміщають у список і хеш-таблицю процесів системи.

7. Процес переводять у стан готовності до виконання.

 

Використання fork() у прикладних програмах

Розглянемо приклад використання системного виклику fork О для створення про­цесу. Опис fork () відповідно до POSIX виглядає так:

#іпсіude <unistd.h>

pid_t fork(); // тип pid_t є цілим

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

 

Після створення процесу він може дістати доступ до ідентифікаційної інфорн мації за допомогою системного виклику getpidO, який повертає ідентифікатор поточного процесу, і getppi d(), що повертає ідентифікатор процесу-предка:

#indude <unistd.h>

pid_t mypid, parent_pid;

mypid = getpidO;

parent_pid - getppidO:

 

t

3.7.5. Завершення процесу

і

і

Для завершення процесу в UNIX-системах використовують системний виклик _exit(). Розглянемо реалізацію цього системного виклику в Linux. Під час його виконання відбуваються такі дії.

1. Стан процесу стає TASK_ZOMBIE (зомбі, про цей стан див. нижче).

2. Процес повідомляє предків і нащадків про те, що він завершився (за допомо­гою спеціальних сигналів).

3. Звільняються ресурси, виділені під час виклику fork().

4. Планувальника повідомляють про те, що контекст можна перемикати.

У прикладних програмах для завершення процесу можна використати безпо­середньо системний виклик _ехі t() або його пакувальник — бібліотечну функцію exit (). Ця функція закриває всі потоки процесу, коректно вивільняє всі ресурси і викликає _exit() для фактичного його завершення:

#inc!ude<unistd.h>

void _exit(int status); // status задає код повернення

#include<stdlib.h>

void exit(int status); // status задає код повернення

exit (128); // вихід з кодом повернення 128

Зазначимо, що краще не викликати ехіt () тоді, коли процес може використо­вувати ресурси разом з іншими процесами (наприклад, він є процесом-нащадком, який має предка, причому нащадок успадкував у предка дескриптори ресурсів). Причина полягає в тому, що в цьому разі спроба звільнити ресурси в нащадку призведе до того, що вони виявляться звільненими й у предка. Для завершення таких процесів потрібно використати _exit().


Дата добавления: 2014-12-20; просмотров: 27 | Нарушение авторских прав




lektsii.net - Лекции.Нет - 2014-2021 год. (0.071 сек.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав