Linux ipc что это

Знакомство с межпроцессным взаимодействием на Linux

Межпроцессное взаимодействие (Inter-process communication (IPC)) — это набор методов для обмена данными между потоками процессов. Процессы могут быть запущены как на одном и том же компьютере, так и на разных, соединенных сетью. IPC бывают нескольких типов: «сигнал», «сокет», «семафор», «файл», «сообщение»…

Отступление: данная статья является учебной и расчитана на людей, только еще вступающих на путь системного программирования. Ее главный замысел — познакомиться с различными способами взаимодействия между процессами на POSIX-совместимой ОС.

Именованный канал

Для передачи сообщений можно использовать механизмы сокетов, каналов, D-bus и другие технологии. Про сокеты на каждом углу можно почитать, а про D-bus отдельную статью написать. Поэтому я решил остановиться на малоозвученных технологиях отвечающих стандартам POSIX и привести рабочие примеры.

Рассмотрим передачу сообщений по именованным каналам. Схематично передача выглядит так:

Для создания именованных каналов будем использовать функцию, mkfifo():

Примечание: mode используется в сочетании с текущим значением umask следующим образом: (mode &

umask). Результатом этой операции и будет новое значение umask для создаваемого нами файла. По этой причине мы используем 0777 (S_IRWXO | S_IRWXG | S_IRWXU), чтобы не затирать ни один бит текущей маски.

Как только файл создан, любой процесс может открыть этот файл для чтения или записи также, как открывает обычный файл. Однако, для корректного использования файла, необходимо открыть его одновременно двумя процессами/потоками, одним для получение данных (чтение файла), другим на передачу (запись в файл).

В случае успешного создания FIFO файла, mkfifo() возвращает 0 (нуль). В случае каких либо ошибок, функция возвращает -1 и выставляет код ошибки в переменную errno.

Типичные ошибки, которые могут возникнуть во время создания канала:

  • EACCES — нет прав на запуск (execute) в одной из директорий в пути pathname
  • EEXIST — файл pathname уже существует, даже если файл — символическая ссылка
  • ENOENT — не существует какой-либо директории, упомянутой в pathname, либо является битой ссылкой
  • ENOSPC — нет места для создания нового файла
  • ENOTDIR — одна из директорий, упомянутых в pathname, на самом деле не является таковой
  • EROFS — попытка создать FIFO файл на файловой системе «только-на-чтение»

Чтение и запись в созданный файл производится с помощью функций read() и write().

Пример

mkfifo.c

Мы открываем файл только для чтения (O_RDONLY). И могли бы использовать O_NONBLOCK модификатор, предназначенный специально для FIFO файлов, чтобы не ждать когда с другой стороны файл откроют для записи. Но в приведенном коде такой способ неудобен.

Компилируем программу, затем запускаем ее:

В соседнем терминальном окне выполняем:

В результате мы увидим следующий вывод от программы:

Разделяемая память

Следующий тип межпроцессного взаимодействия — разделяемая память (shared memory). Схематично изобразим ее как некую именованную область в памяти, к которой обращаются одновременно два процесса:

Для выделения разделяемой памяти будем использовать POSIX функцию shm_open():

Функция возвращает файловый дескриптор, который связан с объектом памяти. Этот дескриптор в дальнейшем можно использовать другими функциями (к примеру, mmap() или mprotect()).

Целостность объекта памяти сохраняется, включая все данные связанные с ним, до тех пор пока объект не отсоединен/удален (shm_unlink()). Это означает, что любой процесс может получить доступ к нашему объекту памяти (если он знает его имя) до тех пор, пока явно в одном из процессов мы не вызовем shm_unlink().

Переменная oflag является побитовым «ИЛИ» следующих флагов:

  • O_RDONLY — открыть только с правами на чтение
  • O_RDWR — открыть с правами на чтение и запись
  • O_CREAT — если объект уже существует, то от флага никакого эффекта. Иначе, объект создается и для него выставляются права доступа в соответствии с mode.
  • O_EXCL — установка этого флага в сочетании с O_CREATE приведет к возврату функцией shm_open ошибки, если сегмент общей памяти уже существует.

Как задается значение параметра mode подробно описано в предыдущем параграфе «передача сообщений».

После создания общего объекта памяти, мы задаем размер разделяемой памяти вызовом ftruncate(). На входе у функции файловый дескриптор нашего объекта и необходимый нам размер.

Пример

Следующий код демонстрирует создание, изменение и удаление разделяемой памяти. Так же показывается как после создания разделяемой памяти, программа выходит, но при следующем же запуске мы можем получить к ней доступ, пока не выполнен shm_unlink().

shm_open.c

