5. Ресурси процесу
При виконанні призначених на процес задач часто приходиться записувати дані у файл, відправляти їх на принтер або відображати отримані результати на екран. Процесу можуть знадобитися дані, що вводяться користувачем з клавіатури або які містяться у файлі. Окрім того, процеси у якості ресурсу можуть використовувати інші процеси, наприклад, підпрограми. Підпрограми, файли, семафори, мьютекси, клавіатура та екран дисплеїв – все це приклади ресурсів, які може запросити процес. Під ресурсом розуміємо все, що використовує процес в довільний заданий час в якості джерела даних, засобів обробки, обчислень або відображення інформації.
Щоб процес отримав доступ до ресурсів,
він спочатку повинен зробити запит,
звернувшись з ним до операційної системи.
Якщо ресурс вільний, операційна
система дозволяє процесу його використати.
Після використання ресурсу процес
звільняє його, щоб він був доступним
для інших процесів. Якщо ресурс
недоступний, запит відхиляється, і
процес повинен почекати його звільнення.
Як тільки ресурс стане доступним, процес
активується. Такий основний підхід до
розподілення ресурсів між процесами.
На рис. 3 подано граф розподілу ресурсів,
за яким можемо зрозуміти, які процеси
утримують ресурси, а які їх очікують.
Так, процес В робить запит на ресурс 2,
який утримується процесом С. П
роцес
С робить запит на ресурс 3,який утримується
процесом D.
Якщо задовольняється відразу декілька запитів на отримання доступу до ресурсу, даний ресурс є використовується спільно, або поділяється (дана ситуація також відображена на рис. 3). Процес А поділяє ресурс R1 з процесом D. Ресурси, які поділяються, можуть позволяти паралельний доступ відразу декількох процесів або дозволяти доступ тільки одному процесу на протязі обмеженого проміжку часу, після чого аналогічним доступом зможе скористатися інший процес. Прикладом такого типу ресурсів які поділяються може бути процесор. Спочатку процесор призначається одному процесу на протязі короткого інтервалу часу, а потім процесор отримує інший процес. Якщо задовольняється тільки один запит на отримання доступу до ресурсу, і це відбувається після того, як ресурс звільнить інший процес, такий ресурс є не використовується спільно, а про процес говорять, що він має монопольний доступ (exclusive access) до ресурсу. В багатопроцесорному середовищі важливо знати, який доступ можемо організувати до ресурсу, який поділяється: паралельний або послідовний (передаючи естафету почергово від ресурсу до ресурсу). Це дозволить уникнути капканів, які властиві паралелізму.
Одні ресурси можуть змінюватися або модифікуватися процесами, а інші – ні. Поведінка ресурсів які поділяються і можуть бути модифіковані і не модифіковані визначається типом ресурсів.
Граф розподілу ресурсів
Графи розподілу ресурсів – це напрямлені графи, які показують, як розподіляються ресурси в системі. Такий граф складається з множини вершин V та множини ребер Е. Множина вершин ділиться на дві категорії:
P = { P1, P2, . . ., Pn }
R = { R1, R2, . . ., Rn }
Множина Р – це множина всіх процесів, R – це множина всіх ресурсів в системі. Ребро, напрямлене від процесу до ресурсу, називаємо ребром запиту, а ребро, напрямлене від ресурсу до процесу, називаємо ребром призначення. Напрямлені ребра позначаються наступним чином:
Pi Rj Ребро запиту: процес Pi запитує екземпляр типу ресурс Rj
Rj Pi Ребро призначення: екземпляр типу ресурсу Rj виділено процесу Pi
Кожен процес у графі розподілу ресурсів відображене колом, а кожен ресурс – прямокутник. Оскільки може бути багато екземплярів одного типу ресурсу, то кожен з них є крапкою в середині прямокутника. Ребро запиту вказує на периметр прямокутника ресурсу, а ребро призначення бере початок з крапки і доходить до кола процесу.
Граф розподілу ресурсів, подано на рис. 3, показує наступне.
Множина P, R і E
P = { Pа, Pb, Pc, Pd }
R = { R1, R2, R3 }
E = { R1 Pа, R1 Pd, Pb R2, R2 Pc, Pc R3, R3 R3 }
5.1. Типи ресурсів
Існує три основних типи ресурсів: апаратні, інформаційні та програмні. Апаратні ресурси є фізичними пристроями, підключеними до комп’ютера (наприклад: процесори, основна пам'ять і всі пристрої вводу-виводу, включаючи принтер, жорсткий диск, накопичувачі на магнітній стрічці, дисковод з zip-архівом, монітори, клавіатура, звукові, мережеві і графічні карти, а також модеми). Всі ці пристрої можуть спільно використовувати декілька процесів.
Деякі апаратні ресурси призупиняються (перериваються), щоб дозволити доступ до них різних процесів. Наприклад, призупинення процесора дозволяє різним процесам виконуватися почергово. Оперативний запам’ятовуючий пристрій, або ОЗП (RAM), - це ще один приклад ресурсу, який поділяється засобами призупинення. Коли процес не виконується, деякі сторінкові блоки, які він не займає, можуть бути вивантажені на допоміжні запам’ятовуючі пристрої, а на їх місце завантажені дані, які належать іншому процесу. В довільний момент часу весь діапазон пам’яті може бути зайнятим сторінковими блоками тільки одного процесу. Прикладом ресурсу який поділяється, але ресурсу, який не призупиняється, це наприклад принтер. При сумісному використанні принтера завдання, які посилають на друк процеси, зберігаються в черзі. Кожне завдання друкується до закінчення, і тільки потім починається виконання наступного завдання. Принтер не призупиняється ні одним завданням, що чекає, якщо не відміняється біжуче завдання.
Інформаційні ресурси – до них зараховуємо дані (наприклад, об’єкти), системні дані (наприклад, змінні середовища, файли і дескриптори) а також глобально визначені змінні, як семафори і мьютекси, - є ресурсами, які поділяються, і які можуть бути модифіковані процесом. Звичайні файли і файли, пов’язані з фізичними пристроями (наприклад, принтер), можуть відкриватися з врахуванням обмежуючого доступу зі сторони процесів. Іншими словами, процеси можуть володіти правом доступу тільки до читання, або тільки для запису, або для читання та запису. Дочірній процес успадковує ресурси батьківського процесу і права доступу до них, існуючі на момент створення процесу-нащадка. Дочірній процес може перемістити файловий вказівник, закрити, модифікувати або перезаписати зміст файлу, відкритого батьком. Доступ до файлів і пам’яті, які використовуються спільно з дозволом запису, повинно бути синхронізовано. Для синхронізації доступу до ресурсів даних, що поділяються, можемо використати такі засоби, як семафори і мьютекси.
Бібліотеки, що поділяються можуть бути прикладом програмних ресурсів. Бібліотеки, що поділяються є колекцією функцій для процесів. Процеси можуть також спільно використовувати додатки, програми та утиліти. В цьому випадку в пам’яті знаходиться тільки одна копія програмного коду, наприклад, додатку. При цьому повинні існувати окремі копії даних, по одній для кожного користувача (процесу). До незмінного програмного коду (який повторно використовується - реентерабельний) можуть отримати доступ декілька процесів одночасно.
5.2. POSIX-функції для встановлення обмежень доступу до ресурсів
В бібліотеці POSIX визначені функції, які обмежують можливості процесу по використанню визначених ресурсів. Так, операційна система встановлює обмеження на можливості процесу по використанню системних ресурсів, а власне:
розмір стеку процесу;
розмір створюваного файлу і файлу ядра;
об’єм часу ЦП, виділеного процесу (розмір кванту часу);
об’єм пам’яті, що використовується процесом;
кількість дескрипторів відкритих файлів.
Операційна система встановлює жорсткі обмеження на використання ресурсів процесом. Процес може встановити або змінити м’які обмеження ресурсів, але це значення не може перейти жорстку межу, встановлену операційною системою. Процес може понизити свою жорстку межу, але його значення не повинне перейти м’якої межі. Операція по зниженню процесом своєї жорсткої межі є незворотною . ЇЇ можуть підвищити тільки процеси, які мають спеціальні привілеї.
Синопсис
# include < sys/resource.h >
int setrlimit (int resource, const struct rlimit *rlp);
int getrlimit (int resource, struct rlimit *rlp);
int getrusage (int who, struct rusage *r_usage);
Функція setrlimit () використовується для встановлення обмежень на використання заданих ресурсів. Дана функція дозволяє встановити як жорстку так і м’яку межі. Параметр resource подає тип ресурсу. Значення типів ресурсів (та їх опис) подано в табл. 4. Жорсткі та м’які межі заданого ресурсу подаються параметром rlp, який вказує на структуру rlimit, яка містить два об’єкти типу rlim_t.
іекгсе rlimit
{
rlim_t rlim_cur;
rlim_t rlim_max;
};
Тип rlim_t – це цілий тип без знаку. Елемент rlim_cur містить значення біжучої, або м’якої межі, а елемент rlim_max – значення максимуму, або жорсткої межі. Елементам rlim_cur і rlim_max можемо надати довільні значення, а також символьні константи, визначені в оголошенні < sys/resource.h >.
RLIM_INFINITY |
Відсутні обмеження |
RLIM_SAVED_MAX |
Непередбачувана жорстка межа, яка зберігається |
RLIM_SAVED_CUR |
Непередбачувана м’яка межа, яка зберігається |
Як жорстку, так і м’яку межі можемо встановити значенню RLIM_ INFINITY, що говорить – ресурс необмежений.
Таблиця 4. |
Значення параметру resource |
|
Визначення ресурсу |
Опис |
|
RLIMIT_CORE |
Максимальний розмір файлу ядра в байтах, який може бути створено процесом |
|
RLIMIT_CPU |
Максимальний об’єм часу ЦП в секундах, який може бути використаний процесом |
|
RLIMIT_DATA |
Максимальний розмір розділу даних процесу в байтах |
|
RLIMIT_FSIZE |
Максимальний розмір файлу в байтах, який може бути створено процесом |
|
RLIMIT_NOFILE |
Збільшене на одиницю максимальне значення, яке система може призначити знову створеному дескриптору файлу |
|
RLIMIT_STACK |
Максимальний розмір стеку процесу в байтах |
|
RLIMIT_AS |
Максимальний розмір доступної пам’яті процесу в байтах |
|
Функція getrlimit () повертає значення м’якої та жорсткої межу заданого ресурсу в об’єкт rlp. Обидві функції повертають значення 0 при успішному завершенні і число -1 в іншому випадку. Приклад встановлення м’якої межі для розміру файлів в байтах подано в лістінгу 3.
Лістінг 3. Використання функції setrlimit () для встановлення м’якої межі
для розміру файлу
# include < sys/resource.h >
// . . .
struct rlimit R_limit;
struct rlimit R_limit_values;
// . . .
R_limit.rlim_cur = 2000;
R_limit.rlim_max = RLIM_SAVED_MAX;
setrlimit (RLIMIT_FSIZE, R_limit);
getrlimit (RLIMIT_FSIZE, R_limit_values);
cout << “мяка межа для розміру файлів:”
<< R_limit_values.rlim_cur
<< endl;
// . . .
В лістінгу 3 м’яка межа для розміру встановлюється рівною 2000 байт, а жорстка межа – максимально можливому значенню. Функції setrlimit () передаються значення RLIMIT_FSIZE і R_limit, а функції getrlimit () – значення RLIMIT_FSIZE і R_limit_values. Після їх виконання на екран виводиться встановлене значення м’якої межі.
Функція getrusage () повертає інформацію про використання ресурсів процесом, що викликає. Вона також повертає інформацію про дочірній процес, завершення якого очікує процес, який викликає. Параметр who може мати наступні значення:
RUSAGE_SELF
RUSAGE_CHILDREN
Якщо параметру who передано значення RUSAGE_SELF, то інформація, яка повертається, біде належати (відноситися) до процесу, що викликає. Якщо ж параметр who містить значення RUSAGE_CHILDREN, то інформація, яка повертається буде належати (відноситися) до нащадка процесу, який викликає. Якщо, процес який викликає, не очікує завершення свого нащадка, інформація, пов’язана з ним, відкидається (не враховується). Інформація, яка повертається передається через параметр r_usage, який вказує на структуру rusage. Дана структура містить елементи, подані в таблиці 5. При вдалому виконанні функція повертає число 0, в іншому випадку – число -1.
Таблиця 5. Елементи структури rusage |
|
Елемент структури |
Опис |
struct timevai ru_utime |
Час, затрачений користувачем |
struct timevai ru_sutime |
Час, використаний системою |
long ru_maxrss |
Максимальний розмір, встановлений для резидентної програми |
long ru_maxixrss |
Розмір пам’яті, яка поділяється |
long ru_maxidrss |
Розмір області даних, що не поділяються |
long ru_maxisrss |
Розмір області стеків, що не поділяється |
long ru_minflt |
Кількість запитів на сторінці |
long ru_majflt |
Кількість помилок через відсутність сторінок |
long ru_nswap |
Кількість (перекачек) сторінок |
long ru_inblock |
Блочні операції по вводу даних |
long ru_oublock |
блочні операції по виводу даних |
long ru_msgsnd |
Кількість відправлених повідомлень |
long ru_msgrcv |
Кількість отриманих повідомлень |
long ru_nsignals |
Кількість отриманих сигналів |
long ru_nvcsw |
Кількість передбачених перемикань контексту |
long ru_nivcsw |
Кількість вимушених перемикань контексту |
5.3.Асинхронні і синхронні процеси
Асинхронні процеси виконуються незалежно один від одного. Це означає, що процес А буде виконуватися до завершення незалежно до процесу В. Між асинхронними процесами можуть бути прямі родинні (батько-донька) відносини, а можуть і не бути. Якщо процес А створює процес В, вини обидва можуть виконуватися незалежно, але в деякий момент батько повинен отримати статус завершення дочірнього процесу. Якщо між процесами немає прямих родинних відносин, у них може бути загальний предок.
Асинхронні процеси можуть виконуватися
послідовно, паралельно або з перекриттям.
Ці сценарії подано на рис. 4. В ситуації
1 до кінця виконується процес А, потім
процес В і тільки потім виконується до
кінця процес С. Це і є послідовне виконання
процесів. В ситуації 2 процеси виконуються
одночасно. Процеси А і В – активні
процеси. Під час виконання процесу А
процес В знаходиться в стані очікування.
На протязі деякого інтервалу часу обидва
процеси перебувають в режимі
очікування. Потім процес В просинається,
причому раніше процесу А, а через деякий
час просинається
і процес А, і тепер обидва процеси
виконуються одночасно. Дана ситуація
показує, що обидва процеси можуть
виконуватися одночасно на певному
інтервалі часу. В ситуації 3 викон
ання
процесів А і В перекриваються.
Асинхронні процеси можуть спільно використовувати наступні ресурси: файли і пам'ять. Це може вимагати (або не вимагати) синхронізації або взаємодії при розподілі ресурсів. Якщо процеси виконуються послідовно (ситуація 1), то вони не потребують ніякої синхронізації. Наприклад, всі три процеси А, В і С можуть поділяти деяку глобальну змінну. Процес А (перед тим як завершиться) записує значення в цю змінну, потім процес В під час свого виконання зчитує дані, що зберігаються в змінній і (перед тим як завершитися) записує до неї своє значення. Потім під час свого виконання процес С зчитує дані з цієї змінної. Але в ситуації 2 і 3 процеси можуть спробувати одночасно модифікувати цю змінну, тому тут необхідна синхронізація доступу до змінної.
Ми визначаємо синхронні процеси як процеси, що виконуються почергово, коли один процес призупиняє своє виконання до тих пір, доки не завершиться другий. Наприклад, процес А, батьківський, при виконанні створює процес В, дочірній. Процес А призупиняє своє виконання до тих пір, доки не завершиться процес В. Після завершення процесу В його вихідний код розміщується в таблицю процесів. Тим самим процес А отримує повідомлення про завершення процесу В. Процес А може продовжити виконання, а потім завершиться або завершиться відразу. В цьому випадку виконання процесів А і В є синхронізованим. Сценарій синхронного виконання процесів А і В (для порівняння) також показано на рис. 4.
5.4. Створення синхронних та асинхронних процесів за допомогою функцій fork (), exec (), system () та posix_spawn ()
Функції fork (), fork-exec і posix_spawn () дозволяють реалізувати асинхронні процеси. При використанні функції fork () дублюється образ батьківського процесу. Після створення дочірнього процесу ця функція повертає батьку (через параметр) ідентифікатор (PID) процесу-нащадка і (звичайним шляхом) число 0, що означає – створення процесу пройшло успішно. При цьому батьківський процес не призупиняється; обидва процеси продовжують виконуватися незалежно від інструкції, яка знаходиться безпосередньо за викликом функції fork (). При створенні дочірнього процесу засобами fork-exec комбінації його образ ініціюється за допомогою образу нового процесу. Якщо функція exec () виконалася успішно (тобто успішно пройшла ініціалізація), то вона не повертає батьківському процесу ніякого значення. Функції posix_spawn () створюють образ дочірніх процесів та ініціюють їх. Окрім ідентифікатора (PID), що повертається через параметр функції posix_spawn () батьківському процесу, звичайним шляхом повертається значення, яке є індикатором успішного продовження процесу. Після виконання функції posix_spawn () обидва процеси виконуються одночасно. Функція system () дозволяє створювати синхронні процеси. При цьому створюється оболонка, яка виконує системну команду або запускає файл, що виконується. В цьому випадку батьківський процес призупиняється до тих пір, доки не завершиться дочірній процес і функція system () не поверне значення.
5.4. Функція wait ()
Асинхронний процес, викликавши функцію wait (), може призупинити виконання до тих пір, доки не завершиться дочірній процес. Після завершення дочірнього процесу батьківський процес, який очікує, зчитує статус завершення свого нащадка, щоб не допустити створення процесу-зомбі. Функція wait () отримує статус завершення з таблиці процесів. Параметр status вказує на область, яка містить статус завершення дочірнього процесу. Якщо батьківський процес має не один, а декілька дочірніх процесів і деякі з них вже завершилися, функція wait () зчитує з таблиці процесів статус завершення тільки для одного дочірнього процесу. Якщо інформація про статус виявиться доступною ще до виконання функції wait (), ця функція завершиться миттєво. Якщо батьківський процес не має ні одного потомка, ця функція повертає код помилки. Функцію wait () можемо використати також в тому випадку, коли процес, що викликає повинен очікувати до тиж пір, доки не отримає сигнал, щоб потім виконати певні дії по його обробці.
Синопсис
# include < sys/wait/h >
pid_t wait (int *status);
pid_t waitpid (pid_t pid, int *status, int oрtions);
Функція waitpid () аналогічна функції wait () за виключенням того, що вона приймає додаткові параметри pid і oрtions. Параметр pid задає множину дочірніх процесів, для яких зчитується параметр завершення. Іншими словами, значення параметра pid визначає, які процеси попадуть в дану множину.
pid > 0 |
Один дочірній процес |
pid = 0 |
Довільний дочірній процес, груповий ідентифікатор якого співпадає з ідентифікатором процесу, який викликає |
pid < -1 |
Довільні дочірні процеси, груповий ідентифікатор яких рівний абсолютному значенню pid |
pid = -1 |
Довільні дочірніпроцеси |
Параметр oрtions визначає, як повинно відбуватися очікування процесу, і може приймати одне із значень наступних констант, визначених в оголошення < sys/wait/h >:
WCONTINUED |
Повертає статус завершення довільного дочірнього процесу (заданого параметром pid), про статус не було повідомлено з моменту його зупинки |
WUNTRACED |
Повертає статус завершення довільного зупиненого дочірнього процесу (заданого параметром pid), про статус не було повідомлено з моменту його зупинки |
WNOHANG |
Процес, який викликає, не призупиняється, якщо статус завершення дочірнього процесу недосяжний. |
Ці константи можуть бути об’єднані за допомогою логічної операції АБО і передані в якості параметра oрtions (наприклад WCONTINUED WUNTRACED).
Обидві дані функції повертають ідентифікатор (PID) дочірнього процесу, для якого отримано статус завершення. Якщо значення, які містяться в параметрі status, рівне числу 1, це значить, що дочірній процес завершився при наступних умовах:
процес повернув значення 0 з функції main ();
процес викликав деяку версію функції exit () з аргументом 0;
процес був завершений, оскільки завершився останній потік процесу.
В табл. 6 подано макроси, які дозволяють обчислити значення статусу завершення.
Таблиця 6. |
Макроси, які дозволяють обчислити значення статусу завершення |
|
Макрос |
Опис |
|
WIFEXITED |
Приводиться до нульового значення, якщо статус було повернено нормально завершеним дочірнім процесом |
|
WEXITSTATUS |
Якщо значення WIFEXITED виявляється ненульовим, то оцінюються молодші 8 біт аргументу status, переданого завершеним дочірнім процесом функції _exit () або exit (), або значення, повернуте функцією main () |
|
WIFSIGNALED |
Приводиться до ненульового значення, якщо статус було повернено від дочірнього процесу, який завершився, оскільки йому було відправлено сигнал, але цей сигнал не було перехоплено |
|
WTERMSIG |
Якщо значення WIFSIGNALED виявляється не нульовим, то оцінюється номер сигналу, який став причиною завершення дочірнього процесу |
|
WIFSTOPPED |
Приводиться до ненульового значення, якщо статус було повернено від дочірнього процесу, який в даний момент зупинено |
|
WSTOPSIG |
Якщо значення WIFSTOPPED виявляється ненульовим, то оцінюється номер сигналу, який став причиною зупинки дочірнього процесу |
|
WIFCONTINUED |
Приводиться до ненульового значення, якщо статус було повернуто від дочірнього процесу, який продовжив виконання після сигналу зупинки, прийнятого від блоку управління завданнями |
|
