Первая страница
Наша команда
Контакты
О нас

    Головна сторінка



“Інформаційні управляючі системи та технології” 080402 “Інформаційні технології проектування” Одеса 2010

“Інформаційні управляючі системи та технології” 080402 “Інформаційні технології проектування” Одеса 2010




Сторінка10/24
Дата конвертації10.03.2017
Розмір1.41 Mb.
1   ...   6   7   8   9   10   11   12   13   ...   24

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

3. 5. 1 Образ процесу


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

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

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

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

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

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

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


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

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

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

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



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

  • Ефективні ідентифікатори користувача і групи процесу (euid, egid) використовують у спеціальному режимі виконання процесу – виконані з правами власника.


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


Розглянемо структуру керуючого блоку процесу в Linux.

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



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

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

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

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

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

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

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

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



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


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

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

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

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

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



  • хеш-таблицею (де в ролі хеша виступає pid процесу), ця структура дає змогу швидко знаходити процес за його ідентифікатором;
    Дина́міка (грец. δύναμις - сила) - розділ механіки,в якому вивчаються причини виникнення механічного руху. Динаміка оперує такими поняттями, як маса, сила, імпульс, момент імпульсу, енергія.


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

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

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


3. 5. 4Створення процесу


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

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



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

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

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

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

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

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

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

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

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

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

#include

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

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

Для нащадка fork() повертає нуль, а для предка – ідентифікатор (pid) створеного процесу-нащадка. Коли з якоїсь причини нащадок не був створений, fork() поверне –1. Тому звичайний код роботи і fork() має такий вигляд:

pid_t pid;

if((pid=fork())==-1)

{/* помилка, аварійне завершення */)}

if(pid==0)

{

// це нащадок



}

else


{

// це предок

printf(“Нащадок запущений з кодом %d\n”,pid);

}

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



#include

pid_t mypid, parent_pid;

mypid=getpid();

parent_pid=getppid();


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


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

  1. Стан процесу сиає TASK_ZOMBIE.

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

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

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

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

#include

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

#include

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

exit(128);

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

3. 5. 6 Очікування завершення процесу


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

Системний виклик waitpid()

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



Синхронне виконання процесів у прикладних програмах

Розглянемо синхронне виконання процесів на базі waitpid(). Відповідно до POSIX цей системний виклик визначається так:

#include

pid_t waitpid(pid_t pid, //pid процесу, який очікуємо

int *status, //інформація про статус завершення нащадка

int options); //задавитемо як 0

Параметр pid можна задавати як 0, що означатиме очікування процесу і з тієї ж групи, до якої належить предок, або як –1, що означатиме очікування будь-якого нащадка. Наведемо приклад реалізації синхронного виконання з очікуванням:

pid_t pid;

if((pid=fork())==-1)

exit(-1);

if(pid==0)

{

//нащадок – виклик exec()



}

else


{

//предок – чекати нащадка

int status;

waitpid(pid,&status,0);

//продовжувати виконання

}

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



Код повернення нащадка отриманий таким чином:

waitpid(pid,&status,0);

if(WIFEXITED(status))

printf(“Нащадок завершився з кодом %d\n”,


WEXITSTATUS(status));

3. 5. 7 Сигнали


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

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



Типи сигналів

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

Активність - поняття, яке визначає темп руху і інтенсивність дій речовин, явищ і живих організмів. Активність визначається в порівнянні
Ці сигнали генерує ядро ОС і негайно

відправляє їх процесу, потік якого викликав помилку.

Асинхронні сигнали процес може отримувати у будь-який момент виконання:


  • програміст може надіслати асинхронний сигнал іншому процесу, використовуючи системний виклик, який у POSIX-системах називають kill(), параметрами цього виклику є номер сигналу та ідентифікатор процесу;
    Пара́метр (від дав.-гр. παραμετρέω) - відмірюю, розмірюю)(рос. параметр, англ. parameter, нім. Parameter m, Kennwert m, Kenngrösse f, Kennzahl f) - величина, що нею характеризують якусь властивість, стан, розмір або форму об'єкта, робочого тіла, процесу, явища або системи тощо.


  • причиною виникнення сигналу може бути також деяка зовнішня подія (натискання користувача на клавіші, завершення процесу-нащадка тощо).

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

Диспозиція сигналів