После создания объекта памяти мы установили нужный нам размер shared memory вызовом ftruncate(). Затем мы получили доступ к разделяемой памяти при помощи mmap(). (Вообще говоря, даже с помощью самого вызова mmap() можно создать разделяемую память. Но отличие вызова shm_open() в том, что память будет оставаться выделенной до момента удаления или перезагрузки компьютера.)

Компилировать код на этот раз нужно с опцией -lrt:

Смотрим что получилось:

Аргумент «create» в нашей программе мы используем как для создания разделенной памяти, так и для изменения ее содержимого.

Зная имя объекта памяти, мы можем менять содержимое разделяемой памяти. Но стоит нам вызвать shm_unlink(), как память перестает быть нам доступна и shm_open() без параметра O_CREATE возвращает ошибку «No such file or directory».

Семафор

Семафор — самый часто употребляемый метод для синхронизации потоков и для контролирования одновременного доступа множеством потоков/процессов к общей памяти (к примеру, глобальной переменной). Взаимодействие между процессами в случае с семафорами заключается в том, что процессы работают с одним и тем же набором данных и корректируют свое поведение в зависимости от этих данных.

Есть два типа семафоров:

  1. семафор со счетчиком (counting semaphore), определяющий лимит ресурсов для процессов, получающих доступ к ним
  2. бинарный семафор (binary semaphore), имеющий два состояния «0» или «1» (чаще: «занят» или «не занят»)

Рассмотрим оба типа семафоров.

Семафор со счетчиком

Смысл семафора со счетчиком в том, чтобы дать доступ к какому-то ресурсу только определенному количеству процессов. Остальные будут ждать в очереди, когда ресурс освободится.

Итак, для реализации семафоров будем использовать POSIX функцию sem_open():

В функцию для создания семафора мы передаем имя семафора, построенное по определенным правилам и управляющие флаги. Таким образом у нас получится именованный семафор.
Имя семафора строится следующим образом: в начале идет символ «/» (косая черта), а следом латинские символы. Символ «косая черта» при этом больше не должен применяться. Длина имени семафора может быть вплоть до 251 знака.

Если нам необходимо создать семафор, то передается управляющий флаг O_CREATE. Чтобы начать использовать уже существующий семафор, то oflag равняется нулю. Если вместе с флагом O_CREATE передать флаг O_EXCL, то функция sem_open() вернет ошибку, в случае если семафор с указанным именем уже существует.

Параметр mode задает права доступа таким же образом, как это объяснено в предыдущих главах. А переменной value инициализируется начальное значение семафора. Оба параметра mode и value игнорируются в случае, когда семафор с указанным именем уже существует, а sem_open() вызван вместе с флагом O_CREATE.

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

Пример семафора со счетчиком

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

sem_open.c

В одной консоли запускаем:

В соседней консоли запускаем:

Бинарный семафор

Вместо бинарного семафора, для которого так же используется функция sem_open, я рассмотрю гораздо чаще употребляемый семафор, называемый «мьютекс» (mutex).

Мьютекс по существу является тем же самым, чем является бинарный семафор (т.е. семафор с двумя состояниями: «занят» и «не занят»). Но термин «mutex» чаще используется чтобы описать схему, которая предохраняет два процесса от одновременного использования общих данных/переменных. В то время как термин «бинарный семафор» чаще употребляется для описания конструкции, которая ограничивает доступ к одному ресурсу. То есть бинарный семафор используют там, где один процесс «занимает» семафор, а другой его «освобождает». В то время как мьютекс освобождается тем же процессом/потоком, который занял его.

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

Для использования мьютекса необходимо вызвать функцию pthread_mutex_init():

Функция инициализирует мьютекс (перемнную mutex) аттрибутом mutexattr. Если mutexattr равен NULL, то мьютекс инициализируется значением по умолчанию. В случае успешного выполнения функции (код возрата 0), мьютекс считается инициализированным и «свободным».

Типичные ошибки, которые могут возникнуть:

  • EAGAIN — недостаточно необходимых ресурсов (кроме памяти) для инициализации мьютекса
  • ENOMEM — недостаточно памяти
  • EPERM — нет прав для выполнения операции
  • EBUSY — попытка инициализировать мьютекс, который уже был инициализирован, но не унечтожен
  • EINVAL — значение mutexattr не валидно

Чтобы занять или освободить мьютекс, используем функции:

Функция pthread_mutex_lock(), если mutex еще не занят, то занимает его, становится его обладателем и сразу же выходит. Если мьютекс занят, то блокирует дальнейшее выполнение процесса и ждет освобождения мьютекса.
Функция pthread_mutex_trylock() идентична по поведению функции pthread_mutex_lock(), с одним исключением — она не блокирует процесс, если mutex занят, а возвращает EBUSY код.
Фунция pthread_mutex_unlock() освобождает занятый мьютекс.

