Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Сигнали як засіб взаємодії процесів↑ Стр 1 из 4Следующая ⇒ Содержание книги
Поиск на нашем сайте
Лабораторна робота 4 Процеси та сигнали ОС Linux
Мета: навчитись використовувати комунікацію між процесами за допомогою сигналів та семафорів
Теоретичні відомості Процес – це завдання в ході його виконання. Він являє собою образ програми, що виконується та включає відображення в пам’яті виконуваного файла, отриманого в ході компіляції, стек, код і дані бібліотек, а також ряд структур даних ядра, необхідних для керування процесом. Виконання процесу полягає в точному проходженні набору інструкцій. Він зчитує і записує інформацію в розділ даних і у стек, але йому недоступні дані і стеки інших процесів. ОС Linux є багатозадачною системою, тому в ній паралельно виконується безліч процесів, їхнє виконання планується ядром. Кілька процесів можуть бути екземплярами однієї програми. Процеси взаємодіють із іншими процесами і з обчислювальними ресурсами тільки за допомогою звертань до ОС, що ефективно розподіляє системні ресурси між активними процесами. Виконання процесу Виконання процесу здійснюється ядром. Підсистема керування процесами відповідає за синхронізацію процесів, їх взаємодію, розподіл пам’яті та планування виконання процесів. З практичної точки зору процес у системі Linux є об’єктом, створюваним у результаті виконання системного виклику fork. Кожен процес, за винятком нульового, породжується в результаті запуску іншим процесом операції fork. Процес, що запустив операцію fork, називається батьківським, а створений процес – породженим. Кожен процес має одного батька, але може породити багато процесів. Ядро системи ідентифікує кожен процес за його номером, що називається ідентифікатором процесу (PID). Нульовий процес є особливим процесом, що створюється “вручну” у результаті завантаження системи. Процес 1, відомий під ім’ям init, є предком будь-якого іншого процесу в системі та пов’язаний з кожним процесом. Ядро завантажує файл в пам’ять при виконанні системної операції exec, при цьому завантажений процес складається щонайменше із трьох частин, так званих областей: коду, даних і стека. Процес у системі Linux може виконуватися у двох режимах – ядра або задачі. У режимі задачі процес виконує інструкції прикладної програми, системні структури даних йому недоступні. Коли процес виконує спеціальну інструкцію (системний виклик), він перемикається в режим ядра. Кожні системній операції відповідає точка входу в бібліотеці системних операцій. Відповідно й образ процесу складається із двох частин: даних режиму ядра і режиму задачі. Коли процес запускає системну операцію exec, ядро системи виділяє області під її код, дані та стек. Якщо процес запускає операцію fork, ядро робить фізичне копіювання адресного простору процесу-батька, не дозволяючи процесам спільно використати ці області. Якщо процес запускає операцію exit, ядро звільняє області, які використалися процесом. Linux є системою з розподілом часу, це означає, що кожному процесу обчислювальні ресурси виділяються на обмежений проміжок часу, після чого вони надаються іншому процесу. Максимальний часовий інтервал, на який процес може захопити процесор, називається часовим квантом. Таким чином, створюється ілюзія того, що процеси працюють одночасно, хоча в дійсності на однопроцесорній машині одночасно може виконуватися тільки один процес. Процеси висувають різні вимоги до системи з погляду їхнього планування і загальної продуктивності. Можна виділити три основних класи додатків: – інтерактивні додатки (командні інтерпретатори, редактори та ін.). Більшу частину часу вони проводять, чекаючи введенні даних від користувача, але для них критичний час відгуку (реакції системи на введення даних); – фонові додатки (не потребують втручання користувача). Основний показник ефективності для них – мінімальний сумарний час виконання в системі; – додатки реального часу. Вони звичайно прив’язані до таймера і вимагають гарантованого часу здійснення тієї або іншої операції та часу відгуку. В основі планування виконання процесів лежать два поняття: квант часу і пріоритет. Кожен процес має два атрибути пріоритету: поточний (на підставі якого здійснюється планування) і відносний, котрий задається при породженні процесу і впливає на поточний. Номери пріоритетів розбиваються на кілька груп: для процесів у режимі завдання, у режимі ядра, для процесів реального часу (групи зазначені відповідно до підвищення пріоритету). Обробник переривань від таймера, зокрема, перевіряє завершення часового кванта для процесів і перераховує пріоритети процесів: чим довше процес займає процесор, тим нижче (у межах групи) стає його пріоритет. Виконання процесу може бути перервано: а) планувальником процесів після закінчення часового кванта або в тому випадку, якщо в черзі готових до виконання процесів є процес із більш високим пріоритетом. б) ядром системи, якщо процес очікує недоступного ресурсу або закінчення тривалої операції вводу (виводу). У режимі ядра пріоритет процесу підвищується для того, щоб його виконання не могло бути перерване, тому що це може призвести до порушення цілісності структур даних ядра. Таким чином, виконання системних викликів здійснюється в безперервному режимі (за винятком деяких апаратних переривань). Контекст процесу Під контекстом процесу розуміється вся інформація для опису процесу. Контекст процесу складається з декількох частин: – адресний простір процесу в режимі задачі (код, дані і стек процесу, а також поділювана пам’ять і дані динамічних бібліотек); – керуюча інформація (структури proc та user – запис таблиці процесів і додаткова інформація відповідно); – оточення процесу (системні змінні, наприклад, домашній каталог, ім’я користувача та ін.); – апаратний контекст (значення використовуваних машинних регістрів). Код операцій системи та її глобальні інформаційні структури спільно використовуються всіма процесами, але не є складовою частиною контексту процесу. Прийнято говорити, що при запуску процесу система виконується в контексті процесу. Коли ядро системи вирішує запустити інший процес, воно виконує перемикання контексту для того, щоб система виконувалася в контексті іншого процесу. Ядро здійснює перемикання контексту тільки за певних умов. Виконуючи перемикання контексту, ядро зберігає інформацію, достатню для того, щоб пізніше перемкнутися знову на перерваний процес і відновити його виконання. Аналогічним чином, при переході з режиму задачі в режим ядра, ядро системи зберігає інформацію, достатню для того, щоб пізніше повернутися в режим задачі та продовжити виконання з перерваного місця. Однак, перехід з режиму задачі в режим ядра є зміною режиму, але не перемиканням контексту. Ядро міняє режим виконання з режиму задачі на режим ядра і навпаки, залишаючись у контексті одного процесу. Ядро обробляє переривання в контексті перерваного процесу, навіть коли процес не викликав ніякого переривання. Перерваний процес може при цьому виконуватися як у режимі задачі, так і у режимі ядра. Ядро зберігає інформацію, достатню для того, щоб можна було пізніше відновити виконання перерваного процесу, і обробляє переривання в режимі ядра. Ядро не породжує і не планує породження якогось особливого процесу з обробки переривань.
Стани процесу Час життя процесу можна розділити на кілька станів, кожний з яких має певні характеристики, що описують процес. Перелічимо основні зі станів: 1) процес виконується; 2) процес не виконується, але готовий до виконання, перебуває в черзі готових до виконання процесів і чекає, коли планувальник вибере його. Природно, у цьому стані може перебувати багато процесів, і алгоритм планування встановлює, який із процесів буде виконуватися наступним. 3) процес припинений (заблокований). Процес блокується, коли він не може більше продовжувати виконання, наприклад, коли чекає завершення вводу (виводу) або звільнення якого-небудь зайнятого ресурсу. Оскільки процесор у кожен момент часу виконує тільки один процес, у стані “процес виконується” може перебувати тільки один процес. Перераховані стани процесу дають статичне подання про процеси, однак процеси безупинно переходять зі стану в стан відповідно до певних правил. Діаграма переходів являє собою орієнтований граф, вершини якого являють собою стани, у які може перейти процес, а дуги – події, що є причинами переходу процесу з одного стану в інший. Перехід між двома станами дозволений, якщо існує дуга з першого стану в другий. Кілька дуг може виходити з одного стану, однак процес переходить тільки по одній з них залежно від того, яке подія відбулася в системі. На pис. 4.1 представлена діаграма переходів для перерахованих станів.
Рис. 4.1 – Стани процесу та переходи між ними Таблиця 4.1 – Назва і призначення сигналів в ОС Linux
Причини виникнення сигналів для різних версій можуть відрізнятися; спочатку вони були обумовлені архітектурними особливостями ЕОМ PDP-11. Причини виникнення сигналів. Виникнення сигналів можна класифікувати в такий спосіб: – введення користувачем керуючого символу з термінала всім процесам, асоційованим з цим терміналом (SIGINT, SIGQUIT, SIGHUP); – виникнення аварійної ситуації при функціонуванні процесу користувача (SIGILL, SIGTRAP, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE); – виникнення непередбаченої або такої, що не піддається іден-тифікації події (SIGTERM, SIGCLD, SIGPWR); – виникнення деякої заздалегідь описаної події (SIGALRM). Посилка сигналів виробляється процесами – один одному, за допомогою функції kill або ядром. Для кожного процесу визначений бінарний вектор, довжина якого дорівнює кількості сигналів у системі. При одержанні процесом сигналу I відповідний i -й розряд цього вектора стає рівним 1. Кожному сигналу відповідає адреса функції, що буде викликана для обробки даного сигналу.
Обробка сигналів. Ядро обробляє сигнали в контексті того процесу, що одержує їх, тому щоб обробити сигнали, потрібно запустити процес Існує три способи обробки сигналів: – реакція за замовчуванням; – ігнорування сигналу; – виконання функції користувача після її отримання. Реакцією за замовчуванням з боку процесу, що виконується в режимі ядра, звичайно є виклик функції exit(), тобто завершення процесу. Але разом з тим реакція процесу на прийнятий сигнал залежить від того, як сам процес визначив своє поводження у випадку прийому цього сигналу: процес може проігнорувати сигнал, викликати на виконання інший процес і т.д. При цьому спосіб обробки сигналів одного типу не впливає на обробку сигналів інших типів. Обробляючи сигнал, ядро визначає тип сигналу і очищає (гасить) розряд у записі таблиці процесів, що відповідає цьому типу сигналу. Таким чином, коли процес одержує будь-який не ігнорований ним сигнал (за винятком SIGILL та SIGTRAP), ОС LINUX автоматично відновлює реакцію “за замовчуванням” на всяке наступне одержання цього сигналу. Якщо необхідно виконати багаторазову обробку того самого сигналу, процес повинен щораз здійснювати системний виклик signal для встановлення необхідної реакції на цей сигнал. Процес не може дізнатись скільки однотипних сигналів ним було отримано. У тому випадку, якщо процес не встигає обробити всі сигнали, що надійшли, відбувається втрата інформації. Якщо процес одержує сигнал SIGINT, що було вирішено ігнорувати (signal (SIGINT,SIG_IGN)), виконання процесу триває так, немов сигналу й не було. Оскільки ядро не скидає значення відповідного поля, що свідчить про необхідність ігнорування сигналу даного типу, то коли сигнал надійде знову, процес знову не зверне на нього уваги. У тому випадку, якщо процес одержує сигнал, реагування на який установлено системним викликом signal, відразу після повернення процесу в режим завдання виконується заздалегідь домовлена дія, описана у виклику signal. Після виконання функції обробки сигналу керування буде передане на те місце в програмі користувача, де було зроблене звертання до системної функції або відбулося переривання.
Групи процесів Незважаючи на те, що в системі Linux процеси ідентифікуються унікальним кодом (PID), системі іноді доводиться використовувати для ідентифікації процесів номер “групи”, у яку вони входять. Наприклад, процеси, що мають загального предка – інтерпретатора shell, взаємозалежні, і тому коли користувач натискає клавіші “delete” або “break”, або коли термінальна лінія “зависає”, всі ці процеси одержують відповідні сигнали. Ядро використовує код групи процесів для ідентифікації групи взаємозалежних процесів, які при настанні певних подій повинні одержувати загальний сигнал. Код групи запам’ятовується в таблиці процесів. При виконанні функції fork процес-нащадок успадковує код групи свого батька. Для того, щоб утворити нову групу процесів, варто скористатися системною функцією setpgrp: grp = setpgrp(); де grp – новий код групи процесів, рівний коду ідентифікації процесу, що здійснив виклик setpgrp(). FORK Створення нового процесу: int fork(void); pid = fork(); В ході виконання функції ядро виконують наступну послідовність дій: – відводить місце в таблиці процесів під новий процес; – присвоює породжуваному процесу унікальний код ідентифікації; – робить копію контексту батьківського процесу. Оскільки ті або інші складові процесу, такі як область команд, можуть розділятися іншими процесами, ядро може іноді замість копіювання області в нову фізичну ділянку пам’яті просто збільшити значення лічильника посилань на область; – збільшує значення лічильника числа файлів, пов’язаних із процесом, як у таблиці файлів, так і у таблиці індексів; – повертає батьківському процесу код ідентифікації породженого процесу, а породженому процесу – 0. У результаті виконання функції fork контекст користувача обох процесів збігається у всьому, крім значення, що повертається змінній pid. Якщо процес не може бути породжений, функція повертає негативний код помилки. Процес, що викликає функцію fork, називається батьківським (процес-батько або процес-предок), знову створюваний процес називається породженим (процес-нащадок). Процес-нащадок завжди має більше високий пріоритет, чим процес-предок, тому що пріоритет процесу є найвищим у момент породження і зменшується в міру знаходження в стані виконання. Нульовий процес, що виникає усередині ядра при завантаженні системи, є єдиним процесом, не створюваним за допомогою функції fork. EXIT Завершення виконання процесу: void exit(int status); де status – значення, що повертається функцією батьківському процесу. Процеси можуть викликати функцію exit як у явному, так й у неявному виді (по закінченні виконання програми функція exit викликається автоматично з параметром 0). Також ядро може викликати функцію exit зі своєї ініціативи, якщо процес не прийняв надісланий йому сигнал. У цьому випадку значення параметра status дорівнює номеру сигналу. Виконання виклику exit призводить до “припинення існування” процесу, звільнення ресурсів і ліквідації контексту. WAIT Очікування завершення виконання процесу-нащадка: int wait(int *stat); pid = wait(stat_addr); де pid – значення коду ідентифікації (PID) нащадка, що завершився, stat_addr – адреса змінної цілого типу, у яку буде поміщене значення, що повертається функцією exit. За допомогою цієї функції процес синхронізує продовження свого виконання з моментом завершення нащадка. Ядро веде пошук нащадків процесу, що припинили існування, і у випадку їхньої відсутності повертає помилку. Якщо виявлений нащадок, що припинив існування, ядро передає його код ідентифікації й значення, що повертається через параметр функції exit, процесу, що викликав функцію wait. Таким чином, через параметр функції exit (status) процес, що завершується, може передавати різні значення у закодованому вигляді отримувачу інформації про причину завершення процесу, однак на практиці цей параметр використається за призначенням досить рідко. Якщо процес, що виконує функцію wait, має нащадків, що продовжують існування, він припиняється до одержання очікуваного сигналу. Ядро не відновляє зі своєї ініціативи процес, що призупинився за допомогою функції wait: такий процес може відновитися тільки у випадку одержання сигналу про “загибель нащадка”. SLEEP Призупинка роботи процесу на певний час: void sleep(unsigned seconds); де seconds – кількість секунд, на яке потрібно призупинити роботу процесу. Спочатку ядро підвищує пріоритет роботи процесу так, щоб заблокувати всі переривання, які могли б (шляхом створення конкуренції) перешкодити роботі із чергами припинених процесів, і запам’ятовує старий пріоритет, щоб відновити його, коли виконання процесу буде відновлено. Процес одержує позначку “припиненого”, адреса призупинки та пріоритет запам’ятовуються в таблиці процесів, а процес поміщається у чергу призупинених процесів. У найпростішому випадку (коли призупинка не допускає переривань) процес виконує перемикання контексту і благополучно “засинає”. Коли припинений процес “пробуджується”, ядро починає планувати його запуск. Таким чином, не можна гарантувати, що після закінчення заданого часу припинений процес відразу відновить свою роботу: він може бути вивантажений на час призупинки і тоді потрібно його підкачування в пам’ять; у цей час на виконанні може перебувати процес із більш високим пріоритетом або процес, що не допускає переривань (наприклад, що перебуває в критичному інтервалі) і т.д. Параметр seconds встановлює мінімальний інтервал, протягом якого процес буде призупинений, а реальний час призупинки завжди буде трохи більшим, хоча б за рахунок часу, необхідного для перемикання процесів.
EXEC Запуск програми. Системний виклик exec здійснює кілька бібліотечних функцій – execl, execv, execle та ін. наведемо формат однієї з них: int execv(char *path, char *argv[ ]); res = execv(path, argv); де path – ім’я виконуваного файла, argv – покажчик на масив параметрів, які передаються програмі. Цей масив аналогічний параметру argv командного рядка функції main. Список argv повинен містити мінімум два параметри: перший – ім’я програми, що підлягає виконанню (відображається в argv[0] функції main нової програми), другий – NULL (завершальний список аргументів). Системний виклик exec дає можливість процесу запускати іншу програму, при цьому відповідний цій програмі виконуваний файл буде розташовуватися в просторі пам’яті процесу. Вміст користувацького контексту після виклику функції стає недоступним, за винятком переданих функції параметрів, які переписуються ядром зі старого адресного простору в новий. Виклик exec повертає 0 при успішному завершенні та –1 при аварійному. В останньому випадку керування повертається у про-граму, що викликає. Як приклад використання цього виклику можна розглядати роботу командного інтерпретатора shell: при виконанні команди він спочатку породжує свою копію за допомогою виклику fork, а потім запускає відповідно зазначеній команді програму системним викликом exec. KILL Посилка всім або деяким процесам будь-якого сигналу: int kill(pid, sig); де sig – номер сигналу, pid – ідентифікатор процесу, що визначає групу родинних процесів, яким буде посланий сигнал: – якщо pid – позитивне ціле число, ядро посилає сигнал процесу з ідентифікатором pid. – якщо значення pid дорівнює 0, сигнал посилається всім процесам, що входять в одну групу із процесом, що викликав функцію kill. – якщо значення pid дорівнює –1, сигнал посилається всім процесам, у яких реальний код ідентифікації користувача збігається з тим, під яким виконується процес, що викликав функцію kill. Якщо процес, що послав сигнал, виконується під кодом ідентифікації суперкористувача, сигнал розсилається всім процесам, крім процесів з ідентифікаторами 0 та 1. – якщо pid – від’ємне ціле число, але не –1, сигнал посилається всім процесам, що входять у групу з номером, рівним абсолютному значенню pid. Виклик kill повертає 0 при успішному завершенні та –1 при аварійному (наприклад, специфікація неіснуючого в ОС Linux сигналу або неіснуючого процесу). У всіх випадках, якщо процес, що послав сигнал, виконується під кодом ідентифікації користувача, що не є суперкористувачем, або якщо коди ідентифікації користувача (реальний і виконуваний) у цього процесу не збігаються з відповідними кодами процесу, що приймає сигнал, kill завершується невдало. Посилка сигналу може супроводжувати виникнення будь-якої події. Сигнали SIGUSR1, SIGUSR2 та SIGKILL можуть бути послані тільки за допомогою системного виклику kill.
SIGNAL Дозволяє процесу самому визначити свою реакцію на одержання того або іншого сигналу: #include <signal.h> int signum; void handler(int); void (*signal(signum, void (*handler)(int)))(int) Після визначення реакції на сигнал signal при одержанні процесом цього сигналу буде автоматично викликатися функція handler(), яка повинна бути описана або оголошена перш, ніж буде здійснений системний виклик signal. При багаторазовій обробці того самого сигналу, процес повинен щораз здійснювати системний виклик signal для встановлення необхідної реакції на даний сигнал. Використання констант SIG_DFL і SIG_IGN дозволяє спростити реалізацію двох найчастіших реакцій процесу на сигнал: – signal(SIGINT,SIG_IGN) – ігнорування сигналу; – signal(SIGINT,SIG_DFL) – відновлення стандартної реакції на сигнал. Аргументом функції-оброблювача є ціле число – номер оброблюваного сигналу. Значення його встановлюється ядром.
PAUSE Припиняє функціонування процесу до одержання ним деякого сигналу: void pause(); Цей системний виклик не має параметрів. Робота процесу відновлюється після одержання ним будь-якого сигналу, крім тих, які ігноруються цим процесом.
ALARM Посилка процесу сигналу побудки SIGALARM: unsigned alarm(unsigned secs); Цим системним викликом процес інформує ядро ОС про те, що ядро повинне послати цьому процесу сигнал побудки через secs секунд. Виклик alarm повертає число секунд, задане при попередньому здійсненні цього системного виклику. Якщо secs дорівнює 0, то специфіковане раніше посилка процесу сигналу SIGALARM буде скасована. Наведена нижче програма в результаті виконання породить три процеси (процес-предок 1 і процеси-нащадки 2 та 3). #include <sys/types.h> #include <fcntl.h> #include <stdio.h void main(void) { int pid2, pid3, st; /* process 1 */ printf(“Process 1, pid = %d:\n”, getpid()); pid2 = fork(); if (pid2 == 0) /* process 2 */ { printf(“Process 2, pid = %d:\n”, getpid()); pid3 = fork(); if (pid3 == 0) /* process 3 */ { printf(“Process 3, pid = %d:\n”, getpid()); sleep(2); printf(“Process 3: end\n”); } /* process 2 */ if (pid3 < 0) printf(“Cann’t create process 3: error %d\n”, pid3); wait(&st); printf(“Process 2: end\n”); } else /* process 1 */ { if (pid2 < 0) printf(“Cann’t create process 2: error %d\n”, pid2); wait(&st); printf(“Process 1: end\n”); } exit(0); } Відповідно до програми спочатку буде створений процес 1 (як нащадок інтерпретатора shell), він повідомить про початок своєї роботи і породить процес 2. Після цього робота процесу 1 призупиниться і почне виконуватися процес 2, як більш пріоритетний. Він також повідомить про початок своєї роботи й породить процес 3. Далі почне виконуватися процес 3, він повідомить про початок роботи і “засне”. Після цього відновить своє виконання або процес 1, або процес 2 залежно від величин пріоритетів і від того, наскільки процесор завантажений іншими процесами. Оскільки жоден із процесів не виконує ніякої роботи, вони, найімовірніше, встигнуть завершиться до поновлення процесу 3, який у цьому випадку завершиться останнім. Приклад синхронізації роботи процесів. Програма в результаті виконання породить три процеси (процес-предок 0 і процеси-нащадки 1 та 2). Процеси 1 та 2 будуть обмінюватися сигналами і видавати відповідні повідомлення на екран, а процес 0 через певну кількість секунд відправить процесам 1 та 2 сигнал завершення і сам припинить своє функціонування. #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #define TIMEOUT 10 extern int f1(int), f2(int), f3(int); int pid0, pid1, pid2; void main(void) { setpgrp(); pid0 = getpid(); pid1 = fork(); if (pid1 == 0) /* process 1 */ { signal(SIGUSR1, f1); pid1 = getpid(); pid2 = fork(); if (pid2 < 0) puts(«Fork error»); if (pid2 > 0) for(;;); else /* process 2 */ { signal(SIGUSR2, f2); pid2 = getpid(); kill(pid1,SIGUSR1); for (;;); } } else /* process 0 */ { signal(SIGALRM, f3); alarm(TIMEOUT); pause(); } exit(0); } int f1(int signum) { signal(SIGUSR1, f1); printf(«Process 1 (%d) has got a signal from process 2 (%d)\n»,pid1,pid2); sleep(1); kill(pid2, SIGUSR2); return 0; } int f2(int signum) { signal(SIGUSR2, f2); printf(«Process 2 (%d) has got a signal from process 1 (%d)\n»,pid2,pid1); sleep(1); kill(pid1, SIGUSR1); return 0; } int f3(int signum) { printf(«End of job - %d\n», pid0); kill(0, SIGKILL); return 0; } Взаємодія процесів у LINUX Пакет IPC (Interprocess communication) містить у собі три механізми. Механізм повідомлень дає процесам можливість посилати іншим процесам потоки форматованих даних, механізм поділу пам'яті дозволяє процесам спільно використати окремі частини віртуального адресного простору, а семафори – синхронізувати своє виконання з виконанням паралельних процесів. Незважаючи на те, що вони реалізуються у вигляді окремих блоків, вони мають загальні властивості. Для використання механізмів IPC необхідно підключити до програми файл <sys/ipc.h>
Семафори У наш час все більше значення надається великим обчислювальним системам, таким як багатопроцесорні обчислювальні комплекси та мережі ЕОМ. Основним засобом збільшення потужностей обчислювальних машин є підвищення в них рівня паралелізму. Очевидно, що паралельність в апаратурі відбивається й на програмному забезпеченні. У зв’язку із цим значно зростає інтерес до паралельних процесів і проблем їхньої синхронізації. На сьогоднішній день запропонована велика кількість різних систем синхронізації процесів. До них відносяться: – блокування пам’яті; – семафори; – критичні області; – умовні критичні області; – монітори; – виключаючі області тощо. Один зі способів синхронізації паралельних процесів – семафори Дейкстри, реалізовані в ОС LINUX. Синхронізація процесів Визначимо поняття процесу як завдання, виконуваного в ос. Оскільки завдання в багатовикористовуваній системі повинні виконуватися незалежно і можуть виконуватися паралельно, то і процеси, що їх представляють, повинні бути незалежними і паралельно викону-ваними. Процесам для роботи часто потрібні різні пристрої та допоміжні програми, які можна назвати відповідно апаратними і програмними ресурсами. Якщо ми хочемо ефективно використати ці ресурси, то ті й інші повинні спільно використатися декількома процесами. Такі спільно використовувані ресурси називаються поділюваними ресурсами. Якщо за умовами роботи потрібно, щоб поділювані ресурси одномоментно були доступні тільки одному процесу, то такі ресурси називаються критичними. Під незалежністю процесів розуміється, що крім (досить рідких) моментів явного зв’язку, процеси розглядаються як зовсім незалежні один від одного. Але насправді вони не є цілком незалежними, тому що вони можуть використати в процесі свого виконання ті самі ресурси. Процес упорядкування зв’язку між конкуруючими процесами називається синхронізацією. Синхронізація задається за допомогою синхронізуючих правил. Реалізація таких правил здійснюється за допомогою засобів синхронізації. Елементарні прийоми синхронізації, такі як використання загальних змінних, мають ряд недоліків, які іноді приводять до неможливості одержання правильних рішень. Тому виникла необхідність у створенні спеціальних синхронізуючих примітивів. Такі примітиви за назвою P та V -операції були запропоновані Дейкстрою в 1968 р. Ці операції можуть виконуватися тільки над спеціальними змінними, називаними семафорами або семафорними змінними. Семафори є цілими величинами та спочатку були визначені як приймаючі тільки ненегативні значення. Крім того, якщо їх використати для рішення завдань взаємного виключення, то область їх значень може бути обмежена лише 0 або 1. Однак надалі була показана важлива область застосування семафорів, що приймають будь-які цілі позитивні значення. Такі семафори одержали назву “загальних семафорів” на відміну від “двійкових семафорів”, використовуваних у завданнях взаємного виключення. P та V операції є єдиними операціями, виконуваними над семафорами. Іноді вони називаються семафорними операціями. Дамо визначення P та V- операцій у тому вигляді, у якому вони були запропоновані Дейкстрою. V -операція (V (S)) – операція з одним аргументом, що повинен бути семафором. Ця операція збільшує значення аргументу на 1. P- операція (P (S)) – операція з одним аргументом, що повинен бути семафором. Її призначення – зменшити величину аргументу на 1, якщо тільки результуюче значення не стає негативним. Завершення P -операції, тобто рішення про те, що дійсний момент є підходящим для виконання зменшення і наступне зменшення значення аргументу, повинне розглядатися як неподільна операція. Ці визначення справедливі як для загальних, так для двійкових семафорів. Реалізація семафорів Системні виклики для роботи із семафорами містяться в пакеті IPC (підключається файл описів – <sys/ipc.h>). Ці виклики забезпечують синхронізацію виконання паралельних процесів, виконуючи набір дій тільки над групою семафорів (засобами низького рівня). Linux підтримує числові семафори (як розширення двійкових семафорів). Семафори Linux носять не обов’язковий, а повідомний характер. Це означає, що зв’язок між семафором і тим ресурсом (ресурсами), доступ до якого розмежовує семафор, є чисто логічним. Якщо при звертанні до цього ресурсу процес не запросить доступ до нього через семафор, ніхто не перешкодить процесу одержати цей доступ (при наявності відповідних прав). Таким чином, процеси повинні заздалегідь домовлятися про використання семафорів. Кожен семафор у системі Linux являє собою набір значень (вектор семафорів). Пов’язані із семафорами системні функції є узагальненням операцій P та V- семафорів Дейкстри, у них допускається одночасне виконання декількох операцій (над семафорами, що належать одному вектору, так звані векторні операції). Ядро виконує операції комплексно; жоден зі сторонніх процесів не зможе переустановлювати значення семафорів, поки всі операції не будуть виконані. Якщо ядро з якихось причин не може виконати всі операції, воно не виконує ні однієї; процес припиняє свою роботу доти, поки ця можливість не буде надана. Семафор в LINUX складається з наступних елементів: – значення семафора; – ідентифікатор останнього із процесів, що працювали із семафором; – кількість процесів, що очікують збільшення значення семафора; – кількість процесів, що очікують моменту, коли значення семафора стане рівним 0. Для створення набору семафорів та одержання доступу до них використається системна функція semget, для виконання різних керуючих операцій над набором – функція semctl, для роботи зі значеннями семафорів – функція semop.
SHMGET Створення області поділюваної пам’яті або одержання номера дескриптора існуючої області: int shmget(key_t key, int size, int flag); id = shmget(key, size, flag); де id – ідентифікатор області поділюваної пам’яті, key – номер області, size – обсяг області в байтах, flag – параметри створення і права доступу. Ядро використає key для ведення пошуку в таблиці поділюваної пам’яті: якщо підходящий запис виявлений і є дозвіл на доступ, ядро повертає викликаючому процесу зазначений у записі дескриптор. Якщо запис не знайдений і користувач встановив прапор IPC_CREAT, що вказує на необхідність створення нової області, ядро перевіряє знаходження розміру області у встановлених системою межах і виділяє область. Ядро записує установки прав доступу, розмір області та покажчик на відповідний запис таблиці областей у таблицю поділюваної пам’яті і встановлює прапор, що свідчить про те, що з областю не зв’язана окрема пам’ять. Області виділяється пам’ять (таблиці сторінок і т.п.) тільки тоді, коли процес приєднує область до свого адресного простору. Ядро встановлює також прапор, що говорить про те, що по завершенні останнього пов’язаного з областю процесу область не повинна звільнятися. Таким чином, дані в поділюваній пам’яті залишаються в цілості, навіть якщо вона не належить жодному із процесів (як частина віртуального адресного простору останнього). SHMAT Приєднує область поділюваної пам’яті до віртуального адресного простору процесу: void *shmat(int id, void *addr, int flag); virtaddr = shmat(id, addr, flag); Значення id, що повертається функцією shmget, ідентифікує область поділюваної пам’яті, addr є віртуальною адресою, за якою користувач хоче підключити область, а за допомогою прапорів (flag) можна вказати, чи призначена область тільки для читання і чи потрібно ядру округляти значення зазначеної користувачем адреси. Значення, що повертається функцією virtaddr, являє собою віртуальну адресу, за якою ядро зробило підключення області і яка не завжди збігається з адресою, зазначеною користувачем. На початку виконання системної функції shmat ядро перевіряє наявність у процесу необхідних прав доступу до області. Воно досліджує зазначену користувачем адресу; якщо вона дорівнює 0, ядро вибирає віртуальну адресу на свій розсуд. Область поділюваної пам’яті не повинна перетинатися у віртуальному адресному просторі процесу з іншими областями; отже, її вибір повинен проводитись розумно й обережно. Так, наприклад, процес може збільшити розмір приналежної йому області даних за допомогою системного виклику brk, і нова область даних буде містити адреси, суміжні з колишньою областю; тому я
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2016-04-26; просмотров: 491; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.138.124.28 (0.012 с.) |