Рейтинг@Mail.ru
[Войти] [Зарегистрироваться]

Наши друзья и партнеры

UnixForum


Lines Club

Ищем достойных соперников.




Книги по Linux (с отзывами читателей)

Библиотека сайта или "Мой Linux Documentation Project"

Вперед Назад Содержание

5. Механизмы 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().

Для выполнения команд GETALL, GETVAL, GETPID, GETNCNT, GETZCNT, IPC_STAT, SETVAL, и SETALL вызывает функцию semctl_main() .

Для выполнения команд 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_array {
    struct kern_ipc_perm sem_perm; /* права доступа .. см. ipc.h */
    time_t sem_otime;              /* время последнего обращения */
    time_t sem_ctime;              /* время последнего изменения */
    struct sem *sem_base;          /* указатель на первый семафор в массиве */
    struct sem_queue *sem_pending; /* операции, ожидающие исполнения */
    struct sem_queue **sem_pending_last; /* последняя ожидающая операция */
    struct sem_undo *undo;         /* список отката для данного массива * /
    unsigned long sem_nsems;       /* кол-во семафоров в массиве */
};


struct sem


/* По одной структуре для каждого семафора в системе. */
struct sem {
        int     semval;         /* текущее значение */
        int     sempid;         /* pid последней операции */
};


struct seminfo


struct  seminfo {
        int semmap;
        int semmni;
        int semmns;
        int semmnu;
        int semmsl;
        int semopm;
        int semume;
        int semusz;
        int semvmx;
        int semaem;
};


struct semid64_ds


struct semid64_ds {
        struct ipc64_perm sem_perm; /* права доступа .. см. ipc.h */
        __kernel_time_t sem_otime;  /* время последнего обращения */
        unsigned long   __unused1;  
        __kernel_time_t sem_ctime;  /* время последнего изменения */
        unsigned long   __unused2;
        unsigned long   sem_nsems;  /* кол-во семафоров в массиве */
        unsigned long   __unused3;
        unsigned long   __unused4;
};


struct sem_queue


/* По одной очереди на каждый ожидающий процесс в системе. */
struct sem_queue {
        struct sem_queue *      next;    /* следующий элемент очереди */
        struct sem_queue **     prev;    /* предыдующий элемент очереди, *(q->prev) == q */
        struct task_struct*     sleeper; /* этот процесс */
        struct sem_undo *       undo;    /* структура откатов */
        int                     pid;     /* pid процесса */
        int                     status;  /* результат выполнения операции */
        struct sem_array *      sma;     /* массив семафоров для выполнения операций */
        int                     id;      /* внутренний sem id */
        struct sembuf *         sops;    /* массив ожидающих операций */
        int                     nsops;   /* кол-во операций */
        int                     alter;   /* признак изменения семафора */
};


struct sembuf


/* системный вызов semop берет массив отсюда. */
struct sembuf {
        unsigned short  sem_num;        /* индекс семафора в массиве */
        short           sem_op;         /* операция */
        short           sem_flg;        /* флаги */
};


struct sem_undo


/* Каждая задача имеет список откатов. Откаты выполняются автоматически
 * по завершении процесса.
 */
struct sem_undo {
        struct sem_undo *       proc_next;      /* следующий элемент списка для данного процесса */
        struct sem_undo *       id_next;        /* следующий элемент в данном наборе семафоров */
        int                     semid;          /* ID набора семафоров */
        short *                 semadj;         /* массив изменений, по одному на семафор */
};


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

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

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().

Структуры очередей сообщений

Структуры данных механизма очередей сообщений определены в msg.c.

struct msg_queue