Коды возврата для pthread_mutex_lock():

  • EINVAL — mutex неправильно инициализирован
  • EDEADLK — мьютекс уже занят текущим процессом

Коды возврата для pthread_mutex_trylock():

  • EBUSY — мьютекс уже занят
  • EINVAL — мьютекс неправильно инициализирован

Коды возврата для pthread_mutex_unlock():

  • EINVAL — мьютекс неправильно инициализирован
  • EPERM — вызывающий процесс не является обладателем мьютекса

Пример mutex

mutex.c

Данный пример демонстрирует совместный доступ двух потоков к общей переменной. Один поток (первый поток) в автоматическом режиме постоянно увеличивает переменную counter на единицу, при этом занимая эту переменную на целую секунду. Этот первый поток дает второму доступ к переменной count только на 10 миллисекунд, затем снова занимает ее на секунду. Во втором потоке предлагается ввести новое значение для переменной с терминала.

Если бы мы не использовали технологию «мьютекс», то какое значение было бы в глобальной переменной, при одновременном доступе двух потоков, нам не известно. Так же во время запуска становится очевидна разница между pthread_mutex_lock() и pthread_mutex_trylock().

Компилировать код нужно с дополнительным параметром -lpthread:

Запускаем и меняем значение переменной просто вводя новое значение в терминальном окне:

Вместо заключения

В следующих статьях я хочу рассмотреть технологии d-bus и RPC. Если есть интерес, дайте знать.
Спасибо.

UPD: Обновил 3-ю главу про семафоры. Добавил подглаву про мьютекс.

Источник

Linux ipc что это

В этой главе описываются механизмы IPC (Inter Process Communication) — семафоры, разделяемая память и очередь сообщений, реализованные в ядре Linux 2.4. Данная глава разбита на 4 раздела. В первых трех разделах рассматривается реализация семафоров, очереди сообщений, и разделяемой памяти соответственно. В последнем разделе описывается набор общих, для всех трех вышеуказанных механизмов, функций и структур данных.

5.1 Семафоры

Функции, описываемые в данном разделе, реализуют механизм семафоров пользовательского уровня. Обратите внимание на то, что реализация пользовательских семафоров основывается на использовании блокировок и семафоров ядра. Во избежание неоднозначности в толковании под термином «семафор ядра» будут пониматься семафоры уровня ядра. Во всех других случаях под термином «семафор» следует понимать семафоры пользовательского уровня.

Семафоры. Интерфейс системных вызовов.

sys_semget()

Вызов sys_semget() защищен глобальным семафором ядра sem_ids.sem.

Для создания и инициализации нового набора семафоров вызывается функция newary(). В вызывающую программу возвращается ID нового набора семафоров.

Если через аогумент key передается существующий набор семафоров, вызывается функция поиска ipc_findkey() заданного набора. Перед возвратом ID проверяются права доступа вызывающей программы.

sys_semctl()

Для выполнения команд IPC_INFO, SEM_INFO, и SEM_STAT вызывает функцию semctl_nolock().

Для выполнения команд IPC_RMID и IPC_SET вызывает функцию semctl_down(). При этом выполняется захват глобального семафора ядра sem_ids.sem.

sys_semop()

После проверки входных параметров данные копируются из пространства пользователя во временный буфер. Если объем информации невелик, то буфер размещается на стеке, в противном случае в памяти размещается буфер большего размера. После копирования данных выполняется глобальная блокировка семафоров, проверяется ID, указанного пользователем набора семафоров и права доступа.

Производится разбор всех, указанных пользователем, операций. В ходе разбора обслуживается счетчик для всех операций, для которых установлен флаг SEM_UNDO. Устанавливается флаг decrease , если какая либо операция выполняет уменьшение значения семафора, и устанавливается флаг alter , если значение любого из семафоров изменяется (т.е. увеличивается или уменьшается). Так же проверяется и номер каждого модифицируемого семафора.

Если установлен флаг SEM_UNDO для какой либо из операций, то в списке отыскивается структура отката текущей задачи (процесса), ассоциированной с данным набором семафоров. Если в ходе поиска в списке обнаруживается структура отката для несуществующего набора семафоров ( un->semid == -1 ), то занимаемая память освобождается и структура исключается из списка вызовом функции freeundos(). Если структура для заданного набора семафоров не найдена, то вызовом alloc_undo() создается и инициализируется новая.

