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


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



ЗНАЕТЕ ЛИ ВЫ?

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



Системні виклики для роботи із семафорами в ОС Linux опи­сані в бібліотеці <sys/sem.h>.

SEMGET

Створення набору семафорів і одержання доступу до них:

int semget(key_t key, int count, int semflg);

semid = semget(key, count, semflg);

де key – номер семафора, count – кількість семафорів, semflg – параметри створення і права доступу. Ядро використовує key для ве-дення пошуку в таблиці семафорів: якщо підходящий запис виявлений і дозвіл на доступ є, ядро повертає викликаючому процесу зазначений у записі дескриптор. Якщо запис не знайдений, а користувач установив прапорець IPC_CREAT – створення нового семафора – ядро перевіряє можливість його створення і виділяє запис у таблиці семафорів. Запис вказує на масив семафорів і містить лічильник count. У записі також зберігається кількість семафорів у масиві, час останнього виконання функцій semop й semctl.

SEMOP

Установка або перевірка значення семафора:

int semop(int semid, struct sembuf *oplist, unsigned nsops);

де semid – дескриптор, що повертається функцією semget, oplist – покажчик на список операцій, nsops – розмір списку. Значення, що повертається функцією, є старим значенням семафора, над яким проводилась операція. Кожен елемент списку операцій має наступний формат (визначення структури sembuf у файлі sys/sem.h):

struct sembuf

{ unsigned short sem_num;

short sem_op;

short sem_flg;

}

де shortsem_num – номер семафора, що ідентифікує елемент масиву семафорів, над яким виконується операція; sem_op – код опе­рації; sem_fl – прапорці операції. Ядро зчитує список операцій oplist з адресного простору задачі та перевіряє коректність номерів семафорів, а також наявність у процесу необхідних дозволів на читання і ко­ректування семафорів. Якщо таких дозволів немає, системна функція завершується невдало (res = –1). Якщо ядру доводиться припиняти свою роботу при звертанні до списку операцій, воно повертає сема­форам їхні колишні значення та перебуває в стані призупинки до настання очікуваної події, після чого системна функція запускається знову. Оскільки ядро зберігає коди операцій над семафорами в гло­бальному списку, воно знову зчитує цей список із простору завдання, коли перезапускає системну функцію. Таким чином, операції вико­нуються комплексно – або всі за один сеанс, або жодної.

Установка прапорця IPC_NOWAIT у функції semop має нас­тупний сенс: якщо ядро попадає в таку ситуацію, коли процес повинен призупинити своє виконання, чекаючи збільшення значення семафора вище певного рівня або, навпаки, зниження цього значення до 0, і якщо при цьому прапор IPC_NOWAIT установлений, ядро виходить із функції з повідомленням про помилку. (Таким чином, якщо не при­пиняти процес у випадку неможливості виконання окремої операції, можна реалізувати умовний тип семафора). Прапор SEM_UNDO до­зволяє уникнути блокування семафора процесом, що закінчив свою ро­боту, перш ніж звільнив захоплений ним семафор. Якщо процес уста­новив прапор SEM_UNDO, то при завершенні цього процесу ядро дасть зворотний хід всім операціям, виконаних процесом. Для цього в розпорядженні в ядра є таблиця, у якій кожному процесу відведений окремий запис. Запис містить покажчик на групу структур відновлен­ня, по одній структурі на кожен використовуваний процесом семафор.

Кожна структура відновлення складається із трьох елементів – ідентифікатора семафора, його порядкового номера в наборі та устано­вочного значення. Ядро виділяє структури відновлення динамічно, під час першого виконання системної функції semop із встановленим пра­пором SEM_UNDO. При наступних звертаннях до функції з тим самим прапором ядро переглядає структури відновлення для процесу в пошу­ках структури з тим самим ідентифікатором і порядковим номером се­мафора, що й у виклику функції. Якщо структура виявлена, ядро відні­має значення зробленої над семафором операції з установочного зна­чення. Таким чином, у структурі відновлення зберігається результат вирахування суми значень всіх операцій, зроблених над семафором, для якого встановлений прапорець SEM_UNDO.

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