/* по одной структуре msq_queue на каждую очередь сообщений в системе */
struct msg_queue {
        struct kern_ipc_perm q_perm;
        time_t q_stime;                 /* время последнего вызова msgsnd */
        time_t q_rtime;                 /* время последнего вызова msgrcv */
        time_t q_ctime;                 /* время последнего изменения */
        unsigned long q_cbytes;         /* текущий размер очереди в байтах */
        unsigned long q_qnum;           /* количество сообщений в очереди */
        unsigned long q_qbytes;         /* максимальный размер очереди в байтах */
        pid_t q_lspid;                  /* pid последнего процесса вызвавшего msgsnd */
        pid_t q_lrpid;                  /* pid последнего процесса-получателя */

        struct list_head q_messages;
        struct list_head q_receivers;
        struct list_head q_senders;
};


struct msg_msg


/* по одной структуре на каждое сообщение */
struct msg_msg {
        struct list_head m_list;
        long  m_type;
        int m_ts;           /* размер сообщения */
        struct msg_msgseg* next;
        /* Далее следует само сообщение */
};


struct msg_msgseg


/* сегмент сообщения на каждое сообщение */
struct msg_msgseg {
        struct msg_msgseg* next;
        /* Далее следует остальная часть сообщения */
};


struct msg_sender


/* по одной структуре msg_sender на каждый ожидающий процесс-отправитель */
struct msg_sender {
        struct list_head list;
        struct task_struct* tsk;
};


struct msg_receiver


/* по одной структуре msg_receiver на каждый ожидающий процесс-получатель */
struct msg_receiver {
        struct list_head r_list;
        struct task_struct* r_tsk;

        int r_mode;
        long r_msgtype;
        long r_maxsize;

        struct msg_msg* volatile r_msg;
};


struct msqid64_ds


struct msqid64_ds {
        struct ipc64_perm msg_perm;
        __kernel_time_t msg_stime;      /* время последнего вызова msgsnd */
        unsigned long   __unused1;
        __kernel_time_t msg_rtime;      /* время последнего вызова msgrcv */
        unsigned long   __unused2;
        __kernel_time_t msg_ctime;      /* время последнего изменения */
        unsigned long   __unused3;
        unsigned long  msg_cbytes;      /* текущий размер очереди в байтах */
        unsigned long  msg_qnum;        /* количество сообщений в очереди */
        unsigned long  msg_qbytes;      /* максимальный размер очереди в байтах */
        __kernel_pid_t msg_lspid;       /* pid процесса последним вызвавшего msgsnd */
        __kernel_pid_t msg_lrpid;       /* pid последнего процесса-получателя */
        unsigned long  __unused4;
        unsigned long  __unused5;
};


struct msqid_ds


struct msqid_ds {
        struct ipc_perm msg_perm;
        struct msg *msg_first;          /* первое сообщение в очереди, не используется  */
        struct msg *msg_last;           /* последнее сообщение в очереди, не используется*/
        __kernel_time_t msg_stime;      /* время последнего вызова msgsnd */
        __kernel_time_t msg_rtime;      /* время последнего вызова msgrcv */
        __kernel_time_t msg_ctime;      /* время последнего изменения */
        unsigned long  msg_lcbytes;     /* Используется для временного хранения 32 бит */
        unsigned long  msg_lqbytes;     /* то же */
        unsigned short msg_cbytes;      /* текущий размер очереди в байтах */
        unsigned short msg_qnum;        /* количество сообщений в очереди */
        unsigned short msg_qbytes;      /* максимальный размер очереди в байтах */
        __kernel_ipc_pid_t msg_lspid;   /* pid процесса последним вызвавшего msgsnd */
        __kernel_ipc_pid_t msg_lrpid;   /* pid последнего процесса-получателя */
};


msg_setbuf


struct msq_setbuf {
        unsigned long   qbytes;
        uid_t           uid;
        gid_t           gid;
        mode_t          mode;
};


Функции поддержки механизма очередей сообщений

newque()