На надходження сигналу процес може реагувати одним із трьох срособів (спосіб реакції процесу на сигнал називають диспозицією сигналу):



  • викликати оброблювач сигналу;

  • проігнорувати сигнал, який у цьому випадку “зникне” і не виконає жодної дії;

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

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

Блокування сигналів

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



Приклади сигналів

Розглянемо сигнали, що визначені POSIX і підтримуються в Linux (у дужках поруч з ім’ям сигналу наведено його номер).

До синхронних сигналів належить, наприклад, сигнал SIGSEGV (11), який генерує система під час записування в захищену ділянку пам’яті.

До асинхронних сигналів належать:



  • SIGHUP (1) – розрив зв’язку (наприклад, вихід користувача із системи);

  • SIGINT і SIGQUIT (2,3) – сигнали переривання програми від клавіатури (генеруються під час натискання користувачем відповідно Ctrl C і Ctrl \);

  • SIGKILL (9) – негайне припинення роботи програми (для такого сигналу не можна змінювати диспозицію);

  • SIGUSR1 SIGUSR2 (10,12) – сигнали користувача, які можуть використовувати прикладні програми;

  • SIGTERM (15) – пропозиція програмі завершити її роботу (цей сигнал, на відміну від SIGKILL, може бути зігнорований).
    По́пит і пропози́ція (правильно українською: попит і пропонування) - економічна модель, що описує процес ціноутворення на ринку. Ця модель вводить поняття попиту та пропозиції як універсальні характеристики ринку та доводить, що, за умовами певних припущень, ці характеристики урівноважуються та приводять до встановлення певної ціни на даний товар.


Діями за замовчуванням для всіх зазначених сигналів, крім SIGSEGV, є припинення роботи програми (для SIGSEGV додатково генерується дамп пам’яті (core dump) – файл, у якому зберігається образ адресного простору процесу для наступного аналізу).
Задання диспозиції сигналів

Для за дання диспозиції сигналу використовують системний виклик


sigaction().

#include

int sigaction(int signum, //номер сигналу

struct sigaction *action, //нова диспозиція

struct sigaction *old_action); //повернення попередньої диспозиції

Диспозицію описують за допомогою структури sigaction з такими полями:



  • sa_handler – покажчик на функцію-оброблювач сигналу;

  • sa_mask – маска сигналу, що задає, які сигнали будуть заблоковані всередені оброблювача;

  • sa_flag – додаткові прапорці.

Обмежимося обнулінням sa_mask і sa_flag (не блокуючи жодного сигналу):

struct sigaction action={0};

Поле sa_handler має бути задане як покажчик на оголошену раніше функцію, що має такий вигляд:

void user_handler(int signum)

{

//обробка сигналу



}

Ця функція і стає оброблювачем сигналів.

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

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

#include

void sigint_handler(int signum)

{

//обробка SIGINT



}

//……..


action.sa_handler=sigint_handler;

sigaction(SIGINT, &action,0);

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

//задати оброблювачі за допомого за допомогою sigaction()

pause(); //чекати сигналу

Генерування сигналів

Розглянемо, як надіслати сигнал процесу. Для цього використовується системний виклик kill().

#include(signal.h>

int kill(pid_t pid, //ідентификатор процесу

int signum); //номер сигналу

Наприклад, надсилання сигналу SIGHUP процесу, pidякого задано у командному рядку:

kill(atoi(argv[1],SIGHUP);

Оргагізація асинхронного виконання процесів

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

Організа́ція (від грец. ὄργανον - інструмент) - цільове об'єднання ресурсів для досягнення певної мети.
Коли необхідно запустити

процес-нащадок асинхронно, то здається природним не викликати в процесі-предку waitpid()для цього нащадка. Але після завершення процесу-нащадка він перетвориться на процес-зомбі.

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

void clear_zombie(int signum)

{

waitpid(-1,NULL,0);



}

struct sigaction action={0};

action.sa_handler=clear_zombie;

sigaction(SIGCHLD,&action,0);

if((pid=fork())==-1)

_exit();


if(pid==0)

{

// нащадок запущений асинхронно



exit();

}

else



{

// предок не має виклику waitpid()

}

1   ...   6   7   8   9   10   11   12   13   ...   24



  • 3. 5. 2 Ідентифікаційна інформація та атрибути безпеки процесу
  • 3. 5.3 Керуючий блок процесу
  • 3. 5. 4Створення процесу
  • 3. 5. 5 Завершення процесу
  • 3. 5. 6 Очікування завершення процесу
  • 3. 5. 7 Сигнали