Для выполнения последовательности операций вызывается функция try_atomic_semop() с входным параметром do_undo равным нулю. Возвращаемое значение свидетельствует о выполнении последовательности операций — либо последовательность была выполнена, либо была ошибка при выполнении, либо последовательность не была выполнена потому что один из семафоров был заблокирован. Каждый из этих случаев описывается ниже:

Незаблокированные операции над семафорами

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

Ошибка при выполнении операций над семафорами

Отрицательное значение, возвращаемое функцией try_atomic_semop(), свидетельствует о возникновении ошибки. В этом случае ни одна из операций не выполняется. Ошибка возникает когда операция приводит к недопустимому значению семафора либо когда операция, помеченная как IPC_NOWAIT, не может быть завершена. Код ошибки возвращается в вызывающую программу.

Перед возвратом из sys_semop() вызывается update_queue(), которая проходит через очередь операций, ожидающих выполнения, для данного набора семафоров и активирует все ожидающие процессы, которые больше не нужно блокировать.

Заблокированные операции над семафорами

Возвращаемое значение из try_atomic_semop(), равное 1 означает, что последовательность операций не была выполнена из-за того, что один из семафоров был заблокирован. В этом случае инициализируется новый элемент очереди sem_queue содержимым данной последовательности операций. Если какая либо из операций изменяет состояние семафора, то новый элемент добавляется в конец очереди, в противном случае новый элемент добавляется в начало очереди.

В поле semsleeping текущей задачи заносится указатель на очередь ожидания sem_queue. Задача переводится в состояние TASK_INTERRUPTIBLE и поле sleeper структуры sem_queue инициализируется указателем на текущую задачу. Далее снимается глобальная блокировка семафора и вызывается планировщик schedule(), чтобы перевести задачу в разряд «спящих».

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

  • Если набор семафоров был удален, то в вызывающую программу возвращается код ошибки EIDRM.
  • Если поле status в структуре sem_queue установлено в 1, то задача была разбужена для повторной попытки выполнения операций над семафорами. В этом случае повторно вызывается try_atomic_semop() для выполнения последовательности операций Если try_atomic_semop() вернула 1, то задача снова блокируется, как описано выше. Иначе возвращается 0 либо соответствующий код ошибки. Перед выходом из sys_semop() сбрасывается поле current->semsleeping и sem_queue удаляется из очереди. Если какая либо из операций производила изменения (увеличение или уменьшение), то вызывается update_queue(), которая проходит через очередь операций, ожидающих выполнения, для данного набора семафоров и активирует все ожидающие процессы, которые больше не нужно блокировать.
  • Если поле status в структуре sem_queueНЕ установлено в 1 и sem_queue не была удалена из очереди, то это означает, что задача была разбужена по прерыванию. В этом случае в вызывающую программу возвращается код ошибки EINTR. Перед возвратом сбрасывается поле current->semsleeping и sem_queue удаляется из очереди. А так же вызывается update_queue(), если какая либо из операций производила изменения.
  • Если поле status в структуре sem_queueНЕ установлено в 1, а sem_queue была удалена из очереди, то это означает, что заданная последовательность операций уже была выполнена в update_queue(). Поле status содержит либо 0, либо код ошибки, это значение и возвращается в вызывающую программу.

Структуры даных поддержки механизма семафоров

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

struct sem_array

struct sem

struct seminfo

struct semid64_ds

struct sem_queue

struct sembuf

struct sem_undo

Функции для работы с семафорами

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

newary()

newary() обращается к ipc_alloc() для распределения памяти под новый набор семафоров. Она распределяет объем памяти достаточный для размещения дескриптора набора и всего набора семафоров. Распределенная память очищается и адрес первого элемента набора семафоров передается в ipc_addid(). Функция ipc_addid() резервирует память под массив элементов нового набора семафоров и инициализирует ( struct kern_ipc_perm) набор. Глобальная переменная used_sems увеличивается на количество семафоров в новом наборе и на этом инициализация данных ( struct kern_ipc_perm) для нового набора завершается. Дополнительно выполняются следующие действия:

  • В поле sem_base заносится адрес первого семафора в наборе.
  • Очередь sem_pending объявляетяс пустой.

Все операции, следующие за вызовом ipc_addid(), выполняются под глобальной блокировкой семафоров. После снятия блокировки вызывается ipc_buildid() (через sem_buildid()). Эта функция создает уникальный ID (используя индекс дескриптора набора семафоров), который и возвращается в вызывающую программу.

freeary()