Функция newque() размещает в памяти новый дескриптор очереди сообщений (struct msg_queue) и вызывает ipc_addid(), которая резервирует элемент массива очередей сообщений за новым дескриптором. Дескриптор очереди сообщений инициализируется следующим образом:

  • Инициализируется структура kern_ipc_perm.
  • В поля q_stime и q_rtime дескриптора заносится число 0. В поле q_ctime заносится CURRENT_TIME.
  • Максимальный размер очереди в байтах (q_qbytes) устанавливается равным MSGMNB, текущий размер очереди в байтах (q_cbytes) устанавливается равным нулю.
  • Очередь ожидающих сообщений (q_messages), очередь ожидания процессов-получателей (q_receivers) и очередь ожидания процессов-отправителей (q_senders) объявляются пустыми.

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

freeque()

Функция freeque() предназначена для удаления очереди сообщений. Функция полагает, что блокировка очереди сообщений уже выполнена. Она освобождает все ресурсы, связанные с данной очередью. Сначала вызывается ipc_rmid() (через msg_rmid()) для удаления дескриптора очереди из массива дескрипторов. Затем вызывается expunge_all для активизации процессов-получателей и ss_wakeup() для активизации процессов-отправителей, находящихся в очередях ожидания. Снимается блокировка очереди. Все сообщения из очереди удаляются и освобождается память, занимаемая дескриптором очереди.

ss_wakeup()

Функция ss_wakeup() активизирует все процессы-отправители, стоящие в заданной очереди ожидания. Если функция вызывается из freeque(), то процессы исключаются из очереди ожидания.

ss_add()

Функция ss_add() принимает в качестве входных параметров указатель на дескриптор очереди сообщений и указатель на структуру msg_sender. Она заносит в поле tsk указатель на текущий процесс, изменяет статус процесса на TASK_INTERRUPTIBLE после чего вставляет структуру msg_sender в начало очереди ожидания процессов-отправителей заданной очереди сообщений.

ss_del()

Удаляет процесс-отправитель из очереди ожидания.

expunge_all()

В функцию expunge_all() передаются дескриптор очереди сообщений (msq) и целочисленное значение (res) которое определяет причину активизации процесса-получателя. Для каждого процесса-получателя из соответствующей очереди ожидания в поле r_msg заносится число res, после чего процесс активируется. Эта функция вызывается в случае ликвидации очереди сообщений или в случае выполнения операций управления очередью сообщений.

load_msg()

Всякий раз, когда процесс передает сообщение, из функции sys_msgsnd(), вызывается load_msg(), которая копирует сообщение из пользовательского пространства в пространство ядра. В пространстве ядра сообщение представляется как связный список блоков данных. В первом блоке размещается структура msg_msg. Размер блока данных, ассоциированного со структурой msg_msg, ограничен числом DATA_MSG_LEN. Блок данных и структура размещаются в непрерывном куске памяти, который не может быть больше одной страницы памяти. Если все сообщение не умещается в первый блок, то в памяти размещаются дополнительные блоки и связываются в список. Размер дополнительных блоков ограничен числом DATA_SEG_LEN и каждый из низ включает в себя структуру msg_msgseg) и связанный блок данных. Блок данных и структура msg_msgseg размещаются в непрерывном куске памяти, который не может быть больше одной страницы памяти. В случае успеха функция возвращает адрес новой структуры msg_msg.

store_msg()

Функция store_msg() вызывается при передаче сообщения в пользовательское пространство. Данные, описываемые структурами msg_msg и msg_msgseg последовательно копируются в пользовательский буфер.

free_msg()

Функция free_msg() освобождает память, занятую сообщением (структурой msg_msg и сегментами сообщения).

convert_mode()

convert_mode() вызывается из sys_msgrcv(). В качестве входных параметров получает указатель на тип сообщения (msgtyp) и флаг (msgflg). Возвращает режим поиска отталкиваясь от значения msgtyp и msgflg. Если в качестве msgtyp передан NULL, то возвращается SEARCH_ANY. Если msgtyp меньше нуля, то в msgtyp заносится абсолютное значение msgtyp и возвращается SEARCH_LESSEQUAL. Если в msgflg установлен флаг MSG_EXCEPT, то возвращается SEARCH_NOTEQUAL, иначе - SEARCH_EQUAL.