Ядро міняє значення семафора залежно від коду операції, за­значеного у виклику функції semop. Якщо код операції має позитивне значення, ядро збільшує значення семафора і виводить зі стану призу­пинки всі процеси, що очікують настання цієї події. Якщо код операції дорівнює 0, ядро перевіряє значення семафора: якщо воно дорівнює 0, ядро переходить до виконання інших операцій; в іншому випадку ядро збільшує число припинених процесів, що очікують, коли значен­ня се­мафора стане нульовим, і “засинає”.

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

SEMCTL

Виконання керуючих операцій над набором семафорів:

int semctl(int semid, int semnum, int cmd, union semun arg);

Параметр arg оголошений як об’єднання типів даних:

union semunion

{ int val; // використовується тільки для SETVAL

struct semid_ds *semstat; // для IPC_STAT й IPC_SET

unsigned short *array;

} arg;

Ядро інтерпретує параметр arg залежно від значення параметра cmd, що може приймати наступні значення:

– GETVAL – повернути значення того семафора, на який вка­зує параметр num;

– SETVAL – установити значення семафора, на який вказує па­раметр num, рівним значенню arg.val;

– GETPID – повернути ідентифікатор процесу, що виконував останнім функцію semop стосовно того семафора, на який вказує пара­метр semnum;

– GETNCNT – повернути число процесів, що очікують того моменту, коли значення семафора стане позитивним;

– GETZCNT – повернути число процесів, що очікують того мо­менту, коли значення семафора стане нульовим;

– GETALL – повернути значення всіх семафорів у масиві arg.array;

– SETALL – установити значення всіх семафорів відповідно до вмісту масиву arg.array;

– IPC_STAT – зчитати структуру заголовку семафора з іденти­фікатором id у буфер arg.buf. Аргумент semnum ігнорується;

– IPC_SET – запис структури семафора з буфера arg.buf;

– IPC_RMID – видалити семафори, пов’язані з ідентифікато­ром id, із системи.

Якщо зазначено команду видалення IPC_RMID, ядро веде по­шук всіх процесів, що містять структури відновлення для даного се­мафора, і видаляє відповідні структури із системи. Потім ядро ініціа­лізує використовувані семафором структури даних і виводить зі стану призупинки всі процеси, що очікують настання деякої пов’язаної з се­мафором події: коли процеси відновлюють своє виконання, вони вияв­ляють, що ідентифікатор семафора є не коректним, і повертають ви­кликаючій програмі помилку. Якщо число, що повертається функцією, дорівнює 0, то функція завершилася успішно, інакше (повертається значення, що дорівнює –1) відбулася помилка. Код помилки зберігає­ться в змінній errno.

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

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

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

/*Запис у поділювану пам'ять і читання з неї*/

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#define SHMKEY

#define K 1024

int shmid;

main()

{

int i, *pint;

char *addr;

extern char *shmat();

extern cleanup();

/* визначення реакції на всі сигнали */

for (i = 0; i < 20; i++) signal(i, cleanup);

/* створення загальнодоступної поділюваної області пам'яті розміром 128*K (або одержання її ідентифікатора, якщо вона вже існує)*/

shmid = shmget(SHMKEY,128*K,0777:IPC_CREAT);

addr = shmat(shmid,0,0);

pint = (int *) addr;

for (i = 0; i < 256; i++) *pint++ = i;

pint = (int *) addr;

*pint = 256;

pint = (int *) addr;

for (i = 0; i < 256; i++)

printf(“index %d\tvalue %d\n”,i,*pint++);

/* очікування сигналу */

pause();

}

/* видалення поділюваної пам'яті */

cleanup()

{

shmctl(shmid,IPC_RMID,0);

exit();

}

/*Читання з поділюваної пам’яті даних, записаних першим процесом */

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#define SHMKEY 75

#define K 1024

int shmid;

main()

{

int i, *pint;

char *addr;

extern char *shmat();

shmid = shmget(SHMKEY,64*K,0777);

addr = shmat(shmid,0,0);

pint = (int *) addr;

while (*pint == 0); /* очікування початку запису */

for (i = 0; i < 256, i++) printf(“%d\n”,*pint++);

}

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

