- •Системное программное обеспечение эвм
- •Часть 1 в. А. Супонев, а. А. Уваров, в. А. Прытков Операционные системы
- •Содержание
- •Введение
- •1.1. Знакомство с Linux
- •1.2. Понятие процессов
- •1.2.1. Linux
- •1.2.2. Windows
- •1.3. Задание
- •2.1. Linux
- •2.2. Windows
- •2.3. Задание
- •3.1. Linux
- •3.2. Windows
- •3.3. Задание
- •4.1. Linux
- •4.2. Windows
- •4.3. Задание
- •5.1. Асинхронные файловые операции
- •5.1.1. Linux
- •5.1.2. Windows
- •5.2. Динамические библиотеки
- •5.2.1. Linux
- •5.2.2. Windows
- •5.3. Задание
- •6.1. Общие сведения
- •6.2. Задание
- •7.1. Общие сведения
- •7.2. Задание
- •Литература
- •Часть 1. Операционные системы
- •220013, Минск, п. Бровки, 6
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() обычно используется для удаления набора семафоров, когда в них отпадает надобность. Делать это необходимо, потому что семафоры являются глобальными объектами ядра и сохраняются в системе и после завершения процесса.