testmsg()

Функция testmsg() проверяет сообщение на соответсвтвие заданным критериям. Возвращает 1, если следующие условия соблюдены:

  • Режим поиска - SEARCH_ANY.
  • Режим поиска - SEARCH_LESSEQUAL и сообщение имеет тип меньший либо равный требуемому.
  • Ркжим поиска - SEARCH_EQUAL и тип сообщения равен требуемому.
  • Режим поиска - SEARCH_NOTEQUAL и тип сообщения не равен требуемому.

pipelined_send()

pipelined_send() позволяет процессам передать сообщение напрямую процессам-получателям минуя очередь ожидания. Вызывает testmsg() в процессе помска процесса-получателя, ожидающего данное сообщение. Если таковой найден, то он удаляется из очереди ожидания для процессов-получателей и активируется. Сообщение передается процессу через поле r_msg получателя. Если сообщение было передано получателю, то функция >pipelined_send() возвращает 1. Если процесса-получателя для данного сообщения не нашлось, то возвращается 0.

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

copy_msqid_to_user()

copy_msqid_to_user() копирует содержимое буфера ядра в пользовательский буфер. На входе получает пользовательский буфер, буфер ядра типа msqid64_ds и флаг версии IPC. Если флаг имеет значение IPC_64 то копирование из буфера ядра в пользовательский буфер производится напрямую, в противном случае инициализируется временный буфер типа msqid_ds и данные из буфера ядра переносятся во временный буфер, после чего содержимое временного буфера копируется в буфер пользователя.

copy_msqid_from_user()

Функция copy_msqid_from_user() получает на входе буфер ядра для сообщения типа struct msq_setbuf, пользовательский буфер и флаг версии IPC. В случае IPC_64, copy_from_user() производит копирование данных из пользовательского буфера во временный буфер типа msqid64_ds, после этого заполняются поля qbytes,uid, gid и mode в буфере ядра в соответствии со значениями в промежуточном буфере. В противном случае, в качестве временного буфера используется struct msqid_ds.

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

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

sys_shmget()

Вызов sys_shmget() регулируется глобальным семаформ разделяемой памяти.

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

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

sys_shmctl()

IPC_INFO

Временный буфер shminfo64 заполняется соответствующими значениями и затем копируется в пользовательское пространство вызвавшего приложения.

SHM_INFO

На время сбора статистической информации по разделяемой памяти, производится захват глобального семафора и глобальной блокировки разделяемой памяти. Для подсчета количества страниц, резмещенных резидентно в памяти и количества страниц на устройстве свопинга, вызывается shm_get_stat(). Дополнительно подсчитываются общее количество страниц разделяемой памяти и количество используемых сегментов. Количество swap_attempts и swap_successes жестко зашито в 0. Эти статистики заносятся во временный буфер shm_info и затем копируются в пользовательское пространство вызывающего приложения.

SHM_STAT, IPC_STAT

Для выполнения SHM_STAT и IPC_STAT инициализируется временный буфер типа struct shmid64_ds и выполняется глобальная блокировка разделяемой памяти.

Для случая SHM_STAT, параметр ID сегмента разделяемой памяти трактуется как простой индекс (т.е. как число в диапазоне от 0 до N, где N - количество зарегистрированных ID в системе). После проверки индекса вызывается ipc_buildid() (через shm_buildid()) для преобразования индекса в ID разделяемой памяти, который в данном случае и будет возвращаемым значением. Примечательно, что это обстоятельство не документировано, но используется для поддержки программы ipcs(8).

Для случая IPC_STAT, параметр ID сегмента разделяемой памяти трактуется как нормальный ID, сгенерированный вызовом shmget(). Перед продолжением работы ID проверяется на корректность. В данном случае в качестве результата будет возвращен 0.