lsid = semget(75, 1, 0777 | IPC_CREAT);

де lsid – це ідентифікатор семафора; 75 – ключ використо-вуваного дескриптора (якщо він зайнятий, система створить свій); 1 – кількість семафорів у масиві; IPC_CREAT – прапор для створення но­вого запису в таблиці дескрипторів (описаний із правами доступу 0777).

Для установки початкового значення семафора використаємо структуру sem. У ній присвоюємо значення:

sem.array[0] = 1;

тобто семафор відкритий для користування.

Завершальним кроком є ініціалізація масиву (у цьому випадку масив складається з одного елемента):

semctl(lsid,1,SETALL,sem);

де lsid – ідентифікатор семафора (виділений рядок у дескрип­торі); 1 – кількість семафорів; SETALL – команда “установити всі се­мафори”; sem – покажчик на структуру.

Установлюємо прапор SEM_UNDO у структурі sop для роботи з функцією semop (значення цього прапора не міняється в процесі роботи).

Далі в програмі організуються два паралельних процеси (на­щадки “головної” програми) за допомогою системної функції fork(). Один процес-нащадок записує дані в поділювану пам’ять, другий зчи­тує ці дані. При цьому процеси-нащадки синхронізують доступ до по­ділюваної пам’яті за допомогою семафорів.

Функція p () описується в такий спосіб:

int p(int sid)

{ sop.sem_num = 0; /* номер семафора */

sop.sem_op = -1;

semop(sid, &sop, 1);

}

У структуру sop заноситься номер семафора, над яким буде виконана операція та значення самої операції (у цьому випадку це зменшення значення на 1). Прапорець був встановлений заздалегідь, тому функція в процесі завжди перебуває, чекаючи вільного семафора. (Функція v () працює аналогічно, але sop.sem_op = 1).

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

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#include <unistd.h>

#include <errno.h>

int shmid, lsid, x;

struct sembuf sop;

union semun

{ int val;

struct semid_ds *buf;

ushort *array;

} sem;

int p(int sid)

{ sop.sem_num = 1;

sop.sem_op = -1;

semop(sid, &sop, 1);

}

int v(int sid)

{ sop.sem_num = 1;

sop.sem_op = 1;

semop(sid, &sop, 1);

}

main()

{ int j, i, id, id1, n;

lsid = semget(75, 1, 0777 | IPC_CREAT);

sem.array = (ushort*)malloc(sizeof(ushort));

sem.array[0] = 1;

sop.sem_flg = SEM_UNDO;

semctl(lsid, 1, SETALL, sem);

printf(«n= «);

scanf(«%d», &n);

id = fork();

if (id == 0) /* перший процес */

{ for(i = 0; i < n; i++)

{ p(lsid);

puts(«\n Працює процес 1»);

v(lsid);

}

exit(0);

}

id1 = fork();

if (id1 == 0) /* другий процес */

{ for (j = 0; j < n; j++)

{ p(lsid);

puts(«\n Працює процес 2»);

v(lsid);

}

exit(0);

}

wait(&x);

wait(&x);

exit(0);

}

Завдання

Завдання 1

1. Процес 1 породжує нащадка 2, що у свою чергу породжує нащадка 3. За допомогою сигналів домогтися того, щоб ці процеси за­кінчувалися в порядку 1, 2, 3.

2. Процес 1 породжує нащадка 2. Обидва процеси після цього відкривають той самий файл і пишуть у нього по черзі по N байт. Ор­ганізувати M циклів запису за допомогою сигналів.

3. Процес 1 відкриває файл і після цього породжує нащадка 2. Процес 2 починає запис у файл після одержання сигналу SIG1 від про­цесу 1 і припиняє її після одержання від процесу 1 сигналу SIG2, що посилає через N секунд після SIG1. Потім процес 1 читає дані з файла і виводить їх на екран.

4. Процес 1 відкриває файл і після цього породжує нащадка 2. Один процес пише у файл один байт, посилає іншому процесу сигнал, інший читає з файла один байт, виводить прочитане на екран і посилає сигнал першому процесу. Організувати N циклів запису (читання).

