Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Laboratorny_praktikum_Ch_1_Operatsionnye_siste.doc
Скачиваний:
4
Добавлен:
01.05.2025
Размер:
368 Кб
Скачать
☆

2.1. Linux

Наиболее распространенными инструментами синхронизации в Linux являются сигналы и семафоры.

Сигналы

Сигналы можно интерпретировать как программные прерывания. При получении сигнала выполнение текущей операции прекращается и вызывается функция-обработчик данного сигнала. Сигналы традиционно делятся на ненадежные и надежные. «Ненадежность» сигнала заключается в том, что в некоторых ситуациях процесс может не получить сгенерированный сигнал. Кроме этого, возможности процессов по управлению ненадежными сигналами довольно ограничены.

Одна из проблем ненадежных сигналов заключается в том, что обработчик сигнала сбрасывается в значение по умолчанию всякий раз, как срабатывает сигнал. Пример использования ненадежных сигналов:

int sig_int(); /* Прототип функции-обработчика сигнала */

...

signal(SIGINT, sig_int); /* установка обработчика на сигнал */

...

sig_int()

{

signal(SIGINT, sig_int); /* восстановить обработчик */

... /* обработать сигнал... */

}

Данный фрагмент кода будет работать корректно в большинстве случаев, однако возможны ситуации, когда второй сигнал придет в промежуток времени между срабатыванием функции-обработчика и восстановлением обработчика посредством вызова signal. В таком случае будет вызван обработчик по умолчанию, что для SIGINT означает завершение процесса.

Для установки обработчика сигнала в ненадежной модели используется следующая функция:

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

В качестве параметров функция signal принимает номер сигнала (допустимы мнемонические макроопределения SIGINT, SIGUSR1 и т.д.) и указатель на функцию-обработчик, которая должна принимать значение типа int (номер сигнала) и не возвращать ничего. Функция signal возвращает указатель на предыдущий обработчик данного сигнала. В качестве второго параметра signal можно указать константы SIG_IGN (игнорировать данный сигнал) или SIG_DFL (установить значение по умолчанию).

Надежная семантика сигналов подразумевает, что ядро ОС гарантирует, что любой сигнал будет доставлен процессу. При этом процесс имеет возможность указать, какие сигналы он будет перехватывать, а какие игнорировать, т.е. составить т.н. маску сигналов.

Установка обработчика в надежной модели производится следующей функцией:

#include <signal.h>

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

Данная функция может как устанавливать новый обработчик, так и возвращать старый. Если параметр act не равен NULL, устанавливается новая реакция на сигнал. Если параметр oact не равен NULL, в этот указатель возвращается старый обработчик. Параметр signo – номер сигнала – аналогичен таковому в функции signal.

В случае неудачной операции sigaction возвращает -1, в противном случае – 0.

Рассмотрим теперь структуру sigaction:

struct sigaction {

void (*sa_handler)(int);

sigset_t sa_mask;

int sa_flags;

void (*sa_sigaction)(int, siginfo_t *, void *);

};

Эта структура содержит следующие поля:

    • sa_handler – указатель на функцию-обработчик. Может принимать значения SIG_IGN и SIG_DFL;

    • sa_mask – дополнительная маска игнорируемых сигналов, которая добавляется к существующей, прежде чем будет вызвана функция-обработчик сигнала. После возврата обработчиком управления маска возвращается в предыдущее состояние;

    • sa_flags – флаги, отвечающие за обработку сигнала. Обычно этот параметр устанавливается в 0;

    • sa_sigaction – альтернативный обработчик сигналов. Используется, если выставлен флаг SA_SIGINFO.

Для генерации сигналов используются две функции: kill() и raise(). Первая служит для отправки сигнала произвольному процессу, вторая – текущему, т.е. самому себе. Синтаксис их вызова таков:

#include <signal.h>

int kill(pid_t pid, int signo);

int raise(int signo);

Здесь pid – идентификатор процесса, которому отправляется сигнал, а signo – номер сигнала.

Семафоры

Сигналы инициируют срабатывание функций-обработчиков в нужное время, что позволяет реализовать произвольную логику взаимодействия процессов. Семафоры – инструмент, разработанный исключительно для реализации механизма критических секций в межпроцессном взаимодействии (IPC, Inter Process Communication).

Семафор представляет собой глобальный системный счетчик, теоретически доступный всем процессам. При этом операции выполняются не над одним семафором, а над набором семафоров (semaphore set).

Создать набор семафоров можно следующей функцией:

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);

Данная функция принимает следующие параметры:

    • key – уникальный ключ, идентифицирующий набор семафоров. Имеет тип длинное целое (long int) и обычно генерируется при помощью функции ftok();

    • nsems – количество семафоров в наборе;

    • flag – флаг, устанавливающий права доступа к набору семафоров.

Функция возвращает идентификатор набора семафоров в случае успешной операции и – 1 при ошибке. При этом данная функция может как создавать новый набор семафоров (flag должен содержать значение IPC_CREAT), так и получать доступ к уже существующему. Для открытия существующего набора семафоров необходимо указать ключ key, идентифицирующий созданный ранее набор, при этом параметр nsems обычно устанавливается в 0.

Операции с семафорами производятся при помощи двух основных функций: semop() и semctl().

Функция semop() работает следующим образом:

#include <sys/sem.h>

int semop(int semid, struct sembuf semoparray[], size_t nops);

Здесь semid – идентификатор набора семафоров, над которым производится операция; semoparray – массив структур типа sembuf, описывающих конкретные операции (если операция одна, массив представляет собой указатель на структуру); nops – количество операций (элементов в массиве).

Рассмотрим структуру sembuf:

struct sembuf {

unsigned short sem_num;

short sem_op;

short sem_flg;

};

Здесь sem_num – номер семафора в наборе, над которым производится операция (начиная с нуля), sem_op – сама операция. При этом действие этого параметра зависит от принимаемого им значения:

    • sem_op > 0: значение параметра прибавляется к текущему значению счетчика семафора;

    • sem_op = 0: операция «дождаться нуля». Процесс переводится в состояние ожидания, пока значение семафора не станет равным нулю;

    • sem_op < 0: модуль значения параметра отнимается от текущего значения счетчика семафора, если при этом результат не становится меньше нуля. В противном случае процесс переводится в состояние ожидания, пока такая операция не станет возможна, т.е. пока значение счетчика не станет больше либо равным модулю sem_op.

При этом, если параметр sem_flg установлен в значение IPC_NOWAIT, процесс в состояние ожидания не переводится, а во всех соответствующих случаях функция semop() возвращает значение -1 и устанавливает переменную errno в значение EAGAIN.

Состояние ожидания процесса прерывается не только установкой семафора в соответствующее состояние, но также в следующих случаях:

    • при удалении набора семафоров, вызвавших блокировку процесса;

    • при получении процессом сигнала, на который установлен обработчик.

Функция semctl() предоставляет расширенный спектр операций над семафорами, позволяя получать статус семафора, получать и устанавливать значение счетчика семафора, удалять набор семафоров и т.д. В общем случае для решения задач межпроцессного взаимодействия достаточно функции semop(), а semctl() обычно используется для удаления набора семафоров, когда в них отпадает надобность. Делать это необходимо, потому что семафоры являются глобальными объектами ядра и сохраняются в системе и после завершения процесса.