Для обоих случаев SHM_STAT и IPC_STAT, проверяются права доступа вызывающей программы. Требуемые статистики загружаются во временный буфер и затем передаются в вызывающую программу.

SHM_LOCK, SHM_UNLOCK

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

IPC_RMID

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

IPC_SET

После проверки ID сегмента разделяемой памяти и прав доступа, uid, gid и флаги mode сегмента модифицируются данными пользователя. Так же обновляется и поле shm_ctime. Все изменения производятся после захвата глобального семафора разделяемой памяти и при установленной блокировке.

sys_shmat()

Функция sys_shmat() принимает в качестве параметров ID сегмента разделяемой памяти, адрес по которому должен быть присоединен сегмент (shmaddr) и флаги, котроые описаны ниже.

Если параметр shmaddr не нулевой и установлен флаг SHM_RND, то shmaddr округляется "вниз" до ближайшего кратного SHMLBA. Если shmaddr не кратен SHMLBA и флаг SHM_RND не установлен, то возвращается код ошибки EINVAL.

Производится проверка прав доступа вызывающего процесса, после чего поле shm_nattch сегмента разделяемой памяти увеличивается на 1. Увеличение этого поля гарантирует сегмент разделяемой памяти от ликвидации, пока он присоединен к сегменту памяти процесса. Эти операции выполняются после установки глобальной блокировки разделяемой памяти.

Вызывается функция do_mmap(), которая отображает страницы сегмента разделяемой памяти на виртуальное адресное пространство. Делается это под семафором mmap_sem текущего процесса. В функцию do_mmap() передается флаг MAP_SHARED, а если вызывающий процесс передал ненулевое значение shmaddr, то передается и флаг MAP_FIXED. В противном случае do_mmap() самостоятельно выберет виртуальный адрес для сегмента разделяемой памяти.

ВАЖНО Из do_mmap() будет вызвана функция shm_inc() через структуру shm_file_operations. Эта функция вызывается для установки PID, текущего времени и увеличения счетчика присоединений данного сегмента разделяемой памяти.

После вызова do_mmap() приобретается глобальный семафор и глобальная блокировка разделяемой памяти. Счетчик присоединений затем уменьшается на 1, уменьшение производится потому, что в вызове shm_inc() счетчик был увеличен на 1. Если после уменьшения счетчик стал равен нулю и если сегмент имеет метку SHM_DEST, то вызывается shm_destroy() для ликвидации сегмента разделяемой памяти.

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

sys_shmdt()

На время исполнения функции sys_shmdt() приобретается глобальный семафор разделяемой памяти. В структуре mm_struct текущего процесса отыскивается vm_area_struct, ассоциированная с заданным адресом разделяемой памяти. Если таковая найдена, то вызывается do_munmap(), чтобы отменить отображение сегмента разделяемой памяти в виртуальные адреса.

Важно так же то, что do_munmap() вызывает shm_close(), которая освобождает ресурсы, занятые сегментом разделяемой памяти, если не было выполнено других присоединений.

sys_shmdt() всегда возвращает 0.

Структуры данных механизма разделяемой памяти

struct shminfo64


struct shminfo64 {
        unsigned long   shmmax;
        unsigned long   shmmin;
        unsigned long   shmmni;
        unsigned long   shmseg;
        unsigned long   shmall;
        unsigned long   __unused1;
        unsigned long   __unused2;
        unsigned long   __unused3;
        unsigned long   __unused4;
};


struct shm_info


struct shm_info {
        int used_ids;
        unsigned long shm_tot;  /* общее количество сегментов */
        unsigned long shm_rss;  /* общее количество резидентных сегментов */
        unsigned long shm_swp;  /* общее количество сегментов на свопинге */
        unsigned long swap_attempts;
        unsigned long swap_successes;
};


struct shmid_kernel