5. Процес 1 відкриває файл і породжує нащадків 2 та 3. На­щадки після сигналу від предка пишуть у файл по N байт, посилають сигнали процесу 1 і завершують роботу. Після цього процес 1 зчитує дані з файла і виводить на екран.

6. Процес 1 відкриває файл і після цього породжує нащадка 2, що у свою чергу породжує нащадка 3. Процес 2 пише N байт у загаль­ний файл, посилає сигнал процесу 3, що теж пише N байт у файл і по­силає сигнал процесу 1, що зчитує дані з файла і виводить їх на екран.

7. Процес 1 відкриває файл і породжує нащадка 2, після цього пише у файл N байт і посилає сигнал процесу 2. Процес 2 пише N байт у файл, посилає сигнал процесу 1 і завершується. Процес 1, одержав­ши сигнал, зчитує дані з файла, виводить їх на екран і завершується.

8. Процес 1 породжує нащадка 2. Обидва процеси відкривають той самий файл і записують у нього в циклі по N байт. За допомогою сигналів організувати черговість запису.

9. Процес 1 відкриває файл і породжує нащадків 2 та 3. На­щадки пишуть у файл по черзі по N байт (M циклів запису, органі­зувати за допомогою сигналів) і завершуються. Останній з них посилає сигнал процесу 1, що читає дані та виводить їх на екран.

10. Процес 1 відкриває файл, породжує нащадка 2, пише у файл N байт і посилає сигнал SIG1 процесу 2. Процес 2 за сигналом SIG1 чи­тає дані, виводить їх на екран і завершується. Якщо сигнал від процесу 1 не надійде протягом M секунд, процес 2 починає зчитувати дані за сигналом SIGALRM.

11. Процес 1 відкриває файл і породжує нащадка 2, що у свою чергу породжує нащадка 3. Процеси 2 та 3 пишуть у загальний файл, причому процес 3 не може почати писати раніше, ніж процес 2. Орга­нізувати черговість за допомогою сигналів. Як тільки розмір файла перевищить N байт, процес 1 посилає нащадкам сигнал SIG2 про за­вершення роботи, зчитує дані з файла і виводить їх на екран.

12. Процес 1 відкриває файл і породжує нащадка 2. Процес 1 з інтервалом в 1 секунду (через alarm) посилає M сигналів SIG1 процесу 2, що за кожним сигналом пише в загальний файл по N чисел. Потім про­цес 1 посилає процесу 2 сигнал SIG2, процес 2 завершується. Процес 1 зчитує дані з файла і виводить їх на екран.

13. Процес 1 відкриває файл і породжує нащадків 2 та 3. Процес 1 з інтервалом в 1 секунду (через alarm) по черзі посилає N сигналів SIG1 процесам 2 та 3, які за кожним сигналом пишуть у загальний файл по M чи­сел. Потім процес 1 посилає нащадкам сигнал SIG2, процеси 2 та 3 за­вершуються. Процес 1 зчитує дані з файла і виводить їх на екран.

14. Процес 1 відкриває два файли і породжує нащадків 2 та 3. Процеси 2 та 3 з інтервалом в 1 секунду (через alarm) посилають по N сигналів процесу 1, що за кожним сигналом пише у відповідний файл по M чисел. Потім процеси 2 та 3 зчитують дані з файлів, виводять їх на екран і завершуються. Процес 1 завершується останнім.

15. Процес 1 відкриває два файли й породжує нащадків 2 та 3. Процеси 2 та 3 посилають по одному сигналі процесу 1, що за кожним сигналом пише у відповідний файл M чисел. Процеси 2 та 3 зчитують дані з файлів і виводять їх на екран. За допомогою сигналів організу­вати непересічний вивід даних.

16. Процес 1 відкриває файл і породжує нащадка 2. Процес 2 за сигналом SIG1 від процесу 1 починає писати у файл, за сигналом SIG2 припиняє запис, а потім за сигналом SIG1 завершується. Сигнал SIG1 надходить із затримкою в 1 секунду щодо першого сигналу SIG2.