Функция freeary() вызывается из semctl_down() для выполнения действий, перечисленных ниже. Вызов функции осуществляется под глобальной блокировкой семафоров, возврат управления происходит со снятой блокировкой.

  • Вызывается ipc_rmid() (через «обертку» sem_rmid()), чтобы удалить ID набора семафоров и получить указатель на набор семафоров.
  • Аннулируется список откатов для данного набора семафоров
  • Все ожидающие процессы пробуждаются, чтобы получить код ошибки EIDRM.
  • Общее количество семафоров уменьшается на количество семафоров в удаляемом наборе.
  • Память, занимаемая набором семафоров, освобождается.

semctl_down()

Функция semctl_down() предназначена для выполнения операций IPC_RMID и IPC_SET системного вызова semctl(). Перед выполнением этих операций проверяется ID набора семафоров и права доступа. Обе эти операции выполняются под глобальной блокировкой семафоров.

IPC_RMID

Операция IPC_RMID вызывает freeary() для удаления набора семафоров.

IPC_SET

Операция IPC_SET изменяет элементы uid , gid , mode и ctime в наборе семафоров.

semctl_nolock()

Функция semctl_nolock() вызывается из sys_semctl() для выполнения операций IPC_INFO, SEM_INFO и SEM_STAT.

IPC_INFO и SEM_INFO

Операции IPC_INFO и SEM_INFO заполняют временный буфер seminfo статическими данными. Затем под глобальной блокировкой семафора ядра sem_ids.sem заполняются элементы semusz и semaem структуры seminfo в соответствии с требуемой операцией (IPC_INFO или SEM_INFO) и в качестве результата возвращается максимальный ID.

SEM_STAT

Операция SEM_STAT инициализирует временный буфер semid64_ds. На время копирования значений sem_otime , sem_ctime , и sem_nsems в буфер выполняется глобальная блокировка семафора. И затем данные копируются в пространство пользователя.

semctl_main()

Функция semctl_main() вызывается из sys_semctl() для выполнения ряда операций, которые описаны ниже. Перед выполнением операций, semctl_main() блокирует семафор и проверяет ID набора семафоров и права доступа. Перед возвратом блокировка снимается.

GETALL

Операция GETALL загружает текущие значения семафора во временный буфер ядра и затем копирует его в пространство пользователя. При небольшом объеме данных, временный буфер размещается на стеке, иначе блокировка временно сбрасывается, чтобы распределить в памяти буфер большего размера. Копирование во временный буфер производится под блокировкой.

SETALL

Операция SETALL копирует значения семафора из пользовательского пространства во временный буфер и затем в набор семафоров. На время копирования из пользовательского пространства во временный буфер и на время проверки значений блокировка сбрасывается. При небольшом объеме данных, временный буфер размещается на стеке, иначе в памяти размещается буфер большего размера. На время выполнения следующих действий блокировка восстанавливается:

  • Информация копируется в набор семафоров.
  • Очищается очередь откатов для заданного набора семафоров.
  • Устанавливается значение sem_ctime для набора семафоров.
  • Вызывается функция update_queue() , которая проходит по списку ожидающих операций в поисках задач, которые могут быть завершены в результате выполнения операции SETALL. Будятся любые ожидающие задачи, которые больше не нужно блокировать.

IPC_STAT

Операция IPC_STAT копирует значения sem_otime , sem_ctime и sem_nsems во временный буфер на стеке. После снятия блокировки данные копируются в пользовательское пространство.

GETVAL

Операция GETVAL возвращает значение заданного семафора.

GETPID

Операция GETPID возвращает pid последней операции, выполненной над семафором.

GETNCNT

Операция GETNCNT возвращает число процессов, ожидающих на семафоре, когда тот станет меньше нуля. Это число подсчитывается функцией count_semncnt().

GETZCNT

Операция GETZCNT возвращает число процессов, ожидающих на семафоре, когда тот станет равным нулю. Это число подсчитывается функцией count_semzcnt().

SETVAL

Проверяет новое значение семафора и выполняет следующие действия:

  • В очереди отката отыскиваются любые корректировки данного семафора и эти корректировки сбрасываются в ноль.
  • Значение семафора устанавливается в заданное.
  • Корректируется значение sem_ctime .
  • Вызывается функция update_queue(), которая проходит по очереди ожидающих операций в поисках тех из них, которые могут быть завершены в результате выполнения операции SETVAL. Все задачи которые оказываются больше незаблокированными — пробуждаются.

count_semncnt()

count_semncnt() возвращает число процессов, ожидающих на семафоре, когда тот станет меньше нуля.

count_semzcnt()

count_semzcnt() возвращает число процессов, ожидающих на семафоре, когда тот станет равным нулю.

update_queue()