struct shmid_kernel /* private to the kernel */
{
        struct kern_ipc_perm    shm_perm;
        struct file *           shm_file;
        int                     id;
        unsigned long           shm_nattch;
        unsigned long           shm_segsz;
        time_t                  shm_atim;
        time_t                  shm_dtim;
        time_t                  shm_ctim;
        pid_t                   shm_cprid;
        pid_t                   shm_lprid;
};


struct shmid64_ds


struct shmid64_ds {
        struct ipc64_perm       shm_perm;       /* права доступа */
        size_t                  shm_segsz;      /* размер сегмента в байтах */
        __kernel_time_t         shm_atime;      /* время последнего присоединения */
        unsigned long           __unused1;
        __kernel_time_t         shm_dtime;      /* время последнего отсоединения */
        unsigned long           __unused2;
        __kernel_time_t         shm_ctime;      /* время последнего изменения */
        unsigned long           __unused3;
        __kernel_pid_t          shm_cpid;       /* pid процесса-создателя */
        __kernel_pid_t          shm_lpid;       /* pid последней операции */
        unsigned long           shm_nattch;     /* количество присоединений */
        unsigned long           __unused4;
        unsigned long           __unused5;
};


struct shmem_inode_info


struct shmem_inode_info {
        spinlock_t      lock;
        unsigned long   max_index;
        swp_entry_t     i_direct[SHMEM_NR_DIRECT]; /* для первых блоков */
        swp_entry_t   **i_indirect; /* doubly indirect blocks */
        unsigned long   swapped;
        int             locked;     /* into memory */
        struct list_head        list;
};


Функции поддержки разделяемой памяти

newseg()

Функция newseg() вызывается, когда возникает необходимость в создании нового сегмента разделяемой памяти. В функцию передаются три параметра - ключ (key), набор флагов (shmflg) и требуемый размер сегмента (size). После выполнения проверок (чтобы запрошенный размер лежал в диапазоне от SHMMIN до SHMMAX и чтобы общее количество сегментов разделяемой памяти не превысило SHMALL) размещает новый дескриптор сегмента. Далее вызывается shmem_file_setup() для создания файла типа tmpfs. Возвращаемый ею указатель записывается в поле shm_file дескриптора сегмента разделяемой памяти. Размер файла устанавливается равным запрошенному размеру сегмента памяти. Дескриптор инициализируется и вставляется в глобальный массив дескрипторов разделяемой памяти. Вызовом shm_buildid() (точнее ipc_buildid()) создается ID сегмента. Этот ID сохраняется в поле id дескриптора сегмента, а так же и в поле i_ino соответствующего inode. Адрес таблицы файловых операций над разделяемой памятью записывается в поле f_op только что созданного файла. Увеличивается значение глобальной переменной shm_tot, которая содержит общее количество сегментов разделяемой памяти в системе. Если в процессе выполнения этих действий ошибок не было, то в вызывающую программу передается ID сегмента.

shm_get_stat()

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

shmem_lock()

shmem_lock() принимает в качестве параметров указатель на дескриптор сегмента разделяемой памяти и флаг требуемой операции - блокирование или разблокирование. Состояние блокировки запоминается в соответствующем inode. Если предыдущее состояние блокировки совпадает с требуемым, то shmem_lock() просто возвращает управление не производя дополнительных действий.

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

  • Функция find_lock_page() блокирует страницу (устанавливает бит PG_locked) и увеличивает счетчик ссылок на страницу. Увеличение счетчика ссылок служит гарантией того, что страница останется в памяти на все время выполнения операции.
  • Если страницу требуется заблокировать, то бит PG_locked сбрасывается, но счетчик ччылок не уменьшается.
  • Если страницу требуется разблокировать, то счетчик ссылок уменьшается дважды, первый раз аннулируется увеличение счетчика, выполненное функцией find_lock_page() и второй раз для ссылки, которая заблокировала страницу в памяти. После этого бит PG_locked сбрасывается.

shm_destroy()

