Застосування семафорів

У четвертому виданні популярного керівництва надано основи програмування в операційній системі Linux. Розглянуті: використання бібліотек C/C++ та стандартних засобів розробки, організація системних викликів, файлове введення/виведення, взаємодія процесів, програмування засобами командної оболонки, створення графічних інтерфейсів за допомогою інструментальних засобів GTK+ або Qt, застосування сокетів та ін. Описана компіляція програм, їх компонування з бібліотеками та робота з термінальним введенням/виводом. Наведено прийоми написання додатків у середовищах GNOME® та KDE®, зберігання даних з використанням СУБД MySQL® та налагодження програм. Книга добре структурована, що робить навчання легким та швидким.
Для Linux-програмістів-початківців
Основи програмування в Linux
Застосування семафорів
Як видно із змісту попередніх розділів, операції із семафорами можуть бути дуже складними. Це не найсумніше, тому що програмування багатьох процесів або потоків із критичними секціями — дуже важке завдання саме по собі, і наявність складного програмного інтерфейсу лише збільшує інтелектуальне навантаження.
На щастя, більшість завдань, які потребують семафорів, можна вирішити, застосовуючи єдиний бінарний семафор – найпростіший тип семафору. У наступному прикладі (вправа 14.1) ви використовуєте повний програмний інтерфейс для створення дуже простого інтерфейсу типу Р та V для бінарного семафору. Потім ви застосовуєте цей простенький інтерфейс для демонстрації того, як функціонують семафори.
В експериментах з семафорами використовуватиметься єдина програма sem1.с, яку ви зможете запускати кілька разів. Необов'язковий параметр застосовуватиметься для того, щоб показати, чи відповідаєпрограма за створення та знищення семафору.
Виведення двох різних символів позначатиме вхід у критичну секцію та вихід із неї. Програма, запущена з параметром, виводить X при вході в критичну секцію та виході із неї. Інші екземпляри запущеної програми виводитимуть символ Про при вході у свої критичні секції та виході з них. Оскільки в будь-який заданий момент часу лише один процес здатний увійти у свою критичну секцію, всі символи X та O повинні з'являтися парами.
Вправа 14.1. Семафори
1. Після системних директив #include ви включаєте файл semun.h. Він визначає об'єднання типу semun відповідно до стандарту X/Open, якщо воно вже не описане у системному файлі sys/sem.h. Далі йдуть прототипи функцій та глобальна змінна, розташовані перед входом до функції main . У ній створюється семафор за допомогою виклику semget, який повертає ID семафору. Якщо програма викликається вперше (тобто викликається з параметром argc > 1 ), виконується виклик set_semvalue для ініціалізації семафору і змінної op_char присвоюється значення O .
#include #include #include #include #include "semun.h" static int set_semvalue(void); static void del_semvalue(void); static int semaphore_p(void); static int semaphore_v(void); static int sem_id; int main(int argc, char *argv[]) int i; int pause_time; char op_char = 'О'; srand((unsigned int)getpid()); sem_id = semget((key_t)1234, 1, 0666 IPC_CREAT); if (argc >1) if (!set_semvalue()) fprintf(stderr, "Failed to initialize semaphoren"); exit(EXIT_FAILURE); > op_char = 'X'; sleep(2); >
2. Далі слідує цикл, в якому 10 разів виконується вхід у критичну секцію і вихід з неї. Ви спочатку виконуєте викликфункцію semaphore_p , яка змушує семафор чекати, коли ця програма буде готова увійти в критичну секцію.
3. Після критичної секції ви викликаєте функцію semaphore_v , яка звільняє семафор перед повторним проходом циклу після очікування протягом випадкового проміжку часу. Після циклу виконується виклик функції del_semvalue для очищення коду.
if (!semaphore_v()) exit(EXIT_FAILURE); pause_time = rand() % 2; sleep(pause_time); > printf("n%d - finishedn", getpid()); if (argc > 1) sleep(10); del_semvalue(); > exit(EXIT_SUCCESS); >
4. Функція set_semvalue ініціалізує семафор за допомогою команди SETVAL у виклику semctl. Це слід зробити перед використанням семафору.
static int set_semvalue(void) union semun sem_union; sem_union.val = 1; if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0); return(1); >
5. У функції del_semvalue майже та сама форма за винятком того, що у виклику semctl застосовується команда IPC_RMID для видалення ID семафору.
static void del_semvalue(void) union semun sem_union; іf (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) fprintf(stderr, "Failed to delete semaphoren"); >
6. Функція semaphore_p змінює лічильник семафору на -1. Це операція очікування чи припинення процесу.
static int semaphore_p(void) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1; /* P() */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) fprintf(stderr, "semaphore_p failedn"); return(0); > return(1); >
7. Функція semaphore_v аналогічна за винятком завдання елемента sem_op структури sembuf , що дорівнює 1. Це операція "звільнення", в результаті якої семафорзнову стає доступним.
static int semaphore_v(void) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1; /* V() */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) fprintf(stderr, "semaphore_v failedn"); return(0); > return(1); >
Ця проста програма дозволяє існування єдиного двійкового семафору для кожної програми, хоча можна було б збільшити кількість, передавши змінну семафора за потреби. Зазвичай одного бінарного семафору достатньо.
Ви можете протестувати програму, запустивши її кілька разів. Вперше ви передасте параметр, щоб повідомити програму про те, що вона відповідає за створення та видалення семафору. В інших примірниках програми, що виконується, не буде параметра.
Далі наведено приблизний висновок для двох запущених екземплярів програми:
$cc sem1.з -о sem1 $./sem1 1 & [1] 1082 $./sem1 OOXXOOXXOOXXOOXXOOXXOOOOXXOOXXOOXXOOXXXX 1083 - finished 1082 - finished
Нагадуємо, що символ Про представляє перший запущений екземпляр програми, а символ X - другий екземпляр програми, що виконується. Оскільки кожен екземпляр програми виводить символ при вході в критичну секцію та при виході з неї, кожен символ має з'являтися лише попарно. Як бачите, символи О та Х насправді утворюють пари, вказуючи на коректну обробку критичних секцій. Якщо програма не працює на вашій системі, можна застосувати команду stty-tostop перед запуском програми, щоб гарантувати, що фонова програма, що генерує виведення на tty, не викликає збудження сигналу.
Як це працює
Програма починається з отримання позначення семафору на основі ключа(довільного), який ви вибрали, застосувавши функцію semget . Прапор IPC_CREAT призводить до створення семафору, якщо він потрібний.
Якщо програма має параметр, вона відповідає за ініціалізацію семафору, яка виконується функцією set_semvalue , спрощеним варіантом функції загального призначення semctl . Вона також використовує наявність параметра визначення символу виведення. Функція sleep просто надає деякий час для запуску інших екземплярів програми до того, як програма виконає занадто багато проходів свого циклу. Для включення до програми кількох псевдовипадкових проміжків часу ви використовуєте функції srand та rand.
Далі програма виконує 10 разів оператори тіла циклу із псевдовипадковими періодами очікування у своїй критичній та некритичній секціях. Критична секція охороняється викликами ваших функцій semaphore_p та semaphore_v, спрощених інтерфейсів функції більш загального виду semop.
Перед видаленням семафору програма, запущена з параметром, чекає, доки завершиться виконання інших екземплярів програми. Якщо семафор не видалений, він продовжуватиме існувати в системі, навіть якщо немає програм, що його використовують. У реальних програмах дуже важливо переконатися, що ви випадково не залишили семафор після завершення виконання. Він може викликати проблеми при наступному запуску програми, крім того, семафори є різновидом обмежених ресурсів, які ви повинні берегти.