Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Системні виклики для роботи із процесами

Поиск

Розглянемо системні виклики, що використовуються при робо­ті із процесами в ОС Linux і описані в бібліотеці <fcntl.h>.

Системний виклик fork створює новий процес, копіюючи ви­кликаючий процес; виклик exit завершує виконання процесу; wait дає можливість батьківському процесу синхронізувати своє продовження із завершенням породженого процесу, а sleep припиняє на певний час виконання процесу. Системний виклик exec дає процесу можливість запускати на виконання іншу програму.

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.

Системні виклики для роботи із сигналами

Розглянемо найбільш часто використовувані системні виклики при роботі із сигналами в ОС Linux, описані в бібліотеці <signal.h>.

 

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>

 



Поделиться:


Последнее изменение этой страницы: 2016-04-26; просмотров: 618; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.221.248.140 (0.012 с.)