В результате исполнения shm_destroy() общее количество страниц, занятых разделяемой памятью, уменьшается на количество страниц, занятых удаляемым сегментом. Затем вызывается ipc_rmid() (через shm_rmid()) для удаления ID сегмента Страницы памяти в сегменте разблокируются функцией shmem_lock. Счетчик ссылок каждой страницы устанавливается в 0. Вызывается fput(), чтобы уменьшить счетчик f_count соответствующего файла. И в заключение вызывается kfree() для освобождения памяти под дескриптором сегмента.

shm_inc()

shm_inc() устанавливает PID, текущее время и увеличивает счетчик подключений для заданного сегмента разделяемой памяти. Эти действия выполняются после выполнения глобальной блокировки разделяемой памяти.

shm_close()

Функция shm_close() обновляет содержимое полей shm_lprid и shm_dtim и уменьшает счетчик подключений. Если счетчик обнулился, то вызывается shm_destroy() для освобождения ресурсов, занятых сегментом разделяемой памяти. Эти действия выполняются после выполнения глобальной блокировки и получения глобального семафора разделяемой памяти.

shmem_file_setup()

shmem_file_setup() создает файл в файловой системе tmpfs с требуемым именем и размером. Если в системе достаточно ресурсов для размещения файла в памяти, то создается новый dentry в корне tmpfs и размещается новый файловый дескриптор и новый inode типа tmpfs. Затем связывает dentry и inode вызовом d_instantiate() и сохраняет адрес dentry в файловом дескрипторе. Поле i_size inode устанавливается равным размеру файла, а в поле i_nlink заносится 0. Также shmem_file_setup() записывает адрес таблицы файловых операций shmem_file_operations в поле f_op и инициализирует поля f_mode и f_vfsmnt файлового дескриптора. Для завершения инициализации inode вызывается shmem_truncate(). И в случае успешного выполнения всех операций возвращает новый файловый дескриптор.

5.4 Примитивы IPC в Linux

Универсальные примитивы, используемые всеми тремя механизмами IPC

Механизмы семафоров, очередей сообщений и разделяемой памяти в Linux основаны на наборе общих примитивов. Этот раздел посвящен их описанию.

ipc_alloc()

Если запрошен размер памяти больше чем PAGE_SIZE, то вызывает vmalloc(), иначе - kmalloc() с флагом GFP_KERNEL.

ipc_addid()

Когда создается новый набор семафоров, очередь сообщений или сегмент разделяемой памяти, ipc_addid() сначала вызывает grow_ary(), чтобы расширить соответствующий массив дескрипторов, если это необходимо. Затем в массиве дескрипторов первый неиспользуемый элемент. Если таковой найден, то увеличивается счетчик используемых дескрипторов. Затем инициализирует структуру kern_ipc_perm и возвращает индекс нового дескриптора. В случае успеха возврат производится под глобальной блокировкой заданного типа IPC.

ipc_rmid()

ipc_rmid() удаляет дескриптор из массива дескрипторов заданного типа, уменьшает счетчик используемых ID и, в случае необходимости, корректирует значение максимального ID. Возвращает указатель на дескриптор, соответствующий заданному ID.

ipc_buildid()

ipc_buildid() создает уникальный ID для дескриптора заданного типа. ID создается в момент добавления нового элемента IPC (например нового сегмента разделяемой памяти или нового набора семафоров). ID достаточно просто преобразуется в индекс массива дескрипторов. Каждый тип IPC имеет свой порядковый номер, который увеличивается каждый раз, когда добавляется новый дескрипторо. ID создается путем умножения порядкового номера на SEQ_MULTIPLIER и добавления к результату индекса дескриптора в массиве. Этот порядковый номер запоминается в соответствующем дескрипторе.

ipc_checkid()

ipc_checkid() делит заданный ID на SEQ_MULTIPLIER и сравнивает со значением seq в дескрипторе. Если они равны, то ID признается достоверным и функция возвращает 1, в противном случае возвращается 0.

grow_ary()