update_queue() проходит по очереди ожидающих операций заданного набора семафоров и вызывает try_atomic_semop() для каждой последовательности операций. Если статус элемента очереди показывает, что заблокированная задача уже была разбужена, то такой элемент пропускается. В качестве аргумента do_undo в функцию try_atomic_semop() передается флаг q-alter , который указывает на то, что любые изменяющие операции необходимо «откатить» перед возвратом управления.

Если последовательность операций заблокирована, то update_queue() возвращает управление без внесения каких-либо изменений.

Последовательность операций может потерпеть неудачу, если в результате какой либо из них семафор примет недопустимое значение или если операция имеющая флаг IPC_NOWAIT не может быть завершена. В таком случае задача, ожидающая выполнения заданной последовательности операций, активируется а в поле статуса очереди заносится соответствующий код ошибки и элемент удаляется из очереди.

Если последовательность операций не предполагает внесения изменений, то в качестве аргумента do_undo в функцию try_atomic_semop() передается ноль. Если выполнение этих операций увенчалось успехом, то они считаются выполненными и удаляются из очереди. Ожидающая задача активируется, а в поле status ей передается признак успешного завершения операций.

Если последовательность операций, которая предполагает внесение изменений в значение семафоров, признана успешной, то статус очереди принимает значение 1, чтобы активировать задачу. Последовательность операций не выполняется и не удаляется из очереди, она будет выполняться разбуженной задачей.

try_atomic_semop()

Функция try_atomic_semop() вызывается из sys_semop() и update_queue() и пытается выполнить каждую из операций в последовательности.

Если была встречена заблокированная операция, то процесс исполнения последовательности прерывается и все операции «откатываются». Если последовательность имела флаг IPC_NOWAIT, то возвращается код ошибки -EAGAIN. Иначе возвращается 1 для индикации того, что последовательность операций заблокирована.

Если значение семафора вышло за рамки системных ограничений, то выполняется «откат» всех операций и возвращается код ошибки -ERANGE.

Если последовательность операций была успешно выполнена и при этом аргумент do_undo не равен нулю, то выполняется «откат» всех операций и возвращается 0. Если аргумент do_undo равен нулю, то результат операций остается в силе и обновляется поле sem_otime .

sem_revalidate()

Функция sem_revalidate() вызывается, когда глобальная блокировка семафора временно была снята и необходимо снова ее получить. Вызывается из semctl_main() и alloc_undo(). Производит проверку ID семафора и права доступа, в случае успеха выполняет глобальную блокировку семафора.

freeundos()

Функция freeundos() проходит по списку «откатов» процесса в поисках заданной структуры. Если таковая найдена, то она изымается из списка и память, занимаемая ею, освобождается. В качестве результата возвращается указатель на следующую структуру «отката»в списке.

alloc_undo()

Вызов функции alloc_undo() должен производиться под установленной глобальной блокировкой семафора. В случае возникновения ошибки — функция завершает работу со снятой блокировкой.

Перед тем как вызовом kmalloc() распределить память под структуру sem_undo и массив корректировок, блокировка снимается. Если память была успешно выделена, то она восстанавливается вызовом sem_revalidate().

Далее новая структура инициализируется, указатель на структуру размещается по адресу, указанному вызывающей программой, после чего структура вставляется в начало списка «откатов» текущего процесса.

sem_exit()

Функция sem_exit() вызывается из do_exit() и отвечает за выполнение всех «откатов» по завершении процесса.

Если процесс находится в состоянии ожидания на семафоре, то он удаляется из списка sem_queue при заблокированном семафоре.

Производится просмотр списка «откатов» текущего процесса и для каждого элемента списка выполняются следующие действия:

  • Проверяется структура «отката» и ID набора семафоров.
  • В списке «откатов» соответствующего набора семафоров отыскиваются ссылки на структуры, которые удаляются из списка.
  • К набору семафоров применяются корректировки из структуры «отката».
  • Обновляется поле sem_otime в наборе семафоров.
  • Вызывается update_queue(), которая просматривает список отложенных операций и активирует задачи, которые могут быть разблокированы в результате «отката».
  • Память, занимаемая структурой, освобождается.

По окончании обработки списка очищается поле current->semundo.

5.2 Очереди сообщений

Интерфейс системных вызовов

sys_msgget()

На входе в sys_msgget() захватывается глобальный семафор очереди сообщений ( msg_ids.sem).

Для создания новой очереди сообщений вызывается функция newque(), которая создает и инициализирует новую очередь и возвращает ID новой очереди.

Если значение параметра key представляет существующую очередь, то вызывается ipc_findkey() для поиска соответствующего индекса в глобальном массиве дескрипторов очередей сообщений (msg_ids.entries). перед возвратом ID очереди производится проверка параметров и прав доступа. И поиск и проверки выполняются под блокировкой (msg_ids.ary).