17. Процес 1 відкриває файл і породжує нащадка 2. Процес 1 читає дані з файла, процес 2 пише дані у файл у такий спосіб: за сигналом SIG1 від процесу 1 процес 2 записує у файл N чисел і посилає процесу 1 сигнал закінчення запису; процес 1 за цим сигналом зчитує дані і посилає про­цесу 2 сигнал SIG1. Процес 2 завжди пише дані в початок файла. Орга­нізувати M циклів запису (читання). Прочитані дані виводяться на екран.

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

19. Процес 1 відкриває файл і породжує нащадка 2. Обидва про­цеси пишуть у нього по черзі по N чисел. Організувати M циклів запи­су за допомогою сигналів.

20. Процес 1 породжує нащадка 2. Процес 1 пише в загальний файл число 1, процес 2 – число 2. Використовуючи сигнали, забезпечити наступний вміст файла:

а) 1 2 1 2 1 2 1 2

б) 1 1 2 2 1 1 2 2

в) 1 1 2 1 1 2 1 1 2

г) 2 1 1 2 1 1 2 1 1

д) 1 2 2 1 2 2 1 2 2

Завдання 2

1. Процес 1 породжує нащадків 2 та 3, всі вони приєднують до себе поділювану пам’ять обсягом (2*sizeof(int)). Процеси 1 та 2 по чер­зі пишуть у цю пам’ять число, рівне своєму номеру (1 або 2). Після Цього один з процесів видаляє поділювану пам’ять, потім процес 3 зчитує вміст області поділюваної пам’яті та записує у файл. Викорис­товуючи семафори, забезпечити наступний вміст файла:

а) 1 2 1 2 1 2 1 2

б) 1 1 2 2 1 1 2 2

в) 1 1 2 1 1 2 1 1 2

г) 2 1 1 2 1 1 2 1 1

д) 1 2 2 1 2 2 1 2 2

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

а) 1 2 3 1 2 3 1 2 3

б) 1 1 2 2 3 3 1 1 2 2 3 3

в) 1 2 1 3 1 2 1 3 1 2 1 3

г) 2 1 1 3 2 1 1 3 2 1 1 3

д) 3 1 2 3 1 2 3 1 2

Останній процес зчитує вміст поділюваної пам’яті, виводить його на екран і видаляє поділювану пам’ять.

3. Процес 1 породжує нащадків 2 та 3, всі вони приєднують до себе дві області поділюваної пам’яті M1 та M2 обсягом (N1*sizeof(int)) і (N2*sizeof(int)) відповідно. Процес 1 пише в M1 число, що після кож­ного запису збільшується на 1; процес 2 переписує k2 чисел з M1 в M2, а процес 3 переписує k3 чисел з M2 у файл. Після кожного етапу робо­ти процес 1 засинає на t1 секунд, процес 2 – на t2 секунд, а процес 3 – на t3 секунд. Процесу 1 забороняється записувати в зайняту область M1; процес 2 може переписати дані, якщо був зроблений запис в M1 та M2 вільна; процес 3 може переписувати дані з M2, тільки якщо був здійснений запис в M2. Використовуючи семафори, забезпечити синхро­нізацію роботи процесів відповідно до заданих умов. Параметри N1, N2, k1, k2, k3, t1, t2, t3 задаються у вигляді аргументів командного рядка.

4. Процес 1 породжує нащадка 2, вони приєднують до себе по­ділювану пам’ять обсягом (N*sizeof(int)). Процес 1 пише в неї k1 чисел відразу, процес 2 переписує k2 чисел з пам’яті у файл. Процес 1 може ро­бити запис, тільки якщо в пам’яті досить місця, а процес 2 переписувати, тільки якщо є не менше, ніж k2 чисел. Після кожного запису (читання) процеси засинають на t секунд. Після кожного запису процес 1 збільшує значення записуваних чисел на 1. Використовуючи семафори, забезпе­чити синхронізацію роботи процесів відповідно до заданих умов. Пара­метри N, k1, k2, t задаються у вигляді аргументів командного рядка.

Контрольні питання

1. Команди для роботи з процесами.

2. Базові сигнали операційної системи Linux.

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

4. Стани процесу і переходи між ними.

5. Групи процесів.

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

7. Використання поділюваної пам’яті.

8. Синхронізація процесів.

9. Семафори та їх реалізація.

 

 



Поделиться:


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

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