grow_ary() предоставляет возможность динамического изменения максимального числа идентификаторов (ID) для заданного типа IPC. Однако текущее значение максимального числа идентификаторовне может превышать системного ограничения (IPCMNI). Если настоящий размер массива дескрипторов достаточно велик, то просто возвращает его текущий размер, иначе создает новый массив большего размера, копирует в него данные из старого массива, после чего память под старым массивом освобождается. На время переназначения массива дескрипторов выполняется глобальная блокировка для заданного типа IPC.

ipc_findkey()

ipc_findkey() ищет в массиве дескрипторов объекта ipc_ids заданный ключ. В случае успеха возвращает индекс соответствующего дескриптора, в противном случае возвращает -1.

ipcperms()

ipcperms() проверяет uid, gid и другие права доступа к ресурсу IPC. Возвращает 0 если доступ разрешен и -1 - в противном случае.

ipc_lock()

ipc_lock() выполняет глобальную блокировку заданного типа IPC и возвращает указатель на дескриптор, соответствующий заданному ID.

ipc_unlock()

ipc_unlock() разблокирует заданный тип IPC.

ipc_lockall()

ipc_lockall() выполняет глобальную блокировку требуемого механизма IPC (т.е. разделяемой памяти, семафоров и очередей сообщений).

ipc_unlockall()

ipc_unlockall() снимает глобальную блокировку с требуемого механизма IPC (т.е. разделяемой памяти, семафоров и очередей сообщений).

ipc_get()

ipc_get() по заданному указателю на механизм IPC (т.е. разделяемая память, очереди сообщений или семафоры) и ID возвращает укзатель на соответствующий дескриптор IPC. Обратите внимание, что хотя различные механизмы IPC используют различные типы данных, тем не менее в каждом из них первым элементом указана общая для всех структура kern_ipc_perm. Возвращаемое функцией ipc_get() значение имеет именно этот общий тип данных. Как правило ipc_get() вызывается через функции-обертки (например shm_get()), которые выполняют приведение типов.

ipc_parse_version()

ipc_parse_version() сбрасывает флаг IPC_64 во входном параметре, если он был установлен, и возвращает либо IPC_64, либо IPC_OLD.

Структуры IPC, используемые механизмами семафоров, очередей сообщений и разделяемой памяти

struct kern_ipc_perm

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


/* используется в структурах данных ядра */
struct kern_ipc_perm {
    key_t key;
    uid_t uid;
    gid_t gid;
    uid_t cuid;
    gid_t cgid;
    mode_t mode;
    unsigned long seq;
};


struct ipc_ids

Структура ipc_ids описывает данные, одинаковые для семафоров, очередей сообщений и разделяемой памяти. Существует три глобальных экземпляра этого типа -- semid_ds, msgid_ds и shmid_ds -- для семафоров, очередей сообщений и разделяемой памяти соответственно. Каждый экземпляр содержит семафор sem, предназначенный для разграничения доступа к нему. Поле entries указывает на массив дескрипторов и поле ary - блокировку доступа к этому массиву. Поле seq хранит порядковый номер, который увеличивается всякий раз при создании нового ресурса IPC.


struct ipc_ids {
    int size;
    int in_use;
    int max_id;
    unsigned short seq;
    unsigned short seq_max;
    struct semaphore sem;
    spinlock_t ary;
    struct ipc_id* entries;
};


struct ipc_id

Массив структур ipc_id имеется в каждом экземпляре ipc_ids. Массив размещается динамически и размер его может быть изменен функцией grow_ary(). Иногда этот массив упоминается как массив дескрипторов, так как тип данных kern_ipc_perm используется универсальным функциями IPC для доступа к дескрипторам.


struct ipc_id {
    struct kern_ipc_perm* p;
};



Вперед Назад Содержание


Эта статья еще не оценивалась
Вы сможете оценить статью и оставить комментарий, если войдете или зарегистрируетесь.
Только зарегистрированные пользователи могут оценивать и комментировать статьи.

Комментарии отсутствуют