sys_msgctl()

Функции sys_msgctl() передаются следующие параметры: ID очереди сообщений ( msqid ), код операции ( cmd ) и указатель на буфер в пользовательском пространстве типа msgid_ds ( buf ). Функция принимает шесть кодов операций: IPC_INFO, MSG_INFO,IPC_STAT, MSG_STAT, IPC_SET и IPC_RMID. После проверки ID очереди и кода операции выполняются следующие действия:

IPC_INFO ( или MSG_INFO)

Глобальная информация очереди сообщений копируется в пользовательское пространство.

IPC_STAT ( или MSG_STAT)

Инициализируется временный буфер типа struct msqid64_ds и выполняется глобальная блокировка очереди сообщений. После проверки прав доступа вызывающего процесса во временный буфер записывается информация о заданной очереди и глобальная блокировка очереди сообщений освобождается. Содержимое буфера копируется в пользовательское пространство вызовом copy_msqid_to_user().

IPC_SET

Пользовательские данные копируются через вызов copy_msqid_to_user(). Производится захват глобального семафора очереди сообщений и устанавливается блокировка очереди, которые в конце отпускаются. После проверки ID очереди и прав доступа текущего процесса, производится обновление информации. Далее, вызовом expunge_all() и ss_wakeup() активируются все процессы-получатели и процессы-отправители, находящиеся в очередях ожидания msq->q_receivers и msq->q_senders соответственно.

IPC_RMID

Захватывается глобальный семафор очереди сообщений и устанавливается глобальная блокировка очереди сообщений. После проверки ID очереди и прав доступа текущего процесса вызывается функция freeque(), которая освобождает ресурсы, занятые очередью. После этого глобальный семафор и глобальная блокировка отпускаются.

sys_msgsnd()

Функция sys_msgsnd() принимает через входные параметры ID очереди сообщений ( msqid ), указатель на буфер типа struct msg_msg ( msgp ), размер передаваемого сообщения ( msgsz ) и флаг — признак разрешения перехода процесса в режим ожидания ( msgflg ). Каждая очередь сообщений имеет две очереди ожидания для процессов и одну очередь ожидающих сообщений. Если в очереди ожидания имеется процесс, ожидающий данное сообщение ( msgp ), то это сообщение передается непосредственно ожидающему процессу, после чего процесс-получатель «пробуждается». В противном случае производится проверка — достаточно ли места в очереди ожидающих сообщений и если достаточно, то сообщение сохраняется в этой очереди. Если же места недостаточно, то процесс-отправитель ставится в очередь ожидания. Более подробное освещение этих действий приводится ниже:

  1. Производится проверка адреса пользовательского буфера и типа сообщения, после чего содержимое буфера копируется вызовом load_msg() во временный буфер msg типа struct msg_msg. Инициализируются поле типа сообщения и поле размера сообщения.
  2. Выполняется глобальная блокировка очереди сообщений и по ID очереди отыскивается ее дескриптор. Если такой очереди не было найдено, то в вызывающую программу возвращается код ошибки EINVAL.
  3. В функции ipc_checkid() (вызывается из msg_checkid()) проверяется ID очереди сообщений и права доступа процесса вызовом ipcperms().
  4. Производится проверка — достаточно ли места в очереди ожидающих сообщений для помещения туда данного сообщения. Если места недостаточно, то выполняются следующие действия:
    1. Если установлен флаг IPC_NOWAIT (в msgflg ) то глобальная блокировка очереди сообщений снимается, память, занимаемая сообщением, освобождается и возвращается код ошибки EAGAIN.
    2. Вызовом ss_add() текущий процесс помещается в очередь ожидания для процессов-отправителей. Так же снимается блокировкаи вызывается планировщик schedule() который переведет процесс в состояние «сна».
    3. После «пробуждения» снова выполняется глобальная блокировка очереди сообщений и проверяется ID очереди сообщений. Если во время «сна» очередь была удалена, то процессу возвращается признак ERMID.
    4. Если для данного процесса имеются какие либо сигналы, ожидающие обработки, то вызовом ss_del() процесс изымается из очереди ожидания для процессов-отправителей, блокировка снимается, вызывается free_msg() для освобождения буфера сообщения и процессу возвращается код EINTR. Иначе осуществляется переход обратно (к п.3) на выполнение необходимых проверок.
  5. Для передачи сообщения напрямую процессу-получателю вызывается pipelined_send() .
  6. Если процессов-получателей, ожидающих данное сообщение, не было обнаружено, то сообщение msg помещается в очередь ожидающих сообщений (msq->q_messages). Обновляются поля q_cbytes и q_qnum в дескрипторе очереди сообщений, а так же глобальные переменные msg_bytes и msg_hdrs , содержащие в себе общий объем сообщений в байтах и общее количество сообщений.
  7. Если сообщение было благополучно передано процессу-получателю либо поставлено в очередь, то обновляются поля q_lspid и q_stime в дескрипторе очереди и освобождается глобальная блокировка.

sys_msgrcv()

На вход функции sys_msgrcv() передаются ID очереди ( msqid ), указатель на буфер типа msg_msg ( msgp ), предполагаемый размер сообщения ( msgsz ), тип сообщения ( msgtyp ) и флаги ( msgflg ). Функция, по очереди ожидающих сообщений, ищет сообщение с заданным типом и первое же найденное сообщение копирует в пользовательский буфер. Если сообщения с заданным типом не обнаружено, то процесс-получатель заносится в очередь ожидания для процессов-получателей и остается там до тех пор, пока не будет получено ожидаемое сообщение. Более подробное описание действий функции sys_msgrcv() приводится ниже:

  1. В первую очередь вызывается функция convert_mode(), которая устанавливает режим поиска, исходя из значения msgtyp . Далее выполняется глобальная блокировка очереди сообщений и находится дескриптор очереди по заданному ID. Если искомая очередь сообщений не найдена, то возвращается код ошибки EINVAL.
  2. Проверяются права доступа текущего процесса.
  3. Для каждого сообщения, начиная с первого, в очереди ожидающих сообщений вызывается testmsg(), которая проверяет тип сообщения на соответствие заданному. Поиск продолжается до тех пор пока искомое сообщение не будет найдено либо пока не будет встречен конец очереди. Если режим поиска задан как SEARCH_LESSEQUAL, то результатом поиска будет первое же встретившееся сообщение с типом равным или меньшим msgtyp .
  4. Если сообщение, удовлетворяющее критериям поиска, было найдено, то далее выполняются следующие действия:
    1. Если размер сообщения больше чем ожидаемый и установлен флаг MSG_NOERROR, то глобальная блокировка снимается и в вызывающий процесс передается код E2BIG.
    2. Сообщение удаляется из очереди ожидающих сообщений и обновляются статистики очереди сообщений.
    3. Активируются процессы, находящиеся в очереди ожидания для процессов-отправителей. Удаление сообщения из очереди на предыдущем шаге делает возможным продолжить работу одному из процессов-отправителей. Переход к выполнению заключительных операций (к п. 10)
  5. Если сообщение не было найдено, то проверяется msgflg . Если установлен флаг IPC_NOWAIT, то глобальная блокировка снимается и вызывающему процессу возвращается код ENOMSG. В противном случае процесс помещается в очередь ожидания для процессов-получателей:
    1. В памяти размещается новая структура msg_receiver msr и добавляется в начало очереди ожидания.
    2. В поле r_tsk в msr заносится указатель на текущий процесс.
    3. В поле r_msgtype и r_mode заносятся ожидаемый тип сообщения и режим поиска соответственно.
    4. Если установлен флаг MSG_NOERROR, то в поле r_maxsize заносится значение из msgsz , в противном случае — значение INT_MAX.
    5. В поле r_msg заносится признак того, что сообщение не найдено.
    6. После завершения инициализации, процесс приобретает статус TASK_INTERRUPTIBLE, глобальная блокировка очереди сообщений снимается и вызывается планировщик schedule().
  6. После активизации ожидающего процесса сразу же проверяется поле r_msg . Это поле содержит либо сообщение переданное напрямую, либо код ошибки. Если поле содержит сообщение то далее переходим к заключительным операциям (к п. 10). В противном случае — опять выполняется глобальная блокировка.
  7. После того как блокировка установлена, поле r_msg проверяется еще раз. Если в процессе установки блокировки было получено сообщение, то производится переход к заключительным операциям (к п. 10).
  8. Если поле r_msg осталось без изменений, то, следовательно, процесс был активирован для выполнения повторной попытки получить сообщение. Проверяется наличие необработанных сигналов для данного процесса и если таковые имеются, то глобальная блокировка снимается и процессу возвращается код EINTR. Иначе — производится повторная попытка получить сообщение.
  9. Если поле r_msg содержит код ошибки, то снимается глобальная блокировка и процессу передается ошибка.
  10. После проверки адреса пользовательского буфера msp , тип сообщения записывается в mtype и содержимое сообщения копируется в поле mtext функцией store_msg(). И в заключение освобождается память вызовом функции free_msg().

Источник

Читайте также:  Mac os пароль внести изменения
Оцените статью